added additional files
This commit is contained in:
214
old/scene_cutter_old.py
Normal file
214
old/scene_cutter_old.py
Normal file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
def get_video_duration(video_path):
|
||||
"""Gets the duration of a video file in seconds using ffprobe."""
|
||||
command = [
|
||||
'ffprobe',
|
||||
'-v', 'error',
|
||||
'-show_entries', 'format=duration',
|
||||
'-of', 'default=noprint_wrappers=1:nokey=1',
|
||||
video_path
|
||||
]
|
||||
try:
|
||||
result = subprocess.run(command, capture_output=True, text=True, check=True)
|
||||
return float(result.stdout.strip())
|
||||
except FileNotFoundError:
|
||||
print("\nError: 'ffprobe' command not found. Is it installed and in your system's PATH?")
|
||||
sys.exit(1)
|
||||
except (subprocess.CalledProcessError, ValueError) as e:
|
||||
print(f"\nError getting video duration: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def get_best_hwaccel():
|
||||
"""
|
||||
Checks for available FFmpeg hardware acceleration methods and returns the best one.
|
||||
Priority is defined as: cuda > qsv > dxva2.
|
||||
Returns the name of the best available method, or None if none are found.
|
||||
"""
|
||||
priority = ['cuda', 'qsv', 'dxva2']
|
||||
print("Checking for available hardware acceleration...")
|
||||
try:
|
||||
# Run ffmpeg to list available hardware acceleration methods
|
||||
result = subprocess.run(
|
||||
['ffmpeg', '-hide_banner', '-hwaccels'],
|
||||
capture_output=True, text=True, encoding='utf-8', check=False
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("Warning: Could not query FFmpeg for hardware acceleration. Using software decoding.")
|
||||
return None
|
||||
|
||||
# Parse the output to get a list of available methods
|
||||
available_methods = result.stdout.strip().split('\n')
|
||||
if len(available_methods) > 1:
|
||||
available_methods = available_methods[1:]
|
||||
|
||||
# Check for our preferred methods in order of priority
|
||||
for method in priority:
|
||||
if method in available_methods:
|
||||
print(f"Found best available hardware acceleration: {method}")
|
||||
return method
|
||||
|
||||
print("No high-priority hardware acceleration (cuda, qsv, dxva2) found. Using software decoding.")
|
||||
return None
|
||||
|
||||
except FileNotFoundError:
|
||||
# This will be handled by the main ffmpeg call later, so we just return None.
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Warning: An error occurred while checking for hwaccel: {e}. Using software decoding.")
|
||||
return None
|
||||
|
||||
def cut_video_into_scenes(video_path, max_segment_length, hwaccel=None):
|
||||
"""
|
||||
Cuts a video into segments based on a .scenes.json file using FFmpeg's segment muxer,
|
||||
ensuring no segment exceeds a maximum length.
|
||||
|
||||
Args:
|
||||
video_path (str): The path to the input video file.
|
||||
max_segment_length (int): The maximum allowed length for any segment in seconds.
|
||||
hwaccel (str, optional): The hardware acceleration method to use for decoding.
|
||||
"""
|
||||
print(f"\nProcessing video: {os.path.basename(video_path)}")
|
||||
|
||||
# 1. Derive the .scenes.json path and check for its existence
|
||||
base_name = os.path.splitext(video_path)[0]
|
||||
json_path = f"{base_name}.scenes.json"
|
||||
|
||||
if not os.path.isfile(json_path):
|
||||
print(f"\nError: Scene file not found at '{json_path}'")
|
||||
print("Please run scene_detector.py on the video first.")
|
||||
sys.exit(1)
|
||||
|
||||
# 2. Load the scene timestamps from the JSON file
|
||||
try:
|
||||
with open(json_path, 'r') as f:
|
||||
scene_timestamps = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
print(f"\nError: Could not parse the JSON file at '{json_path}'. It may be corrupted.")
|
||||
sys.exit(1)
|
||||
|
||||
if not scene_timestamps:
|
||||
print("Warning: The scene file is empty. No segments to cut.")
|
||||
return
|
||||
|
||||
# Get total video duration to handle the last segment correctly
|
||||
print("Getting video duration...")
|
||||
video_duration = get_video_duration(video_path)
|
||||
print(f"Video duration: {video_duration:.2f} seconds.")
|
||||
|
||||
# 3. Create the output directory
|
||||
output_dir = "cuts"
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
print(f"Output will be saved to the '{output_dir}' directory.")
|
||||
|
||||
if hwaccel:
|
||||
print(f"Using hardware acceleration for decoding: {hwaccel}")
|
||||
|
||||
# 4. Process timestamps to enforce max segment length
|
||||
print(f"Enforcing maximum segment length of {max_segment_length} seconds...")
|
||||
|
||||
# Define all segment boundaries, starting at 0 and ending at the video's duration
|
||||
segment_boundaries = [0.0] + sorted(list(set(scene_timestamps))) + [video_duration]
|
||||
|
||||
final_cut_points = []
|
||||
for i in range(len(segment_boundaries) - 1):
|
||||
start = segment_boundaries[i]
|
||||
end = segment_boundaries[i+1]
|
||||
|
||||
current_time = start
|
||||
while (end - current_time) > max_segment_length:
|
||||
current_time += max_segment_length
|
||||
final_cut_points.append(current_time)
|
||||
|
||||
final_cut_points.append(end)
|
||||
|
||||
# Clean up the final list: sort, remove duplicates, and remove the final cut at the very end
|
||||
final_cut_points = sorted(list(set(final_cut_points)))
|
||||
if final_cut_points and final_cut_points[-1] >= video_duration - 0.1: # Use a small tolerance
|
||||
final_cut_points.pop()
|
||||
|
||||
print(f"Original scenes: {len(scene_timestamps)}. Total segments after splitting: {len(final_cut_points) + 1}.")
|
||||
|
||||
# 5. Prepare arguments for the segment muxer
|
||||
segment_times_str = ",".join(map(str, final_cut_points))
|
||||
video_basename = os.path.basename(base_name)
|
||||
output_pattern = os.path.join(output_dir, f"{video_basename}_segment%03d.mkv")
|
||||
|
||||
# 6. Build the single FFmpeg command
|
||||
command = ['ffmpeg', '-hide_banner']
|
||||
if hwaccel:
|
||||
command.extend(['-hwaccel', hwaccel])
|
||||
|
||||
command.extend([
|
||||
'-i', video_path,
|
||||
'-c:v', 'utvideo',
|
||||
'-an', '-sn', '-dn',
|
||||
'-map_metadata', '-1',
|
||||
'-map_chapters', '-1',
|
||||
'-f', 'segment',
|
||||
'-segment_times', segment_times_str,
|
||||
'-segment_start_number', '1',
|
||||
'-reset_timestamps', '1',
|
||||
output_pattern
|
||||
])
|
||||
|
||||
print("\nStarting FFmpeg to cut all segments in a single pass...")
|
||||
try:
|
||||
# Use Popen to show FFmpeg's progress in real-time
|
||||
process = subprocess.Popen(command, stderr=subprocess.PIPE, text=True, encoding='utf-8')
|
||||
|
||||
for line in iter(process.stderr.readline, ''):
|
||||
if line.strip().startswith('frame='):
|
||||
sys.stderr.write(f'\r{line.strip()}')
|
||||
sys.stderr.flush()
|
||||
|
||||
process.wait()
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
|
||||
if process.returncode != 0:
|
||||
print(f"\nWarning: FFmpeg may have encountered an error (exit code: {process.returncode}).")
|
||||
else:
|
||||
total_segments = len(final_cut_points) + 1
|
||||
print(f"\nSuccessfully created {total_segments} segments in the '{output_dir}' directory.")
|
||||
|
||||
except FileNotFoundError:
|
||||
print("\nError: 'ffmpeg' command not found. Is it installed and in your system's PATH?")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\nAn error occurred during cutting: {e}")
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to handle command-line arguments.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Automatically detects the best hardware decoder and cuts a video into scene-based segments using a corresponding '.scenes.json' file."
|
||||
)
|
||||
parser.add_argument("video_path", help="Path to the video file.")
|
||||
parser.add_argument(
|
||||
'--segment-length',
|
||||
type=int,
|
||||
default=10,
|
||||
help='Maximum length of a segment in seconds. Default: 10'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isfile(args.video_path):
|
||||
print(f"Error: Input file not found: '{args.video_path}'")
|
||||
sys.exit(1)
|
||||
|
||||
# Automatically determine the best hardware acceleration method
|
||||
hwaccel_method = get_best_hwaccel()
|
||||
|
||||
cut_video_into_scenes(args.video_path, args.segment_length, hwaccel_method)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
147
old/scene_detector_old.py
Normal file
147
old/scene_detector_old.py
Normal file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
def get_best_hwaccel():
|
||||
"""
|
||||
Checks for available FFmpeg hardware acceleration methods and returns the best one.
|
||||
Priority is defined as: cuda > qsv > dxva2.
|
||||
Returns the name of the best available method, or None if none are found.
|
||||
"""
|
||||
priority = ['cuda', 'qsv', 'dxva2']
|
||||
print("Checking for available hardware acceleration...")
|
||||
try:
|
||||
# Run ffmpeg to list available hardware acceleration methods
|
||||
result = subprocess.run(
|
||||
['ffmpeg', '-hide_banner', '-hwaccels'],
|
||||
capture_output=True, text=True, encoding='utf-8', check=False
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("Warning: Could not query FFmpeg for hardware acceleration. Using software decoding.")
|
||||
return None
|
||||
|
||||
# Parse the output to get a list of available methods
|
||||
available_methods = result.stdout.strip().split('\n')
|
||||
if len(available_methods) > 1:
|
||||
available_methods = available_methods[1:]
|
||||
|
||||
# Check for our preferred methods in order of priority
|
||||
for method in priority:
|
||||
if method in available_methods:
|
||||
print(f"Found best available hardware acceleration: {method}")
|
||||
return method
|
||||
|
||||
print("No high-priority hardware acceleration (cuda, qsv, dxva2) found. Using software decoding.")
|
||||
return None
|
||||
|
||||
except FileNotFoundError:
|
||||
# This will be handled by the main ffmpeg call later, so we just return None.
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Warning: An error occurred while checking for hwaccel: {e}. Using software decoding.")
|
||||
return None
|
||||
|
||||
def detect_scenes(video_path, hwaccel=None, threshold=0.4):
|
||||
"""
|
||||
Uses FFmpeg to detect scene changes in a video and saves the timestamps to a JSON file.
|
||||
"""
|
||||
print(f"Starting scene detection for: {os.path.basename(video_path)}")
|
||||
|
||||
# Define the output JSON file path based on the input video name
|
||||
base_name = os.path.splitext(video_path)[0]
|
||||
json_output_path = f"{base_name}.scenes.json"
|
||||
|
||||
# Base FFmpeg command
|
||||
command = ['ffmpeg', '-hide_banner']
|
||||
|
||||
# If hardware acceleration is specified, add the relevant flags
|
||||
if hwaccel:
|
||||
print(f"Attempting to use hardware acceleration: {hwaccel}")
|
||||
command.extend(['-hwaccel', hwaccel])
|
||||
|
||||
# Add the rest of the command arguments
|
||||
# The filter string is now built dynamically with the specified threshold
|
||||
filter_string = f"select='gt(scene,{threshold})',showinfo"
|
||||
command.extend([
|
||||
'-i', video_path,
|
||||
'-vf', filter_string,
|
||||
'-f', 'null',
|
||||
'-'
|
||||
])
|
||||
|
||||
# Execute the FFmpeg command
|
||||
try:
|
||||
# Use Popen to start the process and read its output in real-time
|
||||
process = subprocess.Popen(command, stderr=subprocess.PIPE, text=True, encoding='utf-8')
|
||||
|
||||
scene_timestamps = []
|
||||
|
||||
# Read stderr line by line as it's produced
|
||||
for line in iter(process.stderr.readline, ''):
|
||||
# Check for scene detection output (which contains pts_time)
|
||||
if 'pts_time:' in line:
|
||||
timestamp_str = line.split('pts_time:')[1].split()[0]
|
||||
scene_timestamps.append(float(timestamp_str))
|
||||
# Check for ffmpeg's progress line (which starts with 'frame=')
|
||||
# and print it to the actual stderr to show progress
|
||||
elif line.strip().startswith('frame='):
|
||||
# Use sys.stderr to avoid interfering with stdout prints
|
||||
# Use '\r' to have the line overwrite itself, mimicking ffmpeg's behavior
|
||||
sys.stderr.write(f'\r{line.strip()}')
|
||||
sys.stderr.flush()
|
||||
|
||||
# Wait for the process to finish and get the return code
|
||||
process.wait()
|
||||
|
||||
# Print a newline to move past the progress bar
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.flush()
|
||||
|
||||
if process.returncode != 0:
|
||||
print(f"\nWarning: FFmpeg may have encountered an error (exit code: {process.returncode}).")
|
||||
|
||||
if not scene_timestamps:
|
||||
print("Warning: No scenes were detected. The output file will be empty.")
|
||||
|
||||
# Save the detected scenes to a JSON file
|
||||
with open(json_output_path, 'w') as json_file:
|
||||
json.dump(scene_timestamps, json_file, indent=4)
|
||||
|
||||
print(f"Scene detection completed. {len(scene_timestamps)} scenes found.")
|
||||
print(f"Results saved to: {json_output_path}")
|
||||
|
||||
except FileNotFoundError:
|
||||
print("Error: 'ffmpeg' command not found. Is it installed and in your system's PATH?")
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to handle command-line arguments.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description="Detect scene changes in a video using FFmpeg.")
|
||||
parser.add_argument("video_path", help="Path to the video file.")
|
||||
parser.add_argument(
|
||||
"-t", "--threshold",
|
||||
type=float,
|
||||
default=0.4,
|
||||
help="Set the scene detection threshold (0.0 to 1.0). Lower is more sensitive. Default: 0.4"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isfile(args.video_path):
|
||||
print(f"Error: Input file not found: '{args.video_path}'")
|
||||
sys.exit(1)
|
||||
|
||||
# Automatically determine the best hardware acceleration method
|
||||
hwaccel_method = get_best_hwaccel()
|
||||
|
||||
detect_scenes(args.video_path, hwaccel_method, args.threshold)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user