Python: add script for batch triming videos
This commit is contained in:
parent
397ecaef55
commit
c479100ac0
1 changed files with 150 additions and 0 deletions
150
PythonScripts/trim-videos.py
Normal file
150
PythonScripts/trim-videos.py
Normal file
|
@ -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()
|
Loading…
Reference in a new issue