jananathbanuka
fix issues
4b12e15
# Copyright (c) 2025 Stephen G. Pope
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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
# Set up logging
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:
# Get the file extension
_, ext = os.path.splitext(input_filename)
# Get the duration of the input file
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 # 24 hours as a fallback
# Validate and process splits
valid_splits = []
for i, split in enumerate(splits):
try:
start_seconds = time_to_seconds(split['start'])
end_seconds = time_to_seconds(split['end'])
# Validate split times
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
# Only add valid splits
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")
# Process each split
for index, (split_index, start_seconds, end_seconds, split_data) in enumerate(valid_splits):
# Create output filename for this split
output_filename = os.path.join(LOCAL_STORAGE_PATH, f"{job_id}_split_{index+1}{ext}")
# Create FFmpeg command to extract the segment
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)}")
# Run the FFmpeg command
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}")
# Add the output file to the list
output_files.append(output_filename)
logger.info(f"Successfully created split {index+1}: {output_filename}")
# Return the list of output files and the input filename
return output_files, input_filename
except Exception as e:
logger.error(f"Video split operation failed: {str(e)}")
# Clean up all temporary files if they exist
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