Skip to content

Commit 7098840

Browse files
Stephen G. PopeStephen G. Pope
authored andcommitted
fix ti video cut
1 parent b0ba435 commit 7098840

1 file changed

Lines changed: 117 additions & 31 deletions

File tree

services/v1/video/cut.py

Lines changed: 117 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import subprocess
2222
import logging
2323
import uuid
24+
import tempfile
2425
from services.file_management import download_file
2526
from services.cloud_storage import upload_file
2627
from 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

Comments
 (0)