diff --git a/README.md b/README.md index 0fccfce..5ff5e82 100644 --- a/README.md +++ b/README.md @@ -31,17 +31,7 @@ python scene_cutter.py "path/to/your/video.mkv" ``` ### 3. Encode Segments -Choose one of the encoder scripts to process the files from the `cuts/` directory. Encoded files will be placed in the `segments/` directory. - -**Option A: VMAF-based Encoding (Recommended)** -Use [`vmaf_encoder.py`](vmaf_encoder.py) to encode each segment to a target VMAF quality level. - -```bash -python vmaf_encoder.py --target-vmaf 96.0 -``` - -**Option B: Static CRF Encoding** -Use [`static_encoder.py`](static_encoder.py) to encode all segments with a single, fixed CRF value. +Use [`static_encoder.py`](static_encoder.py) to encode all segments from the `cuts/` directory with a single, fixed CRF value. Encoded files will be placed in the `segments/` directory. ```bash python static_encoder.py --crf 27 diff --git a/vmaf_encoder.py b/old/vmaf_encoder.py similarity index 100% rename from vmaf_encoder.py rename to old/vmaf_encoder.py diff --git a/scene_cutter.py b/scene_cutter.py index 12920b9..28cc8cd 100644 --- a/scene_cutter.py +++ b/scene_cutter.py @@ -66,7 +66,44 @@ def get_video_duration(video_path): # --- Core Logic Functions --- -def detect_scenes(video_path, json_output_path, hwaccel=None, threshold=0.4): +def detect_crop(video_path, hwaccel=None): + """ + Detects black bars using FFmpeg's cropdetect filter and returns the crop filter string. + Analyzes the first 60 seconds of the video for efficiency. + """ + print("\nStarting crop detection...") + command = ['ffmpeg', '-hide_banner'] + if hwaccel: + command.extend(['-hwaccel', hwaccel]) + + # Analyze a portion of the video to find crop values + command.extend(['-i', video_path, '-t', '60', '-vf', 'cropdetect', '-f', 'null', '-']) + + try: + # Using Popen to read stderr line by line + process = subprocess.Popen(command, stderr=subprocess.PIPE, text=True, encoding='utf-8') + + last_crop_line = "" + for line in iter(process.stderr.readline, ''): + if 'crop=' in line: + last_crop_line = line.strip() + + process.wait() + + if last_crop_line: + # Extract the crop filter part, e.g., "crop=1920:800:0:140" + crop_filter = last_crop_line.split('] ')[-1] + print(f"Crop detection finished. Recommended filter: {crop_filter}") + return crop_filter + else: + print("Could not determine crop. No black bars detected or an error occurred.") + return None + + except (FileNotFoundError, Exception) as e: + print(f"\nAn error occurred during crop detection: {e}") + return None + +def detect_scenes(video_path, json_output_path, hwaccel=None, threshold=0.4, crop_filter=None): """Uses FFmpeg to detect scene changes and saves timestamps to a JSON file.""" print(f"\nStarting scene detection for: {os.path.basename(video_path)}") command = ['ffmpeg', '-hide_banner'] @@ -74,7 +111,13 @@ def detect_scenes(video_path, json_output_path, hwaccel=None, threshold=0.4): print(f"Attempting to use hardware acceleration: {hwaccel}") command.extend(['-hwaccel', hwaccel]) - filter_string = f"select='gt(scene,{threshold})',showinfo" + filters = [] + if crop_filter: + print(f"Applying crop filter during scene detection: {crop_filter}") + filters.append(crop_filter) + filters.append(f"select='gt(scene,{threshold})',showinfo") + filter_string = ",".join(filters) + command.extend(['-i', video_path, '-vf', filter_string, '-f', 'null', '-']) try: @@ -105,7 +148,7 @@ def detect_scenes(video_path, json_output_path, hwaccel=None, threshold=0.4): print(f"\nAn error occurred during scene detection: {e}") return False -def cut_video_into_scenes(video_path, json_path, max_segment_length, hwaccel=None): +def cut_video_into_scenes(video_path, json_path, max_segment_length, hwaccel=None, crop_filter=None): """Cuts a video into segments, ensuring no segment exceeds a maximum length.""" print(f"\nStarting segment cutting for: {os.path.basename(video_path)}") try: @@ -148,7 +191,13 @@ def cut_video_into_scenes(video_path, json_path, max_segment_length, hwaccel=Non 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]) + command.extend(['-i', video_path]) + + if crop_filter: + print(f"Applying crop filter during cutting: {crop_filter}") + command.extend(['-vf', crop_filter]) + + command.extend(['-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: @@ -167,6 +216,11 @@ def main(): formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument("video_path", help="Path to the input video file.") + parser.add_argument( + "--autocrop", + action='store_true', + help="Automatically detect and apply cropping to remove black bars. Default: False" + ) parser.add_argument( "--so", "--sceneonly", action='store_true', @@ -195,21 +249,24 @@ def main(): json_path = f"{base_name}.scenes.json" hwaccel_method = get_best_hwaccel() + crop_filter = None + if args.autocrop: + crop_filter = detect_crop(args.video_path, hwaccel_method) if args.sceneonly: - detect_scenes(args.video_path, json_path, hwaccel_method, args.threshold) + detect_scenes(args.video_path, json_path, hwaccel_method, args.threshold, crop_filter) sys.exit(0) # --- Full Workflow (Detect if needed, then Cut) --- if not os.path.isfile(json_path): print("--- Scene file not found, running detection first ---") - if not detect_scenes(args.video_path, json_path, hwaccel_method, args.threshold): + if not detect_scenes(args.video_path, json_path, hwaccel_method, args.threshold, crop_filter): print("\nScene detection failed. Aborting process.") sys.exit(1) else: print(f"--- Found existing scene file: {os.path.basename(json_path)} ---") - cut_video_into_scenes(args.video_path, json_path, args.segtime, hwaccel_method) + cut_video_into_scenes(args.video_path, json_path, args.segtime, hwaccel_method, crop_filter) if __name__ == "__main__": main() \ No newline at end of file