Refactoring: extract cutting engine inside a dedicated module.

This commit is contained in:
Frédéric Tronel
2026-08-30 17:19:03 +02:00
parent 0345293664
commit ae1a040590
6 changed files with 406 additions and 303 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ from pathlib import Path
import coloredlogs
from tscut.config import ProcessingOptions
from tscut.models import ProcessingOptions
from tscut.exceptions import TSCutError
from tscut.pipeline import process_recording
from tscut.tools.discovery import check_required_tools
-23
View File
@@ -1,23 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2026 Frédéric Tronel
from dataclasses import dataclass
from datetime import timedelta
from pathlib import Path
@dataclass
class ProcessingOptions:
input_file: Path
output_file: Path
parts: list[tuple[timedelta, timedelta]]
tools_paths: dict[str, str]
all_optional_tools: bool
framerate: int|None = None
threshold: int = 0
coarse: bool = False
subtitles_ocr: bool = True
verbose: bool = False
dump_memory: bool = False
keep_temporaries: bool = False
+311
View File
@@ -0,0 +1,311 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2026 Frédéric Tronel
import logging
from typing import IO, Any
from datetime import timedelta
from shutil import copyfile, move
import hexdump
from tscut.exceptions import InvalidMediaError
from tscut.models import PreparedMedia, ProcessingOptions, CutResult
from tscut.tools.ffprobe import (
find_subtitles_tracks,
get_format,
get_frame_rate,
get_movie_duration,
get_nearest_iframe,
get_streams,
)
from tscut.tools.ffmpeg import extract_all_streams, ffmpeg_convert
from tscut.h264.avc import get_avc_config_from_h264, parse_codec_private
from tscut.tools.mkvtoolnix import (
change_codec_private_data,
extract_mkv_part,
extract_track_from_mkv,
get_codec_private_data_from_mkv,
merge_mkvs,
remove_video_tracks_from_mkv,
remux_srt_subtitles,
)
from tscut.tools.timeframe import get_ts_frame
from tscut.tscut import (
concatenate_h264_parts,
concatenate_h264_ts_parts
)
from tscut.matroska.codec import dump_codec_private_data
logger = logging.getLogger(__name__)
def cut_recording(media: PreparedMedia, options: ProcessingOptions,
temporaries: list[IO[Any]]) -> CutResult:
# Pour chaque portion
partnum = 0
mkvparts = []
h264parts = []
h264_ts = []
checks = []
pos = timedelta()
other_avc_configs = []
mkvfilename = media.basename+'.mkv'
for ts1, ts2 in options.parts:
# TODO: translate comment in english
# Trouver l'estampille de la trame 'I' la plus proche (mais postérieure) au début
# de la portion.
# Trouver l'estampille de la trame 'I' la plus proche (mais antérieure) à la fin
# de la portion.
# On a alors
# debut ----- trame --------- trame --------- fin fin+1
# 'B/P' 'B/P'* 'I' 'I' 'B/P'* 'B/P' 'I/B/P'
# Si la trame de début est déjà 'I', il n'y a rien à faire.
# Sinon on extrait les trames 'B' ou 'P' depuis le début jusqu'à la trame 'I' non incluse.
# Si la trame de fin précède une trame I, on n'a rien à faire.
# Sinon on extrait toutes les trames depuis la dernière trame I jusqu'à la trame de fin.
partnum = partnum + 1
# Get the nearest I-frame whose timestamp is greater or equal to the beginning.
head_frames = get_nearest_iframe(options.tools_paths['ffprobe'], media.movie, ts1,
before=False)
if head_frames is None:
raise InvalidMediaError("Impossible to retrieve first I-frame")
# Get the nearest I-frame whose timestamp ...
# TODO: wrong here ...
tail_frames = get_nearest_iframe(options.tools_paths['ffprobe'], media.movie,
ts2, before=True)
if tail_frames is None:
raise InvalidMediaError("Impossible to retrieve last I-frame")
nb_head_frames, head_iframe = head_frames
nb_tail_frames, tail_iframe = tail_frames
logger.info("Found %d frames between beginning of current part and first I-frame",
nb_head_frames)
logger.info("Found %d frames between last I-frame and end of current part",
nb_tail_frames)
head_iframe_ts = get_ts_frame(head_iframe)
if head_iframe_ts is None:
raise InvalidMediaError("Impossible to retrieve timestamp of first i-frame.")
tail_iframe_ts = get_ts_frame(tail_iframe)
if tail_iframe_ts is None:
raise InvalidMediaError("Impossible to retrieve timestamp of final i-frame.")
checks.append(pos+head_iframe_ts-ts1)
subparts = []
# TODO: separate pipeline processing between coarse and not fine grain options.
# if args.coarse:
# do_coarse_processing(ffmpeg=paths['ffmpeg'], ffprobe=paths['ffprobe'], input_file=mkv,
# begin=ts1, end=head_iframe_ts, nb_frames=nb_head_frames-1,
# frameRate=frameRate, files_prefix='part-%d-head' % (partnum),
# streams=streams, width=width, height=height,
# temporaries=temporaries, dump_mem_fd=args.dump)
# else:
# doFineGrainProcessing(ffmpeg=paths['ffmpeg'], ffprobe=paths['ffprobe'],
# input_file=mkv, begin=ts1, end=head_iframe_ts,
# nb_frames=nb_head_frames-1, frameRate=frameRate,
# files_prefix='part-%d-head' % (partnum), streams=streams,
# width=width, height=height, temporaries=temporaries,
# dump_mem_fd=args.dump)
if (not options.coarse) and (nb_head_frames > options.threshold):
# We extract all frames between the beginning upto the frame that immediately preceeds
# the I-frame.
h264_head, h264_head_ts, mkv_head = extract_all_streams(
ffmpeg_path=options.tools_paths['ffmpeg'],
ffprobe_path=options.tools_paths['ffprobe'],
input_file=media.movie, begin=ts1,
end=head_iframe_ts,
nb_frames=nb_head_frames-1,
framerate=media.framerate,
files_prefix=f'part-{partnum:d}-head',
streams=media.streams, width=media.width,
height=media.height,
temporaries=temporaries,
dump_mem_fd=options.dump_memory)
# If we are not at an exact boundary:
if mkv_head is not None:
subparts.append(mkv_head)
if h264_head is not None:
avcconfig = get_avc_config_from_h264(h264_head)
other_avc_configs.append(avcconfig)
h264parts.append(h264_head)
if h264_head_ts is not None:
h264_ts.append(h264_head_ts)
# Creating MKV file that corresponds to current part between I-frames
# Internal video with all streams (video, audio and subtitles)
internal_mkv_name = f'part-{partnum:d}-internal.mkv'
# Internal video stream as a raw H264 stream
internal_h264_name = f'part-{partnum:d}-internal.h264'
# Internal video timestamps
internal_h264_ts_name = f'part-{partnum:d}-internal-ts.txt'
# Internal video with only audio and subtitles streams
internal_novideo_mkv_name = f'part-{partnum:d}-internal-novideo.mkv'
try:
internal_mkv = open(internal_mkv_name, 'wb+')
except OSError:
logger.error('Impossible to create file: %s', internal_mkv_name)
exit(-1)
try:
internal_novideo_mkv = open(internal_novideo_mkv_name, 'wb+')
except OSError:
logger.error('Impossible to create file: %s', internal_novideo_mkv_name)
exit(-1)
try:
internal_h264 = open(internal_h264_name, 'wb+')
except OSError:
logger.error('Impossible to create file: %s', internal_h264_name)
exit(-1)
try:
internal_h264_ts = open(internal_h264_ts_name, 'w+', encoding='utf8')
except OSError:
logger.error('Impossible to create file: %s', internal_h264_ts_name)
exit(-1)
# logger.info('Merge header, middle and trailer subpart into: %s' % internal_mkv_name)
# Extract internal part of MKV
extract_mkv_part(mkvmerge_path=options.tools_paths['mkvmerge'], input_file=media.movie,
output_file=internal_mkv, begin=head_iframe_ts, end=tail_iframe_ts)
# Extract video stream of internal part as a raw H264 and its timestamps.
logger.info('Extract video track as raw H264 file.')
extract_track_from_mkv(mkvextract_path=options.tools_paths['mkvextract'],
input_file=internal_mkv, index=0, output_file=internal_h264,
timestamps=internal_h264_ts)
# Remove video track from internal part of MKV
logger.info('Remove video track from %s', internal_mkv_name)
remove_video_tracks_from_mkv(mkvmerge_path=options.tools_paths['mkvmerge'],
input_file=internal_mkv, output_file=internal_novideo_mkv)
temporaries.append(internal_mkv)
temporaries.append(internal_h264)
temporaries.append(internal_h264_ts)
temporaries.append(internal_novideo_mkv)
h264parts.append(internal_h264)
h264_ts.append(internal_h264_ts)
subparts.append(internal_novideo_mkv)
if (not options.coarse) and (nb_tail_frames > options.threshold):
# We extract all frames between the I-frame (including it) upto the end.
h264_tail, h264_tail_ts, mkv_tail = extract_all_streams(ffmpeg_path=options.tools_paths['ffmpeg'],
ffprobe_path=options.tools_paths['ffprobe'],
input_file=media.movie, begin=tail_iframe_ts,
end=ts2, nb_frames=nb_tail_frames,
framerate=media.framerate,
files_prefix=f'part-{partnum:d}-tail',
streams=media.streams,
width=media.width, height=media.height,
temporaries=temporaries,
dump_mem_fd=options.dump_memory)
if mkv_tail is not None:
subparts.append(mkv_tail)
if h264_tail is not None:
avcconfig = get_avc_config_from_h264(h264_tail)
other_avc_configs.append(avcconfig)
h264parts.append(h264_tail)
if h264_tail_ts is not None:
h264_ts.append(h264_tail_ts)
logger.info('Merging MKV: %s', subparts)
part = merge_mkvs(mkvmerge_path=options.tools_paths['mkvmerge'], inputs=subparts,
output_name=f'part-{partnum:d}.mkv', concatenate=True)
mkvparts.append(part)
temporaries.append(part)
pos = pos+tail_iframe_ts-ts1
# We need to check the end also
checks.append(pos)
# When using coarse option there is a single AVC configuration.
for avc_config in other_avc_configs:
media.avc_config.merge(avc_config)
logger.debug('Merged AVC configuration: %s', media.avc_config)
nb_mkv_parts = len(mkvparts)
if nb_mkv_parts > 0:
try:
full_h264 = open(f'{media.basename}-full.h264', 'wb+')
except OSError:
logger.error('Impossible to create file full H264 stream.')
exit(-1)
logger.info('Merging all H264 tracks')
concatenate_h264_parts(h264parts=h264parts, output=full_h264)
temporaries.append(full_h264)
try:
full_h264_ts = open(f'{media.basename}-ts.txt', 'w+', encoding='utf8')
except OSError:
logger.error('Impossible to create file containing all video timestamps.')
exit(-1)
logger.info('Merging H264 timestamps')
concatenate_h264_ts_parts(h264_ts_parts=h264_ts, output=full_h264_ts)
temporaries.append(full_h264_ts)
final_novideo_name = f'{media.basename}-novideo.mkv'
final_with_video_name = f'{media.basename}-video.mkv'
if nb_mkv_parts > 1:
logger.info('Merging all audio and subtitles parts: %s', mkvparts)
merge_mkvs(mkvmerge_path=options.tools_paths['mkvmerge'], inputs=mkvparts,
output_name=final_novideo_name, concatenate=True)
elif nb_mkv_parts == 1:
copyfile('part-1.mkv', final_novideo_name)
else:
logger.info("Nothing else to do.")
copyfile(mkvfilename, final_with_video_name)
if nb_mkv_parts >=1 :
try:
final_novideo = open(final_novideo_name, 'rb')
except OSError:
logger.error('Impossible to open file: %s.', final_novideo_name)
exit(-1)
temporaries.append(final_novideo)
full_h264_ts.seek(0)
logger.info('Merging final video track and all other tracks together')
final_with_video = merge_mkvs(mkvmerge_path=options.tools_paths['mkvmerge'],
inputs=[full_h264, final_novideo],
output_name=final_with_video_name, concatenate=False,
timestamps={0: full_h264_ts})
final_codec_private_data = dump_codec_private_data(media.avc_config)
logger.debug('Final codec private data: %s', hexdump.dump(final_codec_private_data,
sep=':'))
logger.info('Changing codec private data with the new one.')
change_codec_private_data(options.tools_paths['mkvinfo'], final_with_video,
final_codec_private_data)
return CutResult(
filename = final_with_video_name,
movie = final_with_video,
check_positions = checks
)
+60
View File
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2026 Frédéric Tronel
from dataclasses import dataclass
from datetime import timedelta
from pathlib import Path
from enum import IntEnum, unique
from typing import BinaryIO, IO
from tscut.h264.avc import AVCDecoderConfiguration
@unique
class SupportedFormat(IntEnum):
TS = 1
MP4 = 2
MATROSKA = 3
def __str__(self):
match self:
case SupportedFormat.TS:
return 'mpegts'
case SupportedFormat.MP4:
return 'mov,mp4,m4a,3gp,3g2,mj2'
case SupportedFormat.MATROSKA:
return 'matroska,webm'
case _:
return 'Unsupported format'
@dataclass
class ProcessingOptions:
input_file: Path
output_file: Path
parts: list[tuple[timedelta, timedelta]]
tools_paths: dict[str, str]
all_optional_tools: bool
framerate: int|None = None
threshold: int = 0
coarse: bool = False
subtitles_ocr: bool = True
verbose: bool = False
dump_memory: bool = False
keep_temporaries: bool = False
@dataclass
class PreparedMedia:
basename: str
movie: BinaryIO
duration: timedelta
framerate: float
streams: list
width: int
height: int
avc_config: AVCDecoderConfiguration
@dataclass
class CutResult:
filename: str
movie: IO[bytes]
check_positions: list[timedelta]
+33 -256
View File
@@ -12,12 +12,23 @@ from typing import IO, Any, BinaryIO
import hexdump
from tscut.config import ProcessingOptions
from tscut.models import ProcessingOptions, PreparedMedia, SupportedFormat
from tscut.cutting import cut_recording
from tscut.exceptions import InvalidMediaError
from tscut.h264.avc import get_avc_config_from_h264, parse_codec_private
from tscut.h264.avc import (
get_avc_config_from_h264,
parse_codec_private
)
from tscut.matroska.codec import dump_codec_private_data
from tscut.subtitles.ocr import do_ocr, extract_srt, get_tesseract_supported_lang
from tscut.tools.ffmpeg import extract_all_streams, ffmpeg_convert
from tscut.subtitles.ocr import (
do_ocr,
extract_srt,
get_tesseract_supported_lang
)
from tscut.tools.ffmpeg import (
extract_all_streams,
ffmpeg_convert
)
from tscut.tools.ffprobe import (
find_subtitles_tracks,
get_format,
@@ -36,7 +47,10 @@ from tscut.tools.mkvtoolnix import (
remux_srt_subtitles,
)
from tscut.tools.timeframe import get_ts_frame
from tscut.tscut import SupportedFormat, concatenate_h264_parts, concatenate_h264_ts_parts
from tscut.tscut import (
concatenate_h264_parts,
concatenate_h264_ts_parts
)
logger = logging.getLogger(__name__)
@@ -182,259 +196,22 @@ def process_recording(options: ProcessingOptions) -> None:
iso_avc_config)
exit(-1)
# Pour chaque portion
partnum = 0
mkvparts = []
h264parts = []
h264_ts = []
checks = []
pos = timedelta()
prepared_movie = PreparedMedia(
basename = basename,
movie = mkv,
duration = duration,
framerate = framerate,
streams = streams,
width = int(width),
height = int(height),
avc_config = main_avc_config
)
other_avc_configs = []
cut_movie = cut_recording(prepared_movie, options, temporaries)
for ts1, ts2 in options.parts:
# TODO: translate comment in english
# Trouver l'estampille de la trame 'I' la plus proche (mais postérieure) au début
# de la portion.
# Trouver l'estampille de la trame 'I' la plus proche (mais antérieure) à la fin
# de la portion.
# On a alors
# debut ----- trame --------- trame --------- fin fin+1
# 'B/P' 'B/P'* 'I' 'I' 'B/P'* 'B/P' 'I/B/P'
# Si la trame de début est déjà 'I', il n'y a rien à faire.
# Sinon on extrait les trames 'B' ou 'P' depuis le début jusqu'à la trame 'I' non incluse.
# Si la trame de fin précède une trame I, on n'a rien à faire.
# Sinon on extrait toutes les trames depuis la dernière trame I jusqu'à la trame de fin.
partnum = partnum + 1
# Get the nearest I-frame whose timestamp is greater or equal to the beginning.
head_frames = get_nearest_iframe(options.tools_paths['ffprobe'], mkv, ts1, before=False)
if head_frames is None:
raise InvalidMediaError("Impossible to retrieve first I-frame")
# Get the nearest I-frame whose timestamp ...
# TODO: wrong here ...
tail_frames = get_nearest_iframe(options.tools_paths['ffprobe'], mkv, ts2, before=True)
if tail_frames is None:
raise InvalidMediaError("Impossible to retrieve last I-frame")
nb_head_frames, head_iframe = head_frames
nb_tail_frames, tail_iframe = tail_frames
logger.info("Found %d frames between beginning of current part and first I-frame",
nb_head_frames)
logger.info("Found %d frames between last I-frame and end of current part",
nb_tail_frames)
head_iframe_ts = get_ts_frame(head_iframe)
if head_iframe_ts is None:
raise InvalidMediaError("Impossible to retrieve timestamp of first i-frame.")
tail_iframe_ts = get_ts_frame(tail_iframe)
if tail_iframe_ts is None:
raise InvalidMediaError("Impossible to retrieve timestamp of final i-frame.")
checks.append(pos+head_iframe_ts-ts1)
subparts = []
# TODO: separate pipeline processing between coarse and not fine grain options.
# if args.coarse:
# do_coarse_processing(ffmpeg=paths['ffmpeg'], ffprobe=paths['ffprobe'], input_file=mkv,
# begin=ts1, end=head_iframe_ts, nb_frames=nb_head_frames-1,
# frameRate=frameRate, files_prefix='part-%d-head' % (partnum),
# streams=streams, width=width, height=height,
# temporaries=temporaries, dump_mem_fd=args.dump)
# else:
# doFineGrainProcessing(ffmpeg=paths['ffmpeg'], ffprobe=paths['ffprobe'],
# input_file=mkv, begin=ts1, end=head_iframe_ts,
# nb_frames=nb_head_frames-1, frameRate=frameRate,
# files_prefix='part-%d-head' % (partnum), streams=streams,
# width=width, height=height, temporaries=temporaries,
# dump_mem_fd=args.dump)
if (not options.coarse) and (nb_head_frames > options.threshold):
# We extract all frames between the beginning upto the frame that immediately preceeds
# the I-frame.
h264_head, h264_head_ts, mkv_head = extract_all_streams(
ffmpeg_path=options.tools_paths['ffmpeg'],
ffprobe_path=options.tools_paths['ffprobe'],
input_file=mkv, begin=ts1,
end=head_iframe_ts,
nb_frames=nb_head_frames-1,
framerate=framerate,
files_prefix=f'part-{partnum:d}-head',
streams=streams, width=width,
height=height,
temporaries=temporaries,
dump_mem_fd=options.dump_memory)
# If we are not at an exact boundary:
if mkv_head is not None:
subparts.append(mkv_head)
if h264_head is not None:
avcconfig = get_avc_config_from_h264(h264_head)
other_avc_configs.append(avcconfig)
h264parts.append(h264_head)
if h264_head_ts is not None:
h264_ts.append(h264_head_ts)
# Creating MKV file that corresponds to current part between I-frames
# Internal video with all streams (video, audio and subtitles)
internal_mkv_name = f'part-{partnum:d}-internal.mkv'
# Internal video stream as a raw H264 stream
internal_h264_name = f'part-{partnum:d}-internal.h264'
# Internal video timestamps
internal_h264_ts_name = f'part-{partnum:d}-internal-ts.txt'
# Internal video with only audio and subtitles streams
internal_novideo_mkv_name = f'part-{partnum:d}-internal-novideo.mkv'
try:
internal_mkv = open(internal_mkv_name, 'wb+')
except OSError:
logger.error('Impossible to create file: %s', internal_mkv_name)
exit(-1)
try:
internal_novideo_mkv = open(internal_novideo_mkv_name, 'wb+')
except OSError:
logger.error('Impossible to create file: %s', internal_novideo_mkv_name)
exit(-1)
try:
internal_h264 = open(internal_h264_name, 'wb+')
except OSError:
logger.error('Impossible to create file: %s', internal_h264_name)
exit(-1)
try:
internal_h264_ts = open(internal_h264_ts_name, 'w+', encoding='utf8')
except OSError:
logger.error('Impossible to create file: %s', internal_h264_ts_name)
exit(-1)
# logger.info('Merge header, middle and trailer subpart into: %s' % internal_mkv_name)
# Extract internal part of MKV
extract_mkv_part(mkvmerge_path=options.tools_paths['mkvmerge'], input_file=mkv,
output_file=internal_mkv, begin=head_iframe_ts, end=tail_iframe_ts)
# Extract video stream of internal part as a raw H264 and its timestamps.
logger.info('Extract video track as raw H264 file.')
extract_track_from_mkv(mkvextract_path=options.tools_paths['mkvextract'],
input_file=internal_mkv, index=0, output_file=internal_h264,
timestamps=internal_h264_ts)
# Remove video track from internal part of MKV
logger.info('Remove video track from %s', internal_mkv_name)
remove_video_tracks_from_mkv(mkvmerge_path=options.tools_paths['mkvmerge'],
input_file=internal_mkv, output_file=internal_novideo_mkv)
temporaries.append(internal_mkv)
temporaries.append(internal_h264)
temporaries.append(internal_h264_ts)
temporaries.append(internal_novideo_mkv)
h264parts.append(internal_h264)
h264_ts.append(internal_h264_ts)
subparts.append(internal_novideo_mkv)
if (not options.coarse) and (nb_tail_frames > options.threshold):
# We extract all frames between the I-frame (including it) upto the end.
h264_tail, h264_tail_ts, mkv_tail = extract_all_streams(ffmpeg_path=options.tools_paths['ffmpeg'],
ffprobe_path=options.tools_paths['ffprobe'],
input_file=mkv, begin=tail_iframe_ts,
end=ts2, nb_frames=nb_tail_frames,
framerate=framerate,
files_prefix=f'part-{partnum:d}-tail',
streams=streams,
width=width, height=height,
temporaries=temporaries,
dump_mem_fd=options.dump_memory)
if mkv_tail is not None:
subparts.append(mkv_tail)
if h264_tail is not None:
avcconfig = get_avc_config_from_h264(h264_tail)
other_avc_configs.append(avcconfig)
h264parts.append(h264_tail)
if h264_tail_ts is not None:
h264_ts.append(h264_tail_ts)
logger.info('Merging MKV: %s', subparts)
part = merge_mkvs(mkvmerge_path=options.tools_paths['mkvmerge'], inputs=subparts,
output_name=f'part-{partnum:d}.mkv', concatenate=True)
mkvparts.append(part)
temporaries.append(part)
pos = pos+tail_iframe_ts-ts1
# We need to check the end also
checks.append(pos)
# When using coarse option there is a single AVC configuration.
for avc_config in other_avc_configs:
main_avc_config.merge(avc_config)
logger.debug('Merged AVC configuration: %s', main_avc_config)
nb_mkv_parts = len(mkvparts)
if nb_mkv_parts > 0:
try:
full_h264 = open(f'{basename}-full.h264', 'wb+')
except OSError:
logger.error('Impossible to create file full H264 stream.')
exit(-1)
logger.info('Merging all H264 tracks')
concatenate_h264_parts(h264parts=h264parts, output=full_h264)
temporaries.append(full_h264)
try:
full_h264_ts = open(f'{basename}-ts.txt', 'w+', encoding='utf8')
except OSError:
logger.error('Impossible to create file containing all video timestamps.')
exit(-1)
logger.info('Merging H264 timestamps')
concatenate_h264_ts_parts(h264_ts_parts=h264_ts, output=full_h264_ts)
temporaries.append(full_h264_ts)
final_novideo_name = f'{basename}-novideo.mkv'
final_with_video_name = f'{basename}-video.mkv'
if nb_mkv_parts > 1:
logger.info('Merging all audio and subtitles parts: %s', mkvparts)
merge_mkvs(mkvmerge_path=options.tools_paths['mkvmerge'], inputs=mkvparts,
output_name=final_novideo_name, concatenate=True)
elif nb_mkv_parts == 1:
copyfile('part-1.mkv', final_novideo_name)
else:
logger.info("Nothing else to do.")
copyfile(mkvfilename, final_with_video_name)
if nb_mkv_parts >=1 :
try:
final_novideo = open(final_novideo_name, 'rb')
except OSError:
logger.error('Impossible to open file: %s.', final_novideo_name)
exit(-1)
temporaries.append(final_novideo)
full_h264_ts.seek(0)
logger.info('Merging final video track and all other tracks together')
final_with_video = merge_mkvs(mkvmerge_path=options.tools_paths['mkvmerge'],
inputs=[full_h264, final_novideo],
output_name=final_with_video_name, concatenate=False,
timestamps={0: full_h264_ts})
final_codec_private_data = dump_codec_private_data(main_avc_config)
logger.debug('Final codec private data: %s', hexdump.dump(final_codec_private_data,
sep=':'))
logger.info('Changing codec private data with the new one.')
change_codec_private_data(options.tools_paths['mkvinfo'], final_with_video,
final_codec_private_data)
final_with_video_name = cut_movie.filename
final_with_video = cut_movie.movie
checks = cut_movie.check_positions
if options.subtitles_ocr:
if not options.all_optional_tools:
+1 -23
View File
@@ -3,7 +3,6 @@
# Standard modules
import logging
from enum import IntEnum, unique
from os import (
SEEK_SET,
fstat,
@@ -18,6 +17,7 @@ from typing import IO, BinaryIO, Sequence, TextIO
from tqdm import tqdm
from typeguard import typechecked
from tscut.models import SupportedFormat
from tscut.tools.mkvtoolnix import (
extract_mkv_part,
)
@@ -46,28 +46,6 @@ from tscut.tools.mkvtoolnix import (
# Then finally, change the Private Codec Data in the final MKV.
@unique
class SupportedFormat(IntEnum):
TS = 1
MP4 = 2
MATROSKA = 3
def __str__(self):
match self:
case SupportedFormat.TS:
return 'mpegts'
case SupportedFormat.MP4:
return 'mov,mp4,m4a,3gp,3g2,mj2'
case SupportedFormat.MATROSKA:
return 'matroska,webm'
case _:
return 'Unsupported format'
# Extract SPS/PPS
# https://gitlab.com/mbunkus/mkvtoolnix/-/issues/2390
# ffmpeg -i <InputFile (before concatenation)> -c:v copy -an -sn -bsf:v trace_headers -t 0.01\