Skip to content

Commit dfb55ed

Browse files
committed
resolution and fps fix
1 parent 87dd4bd commit dfb55ed

File tree

4 files changed

+385
-440
lines changed

4 files changed

+385
-440
lines changed

gradio/routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1699,7 +1699,7 @@ async def process_recording(
16991699
"zoom_bottom_right_x": form.get("zoom_bottom_right_x"),
17001700
"zoom_bottom_right_y": form.get("zoom_bottom_right_y"),
17011701
"zoom_duration": form.get("zoom_duration"),
1702-
"zoom_timestamp": form.get("zoom_timestamp"),
1702+
"zoom_start_frame": form.get("zoom_start_frame"),
17031703
}
17041704

17051705
print("Zoom parameters in request:", {k: v for k, v in params.items() if k.startswith("zoom_")})

gradio/screen_recording_utils.py

Lines changed: 53 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,14 @@
33
import shutil
44
import tempfile
55
import traceback
6-
import json
6+
import subprocess
77

88

99
async def process_video_with_ffmpeg(input_path, output_path, params):
10-
"""Process a video with ffmpeg based on the provided parameters."""
1110
current_input = input_path
1211
temp_files = [input_path]
1312

1413
try:
15-
# Debug: Print all parameters
16-
print("Processing video with parameters:", params)
17-
18-
# Handle segment removal
1914
if params.get("remove_segment_start") and params.get("remove_segment_end"):
2015
start = float(params["remove_segment_start"])
2116
end = float(params["remove_segment_end"])
@@ -53,9 +48,6 @@ async def process_video_with_ffmpeg(input_path, output_path, params):
5348
)
5449
stdout, stderr = await process.communicate()
5550

56-
if process.returncode != 0:
57-
print(f"FFmpeg before segment error: {stderr.decode()}")
58-
5951
cmd_after = [
6052
"ffmpeg",
6153
"-y",
@@ -81,9 +73,6 @@ async def process_video_with_ffmpeg(input_path, output_path, params):
8173
)
8274
stdout, stderr = await process.communicate()
8375

84-
if process.returncode != 0:
85-
print(f"FFmpeg after segment error: {stderr.decode()}")
86-
8776
concat_file = tempfile.mktemp(suffix="_concat.txt")
8877
temp_files.append(concat_file)
8978

@@ -122,30 +111,18 @@ async def process_video_with_ffmpeg(input_path, output_path, params):
122111
)
123112
stdout, stderr = await process.communicate()
124113

125-
if process.returncode != 0:
126-
print(f"FFmpeg concat error: {stderr.decode()}")
127-
else:
128-
current_input = segment_output
114+
current_input = segment_output
129115

130116
for file in [before_segment, after_segment, concat_file]:
131117
try:
132118
if os.path.exists(file):
133119
os.unlink(file)
134120
except:
135121
pass
136-
137-
# Handle zoom effect with individual coordinates
138-
print("Processing zoom effect....",
139-
params.get("zoom_top_left_x"),
140-
params.get("zoom_top_left_y"),
141-
params.get("zoom_bottom_right_x"),
142-
params.get("zoom_bottom_right_y"),
143-
params.get("zoom_timestamp"))
144122

145123
if (params.get("zoom_top_left_x") and params.get("zoom_top_left_y") and
146124
params.get("zoom_bottom_right_x") and params.get("zoom_bottom_right_y")):
147125
try:
148-
# Parse zoom parameters from individual coordinates
149126
zoom_top_left = [
150127
float(params.get("zoom_top_left_x")),
151128
float(params.get("zoom_top_left_y"))
@@ -155,30 +132,23 @@ async def process_video_with_ffmpeg(input_path, output_path, params):
155132
float(params.get("zoom_bottom_right_y"))
156133
]
157134
zoom_duration = float(params.get("zoom_duration", 2.0))
158-
zoom_timestamp = params.get("zoom_timestamp")
159-
160-
print(f"Parsed zoom parameters: top_left={zoom_top_left}, bottom_right={zoom_bottom_right}, duration={zoom_duration}, timestamp={zoom_timestamp}")
135+
zoom_start_frame = params.get("zoom_start_frame")
161136

162-
# Call zoom_in function with timestamp
163137
zoom_output, zoom_temp_files = await zoom_in(
164138
current_input,
165139
output_path,
166140
zoom_top_left,
167141
zoom_bottom_right,
168142
zoom_duration,
169-
zoom_timestamp
143+
zoom_start_frame
170144
)
171145

172-
# Update current input and temp files
173146
if zoom_output and zoom_output != current_input:
174147
current_input = zoom_output
175148
temp_files.extend(zoom_temp_files)
176149
except Exception as e:
177-
print(f"Error applying zoom effect: {str(e)}")
178150
traceback.print_exc()
179-
# Continue with processing without zoom effect
180151

181-
# For MP4 output, just ensure it's in compatible format
182152
final_mp4 = output_path
183153
cmd = [
184154
"ffmpeg",
@@ -204,198 +174,131 @@ async def process_video_with_ffmpeg(input_path, output_path, params):
204174
stdout, stderr = await process.communicate()
205175

206176
if process.returncode != 0:
207-
print(f"FFmpeg final MP4 conversion error: {stderr.decode()}")
208177
shutil.copy(current_input, final_mp4)
209178

210179
current_input = final_mp4
211180
return current_input, temp_files
212181

213182
except Exception as e:
214-
print(f"Error in process_video_with_ffmpeg: {str(e)}")
215183
traceback.print_exc()
216184
return input_path, temp_files
217185

218-
async def zoom_in(input_path, output_path, top_left=None, bottom_right=None, zoom_duration=2.0, zoom_timestamp=None):
186+
async def zoom_in(input_path, output_path, top_left=None, bottom_right=None, zoom_duration=2.0, zoom_start_frame=None):
187+
from ffmpy import FFmpeg
219188
temp_files = []
220189

221190
try:
222-
# Validate inputs
223191
if not input_path or not os.path.exists(input_path):
224-
print(f"Input file does not exist: {input_path}")
225192
return input_path, temp_files
226193

227-
# Get zoom timestamp (when to start the zoom effect)
228-
if zoom_timestamp is None:
229-
print("No zoom timestamp provided, defaulting to 2 seconds")
230-
zoom_timestamp = 2.0
194+
if zoom_start_frame is None:
195+
zoom_start_frame = 60
231196
else:
232197
try:
233-
zoom_timestamp = float(zoom_timestamp)
234-
print(f"Using zoom timestamp: {zoom_timestamp} seconds")
198+
zoom_start_frame = float(zoom_start_frame)
235199
except (ValueError, TypeError):
236-
print("Invalid zoom timestamp, defaulting to 2 seconds")
237-
zoom_timestamp = 2.0
200+
zoom_start_frame = 60
238201

239-
# Default values if coordinates are None
240202
if top_left is None:
241-
print("Using default top_left coordinates (0.25, 0.25)")
242203
top_left = [0.25, 0.25]
243204

244205
if bottom_right is None:
245-
print("Using default bottom_right coordinates (0.75, 0.75)")
246206
bottom_right = [0.75, 0.75]
247207

248-
# Print coordinates for debugging
249-
print(f"Zoom coordinates: top_left={top_left}, bottom_right={bottom_right}")
250-
251-
# Safely extract coordinates
252208
try:
253209
x1, y1 = float(top_left[0]), float(top_left[1])
254210
x2, y2 = float(bottom_right[0]), float(bottom_right[1])
255211
except (TypeError, ValueError, IndexError) as e:
256-
print(f"Error converting coordinates to float: {e}")
257-
print("Using default coordinates")
258212
x1, y1 = 0.25, 0.25
259213
x2, y2 = 0.75, 0.75
260214

261-
# Ensure coordinates are within valid range
262215
x1 = max(0.0, min(0.9, x1))
263216
y1 = max(0.0, min(0.9, y1))
264217
x2 = max(0.1, min(1.0, x2))
265218
y2 = max(0.1, min(1.0, y2))
266219

267-
# Ensure x2 > x1 and y2 > y1
268220
if x2 <= x1:
269221
x1, x2 = 0.25, 0.75
270222
if y2 <= y1:
271223
y1, y2 = 0.25, 0.75
272224

273-
# Get video dimensions
274-
# cmd_dimensions = [
275-
# "ffprobe",
276-
# "-v", "error",
277-
# "-select_streams", "v:0",
278-
# "-show_entries", "stream=width,height",
279-
# "-of", "csv=s=x:p=0",
280-
# input_path
281-
# ]
282-
283-
# process = await asyncio.create_subprocess_exec(
284-
# *cmd_dimensions,
285-
# stdout=asyncio.subprocess.PIPE,
286-
# stderr=asyncio.subprocess.PIPE,
287-
# )
288-
# stdout, stderr = await process.communicate()
225+
duration_cmd = f"ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"{input_path}\""
289226

290-
# try:
291-
# dimensions = stdout.decode().strip().split('x')
292-
# width = int(dimensions[0])
293-
# height = int(dimensions[1])
294-
# print(f"Video dimensions: {width}x{height}")
295-
# except (ValueError, IndexError):
296-
# print("Could not determine video dimensions, using defaults")
297-
# width, height = 1920, 1080
298-
299-
# Get video duration
300-
cmd_duration = [
301-
"ffprobe",
302-
"-v", "error",
303-
"-show_entries", "format=duration",
304-
"-of", "default=noprint_wrappers=1:nokey=1",
305-
input_path
306-
]
307-
308-
process = await asyncio.create_subprocess_exec(
309-
*cmd_duration,
227+
process = await asyncio.create_subprocess_shell(
228+
duration_cmd,
310229
stdout=asyncio.subprocess.PIPE,
311230
stderr=asyncio.subprocess.PIPE,
312231
)
313232
stdout, stderr = await process.communicate()
314233

315234
try:
316-
video_duration = float(stdout.decode().strip())
317-
print(f"Video duration: {video_duration} seconds")
318-
except (ValueError, TypeError):
319-
print(f"Could not determine video duration, using default")
235+
output = stdout.decode().strip()
236+
video_duration = float(output)
237+
except (ValueError, TypeError) as e:
320238
video_duration = 10.0
321239

322240
zoom_duration = min(float(zoom_duration), video_duration)
323-
324-
# Apply the zoom pan effect directly without box
241+
325242
zoom_output = tempfile.mktemp(suffix="_zoomed.mp4")
326243
temp_files.append(zoom_output)
327244

328-
# Calculate the center of the specified area
329245
center_x = (x1 + x2) / 2
330246
center_y = (y1 + y2) / 2
331247

332-
# Create a complex filter for the zoom pan effect
333-
fps = 25
334-
zoom_in_frames = int(0.3 * fps)
335-
hold_frames = int(2 * fps) # 2 seconds to hold
336-
zoom_out_frames = int(0.3 * fps)
337-
total_frames = zoom_in_frames + hold_frames + zoom_out_frames
248+
# zoom_start_frame = 60 # *** TODO: causing issues when replaced with param
249+
zoom_in_frames = 15 # Zoom in over 30 frames
250+
zoom_out_frames = 15 # Zoom out over 30 frames
251+
max_zoom = 2.0 # Maximum zoom level
252+
hold_frames = 60
338253

339-
# Max zoom factor (reduced from 2.0 to 1.5)
340-
max_zoom = 1.5
341-
342-
# Set fixed dimensions
343254
width, height = 1920, 1080
344255

345-
# Update the complex filter to remove padding/black bars
346256
complex_filter = (
347-
f"[0:v]split=3[v1][v2][v3];"
348-
f"[v1]trim=0:{zoom_timestamp},setpts=PTS-STARTPTS,scale={width}:{height}[intro];" # Direct scaling
349-
f"[v2]trim={zoom_timestamp}:{zoom_timestamp+zoom_duration},setpts=PTS-STARTPTS,"
350-
f"scale={width}:{height}," # Direct scaling without preserving aspect ratio
351-
f"zoompan=z='if(lt(on,{zoom_in_frames}),"
352-
f" 1+({max_zoom}-1)*on/{zoom_in_frames}," # zoom in phase
353-
f" if(lt(on,{zoom_in_frames+hold_frames}),"
354-
f" {max_zoom}," # hold phase
355-
f" {max_zoom}-({max_zoom}-1)*(on-{zoom_in_frames+hold_frames})/{zoom_out_frames}" # zoom out phase
356-
f" )"
357-
f" )':"
358-
f"x='iw*{center_x}-iw/zoom*{center_x}':y='ih*{center_y}-ih/zoom*{center_y}':"
359-
f"d={total_frames}:s={width}x{height}:fps={fps}[zoom];"
360-
f"[v3]trim={zoom_timestamp+zoom_duration}:{video_duration},setpts=PTS-STARTPTS,scale={width}:{height}[outro];" # Direct scaling
361-
f"[intro][zoom][outro]concat=n=3:v=1:a=0[outv]"
257+
f"[0:v]zoompan="
258+
f"z='if(between(on,{zoom_start_frame},{zoom_start_frame + zoom_in_frames + hold_frames + zoom_out_frames}),"
259+
f"if(lt(on-{zoom_start_frame},{zoom_in_frames}),"
260+
f"1+(({max_zoom}-1)*(on-{zoom_start_frame})/{zoom_in_frames}),"
261+
f"if(lt(on-{zoom_start_frame},{zoom_in_frames + hold_frames}),"
262+
f"{max_zoom},"
263+
f"{max_zoom}-(({max_zoom}-1)*((on-{zoom_start_frame}-{zoom_in_frames}-{hold_frames}))/{zoom_out_frames})"
264+
f")),1)':"
265+
f"x='iw*{center_x}-iw/zoom*{center_x}':"
266+
f"y='ih*{center_y}-ih/zoom*{center_y}':"
267+
f"d=0:"
268+
f"s={width}x{height}[outv]"
362269
)
363270

364-
cmd_zoom = [
365-
"ffmpeg",
366-
"-y",
367-
"-i", input_path,
368-
"-filter_complex", complex_filter,
369-
"-map", "[outv]",
370-
"-map", "0:a?", # Make audio mapping optional with ?
371-
"-c:v", "libx264",
372-
"-tag:v", "avc3",
373-
"-profile:v", "baseline",
374-
"-level", "3.0",
375-
"-pix_fmt", "yuv420p",
376-
"-preset", "fast",
377-
"-c:a", "copy",
378-
zoom_output,
379-
]
380-
381-
print(f"Running zoom pan effect command: {' '.join(cmd_zoom)}")
271+
ff = FFmpeg(
272+
inputs={input_path: None},
273+
outputs={
274+
zoom_output: (
275+
f"-filter_complex \"{complex_filter}\" "
276+
f"-map \"[outv]\" "
277+
f"-map 0:a? "
278+
f"-c:v libx264 "
279+
f"-pix_fmt yuv420p "
280+
f"-movflags +faststart "
281+
f"-preset fast "
282+
f"-c:a aac "
283+
f"-y"
284+
)
285+
}
286+
)
382287

288+
cmd_parts = ff.cmd.split()
383289
process = await asyncio.create_subprocess_exec(
384-
*cmd_zoom,
290+
*cmd_parts,
385291
stdout=asyncio.subprocess.PIPE,
386292
stderr=asyncio.subprocess.PIPE,
387293
)
388294
stdout, stderr = await process.communicate()
389295

390296
if process.returncode != 0:
391-
print(f"FFmpeg zoom pan effect error: {stderr.decode()}")
392-
print(stderr.decode())
393-
# Return the input if zoom fails
297+
print("FFmpeg error:", stderr.decode())
394298
return input_path, temp_files
395299

396300
return zoom_output, temp_files
397301

398302
except Exception as e:
399-
print(f"Error in zoom_in: {str(e)}")
400303
traceback.print_exc()
401304
return input_path, temp_files

0 commit comments

Comments
 (0)