Skip to content

Commit 23ff8f9

Browse files
committed
Add support for embedding title/description/chapters in final mkv files
1 parent d28516c commit 23ff8f9

File tree

1 file changed

+41
-13
lines changed

1 file changed

+41
-13
lines changed

ffmpeg_editlist.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ def test_seconds():
124124
assert seconds('1:30') == 90
125125
assert seconds('1:1:30') == 3690
126126
assert seconds('1:1:30.5') == 3690.5
127-
def humantime(x):
127+
def humantime(x, show_hour=False):
128128
hou = x // 3600
129129
min = (x % 3600) // 60
130130
sec = x % 60
131-
if hou:
131+
if hou or show_hour:
132132
return "%d:%02d:%02d"%(hou, min, floor(sec))
133133
return "%02d:%02d"%(min, floor(sec))
134134
def test_humantime():
@@ -238,6 +238,8 @@ def main(argv=sys.argv[1:]):
238238
help='Number of encoding threads. Default: unset, autodetect')
239239
parser.add_argument('--wait', action='store_true',
240240
help='Wait after each encoding (don\'t clean up the temporary directory right away')
241+
parser.add_argument('--no-mkv-chapters', action='store_false', default=True, dest='mkv_chapters',
242+
help="Don't try to encode the chapters and description into the mkv file. This requires mkvnixtools to be installed")
241243
parser.add_argument('--list', action='store_true',
242244
help="Don't do anything, just list all outputs that would be processed (and nothing else)")
243245

@@ -494,22 +496,15 @@ def main(argv=sys.argv[1:]):
494496
LOG.info(shell_join(cmd))
495497
if not args.check:
496498
subprocess.check_call(cmd)
497-
# We need another copy, since ffmpeg detects output based on filename. Yet for atomicness, we need a temporary filename for the temp part
498-
if not args.check:
499-
with atomic_write(output) as tmp_output:
500-
shutil.move(tmpdir_out, tmp_output)
501499

502-
# Subtitles
503-
if args.srt:
504-
srt_output = os.path.splitext(output)[0] + '.srt'
505-
open(srt_output, 'w').write(srt.compose(subtitles))
500+
# Create the video properties/chapters/etc (needs to be done before
501+
# making the final mkv because it gets encoded into the mkv file).
506502

507503
# Print table of contents
508504
import pprint
509505
LOG.debug(pprint.pformat(segment_list))
510506
LOG.debug(pprint.pformat(TOC))
511507

512-
513508
video_description = [ ]
514509
if segment.get('title'):
515510
title = segment['title']
@@ -521,12 +516,18 @@ def main(argv=sys.argv[1:]):
521516
video_description.extend([segment['description'].strip().replace('\n', '\n\n')])
522517
# Print out the table of contents
523518
#video_description.append('\n')
519+
# Making chapters
524520
toc = [ ]
525-
for seg_n, time, name in TOC:
521+
chapter_file = Path(tmpdir) / 'chapters.txt'
522+
chapter_file_f = open(chapter_file, 'w')
523+
for i, (seg_n, time, name) in enumerate(TOC):
526524
LOG.debug("TOC entry %s %s", time, name)
527525
new_time = map_time(seg_n, segment_list, time)
528526
print(humantime(new_time), name)
529527
toc.append(f"{humantime(new_time)} {name}")
528+
chapter_file_f.write(f'CHAPTER{i+1:02d}={humantime(new_time, show_hour=True)}.000\n')
529+
chapter_file_f.write(f'CHAPTER{i+1:02d}NAME={name}\n')
530+
chapter_file_f.close()
530531
if toc:
531532
video_description.append('\n'.join(toc))
532533

@@ -535,9 +536,36 @@ def main(argv=sys.argv[1:]):
535536
video_description.append(workshop_description.replace('\n', '\n\n').strip())
536537

537538
if video_description:
538-
with atomic_write(os.path.splitext(str(output))[0]+'.info.txt', 'w') as toc_file:
539+
video_description_file = os.path.splitext(str(output))[0]+'.info.txt'
540+
with atomic_write(video_description_file, 'w') as toc_file:
539541
open(toc_file, 'w').write('\n\n'.join(video_description))
540542

543+
# mkv chapters
544+
cmd = ['mkvpropedit', tmpdir_out,
545+
'--set', f'title={title}',
546+
*(['--chapters', str(chapter_file),] if toc else []),
547+
*(['--attachment-name', 'description', '--add-attachment', video_description_file] if video_description else []),
548+
]
549+
print(cmd)
550+
if not args.check and args.mkv_chapters:
551+
subprocess.check_call(cmd)
552+
553+
554+
# Finalize the video
555+
556+
# We need another copy, since ffmpeg detects output based on
557+
# filename. Yet for atomicness, we need a temporary filename for
558+
# the temp part
559+
if not args.check:
560+
with atomic_write(output) as tmp_output:
561+
shutil.move(tmpdir_out, tmp_output)
562+
563+
# Subtitles
564+
if args.srt:
565+
srt_output = os.path.splitext(output)[0] + '.srt'
566+
open(srt_output, 'w').write(srt.compose(subtitles))
567+
568+
541569
# Print out covered segments (for verification purposes)
542570
for seg_n, time in covers:
543571
new_time = map_time(seg_n, segment_list, time)

0 commit comments

Comments
 (0)