Spaces:
Running
Running
add decart video to video
Browse files
app.py
CHANGED
|
@@ -2660,12 +2660,13 @@ def compress_video_for_data_uri(video_bytes: bytes, max_size_mb: int = 8) -> byt
|
|
| 2660 |
temp_output_path = temp_input_path.replace('.mp4', '_compressed.mp4')
|
| 2661 |
|
| 2662 |
try:
|
| 2663 |
-
# Compress with ffmpeg - aggressive settings for
|
| 2664 |
subprocess.run([
|
| 2665 |
'ffmpeg', '-i', temp_input_path,
|
| 2666 |
-
'-vcodec', 'libx264', '-crf', '
|
| 2667 |
-
'-vf', 'scale=
|
| 2668 |
'-an', # Remove audio to save space
|
|
|
|
| 2669 |
'-y', temp_output_path
|
| 2670 |
], check=True, capture_output=True, stderr=subprocess.DEVNULL)
|
| 2671 |
|
|
@@ -3504,6 +3505,114 @@ def generate_video_from_text(prompt: str, session_id: Optional[str] = None, toke
|
|
| 3504 |
print(f"Text-to-video generation error: {str(e)}")
|
| 3505 |
return f"Error generating video (text-to-video): {str(e)}"
|
| 3506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3507 |
def generate_music_from_text(prompt: str, music_length_ms: int = 30000, session_id: Optional[str] = None, token: gr.OAuthToken | None = None) -> str:
|
| 3508 |
"""Generate music from a text prompt using ElevenLabs Music API and return an HTML <audio> tag.
|
| 3509 |
|
|
@@ -4120,7 +4229,94 @@ def create_video_replacement_blocks_from_input_image(html_content: str, user_pro
|
|
| 4120 |
print("[Image2Video] No <body> tag; appending video via replacement block")
|
| 4121 |
return f"{SEARCH_START}\n\n{DIVIDER}\n{video_html}\n{REPLACE_END}"
|
| 4122 |
|
| 4123 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4124 |
"""Apply text/image/video/music replacements to HTML content.
|
| 4125 |
|
| 4126 |
- Works with single-document HTML strings
|
|
@@ -4150,7 +4346,7 @@ def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_te
|
|
| 4150 |
try:
|
| 4151 |
print(
|
| 4152 |
f"[MediaApply] enable_i2v={enable_image_to_video}, enable_i2i={enable_image_to_image}, "
|
| 4153 |
-
f"enable_t2i={enable_text_to_image}, enable_t2v={enable_text_to_video}, enable_t2m={enable_text_to_music}, has_image={input_image_data is not None}"
|
| 4154 |
)
|
| 4155 |
# If image-to-video is enabled, replace the first image with a generated video and return.
|
| 4156 |
if enable_image_to_video and input_image_data is not None and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
|
|
@@ -4195,6 +4391,50 @@ def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_te
|
|
| 4195 |
return format_multipage_output(multipage_files)
|
| 4196 |
return result
|
| 4197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4198 |
# If text-to-video is enabled, insert a generated video (no input image required) and return.
|
| 4199 |
if enable_text_to_video and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
|
| 4200 |
t2v_prompt = (text_to_video_prompt or user_prompt or "").strip()
|
|
@@ -4606,9 +4846,22 @@ def send_to_sandbox(code):
|
|
| 4606 |
with open(path, 'rb') as _f:
|
| 4607 |
raw = _f.read()
|
| 4608 |
mime = _mtypes.guess_type(path)[0] or 'application/octet-stream'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4609 |
b64 = _b64.b64encode(raw).decode()
|
| 4610 |
return f"data:{mime};base64,{b64}"
|
| 4611 |
-
except Exception:
|
|
|
|
| 4612 |
return None
|
| 4613 |
def _repl_double(m):
|
| 4614 |
url = m.group(1)
|
|
@@ -4620,6 +4873,36 @@ def send_to_sandbox(code):
|
|
| 4620 |
return f"src='{data_uri}'" if data_uri else m.group(0)
|
| 4621 |
html_doc = re.sub(r'src="(file:[^"]+)"', _repl_double, html_doc)
|
| 4622 |
html_doc = re.sub(r"src='(file:[^']+)'", _repl_single, html_doc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4623 |
except Exception:
|
| 4624 |
# Best-effort; continue without inlining
|
| 4625 |
pass
|
|
@@ -4648,9 +4931,22 @@ def send_to_sandbox_with_refresh(code):
|
|
| 4648 |
with open(path, 'rb') as _f:
|
| 4649 |
raw = _f.read()
|
| 4650 |
mime = _mtypes.guess_type(path)[0] or 'application/octet-stream'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4651 |
b64 = _b64.b64encode(raw).decode()
|
| 4652 |
return f"data:{mime};base64,{b64}"
|
| 4653 |
-
except Exception:
|
|
|
|
| 4654 |
return None
|
| 4655 |
def _repl_double(m):
|
| 4656 |
url = m.group(1)
|
|
@@ -4662,6 +4958,36 @@ def send_to_sandbox_with_refresh(code):
|
|
| 4662 |
return f"src='{data_uri}'" if data_uri else m.group(0)
|
| 4663 |
html_doc = re.sub(r'src="(file:[^"]+)"', _repl_double, html_doc)
|
| 4664 |
html_doc = re.sub(r"src='(file:[^']+)'", _repl_single, html_doc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4665 |
except Exception:
|
| 4666 |
# Best-effort; continue without inlining
|
| 4667 |
pass
|
|
@@ -5175,7 +5501,7 @@ The HTML code above contains the complete original website structure with all im
|
|
| 5175 |
stop_generation = False
|
| 5176 |
|
| 5177 |
|
| 5178 |
-
def generation_code(query: Optional[str], vlm_image: Optional[gr.Image], gen_image: Optional[gr.Image], file: Optional[str], website_url: Optional[str], _setting: Dict[str, str], _history: Optional[History], _current_model: Dict, enable_search: bool = False, language: str = "html", provider: str = "auto", enable_image_generation: bool = False, enable_image_to_image: bool = False, image_to_image_prompt: Optional[str] = None, text_to_image_prompt: Optional[str] = None, enable_image_to_video: bool = False, image_to_video_prompt: Optional[str] = None, enable_text_to_video: bool = False, text_to_video_prompt: Optional[str] = None, enable_text_to_music: bool = False, text_to_music_prompt: Optional[str] = None):
|
| 5179 |
if query is None:
|
| 5180 |
query = ''
|
| 5181 |
if _history is None:
|
|
@@ -5447,6 +5773,9 @@ This will help me create a better design for you."""
|
|
| 5447 |
session_id=session_id,
|
| 5448 |
enable_text_to_video=enable_text_to_video,
|
| 5449 |
text_to_video_prompt=text_to_video_prompt,
|
|
|
|
|
|
|
|
|
|
| 5450 |
enable_text_to_music=enable_text_to_music,
|
| 5451 |
text_to_music_prompt=text_to_music_prompt,
|
| 5452 |
)
|
|
@@ -5518,6 +5847,9 @@ This will help me create a better design for you."""
|
|
| 5518 |
session_id=session_id,
|
| 5519 |
enable_text_to_video=enable_text_to_video,
|
| 5520 |
text_to_video_prompt=text_to_video_prompt,
|
|
|
|
|
|
|
|
|
|
| 5521 |
enable_text_to_music=enable_text_to_music,
|
| 5522 |
text_to_music_prompt=text_to_music_prompt,
|
| 5523 |
token=None,
|
|
@@ -5547,6 +5879,9 @@ This will help me create a better design for you."""
|
|
| 5547 |
session_id=session_id,
|
| 5548 |
enable_text_to_video=enable_text_to_video,
|
| 5549 |
text_to_video_prompt=text_to_video_prompt,
|
|
|
|
|
|
|
|
|
|
| 5550 |
enable_text_to_music=enable_text_to_music,
|
| 5551 |
text_to_music_prompt=text_to_music_prompt,
|
| 5552 |
token=None,
|
|
@@ -5985,6 +6320,9 @@ This will help me create a better design for you."""
|
|
| 5985 |
text_to_image_prompt=text_to_image_prompt,
|
| 5986 |
enable_text_to_video=enable_text_to_video,
|
| 5987 |
text_to_video_prompt=text_to_video_prompt,
|
|
|
|
|
|
|
|
|
|
| 5988 |
enable_text_to_music=enable_text_to_music,
|
| 5989 |
text_to_music_prompt=text_to_music_prompt,
|
| 5990 |
token=None,
|
|
@@ -6017,6 +6355,9 @@ This will help me create a better design for you."""
|
|
| 6017 |
session_id=session_id,
|
| 6018 |
enable_text_to_video=enable_text_to_video,
|
| 6019 |
text_to_video_prompt=text_to_video_prompt,
|
|
|
|
|
|
|
|
|
|
| 6020 |
enable_text_to_music=enable_text_to_music,
|
| 6021 |
text_to_music_prompt=text_to_music_prompt,
|
| 6022 |
)
|
|
@@ -7286,6 +7627,24 @@ with gr.Blocks(
|
|
| 7286 |
visible=False
|
| 7287 |
)
|
| 7288 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7289 |
# Text-to-Music
|
| 7290 |
text_to_music_toggle = gr.Checkbox(
|
| 7291 |
label="π΅ Generate Music (text β music)",
|
|
@@ -7335,6 +7694,11 @@ with gr.Blocks(
|
|
| 7335 |
inputs=[text_to_video_toggle, beta_toggle],
|
| 7336 |
outputs=[text_to_video_prompt]
|
| 7337 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7338 |
text_to_music_toggle.change(
|
| 7339 |
on_text_to_image_toggle,
|
| 7340 |
inputs=[text_to_music_toggle, beta_toggle],
|
|
@@ -7892,7 +8256,7 @@ with gr.Blocks(
|
|
| 7892 |
show_progress="hidden",
|
| 7893 |
).then(
|
| 7894 |
generation_code,
|
| 7895 |
-
inputs=[input, image_input, generation_image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown, provider_state, image_generation_toggle, image_to_image_toggle, image_to_image_prompt, text_to_image_prompt, image_to_video_toggle, image_to_video_prompt, text_to_video_toggle, text_to_video_prompt, text_to_music_toggle, text_to_music_prompt],
|
| 7896 |
outputs=[code_output, history, sandbox, history_output]
|
| 7897 |
).then(
|
| 7898 |
end_generation_ui,
|
|
@@ -7933,7 +8297,7 @@ with gr.Blocks(
|
|
| 7933 |
show_progress="hidden",
|
| 7934 |
).then(
|
| 7935 |
generation_code,
|
| 7936 |
-
inputs=[input, image_input, generation_image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown, provider_state, image_generation_toggle, image_to_image_toggle, image_to_image_prompt, text_to_image_prompt, image_to_video_toggle, image_to_video_prompt, text_to_video_toggle, text_to_video_prompt, text_to_music_toggle, text_to_music_prompt],
|
| 7937 |
outputs=[code_output, history, sandbox, history_output]
|
| 7938 |
).then(
|
| 7939 |
end_generation_ui,
|
|
@@ -7982,6 +8346,9 @@ with gr.Blocks(
|
|
| 7982 |
upd_i2v_prompt = gr.skip()
|
| 7983 |
upd_t2v_toggle = gr.skip()
|
| 7984 |
upd_t2v_prompt = gr.skip()
|
|
|
|
|
|
|
|
|
|
| 7985 |
upd_model_dropdown = gr.skip()
|
| 7986 |
upd_current_model = gr.skip()
|
| 7987 |
upd_t2m_toggle = gr.skip()
|
|
@@ -8051,6 +8418,13 @@ with gr.Blocks(
|
|
| 8051 |
if p:
|
| 8052 |
upd_t2v_prompt = gr.update(value=p)
|
| 8053 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8054 |
# Text-to-music
|
| 8055 |
if ("text to music" in seg_norm) or ("text-to-music" in seg_norm) or ("generate music" in seg_norm) or ("compose music" in seg_norm):
|
| 8056 |
upd_t2m_toggle = gr.update(value=True)
|
|
@@ -8075,9 +8449,10 @@ with gr.Blocks(
|
|
| 8075 |
upd_model_dropdown = gr.update(value=model_obj["name"]) # keep dropdown in sync
|
| 8076 |
upd_current_model = model_obj # pass directly to State for immediate effect
|
| 8077 |
|
| 8078 |
-
# Files: attach first non-image to file_input; image to generation_image_input
|
| 8079 |
img_assigned = False
|
| 8080 |
-
|
|
|
|
| 8081 |
for f in files:
|
| 8082 |
try:
|
| 8083 |
path = f["path"] if isinstance(f, dict) and "path" in f else f
|
|
@@ -8088,9 +8463,12 @@ with gr.Blocks(
|
|
| 8088 |
if not img_assigned and any(str(path).lower().endswith(ext) for ext in [".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp", ".tiff", ".tif"]):
|
| 8089 |
upd_image_for_gen = gr.update(value=path)
|
| 8090 |
img_assigned = True
|
| 8091 |
-
elif not
|
|
|
|
|
|
|
|
|
|
| 8092 |
upd_file = gr.update(value=path)
|
| 8093 |
-
|
| 8094 |
|
| 8095 |
# Set main build intent from first segment (if present), otherwise full text
|
| 8096 |
if main_prompt:
|
|
@@ -8120,6 +8498,9 @@ with gr.Blocks(
|
|
| 8120 |
upd_i2v_prompt,
|
| 8121 |
upd_t2v_toggle,
|
| 8122 |
upd_t2v_prompt,
|
|
|
|
|
|
|
|
|
|
| 8123 |
upd_model_dropdown,
|
| 8124 |
upd_current_model,
|
| 8125 |
upd_t2m_toggle,
|
|
@@ -8147,6 +8528,9 @@ with gr.Blocks(
|
|
| 8147 |
image_to_video_prompt,
|
| 8148 |
text_to_video_toggle,
|
| 8149 |
text_to_video_prompt,
|
|
|
|
|
|
|
|
|
|
| 8150 |
model_dropdown,
|
| 8151 |
current_model,
|
| 8152 |
text_to_music_toggle,
|
|
@@ -8160,7 +8544,7 @@ with gr.Blocks(
|
|
| 8160 |
show_progress="hidden",
|
| 8161 |
).then(
|
| 8162 |
generation_code,
|
| 8163 |
-
inputs=[input, image_input, generation_image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown, provider_state, image_generation_toggle, image_to_image_toggle, image_to_image_prompt, text_to_image_prompt, image_to_video_toggle, image_to_video_prompt, text_to_video_toggle, text_to_video_prompt, text_to_music_toggle, text_to_music_prompt],
|
| 8164 |
outputs=[code_output, history, sandbox, history_output]
|
| 8165 |
).then(
|
| 8166 |
end_generation_ui,
|
|
@@ -8192,12 +8576,13 @@ with gr.Blocks(
|
|
| 8192 |
)
|
| 8193 |
|
| 8194 |
# Toggle between classic controls and beta chat UI
|
| 8195 |
-
def toggle_beta(checked: bool, t2i: bool, i2i: bool, i2v: bool, t2v: bool, t2m: bool):
|
| 8196 |
# Prompts only visible in classic mode and when their toggles are on
|
| 8197 |
t2i_vis = (not checked) and bool(t2i)
|
| 8198 |
i2i_vis = (not checked) and bool(i2i)
|
| 8199 |
i2v_vis = (not checked) and bool(i2v)
|
| 8200 |
t2v_vis = (not checked) and bool(t2v)
|
|
|
|
| 8201 |
t2m_vis = (not checked) and bool(t2m)
|
| 8202 |
|
| 8203 |
return (
|
|
@@ -8222,6 +8607,9 @@ with gr.Blocks(
|
|
| 8222 |
gr.update(visible=i2v_vis), # image_to_video_prompt
|
| 8223 |
gr.update(visible=not checked), # text_to_video_toggle
|
| 8224 |
gr.update(visible=t2v_vis), # text_to_video_prompt
|
|
|
|
|
|
|
|
|
|
| 8225 |
gr.update(visible=not checked), # text_to_music_toggle
|
| 8226 |
gr.update(visible=t2m_vis), # text_to_music_prompt
|
| 8227 |
gr.update(visible=not checked), # model_dropdown
|
|
@@ -8231,7 +8619,7 @@ with gr.Blocks(
|
|
| 8231 |
|
| 8232 |
beta_toggle.change(
|
| 8233 |
toggle_beta,
|
| 8234 |
-
inputs=[beta_toggle, image_generation_toggle, image_to_image_toggle, image_to_video_toggle, text_to_video_toggle, text_to_music_toggle],
|
| 8235 |
outputs=[
|
| 8236 |
sidebar_chatbot,
|
| 8237 |
sidebar_msg,
|
|
@@ -8252,6 +8640,9 @@ with gr.Blocks(
|
|
| 8252 |
image_to_video_prompt,
|
| 8253 |
text_to_video_toggle,
|
| 8254 |
text_to_video_prompt,
|
|
|
|
|
|
|
|
|
|
| 8255 |
text_to_music_toggle,
|
| 8256 |
text_to_music_prompt,
|
| 8257 |
model_dropdown,
|
|
|
|
| 2660 |
temp_output_path = temp_input_path.replace('.mp4', '_compressed.mp4')
|
| 2661 |
|
| 2662 |
try:
|
| 2663 |
+
# Compress with ffmpeg - extremely aggressive settings for tiny preview size
|
| 2664 |
subprocess.run([
|
| 2665 |
'ffmpeg', '-i', temp_input_path,
|
| 2666 |
+
'-vcodec', 'libx264', '-crf', '40', '-preset', 'ultrafast',
|
| 2667 |
+
'-vf', 'scale=320:-1', '-r', '10', # Very low resolution and frame rate
|
| 2668 |
'-an', # Remove audio to save space
|
| 2669 |
+
'-t', '10', # Limit to first 10 seconds for preview
|
| 2670 |
'-y', temp_output_path
|
| 2671 |
], check=True, capture_output=True, stderr=subprocess.DEVNULL)
|
| 2672 |
|
|
|
|
| 3505 |
print(f"Text-to-video generation error: {str(e)}")
|
| 3506 |
return f"Error generating video (text-to-video): {str(e)}"
|
| 3507 |
|
| 3508 |
+
def generate_video_from_video(input_video_data, prompt: str, session_id: Optional[str] = None, token: gr.OAuthToken | None = None) -> str:
|
| 3509 |
+
"""Generate a video from an input video and prompt using Decart AI's Lucy Pro V2V API.
|
| 3510 |
+
|
| 3511 |
+
Returns an HTML <video> tag whose source points to a temporary file URL.
|
| 3512 |
+
"""
|
| 3513 |
+
try:
|
| 3514 |
+
print("[Video2Video] Starting video generation from video")
|
| 3515 |
+
|
| 3516 |
+
# Check for Decart API key
|
| 3517 |
+
api_key = os.getenv('DECART_API_KEY')
|
| 3518 |
+
if not api_key:
|
| 3519 |
+
print("[Video2Video] Missing DECART_API_KEY")
|
| 3520 |
+
return "Error: DECART_API_KEY environment variable is not set. Please set it to your Decart AI API token."
|
| 3521 |
+
|
| 3522 |
+
# Normalize input video to bytes
|
| 3523 |
+
import io
|
| 3524 |
+
import tempfile
|
| 3525 |
+
|
| 3526 |
+
def _load_video_bytes(video_like) -> bytes:
|
| 3527 |
+
if hasattr(video_like, 'read'):
|
| 3528 |
+
return video_like.read()
|
| 3529 |
+
if isinstance(video_like, (bytes, bytearray)):
|
| 3530 |
+
return bytes(video_like)
|
| 3531 |
+
if hasattr(video_like, 'name'): # File path
|
| 3532 |
+
with open(video_like.name, 'rb') as f:
|
| 3533 |
+
return f.read()
|
| 3534 |
+
# If it's a string, assume it's a file path
|
| 3535 |
+
if isinstance(video_like, str):
|
| 3536 |
+
with open(video_like, 'rb') as f:
|
| 3537 |
+
return f.read()
|
| 3538 |
+
return bytes(video_like)
|
| 3539 |
+
|
| 3540 |
+
video_bytes = _load_video_bytes(input_video_data)
|
| 3541 |
+
print(f"[Video2Video] Input video size: {len(video_bytes)} bytes")
|
| 3542 |
+
|
| 3543 |
+
# Prepare the API request
|
| 3544 |
+
form_data = {
|
| 3545 |
+
"prompt": prompt or "Enhance the video quality"
|
| 3546 |
+
}
|
| 3547 |
+
|
| 3548 |
+
# Create temporary file for video data
|
| 3549 |
+
with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as temp_file:
|
| 3550 |
+
temp_file.write(video_bytes)
|
| 3551 |
+
temp_file_path = temp_file.name
|
| 3552 |
+
|
| 3553 |
+
try:
|
| 3554 |
+
# Make API request to Decart AI
|
| 3555 |
+
with open(temp_file_path, "rb") as video_file:
|
| 3556 |
+
files = {"data": video_file}
|
| 3557 |
+
headers = {"X-API-KEY": api_key}
|
| 3558 |
+
|
| 3559 |
+
print(f"[Video2Video] Calling Decart API with prompt: {prompt}")
|
| 3560 |
+
response = requests.post(
|
| 3561 |
+
"https://api.decart.ai/v1/generate/lucy-pro-v2v",
|
| 3562 |
+
headers=headers,
|
| 3563 |
+
data=form_data,
|
| 3564 |
+
files=files,
|
| 3565 |
+
timeout=300 # 5 minute timeout
|
| 3566 |
+
)
|
| 3567 |
+
|
| 3568 |
+
if response.status_code != 200:
|
| 3569 |
+
print(f"[Video2Video] API request failed with status {response.status_code}: {response.text}")
|
| 3570 |
+
return f"Error: Decart API request failed with status {response.status_code}"
|
| 3571 |
+
|
| 3572 |
+
result_video_bytes = response.content
|
| 3573 |
+
print(f"[Video2Video] Received video bytes: {len(result_video_bytes)}")
|
| 3574 |
+
|
| 3575 |
+
finally:
|
| 3576 |
+
# Clean up temporary file
|
| 3577 |
+
try:
|
| 3578 |
+
os.unlink(temp_file_path)
|
| 3579 |
+
except Exception:
|
| 3580 |
+
pass
|
| 3581 |
+
|
| 3582 |
+
# Create temporary URL for preview (will be uploaded to HF during deploy)
|
| 3583 |
+
filename = "video_to_video_result.mp4"
|
| 3584 |
+
temp_url = upload_media_to_hf(result_video_bytes, filename, "video", token, use_temp=True)
|
| 3585 |
+
|
| 3586 |
+
# Check if creation was successful
|
| 3587 |
+
if temp_url.startswith("Error"):
|
| 3588 |
+
return temp_url
|
| 3589 |
+
|
| 3590 |
+
video_html = (
|
| 3591 |
+
f'<video controls autoplay muted loop playsinline '
|
| 3592 |
+
f'style="max-width: 100%; height: auto; border-radius: 8px; margin: 10px 0; display: block;" '
|
| 3593 |
+
f'onloadstart="this.style.backgroundColor=\'#f0f0f0\'" '
|
| 3594 |
+
f'onerror="this.style.display=\'none\'; console.error(\'Video failed to load\')">'
|
| 3595 |
+
f'<source src="{temp_url}" type="video/mp4" />'
|
| 3596 |
+
f'<p style="text-align: center; color: #666;">Your browser does not support the video tag.</p>'
|
| 3597 |
+
f'</video>'
|
| 3598 |
+
)
|
| 3599 |
+
|
| 3600 |
+
print(f"[Video2Video] Successfully generated video HTML tag with temporary URL: {temp_url}")
|
| 3601 |
+
|
| 3602 |
+
# Validate the generated video HTML
|
| 3603 |
+
if not validate_video_html(video_html):
|
| 3604 |
+
print("[Video2Video] Generated video HTML failed validation")
|
| 3605 |
+
return "Error: Generated video HTML is malformed"
|
| 3606 |
+
|
| 3607 |
+
return video_html
|
| 3608 |
+
|
| 3609 |
+
except Exception as e:
|
| 3610 |
+
import traceback
|
| 3611 |
+
print("[Video2Video] Exception during generation:")
|
| 3612 |
+
traceback.print_exc()
|
| 3613 |
+
print(f"Video-to-video generation error: {str(e)}")
|
| 3614 |
+
return f"Error generating video (video-to-video): {str(e)}"
|
| 3615 |
+
|
| 3616 |
def generate_music_from_text(prompt: str, music_length_ms: int = 30000, session_id: Optional[str] = None, token: gr.OAuthToken | None = None) -> str:
|
| 3617 |
"""Generate music from a text prompt using ElevenLabs Music API and return an HTML <audio> tag.
|
| 3618 |
|
|
|
|
| 4229 |
print("[Image2Video] No <body> tag; appending video via replacement block")
|
| 4230 |
return f"{SEARCH_START}\n\n{DIVIDER}\n{video_html}\n{REPLACE_END}"
|
| 4231 |
|
| 4232 |
+
def create_video_replacement_blocks_from_input_video(html_content: str, user_prompt: str, input_video_data, session_id: Optional[str] = None) -> str:
|
| 4233 |
+
"""Create search/replace blocks that replace the first <video> (or placeholder) with a generated <video>.
|
| 4234 |
+
|
| 4235 |
+
Uses generate_video_from_video to produce a single video and swaps it in.
|
| 4236 |
+
"""
|
| 4237 |
+
if not user_prompt:
|
| 4238 |
+
return ""
|
| 4239 |
+
|
| 4240 |
+
import re
|
| 4241 |
+
print("[Video2Video] Creating replacement blocks for video replacement")
|
| 4242 |
+
|
| 4243 |
+
# Look for existing video elements first
|
| 4244 |
+
video_patterns = [
|
| 4245 |
+
r'<video[^>]*>.*?</video>',
|
| 4246 |
+
r'<video[^>]*/>',
|
| 4247 |
+
r'<video[^>]*></video>',
|
| 4248 |
+
]
|
| 4249 |
+
|
| 4250 |
+
placeholder_videos = []
|
| 4251 |
+
for pattern in video_patterns:
|
| 4252 |
+
matches = re.findall(pattern, html_content, re.IGNORECASE | re.DOTALL)
|
| 4253 |
+
if matches:
|
| 4254 |
+
placeholder_videos.extend(matches)
|
| 4255 |
+
|
| 4256 |
+
# If no videos found, look for video placeholders or divs that might represent videos
|
| 4257 |
+
if not placeholder_videos:
|
| 4258 |
+
placeholder_patterns = [
|
| 4259 |
+
r'<div[^>]*class=["\'][^"\']*video[^"\']*["\'][^>]*>.*?</div>',
|
| 4260 |
+
r'<div[^>]*id=["\'][^"\']*video[^"\']*["\'][^>]*>.*?</div>',
|
| 4261 |
+
r'<iframe[^>]*src=["\'][^"\']*youtube[^"\']*["\'][^>]*>.*?</iframe>',
|
| 4262 |
+
r'<iframe[^>]*src=["\'][^"\']*vimeo[^"\']*["\'][^>]*>.*?</iframe>',
|
| 4263 |
+
]
|
| 4264 |
+
for pattern in placeholder_patterns:
|
| 4265 |
+
matches = re.findall(pattern, html_content, re.IGNORECASE | re.DOTALL)
|
| 4266 |
+
if matches:
|
| 4267 |
+
placeholder_videos.extend(matches)
|
| 4268 |
+
|
| 4269 |
+
print(f"[Video2Video] Found {len(placeholder_videos)} candidate video elements")
|
| 4270 |
+
|
| 4271 |
+
video_html = generate_video_from_video(input_video_data, user_prompt, session_id=session_id, token=None)
|
| 4272 |
+
try:
|
| 4273 |
+
has_file_src = 'src="' in video_html and video_html.count('src="') >= 1 and 'data:video/mp4;base64' not in video_html.split('src="', 1)[1]
|
| 4274 |
+
print(f"[Video2Video] Generated video HTML length={len(video_html)}; has_file_src={has_file_src}")
|
| 4275 |
+
except Exception:
|
| 4276 |
+
pass
|
| 4277 |
+
if video_html.startswith("Error"):
|
| 4278 |
+
print("[Video2Video] Video generation returned error; aborting replacement")
|
| 4279 |
+
return ""
|
| 4280 |
+
|
| 4281 |
+
if placeholder_videos:
|
| 4282 |
+
placeholder = placeholder_videos[0]
|
| 4283 |
+
placeholder_clean = re.sub(r'\s+', ' ', placeholder.strip())
|
| 4284 |
+
print("[Video2Video] Replacing first video placeholder with generated video")
|
| 4285 |
+
placeholder_variations = [
|
| 4286 |
+
# Try the exact string first to maximize replacement success
|
| 4287 |
+
placeholder,
|
| 4288 |
+
placeholder_clean,
|
| 4289 |
+
placeholder_clean.replace('"', "'"),
|
| 4290 |
+
placeholder_clean.replace("'", '"'),
|
| 4291 |
+
re.sub(r'\s+', ' ', placeholder_clean),
|
| 4292 |
+
placeholder_clean.replace(' ', ' '),
|
| 4293 |
+
]
|
| 4294 |
+
blocks = []
|
| 4295 |
+
for variation in placeholder_variations:
|
| 4296 |
+
blocks.append(f"""{SEARCH_START}
|
| 4297 |
+
{variation}
|
| 4298 |
+
{DIVIDER}
|
| 4299 |
+
{video_html}
|
| 4300 |
+
{REPLACE_END}""")
|
| 4301 |
+
return '\n\n'.join(blocks)
|
| 4302 |
+
|
| 4303 |
+
if '<body' in html_content:
|
| 4304 |
+
body_start = html_content.find('<body')
|
| 4305 |
+
body_end = html_content.find('>', body_start) + 1
|
| 4306 |
+
opening_body_tag = html_content[body_start:body_end]
|
| 4307 |
+
print("[Video2Video] No <video> found; inserting video right after the opening <body> tag")
|
| 4308 |
+
print(f"[Video2Video] Opening <body> tag snippet: {opening_body_tag[:120]}")
|
| 4309 |
+
return f"""{SEARCH_START}
|
| 4310 |
+
{opening_body_tag}
|
| 4311 |
+
{DIVIDER}
|
| 4312 |
+
{opening_body_tag}
|
| 4313 |
+
{video_html}
|
| 4314 |
+
{REPLACE_END}"""
|
| 4315 |
+
|
| 4316 |
+
print("[Video2Video] No <body> tag; appending video via replacement block")
|
| 4317 |
+
return f"{SEARCH_START}\n\n{DIVIDER}\n{video_html}\n{REPLACE_END}"
|
| 4318 |
+
|
| 4319 |
+
def apply_generated_media_to_html(html_content: str, user_prompt: str, enable_text_to_image: bool, enable_image_to_image: bool, input_image_data, image_to_image_prompt: str | None = None, text_to_image_prompt: str | None = None, enable_image_to_video: bool = False, image_to_video_prompt: str | None = None, session_id: Optional[str] = None, enable_text_to_video: bool = False, text_to_video_prompt: Optional[str] = None, enable_video_to_video: bool = False, video_to_video_prompt: Optional[str] = None, input_video_data = None, enable_text_to_music: bool = False, text_to_music_prompt: Optional[str] = None, token: gr.OAuthToken | None = None) -> str:
|
| 4320 |
"""Apply text/image/video/music replacements to HTML content.
|
| 4321 |
|
| 4322 |
- Works with single-document HTML strings
|
|
|
|
| 4346 |
try:
|
| 4347 |
print(
|
| 4348 |
f"[MediaApply] enable_i2v={enable_image_to_video}, enable_i2i={enable_image_to_image}, "
|
| 4349 |
+
f"enable_t2i={enable_text_to_image}, enable_t2v={enable_text_to_video}, enable_v2v={enable_video_to_video}, enable_t2m={enable_text_to_music}, has_image={input_image_data is not None}, has_video={input_video_data is not None}"
|
| 4350 |
)
|
| 4351 |
# If image-to-video is enabled, replace the first image with a generated video and return.
|
| 4352 |
if enable_image_to_video and input_image_data is not None and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
|
|
|
|
| 4391 |
return format_multipage_output(multipage_files)
|
| 4392 |
return result
|
| 4393 |
|
| 4394 |
+
# If video-to-video is enabled, replace the first video with a generated video and return.
|
| 4395 |
+
if enable_video_to_video and input_video_data is not None and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
|
| 4396 |
+
v2v_prompt = (video_to_video_prompt or user_prompt or "").strip()
|
| 4397 |
+
print(f"[MediaApply] Running video-to-video with prompt len={len(v2v_prompt)}")
|
| 4398 |
+
try:
|
| 4399 |
+
video_html_tag = generate_video_from_video(input_video_data, v2v_prompt, session_id=session_id, token=token)
|
| 4400 |
+
if not (video_html_tag or "").startswith("Error"):
|
| 4401 |
+
# Validate video HTML before attempting placement
|
| 4402 |
+
if validate_video_html(video_html_tag):
|
| 4403 |
+
blocks_v = llm_place_media(result, video_html_tag, media_kind="video")
|
| 4404 |
+
else:
|
| 4405 |
+
print("[MediaApply] Generated video HTML failed validation, skipping LLM placement")
|
| 4406 |
+
blocks_v = ""
|
| 4407 |
+
else:
|
| 4408 |
+
print(f"[MediaApply] Video generation failed: {video_html_tag}")
|
| 4409 |
+
blocks_v = ""
|
| 4410 |
+
except Exception as e:
|
| 4411 |
+
print(f"[MediaApply] Exception during video-to-video generation: {str(e)}")
|
| 4412 |
+
blocks_v = ""
|
| 4413 |
+
if not blocks_v:
|
| 4414 |
+
# Create fallback video replacement blocks
|
| 4415 |
+
blocks_v = create_video_replacement_blocks_from_input_video(result, v2v_prompt, input_video_data, session_id=session_id)
|
| 4416 |
+
if blocks_v:
|
| 4417 |
+
print("[MediaApply] Applying video-to-video replacement blocks")
|
| 4418 |
+
before_len = len(result)
|
| 4419 |
+
result_after = apply_search_replace_changes(result, blocks_v)
|
| 4420 |
+
after_len = len(result_after)
|
| 4421 |
+
changed = (result_after != result)
|
| 4422 |
+
print(f"[MediaApply] v2v blocks length={len(blocks_v)}; html before={before_len}, after={after_len}, changed={changed}")
|
| 4423 |
+
if not changed:
|
| 4424 |
+
print("[MediaApply] DEBUG: Replacement did not change content. Dumping first block:")
|
| 4425 |
+
try:
|
| 4426 |
+
first_block = blocks_v.split(REPLACE_END)[0][:1000]
|
| 4427 |
+
print(first_block)
|
| 4428 |
+
except Exception:
|
| 4429 |
+
pass
|
| 4430 |
+
result = result_after
|
| 4431 |
+
else:
|
| 4432 |
+
print("[MediaApply] No v2v replacement blocks generated")
|
| 4433 |
+
if is_multipage and entry_html_path:
|
| 4434 |
+
multipage_files[entry_html_path] = result
|
| 4435 |
+
return format_multipage_output(multipage_files)
|
| 4436 |
+
return result
|
| 4437 |
+
|
| 4438 |
# If text-to-video is enabled, insert a generated video (no input image required) and return.
|
| 4439 |
if enable_text_to_video and (result.strip().startswith('<!DOCTYPE html>') or result.strip().startswith('<html')):
|
| 4440 |
t2v_prompt = (text_to_video_prompt or user_prompt or "").strip()
|
|
|
|
| 4846 |
with open(path, 'rb') as _f:
|
| 4847 |
raw = _f.read()
|
| 4848 |
mime = _mtypes.guess_type(path)[0] or 'application/octet-stream'
|
| 4849 |
+
|
| 4850 |
+
# Compress video files before converting to data URI to prevent preview breaks
|
| 4851 |
+
if mime and mime.startswith('video/'):
|
| 4852 |
+
print(f"[Sandbox] Compressing video for preview: {len(raw)} bytes")
|
| 4853 |
+
raw = compress_video_for_data_uri(raw, max_size_mb=1) # Very small limit for preview
|
| 4854 |
+
print(f"[Sandbox] Compressed video size: {len(raw)} bytes")
|
| 4855 |
+
|
| 4856 |
+
# If still too large, skip video embedding for preview
|
| 4857 |
+
if len(raw) > 512 * 1024: # 512KB final limit
|
| 4858 |
+
print(f"[Sandbox] Video still too large after compression, using placeholder")
|
| 4859 |
+
return None # Let the replacement function handle the fallback
|
| 4860 |
+
|
| 4861 |
b64 = _b64.b64encode(raw).decode()
|
| 4862 |
return f"data:{mime};base64,{b64}"
|
| 4863 |
+
except Exception as e:
|
| 4864 |
+
print(f"[Sandbox] Failed to convert file URL to data URI: {str(e)}")
|
| 4865 |
return None
|
| 4866 |
def _repl_double(m):
|
| 4867 |
url = m.group(1)
|
|
|
|
| 4873 |
return f"src='{data_uri}'" if data_uri else m.group(0)
|
| 4874 |
html_doc = re.sub(r'src="(file:[^"]+)"', _repl_double, html_doc)
|
| 4875 |
html_doc = re.sub(r"src='(file:[^']+)'", _repl_single, html_doc)
|
| 4876 |
+
|
| 4877 |
+
# Add deployment message for videos that couldn't be converted
|
| 4878 |
+
if 'file://' in html_doc and ('video' in html_doc.lower() or '.mp4' in html_doc.lower()):
|
| 4879 |
+
deployment_notice = '''
|
| 4880 |
+
<div style="
|
| 4881 |
+
position: fixed;
|
| 4882 |
+
top: 10px;
|
| 4883 |
+
right: 10px;
|
| 4884 |
+
background: #ff6b35;
|
| 4885 |
+
color: white;
|
| 4886 |
+
padding: 12px 16px;
|
| 4887 |
+
border-radius: 8px;
|
| 4888 |
+
font-family: Arial, sans-serif;
|
| 4889 |
+
font-size: 14px;
|
| 4890 |
+
font-weight: bold;
|
| 4891 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 4892 |
+
z-index: 9999;
|
| 4893 |
+
max-width: 300px;
|
| 4894 |
+
text-align: center;
|
| 4895 |
+
">
|
| 4896 |
+
π Deploy app to see videos with permanent URLs!
|
| 4897 |
+
</div>
|
| 4898 |
+
'''
|
| 4899 |
+
# Insert the notice right after the opening body tag
|
| 4900 |
+
if '<body' in html_doc:
|
| 4901 |
+
body_end = html_doc.find('>', html_doc.find('<body')) + 1
|
| 4902 |
+
html_doc = html_doc[:body_end] + deployment_notice + html_doc[body_end:]
|
| 4903 |
+
else:
|
| 4904 |
+
html_doc = deployment_notice + html_doc
|
| 4905 |
+
|
| 4906 |
except Exception:
|
| 4907 |
# Best-effort; continue without inlining
|
| 4908 |
pass
|
|
|
|
| 4931 |
with open(path, 'rb') as _f:
|
| 4932 |
raw = _f.read()
|
| 4933 |
mime = _mtypes.guess_type(path)[0] or 'application/octet-stream'
|
| 4934 |
+
|
| 4935 |
+
# Compress video files before converting to data URI to prevent preview breaks
|
| 4936 |
+
if mime and mime.startswith('video/'):
|
| 4937 |
+
print(f"[Sandbox] Compressing video for preview: {len(raw)} bytes")
|
| 4938 |
+
raw = compress_video_for_data_uri(raw, max_size_mb=1) # Very small limit for preview
|
| 4939 |
+
print(f"[Sandbox] Compressed video size: {len(raw)} bytes")
|
| 4940 |
+
|
| 4941 |
+
# If still too large, skip video embedding for preview
|
| 4942 |
+
if len(raw) > 512 * 1024: # 512KB final limit
|
| 4943 |
+
print(f"[Sandbox] Video still too large after compression, using placeholder")
|
| 4944 |
+
return None # Let the replacement function handle the fallback
|
| 4945 |
+
|
| 4946 |
b64 = _b64.b64encode(raw).decode()
|
| 4947 |
return f"data:{mime};base64,{b64}"
|
| 4948 |
+
except Exception as e:
|
| 4949 |
+
print(f"[Sandbox] Failed to convert file URL to data URI: {str(e)}")
|
| 4950 |
return None
|
| 4951 |
def _repl_double(m):
|
| 4952 |
url = m.group(1)
|
|
|
|
| 4958 |
return f"src='{data_uri}'" if data_uri else m.group(0)
|
| 4959 |
html_doc = re.sub(r'src="(file:[^"]+)"', _repl_double, html_doc)
|
| 4960 |
html_doc = re.sub(r"src='(file:[^']+)'", _repl_single, html_doc)
|
| 4961 |
+
|
| 4962 |
+
# Add deployment message for videos that couldn't be converted
|
| 4963 |
+
if 'file://' in html_doc and ('video' in html_doc.lower() or '.mp4' in html_doc.lower()):
|
| 4964 |
+
deployment_notice = '''
|
| 4965 |
+
<div style="
|
| 4966 |
+
position: fixed;
|
| 4967 |
+
top: 10px;
|
| 4968 |
+
right: 10px;
|
| 4969 |
+
background: #ff6b35;
|
| 4970 |
+
color: white;
|
| 4971 |
+
padding: 12px 16px;
|
| 4972 |
+
border-radius: 8px;
|
| 4973 |
+
font-family: Arial, sans-serif;
|
| 4974 |
+
font-size: 14px;
|
| 4975 |
+
font-weight: bold;
|
| 4976 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 4977 |
+
z-index: 9999;
|
| 4978 |
+
max-width: 300px;
|
| 4979 |
+
text-align: center;
|
| 4980 |
+
">
|
| 4981 |
+
π Deploy app to see videos with permanent URLs!
|
| 4982 |
+
</div>
|
| 4983 |
+
'''
|
| 4984 |
+
# Insert the notice right after the opening body tag
|
| 4985 |
+
if '<body' in html_doc:
|
| 4986 |
+
body_end = html_doc.find('>', html_doc.find('<body')) + 1
|
| 4987 |
+
html_doc = html_doc[:body_end] + deployment_notice + html_doc[body_end:]
|
| 4988 |
+
else:
|
| 4989 |
+
html_doc = deployment_notice + html_doc
|
| 4990 |
+
|
| 4991 |
except Exception:
|
| 4992 |
# Best-effort; continue without inlining
|
| 4993 |
pass
|
|
|
|
| 5501 |
stop_generation = False
|
| 5502 |
|
| 5503 |
|
| 5504 |
+
def generation_code(query: Optional[str], vlm_image: Optional[gr.Image], gen_image: Optional[gr.Image], file: Optional[str], website_url: Optional[str], _setting: Dict[str, str], _history: Optional[History], _current_model: Dict, enable_search: bool = False, language: str = "html", provider: str = "auto", enable_image_generation: bool = False, enable_image_to_image: bool = False, image_to_image_prompt: Optional[str] = None, text_to_image_prompt: Optional[str] = None, enable_image_to_video: bool = False, image_to_video_prompt: Optional[str] = None, enable_text_to_video: bool = False, text_to_video_prompt: Optional[str] = None, enable_video_to_video: bool = False, video_to_video_prompt: Optional[str] = None, input_video_data = None, enable_text_to_music: bool = False, text_to_music_prompt: Optional[str] = None):
|
| 5505 |
if query is None:
|
| 5506 |
query = ''
|
| 5507 |
if _history is None:
|
|
|
|
| 5773 |
session_id=session_id,
|
| 5774 |
enable_text_to_video=enable_text_to_video,
|
| 5775 |
text_to_video_prompt=text_to_video_prompt,
|
| 5776 |
+
enable_video_to_video=enable_video_to_video,
|
| 5777 |
+
video_to_video_prompt=video_to_video_prompt,
|
| 5778 |
+
input_video_data=input_video_data,
|
| 5779 |
enable_text_to_music=enable_text_to_music,
|
| 5780 |
text_to_music_prompt=text_to_music_prompt,
|
| 5781 |
)
|
|
|
|
| 5847 |
session_id=session_id,
|
| 5848 |
enable_text_to_video=enable_text_to_video,
|
| 5849 |
text_to_video_prompt=text_to_video_prompt,
|
| 5850 |
+
enable_video_to_video=enable_video_to_video,
|
| 5851 |
+
video_to_video_prompt=video_to_video_prompt,
|
| 5852 |
+
input_video_data=input_video_data,
|
| 5853 |
enable_text_to_music=enable_text_to_music,
|
| 5854 |
text_to_music_prompt=text_to_music_prompt,
|
| 5855 |
token=None,
|
|
|
|
| 5879 |
session_id=session_id,
|
| 5880 |
enable_text_to_video=enable_text_to_video,
|
| 5881 |
text_to_video_prompt=text_to_video_prompt,
|
| 5882 |
+
enable_video_to_video=enable_video_to_video,
|
| 5883 |
+
video_to_video_prompt=video_to_video_prompt,
|
| 5884 |
+
input_video_data=input_video_data,
|
| 5885 |
enable_text_to_music=enable_text_to_music,
|
| 5886 |
text_to_music_prompt=text_to_music_prompt,
|
| 5887 |
token=None,
|
|
|
|
| 6320 |
text_to_image_prompt=text_to_image_prompt,
|
| 6321 |
enable_text_to_video=enable_text_to_video,
|
| 6322 |
text_to_video_prompt=text_to_video_prompt,
|
| 6323 |
+
enable_video_to_video=enable_video_to_video,
|
| 6324 |
+
video_to_video_prompt=video_to_video_prompt,
|
| 6325 |
+
input_video_data=input_video_data,
|
| 6326 |
enable_text_to_music=enable_text_to_music,
|
| 6327 |
text_to_music_prompt=text_to_music_prompt,
|
| 6328 |
token=None,
|
|
|
|
| 6355 |
session_id=session_id,
|
| 6356 |
enable_text_to_video=enable_text_to_video,
|
| 6357 |
text_to_video_prompt=text_to_video_prompt,
|
| 6358 |
+
enable_video_to_video=enable_video_to_video,
|
| 6359 |
+
video_to_video_prompt=video_to_video_prompt,
|
| 6360 |
+
input_video_data=input_video_data,
|
| 6361 |
enable_text_to_music=enable_text_to_music,
|
| 6362 |
text_to_music_prompt=text_to_music_prompt,
|
| 6363 |
)
|
|
|
|
| 7627 |
visible=False
|
| 7628 |
)
|
| 7629 |
|
| 7630 |
+
# Video-to-Video
|
| 7631 |
+
video_to_video_toggle = gr.Checkbox(
|
| 7632 |
+
label="π¬ Video to Video (uses input video)",
|
| 7633 |
+
value=False,
|
| 7634 |
+
visible=True,
|
| 7635 |
+
info="Transform your uploaded video using Decart AI's Lucy Pro V2V"
|
| 7636 |
+
)
|
| 7637 |
+
video_to_video_prompt = gr.Textbox(
|
| 7638 |
+
label="Video-to-Video Prompt",
|
| 7639 |
+
placeholder="Describe the transformation (e.g., 'Change their shirt to black and shiny leather')",
|
| 7640 |
+
lines=2,
|
| 7641 |
+
visible=False
|
| 7642 |
+
)
|
| 7643 |
+
video_input = gr.Video(
|
| 7644 |
+
label="Input video for transformation",
|
| 7645 |
+
visible=False
|
| 7646 |
+
)
|
| 7647 |
+
|
| 7648 |
# Text-to-Music
|
| 7649 |
text_to_music_toggle = gr.Checkbox(
|
| 7650 |
label="π΅ Generate Music (text β music)",
|
|
|
|
| 7694 |
inputs=[text_to_video_toggle, beta_toggle],
|
| 7695 |
outputs=[text_to_video_prompt]
|
| 7696 |
)
|
| 7697 |
+
video_to_video_toggle.change(
|
| 7698 |
+
on_image_to_video_toggle,
|
| 7699 |
+
inputs=[video_to_video_toggle, beta_toggle],
|
| 7700 |
+
outputs=[video_input, video_to_video_prompt]
|
| 7701 |
+
)
|
| 7702 |
text_to_music_toggle.change(
|
| 7703 |
on_text_to_image_toggle,
|
| 7704 |
inputs=[text_to_music_toggle, beta_toggle],
|
|
|
|
| 8256 |
show_progress="hidden",
|
| 8257 |
).then(
|
| 8258 |
generation_code,
|
| 8259 |
+
inputs=[input, image_input, generation_image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown, provider_state, image_generation_toggle, image_to_image_toggle, image_to_image_prompt, text_to_image_prompt, image_to_video_toggle, image_to_video_prompt, text_to_video_toggle, text_to_video_prompt, video_to_video_toggle, video_to_video_prompt, video_input, text_to_music_toggle, text_to_music_prompt],
|
| 8260 |
outputs=[code_output, history, sandbox, history_output]
|
| 8261 |
).then(
|
| 8262 |
end_generation_ui,
|
|
|
|
| 8297 |
show_progress="hidden",
|
| 8298 |
).then(
|
| 8299 |
generation_code,
|
| 8300 |
+
inputs=[input, image_input, generation_image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown, provider_state, image_generation_toggle, image_to_image_toggle, image_to_image_prompt, text_to_image_prompt, image_to_video_toggle, image_to_video_prompt, text_to_video_toggle, text_to_video_prompt, video_to_video_toggle, video_to_video_prompt, video_input, text_to_music_toggle, text_to_music_prompt],
|
| 8301 |
outputs=[code_output, history, sandbox, history_output]
|
| 8302 |
).then(
|
| 8303 |
end_generation_ui,
|
|
|
|
| 8346 |
upd_i2v_prompt = gr.skip()
|
| 8347 |
upd_t2v_toggle = gr.skip()
|
| 8348 |
upd_t2v_prompt = gr.skip()
|
| 8349 |
+
upd_v2v_toggle = gr.skip()
|
| 8350 |
+
upd_v2v_prompt = gr.skip()
|
| 8351 |
+
upd_video_input = gr.skip()
|
| 8352 |
upd_model_dropdown = gr.skip()
|
| 8353 |
upd_current_model = gr.skip()
|
| 8354 |
upd_t2m_toggle = gr.skip()
|
|
|
|
| 8418 |
if p:
|
| 8419 |
upd_t2v_prompt = gr.update(value=p)
|
| 8420 |
|
| 8421 |
+
# Video-to-video
|
| 8422 |
+
if ("video to video" in seg_norm) or ("video-to-video" in seg_norm) or ("transform video" in seg_norm):
|
| 8423 |
+
upd_v2v_toggle = gr.update(value=True)
|
| 8424 |
+
p = after_colon(seg)
|
| 8425 |
+
if p:
|
| 8426 |
+
upd_v2v_prompt = gr.update(value=p)
|
| 8427 |
+
|
| 8428 |
# Text-to-music
|
| 8429 |
if ("text to music" in seg_norm) or ("text-to-music" in seg_norm) or ("generate music" in seg_norm) or ("compose music" in seg_norm):
|
| 8430 |
upd_t2m_toggle = gr.update(value=True)
|
|
|
|
| 8449 |
upd_model_dropdown = gr.update(value=model_obj["name"]) # keep dropdown in sync
|
| 8450 |
upd_current_model = model_obj # pass directly to State for immediate effect
|
| 8451 |
|
| 8452 |
+
# Files: attach first non-image/video to file_input; image to generation_image_input; video to video_input
|
| 8453 |
img_assigned = False
|
| 8454 |
+
video_assigned = False
|
| 8455 |
+
non_media_assigned = False
|
| 8456 |
for f in files:
|
| 8457 |
try:
|
| 8458 |
path = f["path"] if isinstance(f, dict) and "path" in f else f
|
|
|
|
| 8463 |
if not img_assigned and any(str(path).lower().endswith(ext) for ext in [".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp", ".tiff", ".tif"]):
|
| 8464 |
upd_image_for_gen = gr.update(value=path)
|
| 8465 |
img_assigned = True
|
| 8466 |
+
elif not video_assigned and any(str(path).lower().endswith(ext) for ext in [".mp4", ".avi", ".mov", ".mkv", ".webm", ".m4v"]):
|
| 8467 |
+
upd_video_input = gr.update(value=path)
|
| 8468 |
+
video_assigned = True
|
| 8469 |
+
elif not non_media_assigned:
|
| 8470 |
upd_file = gr.update(value=path)
|
| 8471 |
+
non_media_assigned = True
|
| 8472 |
|
| 8473 |
# Set main build intent from first segment (if present), otherwise full text
|
| 8474 |
if main_prompt:
|
|
|
|
| 8498 |
upd_i2v_prompt,
|
| 8499 |
upd_t2v_toggle,
|
| 8500 |
upd_t2v_prompt,
|
| 8501 |
+
upd_v2v_toggle,
|
| 8502 |
+
upd_v2v_prompt,
|
| 8503 |
+
upd_video_input,
|
| 8504 |
upd_model_dropdown,
|
| 8505 |
upd_current_model,
|
| 8506 |
upd_t2m_toggle,
|
|
|
|
| 8528 |
image_to_video_prompt,
|
| 8529 |
text_to_video_toggle,
|
| 8530 |
text_to_video_prompt,
|
| 8531 |
+
video_to_video_toggle,
|
| 8532 |
+
video_to_video_prompt,
|
| 8533 |
+
video_input,
|
| 8534 |
model_dropdown,
|
| 8535 |
current_model,
|
| 8536 |
text_to_music_toggle,
|
|
|
|
| 8544 |
show_progress="hidden",
|
| 8545 |
).then(
|
| 8546 |
generation_code,
|
| 8547 |
+
inputs=[input, image_input, generation_image_input, file_input, website_url_input, setting, history, current_model, search_toggle, language_dropdown, provider_state, image_generation_toggle, image_to_image_toggle, image_to_image_prompt, text_to_image_prompt, image_to_video_toggle, image_to_video_prompt, text_to_video_toggle, text_to_video_prompt, video_to_video_toggle, video_to_video_prompt, video_input, text_to_music_toggle, text_to_music_prompt],
|
| 8548 |
outputs=[code_output, history, sandbox, history_output]
|
| 8549 |
).then(
|
| 8550 |
end_generation_ui,
|
|
|
|
| 8576 |
)
|
| 8577 |
|
| 8578 |
# Toggle between classic controls and beta chat UI
|
| 8579 |
+
def toggle_beta(checked: bool, t2i: bool, i2i: bool, i2v: bool, t2v: bool, v2v: bool, t2m: bool):
|
| 8580 |
# Prompts only visible in classic mode and when their toggles are on
|
| 8581 |
t2i_vis = (not checked) and bool(t2i)
|
| 8582 |
i2i_vis = (not checked) and bool(i2i)
|
| 8583 |
i2v_vis = (not checked) and bool(i2v)
|
| 8584 |
t2v_vis = (not checked) and bool(t2v)
|
| 8585 |
+
v2v_vis = (not checked) and bool(v2v)
|
| 8586 |
t2m_vis = (not checked) and bool(t2m)
|
| 8587 |
|
| 8588 |
return (
|
|
|
|
| 8607 |
gr.update(visible=i2v_vis), # image_to_video_prompt
|
| 8608 |
gr.update(visible=not checked), # text_to_video_toggle
|
| 8609 |
gr.update(visible=t2v_vis), # text_to_video_prompt
|
| 8610 |
+
gr.update(visible=not checked), # video_to_video_toggle
|
| 8611 |
+
gr.update(visible=v2v_vis), # video_to_video_prompt
|
| 8612 |
+
gr.update(visible=v2v_vis), # video_input
|
| 8613 |
gr.update(visible=not checked), # text_to_music_toggle
|
| 8614 |
gr.update(visible=t2m_vis), # text_to_music_prompt
|
| 8615 |
gr.update(visible=not checked), # model_dropdown
|
|
|
|
| 8619 |
|
| 8620 |
beta_toggle.change(
|
| 8621 |
toggle_beta,
|
| 8622 |
+
inputs=[beta_toggle, image_generation_toggle, image_to_image_toggle, image_to_video_toggle, text_to_video_toggle, video_to_video_toggle, text_to_music_toggle],
|
| 8623 |
outputs=[
|
| 8624 |
sidebar_chatbot,
|
| 8625 |
sidebar_msg,
|
|
|
|
| 8640 |
image_to_video_prompt,
|
| 8641 |
text_to_video_toggle,
|
| 8642 |
text_to_video_prompt,
|
| 8643 |
+
video_to_video_toggle,
|
| 8644 |
+
video_to_video_prompt,
|
| 8645 |
+
video_input,
|
| 8646 |
text_to_music_toggle,
|
| 8647 |
text_to_music_prompt,
|
| 8648 |
model_dropdown,
|