Repository navigation
Add save-chapters command for FFmetadata chapter export (#323) - #599
Davidvisualstudio007 wants to merge 6 commits into
Conversation
|
|
||
| The chapters can be added to a video with ffmpeg without re-encoding, e.g.: | ||
|
|
||
| ffmpeg -i video.mp4 -i video-Chapters.txt -map_metadata 1 -codec copy output.mp4 |
There was a problem hiding this comment.
Should we be using -map_chapters 1 here instead of -map_metadata 1?
The same command is also in the write_scene_list_ffmetadata docstring, the roundtrip test in tests/test_output.py, and the PR description.
There was a problem hiding this comment.
ohh mb, didn't realise -map_metadata copies the other metadata too. Switched it to -map_chapters everywhere (help text, docstring, test and the PR description).
| ``save-chapters`` | ||
| ======================================================================== |
There was a problem hiding this comment.
Could you merge main, run python docs/generate_cli_docs.py, and commit what it changes under docs/? It should handle most of these docs changes for you now.
There was a problem hiding this comment.
Done, rebased and ran the script. It also flagged that save-chapters was missing from CONCORDANCE and the API page, so I added it to both.
| write_scene_list_ffmetadata( | ||
| output_path=chapters_path, | ||
| scene_list=scenes, | ||
| title_format=title, |
There was a problem hiding this comment.
save-edl and save-otio substitute $VIDEO_NAME in their title before passing it on. Should we do the same here, so -t '$VIDEO_NAME - Scene $SCENE_NUMBER' works?
There was a problem hiding this comment.
Ah nice, didn't think of that. Added it, and changed the CLI test to use "$VIDEO_NAME - Shot $SCENE_NUMBER" so it should be sweettt
| def write_scene_list_ffmetadata( | ||
| output_path: StrPath | BytePath | ty.TextIO, | ||
| scene_list: SceneList, | ||
| title_format: str = "Scene $SCENE_NUMBER", |
There was a problem hiding this comment.
Let's call this title_format_template to be more clear that it's just a template, and for consistency w/ other commands args.
| start_ms = round(_frame_timecode_seconds(start) * 1000) | ||
| end_ms = round(_frame_timecode_seconds(end) * 1000) |
There was a problem hiding this comment.
Would math.ceil() be safer? Frame 12 at 24000/1001 fps (500.5 ms) would make a good test case since it should equal a start of 501.
There was a problem hiding this comment.
Makes sense, switched to math.ceil and added your frame 12 example as a test, it comes out as 501 now.
|
Thanks for the PR. Left you a few comments. Also I'll fix up the docs generator script to make it easier to update, so you will need to rebase this on main again, sorry. |
| for char in ("\\", "=", ";", "#", "\n"): | ||
| value = value.replace(char, "\\" + char) | ||
| return value |
There was a problem hiding this comment.
We should also escape \r, and append a space when the escaped value ends in a backslash.
There was a problem hiding this comment.
Added both, plus tests for each. Missed the trailing backslash one, thanks 4 the catch
918b2c7 to
6f25206
Compare
|
Thanks for going through it! much appreciated. Rebased on main and sorted all the comments. I also moved the changelog entry to 0.8 since 0.7.3 is out now. LMK if anything needs changing, Cheers! |
MuhammadBilalKhan267
left a comment
There was a problem hiding this comment.
Found one minor issue with title substitution.
| assert context.video_stream is not None | ||
| video_name = context.video_stream.name | ||
| chapters_path = get_and_create_path( | ||
| Template(filename).safe_substitute(VIDEO_NAME=video_name), |
There was a problem hiding this comment.
Could we pass the video name to the writer and substitute both variables in one pass per scene? The current two passes can interpret $SCENE_NUMBER inside the video name or an escaped literal as a placeholder.
There was a problem hiding this comment.
Done. The writer now takes a video_name argument (same as write_scene_list_fcpx) and fills in both in one pass. Added tests for a video name with a placeholder in it and for $$ escapes.
Closes #323
This adds a
save-chapterscommand that saves the detected scenes as chapters in FFmetadata format. You can then add them to a video with ffmpeg without re-encoding:What I changed:
write_scene_list_ffmetadata()toscenedetect.output. I based it on the EDL writer, so it works with a file path or an open filesave-chapterscommand with-f,-tand-ooptions, and a[save-chapters]section in the config.-tlets you change chapter names using$VIDEO_NAMEand$SCENE_NUMBERsave-chaptersto the docs generator's CONCORDANCE and the API docs pageFor the timing I used
_frame_timecode_seconds(), so it stays accurate at 23.976 and 29.97 and doesn't drift like the EDL issue in #593. Times round up to the next millisecond so a chapter never starts before its first frame.I added 25 tests in
test_output.pyand 2 intest_cli.py, including one that actually runs ffmpeg to add the chapters to a video and checks they're there. Ruff passes and the output and CLI tests all pass on the latest main. I also tried it on goldeneye.mp4 and the chapters showed up in VLC.I left YouTube style chapters out for now. YouTube needs the first chapter at 00:00, at least 3 chapters, and each one at least 10 seconds long, so I wasn't sure how you'd want short scenes handled.