Improve error handling.
This commit is contained in:
@@ -144,12 +144,13 @@ def cut_recording(media: PreparedMedia, options: ProcessingOptions,
|
|||||||
nb_head_frames)
|
nb_head_frames)
|
||||||
logger.info("Found %d frames between last I-frame and end of current part",
|
logger.info("Found %d frames between last I-frame and end of current part",
|
||||||
nb_tail_frames)
|
nb_tail_frames)
|
||||||
|
try:
|
||||||
head_iframe_ts = get_ts_frame(head_iframe)
|
head_iframe_ts = get_ts_frame(head_iframe)
|
||||||
if head_iframe_ts is None:
|
except InvalidMediaError:
|
||||||
raise InvalidMediaError("Impossible to retrieve timestamp of first i-frame.")
|
raise InvalidMediaError("Impossible to retrieve timestamp of first i-frame.")
|
||||||
tail_iframe_ts = get_ts_frame(tail_iframe)
|
try:
|
||||||
if tail_iframe_ts is None:
|
tail_iframe_ts = get_ts_frame(tail_iframe)
|
||||||
|
except InvalidMediaError:
|
||||||
raise InvalidMediaError("Impossible to retrieve timestamp of final i-frame.")
|
raise InvalidMediaError("Impossible to retrieve timestamp of final i-frame.")
|
||||||
|
|
||||||
checks.append(pos+head_iframe_ts-ts1)
|
checks.append(pos+head_iframe_ts-ts1)
|
||||||
|
|||||||
@@ -148,9 +148,6 @@ def process_recording(options: ProcessingOptions, temporaries: TemporaryFiles) -
|
|||||||
logger.debug('Main video stream has following private data: %s',
|
logger.debug('Main video stream has following private data: %s',
|
||||||
hexdump.dump(main_codec_private_data, sep=':'))
|
hexdump.dump(main_codec_private_data, sep=':'))
|
||||||
|
|
||||||
if main_codec_private_data is None:
|
|
||||||
raise InvalidMediaError(f"Impossible to retrieve private data from MKV file {mkv}")
|
|
||||||
|
|
||||||
# We parse them
|
# We parse them
|
||||||
main_avc_config = parse_codec_private(main_codec_private_data)
|
main_avc_config = parse_codec_private(main_codec_private_data)
|
||||||
logger.debug('AVC configuration: %s', main_avc_config)
|
logger.debug('AVC configuration: %s', main_avc_config)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from tqdm import tqdm
|
|||||||
from typeguard import typechecked
|
from typeguard import typechecked
|
||||||
|
|
||||||
from tscut.exceptions import ExternalToolError, InvalidMediaError, TemporaryFileError
|
from tscut.exceptions import ExternalToolError, InvalidMediaError, TemporaryFileError
|
||||||
|
from tscut.temporaries import TemporaryFiles
|
||||||
from tscut.tools.ffprobe import (
|
from tscut.tools.ffprobe import (
|
||||||
get_frames_in_stream,
|
get_frames_in_stream,
|
||||||
get_video_dimensions,
|
get_video_dimensions,
|
||||||
@@ -21,7 +22,6 @@ from tscut.tools.ffprobe import (
|
|||||||
)
|
)
|
||||||
from tscut.tools.ppm import dump_ppm
|
from tscut.tools.ppm import dump_ppm
|
||||||
from tscut.tools.timeframe import get_packet_duration, parse_timestamp
|
from tscut.tools.timeframe import get_packet_duration, parse_timestamp
|
||||||
from tscut.temporaries import TemporaryFiles
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -263,8 +263,6 @@ def extract_all_streams(ffmpeg_path:str, ffprobe_path:str, input_file:IO[bytes],
|
|||||||
logger.debug("Found %d packets to be extracted from audio track.", nb_packets)
|
logger.debug("Found %d packets to be extracted from audio track.", nb_packets)
|
||||||
if nb_packets > 0:
|
if nb_packets > 0:
|
||||||
packet_duration = get_packet_duration(packets[0])
|
packet_duration = get_packet_duration(packets[0])
|
||||||
if packet_duration is None:
|
|
||||||
return None
|
|
||||||
else:
|
else:
|
||||||
packet_duration = 0
|
packet_duration = 0
|
||||||
|
|
||||||
|
|||||||
+11
-17
@@ -18,7 +18,7 @@ from typing import IO
|
|||||||
|
|
||||||
from typeguard import typechecked
|
from typeguard import typechecked
|
||||||
|
|
||||||
from tscut.exceptions import InvalidMediaError
|
from tscut.exceptions import InvalidMediaError, ExternalToolError
|
||||||
from tscut.tools.timeframe import get_ts_frame
|
from tscut.tools.timeframe import get_ts_frame
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -170,8 +170,7 @@ def get_movie_duration(ffprobe_path:str, input_file: IO[bytes]) -> timedelta:
|
|||||||
|
|
||||||
# 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
|
||||||
def get_video_dimensions(ffprobe_path:str,
|
def get_video_dimensions(ffprobe_path:str, input_file: IO[bytes]) -> tuple[int,int]:
|
||||||
input_file: IO[bytes]) -> tuple[int,int]| tuple[None,None]:
|
|
||||||
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)
|
||||||
@@ -185,8 +184,7 @@ def get_video_dimensions(ffprobe_path:str,
|
|||||||
if ('width' in video) and ('height' in video):
|
if ('width' in video) and ('height' in video):
|
||||||
return int(video['width']), int(video['height'])
|
return int(video['width']), int(video['height'])
|
||||||
|
|
||||||
logger.error('Impossible to retrieve dimensions of video')
|
raise InvalidMediaError('Impossible to retrieve dimensions of video')
|
||||||
return None, None
|
|
||||||
|
|
||||||
@typechecked
|
@typechecked
|
||||||
def get_streams(ffprobe_path:str, input_file: IO[bytes]) -> list|None:
|
def get_streams(ffprobe_path:str, input_file: IO[bytes]) -> list|None:
|
||||||
@@ -276,8 +274,7 @@ def get_frames_in_stream(ffprobe_path: str, input_file: IO[bytes], begin:timedel
|
|||||||
frames = json.load(BytesIO(out))
|
frames = json.load(BytesIO(out))
|
||||||
status = ffprobe.wait()
|
status = ffprobe.wait()
|
||||||
if status != 0:
|
if status != 0:
|
||||||
logger.error('ffprobe failed with status code: %d', status)
|
raise ExternalToolError(f"ffprobe failed with status code: {status:d}")
|
||||||
return None
|
|
||||||
|
|
||||||
# Sort frames by timestamp
|
# Sort frames by timestamp
|
||||||
tmp = {}
|
tmp = {}
|
||||||
@@ -285,8 +282,6 @@ def get_frames_in_stream(ffprobe_path: str, input_file: IO[bytes], begin:timedel
|
|||||||
frames = frames['frames']
|
frames = frames['frames']
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
ts = get_ts_frame(frame)
|
ts = get_ts_frame(frame)
|
||||||
if ts is None:
|
|
||||||
return None
|
|
||||||
if begin <= ts <= end:
|
if begin <= ts <= end:
|
||||||
tmp[ts]=frame
|
tmp[ts]=frame
|
||||||
|
|
||||||
@@ -322,15 +317,12 @@ def get_nearest_idr_frame(ffprobe_path: str, input_file: IO[bytes], timestamp:ti
|
|||||||
frames = json.load(BytesIO(out))
|
frames = json.load(BytesIO(out))
|
||||||
status = ffprobe.wait()
|
status = ffprobe.wait()
|
||||||
if status != 0:
|
if status != 0:
|
||||||
logger.error('ffprobe failed with status code: %d', status)
|
raise ExternalToolError(f"ffprobe failed with status code: {status:d}")
|
||||||
return
|
|
||||||
|
|
||||||
if 'frames' in frames:
|
if 'frames' in frames:
|
||||||
frames = frames['frames']
|
frames = frames['frames']
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
ts = get_ts_frame(frame)
|
ts = get_ts_frame(frame)
|
||||||
if ts is None:
|
|
||||||
return
|
|
||||||
if tbegin <= ts <= tend:
|
if tbegin <= ts <= tend:
|
||||||
idrs.append(frame)
|
idrs.append(frame)
|
||||||
else:
|
else:
|
||||||
@@ -371,8 +363,9 @@ def get_nearest_iframe(ffprobe_path:str, input_file: IO[bytes],
|
|||||||
|
|
||||||
found = False
|
found = False
|
||||||
for frame in iframes:
|
for frame in iframes:
|
||||||
ts = get_ts_frame(frame)
|
try:
|
||||||
if ts is None:
|
ts = get_ts_frame(frame)
|
||||||
|
except InvalidMediaError:
|
||||||
logger.warning('I-frame with no timestamp: %s', frame)
|
logger.warning('I-frame with no timestamp: %s', frame)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -397,8 +390,9 @@ def get_nearest_iframe(ffprobe_path:str, input_file: IO[bytes],
|
|||||||
return None
|
return None
|
||||||
nb_frames = 0
|
nb_frames = 0
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
ts = get_ts_frame(frame)
|
try:
|
||||||
if ts is None:
|
ts = get_ts_frame(frame)
|
||||||
|
except InvalidMediaError:
|
||||||
logger.warning('Frame without timestamp: %s', frame)
|
logger.warning('Frame without timestamp: %s', frame)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ import hexdump
|
|||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from typeguard import typechecked
|
from typeguard import typechecked
|
||||||
|
|
||||||
from tscut.exceptions import InvalidMediaError
|
from tscut.exceptions import InvalidMediaError, ExternalToolError
|
||||||
from tscut.matroska.ebml import change_ebml_element_size
|
from tscut.matroska.ebml import change_ebml_element_size
|
||||||
|
|
||||||
|
|
||||||
# Found codec private data using mkvinfo
|
# Found codec private data using mkvinfo
|
||||||
@typechecked
|
@typechecked
|
||||||
def get_codec_private_data_from_mkv(mkvinfo_path:str,
|
def get_codec_private_data_from_mkv(mkvinfo_path:str,
|
||||||
input_file: IO[bytes]) -> tuple[int, bytes]|tuple[None,None]:
|
input_file: IO[bytes]) -> tuple[int, bytes]:
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
infd = input_file.fileno()
|
infd = input_file.fileno()
|
||||||
@@ -59,8 +59,7 @@ def get_codec_private_data_from_mkv(mkvinfo_path:str,
|
|||||||
data = read(infd, size)
|
data = read(infd, size)
|
||||||
return position, data
|
return position, data
|
||||||
|
|
||||||
logger.error("Impossible to retrieve codec private data from MKV !")
|
raise InvalidMediaError("Impossible to retrieve codec private data from MKV !")
|
||||||
return None, None
|
|
||||||
|
|
||||||
@typechecked
|
@typechecked
|
||||||
def parse_mkv_tree(mkvinfo_path:str, input_file: IO[bytes]) -> dict[str,tuple[int,int]]:
|
def parse_mkv_tree(mkvinfo_path:str, input_file: IO[bytes]) -> dict[str,tuple[int,int]]:
|
||||||
@@ -233,7 +232,7 @@ def extract_mkv_part(mkvmerge_path:str, input_file:IO[bytes], output_file:IO[byt
|
|||||||
for w in warnings:
|
for w in warnings:
|
||||||
logger.warning(w)
|
logger.warning(w)
|
||||||
elif status == 2:
|
elif status == 2:
|
||||||
logger.error('Extraction returns errors')
|
raise ExternalToolError("Extraction returns errors")
|
||||||
|
|
||||||
|
|
||||||
# Merge a list of mkv files passed as input, and produce a new MKV as output
|
# Merge a list of mkv files passed as input, and produce a new MKV as output
|
||||||
@@ -367,7 +366,7 @@ def extract_track_from_mkv(mkvextract_path: str, input_file: IO[bytes], index,
|
|||||||
extract.wait()
|
extract.wait()
|
||||||
|
|
||||||
if extract.returncode != 0:
|
if extract.returncode != 0:
|
||||||
logger.error('Mkvextract returns an error code: %d', extract.returncode)
|
raise ExternalToolError(f"Mkvextract returns an error code: {extract.returncode:d}")
|
||||||
else:
|
else:
|
||||||
logger.info('Track %d was succesfully extracted.', index)
|
logger.info('Track %d was succesfully extracted.', index)
|
||||||
|
|
||||||
@@ -408,7 +407,7 @@ def remove_video_tracks_from_mkv(mkvmerge_path:str, input_file: IO[bytes],
|
|||||||
remove.wait()
|
remove.wait()
|
||||||
|
|
||||||
if remove.returncode != 0:
|
if remove.returncode != 0:
|
||||||
logger.error('Mkvmerge returns an error code: %d', remove.returncode)
|
raise ExternalToolError(f"Mkvmerge returns an error code: {remove.returncode:d}")
|
||||||
else:
|
else:
|
||||||
logger.info('Video tracks were succesfully extracted.')
|
logger.info('Video tracks were succesfully extracted.')
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ from typing import IO
|
|||||||
|
|
||||||
from typeguard import typechecked
|
from typeguard import typechecked
|
||||||
|
|
||||||
from tscut.temporaries import TemporaryFiles
|
|
||||||
from tscut.exceptions import TemporaryFileError
|
from tscut.exceptions import TemporaryFileError
|
||||||
|
from tscut.temporaries import TemporaryFiles
|
||||||
|
|
||||||
|
|
||||||
@typechecked
|
@typechecked
|
||||||
def dump_ppm(pictures: bytes, prefix: str, temporaries: TemporaryFiles) -> None:
|
def dump_ppm(pictures: bytes, prefix: str, temporaries: TemporaryFiles) -> None:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from typeguard import typechecked
|
|||||||
|
|
||||||
from tscut.exceptions import InvalidMediaError
|
from tscut.exceptions import InvalidMediaError
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@typechecked
|
@typechecked
|
||||||
def parse_timestamp(ts:str) -> timedelta|None:
|
def parse_timestamp(ts:str) -> timedelta|None:
|
||||||
@@ -31,8 +32,6 @@ def parse_timestamp(ts:str) -> timedelta|None:
|
|||||||
- the timestamp string is not in the correct format
|
- the timestamp string is not in the correct format
|
||||||
- the timestamp values are out of range (e.g. hour > 23, minute > 59, etc.)
|
- the timestamp values are out of range (e.g. hour > 23, minute > 59, etc.)
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
ts_reg_exp = (r'^(?P<hour>[0-9]{1,2}):(?P<minute>[0-9]{1,2})'
|
ts_reg_exp = (r'^(?P<hour>[0-9]{1,2}):(?P<minute>[0-9]{1,2})'
|
||||||
r':(?P<second>[0-9]{1,2})(\.(?P<us>[0-9]{1,6}))?$')
|
r':(?P<second>[0-9]{1,2})(\.(?P<us>[0-9]{1,6}))?$')
|
||||||
p = re.compile(ts_reg_exp)
|
p = re.compile(ts_reg_exp)
|
||||||
@@ -91,8 +90,6 @@ def parse_time_interval(interval: str) -> tuple[timedelta, timedelta] | tuple[No
|
|||||||
- the time values are out of range (e.g. hour > 23, minute > 59, etc.)
|
- the time values are out of range (e.g. hour > 23, minute > 59, etc.)
|
||||||
- the end time is before the start time (non-monotonic interval)
|
- the end time is before the start time (non-monotonic interval)
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
interval_reg_exp = (r'^(?P<hour1>[0-9]{1,2}):(?P<minute1>[0-9]{1,2}):(?P<second1>[0-9]{1,2})'
|
interval_reg_exp = (r'^(?P<hour1>[0-9]{1,2}):(?P<minute1>[0-9]{1,2}):(?P<second1>[0-9]{1,2})'
|
||||||
r'(\.(?P<ms1>[0-9]{1,3}))?-(?P<hour2>[0-9]{1,2}):(?P<minute2>[0-9]{1,2})'
|
r'(\.(?P<ms1>[0-9]{1,3}))?-(?P<hour2>[0-9]{1,2}):(?P<minute2>[0-9]{1,2})'
|
||||||
r':(?P<second2>[0-9]{1,2})(\.(?P<ms2>[0-9]{1,3}))?$')
|
r':(?P<second2>[0-9]{1,2})(\.(?P<ms2>[0-9]{1,3}))?$')
|
||||||
@@ -194,23 +191,18 @@ def compare_time_interval(interval1: tuple[timedelta, timedelta],
|
|||||||
|
|
||||||
|
|
||||||
@typechecked
|
@typechecked
|
||||||
def get_ts_frame(frame: dict) -> timedelta|None:
|
def get_ts_frame(frame: dict) -> timedelta:
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
if 'pts_time' in frame:
|
if 'pts_time' in frame:
|
||||||
pts_time = float(frame['pts_time'])
|
pts_time = float(frame['pts_time'])
|
||||||
elif 'pkt_pts_time' in frame:
|
elif 'pkt_pts_time' in frame:
|
||||||
pts_time = float(frame['pkt_pts_time'])
|
pts_time = float(frame['pkt_pts_time'])
|
||||||
else:
|
else:
|
||||||
logger.error('Impossible to find timestamp of frame %s', frame)
|
raise InvalidMediaError(f"Impossible to find timestamp of frame {frame}")
|
||||||
return None
|
|
||||||
|
|
||||||
return timedelta(seconds=pts_time)
|
return timedelta(seconds=pts_time)
|
||||||
|
|
||||||
@typechecked
|
@typechecked
|
||||||
def get_packet_duration(packet: dict) -> int:
|
def get_packet_duration(packet: dict) -> int:
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
if 'duration' in packet:
|
if 'duration' in packet:
|
||||||
duration = int(packet['duration'])
|
duration = int(packet['duration'])
|
||||||
elif 'pkt_duration' in packet:
|
elif 'pkt_duration' in packet:
|
||||||
|
|||||||
Reference in New Issue
Block a user