remzloev commited on
Commit
f89e77a
·
verified ·
1 Parent(s): d1cea89

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +226 -0
app.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import subprocess
3
+ import os
4
+ import concurrent.futures
5
+ import shutil
6
+ import tempfile
7
+ import argparse
8
+ import cv2
9
+ from dotenv import load_dotenv
10
+ import gradio as gr
11
+ import ffmpeg
12
+ import time
13
+
14
+ FRAMES_FOLDER = "temp_frames"
15
+ OUTPUT_FRAMES_FOLDER = "temp_output_frames"
16
+
17
+ load_dotenv()
18
+ rife_ncnn_vulkan_path = os.getenv("RIFE_NCNN_VULKAN_PATH")
19
+
20
+ parser = argparse.ArgumentParser(description="Launch the smooth-frames-web-ui on Gradio Interface")
21
+ parser.add_argument("--share", action="store_true", help="Enable sharing the app")
22
+ args = parser.parse_args()
23
+
24
+ codec_mapping = {
25
+ "OpenH264": "openh264",
26
+ "VP9": "vp09"
27
+ }
28
+
29
+
30
+ def get_fps(video_path):
31
+ cap = cv2.VideoCapture(video_path)
32
+ fps = cap.get(cv2.CAP_PROP_FPS)
33
+ cap.release()
34
+ return fps
35
+
36
+
37
+ def smart_roundup(number, base=10):
38
+ """If the first digit of number is non-zero, carry forward to base."""
39
+ return number if number % base == 0 else math.ceil(number / base) * base
40
+
41
+
42
+ def interpolate_frames(prev_frame, next_frame, interp_factor):
43
+ interpolated_frames = []
44
+ for i in range(1, interp_factor):
45
+ alpha = i / interp_factor
46
+ beta = 1.0 - alpha
47
+ interpolated_frame = cv2.addWeighted(prev_frame, beta, next_frame, alpha, 0)
48
+ interpolated_frames.append(interpolated_frame)
49
+ return interpolated_frames
50
+
51
+
52
+ def generate_intermediate_frame(img_path1, img_path2, output_path):
53
+ cmd = [rife_ncnn_vulkan_path, "-0", img_path1, "-1", img_path2, "-o", output_path]
54
+ subprocess.run(cmd)
55
+ return output_path
56
+
57
+
58
+ def generate_intermediate_frames_with_dir(input_dir, output_path):
59
+ cmd = [rife_ncnn_vulkan_path, "-v", "-i", input_dir, "-o", output_path]
60
+ subprocess.run(cmd)
61
+ return output_path
62
+
63
+
64
+ def extract_frames_from_video(video_path, output_folder, target_fps=60/2):
65
+ orig_fps = smart_roundup(get_fps(video_path))
66
+ interp_factor = math.ceil(target_fps / orig_fps) # Rounding up to ensure >= target_fps
67
+
68
+ cap = cv2.VideoCapture(video_path)
69
+ frames = []
70
+ count = 0
71
+ ret, prev_frame = cap.read()
72
+ if not ret:
73
+ cap.release()
74
+ return frames
75
+
76
+ prev_frame_path = os.path.join(output_folder, f"frame_{count:07}.png")
77
+ cv2.imwrite(prev_frame_path, prev_frame)
78
+ frames.append(prev_frame_path)
79
+ count += 1
80
+
81
+ futures = []
82
+
83
+ with concurrent.futures.ThreadPoolExecutor() as executor:
84
+ while True:
85
+ ret, next_frame = cap.read()
86
+ if not ret:
87
+ break
88
+
89
+ if orig_fps < target_fps:
90
+ for i in range(1, interp_factor):
91
+ count += 2
92
+ next_frame_path = os.path.join(output_folder, f"frame_{count+1:07}.png")
93
+ cv2.imwrite(next_frame_path, next_frame)
94
+ frames.append(next_frame_path)
95
+
96
+ output_frame_path = os.path.join(output_folder, f"frame_{count:07}.png")
97
+
98
+
99
+ future = executor.submit(
100
+ generate_intermediate_frame,
101
+ prev_frame_path,
102
+ next_frame_path,
103
+ output_frame_path,
104
+ )
105
+ futures.append(future)
106
+
107
+ frames.append(output_frame_path)
108
+
109
+ prev_frame_path = next_frame_path # Set the path of the current frame for the next iteration
110
+ next_frame_path = os.path.join(output_folder, f"frame_{count:07}.png")
111
+ else:
112
+ count += 1
113
+ next_frame_path = os.path.join(output_folder, f"frame_{count:07}.png")
114
+ cv2.imwrite(next_frame_path, next_frame)
115
+ frames.append(next_frame_path)
116
+
117
+
118
+ concurrent.futures.wait(futures)
119
+
120
+ cap.release()
121
+ return frames
122
+
123
+
124
+ def generate_video_from_images(image_dir, codec):
125
+ if codec == "openh264":
126
+ vcodec = 'libopenh264'
127
+ output_video_name = "output.mp4"
128
+ pix_fmt = 'yuv420p'
129
+ elif codec == "vp09":
130
+ vcodec = 'libvpx-vp9'
131
+ output_video_name = "output.webm"
132
+ pix_fmt = 'yuva420p'
133
+ else:
134
+ raise ValueError("Invalid codec specified")
135
+
136
+ # Ensure the directory is valid
137
+ if not os.path.exists(image_dir):
138
+ raise ValueError("The image directory does not exist")
139
+
140
+ image_pattern = os.path.join(image_dir, '%08d.png')
141
+
142
+ # Obtain the dimensions of the first image
143
+ probe = ffmpeg.probe(image_pattern % 1)
144
+ video_info = next(stream for stream in probe['streams'] if stream['codec_type'] == 'video')
145
+ width = int(video_info['width'])
146
+ height = int(video_info['height'])
147
+
148
+ try:
149
+ (
150
+ ffmpeg
151
+ .input(image_pattern, framerate=60)
152
+ .output(output_video_name, vcodec=vcodec, pix_fmt=pix_fmt, s=f'{width}x{height}', video_bitrate='5000k')
153
+ .global_args("-y")
154
+ .run()
155
+ )
156
+ except ffmpeg._run.Error as e:
157
+ print(f'ffmpeg stderr:\n{e.stderr.decode()}')
158
+ raise e
159
+
160
+ return output_video_name
161
+
162
+
163
+ def is_valid_frame_filename(filename):
164
+ if not filename.endswith('.png'):
165
+ return False
166
+
167
+ return True
168
+
169
+
170
+ def interpolate_and_create_video(video_path, codec):
171
+ extract_frames_from_video(video_path, FRAMES_FOLDER)
172
+
173
+ generate_intermediate_frames_with_dir(FRAMES_FOLDER, OUTPUT_FRAMES_FOLDER)
174
+
175
+ return generate_video_from_images(OUTPUT_FRAMES_FOLDER, codec)
176
+
177
+
178
+ def delete_all_files_in_dir(dir_path):
179
+ if not os.path.exists(dir_path):
180
+ print(f"The path {dir_path} does not exist.")
181
+ return
182
+
183
+ for filename in os.listdir(dir_path):
184
+ file_path = os.path.join(dir_path, filename)
185
+ if os.path.isfile(file_path):
186
+ try:
187
+ os.remove(file_path)
188
+ except OSError as e:
189
+ print(f"Error: {file_path} : {e.strerror}")
190
+
191
+
192
+ def process_video(input_video, codec_choice, output_video):
193
+ start_time = time.time()
194
+
195
+ if not os.path.exists(rife_ncnn_vulkan_path):
196
+ raise FileNotFoundError(f"RIFE ncnn Vulkan at path {rife_ncnn_vulkan_path} does not exist. Check your configuration.")
197
+
198
+ if not os.path.exists(FRAMES_FOLDER):
199
+ os.makedirs(FRAMES_FOLDER)
200
+ if not os.path.exists(OUTPUT_FRAMES_FOLDER):
201
+ os.makedirs(OUTPUT_FRAMES_FOLDER)
202
+
203
+ codec = codec_mapping.get(codec_choice, "vp9")
204
+
205
+ temp_dir = tempfile.mkdtemp()
206
+ _, file_extension = os.path.splitext(input_video.name)
207
+ temp_file_path = os.path.join(temp_dir, "input_video" + file_extension)
208
+ shutil.copy2(input_video.name, temp_file_path)
209
+
210
+ output_video = interpolate_and_create_video(temp_file_path, codec)
211
+
212
+ # clean
213
+ delete_all_files_in_dir('temp_frames')
214
+ delete_all_files_in_dir('temp_output_frames')
215
+ shutil.rmtree(temp_dir)
216
+
217
+ end_time = time.time()
218
+ print(f"Execution time: {end_time - start_time} second")
219
+ return output_video
220
+
221
+
222
+ # gradio
223
+ input_video = gr.File(label="Upload a video")
224
+ output_video = gr.Video(label="Processed video")
225
+ codec_choice = gr.Dropdown(choices=["OpenH264", "VP9"], value="OpenH264", label="Select a Codec")
226
+ gr.Interface(fn=process_video, inputs=[input_video, codec_choice], outputs=output_video).launch(share=args.share)