import ffmpeg import random import string from datetime import datetime import os # Define target resolution target_width = 1080 target_height = 1920 def concatenate_videos(files_timestamps): processed_streams = [] for file_info in files_timestamps: file_path = file_info["path"] start_time = file_info["start"] end_time = file_info["end"] try: if end_time == "": # Create input stream from start_time to end of video stream = ffmpeg.input(file_path, ss=start_time) else: time_obj = datetime.strptime(start_time, "%M:%S.%f") start_time = time_obj.minute * 60 + time_obj.second + time_obj.microsecond / 1_000_000 time_obj = datetime.strptime(end_time, "%M:%S.%f") end_time = time_obj.minute * 60 + time_obj.second + time_obj.microsecond / 1_000_000 # Create input stream with trimming stream = ffmpeg.input(file_path, ss=start_time, t=end_time - start_time) video = stream.video audio = stream.audio # Get video metadata to calculate aspect ratio probe = ffmpeg.probe(file_path) video_stream = next(s for s in probe['streams'] if s['codec_type'] == 'video') video_width = int(video_stream['width']) video_height = int(video_stream['height']) video_aspect = video_width / video_height target_aspect = target_width / target_height print(f"Processing {file_path}: resolution={video_width}x{video_height}, aspect={video_aspect:.2f}, trimmed from {start_time}s to {end_time}s") # Scale the video to fit within target resolution while preserving aspect ratio if abs(video_aspect - target_aspect) > 0.01: # Allow small tolerance if video_aspect > target_aspect: # Video is wider: scale to target height, ensure width <= target_width video = video.filter('scale', f'min({target_width},iw*({target_height}/ih))', target_height, force_original_aspect_ratio='decrease') # Pad to target resolution, align to left for right-side padding video = video.filter('pad', target_width, target_height, 0, '(oh-ih)/2', color='black') else: # Video is taller: scale to target width, ensure height <= target_height video = video.filter('scale', target_width, f'min({target_height},ih*({target_width}/iw))', force_original_aspect_ratio='decrease') # Pad to target resolution, center vertically video = video.filter('pad', target_width, target_height, '(ow-iw)/2', '(oh-ih)/2', color='black') else: # Aspect ratio matches: scale directly to target resolution video = video.filter('scale', target_width, target_height, force_original_aspect_ratio='decrease') # Ensure consistent frame rate video = video.filter('fps', fps=30) print(f"After scaling {file_path}: target resolution={target_width}x{target_height}") processed_streams.append(video) processed_streams.append(audio) except ffmpeg.Error as e: print(f"Error processing {file_path}: {e.stderr.decode()}") continue except StopIteration: print(f"Error: No video stream found in {file_path}") continue except Exception as e: print(f"Unexpected error processing {file_path}: {str(e)}") continue # Check if we have valid streams to concatenate if not processed_streams: print("Error: No valid streams to concatenate") exit(1) # Concatenate all video and audio streams joined = ffmpeg.concat(*processed_streams, v=1, a=1).node # Output the final video output_dir = "edited_videos" if not os.path.exists(output_dir): os.makedirs(output_dir) print(f"Created directory: {output_dir}") try: # Generate a random string for the filename random_string = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) file_name = f"{output_dir}/video_edited_{random_string}.mp4" # Use .mp4 for compatibility # Perform the concatenation using ffmpeg ffmpeg.output(joined[0], joined[1], file_name, **{'c:v': 'libx264', 'c:a': 'aac'}, preset='fast').run(overwrite_output=True) print("Video concatenation successful") return file_name except ffmpeg.Error as e: print(f"Error during concatenation: {e}") return None