Skip to content

Commit 49b0211

Browse files
committed
fix(ffmpeg): centralize concat list writers
1 parent 577e02b commit 49b0211

40 files changed

Lines changed: 194 additions & 189 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ record also lives in the git commit messages.
135135
names.** The concat-demuxer escaping used `\'` (rejected by ffmpeg) and the
136136
default text codec (cp1252 on Windows). Added `write_concat_list()` (UTF-8 +
137137
the correct `'\''` idiom). *Verified: apostrophe and Cyrillic/CJK names.*
138+
- All core concat-demuxer writers found by the repo-wide scan now use
139+
`write_concat_list()` or the shared concat-line formatter instead of local
140+
platform-codec path writes.
138141
- New shared escaping helpers in `opencut/helpers.py`
139142
(`escape_filter_path`, `escape_drawtext`, `write_concat_list`) with a
140143
string-level regression test (`tests/test_ffmpeg_escaping.py`) locking the

ROADMAP-COMPLETED.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Completed Roadmap Reference
2+
3+
Completed roadmap work is recorded in `CHANGELOG.md` and git history.
4+
5+
`ROADMAP.md` is the only source of truth for incomplete actionable work.

ROADMAP.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ history, not here.
2323
Why: six full :root token redefinitions (12, 4462, 5386, 13215, 15466, 17214), two divergent html.theme-light blocks (16701 vs 17628), three different :focus-visible rules, and a triplicated prefers-reduced-motion block — the effective theme is "whatever the last pass overrode" and ~⅓ of 18k lines is dead weight. Consolidate to one token block per theme; then retokenize the ~340 stray hex literals.
2424
Where: extension/com.opencut.panel/client/style.css
2525

26-
- [ ] P2 — Concat lists in ~17 feature modules still use unescaped, platform-codec writes
27-
Why: write_concat_list() now exists in helpers.py (UTF-8 + correct quote escaping) and the merge path uses it, but beat_cuts, ai_intro_gen, auto_dub_pipeline, photo_montage, instant_replay, cursor_zoom, event_recap, stream_highlights, video_360, generative_extend, auto_montage, glitch_effects, hook_generator, fit_to_fill, paper_edit, guest_compilation, stringout_reel still hand-roll open(path,"w") lists that break on apostrophes/non-ASCII names.
28-
Where: opencut/core/* (grep: "file '" writes); replace with helpers.write_concat_list
29-
3026
- [ ] P2 — Drawtext escaping is duplicated and wrong about apostrophes in ~10 modules
3127
Why: the local _escape_drawtext copies use \' inside single quotes (apostrophes silently dropped, graph can be mangled); helpers.escape_drawtext (ffmpeg-verified, expansion=none contract) should replace them.
3228
Where: opencut/core/caption_styles.py:365-372, click_overlay.py, news_ticker.py, quiz_overlay.py, telemetry_overlay.py, callout_gen.py, audiogram.py, brand_kit.py, end_screen.py, guest_compilation.py

opencut/core/ai_intro_gen.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
output_path,
2626
require_package,
2727
run_ffmpeg,
28+
write_concat_list,
2829
)
2930

3031
logger = logging.getLogger("opencut")
@@ -715,9 +716,7 @@ def _prepend_intro(intro_path: str, main_video: str, out_path: str) -> str:
715716
_fd, concat_file = tempfile.mkstemp(suffix=".txt", prefix="intro_concat_")
716717
os.close(_fd)
717718
try:
718-
with open(concat_file, "w") as f:
719-
f.write(f"file '{intro_path}'\n")
720-
f.write(f"file '{main_video}'\n")
719+
write_concat_list([intro_path, main_video], concat_file)
721720

722721
cmd = (FFmpegCmd()
723722
.option("f", "concat")

opencut/core/auto_chapter_art.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from opencut.helpers import (
2323
FFmpegCmd,
24+
_concat_file_line,
2425
get_video_info,
2526
require_package,
2627
run_ffmpeg,
@@ -645,13 +646,13 @@ def _assemble_cards_to_video(card_paths: List[str], chapters: List[ChapterInfo],
645646
_fd, concat_file = tempfile.mkstemp(suffix=".txt", prefix="chapter_concat_")
646647
os.close(_fd)
647648
try:
648-
with open(concat_file, "w") as f:
649+
with open(concat_file, "w", encoding="utf-8") as f:
649650
for i, card_path in enumerate(card_paths):
650-
f.write(f"file '{card_path}'\n")
651+
f.write(_concat_file_line(card_path))
651652
f.write(f"duration {config.card_duration}\n")
652653
# Repeat last frame to avoid truncation
653654
if card_paths:
654-
f.write(f"file '{card_paths[-1]}'\n")
655+
f.write(_concat_file_line(card_paths[-1]))
655656

656657
cmd = (FFmpegCmd()
657658
.option("f", "concat")

opencut/core/auto_dub_pipeline.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
get_video_info,
2727
output_path,
2828
run_ffmpeg,
29+
write_concat_list,
2930
)
3031

3132
logger = logging.getLogger("opencut")
@@ -601,9 +602,7 @@ def _composite_dubbed_audio(
601602
# Concatenate all TTS to single track
602603
concat_path = os.path.join(work_dir, "concat_tts.wav")
603604
list_path = os.path.join(work_dir, "concat_list.txt")
604-
with open(list_path, "w") as f:
605-
for seg in tts_segments_with_audio:
606-
f.write(f"file '{seg.tts_audio_path}'\n")
605+
write_concat_list((seg.tts_audio_path for seg in tts_segments_with_audio), list_path)
607606
concat_cmd = (FFmpegCmd()
608607
.option("f", "concat")
609608
.option("safe", "0")

opencut/core/auto_montage.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
get_video_info,
3131
output_path,
3232
run_ffmpeg,
33+
write_concat_list,
3334
)
3435

3536
logger = logging.getLogger("opencut")
@@ -416,9 +417,7 @@ def assemble_montage(
416417

417418
# Concat all segments
418419
concat_file = os.path.join(tmp_dir, "concat.txt")
419-
with open(concat_file, "w") as f:
420-
for sp in segment_files:
421-
f.write(f"file '{sp}'\n")
420+
write_concat_list(segment_files, concat_file)
422421

423422
concat_video = os.path.join(tmp_dir, "concat_video.mp4")
424423
cmd = [

opencut/core/beat_cuts.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@
1717
from dataclasses import dataclass, field
1818
from typing import Callable, List, Optional
1919

20-
from opencut.helpers import get_ffmpeg_path, get_ffprobe_path, get_video_info, output_path, run_ffmpeg
20+
from opencut.helpers import (
21+
get_ffmpeg_path,
22+
get_ffprobe_path,
23+
get_video_info,
24+
output_path,
25+
run_ffmpeg,
26+
write_concat_list,
27+
)
2128

2229
logger = logging.getLogger("opencut")
2330

@@ -421,9 +428,7 @@ def assemble_beat_synced(
421428

422429
# Write concat list
423430
concat_file = os.path.join(tmp_dir, "concat.txt")
424-
with open(concat_file, "w") as f:
425-
for sp in segment_files:
426-
f.write(f"file '{sp}'\n")
431+
write_concat_list(segment_files, concat_file)
427432

428433
# Concat video segments
429434
concat_video = os.path.join(tmp_dir, "concat_video.mp4")

opencut/core/beat_sync_edit.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@
1919
from dataclasses import dataclass, field
2020
from typing import Callable, Dict, List, Optional, Tuple
2121

22-
from opencut.helpers import get_ffmpeg_path, get_ffprobe_path, get_video_info, output_path, run_ffmpeg
22+
from opencut.helpers import (
23+
get_ffmpeg_path,
24+
get_ffprobe_path,
25+
get_video_info,
26+
output_path,
27+
run_ffmpeg,
28+
write_concat_list,
29+
)
2330

2431
logger = logging.getLogger("opencut")
2532

@@ -585,10 +592,7 @@ def assemble_beat_sync(
585592
fd, list_path = tempfile.mkstemp(suffix="_beatconcat.txt")
586593
os.close(fd)
587594
try:
588-
with open(list_path, "w", encoding="utf-8") as f:
589-
for p in segment_paths:
590-
safe_p = p.replace("\\", "/").replace("'", "'\\''")
591-
f.write(f"file '{safe_p}'\n")
595+
write_concat_list(segment_paths, list_path)
592596

593597
# Determine output path
594598
if output_path_val:

opencut/core/ceremony_autoedit.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
get_video_info,
1919
output_path,
2020
run_ffmpeg,
21+
write_concat_list,
2122
)
2223

2324
logger = logging.getLogger("opencut")
@@ -421,10 +422,7 @@ def auto_edit_ceremony(
421422
on_progress(96, "Concatenating final video...")
422423

423424
concat_path = os.path.join(tmp_dir, "concat.txt")
424-
with open(concat_path, "w", encoding="utf-8") as f:
425-
for seg in segment_files:
426-
safe = seg.replace("\\", "/").replace("'", "'\\''")
427-
f.write(f"file '{safe}'\n")
425+
write_concat_list(segment_files, concat_path)
428426

429427
cmd = (FFmpegCmd()
430428
.option("f", "concat")

0 commit comments

Comments
 (0)