You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- `src/clippyme/pipeline/` — `main.py` orchestrator (~800 LOC, was 2216), `deepgram_transcribe.py`, `gemini_parser.py`, `gemini_service.py`, plus extracted stages: `transcribe_cache.py`, `download.py`, `scene_detection.py`, `postprocess.py`, `diarization.py`, `hardware.py` (DEVICE/Whisper-model selection), `reframe.py` (cv2/YOLO/MediaPipe core: `process_video_to_vertical` + `SmoothedCameraman`/`SpeakerTracker`/`DetectionSmoother` + face/person detection; `ASPECT_RATIO` is a module global `main` sets per-job via `reframe.ASPECT_RATIO = ...`), pure-math `reframe_ops.py`, and `media_probe.py` (cv2-free ffprobe wrappers + pure A/V-sync helpers: VFR detection, stream `start_time` compensation, frame-rate parsing — host-unit-tested; wired into `reframe.py`/`main.py` to fix VFR drift + YouTube audio desync. Ported from `kamilstanuch/Autocrop-vertical`, see `docs/autocrop-vertical-analysis.md`), and `texttiling_ops.py` (cv2-free, dependency-light **lexical TextTiling** topic segmentation — gap/smooth/depth/boundary math ported from `ClipsAI/clipsai` but with a classic Hearst bag-of-words front-end instead of neural sentence embeddings, so **zero new deps**; host-unit-tested. Powers `main.build_texttiling_fallback`, the smarter **no-AI whole-video fallback**: when Gemini viral detection is unavailable/fails, the transcript is topic-segmented into several clips — shaped exactly like `get_viral_clips` output (`viral_score=0` + heuristic `viral_reason`) so they flow through the identical clip loop — instead of dumping the whole source as one vertical clip. Whole-video render remains the final safety net. Ported from `ClipsAI/clipsai`, see `docs/clipsai-analysis.md`). `main` re-exports the reframe classes for back-compat. Verify pipeline changes with `docker compose run --rm -u root backend sh -lc "pip install -q pytest && pytest -m integration"`.
**video-use-inspired improvements** (8 changes ported from the `/video-use` skill's Hard Rules + superpowers verification; pure cores host-tested, ffmpeg paths integration-tested in `tests/domain/test_ffmpeg_render_integration.py`):
18
+
-**#1 Audio-pop fix** — `pipeline/cut_ops.audio_fade_filter` adds a 30ms `afade` in/out at every segment boundary in `smartcut._render_with_ffmpeg` (the ffmpeg-concat fallback). The auto-editor v3 primary path cuts at silence keep-segments so it doesn't pop.
19
+
-**#2 Word-boundary snap** — `pipeline/cut_ops.snap_clip_to_words`/`flatten_words` snap each Gemini clip `[start,end]` to the nearest transcript word boundary + 50/80ms pad, applied in `main.py` BEFORE the metadata write so subtitles/Smart Cut stay aligned. No-op when the transcript lacks word timing.
20
+
-**#3 Self-eval QA** — `domain/clip_qa.evaluate_clip_qa` + `compose._self_eval` ffprobe the final composed clip and log duration/audio/empty-file issues (soft check, never raises). This also subsumes the #6 overlay/subtitle z-order sanity concern.
21
+
-**#4 Colour grade** — `domain/grade.py` compose layer (`GRADE_PRESETS`: warm_cinematic/cool_crisp/neutral_punch/vivid_pop), runs FIRST in compose so overlays keep authored colour. Toggle in both Create (`opts.gradePreset`) and EditClipModal (Grade tab).
22
+
-**#5 Animated hooks** — `hooks.build_hook_overlay_filter` animates the hook PNG with a fade + ease-out-cubic (`pow(1-p,3)`) slide-up entrance; `style.animate` toggle (synced in `data.js` + `hooks.py` defaults). animate=False is byte-identical to the legacy static overlay.
23
+
-**#7 Conversational trim** — `domain/clip_edit_ai` + `POST /api/edit-ai/{job}/{clip}`: a plain-English instruction → Gemini → spans to cut, fed through the existing `drop_ranges` machinery. Frontend "AI trim" input in the EditClipModal Trim tab.
24
+
-**#8 Cross-job taste memory** — `dashboard/src/lib/taste.js` records kept(publish)/discarded(remove) clip signals and distils a one-line hint appended to the Gemini `instructions` on the next job (rides the existing channel, no backend change). 120-event rolling window.
@@ -171,7 +180,7 @@ The redesign uses hand-rolled primitives in `dashboard/src/redesign/primitives.j
171
180
-**Per-job LLM model override**: the Gemini model for viral detection is global (Settings → `GEMINI_MODEL`) but can be overridden **per job** via `ProcessRequest.model`/`BatchRequest.model` → `build_main_cmd(model=...)` → `--model` CLI arg → `main.py` sets `os.environ["GEMINI_MODEL"]` before `get_viral_clips` (mirrors the `--language` override). Validated at the boundary by `job_results.GEMINI_MODEL_RE` (`^gemini-[A-Za-z0-9.\-]{1,64}$` — blocks argv injection; allows future `gemini-3*`). Frontend: a quick-picker in MediaInput's Clip Options (`preselections.model`) + the live-discovery dropdown in Settings (`/api/config/models`, allow-list prefixes `gemini-2.5-`/`gemini-3` in `gemini_service.py`). Unknown models fall through to a `$0.00` "Pricing not available" cost note (`main.py:MODEL_PRICING`).
172
181
-**Batch processing**: `POST /api/batch` accepts up to 20 URLs, creates one job per URL, and returns the list of `job_id`s. The frontend polls each job individually via `GET /api/status/{job_id}` and aggregates progress client-side. Supports `reframe_mode` parameter.
173
182
-**Mixed batch (URLs + files)**: The frontend `useJobSubmission.handleBatchProcess` supports both. URLs are submitted in one shot to `/api/batch`; each file is submitted individually to `/api/process`. The hook then unifies polling across all returned `job_id`s using `/api/status/{job_id}`, aggregating progress until every job reaches a terminal state. No backend change is needed for mixed batches.
174
-
-**Compose endpoint**: `POST /api/compose/{job_id}/{clip_index}` accepts `toggles` (smartcut/hook/subtitles/**logo** booleans), `hook_params`, `subtitle_params`, **`logo_params`**. Composes layers in order: **Subtitles → Smart Cut → Hook → Logo** (subtitle-drift-safe; logo absolutely last so the brand mark sits on top of every other layer). Returns `composed_url`. Cleans up intermediate files.
183
+
-**Compose endpoint**: `POST /api/compose/{job_id}/{clip_index}` accepts `toggles` (**grade**/smartcut/hook/subtitles/**logo** booleans), `hook_params`, `subtitle_params`, **`logo_params`**, **`grade_params`** (`{preset}`). Composes layers in order: **Grade → Subtitles → Smart Cut → Hook → Logo** (grade first so overlays keep authored colour; subtitle-drift-safe; logo absolutely last so the brand mark sits on top of every other layer). After the final copy, `compose._self_eval` ffprobes the output and logs any QA issue (soft). Returns `composed_url`. Cleans up intermediate files.
175
184
-**Brand assets** (client deliverables — e.g. ASCENSORE): a persistent **logo overlay** + **custom subtitle fonts**, both managed in Settings → *Brand assets*.
176
185
-**Logo** (`domain/logo.py:add_logo_to_video`): ffmpeg `overlay` of an uploaded transparent PNG (`data/logo.png`, set via `POST /api/config/logo`). Placement is a position preset (7 anchors: corners + edge-centers + center) × size preset (`S/M/L` → 0.12/0.18/0.26 of frame width) × opacity. Geometry helper `logo_overlay_xy` is pure (host-tested, no ffmpeg). The compose **logo** layer reads `LOGO_PATH` and skips silently if no logo is uploaded.
177
186
-**Custom fonts** (`subtitles.py:list_available_fonts` / `effective_fonts_dir`): user TTF/OTF uploads (`POST /api/config/fonts`) land in the writable `data/fonts/` volume; `effective_fonts_dir()` seeds it with copies of the bundled `fonts/` faces so a single `fontsdir` serves both (libass's `ass`/`subtitles` filter takes only one dir). Both burn branches pass `fontsdir`, so an uploaded face (e.g. a licensed **Stratos**) resolves at burn time. Upload validates the sfnt magic + the strict `_FONT_NAME_RE` (the stem becomes the libass font name injected into the ASS style). Frontend font dropdowns live-merge bundled + uploaded via `hooks/useFontList.js`.
0 commit comments