Improve error handling.

This commit is contained in:
Frédéric Tronel
2026-08-31 13:54:03 +02:00
parent 153873637e
commit 51bab76c00
5 changed files with 45 additions and 49 deletions
+31 -38
View File
@@ -8,12 +8,12 @@ from io import TextIOWrapper
from math import ceil, floor, log
from os import SEEK_SET, close, lseek, memfd_create, read, set_inheritable, write
from subprocess import PIPE, Popen
from typing import IO
from typing import IO, BinaryIO
from tqdm import tqdm
from typeguard import typechecked
from tscut.exceptions import ExternalToolError, InvalidMediaError
from tscut.exceptions import ExternalToolError, InvalidMediaError, TemporaryFileError
from tscut.tools.ffprobe import (
get_frames_in_stream,
get_video_dimensions,
@@ -21,13 +21,14 @@ from tscut.tools.ffprobe import (
)
from tscut.tools.ppm import dump_ppm
from tscut.tools.timeframe import get_packet_duration, parse_timestamp
from tscut.temporaries import TemporaryFiles
logger = logging.getLogger(__name__)
@typechecked
def ffmpeg_convert(ffmpeg_path:str, ffprobe_path:str, input_file: IO[bytes], input_format:str,
output_file: IO[bytes], output_format:str, duration: timedelta):
output_file: IO[bytes], output_format:str, duration: timedelta) -> None:
width, height = get_video_dimensions(ffprobe_path, input_file)
if width is None or height is None:
return
@@ -68,7 +69,7 @@ def ffmpeg_convert(ffmpeg_path:str, ffprobe_path:str, input_file: IO[bytes], inp
pb.update()
status = ffmpeg.wait()
if status != 0:
logger.error('Conversion failed with status code: %d', status)
raise ExternalToolError(f"Conversion failed with status code: {status:d}")
@typechecked
@@ -116,14 +117,12 @@ def extract_pictures(ffmpeg_path:str, input_file:IO[bytes], begin:timedelta, nb_
with Popen(command, stdout=PIPE, close_fds=False) as ffmpeg:
status = ffmpeg.wait()
if status != 0:
logger.error('Conversion failed with status code: %d', status)
raise ExternalToolError("")
raise ExternalToolError(f"Conversion failed with status code: {status:d}")
lseek(outfd, 0, SEEK_SET)
images = read(outfd,length)
if len(images) != length:
logger.error("Received %d bytes but %d were expected.", len(images), length)
raise InvalidMediaError("")
raise InvalidMediaError(f"Received {len(images)} bytes but {length} were expected.")
lseek(outfd, 0, SEEK_SET)
return images, outfd
@@ -150,24 +149,25 @@ def extract_sound(ffmpeg_path:str, input_file: IO[bytes], begin:timedelta, outpu
with Popen(command, stdout=PIPE, close_fds=False) as ffmpeg:
status = ffmpeg.wait()
if status != 0:
logger.error('Sound extraction returns error code: %d', status)
raise ExternalToolError("")
raise ExternalToolError(f"Sound extraction returns error code: {status}")
lseek(outfd, 0, SEEK_SET)
sound = read(outfd, length)
if len(sound) != length:
logger.info("Received %d bytes but %d were expected (channels=%d, freq=%d, packets=%d,\
duration=%d ms).", len(sound), length, nb_channels, sample_rate, nb_packets,
packet_duration)
raise InvalidMediaError("")
raise InvalidMediaError(f"Received {len(sound)} bytes but {length} were expected (\
channels={nb_channels}, freq={sample_rate} packets={nb_packets},\
duration={packet_duration} ms).")
return sound, outfd
@typechecked
def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes], begin:timedelta,
end:timedelta, streams, files_prefix, nb_frames:int, framerate:float,
width:int, height:int, temporaries, dump_mem_fd:bool=False):
width:int, height:int, temporaries:TemporaryFiles,
dump_mem_fd:bool=False) -> tuple[BinaryIO|None,
TextIOWrapper|None,
BinaryIO|None]:
# The command line for encoding only video track
video_encoder_params = [ ffmpeg_path, '-y', '-loglevel', 'quiet']
video_input_params = []
@@ -258,8 +258,7 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
packets = get_frames_in_stream(ffprobe_path, input_file=input_file, begin=begin,
end=end, stream_kind='a', sub_stream_id=audio_id)
if packets is None:
logger.error("Impossible to retrieve audio packets")
return None
raise InvalidMediaError("Impossible to retrieve audio packets")
nb_packets = len(packets)
logger.debug("Found %d packets to be extracted from audio track.", nb_packets)
if nb_packets > 0:
@@ -283,14 +282,13 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
if dump_mem_fd:
try:
with open(tmpname,'wb') as output:
temporaries.append(output)
temporaries.add(output)
outfd = output.fileno()
pos = 0
while pos < len(sound_bytes):
pos+=write(outfd, sound_bytes[pos:])
except OSError:
logger.error('Impossible to create file: %s', tmpname)
return None
except OSError as e:
raise TemporaryFileError(f"Impossible to create file: {tmpname}") from e
# We rewind to zero the memory file descriptor
lseek(memfd, 0, SEEK_SET)
@@ -323,9 +321,8 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
mkv_filename = f'{files_prefix}.mkv'
try:
mkv_output = open(mkv_filename,'wb+')
except OSError:
logger.error('Impossible to create file: %s', mkv_filename)
return None
except OSError as e:
raise TemporaryFileError(f"Impossible to create file: {mkv_filename}") from e
mkvoutfd = mkv_output.fileno()
set_inheritable(mkvoutfd, True)
@@ -336,17 +333,15 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
with Popen(generic_encoder_params, stdout=PIPE, close_fds=False) as ffmpeg:
status = ffmpeg.wait()
if status != 0:
logger.error('Encoding failed with status code: %d', status)
return None
raise ExternalToolError(f"Encoding failed with status code: {status}")
temporaries.append(mkv_output)
temporaries.add(mkv_output)
h264_filename = f'{files_prefix}.h264'
try:
h264_output = open(h264_filename,'wb+')
except OSError:
logger.error('Impossible to create file: %s', h264_filename)
return None
except OSError as e:
raise TemporaryFileError(f"Impossible to create file {h264_filename}") from e
h264outfd = h264_output.fileno()
set_inheritable(h264outfd, True)
@@ -365,17 +360,15 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
with Popen(video_encoder_params, stdout=PIPE, close_fds=False) as ffmpeg:
status = ffmpeg.wait()
if status != 0:
logger.error('Encoding failed with status code: %d', status)
return None
raise ExternalToolError(f"Encoding failed with status code: {status:d}")
temporaries.append(h264_output)
temporaries.add(h264_output)
h264_ts_filename = f'{files_prefix}-ts.txt'
try:
h264_ts_output = open(h264_ts_filename,'w+', encoding='utf8')
except OSError:
logger.error('Impossible to create file: %s', h264_ts_filename)
return None
except OSError as e:
raise TemporaryFileError(f"Impossible to create file: {h264_ts_filename}") from e
h264_ts_output.write('# timestamp format v2\n')
ts = 0
@@ -385,7 +378,7 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
h264_ts_output.flush()
h264_ts_output.seek(0)
temporaries.append(h264_ts_output)
temporaries.add(h264_ts_output)
for memfd in memfds:
close(memfd)
@@ -393,5 +386,5 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
return h264_output, h264_ts_output, mkv_output
# Nothing to be done. We are already at a i-frame boundary.
return None, None
return None, None, None