# ComfyUI-OldTimeRadio v1.0 dependencies
# IMPORTANT: Do NOT pin torch — ComfyUI manages its own torch version
# Pin loosely to avoid conflicts with other node packs

# Core TTS — Bark via transformers. Gemma4Unified (12B writer) requires the
# official architecture implementation introduced in the 5.10 stable line.
# Legacy parler-tts 0.2.2 hard-pins Transformers 4.46.1 and cannot share this
# runtime; Parler is not an OTR dependency and must remain in an isolated env.
transformers>=5.10.4,<6.0

# Audio processing — WAV I/O and DSP
soundfile>=0.12
numpy>=1.24

# Science news fetching (REQUIRED — no fallback, pipeline will hard-fail without it)
feedparser>=6.0

# Article BODY scraping. feedparser gets the headline + teaser; THIS gets the
# methodology and findings the story is actually written from. It was never
# listed here, and on 2026-07-14 it was found simply absent from the venv --
# so _fetch_full_article() had been returning "" forever, every science episode
# had been written from a ~120-character RSS teaser, and the v4 source floor was
# failing runs with a message that blamed the feeds. Not optional.
beautifulsoup4>=4.12

# Tokenizers. FLOOR ONLY -- never re-add an upper bound here.
# 2026-09-07, measured on a Mac mini M4 (ComfyUI Desktop 0.34.6, py3.13, the
# bundled transformers 5.16.1): the old pin read `tokenizers>=0.22,<=0.23`, and
# under PEP 440 `<=0.23` EXCLUDES 0.23.1 -- while transformers 5.16 requires
# `tokenizers>=0.23.1,<0.24.0`. The two are mutually unsatisfiable. ComfyUI
# Manager installs this file ONE LINE AT A TIME (`uv pip install <one req>`), so
# the resolver never saw both constraints together: it silently DOWNGRADED a
# working tokenizers 0.23.2 to 0.22.2 and reported success. transformers then
# failed its own dependency_versions_check on import, which killed
# comfy/sd1_clip.py -> comfy/model_base.py -> execution.py -> main.py.
# ComfyUI did not start AT ALL -- exit code 1, every launch.
# The trap is that ComfyUI Manager IS a ComfyUI extension, so a bricked boot
# means the UI cannot be used to install the fixed version. Recovery needs a
# terminal, which is precisely the audience a registry install is meant to spare.
# transformers already pins tokenizers correctly and is the right owner of that
# constraint; this line only keeps a floor so the dependency stays declared.
tokenizers>=0.22
sentencepiece>=0.1.99

# CUDA-only; the bare line blocked the whole registry install on macOS (2026-09-01
# ship audit). Mirrors the marker in pyproject.toml (alpha.15).
bitsandbytes>=0.42.0; sys_platform != 'darwin'

# 2026-08-25: accelerate was NEVER declared, in either this file or
# pyproject.toml, and it is only a transformers EXTRA -- never a base
# requirement. But nodes/_otr_model_loader.py passes low_cpu_mem_usage=True
# UNCONDITIONALLY (:528), plus device_map / max_memory (:538-546) and
# quantization_config (:543), and transformers routes every one of those
# through accelerate. So a fresh registry install, or a bare
# `pip install -r requirements.txt`, got a writer that cannot load a model.
# This box only ever worked because accelerate 1.13.0 arrived transitively
# from ComfyUI core or another pack -- which is precisely why the gap was
# invisible here and fatal on the 8 GB scratch install that found it.
# Floor is 1.1.0, which is transformers 5.10's own extras floor (NOT the
# 0.26.0 an outside report suggested -- that predates the v5 line).
accelerate>=1.1.0

# Sprint 10A step 3 -- grammar-constrained generation for the Stage 1
# structural-plan call. Pure Python wheel, no compiled deps; works on
# the project's torch 2.10 + CUDA 13 + Blackwell sm_120 + Windows stack
# where prebuilt wheels for other constrained-decode libraries
# (xgrammar, outlines binary backends) are scarce. transformers v5
# compat is provided by nodes/_otr_lmfe_compat.py (the third-party
# integration hard-imports PreTrainedTokenizerBase from the v4 path).
lm-format-enforcer>=0.11.3,<1.0

# Queue item 8 (2026-08-08): spandrel for the shipped `spandrel_esrgan`
# upscale engine. Pinned to 0.4.x — the API this pack calls (ModelLoader
# device kwarg, load_from_file returning ImageModelDescriptor with a
# batch=1 __call__, descriptor.scale/.device/.to) was verified on 0.4.1.
# A 0.5 major bump may reshape those; a follow-up chip re-verifies then.
spandrel~=0.4.1

# 2026-08-22: both were imported UNGUARDED at module scope (pydantic in 21
# files, including _otr_visual_styles.py and _otr_story_brief.py which
# OTR_LedgerScriptWriter pulls in; yaml in _otr_engine_profiles.py) while
# never being declared here. They only worked because lm-format-enforcer
# happens to drag pydantic in transitively -- a resolver change would have
# silently dropped OTR_LedgerScriptWriter and OTR_ImageDirector from the
# node list. Declare what we actually import.
pydantic>=2.0
PyYAML>=6.0

# kokoro is the DEFAULT announcer voice in the shipped canonical workflow, and
# it was never declared here -- so a fresh install reached the first announcer
# line and failed on an import nobody had asked pip to satisfy. Declared
# 2026-08-29 alongside the cold-install fix in `eng_kokoro.py`, where the
# `repo_id` kwarg is now signature-gated: the current PyPI line (0.7.x) does
# not accept it and raised TypeError on the first clip, while newer builds
# (0.9.x) do. Both now work.
#
# BOTH LINES ARE NOW IN `pyproject.toml` and have been since alpha.15/.16.
# This block used to say they were deliberately held back waiting on a version
# bump; that stopped being true three versions ago and the stale note was
# still here at alpha.28. A shipped file must not describe a state the repo
# left behind -- the two files are in sync and a test proves it.
#
# WHAT REMAINS TRUE, and it is the reason the sync is checked by hand: the
# Comfy Registry reads pyproject's STATIC list and editing that file
# AUTO-FIRES a publish (CLAUDE.md 7A), so the two lists can only ever be
# reconciled ON a deliberate bump, never casually.
#
# 2026-09-01, MEASURED ON A CLEAN WINDOWS PORTABLE INSTALL (Python 3.13.14):
# kokoro cannot be pip-installed on Python 3.13 at all. 0.7.16 pins
# numpy==1.26.4 (no 3.13 wheel); 0.8.x/0.9.x and every misaki>=0.7.5 declare
# Requires-Python <3.13, and misaki[en] drags spacy/thinc/blis with no 3.13
# wheels. pip resolves this file all-or-nothing, so the bare kokoro line made
# `pip install -r requirements.txt` fail and install NONE of the 18 packages
# (verified: pip freeze before == after). ComfyUI Desktop and the portable
# build both ship Python 3.13, so that was every mainstream Windows install.
# The marker lets the other 17 install; the kokoro ENGINE then reports itself
# unusable by name and the announcer/character voice falls to the operator's
# dropdown choice (bark installs everywhere). What the 3.13 DEFAULT voice
# should be is an open operator decision -- see docs/GO_FORWARD_PLAN.md
# 2026-09-01. Mirror this marker into pyproject.toml on the next bump.
kokoro>=0.7.16; python_version < "3.13"
# 2026-09-02 (queue item 2): the SAME kokoro voices on Python 3.13, through ONNX
# Runtime instead of torch -- `nodes/_otr_audio_engines/_kokoro_backends.py`. The
# engine stays "kokoro" in every dropdown; it selects the torch package when that
# imports and kokoro-onnx otherwise. The two markers are COMPLEMENTARY on purpose:
# each interpreter gets exactly one backend, and the upper bound is there because
# kokoro-onnx 0.6.x declares Requires-Python <3.14 -- a bare line would repeat
# PBUG-20260901-04 on the day ComfyUI ships 3.14 (pip resolves this file
# all-or-nothing). onnxruntime, phonemizer and the bundled espeak-ng come with it.
# The ONNX model (326 MB) is fetched once at boot by the prefetch, never mid-render.
kokoro-onnx>=0.6.1; python_version >= "3.13" and python_version < "3.14"

# pyloudnorm was declared NOWHERE -- not here, not in pyproject.toml -- while
# `scene_sequencer.py` imports it to measure integrated loudness and master
# every episode to -14 LUFS. Its import sits in a try/except with a
# deliberate fallback to the legacy peak master, so a fresh install does not
# crash; it just quietly produces a QUIETER, differently-mastered episode
# than the one this project tunes and listens to, and says so only in a
# warning line. That is the worst shape a missing dependency can take: no
# error, no failed render, just worse audio for everyone who installed from
# the registry. Declared 2026-08-29. 0.2.0 is what this box has measured on.
pyloudnorm>=0.1.1

# 2026-08-31, found by scripts/otr_venv_audit.py -- the inventory the operator
# asked for after `accelerate` and `feedparser` both turned out to be declared
# but absent from ComfyUI's own interpreter. These three are the reverse case:
# imported by SHIPPING node code and never declared at all, so a fresh install
# has them only by luck of another pack pulling them in.
#
# pycairo: `import cairo` inside nodes/_otr_shared/scope_draw.py and
# eng_viz_mandala.py. Confirmed missing on the rented pod on 2026-08-31 while
# present here, which is exactly how a gap stays invisible on the box that
# authors it.
#
# WINDOWS-ONLY MARKER, 2026-09-03, and the reason is the registry node count.
# pycairo publishes 21 Windows wheels, 1 sdist and ZERO Linux wheels, so on
# Linux pip must build from the sdist and that needs the libcairo2-dev headers.
# Comfy-Org's `node-pack-extract` boots a headless CPU ComfyUI in a Linux
# container, runs `pip install -r requirements.txt` under `set -e`, and reads
# /object_info to publish the pack's node list; that list is what Algolia
# renders as "N Nodes" on the registry card. A pip failure kills the boot, so
# the pack has never produced rows: /versions/2.0.0-alpha.16/comfy-nodes
# returns `{"comfy_nodes": null, "totalNumberOfPages": 0}` where
# comfyui-videohelpersuite/versions/1.7.9 returns real ones. That extractor is
# independent of Flagged/Active -- an admin approval would not populate it.
#
# MEASURED BLAST RADIUS: exactly one function. The only `import cairo` in
# scope_draw.py is at line 629, indented INSIDE `paint_mandala`, so importing
# the module never imports cairo; `freq_bars_green`, `cfr_flags`,
# `find_ffmpeg` and `encode_silent_mp4` are all cairo-free, which leaves the
# scope overlays, caption burn, silent composite and encode sink untouched.
# eng_viz_mandala guards both `load()` and `assert_usable()` and raises
# EngineUnusable naming the pip command, and `viz_mxc_mandala` appears zero
# times in workflows/otr_canonical.json. So Windows installs exactly as before
# and Linux loses only the mandala visualizer, which fails loud with the fix in
# its own message. (The previous comment here claimed every scope overlay
# raises ImportError without it -- that was never true; only the mandala does.)
#
# THIS MARKER IS MIRRORED IN `pyproject.toml` (alpha.17 onward); the note that
# used to say it was still waiting for a bump was stale. The registry reads
# pyproject's static list for DISPLAY, but node-pack-extract installs from THIS
# file, so this is the line that governs the node count.
pycairo>=1.24; sys_platform == 'win32'
# Pillow: image handling in the cloud-image engine and the media canonicaliser.
pillow>=10.0
# aiohttp: imported by __init__.py itself, so it is on the pack's load path.
aiohttp>=3.9

# 2026-09-07: ffmpeg itself, as a pip dependency, and it closes the LAST gap
# between "pip install finished" and "this box can produce an episode".
#
# MEASURED on a Mac mini M4: a full canonical run wrote its 108.8 s master WAV
# -- writer, Kokoro voices and Stable Audio 3 all succeeded -- and then died at
# the mp4 encode with `RuntimeError: ffmpeg not found. Install via: winget
# install ffmpeg`. A Windows package-manager command, raised on macOS, at the
# final step of a 13-minute render, on a box that had no Homebrew.
#
# ffmpeg was never a declared dependency at all: the pack asked PATH, then the
# well-known Homebrew/MacPorts/Windows install directories, and gave up. That
# works only for someone who already installed ffmpeg by hand -- and a
# GUI-launched ComfyUI does not even inherit a login shell's PATH, so it fails
# for some of the people who DID (see the note in _otr_shared/ffmpeg.py).
#
# imageio-ffmpeg publishes prebuilt ffmpeg binaries as ordinary wheels for
# Windows, Linux and macOS (arm64 AND x86_64), and `get_ffmpeg_exe()` fetches
# one if the wheel carries none. So this single line gives every platform a
# working encoder at install time. The bundled macOS arm64 build is ffmpeg 7.1
# with libx264, aac and h264_videotoolbox (Apple's hardware H.264 encoder).
#
# It is resolved LAST in `_otr_shared.ffmpeg.resolve_ffmpeg` -- after the
# operator's OTR_FFMPEG pin, PATH, and real system installs -- so a box with
# its own ffmpeg keeps using it and nothing changes on the boxes that work
# today.
imageio-ffmpeg>=0.5
# ...and ffprobe, which imageio-ffmpeg does NOT ship. Measured 2026-09-07 on a
# Mac mini M4: with only imageio-ffmpeg present, an episode rendered its audio,
# encoded its video frames, and then died in eng_visualizer.py at
#     validate_silent_clip_contract(ffprobe_clip_fields(out_path), fps)
# -- the visualizer encodes a clip and probes it back to verify the contract,
# so shipping ffmpeg alone is only half the dependency.
# ffmpeg-downloader installs a MATCHED ffmpeg + ffprobe pair into one directory
# on Windows, Linux and macOS, which is what `_otr_shared.ffprobe` wants: it
# finds ffprobe as a SIBLING of the ffmpeg this box runs. Light deps
# (platformdirs, tabulate). Binaries are fetched by `ffdl install`, and
# resolve_ffmpeg prefers this pair over the imageio wheel precisely because of
# the sibling; the imageio wheel remains the fallback for plain encoding.
ffmpeg-downloader>=0.5
# PyAV is what the pack MEASURES media with on a box that has ffmpeg and no
# ffprobe (`_otr_shared/ffprobe.py`, the fallback section, 2026-09-11): the
# imageio wheel above ships one binary and `ffdl install` is a manual step
# nothing runs, so a cold install used to die at the first clip probe-back.
# ComfyUI's own requirements.txt already pins av>=17.0.0; declared here so
# the pack states its own need instead of borrowing the host's.
av>=17.0.0
