Fix of several typing errors.

This commit is contained in:
Frédéric Tronel
2026-08-30 13:56:41 +02:00
parent 447d04d13d
commit a6a41c112e
8 changed files with 54 additions and 43 deletions
+1
View File
@@ -13,6 +13,7 @@ from functools import cmp_to_key
import coloredlogs import coloredlogs
from tscut.exceptions import TSCutError
from tscut.config import ProcessingOptions from tscut.config import ProcessingOptions
from tscut.tools.discovery import check_required_tools from tscut.tools.discovery import check_required_tools
from tscut.tools.timeframe import parse_time_interval, compare_time_interval from tscut.tools.timeframe import parse_time_interval, compare_time_interval
+1 -1
View File
@@ -243,7 +243,7 @@ class AVCDecoderConfiguration:
# TODO: do the same with extended SPS ! # TODO: do the same with extended SPS !
@typechecked @typechecked
def parse_codec_private(codec_private_data: bytes) -> AVCDecoderConfiguration: def parse_codec_private(codec_private_data: bytes | bytearray) -> AVCDecoderConfiguration:
if codec_private_data[0] != 0x63: if codec_private_data[0] != 0x63:
raise ValueError(f'Matroska header is wrong: {codec_private_data[0]:x}') raise ValueError(f'Matroska header is wrong: {codec_private_data[0]:x}')
if codec_private_data[1] != 0xA2: if codec_private_data[1] != 0xA2:
+11 -12
View File
@@ -11,6 +11,7 @@ import logging
import hexdump import hexdump
from tscut.exceptions import InvalidMediaError
from tscut.config import ProcessingOptions from tscut.config import ProcessingOptions
from tscut.tools.mkvtoolnix import ( from tscut.tools.mkvtoolnix import (
get_codec_private_data_from_mkv, get_codec_private_data_from_mkv,
@@ -90,13 +91,13 @@ def process_recording(options: ProcessingOptions) -> None:
if 'format_name' in format_of_file: if 'format_name' in format_of_file:
if format_of_file['format_name'] == str(f): if format_of_file['format_name'] == str(f):
found = True found = True
format_of_file = f final_format_of_file = f
break break
if not found: if not found:
logger.error('Unsupported format of file') logger.error('Unsupported format of file')
if format_of_file == SupportedFormat.TS: if final_format_of_file == SupportedFormat.TS:
logger.info("Converting TS to MP4 (to fix timestamps).") logger.info("Converting TS to MP4 (to fix timestamps).")
try: try:
with open(mp4filename, 'wb+') as mp4: with open(mp4filename, 'wb+') as mp4:
@@ -116,7 +117,7 @@ def process_recording(options: ProcessingOptions) -> None:
except OSError: except OSError:
logger.error('') logger.error('')
elif format_of_file == SupportedFormat.MP4: elif final_format_of_file == SupportedFormat.MP4:
logger.info("Converting MP4 to MKV") logger.info("Converting MP4 to MKV")
try: try:
mkv = open(mkvfilename, 'wb+') mkv = open(mkvfilename, 'wb+')
@@ -221,15 +222,13 @@ def process_recording(options: ProcessingOptions) -> None:
# Get the nearest I-frame whose timestamp is greater or equal to the beginning. # 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) head_frames = get_nearest_iframe(options.tools_paths['ffprobe'], mkv, ts1, before=False)
if head_frames is None: if head_frames is None:
logger.error('Impossible to retrieve I-frame') raise InvalidMediaError("Impossible to retrieve first I-frame")
exit(-1)
# Get the nearest I-frame whose timestamp ... # Get the nearest I-frame whose timestamp ...
# TODO: wrong here ... # TODO: wrong here ...
tail_frames = get_nearest_iframe(options.tools_paths['ffprobe'], mkv, ts2, before=True) tail_frames = get_nearest_iframe(options.tools_paths['ffprobe'], mkv, ts2, before=True)
if tail_frames is None: if tail_frames is None:
logger.error('Impossible to retrieve I-frame') raise InvalidMediaError("Impossible to retrieve last I-frame")
exit(-1)
nb_head_frames, head_iframe = head_frames nb_head_frames, head_iframe = head_frames
nb_tail_frames, tail_iframe = tail_frames nb_tail_frames, tail_iframe = tail_frames
@@ -241,10 +240,10 @@ def process_recording(options: ProcessingOptions) -> None:
head_iframe_ts = get_ts_frame(head_iframe) head_iframe_ts = get_ts_frame(head_iframe)
if head_iframe_ts is None: if head_iframe_ts is None:
exit(-1) raise InvalidMediaError("Impossible to retrieve timestamp of first i-frame.")
tail_iframe_ts = get_ts_frame(tail_iframe) tail_iframe_ts = get_ts_frame(tail_iframe)
if tail_iframe_ts is None: if tail_iframe_ts is None:
exit(-1) raise InvalidMediaError("Impossible to retrieve timestamp of final i-frame.")
checks.append(pos+head_iframe_ts-ts1) checks.append(pos+head_iframe_ts-ts1)
@@ -513,10 +512,10 @@ def process_recording(options: ProcessingOptions) -> None:
if not options.keep_temporaries: if not options.keep_temporaries:
logger.info("Cleaning temporary files") logger.info("Cleaning temporary files")
for f in temporaries: for tmp in temporaries:
path = os.path.realpath(f.name) path = os.path.realpath(tmp.name)
logger.info("Removing: %s", path) logger.info("Removing: %s", path)
f.close() tmp.close()
unlink(path) unlink(path)
d = datetime(1,1,1) d = datetime(1,1,1)
+6 -5
View File
@@ -4,7 +4,7 @@
import logging import logging
from typing import IO from typing import IO, Sequence
from io import TextIOWrapper from io import TextIOWrapper
from datetime import timedelta from datetime import timedelta
from os import ( from os import (
@@ -24,6 +24,8 @@ from iso639.exceptions import InvalidLanguageValue
from typeguard import typechecked from typeguard import typechecked
from tqdm import tqdm from tqdm import tqdm
from tscut.exceptions import ExternalToolError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@typechecked @typechecked
@@ -70,7 +72,7 @@ def get_tesseract_supported_lang(tesseract_path:str) -> dict[Lang, str]|None:
@typechecked @typechecked
def extract_srt(mkvextract:str, filename:str, subtitles:dict[str, list[int]], def extract_srt(mkvextract:str, filename:str, subtitles:dict[str, list[int]],
langs:dict[Lang,str]) -> list[tuple[str,str,str,str]]|None: langs:dict[Lang,str]) -> list[tuple[str,str,str,str]]:
params = [mkvextract, filename, 'tracks'] params = [mkvextract, filename, 'tracks']
res = [] res = []
@@ -120,13 +122,12 @@ def extract_srt(mkvextract:str, filename:str, subtitles:dict[str, list[int]],
case 1: case 1:
logger.warning('Mkvextract returns warning') logger.warning('Mkvextract returns warning')
case 2: case 2:
logger.error('Mkvextract returns an error code: %d', extract.returncode) raise ExternalToolError('Mkvextract returns an error code: %d', extract.returncode)
res = None
return res return res
@typechecked @typechecked
def do_ocr(vobsubocr:str, idxs: list[tuple[str,str,str,str]], duration:timedelta, def do_ocr(vobsubocr:str, idxs: Sequence[tuple[str,str,str,str]], duration:timedelta,
temporaries:list[IO[bytes]], dump_mem_fd:bool=False): temporaries:list[IO[bytes]], dump_mem_fd:bool=False):
res = [] res = []
+1
View File
@@ -56,6 +56,7 @@ def ffmpeg_convert(ffmpeg_path:str, ffprobe_path:str, input_file: IO[bytes], inp
logger.debug('Executing %s', params) logger.debug('Executing %s', params)
with Popen(params, stdout=PIPE, close_fds=False) as ffmpeg: with Popen(params, stdout=PIPE, close_fds=False) as ffmpeg:
assert ffmpeg.stdout is not None
pb = tqdm(TextIOWrapper(ffmpeg.stdout, encoding="utf-8"), pb = tqdm(TextIOWrapper(ffmpeg.stdout, encoding="utf-8"),
total=int(duration/timedelta(seconds=1)), unit='s', desc='Conversion') total=int(duration/timedelta(seconds=1)), unit='s', desc='Conversion')
for line in pb: for line in pb:
+8 -10
View File
@@ -19,6 +19,7 @@ from typing import IO
from typeguard import typechecked from typeguard import typechecked
from tscut.tools.timeframe import get_ts_frame from tscut.tools.timeframe import get_ts_frame
from tscut.exceptions import InvalidMediaError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -154,7 +155,7 @@ def get_format(ffprobe_path:str, input_file: IO[bytes]) -> dict|None:
return None return None
@typechecked @typechecked
def get_movie_duration(ffprobe_path:str, input_file: IO[bytes]) -> timedelta|None: def get_movie_duration(ffprobe_path:str, input_file: IO[bytes]) -> timedelta:
infd = input_file.fileno() infd = input_file.fileno()
lseek(infd, 0, SEEK_SET) lseek(infd, 0, SEEK_SET)
set_inheritable(infd, True) set_inheritable(infd, True)
@@ -165,9 +166,7 @@ def get_movie_duration(ffprobe_path:str, input_file: IO[bytes]) -> timedelta|Non
if 'format' in out and 'duration' in out['format']: if 'format' in out and 'duration' in out['format']:
duration = floor(float(out['format']['duration'])) duration = floor(float(out['format']['duration']))
return timedelta(seconds=duration) return timedelta(seconds=duration)
logger.error('Impossible to retrieve duration of movie') raise InvalidMediaError("Impossible to retrieve duration of movie")
return None
# ffprobe -loglevel quiet -select_streams v:0 -show_entries stream=width,height -of json sample.ts # ffprobe -loglevel quiet -select_streams v:0 -show_entries stream=width,height -of json sample.ts
@typechecked @typechecked
@@ -242,7 +241,7 @@ def with_subtitles(ffprobe_path:str, input_file: IO[bytes]) -> bool:
return False return False
def find_subtitles_tracks(ffprobe_path:str, input_file: IO[bytes]) -> dict|None: def find_subtitles_tracks(ffprobe_path:str, input_file: IO[bytes]) -> dict:
infd = input_file.fileno() infd = input_file.fileno()
lseek(infd, 0, SEEK_SET) lseek(infd, 0, SEEK_SET)
set_inheritable(infd, True) set_inheritable(infd, True)
@@ -257,10 +256,9 @@ def find_subtitles_tracks(ffprobe_path:str, input_file: IO[bytes]) -> dict|None:
out = json.load(BytesIO(out)) out = json.load(BytesIO(out))
if 'streams' in out: if 'streams' in out:
return out['streams'] return out['streams']
logger.error('Impossible to retrieve format of file')
ffprobe.wait() ffprobe.wait()
return None raise InvalidMediaError('Impossible to retrieve format of file')
@typechecked @typechecked
def get_frames_in_stream(ffprobe_path: str, input_file: IO[bytes], begin:timedelta, end:timedelta, def get_frames_in_stream(ffprobe_path: str, input_file: IO[bytes], begin:timedelta, end:timedelta,
@@ -345,7 +343,7 @@ def get_nearest_idr_frame(ffprobe_path: str, input_file: IO[bytes], timestamp:ti
@typechecked @typechecked
def get_nearest_iframe(ffprobe_path:str, input_file: IO[bytes], def get_nearest_iframe(ffprobe_path:str, input_file: IO[bytes],
timestamp:timedelta, before:bool=True, timestamp:timedelta, before:bool=True,
delta_max:timedelta=timedelta(seconds=15))-> tuple[int,dict | None]: delta_max:timedelta=timedelta(seconds=15))-> tuple[int,dict] | None:
infd = input_file.fileno() infd = input_file.fileno()
set_inheritable(infd, True) set_inheritable(infd, True)
@@ -396,7 +394,7 @@ def get_nearest_iframe(ffprobe_path:str, input_file: IO[bytes],
its = get_ts_frame(iframe) its = get_ts_frame(iframe)
if its is None: if its is None:
logger.error("Impossible to retrieve timestamp of i-frame !") logger.error("Impossible to retrieve timestamp of i-frame !")
return 0, None return None
nb_frames = 0 nb_frames = 0
for frame in frames: for frame in frames:
ts = get_ts_frame(frame) ts = get_ts_frame(frame)
@@ -414,6 +412,6 @@ def get_nearest_iframe(ffprobe_path:str, input_file: IO[bytes],
nb_frames = nb_frames+1 nb_frames = nb_frames+1
else: else:
logger.error("Impossible to find I-frame between: %s and %s", tbegin, tend) logger.error("Impossible to find I-frame between: %s and %s", tbegin, tend)
return 0, None return None
return(nb_frames, iframe) return(nb_frames, iframe)
+23 -12
View File
@@ -125,7 +125,8 @@ def parse_mkv_tree(mkvinfo_path:str, input_file: IO[bytes]) -> dict[str,tuple[in
return elements return elements
@typechecked @typechecked
def change_codec_private_data(mkvinfo_path:str, input_file: IO[bytes], codec_data:bytes) -> None: def change_codec_private_data(mkvinfo_path:str, input_file: IO[bytes],
codec_data:bytes | bytearray) -> None:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
infd = input_file.fileno() infd = input_file.fileno()
@@ -217,6 +218,7 @@ def extract_mkv_part(mkvmerge_path:str, input_file:IO[bytes], output_file:IO[byt
logger.debug('Executing: %s', command) logger.debug('Executing: %s', command)
with Popen(command, stdout=PIPE, close_fds=False, env=env) as mkvmerge: with Popen(command, stdout=PIPE, close_fds=False, env=env) as mkvmerge:
assert mkvmerge.stdout is not None
pb = tqdm(TextIOWrapper(mkvmerge.stdout, encoding="utf-8"), total=100, unit='%', pb = tqdm(TextIOWrapper(mkvmerge.stdout, encoding="utf-8"), total=100, unit='%',
desc='Extraction') desc='Extraction')
for line in pb: for line in pb:
@@ -225,7 +227,8 @@ def extract_mkv_part(mkvmerge_path:str, input_file:IO[bytes], output_file:IO[byt
m = p.match(line) m = p.match(line)
if m is None: if m is None:
logger.error('Impossible to parse progress') logger.error('Impossible to parse progress')
pb.update(int(m['progress'])-pb.n) else:
pb.update(int(m['progress'])-pb.n)
elif line.startswith('Warning'): elif line.startswith('Warning'):
warnings.append(line) warnings.append(line)
pb.update(100-pb.n) pb.update(100-pb.n)
@@ -245,7 +248,7 @@ def extract_mkv_part(mkvmerge_path:str, input_file:IO[bytes], output_file:IO[byt
@typechecked @typechecked
def merge_mkvs(mkvmerge_path:str, inputs: list[IO[bytes]], output_name:str, def merge_mkvs(mkvmerge_path:str, inputs: list[IO[bytes]], output_name:str,
concatenate: bool=True, concatenate: bool=True,
timestamps: dict[int, IO[str]] | None = None) -> IO[bytes]|None: timestamps: dict[int, IO[str]] | None = None) -> IO[bytes]:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if timestamps is None: if timestamps is None:
@@ -254,9 +257,9 @@ def merge_mkvs(mkvmerge_path:str, inputs: list[IO[bytes]], output_name:str,
fds = [] fds = []
try: try:
out = open(output_name, 'wb+') out = open(output_name, 'wb+')
except OSError: except OSError as e:
logger.error('Impossible to create file: %s', output_name) logger.error('Impossible to create file: %s', output_name)
return None raise e
outfd = out.fileno() outfd = out.fileno()
lseek(outfd, 0, SEEK_SET) lseek(outfd, 0, SEEK_SET)
@@ -299,6 +302,7 @@ def merge_mkvs(mkvmerge_path:str, inputs: list[IO[bytes]], output_name:str,
logger.debug('Executing: LANG=C %s', merge_params) logger.debug('Executing: LANG=C %s', merge_params)
with Popen(merge_params, stdout=PIPE, close_fds=False, env=env) as mkvmerge: with Popen(merge_params, stdout=PIPE, close_fds=False, env=env) as mkvmerge:
assert mkvmerge.stdout is not None
pb = tqdm(TextIOWrapper(mkvmerge.stdout, encoding="utf-8"), total=100, unit='%', pb = tqdm(TextIOWrapper(mkvmerge.stdout, encoding="utf-8"), total=100, unit='%',
desc='Merging') desc='Merging')
for line in pb: for line in pb:
@@ -307,8 +311,9 @@ def merge_mkvs(mkvmerge_path:str, inputs: list[IO[bytes]], output_name:str,
m = p.match(line) m = p.match(line)
if m is None: if m is None:
logger.error('Impossible to parse progress') logger.error('Impossible to parse progress')
pb.n = int(m['progress']) else:
pb.update() pb.n = int(m['progress'])
pb.update()
elif line.startswith('Warning'): elif line.startswith('Warning'):
warnings.append(line) warnings.append(line)
@@ -352,6 +357,7 @@ def extract_track_from_mkv(mkvextract_path: str, input_file: IO[bytes], index,
logger.debug('Executing: LANG=C %s', params) logger.debug('Executing: LANG=C %s', params)
with Popen(params, stdout=PIPE, close_fds=False, env=env) as extract: with Popen(params, stdout=PIPE, close_fds=False, env=env) as extract:
assert extract.stdout is not None
pb = tqdm(TextIOWrapper(extract.stdout, encoding="utf-8"), total=100, unit='%', pb = tqdm(TextIOWrapper(extract.stdout, encoding="utf-8"), total=100, unit='%',
desc='Extraction of track') desc='Extraction of track')
for line in pb: for line in pb:
@@ -360,7 +366,8 @@ def extract_track_from_mkv(mkvextract_path: str, input_file: IO[bytes], index,
m = p.match(line) m = p.match(line)
if m is None: if m is None:
logger.error('Impossible to parse progress') logger.error('Impossible to parse progress')
pb.update(int(m['progress'])-pb.n) else:
pb.update(int(m['progress'])-pb.n)
pb.update(100-pb.n) pb.update(100-pb.n)
pb.refresh() pb.refresh()
pb.close() pb.close()
@@ -391,6 +398,7 @@ def remove_video_tracks_from_mkv(mkvmerge_path:str, input_file: IO[bytes],
env = {**os.environ, 'LANG': 'C'} env = {**os.environ, 'LANG': 'C'}
with Popen(params, stdout=PIPE, close_fds=False, env=env) as remove: with Popen(params, stdout=PIPE, close_fds=False, env=env) as remove:
assert remove.stdout is not None
pb = tqdm(TextIOWrapper(remove.stdout, encoding="utf-8"), total=100, unit='%', pb = tqdm(TextIOWrapper(remove.stdout, encoding="utf-8"), total=100, unit='%',
desc='Removal of video track:') desc='Removal of video track:')
for line in pb: for line in pb:
@@ -399,7 +407,8 @@ def remove_video_tracks_from_mkv(mkvmerge_path:str, input_file: IO[bytes],
m = p.match(line) m = p.match(line)
if m is None: if m is None:
logger.error('Impossible to parse progress') logger.error('Impossible to parse progress')
pb.update(int(m['progress'])-pb.n) else:
pb.update(int(m['progress'])-pb.n)
pb.update(100-pb.n) pb.update(100-pb.n)
pb.refresh() pb.refresh()
pb.close() pb.close()
@@ -420,7 +429,7 @@ def remux_srt_subtitles(mkvmerge_path:str, input_file: IO[bytes], output_file: P
try: try:
out = open(output_file, 'w', encoding='utf8') out = open(output_file, 'w', encoding='utf8')
except OSError: except OSError:
logger.error('Impossible to create file: %s', output_filename) logger.error('Impossible to create file: %s', output_file)
return None return None
outfd = out.fileno() outfd = out.fileno()
@@ -441,6 +450,7 @@ def remux_srt_subtitles(mkvmerge_path:str, input_file: IO[bytes], output_file: P
env = {**os.environ, 'LANG': 'C'} env = {**os.environ, 'LANG': 'C'}
logger.info('Remux subtitles: %s', mkv_merge_params) logger.info('Remux subtitles: %s', mkv_merge_params)
with Popen(mkv_merge_params, stdout=PIPE, close_fds=False, env=env) as mkvmerge: with Popen(mkv_merge_params, stdout=PIPE, close_fds=False, env=env) as mkvmerge:
assert mkvmerge.stdout is not None
pb = tqdm(TextIOWrapper(mkvmerge.stdout, encoding="utf-8"), total=100, unit='%', pb = tqdm(TextIOWrapper(mkvmerge.stdout, encoding="utf-8"), total=100, unit='%',
desc='Remux subtitles:') desc='Remux subtitles:')
for line in pb: for line in pb:
@@ -449,8 +459,9 @@ def remux_srt_subtitles(mkvmerge_path:str, input_file: IO[bytes], output_file: P
m = p.match(line) m = p.match(line)
if m is None: if m is None:
logger.error('Impossible to parse progress') logger.error('Impossible to parse progress')
pb.n = int(m['progress']) else:
pb.update() pb.n = int(m['progress'])
pb.update()
elif line.startswith('Warning'): elif line.startswith('Warning'):
warnings.append(line) warnings.append(line)
+3 -3
View File
@@ -12,7 +12,7 @@ from os import (
write, write,
) )
from sys import exit from sys import exit
from typing import IO from typing import IO, BinaryIO, TextIO, Sequence
# Third party libraries # Third party libraries
from tqdm import tqdm from tqdm import tqdm
@@ -115,7 +115,7 @@ class SupportedFormat(IntEnum):
@typechecked @typechecked
def concatenate_h264_parts(h264parts: list[IO[bytes]], output: IO[bytes]) -> None: def concatenate_h264_parts(h264parts: Sequence[BinaryIO], output: BinaryIO) -> None:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
total_length = 0 total_length = 0
@@ -142,7 +142,7 @@ def concatenate_h264_parts(h264parts: list[IO[bytes]], output: IO[bytes]) -> Non
pb.update(nb_bytes) pb.update(nb_bytes)
pos += nb_bytes pos += nb_bytes
def concatenate_h264_ts_parts(h264_ts_parts: list[IO[bytes]], output: IO[bytes]) -> None: def concatenate_h264_ts_parts(h264_ts_parts: Sequence[TextIO], output: TextIO) -> None:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
header = '# timestamp format v2\n' header = '# timestamp format v2\n'
output.write(header) output.write(header)