2121import subprocess
2222import logging
2323import uuid
24+ import tempfile
2425from services .file_management import download_file
2526from services .cloud_storage import upload_file
2627from config import LOCAL_STORAGE_PATH
@@ -74,16 +75,15 @@ def cut_media(video_url, cuts, job_id=None, video_codec='libx264', video_preset=
7475 input_filename = download_file (video_url , os .path .join (LOCAL_STORAGE_PATH , f"{ job_id } _input" ))
7576 logger .info (f"Downloaded video to local file: { input_filename } " )
7677
78+ temp_files = []
79+
7780 try :
7881 # Get the file extension
7982 _ , ext = os .path .splitext (input_filename )
8083
8184 # Create output filename
8285 output_filename = os .path .join (LOCAL_STORAGE_PATH , f"{ job_id } _output{ ext } " )
8386
84- # Using a single FFmpeg command with filter_complex to remove segments
85- # This is much more efficient and reliable than concatenating segments
86-
8787 # Get the duration of the input file
8888 probe_cmd = [
8989 'ffprobe' ,
@@ -137,7 +137,7 @@ def cut_media(video_url, cuts, job_id=None, video_codec='libx264', video_preset=
137137 # Add the last segment
138138 merged_cuts .append ((current_start , current_end ))
139139
140- logger .info (f"Merged cuts (segments to remove) : { merged_cuts } " )
140+ logger .info (f"Processing cuts: { merged_cuts } " )
141141
142142 if not merged_cuts :
143143 logger .info ("No valid cuts to apply, copying the original file" )
@@ -147,43 +147,129 @@ def cut_media(video_url, cuts, job_id=None, video_codec='libx264', video_preset=
147147 '-c' , 'copy' ,
148148 output_filename
149149 ]
150+ subprocess .run (cmd , check = True , capture_output = True , text = True )
150151 else :
151- # Build a select filter that excludes the segments we want to cut
152- # Format: not(between(t,start1,end1)+between(t,start2,end2)+...)
153- select_conditions = []
154- for start , end in merged_cuts :
155- select_conditions .append (f"between(t,{ start } ,{ end } )" )
152+ # Switch to a different approach: extract segments and concatenate
153+ segment_files = []
156154
157- select_expr = "not(" + "+" .join (select_conditions ) + ")"
158- logger .info (f"Using select expression: { select_expr } " )
155+ # Create segments to keep
156+ last_end = 0
157+ for i , (start , end ) in enumerate (merged_cuts ):
158+ # If there's a gap between last segment end and current segment start, extract it
159+ if start > last_end :
160+ segment_file = os .path .join (LOCAL_STORAGE_PATH , f"{ job_id } _segment_{ i } { ext } " )
161+ segment_files .append (segment_file )
162+ temp_files .append (segment_file )
163+
164+ # Extract segment from last_end to start
165+ duration = start - last_end
166+ cmd = [
167+ 'ffmpeg' ,
168+ '-i' , input_filename ,
169+ '-ss' , str (last_end ),
170+ '-t' , str (duration ),
171+ '-c:v' , video_codec ,
172+ '-preset' , video_preset ,
173+ '-crf' , str (video_crf ),
174+ '-c:a' , audio_codec ,
175+ '-b:a' , audio_bitrate ,
176+ '-pix_fmt' , 'yuv420p' ,
177+ '-vsync' , 'cfr' ,
178+ '-r' , '30' ,
179+ '-avoid_negative_ts' , 'make_zero' ,
180+ segment_file
181+ ]
182+ logger .info (f"Extracting segment { i } : { ' ' .join (cmd )} " )
183+ process = subprocess .run (cmd , capture_output = True , text = True )
184+
185+ if process .returncode != 0 :
186+ logger .error (f"Error during segment { i } extraction: { process .stderr } " )
187+ raise Exception (f"FFmpeg error: { process .stderr } " )
188+
189+ last_end = end
159190
160- # Create the single FFmpeg command with filter_complex and specified encoding settings
161- cmd = [
162- 'ffmpeg' ,
163- '-i' , input_filename ,
164- '-filter_complex' ,
165- f"[0:v]select='{ select_expr } ',setpts=N/FRAME_RATE/TB[v];[0:a]aselect='{ select_expr } ',asetpts=N/SR/TB[a]" ,
166- '-map' , '[v]' ,
167- '-map' , '[a]' ,
168- '-c:v' , video_codec ,
169- '-preset' , video_preset ,
170- '-crf' , str (video_crf ),
171- '-c:a' , audio_codec ,
172- '-b:a' , audio_bitrate ,
173- output_filename
174- ]
175-
176- logger .info (f"Running FFmpeg command: { ' ' .join (cmd )} " )
191+ # Add final segment if needed
192+ if last_end < file_duration :
193+ segment_file = os .path .join (LOCAL_STORAGE_PATH , f"{ job_id } _segment_final{ ext } " )
194+ segment_files .append (segment_file )
195+ temp_files .append (segment_file )
196+
197+ cmd = [
198+ 'ffmpeg' ,
199+ '-i' , input_filename ,
200+ '-ss' , str (last_end ),
201+ '-c:v' , video_codec ,
202+ '-preset' , video_preset ,
203+ '-crf' , str (video_crf ),
204+ '-c:a' , audio_codec ,
205+ '-b:a' , audio_bitrate ,
206+ '-pix_fmt' , 'yuv420p' ,
207+ '-vsync' , 'cfr' ,
208+ '-r' , '30' ,
209+ '-avoid_negative_ts' , 'make_zero' ,
210+ segment_file
211+ ]
212+ logger .info (f"Extracting final segment: { ' ' .join (cmd )} " )
213+ process = subprocess .run (cmd , capture_output = True , text = True )
214+
215+ if process .returncode != 0 :
216+ logger .error (f"Error during final segment extraction: { process .stderr } " )
217+ raise Exception (f"FFmpeg error: { process .stderr } " )
218+
219+ # If we have segments to concatenate
220+ if segment_files :
221+ # Create a concat file
222+ concat_file = os .path .join (LOCAL_STORAGE_PATH , f"{ job_id } _concat.txt" )
223+ temp_files .append (concat_file )
224+
225+ with open (concat_file , 'w' ) as f :
226+ for segment in segment_files :
227+ f .write (f"file '{ segment } '\n " )
228+
229+ # Concatenate the segments
230+ cmd = [
231+ 'ffmpeg' ,
232+ '-f' , 'concat' ,
233+ '-safe' , '0' ,
234+ '-i' , concat_file ,
235+ '-c:v' , video_codec ,
236+ '-preset' , video_preset ,
237+ '-crf' , str (video_crf ),
238+ '-c:a' , audio_codec ,
239+ '-b:a' , audio_bitrate ,
240+ '-vsync' , 'cfr' ,
241+ '-r' , '30' ,
242+ '-pix_fmt' , 'yuv420p' ,
243+ '-movflags' , '+faststart' ,
244+ output_filename
245+ ]
246+ logger .info (f"Concatenating segments: { ' ' .join (cmd )} " )
247+ process = subprocess .run (cmd , capture_output = True , text = True )
248+
249+ if process .returncode != 0 :
250+ logger .error (f"Error during concatenation: { process .stderr } " )
251+ raise Exception (f"FFmpeg error: { process .stderr } " )
252+ else :
253+ # No segments to keep
254+ with open (output_filename , 'wb' ) as f :
255+ # Create an empty file
256+ pass
177257
178- # Run the FFmpeg command
179- subprocess .run (cmd , check = True , stderr = subprocess .PIPE )
258+ # Clean up temporary files
259+ for temp_file in temp_files :
260+ if os .path .exists (temp_file ):
261+ os .remove (temp_file )
262+ logger .info (f"Removed temporary file: { temp_file } " )
180263
181- # Return the path to the output file (route will handle upload)
182264 return output_filename , input_filename
183265
184266 except Exception as e :
185267 logger .error (f"Video cut operation failed: { str (e )} " )
186268 # Clean up all temporary files if they exist
269+ for temp_file in temp_files :
270+ if os .path .exists (temp_file ):
271+ os .remove (temp_file )
272+
187273 if 'input_filename' in locals () and os .path .exists (input_filename ):
188274 os .remove (input_filename )
189275
0 commit comments