Add a dedicated class for temporary files management.
This commit is contained in:
+6
-1
@@ -15,6 +15,7 @@ import coloredlogs
|
||||
from tscut.exceptions import TSCutError
|
||||
from tscut.models import ProcessingOptions
|
||||
from tscut.pipeline import process_recording
|
||||
from tscut.temporaries import TemporaryFiles
|
||||
from tscut.tools.discovery import check_required_tools
|
||||
from tscut.tools.timeframe import compare_time_interval, parse_time_interval
|
||||
|
||||
@@ -110,9 +111,13 @@ def main() -> int:
|
||||
|
||||
logger.debug("Configuration: %s", config)
|
||||
try:
|
||||
process_recording(config)
|
||||
temporaries = TemporaryFiles(config.keep_temporaries)
|
||||
process_recording(config, temporaries)
|
||||
except TSCutError as exc:
|
||||
logger.error("%s", exc)
|
||||
return 1
|
||||
finally:
|
||||
logger.info("Cleaning temporary files")
|
||||
temporaries.cleanup()
|
||||
|
||||
return 0
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
import logging
|
||||
from typing import IO
|
||||
|
||||
from tscut.tools.mkvtoolnix import extract_mkv_part
|
||||
from tscut.exceptions import TemporaryFileError
|
||||
from tscut.tools.mkvtoolnix import extract_mkv_part
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,4 +27,4 @@ def do_coarse_processing(ffmpeg_path:str, ffprobe_path:str, mkvmerge_path:str,
|
||||
extract_mkv_part(mkvmerge_path=mkvmerge_path, input_file=input_file, output_file=internal_mkv,
|
||||
begin=begin, end=end)
|
||||
|
||||
temporaries.append(internal_mkv)
|
||||
temporaries.add(internal_mkv)
|
||||
|
||||
+11
-10
@@ -12,7 +12,7 @@ from os import (
|
||||
write,
|
||||
)
|
||||
from shutil import copyfile
|
||||
from typing import IO, Any, BinaryIO, Sequence, TextIO
|
||||
from typing import BinaryIO, Sequence, TextIO
|
||||
|
||||
import hexdump
|
||||
from tqdm import tqdm
|
||||
@@ -22,6 +22,7 @@ from tscut.exceptions import InvalidMediaError, TemporaryFileError
|
||||
from tscut.h264.avc import get_avc_config_from_h264
|
||||
from tscut.matroska.codec import dump_codec_private_data
|
||||
from tscut.models import CutResult, PreparedMedia, ProcessingOptions
|
||||
from tscut.temporaries import TemporaryFiles
|
||||
from tscut.tools.ffmpeg import extract_all_streams
|
||||
from tscut.tools.ffprobe import (
|
||||
get_nearest_iframe,
|
||||
@@ -94,7 +95,7 @@ def concatenate_h264_ts_parts(h264_ts_parts: Sequence[TextIO], output: TextIO) -
|
||||
first = False
|
||||
|
||||
def cut_recording(media: PreparedMedia, options: ProcessingOptions,
|
||||
temporaries: list[IO[Any]]) -> CutResult:
|
||||
temporaries: TemporaryFiles) -> CutResult:
|
||||
|
||||
# Pour chaque portion
|
||||
partnum = 0
|
||||
@@ -243,10 +244,10 @@ def cut_recording(media: PreparedMedia, options: ProcessingOptions,
|
||||
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)
|
||||
temporaries.add(internal_mkv)
|
||||
temporaries.add(internal_h264)
|
||||
temporaries.add(internal_h264_ts)
|
||||
temporaries.add(internal_novideo_mkv)
|
||||
|
||||
h264parts.append(internal_h264)
|
||||
h264_ts.append(internal_h264_ts)
|
||||
@@ -280,7 +281,7 @@ def cut_recording(media: PreparedMedia, options: ProcessingOptions,
|
||||
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)
|
||||
temporaries.add(part)
|
||||
|
||||
pos = pos+tail_iframe_ts-ts1
|
||||
|
||||
@@ -301,7 +302,7 @@ def cut_recording(media: PreparedMedia, options: ProcessingOptions,
|
||||
|
||||
logger.info('Merging all H264 tracks')
|
||||
concatenate_h264_parts(h264parts=h264parts, output=full_h264)
|
||||
temporaries.append(full_h264)
|
||||
temporaries.add(full_h264)
|
||||
|
||||
try:
|
||||
full_h264_ts = open(f'{media.basename}-ts.txt', 'w+', encoding='utf8')
|
||||
@@ -310,7 +311,7 @@ def cut_recording(media: PreparedMedia, options: ProcessingOptions,
|
||||
|
||||
logger.info('Merging H264 timestamps')
|
||||
concatenate_h264_ts_parts(h264_ts_parts=h264_ts, output=full_h264_ts)
|
||||
temporaries.append(full_h264_ts)
|
||||
temporaries.add(full_h264_ts)
|
||||
|
||||
final_novideo_name = f'{media.basename}-novideo.mkv'
|
||||
final_with_video_name = f'{media.basename}-video.mkv'
|
||||
@@ -331,7 +332,7 @@ def cut_recording(media: PreparedMedia, options: ProcessingOptions,
|
||||
except OSError:
|
||||
raise TemporaryFileError(f"Impossible to open file: {final_novideo_name}")
|
||||
|
||||
temporaries.append(final_novideo)
|
||||
temporaries.add(final_novideo)
|
||||
|
||||
full_h264_ts.seek(0)
|
||||
|
||||
|
||||
+1
-1
@@ -57,5 +57,5 @@ class PreparedMedia:
|
||||
@dataclass
|
||||
class CutResult:
|
||||
filename: str
|
||||
movie: BinaryIO
|
||||
movie: IO[bytes]
|
||||
check_positions: list[timedelta]
|
||||
|
||||
+9
-19
@@ -5,9 +5,8 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from os import unlink
|
||||
from shutil import copyfile, move
|
||||
from typing import IO, Any, BinaryIO
|
||||
from typing import BinaryIO
|
||||
|
||||
import hexdump
|
||||
|
||||
@@ -17,6 +16,7 @@ from tscut.h264.avc import parse_codec_private
|
||||
from tscut.matroska.codec import dump_codec_private_data
|
||||
from tscut.models import PreparedMedia, ProcessingOptions, SupportedFormat
|
||||
from tscut.subtitles.ocr import do_ocr, extract_srt, get_tesseract_supported_lang
|
||||
from tscut.temporaries import TemporaryFiles
|
||||
from tscut.tools.ffmpeg import ffmpeg_convert
|
||||
from tscut.tools.ffprobe import (
|
||||
find_subtitles_tracks,
|
||||
@@ -42,10 +42,8 @@ def detect_supported_format(format_name: str) -> SupportedFormat:
|
||||
f"Unsupported media format: {format_name}"
|
||||
)
|
||||
|
||||
def process_recording(options: ProcessingOptions) -> None:
|
||||
def process_recording(options: ProcessingOptions, temporaries: TemporaryFiles) -> None:
|
||||
nb_parts = len(options.parts)
|
||||
temporaries : list[IO[Any]] = []
|
||||
|
||||
basename = os.path.splitext(os.path.basename(options.input_file))[0]
|
||||
mp4filename = basename+'.mp4'
|
||||
mkvfilename = basename+'.mkv'
|
||||
@@ -88,7 +86,7 @@ def process_recording(options: ProcessingOptions) -> None:
|
||||
with open(mp4filename, 'wb+') as mp4:
|
||||
ffmpeg_convert(options.tools_paths['ffmpeg'], options.tools_paths['ffprobe'],
|
||||
input_file, 'mpegts', mp4, 'mp4', duration)
|
||||
temporaries.append(mp4)
|
||||
temporaries.add(mp4)
|
||||
logger.info("Converting MP4 to MKV.")
|
||||
try:
|
||||
mkv = open(mkvfilename, 'wb+')
|
||||
@@ -98,7 +96,7 @@ def process_recording(options: ProcessingOptions) -> None:
|
||||
ffmpeg_convert(options.tools_paths['ffmpeg'], options.tools_paths['ffprobe'],
|
||||
mp4, 'mp4', mkv, 'matroska', duration)
|
||||
if nb_parts > 0:
|
||||
temporaries.append(mkv)
|
||||
temporaries.add(mkv)
|
||||
except OSError:
|
||||
raise TemporaryFileError(f"Impossible to create: {mp4filename}")
|
||||
|
||||
@@ -112,7 +110,7 @@ def process_recording(options: ProcessingOptions) -> None:
|
||||
ffmpeg_convert(options.tools_paths['ffmpeg'], options.tools_paths['ffprobe'],
|
||||
input_file, 'mp4', mkv, 'matroska', duration)
|
||||
if nb_parts > 0:
|
||||
temporaries.append(mkv)
|
||||
temporaries.add(mkv)
|
||||
else:
|
||||
logger.info("Already in MKV")
|
||||
mkv = input_file
|
||||
@@ -198,7 +196,7 @@ def process_recording(options: ProcessingOptions) -> None:
|
||||
move(final_with_video_name, options.output_file)
|
||||
else:
|
||||
# Final cut is not any more the final step.
|
||||
temporaries.append(final_with_video)
|
||||
temporaries.add(final_with_video)
|
||||
duration = get_movie_duration(options.tools_paths['ffprobe'], final_with_video)
|
||||
supported_langs = get_tesseract_supported_lang(options.tools_paths['tesseract'])
|
||||
logger.info('Supported lang: %s', supported_langs)
|
||||
@@ -238,8 +236,8 @@ def process_recording(options: ProcessingOptions) -> None:
|
||||
except OSError:
|
||||
raise TemporaryFileError(f"Impossible to open {sub_name}")
|
||||
|
||||
temporaries.append(idx)
|
||||
temporaries.append(sub)
|
||||
temporaries.add(idx)
|
||||
temporaries.add(sub)
|
||||
|
||||
ocr = do_ocr(options.tools_paths['vobsubocr'], list_of_subtitles, duration,
|
||||
temporaries, options.dump_memory)
|
||||
@@ -253,14 +251,6 @@ def process_recording(options: ProcessingOptions) -> None:
|
||||
else:
|
||||
move(final_with_video_name, options.output_file)
|
||||
|
||||
if not options.keep_temporaries:
|
||||
logger.info("Cleaning temporary files")
|
||||
for tmp in temporaries:
|
||||
path = os.path.realpath(tmp.name)
|
||||
logger.info("Removing: %s", path)
|
||||
tmp.close()
|
||||
unlink(path)
|
||||
|
||||
d = datetime(1,1,1)
|
||||
for c in checks:
|
||||
logger.info("Please check cut smoothness at %s", (c+d).strftime("%H:%M:%S"))
|
||||
|
||||
@@ -17,7 +17,7 @@ from os import (
|
||||
write,
|
||||
)
|
||||
from subprocess import PIPE, Popen
|
||||
from typing import IO, Any, Sequence
|
||||
from typing import Sequence
|
||||
|
||||
from iso639 import Lang
|
||||
from iso639.exceptions import InvalidLanguageValue
|
||||
@@ -25,6 +25,7 @@ from tqdm import tqdm
|
||||
from typeguard import typechecked
|
||||
|
||||
from tscut.exceptions import ExternalToolError
|
||||
from tscut.temporaries import TemporaryFiles
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -130,7 +131,7 @@ def extract_srt(mkvextract:str, filename:str, subtitles:dict[str, list[int]],
|
||||
|
||||
@typechecked
|
||||
def do_ocr(vobsubocr:str, idxs: Sequence[tuple[str,str,str,str]], duration:timedelta,
|
||||
temporaries:list[IO[Any]], dump_mem_fd:bool=False):
|
||||
temporaries:TemporaryFiles, dump_mem_fd:bool=False):
|
||||
res = []
|
||||
|
||||
for idx_name, _, lang, iso in idxs:
|
||||
@@ -177,7 +178,7 @@ def do_ocr(vobsubocr:str, idxs: Sequence[tuple[str,str,str,str]], duration:timed
|
||||
pos = 0
|
||||
while pos < srt_length:
|
||||
pos+=write(outfd, buf[pos:])
|
||||
temporaries.append(dump_srt)
|
||||
temporaries.add(dump_srt)
|
||||
except OSError:
|
||||
logger.error('Impossible to create file: %s', srtname)
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Copyright (C) 2026 Frédéric Tronel
|
||||
|
||||
"""A class to manage (and clean) all temporary files created during conversion"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import IO, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TemporaryFiles:
|
||||
def __init__(self, keep: bool = False) -> None:
|
||||
self._files: list[IO[Any]] = []
|
||||
self._keep = keep
|
||||
|
||||
def add(self, file: IO[Any]) -> None:
|
||||
self._files.append(file)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
if self._keep:
|
||||
return
|
||||
|
||||
for f in self._files:
|
||||
path = os.path.realpath(f.name)
|
||||
logger.info("Removing: %s", path)
|
||||
f.close()
|
||||
os.unlink(path)
|
||||
Reference in New Issue
Block a user