Paws/PythonScripts/trim-videos.py

151 lines
5.2 KiB
Python
Raw Normal View History

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()