Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -58,14 +58,12 @@ allowed_medias = [
|
|
58 |
".3gpp",
|
59 |
]
|
60 |
|
61 |
-
|
62 |
def get_files_infos(files):
|
63 |
results = []
|
64 |
for file in files:
|
65 |
file_path = Path(file.name)
|
66 |
info = {}
|
67 |
info["size"] = os.path.getsize(file_path)
|
68 |
-
# Sanitize filename by replacing spaces with underscores
|
69 |
info["name"] = file_path.name.replace(" ", "_")
|
70 |
file_extension = file_path.suffix
|
71 |
|
@@ -84,39 +82,21 @@ def get_files_infos(files):
|
|
84 |
info["duration"] = audio.duration
|
85 |
info["audio_channels"] = audio.nchannels
|
86 |
audio.close()
|
87 |
-
elif file_extension in (
|
88 |
-
".png",
|
89 |
-
".jpg",
|
90 |
-
".jpeg",
|
91 |
-
".tiff",
|
92 |
-
".bmp",
|
93 |
-
".gif",
|
94 |
-
".svg",
|
95 |
-
):
|
96 |
info["type"] = "image"
|
97 |
img = Image.open(file.name)
|
98 |
info["dimensions"] = "{}x{}".format(img.size[0], img.size[1])
|
99 |
results.append(info)
|
100 |
return results
|
101 |
|
102 |
-
|
103 |
def get_completion(prompt, files_info, top_p, temperature, model_choice):
|
104 |
-
# Create table header
|
105 |
files_info_string = "| Type | Name | Dimensions | Duration | Audio Channels |\n"
|
106 |
files_info_string += "|------|------|------------|-----------|--------|\n"
|
107 |
|
108 |
-
# Add each file as a table row
|
109 |
for file_info in files_info:
|
110 |
dimensions = file_info.get("dimensions", "-")
|
111 |
-
duration = (
|
112 |
-
|
113 |
-
)
|
114 |
-
audio = (
|
115 |
-
f"{file_info.get('audio_channels', '-')} channels"
|
116 |
-
if "audio_channels" in file_info
|
117 |
-
else "-"
|
118 |
-
)
|
119 |
-
|
120 |
files_info_string += f"| {file_info['type']} | {file_info['name']} | {dimensions} | {duration} | {audio} |\n"
|
121 |
|
122 |
messages = [
|
@@ -161,7 +141,6 @@ YOUR FFMPEG COMMAND:
|
|
161 |
},
|
162 |
]
|
163 |
try:
|
164 |
-
# Print the complete prompt
|
165 |
print("\n=== COMPLETE PROMPT ===")
|
166 |
for msg in messages:
|
167 |
print(f"\n[{msg['role'].upper()}]:")
|
@@ -184,11 +163,8 @@ YOUR FFMPEG COMMAND:
|
|
184 |
max_tokens=2048,
|
185 |
)
|
186 |
content = completion.choices[0].message.content
|
187 |
-
# Extract command from code block if present
|
188 |
if "```" in content:
|
189 |
-
# Find content between ```sh or ```bash and the next ```
|
190 |
import re
|
191 |
-
|
192 |
command = re.search(r"```(?:sh|bash)?\n(.*?)\n```", content, re.DOTALL)
|
193 |
if command:
|
194 |
command = command.group(1).strip()
|
@@ -197,32 +173,20 @@ YOUR FFMPEG COMMAND:
|
|
197 |
else:
|
198 |
command = content.replace("\n", "")
|
199 |
|
200 |
-
# remove output.mp4 with the actual output file path
|
201 |
command = command.replace("output.mp4", "")
|
202 |
-
|
203 |
return command
|
204 |
except Exception as e:
|
205 |
raise Exception("API Error")
|
206 |
|
207 |
-
|
208 |
-
def update(
|
209 |
-
files,
|
210 |
-
prompt,
|
211 |
-
top_p=1,
|
212 |
-
temperature=1,
|
213 |
-
model_choice="Qwen/Qwen2.5-Coder-32B-Instruct",
|
214 |
-
):
|
215 |
if prompt == "":
|
216 |
raise gr.Error("Please enter a prompt.")
|
217 |
|
218 |
files_info = get_files_infos(files)
|
219 |
-
# disable this if you're running the app locally or on your own server
|
220 |
for file_info in files_info:
|
221 |
if file_info["type"] == "video":
|
222 |
if file_info["duration"] > 120:
|
223 |
-
raise gr.Error(
|
224 |
-
"Please make sure all videos are less than 2 minute long."
|
225 |
-
)
|
226 |
if file_info["size"] > 100000000:
|
227 |
raise gr.Error("Please make sure all files are less than 100MB in size.")
|
228 |
|
@@ -230,25 +194,18 @@ def update(
|
|
230 |
while attempts < 2:
|
231 |
print("ATTEMPT", attempts)
|
232 |
try:
|
233 |
-
command_string = get_completion(
|
234 |
-
|
235 |
-
)
|
236 |
-
print(
|
237 |
-
f"""///PROMTP {prompt} \n\n/// START OF COMMAND ///:\n\n{command_string}\n\n/// END OF COMMAND ///\n\n"""
|
238 |
-
)
|
239 |
|
240 |
-
# split command string into list of arguments
|
241 |
args = shlex.split(command_string)
|
242 |
if args[0] != "ffmpeg":
|
243 |
raise Exception("Command does not start with ffmpeg")
|
244 |
temp_dir = tempfile.mkdtemp()
|
245 |
-
# copy files to temp dir with sanitized names
|
246 |
for file in files:
|
247 |
file_path = Path(file.name)
|
248 |
sanitized_name = file_path.name.replace(" ", "_")
|
249 |
shutil.copy(file_path, Path(temp_dir) / sanitized_name)
|
250 |
|
251 |
-
# test if ffmpeg command is valid dry run
|
252 |
ffmpg_dry_run = subprocess.run(
|
253 |
args + ["-f", "null", "-"],
|
254 |
stderr=subprocess.PIPE,
|
@@ -260,16 +217,12 @@ def update(
|
|
260 |
else:
|
261 |
print("Command is not valid. Error output:")
|
262 |
print(ffmpg_dry_run.stderr)
|
263 |
-
raise Exception(
|
264 |
-
"FFMPEG generated command is not valid. Please try something else."
|
265 |
-
)
|
266 |
|
267 |
output_file_name = f"output_{uuid.uuid4()}.mp4"
|
268 |
output_file_path = str((Path(temp_dir) / output_file_name).resolve())
|
269 |
final_command = args + ["-y", output_file_path]
|
270 |
-
print(
|
271 |
-
f"\n=== EXECUTING FFMPEG COMMAND ===\nffmpeg {' '.join(final_command[1:])}\n"
|
272 |
-
)
|
273 |
subprocess.run(final_command, cwd=temp_dir)
|
274 |
generated_command = f"### Generated Command\n```bash\nffmpeg {' '.join(args[1:])} -y output.mp4\n```"
|
275 |
return output_file_path, gr.update(value=generated_command)
|
@@ -279,22 +232,28 @@ def update(
|
|
279 |
print("FROM UPDATE", e)
|
280 |
raise gr.Error(e)
|
281 |
|
282 |
-
|
283 |
-
# Custom login system
|
284 |
def check_password(password):
|
285 |
return password == PASSWORD
|
286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
|
288 |
-
|
289 |
-
with gr.Blocks() as demo:
|
290 |
-
# Login interface (visible by default)
|
291 |
with gr.Row(visible=True) as login_interface:
|
292 |
with gr.Column():
|
293 |
password_input = gr.Textbox(label="Enter Password", type="password")
|
294 |
login_button = gr.Button("Login")
|
295 |
login_status = gr.Textbox(label="Login Status", interactive=False)
|
296 |
|
297 |
-
# Main app interface (hidden by default)
|
298 |
with gr.Row(visible=False) as main_interface:
|
299 |
gr.Markdown(
|
300 |
"""
|
@@ -303,45 +262,43 @@ with gr.Blocks() as demo:
|
|
303 |
""",
|
304 |
elem_id="header",
|
305 |
)
|
306 |
-
with gr.
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
312 |
)
|
313 |
-
|
314 |
-
|
315 |
-
|
|
|
|
|
|
|
|
|
316 |
)
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
top_p = gr.Slider(
|
325 |
-
minimum=-0,
|
326 |
-
maximum=1.0,
|
327 |
-
value=0.7,
|
328 |
-
step=0.05,
|
329 |
-
interactive=True,
|
330 |
-
label="Top-p (nucleus sampling)",
|
331 |
-
)
|
332 |
-
temperature = gr.Slider(
|
333 |
-
minimum=-0,
|
334 |
-
maximum=5.0,
|
335 |
-
value=0.1,
|
336 |
-
step=0.1,
|
337 |
-
interactive=True,
|
338 |
-
label="Temperature",
|
339 |
-
)
|
340 |
-
with gr.Column():
|
341 |
-
generated_video = gr.Video(
|
342 |
-
interactive=False, label="Generated Video", include_audio=True
|
343 |
)
|
344 |
-
|
|
|
|
|
|
|
345 |
|
346 |
btn.click(
|
347 |
fn=update,
|
@@ -396,9 +353,15 @@ with gr.Blocks() as demo:
|
|
396 |
cache_examples=False,
|
397 |
)
|
398 |
|
399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
|
401 |
-
# Show/hide main interface based on login status
|
402 |
def toggle_interface(password):
|
403 |
if check_password(password):
|
404 |
return gr.update(visible=False), gr.update(visible=True), "Login successful!"
|
@@ -411,6 +374,5 @@ with gr.Blocks() as demo:
|
|
411 |
outputs=[login_interface, main_interface, login_status],
|
412 |
)
|
413 |
|
414 |
-
# Launch the app with embedding enabled
|
415 |
demo.queue(default_concurrency_limit=200)
|
416 |
demo.launch(show_api=False, ssr_mode=False, share=True, inline=True)
|
|
|
58 |
".3gpp",
|
59 |
]
|
60 |
|
|
|
61 |
def get_files_infos(files):
|
62 |
results = []
|
63 |
for file in files:
|
64 |
file_path = Path(file.name)
|
65 |
info = {}
|
66 |
info["size"] = os.path.getsize(file_path)
|
|
|
67 |
info["name"] = file_path.name.replace(" ", "_")
|
68 |
file_extension = file_path.suffix
|
69 |
|
|
|
82 |
info["duration"] = audio.duration
|
83 |
info["audio_channels"] = audio.nchannels
|
84 |
audio.close()
|
85 |
+
elif file_extension in (".png", ".jpg", ".jpeg", ".tiff", ".bmp", ".gif", ".svg"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
info["type"] = "image"
|
87 |
img = Image.open(file.name)
|
88 |
info["dimensions"] = "{}x{}".format(img.size[0], img.size[1])
|
89 |
results.append(info)
|
90 |
return results
|
91 |
|
|
|
92 |
def get_completion(prompt, files_info, top_p, temperature, model_choice):
|
|
|
93 |
files_info_string = "| Type | Name | Dimensions | Duration | Audio Channels |\n"
|
94 |
files_info_string += "|------|------|------------|-----------|--------|\n"
|
95 |
|
|
|
96 |
for file_info in files_info:
|
97 |
dimensions = file_info.get("dimensions", "-")
|
98 |
+
duration = f"{file_info.get('duration', '-')}s" if "duration" in file_info else "-"
|
99 |
+
audio = f"{file_info.get('audio_channels', '-')} channels" if "audio_channels" in file_info else "-"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
files_info_string += f"| {file_info['type']} | {file_info['name']} | {dimensions} | {duration} | {audio} |\n"
|
101 |
|
102 |
messages = [
|
|
|
141 |
},
|
142 |
]
|
143 |
try:
|
|
|
144 |
print("\n=== COMPLETE PROMPT ===")
|
145 |
for msg in messages:
|
146 |
print(f"\n[{msg['role'].upper()}]:")
|
|
|
163 |
max_tokens=2048,
|
164 |
)
|
165 |
content = completion.choices[0].message.content
|
|
|
166 |
if "```" in content:
|
|
|
167 |
import re
|
|
|
168 |
command = re.search(r"```(?:sh|bash)?\n(.*?)\n```", content, re.DOTALL)
|
169 |
if command:
|
170 |
command = command.group(1).strip()
|
|
|
173 |
else:
|
174 |
command = content.replace("\n", "")
|
175 |
|
|
|
176 |
command = command.replace("output.mp4", "")
|
|
|
177 |
return command
|
178 |
except Exception as e:
|
179 |
raise Exception("API Error")
|
180 |
|
181 |
+
def update(files, prompt, top_p=1, temperature=1, model_choice="Qwen/Qwen2.5-Coder-32B-Instruct"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
if prompt == "":
|
183 |
raise gr.Error("Please enter a prompt.")
|
184 |
|
185 |
files_info = get_files_infos(files)
|
|
|
186 |
for file_info in files_info:
|
187 |
if file_info["type"] == "video":
|
188 |
if file_info["duration"] > 120:
|
189 |
+
raise gr.Error("Please make sure all videos are less than 2 minute long.")
|
|
|
|
|
190 |
if file_info["size"] > 100000000:
|
191 |
raise gr.Error("Please make sure all files are less than 100MB in size.")
|
192 |
|
|
|
194 |
while attempts < 2:
|
195 |
print("ATTEMPT", attempts)
|
196 |
try:
|
197 |
+
command_string = get_completion(prompt, files_info, top_p, temperature, model_choice)
|
198 |
+
print(f"""///PROMTP {prompt} \n\n/// START OF COMMAND ///:\n\n{command_string}\n\n/// END OF COMMAND ///\n\n""")
|
|
|
|
|
|
|
|
|
199 |
|
|
|
200 |
args = shlex.split(command_string)
|
201 |
if args[0] != "ffmpeg":
|
202 |
raise Exception("Command does not start with ffmpeg")
|
203 |
temp_dir = tempfile.mkdtemp()
|
|
|
204 |
for file in files:
|
205 |
file_path = Path(file.name)
|
206 |
sanitized_name = file_path.name.replace(" ", "_")
|
207 |
shutil.copy(file_path, Path(temp_dir) / sanitized_name)
|
208 |
|
|
|
209 |
ffmpg_dry_run = subprocess.run(
|
210 |
args + ["-f", "null", "-"],
|
211 |
stderr=subprocess.PIPE,
|
|
|
217 |
else:
|
218 |
print("Command is not valid. Error output:")
|
219 |
print(ffmpg_dry_run.stderr)
|
220 |
+
raise Exception("FFMPEG generated command is not valid. Please try something else.")
|
|
|
|
|
221 |
|
222 |
output_file_name = f"output_{uuid.uuid4()}.mp4"
|
223 |
output_file_path = str((Path(temp_dir) / output_file_name).resolve())
|
224 |
final_command = args + ["-y", output_file_path]
|
225 |
+
print(f"\n=== EXECUTING FFMPEG COMMAND ===\nffmpeg {' '.join(final_command[1:])}\n")
|
|
|
|
|
226 |
subprocess.run(final_command, cwd=temp_dir)
|
227 |
generated_command = f"### Generated Command\n```bash\nffmpeg {' '.join(args[1:])} -y output.mp4\n```"
|
228 |
return output_file_path, gr.update(value=generated_command)
|
|
|
232 |
print("FROM UPDATE", e)
|
233 |
raise gr.Error(e)
|
234 |
|
|
|
|
|
235 |
def check_password(password):
|
236 |
return password == PASSWORD
|
237 |
|
238 |
+
# Custom CSS for mobile responsiveness
|
239 |
+
custom_css = """
|
240 |
+
@media (max-width: 768px) {
|
241 |
+
.gradio-container {
|
242 |
+
flex-direction: column;
|
243 |
+
}
|
244 |
+
.gradio-column {
|
245 |
+
width: 100% !important;
|
246 |
+
}
|
247 |
+
}
|
248 |
+
"""
|
249 |
|
250 |
+
with gr.Blocks(css=custom_css) as demo:
|
|
|
|
|
251 |
with gr.Row(visible=True) as login_interface:
|
252 |
with gr.Column():
|
253 |
password_input = gr.Textbox(label="Enter Password", type="password")
|
254 |
login_button = gr.Button("Login")
|
255 |
login_status = gr.Textbox(label="Login Status", interactive=False)
|
256 |
|
|
|
257 |
with gr.Row(visible=False) as main_interface:
|
258 |
gr.Markdown(
|
259 |
"""
|
|
|
262 |
""",
|
263 |
elem_id="header",
|
264 |
)
|
265 |
+
with gr.Column():
|
266 |
+
user_files = gr.File(
|
267 |
+
file_count="multiple",
|
268 |
+
label="Media files",
|
269 |
+
file_types=allowed_medias,
|
270 |
+
)
|
271 |
+
user_prompt = gr.Textbox(
|
272 |
+
placeholder="eg: Remove the 3 first seconds of the video",
|
273 |
+
label="Instructions",
|
274 |
+
)
|
275 |
+
btn = gr.Button("Run")
|
276 |
+
with gr.Accordion("Parameters", open=False):
|
277 |
+
model_choice = gr.Radio(
|
278 |
+
choices=list(MODELS.keys()),
|
279 |
+
value=list(MODELS.keys())[0],
|
280 |
+
label="Model",
|
281 |
)
|
282 |
+
top_p = gr.Slider(
|
283 |
+
minimum=-0,
|
284 |
+
maximum=1.0,
|
285 |
+
value=0.7,
|
286 |
+
step=0.05,
|
287 |
+
interactive=True,
|
288 |
+
label="Top-p (nucleus sampling)",
|
289 |
)
|
290 |
+
temperature = gr.Slider(
|
291 |
+
minimum=-0,
|
292 |
+
maximum=5.0,
|
293 |
+
value=0.1,
|
294 |
+
step=0.1,
|
295 |
+
interactive=True,
|
296 |
+
label="Temperature",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
)
|
298 |
+
generated_video = gr.Video(
|
299 |
+
interactive=False, label="Generated Video", include_audio=True
|
300 |
+
)
|
301 |
+
generated_command = gr.Markdown()
|
302 |
|
303 |
btn.click(
|
304 |
fn=update,
|
|
|
353 |
cache_examples=False,
|
354 |
)
|
355 |
|
356 |
+
with gr.Row():
|
357 |
+
gr.Markdown(
|
358 |
+
"""
|
359 |
+
If you have idea to improve this please open a PR:
|
360 |
+
|
361 |
+
[](https://huggingface.co/spaces/huggingface-projects/video-composer-gpt4/discussions)
|
362 |
+
""",
|
363 |
+
)
|
364 |
|
|
|
365 |
def toggle_interface(password):
|
366 |
if check_password(password):
|
367 |
return gr.update(visible=False), gr.update(visible=True), "Login successful!"
|
|
|
374 |
outputs=[login_interface, main_interface, login_status],
|
375 |
)
|
376 |
|
|
|
377 |
demo.queue(default_concurrency_limit=200)
|
378 |
demo.launch(show_api=False, ssr_mode=False, share=True, inline=True)
|