From c479100ac0aa8353bfc308ce8851b45cc5a4531b Mon Sep 17 00:00:00 2001 From: Aroy-Art Date: Sat, 21 Dec 2024 16:26:33 +0100 Subject: [PATCH] Python: add script for batch triming videos --- PythonScripts/trim-videos.py | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 PythonScripts/trim-videos.py diff --git a/PythonScripts/trim-videos.py b/PythonScripts/trim-videos.py new file mode 100644 index 0000000..6a9ed45 --- /dev/null +++ b/PythonScripts/trim-videos.py @@ -0,0 +1,150 @@ +import os +import subprocess +import argparse + + +def get_video_duration(input_file): + """ + Get the duration of a video file using ffprobe. + + ffprobe is a command-line tool used to analyze multimedia streams. In this function: + - `-v error`: Limits output to only errors. + - `-select_streams v:0`: Selects the first video stream. + - `-show_entries format=duration`: Outputs only the video duration. + - `-of default=noprint_wrappers=1:nokey=1`: Formats the output to display only the duration value. + """ + try: + result = subprocess.run( + [ + "ffprobe", + "-v", + "error", + "-select_streams", + "v:0", + "-show_entries", + "format=duration", + "-of", + "default=noprint_wrappers=1:nokey=1", + input_file, + ], + capture_output=True, + text=True, + check=True, + ) + return float(result.stdout.strip()) + except (subprocess.CalledProcessError, ValueError) as e: + raise RuntimeError(f"Failed to get duration for {input_file}: {e}") + + +def process_folder(input_dir, output_dir, trim_start, trim_end, supported_formats): + """Process each folder in the input directory and trim video files.""" + os.makedirs(output_dir, exist_ok=True) + + for folder in os.listdir(input_dir): + folder_path = os.path.join(input_dir, folder) + if not os.path.isdir(folder_path): + continue + + for file in os.listdir(folder_path): + if file.endswith(tuple(supported_formats)): + input_file = os.path.join(folder_path, file) + + # Extract episode number or default to folder name + # Expected folder naming convention: 'SeriesName #EpisodeNumber - Description' + # If '#' is missing, the folder name will be used as the episode identifier + if "#" in folder: + try: + episode = folder.split("#")[1].strip().split(" - ")[0] + except IndexError: + episode = folder.replace(" ", "_") + else: + episode = folder.replace(" ", "_") + + output_ext = os.path.splitext(file)[1] + output_file = os.path.join(output_dir, f"ep{episode}{output_ext}") + + try: + duration = get_video_duration(input_file) + except RuntimeError as e: + print(e) + continue + + if trim_start >= duration: + print( + f"Skipping {input_file}: trim-start ({trim_start}s) is greater than or equal to video duration ({duration}s)." + ) + continue + if duration - trim_end <= trim_start: + print( + f"Skipping {input_file}: Invalid trim range (start: {trim_start}s, end: {trim_end}s, duration: {duration}s)." + ) + continue + + end_time = duration - trim_end + + # FFmpeg command to trim the video + ffmpeg_command = [ + "ffmpeg", # Command to run FFmpeg + "-i", + input_file, # Input file path + "-ss", + f"{trim_start:.2f}", # Start trimming from this timestamp (in seconds) + "-to", + f"{end_time:.2f}", # End trimming at this timestamp (in seconds) + "-c", + "copy", # Copy codec to avoid re-encoding + output_file, # Output file path + ] + + try: + subprocess.run(ffmpeg_command, check=True) + print(f"Trimmed and saved: {output_file}") + except subprocess.CalledProcessError as e: + print(f"Failed to process {input_file}: {e}") + + +def main(): + """Main function to parse arguments and run the script.""" + parser = argparse.ArgumentParser( + description="Trim video files in folders and save them with episode numbers." + ) + parser.add_argument( + "--input", + type=str, + default=".", + help="Input directory containing folders with video files", + ) + parser.add_argument( + "--output", + type=str, + default="output", + help="Output directory for trimmed videos", + ) + parser.add_argument( + "--trim-start", + type=float, + default=46.0, + help="Number of seconds to trim from the start", + ) + parser.add_argument( + "--trim-end", + type=float, + default=16.0, + help="Number of seconds to trim from the end", + ) + parser.add_argument( + "--formats", + type=str, + default=".mp4,.webm,.mkv", + help="Comma-separated list of supported video formats eg, '.mp4,.webm,.mkv' ", + ) + + args = parser.parse_args() + supported_formats = [fmt.strip() for fmt in args.formats.split(",")] + process_folder( + args.input, args.output, args.trim_start, args.trim_end, supported_formats + ) + + +if __name__ == "__main__": + main()