Update app.py
Browse files
app.py
CHANGED
@@ -9,6 +9,9 @@ import ffmpeg
|
|
9 |
def extract_frames(video_path):
|
10 |
"""
|
11 |
Extracts all frames from the input video.
|
|
|
|
|
|
|
12 |
"""
|
13 |
cap = cv2.VideoCapture(video_path)
|
14 |
frames = []
|
@@ -23,8 +26,13 @@ def extract_frames(video_path):
|
|
23 |
|
24 |
def apply_style_propagation(frames, style_image_path):
|
25 |
"""
|
26 |
-
Applies the style from the provided image to
|
27 |
-
|
|
|
|
|
|
|
|
|
|
|
28 |
"""
|
29 |
style_image = cv2.imread(style_image_path)
|
30 |
if style_image is None:
|
@@ -33,7 +41,6 @@ def apply_style_propagation(frames, style_image_path):
|
|
33 |
h, w = frames[0].shape[:2]
|
34 |
style_image = cv2.resize(style_image, (w, h))
|
35 |
|
36 |
-
# The first styled frame is the style image.
|
37 |
styled_frames = [style_image]
|
38 |
prev_gray = cv2.cvtColor(frames[0], cv2.COLOR_BGR2GRAY)
|
39 |
|
@@ -47,7 +54,7 @@ def apply_style_propagation(frames, style_image_path):
|
|
47 |
grid_x, grid_y = np.meshgrid(np.arange(w), np.arange(h))
|
48 |
map_x = grid_x + flow[..., 0]
|
49 |
map_y = grid_y + flow[..., 1]
|
50 |
-
# Clip coordinates to
|
51 |
map_x = np.clip(map_x, 0, w - 1).astype(np.float32)
|
52 |
map_y = np.clip(map_y, 0, h - 1).astype(np.float32)
|
53 |
|
@@ -56,7 +63,6 @@ def apply_style_propagation(frames, style_image_path):
|
|
56 |
prev_gray = curr_gray
|
57 |
|
58 |
print(f"Propagated style to {len(styled_frames)} frames.")
|
59 |
-
# Debug: show a sample frame's mean pixel intensity.
|
60 |
sample_frame = styled_frames[len(styled_frames) // 2]
|
61 |
print(f"Sample styled frame mean intensity: {np.mean(sample_frame):.2f}")
|
62 |
return styled_frames
|
@@ -64,6 +70,8 @@ def apply_style_propagation(frames, style_image_path):
|
|
64 |
def save_video_cv2(frames, output_path, fps=30):
|
65 |
"""
|
66 |
Saves a list of frames as a video using OpenCV.
|
|
|
|
|
67 |
"""
|
68 |
h, w, _ = frames[0].shape
|
69 |
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
@@ -74,13 +82,23 @@ def save_video_cv2(frames, output_path, fps=30):
|
|
74 |
size = os.path.getsize(output_path)
|
75 |
print(f"Intermediate video saved to {output_path} (size: {size} bytes)")
|
76 |
|
77 |
-
def process_video(video_file, style_image_file, fps=30):
|
78 |
"""
|
79 |
-
Processes the input video
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
"""
|
83 |
-
#
|
84 |
video_path = video_file if isinstance(video_file, str) else video_file["name"]
|
85 |
|
86 |
# Process the style image input.
|
@@ -95,17 +113,27 @@ def process_video(video_file, style_image_file, fps=30):
|
|
95 |
else:
|
96 |
return "Error: Unsupported style image format."
|
97 |
|
98 |
-
# Extract frames from the video.
|
99 |
frames = extract_frames(video_path)
|
100 |
if not frames:
|
101 |
return "Error: No frames extracted from the video."
|
102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
# Propagate style.
|
104 |
styled_frames = apply_style_propagation(frames, style_image_path)
|
105 |
|
106 |
# Save intermediate video using OpenCV to a named temporary file.
|
107 |
temp_video_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
|
108 |
-
temp_video_file.close() # Close so
|
109 |
temp_video_path = temp_video_file.name
|
110 |
save_video_cv2(styled_frames, temp_video_path, fps=fps)
|
111 |
|
@@ -140,14 +168,17 @@ iface = gr.Interface(
|
|
140 |
inputs=[
|
141 |
gr.Video(label="Input Video (v.mp4)"),
|
142 |
gr.Image(label="Stylized Keyframe (a.jpeg)"),
|
143 |
-
gr.Slider(minimum=1, maximum=60, step=1, value=30, label="Output FPS")
|
|
|
|
|
144 |
],
|
145 |
outputs=gr.Video(label="Styled Video"),
|
146 |
-
title="Optical Flow Style Propagation",
|
147 |
description=(
|
148 |
-
"Upload a video and a stylized keyframe image.
|
149 |
-
"
|
150 |
-
"
|
|
|
151 |
)
|
152 |
)
|
153 |
|
|
|
9 |
def extract_frames(video_path):
|
10 |
"""
|
11 |
Extracts all frames from the input video.
|
12 |
+
Logic:
|
13 |
+
- Open the video file using cv2.VideoCapture.
|
14 |
+
- Read frames until the video ends.
|
15 |
"""
|
16 |
cap = cv2.VideoCapture(video_path)
|
17 |
frames = []
|
|
|
26 |
|
27 |
def apply_style_propagation(frames, style_image_path):
|
28 |
"""
|
29 |
+
Applies the style from the provided image to each video frame using optical flow.
|
30 |
+
Logic:
|
31 |
+
- Load and resize the style image to match the frame dimensions.
|
32 |
+
- Use the style image as the first styled frame.
|
33 |
+
- For each subsequent frame, compute dense optical flow between consecutive frames.
|
34 |
+
- Warp the previously styled frame using the computed flow.
|
35 |
+
- Clip mapping coordinates to avoid out-of-bound values.
|
36 |
"""
|
37 |
style_image = cv2.imread(style_image_path)
|
38 |
if style_image is None:
|
|
|
41 |
h, w = frames[0].shape[:2]
|
42 |
style_image = cv2.resize(style_image, (w, h))
|
43 |
|
|
|
44 |
styled_frames = [style_image]
|
45 |
prev_gray = cv2.cvtColor(frames[0], cv2.COLOR_BGR2GRAY)
|
46 |
|
|
|
54 |
grid_x, grid_y = np.meshgrid(np.arange(w), np.arange(h))
|
55 |
map_x = grid_x + flow[..., 0]
|
56 |
map_y = grid_y + flow[..., 1]
|
57 |
+
# Clip mapping coordinates to valid pixel indices.
|
58 |
map_x = np.clip(map_x, 0, w - 1).astype(np.float32)
|
59 |
map_y = np.clip(map_y, 0, h - 1).astype(np.float32)
|
60 |
|
|
|
63 |
prev_gray = curr_gray
|
64 |
|
65 |
print(f"Propagated style to {len(styled_frames)} frames.")
|
|
|
66 |
sample_frame = styled_frames[len(styled_frames) // 2]
|
67 |
print(f"Sample styled frame mean intensity: {np.mean(sample_frame):.2f}")
|
68 |
return styled_frames
|
|
|
70 |
def save_video_cv2(frames, output_path, fps=30):
|
71 |
"""
|
72 |
Saves a list of frames as a video using OpenCV.
|
73 |
+
Logic:
|
74 |
+
- Use cv2.VideoWriter with codec 'mp4v' to create a temporary video file.
|
75 |
"""
|
76 |
h, w, _ = frames[0].shape
|
77 |
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
|
|
82 |
size = os.path.getsize(output_path)
|
83 |
print(f"Intermediate video saved to {output_path} (size: {size} bytes)")
|
84 |
|
85 |
+
def process_video(video_file, style_image_file, fps=30, target_width=0, target_height=0):
|
86 |
"""
|
87 |
+
Processes the input video by applying the style image via optical flow,
|
88 |
+
optionally downscaling the video and style image to a specified resolution.
|
89 |
+
Then re-encodes the video with FFmpeg for web compatibility.
|
90 |
+
|
91 |
+
Inputs:
|
92 |
+
- video_file: The input video file.
|
93 |
+
- style_image_file: The stylized keyframe image.
|
94 |
+
- fps: Output frames per second.
|
95 |
+
- target_width: Target width for downscaling (0 means no downscale).
|
96 |
+
- target_height: Target height for downscaling (0 means no downscale).
|
97 |
+
|
98 |
+
Returns:
|
99 |
+
- Path to the final, web-playable video.
|
100 |
"""
|
101 |
+
# Determine video file path.
|
102 |
video_path = video_file if isinstance(video_file, str) else video_file["name"]
|
103 |
|
104 |
# Process the style image input.
|
|
|
113 |
else:
|
114 |
return "Error: Unsupported style image format."
|
115 |
|
116 |
+
# Extract frames from the input video.
|
117 |
frames = extract_frames(video_path)
|
118 |
if not frames:
|
119 |
return "Error: No frames extracted from the video."
|
120 |
|
121 |
+
original_h, original_w = frames[0].shape[:2]
|
122 |
+
print(f"Original video resolution: {original_w}x{original_h}")
|
123 |
+
|
124 |
+
# Downscale if target dimensions are provided (non-zero).
|
125 |
+
if target_width > 0 and target_height > 0:
|
126 |
+
print(f"Downscaling frames to resolution: {target_width}x{target_height}")
|
127 |
+
frames = [cv2.resize(frame, (target_width, target_height)) for frame in frames]
|
128 |
+
else:
|
129 |
+
print("No downscaling applied. Using original resolution.")
|
130 |
+
|
131 |
# Propagate style.
|
132 |
styled_frames = apply_style_propagation(frames, style_image_path)
|
133 |
|
134 |
# Save intermediate video using OpenCV to a named temporary file.
|
135 |
temp_video_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
|
136 |
+
temp_video_file.close() # Close so OpenCV can write to this file.
|
137 |
temp_video_path = temp_video_file.name
|
138 |
save_video_cv2(styled_frames, temp_video_path, fps=fps)
|
139 |
|
|
|
168 |
inputs=[
|
169 |
gr.Video(label="Input Video (v.mp4)"),
|
170 |
gr.Image(label="Stylized Keyframe (a.jpeg)"),
|
171 |
+
gr.Slider(minimum=1, maximum=60, step=1, value=30, label="Output FPS"),
|
172 |
+
gr.Slider(minimum=0, maximum=1920, step=1, value=0, label="Target Width (0 for original)"),
|
173 |
+
gr.Slider(minimum=0, maximum=1080, step=1, value=0, label="Target Height (0 for original)")
|
174 |
],
|
175 |
outputs=gr.Video(label="Styled Video"),
|
176 |
+
title="Optical Flow Style Propagation with Optional Downscaling",
|
177 |
description=(
|
178 |
+
"Upload a video and a stylized keyframe image. Optionally downscale both to a target resolution "
|
179 |
+
"by specifying width and height (set both to 0 for original resolution). "
|
180 |
+
"The style from the keyframe is propagated across the video using optical flow and warping. "
|
181 |
+
"The output video is re-encoded for web compatibility."
|
182 |
)
|
183 |
)
|
184 |
|