aliSaac510 commited on
Commit
73b4be4
·
1 Parent(s): b7c85d5

fix pgmusic with ffmgep

Browse files
Files changed (5) hide show
  1. ffmpeg_utils.py +55 -17
  2. hybrid_processor.py +7 -17
  3. routers/video.py +1 -1
  4. schemas.py +6 -3
  5. video_processor.py +102 -23
ffmpeg_utils.py CHANGED
@@ -124,7 +124,11 @@ def extract_clip_ffmpeg(
124
  target_height: Optional[int] = None,
125
  include_audio: bool = True,
126
  style: Optional[ShortsStyle] = None,
127
- aspect_ratio: Optional[AspectRatio] = None
 
 
 
 
128
  ) -> str:
129
  """
130
  Extract video clip using FFmpeg - high speed with advanced filters
@@ -133,9 +137,19 @@ def extract_clip_ffmpeg(
133
  duration = end_time - start_time
134
  ffmpeg_exe = imageio_ffmpeg.get_ffmpeg_exe()
135
 
 
 
 
 
136
  # Build FFmpeg command
137
- # -ss AFTER -i is slightly slower but MUCH more accurate for audio mapping and sync
138
- cmd = [ffmpeg_exe, '-i', video_path, '-ss', str(start_time), '-t', str(duration)]
 
 
 
 
 
 
139
 
140
  # Resolution defaults for Shorts if not provided
141
  if not target_width or not target_height:
@@ -143,13 +157,17 @@ def extract_clip_ffmpeg(
143
 
144
  filter_complex = ""
145
 
146
- if style == ShortsStyle.CINEMATIC:
 
 
 
147
  # Cinematic Blur: BG (Blurred & Cropped) + FG (Scaled to fit width)
 
148
  filter_complex = (
149
  f"[0:v]split=2[bg_raw][fg_raw];"
150
- f"[bg_raw]scale={target_width}:{target_height}:force_original_aspect_ratio=increase,crop={target_width}:{target_height},boxblur=20:10[bg];"
151
- f"[fg_raw]scale={target_width}:-1[fg];"
152
- f"[bg][fg]overlay=(W-w)/2:(H-h)/2[v]"
153
  )
154
  elif style == ShortsStyle.SPLIT_SCREEN:
155
  # Split Screen: Top half and Bottom half
@@ -166,22 +184,42 @@ def extract_clip_ffmpeg(
166
  elif style == ShortsStyle.FIT_BARS:
167
  # Fit with black bars
168
  filter_complex = f"scale={target_width}:{target_height}:force_original_aspect_ratio=decrease,pad={target_width}:{target_height}:(ow-iw)/2:(oh-ih)/2"
 
 
 
 
169
  else:
170
- # Default: Scale to fit width
171
- filter_complex = f"scale={target_width}:-1,pad={target_width}:{target_height}:(ow-iw)/2:(oh-ih)/2"
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  if filter_complex:
174
- if "[v]" in filter_complex:
175
- # When using complex filters, we MUST map video and audio explicitly
 
 
 
 
 
 
176
  cmd.extend(['-filter_complex', filter_complex, '-map', '[v]'])
177
  if include_audio:
178
- # Always try to map audio if it exists
179
  cmd.extend(['-map', '0:a?'])
180
- else:
181
- # For simple filters
182
- cmd.extend(['-vf', filter_complex])
183
- if include_audio:
184
- cmd.extend(['-map', '0:v:0', '-map', '0:a?'])
185
 
186
  if not include_audio:
187
  cmd.extend(['-an'])
 
124
  target_height: Optional[int] = None,
125
  include_audio: bool = True,
126
  style: Optional[ShortsStyle] = None,
127
+ aspect_ratio: Optional[AspectRatio] = None,
128
+ bg_music_path: Optional[str] = None,
129
+ video_volume: float = 1.0,
130
+ music_volume: float = 0.2,
131
+ loop_music: bool = True
132
  ) -> str:
133
  """
134
  Extract video clip using FFmpeg - high speed with advanced filters
 
137
  duration = end_time - start_time
138
  ffmpeg_exe = imageio_ffmpeg.get_ffmpeg_exe()
139
 
140
+ # Get video info to check for audio stream
141
+ video_info = get_video_info_ffmpeg(video_path)
142
+ has_orig_audio = video_info.get('has_audio', False) if video_info else False
143
+
144
  # Build FFmpeg command
145
+ inputs = ['-i', video_path]
146
+ if bg_music_path and os.path.exists(bg_music_path):
147
+ if loop_music:
148
+ inputs.extend(['-stream_loop', '-1', '-i', bg_music_path])
149
+ else:
150
+ inputs.extend(['-i', bg_music_path])
151
+
152
+ cmd = [ffmpeg_exe] + inputs + ['-ss', str(start_time), '-t', str(duration)]
153
 
154
  # Resolution defaults for Shorts if not provided
155
  if not target_width or not target_height:
 
157
 
158
  filter_complex = ""
159
 
160
+ if aspect_ratio == AspectRatio.ORIGINAL or style == ShortsStyle.ORIGINAL:
161
+ # No filters, just copy/passthrough
162
+ filter_complex = ""
163
+ elif style == ShortsStyle.CINEMATIC:
164
  # Cinematic Blur: BG (Blurred & Cropped) + FG (Scaled to fit width)
165
+ # Use -2 to ensure height is divisible by 2 for yuv420p
166
  filter_complex = (
167
  f"[0:v]split=2[bg_raw][fg_raw];"
168
+ f"[bg_raw]scale={target_width}:{target_height}:force_original_aspect_ratio=increase,crop={target_width}:{target_height},boxblur=20:2[bg];"
169
+ f"[fg_raw]scale={target_width}:-2,setsar=1[fg];"
170
+ f"[bg][fg]overlay=(W-w)/2:(H-h)/2,setsar=1[v]"
171
  )
172
  elif style == ShortsStyle.SPLIT_SCREEN:
173
  # Split Screen: Top half and Bottom half
 
184
  elif style == ShortsStyle.FIT_BARS:
185
  # Fit with black bars
186
  filter_complex = f"scale={target_width}:{target_height}:force_original_aspect_ratio=decrease,pad={target_width}:{target_height}:(ow-iw)/2:(oh-ih)/2"
187
+ elif aspect_ratio != AspectRatio.ORIGINAL:
188
+ # Default for non-original ratios: Scale to fit width
189
+ # Use -2 to ensure dimensions are divisible by 2 for yuv420p
190
+ filter_complex = f"scale={target_width}:-2,pad={target_width}:{target_height}:(ow-iw)/2:(oh-ih)/2,setsar=1[v]"
191
  else:
192
+ filter_complex = ""
193
+
194
+ # Audio Filter logic
195
+ audio_filter = ""
196
+ if include_audio:
197
+ if bg_music_path and os.path.exists(bg_music_path):
198
+ if has_orig_audio:
199
+ # Mix original audio with background music
200
+ # Use unique names [a_orig] [a_bg] to avoid collisions with [v]
201
+ audio_filter = f"[0:a]volume={video_volume}[a_orig];[1:a]volume={music_volume}[a_bg];[a_orig][a_bg]amix=inputs=2:duration=first[aout]"
202
+ else:
203
+ # Only background music (original has no audio)
204
+ audio_filter = f"[1:a]volume={music_volume}[aout]"
205
+ elif has_orig_audio and video_volume != 1.0:
206
+ audio_filter = f"[0:a]volume={video_volume}[aout]"
207
 
208
  if filter_complex:
209
+ # If [v] is not the final tag, ensure it is
210
+ if "[v]" not in filter_complex:
211
+ filter_complex += "[v]"
212
+
213
+ if audio_filter:
214
+ combined_filter = f"{filter_complex};{audio_filter}"
215
+ cmd.extend(['-filter_complex', combined_filter, '-map', '[v]', '-map', '[aout]'])
216
+ else:
217
  cmd.extend(['-filter_complex', filter_complex, '-map', '[v]'])
218
  if include_audio:
 
219
  cmd.extend(['-map', '0:a?'])
220
+ elif audio_filter:
221
+ # Only audio filter
222
+ cmd.extend(['-filter_complex', audio_filter, '-map', '0:v:0', '-map', '[aout]'])
 
 
223
 
224
  if not include_audio:
225
  cmd.extend(['-an'])
hybrid_processor.py CHANGED
@@ -8,22 +8,8 @@ from typing import List, Optional, Tuple, Any
8
  from ffmpeg_utils import extract_clip_ffmpeg, get_video_info_ffmpeg
9
 
10
  def should_use_ffmpeg_processing(timestamps, custom_dims, export_audio, bg_music, output_format) -> bool:
11
- from schemas import LayoutType, ShortsStyle
12
-
13
- # If background music is requested, need MoviePy for audio mixing
14
- if bg_music:
15
- return False
16
-
17
- # We can now handle almost all ShortsStyle in FFmpeg!
18
- # These styles are now optimized in ffmpeg_utils.py
19
- if output_format and isinstance(output_format, ShortsStyle):
20
- return True
21
-
22
- # If a specific LayoutType is requested
23
- if custom_dims and hasattr(custom_dims, 'layout_type'):
24
- return True
25
-
26
- # Default fallback
27
  return True
28
 
29
  def process_single_clip_hybrid(video_path: str, start_time: float, end_time: float, clip_id: str,
@@ -59,7 +45,11 @@ def process_single_clip_hybrid(video_path: str, start_time: float, end_time: flo
59
  target_height=custom_dims.height if hasattr(custom_dims, 'height') else None,
60
  include_audio=True, # Video should always have audio if it exists
61
  style=style,
62
- aspect_ratio=aspect_ratio
 
 
 
 
63
  )
64
  else:
65
  # Use MoviePy for features (fallback)
 
8
  from ffmpeg_utils import extract_clip_ffmpeg, get_video_info_ffmpeg
9
 
10
  def should_use_ffmpeg_processing(timestamps, custom_dims, export_audio, bg_music, output_format) -> bool:
11
+ # FFmpeg can now handle background music mixing and all ShortsStyle!
12
+ # Default to True to use FFmpeg optimization for most cases
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  return True
14
 
15
  def process_single_clip_hybrid(video_path: str, start_time: float, end_time: float, clip_id: str,
 
45
  target_height=custom_dims.height if hasattr(custom_dims, 'height') else None,
46
  include_audio=True, # Video should always have audio if it exists
47
  style=style,
48
+ aspect_ratio=aspect_ratio,
49
+ bg_music_path=bg_music,
50
+ video_volume=custom_dims.video_volume if hasattr(custom_dims, 'video_volume') else 1.0,
51
+ music_volume=custom_dims.music_volume if hasattr(custom_dims, 'music_volume') else 0.2,
52
+ loop_music=custom_dims.loop_music if hasattr(custom_dims, 'loop_music') else True
53
  )
54
  else:
55
  # Use MoviePy for features (fallback)
routers/video.py CHANGED
@@ -219,7 +219,7 @@ async def process_video(
219
  background_tasks: BackgroundTasks,
220
  video: UploadFile = File(...),
221
  aspect_ratio: AspectRatio = Form(AspectRatio.RATIO_9_16, description="Select target aspect ratio"),
222
- style: ShortsStyle = Form(ShortsStyle.CINEMATIC, description="Select the visual style"),
223
  background_music: UploadFile = File(None, description="Upload an audio file for background music"),
224
  video_volume: float = Form(1.0, description="Volume of original video (0.0 to 1.0+)"),
225
  music_volume: float = Form(0.2, description="Volume of background music (0.0 to 1.0+)"),
 
219
  background_tasks: BackgroundTasks,
220
  video: UploadFile = File(...),
221
  aspect_ratio: AspectRatio = Form(AspectRatio.RATIO_9_16, description="Select target aspect ratio"),
222
+ style: ShortsStyle = Form(ShortsStyle.ORIGINAL, description="Select the visual style"),
223
  background_music: UploadFile = File(None, description="Upload an audio file for background music"),
224
  video_volume: float = Form(1.0, description="Volume of original video (0.0 to 1.0+)"),
225
  music_volume: float = Form(0.2, description="Volume of background music (0.0 to 1.0+)"),
schemas.py CHANGED
@@ -19,11 +19,11 @@ class AspectRatio(str, Enum):
19
  ORIGINAL = "original" # Keep source ratio
20
 
21
  class ShortsStyle(str, Enum):
 
22
  CINEMATIC = "cinematic" # Blurred background
23
  CROP_FILL = "crop_fill" # Crop to fill target ratio
24
  SPLIT_SCREEN = "split" # Split screen
25
  FIT_BARS = "fit_bars" # Letterboxing
26
- ORIGINAL = "original" # No changes
27
 
28
  class ProcessingType(str, Enum):
29
  TRANSCRIPT = "transcript"
@@ -48,7 +48,7 @@ class Dimensions(BaseModel):
48
  width: Optional[int] = 0
49
  height: Optional[int] = 0
50
  target_ratio: AspectRatio = AspectRatio.RATIO_9_16
51
- style: ShortsStyle = ShortsStyle.CINEMATIC
52
  video_scale: float = 1.0
53
  vertical_shift: float = 0.0
54
  blur_intensity: int = 20
@@ -62,7 +62,10 @@ class Dimensions(BaseModel):
62
  class ClipRequest(BaseModel):
63
  video_url: Optional[str] = None
64
  aspect_ratio: AspectRatio = AspectRatio.RATIO_9_16
65
- style: ShortsStyle = ShortsStyle.CINEMATIC
 
 
 
66
  custom_dimensions: Optional[Dimensions] = None
67
  timestamps: Optional[List[Timestamp]] = None
68
 
 
19
  ORIGINAL = "original" # Keep source ratio
20
 
21
  class ShortsStyle(str, Enum):
22
+ ORIGINAL = "original" # Keep original style
23
  CINEMATIC = "cinematic" # Blurred background
24
  CROP_FILL = "crop_fill" # Crop to fill target ratio
25
  SPLIT_SCREEN = "split" # Split screen
26
  FIT_BARS = "fit_bars" # Letterboxing
 
27
 
28
  class ProcessingType(str, Enum):
29
  TRANSCRIPT = "transcript"
 
48
  width: Optional[int] = 0
49
  height: Optional[int] = 0
50
  target_ratio: AspectRatio = AspectRatio.RATIO_9_16
51
+ style: ShortsStyle = ShortsStyle.ORIGINAL
52
  video_scale: float = 1.0
53
  vertical_shift: float = 0.0
54
  blur_intensity: int = 20
 
62
  class ClipRequest(BaseModel):
63
  video_url: Optional[str] = None
64
  aspect_ratio: AspectRatio = AspectRatio.RATIO_9_16
65
+ style: ShortsStyle = ShortsStyle.ORIGINAL
66
+ video_volume: float = 1.0
67
+ music_volume: float = 0.2
68
+ loop_music: bool = True
69
  custom_dimensions: Optional[Dimensions] = None
70
  timestamps: Optional[List[Timestamp]] = None
71
 
video_processor.py CHANGED
@@ -19,7 +19,7 @@ def get_canvas_dimensions(ratio: AspectRatio) -> tuple:
19
  return 1080, 1350
20
  return None, None # For ORIGINAL
21
 
22
- def process_video_clips(video_path: str, timestamps, aspect_ratio: AspectRatio = AspectRatio.RATIO_9_16, style: ShortsStyle = ShortsStyle.CINEMATIC, custom_dims: Dimensions = None, export_audio: bool = False, use_parallel: bool = True, use_ffmpeg_optimization: bool = True):
23
  """
24
  Processes a video file into multiple clips based on timestamps and style.
25
  If export_audio is True, also saves the original audio track of each clip.
@@ -29,6 +29,18 @@ def process_video_clips(video_path: str, timestamps, aspect_ratio: AspectRatio =
29
  clip_paths = []
30
  audio_paths = []
31
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # Try FFmpeg optimization first for simple cases
33
  if use_ffmpeg_optimization:
34
  try:
@@ -39,7 +51,8 @@ def process_video_clips(video_path: str, timestamps, aspect_ratio: AspectRatio =
39
  output_format=style,
40
  custom_dims=custom_dims,
41
  export_audio=export_audio,
42
- aspect_ratio=aspect_ratio
 
43
  )
44
  if clip_paths: # If FFmpeg worked, return results
45
  print(f"✅ FFmpeg optimization successful! Processed {len(clip_paths)} clips")
@@ -49,13 +62,12 @@ def process_video_clips(video_path: str, timestamps, aspect_ratio: AspectRatio =
49
  print("🎬 Falling back to MoviePy...")
50
 
51
  try:
52
- # Load background music if provided
53
  bg_music = None
54
- if custom_dims and hasattr(custom_dims, 'audio_path') and custom_dims.audio_path:
55
  from moviepy import AudioFileClip, CompositeAudioClip
56
  import moviepy.audio.fx as afx
57
- if os.path.exists(custom_dims.audio_path):
58
- bg_music = AudioFileClip(custom_dims.audio_path)
59
 
60
  if use_parallel and len(timestamps) > 1:
61
  # Process clips in parallel for better performance
@@ -69,7 +81,10 @@ def process_video_clips(video_path: str, timestamps, aspect_ratio: AspectRatio =
69
  future = executor.submit(
70
  process_single_clip,
71
  ts, video_path, aspect_ratio, style, custom_dims,
72
- export_audio, bg_music, clip_id
 
 
 
73
  )
74
  futures.append((future, clip_id))
75
 
@@ -114,14 +129,40 @@ def process_video_clips(video_path: str, timestamps, aspect_ratio: AspectRatio =
114
 
115
  # Extract subclip and process it
116
  with video.subclipped(ts.start_time, end) as subclip:
117
- # (Removed automatic audio extraction to mp3)
118
-
119
  # Apply background music if available
120
  if bg_music:
121
- # ... (existing code)
122
- pass
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- # ... (existing code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  # Write file - optimized settings for compatibility and speed
127
  temp_audio = os.path.join(TEMP_DIR, f"temp-audio-{clip_id}.m4a")
@@ -299,13 +340,23 @@ def create_zip_archive(file_paths: list, output_filename: str):
299
 
300
  return zip_path
301
 
302
- def process_single_clip(ts, video_path, style, custom_dims, export_audio, bg_music, clip_id):
303
  """
304
  Process a single clip - for parallel processing.
305
  """
306
  try:
 
 
 
 
 
 
 
 
 
307
  from moviepy import VideoFileClip, CompositeAudioClip
308
  import moviepy.audio.fx as afx
 
309
 
310
  # Open video for this clip (parallel processing)
311
  with VideoFileClip(video_path) as video:
@@ -330,7 +381,41 @@ def process_single_clip(ts, video_path, style, custom_dims, export_audio, bg_mus
330
  # (Removed automatic audio extraction to mp3)
331
  audio_output_path = None
332
 
333
- # ... (existing music code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
  # Write file - optimized settings for speed
336
  temp_audio = os.path.join(TEMP_DIR, f"temp-audio-{clip_id}.m4a")
@@ -359,21 +444,15 @@ def process_single_clip(ts, video_path, style, custom_dims, export_audio, bg_mus
359
  def extract_audio_from_video(video_path: str, output_format: str = "mp3"):
360
  """
361
  Extract audio from a video file and save it as an audio file.
362
-
363
- Args:
364
- video_path: Path to the input video file
365
- output_format: Output audio format (mp3, wav, etc.)
366
-
367
- Returns:
368
- Path to the extracted audio file
369
  """
370
  try:
 
371
  # Generate output filename
372
  base_name = os.path.splitext(os.path.basename(video_path))[0]
373
  audio_filename = f"{base_name}_audio.{output_format}"
374
- output_path = os.path.join(os.path.dirname(video_path), audio_filename)
375
 
376
- # Try FFmpeg first (fastest method) - use imageio_ffmpeg path
377
  try:
378
  import subprocess
379
  import imageio_ffmpeg
 
19
  return 1080, 1350
20
  return None, None # For ORIGINAL
21
 
22
+ def process_video_clips(video_path: str, timestamps, aspect_ratio: AspectRatio = AspectRatio.RATIO_9_16, style: ShortsStyle = ShortsStyle.ORIGINAL, custom_dims: Dimensions = None, export_audio: bool = False, use_parallel: bool = True, use_ffmpeg_optimization: bool = True, video_volume: float = 1.0, music_volume: float = 0.2, loop_music: bool = True):
23
  """
24
  Processes a video file into multiple clips based on timestamps and style.
25
  If export_audio is True, also saves the original audio track of each clip.
 
29
  clip_paths = []
30
  audio_paths = []
31
 
32
+ # Extract background music path if available
33
+ bg_music_path = None
34
+ if custom_dims and hasattr(custom_dims, 'audio_path') and custom_dims.audio_path:
35
+ if os.path.exists(custom_dims.audio_path):
36
+ bg_music_path = custom_dims.audio_path
37
+
38
+ # Ensure custom_dims has the volume info for hybrid processing
39
+ if custom_dims:
40
+ custom_dims.video_volume = video_volume
41
+ custom_dims.music_volume = music_volume
42
+ custom_dims.loop_music = loop_music
43
+
44
  # Try FFmpeg optimization first for simple cases
45
  if use_ffmpeg_optimization:
46
  try:
 
51
  output_format=style,
52
  custom_dims=custom_dims,
53
  export_audio=export_audio,
54
+ aspect_ratio=aspect_ratio,
55
+ bg_music=bg_music_path
56
  )
57
  if clip_paths: # If FFmpeg worked, return results
58
  print(f"✅ FFmpeg optimization successful! Processed {len(clip_paths)} clips")
 
62
  print("🎬 Falling back to MoviePy...")
63
 
64
  try:
65
+ # Load background music if provided for MoviePy fallback
66
  bg_music = None
67
+ if bg_music_path:
68
  from moviepy import AudioFileClip, CompositeAudioClip
69
  import moviepy.audio.fx as afx
70
+ bg_music = AudioFileClip(bg_music_path)
 
71
 
72
  if use_parallel and len(timestamps) > 1:
73
  # Process clips in parallel for better performance
 
81
  future = executor.submit(
82
  process_single_clip,
83
  ts, video_path, aspect_ratio, style, custom_dims,
84
+ export_audio, bg_music, clip_id,
85
+ video_volume=video_volume,
86
+ music_volume=music_volume,
87
+ loop_music=loop_music
88
  )
89
  futures.append((future, clip_id))
90
 
 
129
 
130
  # Extract subclip and process it
131
  with video.subclipped(ts.start_time, end) as subclip:
 
 
132
  # Apply background music if available
133
  if bg_music:
134
+ # Setup background music
135
+ bg_music_clip = bg_music.with_duration(subclip.duration)
136
+ if loop_music:
137
+ bg_music_clip = bg_music_clip.fx(afx.AudioLoop, duration=subclip.duration)
138
+
139
+ # Apply volumes
140
+ if subclip.audio:
141
+ original_audio = subclip.audio.with_volume_scaled(video_volume)
142
+ bg_music_clip = bg_music_clip.with_volume_scaled(music_volume)
143
+ # Combine audio
144
+ subclip.audio = CompositeAudioClip([original_audio, bg_music_clip])
145
+ else:
146
+ subclip.audio = bg_music_clip.with_volume_scaled(music_volume)
147
 
148
+ # Apply formatting
149
+ if aspect_ratio == AspectRatio.ORIGINAL or style == ShortsStyle.ORIGINAL:
150
+ pass # Skip resizing, keep original dimensions
151
+ else:
152
+ # Map style to layout
153
+ layout_map = {
154
+ ShortsStyle.CINEMATIC: LayoutType.CINEMATIC_BLUR,
155
+ ShortsStyle.CROP_FILL: LayoutType.CROP_CENTER,
156
+ ShortsStyle.FIT_BARS: LayoutType.FIT_CENTER,
157
+ ShortsStyle.SPLIT_SCREEN: LayoutType.SPLIT_SCREEN
158
+ }
159
+ layout = layout_map.get(style, LayoutType.CROP_CENTER)
160
+
161
+ # Get target dimensions
162
+ target_w, target_h = get_canvas_dimensions(aspect_ratio)
163
+
164
+ if target_w and target_h:
165
+ subclip = apply_layout_factory(subclip, layout, target_w, target_h, custom_dims)
166
 
167
  # Write file - optimized settings for compatibility and speed
168
  temp_audio = os.path.join(TEMP_DIR, f"temp-audio-{clip_id}.m4a")
 
340
 
341
  return zip_path
342
 
343
+ def process_single_clip(ts, video_path, aspect_ratio, style, custom_dims, export_audio, bg_music, clip_id, video_volume=1.0, music_volume=0.2, loop_music=True):
344
  """
345
  Process a single clip - for parallel processing.
346
  """
347
  try:
348
+ # Ensure custom_dims has the volume info
349
+ if not custom_dims:
350
+ from schemas import Dimensions
351
+ custom_dims = Dimensions()
352
+
353
+ custom_dims.video_volume = video_volume
354
+ custom_dims.music_volume = music_volume
355
+ custom_dims.loop_music = loop_music
356
+
357
  from moviepy import VideoFileClip, CompositeAudioClip
358
  import moviepy.audio.fx as afx
359
+ from schemas import AspectRatio, ShortsStyle, LayoutType
360
 
361
  # Open video for this clip (parallel processing)
362
  with VideoFileClip(video_path) as video:
 
381
  # (Removed automatic audio extraction to mp3)
382
  audio_output_path = None
383
 
384
+ # Apply background music if available
385
+ if bg_music:
386
+ from moviepy import CompositeAudioClip
387
+ import moviepy.audio.fx as afx
388
+
389
+ # Setup background music
390
+ bg_music_clip = bg_music.with_duration(subclip.duration)
391
+ if loop_music:
392
+ bg_music_clip = bg_music_clip.fx(afx.AudioLoop, duration=subclip.duration)
393
+
394
+ # Apply volumes
395
+ original_audio = subclip.audio.with_volume_scaled(video_volume)
396
+ bg_music_clip = bg_music_clip.with_volume_scaled(music_volume)
397
+
398
+ # Combine audio
399
+ subclip.audio = CompositeAudioClip([original_audio, bg_music_clip])
400
+
401
+ # Apply formatting
402
+ if aspect_ratio == AspectRatio.ORIGINAL or style == ShortsStyle.ORIGINAL:
403
+ pass # Skip resizing, keep original dimensions
404
+ else:
405
+ # Map style to layout
406
+ layout_map = {
407
+ ShortsStyle.CINEMATIC: LayoutType.CINEMATIC_BLUR,
408
+ ShortsStyle.CROP_FILL: LayoutType.CROP_CENTER,
409
+ ShortsStyle.FIT_BARS: LayoutType.FIT_CENTER,
410
+ ShortsStyle.SPLIT_SCREEN: LayoutType.SPLIT_SCREEN
411
+ }
412
+ layout = layout_map.get(style, LayoutType.CROP_CENTER)
413
+
414
+ # Get target dimensions
415
+ target_w, target_h = get_canvas_dimensions(aspect_ratio)
416
+
417
+ if target_w and target_h:
418
+ subclip = apply_layout_factory(subclip, layout, target_w, target_h, custom_dims)
419
 
420
  # Write file - optimized settings for speed
421
  temp_audio = os.path.join(TEMP_DIR, f"temp-audio-{clip_id}.m4a")
 
444
  def extract_audio_from_video(video_path: str, output_format: str = "mp3"):
445
  """
446
  Extract audio from a video file and save it as an audio file.
 
 
 
 
 
 
 
447
  """
448
  try:
449
+ from routers.video import AUDIO_DIR
450
  # Generate output filename
451
  base_name = os.path.splitext(os.path.basename(video_path))[0]
452
  audio_filename = f"{base_name}_audio.{output_format}"
453
+ output_path = os.path.join(AUDIO_DIR, audio_filename)
454
 
455
+ # Try FFmpeg first (fastest method)
456
  try:
457
  import subprocess
458
  import imageio_ffmpeg