| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
|
|
| import os |
| import json |
| import subprocess |
| import logging |
| import uuid |
| from services.file_management import download_file |
| from services.cloud_storage import upload_file |
| from config import LOCAL_STORAGE_PATH |
|
|
| |
| logger = logging.getLogger(__name__) |
| logging.basicConfig(level=logging.INFO) |
|
|
| def time_to_seconds(time_str): |
| """ |
| Convert a time string in format HH:MM:SS[.mmm] to seconds. |
| |
| Args: |
| time_str (str): Time string |
| |
| Returns: |
| float: Time in seconds |
| """ |
| try: |
| parts = time_str.split(':') |
| if len(parts) == 3: |
| hours, minutes, seconds = parts |
| return int(hours) * 3600 + int(minutes) * 60 + float(seconds) |
| elif len(parts) == 2: |
| minutes, seconds = parts |
| return int(minutes) * 60 + float(seconds) |
| else: |
| return float(time_str) |
| except ValueError: |
| raise ValueError(f"Invalid time format: {time_str}. Expected HH:MM:SS[.mmm]") |
|
|
| def split_video(video_url, splits, job_id=None, video_codec='libx264', video_preset='medium', |
| video_crf=23, audio_codec='aac', audio_bitrate='128k'): |
| """ |
| Splits a video file into multiple segments with customizable encoding settings. |
| |
| Args: |
| video_url (str): URL of the video file to split |
| splits (list): List of dictionaries with 'start' and 'end' timestamps |
| job_id (str, optional): Unique job identifier |
| video_codec (str, optional): Video codec to use for encoding (default: 'libx264') |
| video_preset (str, optional): Encoding preset for speed/quality tradeoff (default: 'medium') |
| video_crf (int, optional): Constant Rate Factor for quality (0-51, default: 23) |
| audio_codec (str, optional): Audio codec to use for encoding (default: 'aac') |
| audio_bitrate (str, optional): Audio bitrate (default: '128k') |
| |
| Returns: |
| tuple: (list of output file paths, input file path) |
| """ |
| logger.info(f"Starting video split operation for {video_url}") |
| if not job_id: |
| job_id = str(uuid.uuid4()) |
| |
| input_filename = download_file(video_url, os.path.join(LOCAL_STORAGE_PATH, f"{job_id}_input")) |
| logger.info(f"Downloaded video to local file: {input_filename}") |
| |
| output_files = [] |
| |
| try: |
| |
| _, ext = os.path.splitext(input_filename) |
| |
| |
| probe_cmd = [ |
| 'ffprobe', |
| '-v', 'error', |
| '-show_entries', 'format=duration', |
| '-of', 'default=noprint_wrappers=1:nokey=1', |
| input_filename |
| ] |
| duration_result = subprocess.run(probe_cmd, capture_output=True, text=True) |
| |
| try: |
| file_duration = float(duration_result.stdout.strip()) |
| logger.info(f"File duration: {file_duration} seconds") |
| except (ValueError, IndexError): |
| logger.warning("Could not determine file duration, using a large value") |
| file_duration = 86400 |
| |
| |
| valid_splits = [] |
| for i, split in enumerate(splits): |
| try: |
| start_seconds = time_to_seconds(split['start']) |
| end_seconds = time_to_seconds(split['end']) |
| |
| |
| if start_seconds >= end_seconds: |
| logger.warning(f"Invalid split {i+1}: start time ({split['start']}) must be before end time ({split['end']}). Skipping.") |
| continue |
| |
| if start_seconds < 0: |
| logger.warning(f"Split {i+1} start time {split['start']} is negative, using 0 instead") |
| start_seconds = 0 |
| |
| if end_seconds > file_duration: |
| logger.warning(f"Split {i+1} end time {split['end']} exceeds file duration, using file duration instead") |
| end_seconds = file_duration |
| |
| |
| if start_seconds < end_seconds: |
| valid_splits.append((i, start_seconds, end_seconds, split)) |
| except ValueError as e: |
| logger.warning(f"Error processing split {i+1}: {str(e)}. Skipping.") |
| |
| if not valid_splits: |
| raise ValueError("No valid split segments specified") |
| |
| logger.info(f"Processing {len(valid_splits)} valid splits") |
| |
| |
| for index, (split_index, start_seconds, end_seconds, split_data) in enumerate(valid_splits): |
| |
| output_filename = os.path.join(LOCAL_STORAGE_PATH, f"{job_id}_split_{index+1}{ext}") |
| |
| |
| cmd = [ |
| 'ffmpeg', |
| '-i', input_filename, |
| '-ss', str(start_seconds), |
| '-to', str(end_seconds), |
| '-c:v', video_codec, |
| '-preset', video_preset, |
| '-crf', str(video_crf), |
| '-c:a', audio_codec, |
| '-b:a', audio_bitrate, |
| '-avoid_negative_ts', 'make_zero', |
| output_filename |
| ] |
| |
| logger.info(f"Running FFmpeg command for split {index+1}: {' '.join(cmd)}") |
| |
| |
| process = subprocess.run(cmd, capture_output=True, text=True) |
| |
| if process.returncode != 0: |
| logger.error(f"Error processing split {index+1}: {process.stderr}") |
| raise Exception(f"FFmpeg error for split {index+1}: {process.stderr}") |
| |
| |
| output_files.append(output_filename) |
| logger.info(f"Successfully created split {index+1}: {output_filename}") |
| |
| |
| return output_files, input_filename |
| |
| except Exception as e: |
| logger.error(f"Video split operation failed: {str(e)}") |
| |
| |
| if 'input_filename' in locals() and os.path.exists(input_filename): |
| os.remove(input_filename) |
| |
| for output_file in output_files: |
| if os.path.exists(output_file): |
| os.remove(output_file) |
| |
| raise |