Spaces:
Running
Running
tayyab415
commited on
Commit
Β·
ba7ebb7
1
Parent(s):
4471bb8
Add time estimate hint to Step 1 in Gradio UI (~3-4 mins)
Browse files
app.py
CHANGED
|
@@ -440,13 +440,13 @@ def reset_workflow():
|
|
| 440 |
|
| 441 |
def add_production_wrapper(video_file, mood_override, enable_smart_crop, add_intro_image, add_subtitles, progress=gr.Progress()):
|
| 442 |
"""Production via Modal - uploads video and processes with fresh job_id"""
|
| 443 |
-
|
| 444 |
# Debug: Log what we received from Gradio
|
| 445 |
logger.info(f"video_file type: {type(video_file)}")
|
| 446 |
logger.info(f"video_file value: {video_file}")
|
| 447 |
-
|
| 448 |
actual_video_path = None
|
| 449 |
-
|
| 450 |
# Handle different Gradio 6 input formats
|
| 451 |
if video_file is None:
|
| 452 |
if workflow_state.get('final_video_path'):
|
|
@@ -460,7 +460,8 @@ def add_production_wrapper(video_file, mood_override, enable_smart_crop, add_int
|
|
| 460 |
actual_video_path = video_file
|
| 461 |
elif isinstance(video_file, dict):
|
| 462 |
# Gradio 6 may return dict with 'path' or 'name' key
|
| 463 |
-
actual_video_path = video_file.get('path') or video_file.get(
|
|
|
|
| 464 |
logger.info(f"Extracted path from dict: {actual_video_path}")
|
| 465 |
elif hasattr(video_file, 'name'):
|
| 466 |
# File-like object
|
|
@@ -468,7 +469,7 @@ def add_production_wrapper(video_file, mood_override, enable_smart_crop, add_int
|
|
| 468 |
else:
|
| 469 |
yield f"β Unexpected video input type: {type(video_file)}", None
|
| 470 |
return
|
| 471 |
-
|
| 472 |
if not actual_video_path:
|
| 473 |
yield "β Could not determine video file path", None
|
| 474 |
return
|
|
@@ -487,35 +488,37 @@ def add_production_wrapper(video_file, mood_override, enable_smart_crop, add_int
|
|
| 487 |
try:
|
| 488 |
progress(0.1, desc="Preparing video...")
|
| 489 |
yield "π€ Preparing video for processing...", None
|
| 490 |
-
|
| 491 |
# Copy file to our temp directory to prevent Gradio cleanup issues
|
| 492 |
import shutil
|
| 493 |
import uuid
|
| 494 |
-
|
| 495 |
temp_dir = os.path.join(os.getcwd(), "temp")
|
| 496 |
os.makedirs(temp_dir, exist_ok=True)
|
| 497 |
-
|
| 498 |
# Generate a unique filename
|
| 499 |
temp_filename = f"upload_{uuid.uuid4().hex[:8]}_{os.path.basename(actual_video_path)}"
|
| 500 |
local_video_path = os.path.join(temp_dir, temp_filename)
|
| 501 |
-
|
| 502 |
logger.info(f"Copying uploaded file to: {local_video_path}")
|
| 503 |
shutil.copy2(actual_video_path, local_video_path)
|
| 504 |
-
|
| 505 |
# Get the file size for logging
|
| 506 |
file_size = os.path.getsize(local_video_path) / 1024 / 1024
|
| 507 |
-
logger.info(
|
|
|
|
| 508 |
|
| 509 |
# Read video as base64 for transfer to Modal
|
| 510 |
progress(0.2, desc="Reading video file...")
|
| 511 |
yield "π¦ Reading video file...", None
|
| 512 |
-
|
| 513 |
with open(local_video_path, 'rb') as f:
|
| 514 |
video_bytes = f.read()
|
| 515 |
video_base64 = base64.b64encode(video_bytes).decode('utf-8')
|
| 516 |
-
|
| 517 |
-
logger.info(
|
| 518 |
-
|
|
|
|
| 519 |
progress(0.3, desc="Processing on Modal...")
|
| 520 |
yield "π¬ Processing on Modal (this may take a few minutes)...", None
|
| 521 |
|
|
@@ -665,22 +668,22 @@ def add_production_to_video(
|
|
| 665 |
) -> str:
|
| 666 |
"""
|
| 667 |
π¬ MAIN VIDEO PROCESSING TOOL - Transform any video into viral-ready content!
|
| 668 |
-
|
| 669 |
β οΈ IMPORTANT: This tool requires a WEB URL (http:// or https://), NOT a local file path!
|
| 670 |
- β
YouTube URLs work: https://youtube.com/watch?v=...
|
| 671 |
- β
Direct video URLs work: https://example.com/video.mp4
|
| 672 |
- β Local paths do NOT work: /mnt/data/file.mp4
|
| 673 |
-
|
| 674 |
If the user uploads a file, tell them to:
|
| 675 |
1. Upload the video to YouTube (unlisted) and provide the URL, OR
|
| 676 |
2. Use a cloud storage link (Google Drive public link, Dropbox, etc.)
|
| 677 |
-
|
| 678 |
This is the PRIMARY tool for video editing. Use this tool when the user wants to:
|
| 679 |
- Process a YouTube video
|
| 680 |
- Add professional production value to a video
|
| 681 |
- Create short-form vertical content for TikTok/Reels/Shorts
|
| 682 |
- Add intros, subtitles, smart crop, or background music
|
| 683 |
-
|
| 684 |
The tool automatically:
|
| 685 |
1. Downloads the video from the URL
|
| 686 |
2. Applies AI-powered 9:16 smart crop for mobile viewing
|
|
@@ -688,7 +691,7 @@ def add_production_to_video(
|
|
| 688 |
4. Adds auto-generated subtitles using Whisper
|
| 689 |
5. Adds mood-matched background music
|
| 690 |
6. Returns a download link for the finished video
|
| 691 |
-
|
| 692 |
Parameters:
|
| 693 |
video_url: The WEB URL of the video (must start with http:// or https://)
|
| 694 |
- YouTube URL: https://youtube.com/watch?v=VIDEO_ID
|
|
@@ -697,7 +700,7 @@ def add_production_to_video(
|
|
| 697 |
enable_smart_crop: If True, crops video to 9:16 vertical format for mobile
|
| 698 |
add_intro: If True, generates AI intro with voiceover and title card
|
| 699 |
add_subtitles: If True, adds auto-generated subtitles
|
| 700 |
-
|
| 701 |
Returns:
|
| 702 |
Processing result with a download URL for the produced video
|
| 703 |
"""
|
|
@@ -705,16 +708,17 @@ def add_production_to_video(
|
|
| 705 |
# Validate URL - must be a web URL, not a local path
|
| 706 |
if not video_url:
|
| 707 |
return "β Error: No video URL provided"
|
| 708 |
-
|
| 709 |
# Check for local file paths (reject these)
|
| 710 |
if video_url.startswith('/mnt/data/') or video_url.startswith('/tmp/') or video_url.startswith('C:\\'):
|
| 711 |
return f"β Error: Local file path detected.\n\nβ οΈ This tool requires a web URL, not a local file.\n\n**Options:**\n1. Use the Video Studio widget to upload files directly\n2. Upload to YouTube (unlisted) and share the URL\n3. Use a cloud storage link (Google Drive, Dropbox)"
|
| 712 |
-
|
| 713 |
if not video_url.startswith(('http://', 'https://')):
|
| 714 |
return f"β Error: Invalid URL '{video_url[:50]}...'\n\nβ οΈ This tool requires a web URL (http:// or https://), not a local file path.\n\n**Please provide:**\n- A YouTube URL: https://youtube.com/watch?v=...\n- Or a direct video URL: https://example.com/video.mp4\n\nIf you uploaded a file, please upload it to YouTube (unlisted) first and share that URL."
|
| 715 |
-
|
| 716 |
-
logger.info(
|
| 717 |
-
|
|
|
|
| 718 |
# Call Modal Step 6 with video_url for standalone mode
|
| 719 |
response = call_modal("step", data={
|
| 720 |
"step": 6,
|
|
@@ -724,18 +728,18 @@ def add_production_to_video(
|
|
| 724 |
"add_intro": add_intro,
|
| 725 |
"add_subtitles": add_subtitles,
|
| 726 |
}, timeout=2400) # 40 min timeout for large videos
|
| 727 |
-
|
| 728 |
if response.get("error"):
|
| 729 |
return f"β Error: {response['error']}"
|
| 730 |
-
|
| 731 |
if response.get("success"):
|
| 732 |
job_id = response.get('job_id')
|
| 733 |
duration = response.get('duration', 0)
|
| 734 |
detected_mood = response.get('mood', mood)
|
| 735 |
-
|
| 736 |
# Build download URL
|
| 737 |
download_url = f"https://tayyabkhn343--directors-cut-download.modal.run?job_id={job_id}"
|
| 738 |
-
|
| 739 |
result = f"""β
Video produced successfully!
|
| 740 |
|
| 741 |
π **Details:**
|
|
@@ -748,11 +752,11 @@ def add_production_to_video(
|
|
| 748 |
- Background Music: {'β' if response.get('has_music') else 'β'}
|
| 749 |
|
| 750 |
π **Download:** {download_url}"""
|
| 751 |
-
|
| 752 |
return result
|
| 753 |
else:
|
| 754 |
return f"β Processing failed: {response.get('error', 'Unknown error')}"
|
| 755 |
-
|
| 756 |
except Exception as e:
|
| 757 |
logger.error(f"Production pipeline error: {e}")
|
| 758 |
return f"β Error: {str(e)}"
|
|
@@ -1251,19 +1255,19 @@ def video_studio_widget_html():
|
|
| 1251 |
def open_video_studio() -> str:
|
| 1252 |
"""
|
| 1253 |
π¬ Open the Director's Cut Video Studio interface.
|
| 1254 |
-
|
| 1255 |
Use this tool when the user wants to:
|
| 1256 |
- Process or edit a video
|
| 1257 |
- Upload a video file
|
| 1258 |
- Add production value to any video
|
| 1259 |
- Create viral short-form content
|
| 1260 |
-
|
| 1261 |
This opens an interactive studio where users can:
|
| 1262 |
- Paste a YouTube URL OR upload a video file directly
|
| 1263 |
- Choose mood (hype, chill, suspense, or auto-detect)
|
| 1264 |
- Enable/disable smart crop, AI intro, and subtitles
|
| 1265 |
- Process the video and download the result
|
| 1266 |
-
|
| 1267 |
Returns:
|
| 1268 |
str: Confirmation that the studio is ready
|
| 1269 |
"""
|
|
@@ -1291,6 +1295,7 @@ with gr.Blocks(title="Director's Cut") as app:
|
|
| 1291 |
# Step 1
|
| 1292 |
with gr.Group():
|
| 1293 |
gr.Markdown("### Step 1: Analyze Video")
|
|
|
|
| 1294 |
step1_btn = gr.Button(
|
| 1295 |
"1οΈβ£ Analyze & Classify", variant="primary")
|
| 1296 |
step1_output = gr.Markdown()
|
|
@@ -1376,35 +1381,40 @@ with gr.Blocks(title="Director's Cut") as app:
|
|
| 1376 |
with gr.Tab("π€ ChatGPT Integration", visible=False):
|
| 1377 |
# This tab binds MCP tools and resources to Gradio events
|
| 1378 |
# Required by Gradio MCP to expose them to ChatGPT
|
| 1379 |
-
|
| 1380 |
# Bind the add_production_to_video tool
|
| 1381 |
chatgpt_url_input = gr.Textbox(label="Video URL")
|
| 1382 |
-
chatgpt_mood = gr.Dropdown(
|
|
|
|
| 1383 |
chatgpt_crop = gr.Checkbox(value=True)
|
| 1384 |
chatgpt_intro = gr.Checkbox(value=True)
|
| 1385 |
chatgpt_subs = gr.Checkbox(value=True)
|
| 1386 |
chatgpt_output = gr.Textbox(label="Result")
|
| 1387 |
chatgpt_btn = gr.Button("Process Video")
|
| 1388 |
chatgpt_btn.click(
|
| 1389 |
-
add_production_to_video,
|
| 1390 |
-
inputs=[chatgpt_url_input, chatgpt_mood,
|
|
|
|
| 1391 |
outputs=chatgpt_output
|
| 1392 |
)
|
| 1393 |
-
|
| 1394 |
# Bind the production widget resource
|
| 1395 |
-
widget_code = gr.Code(label="Widget HTML",
|
|
|
|
| 1396 |
widget_btn = gr.Button("Show Widget")
|
| 1397 |
widget_btn.click(production_widget_html, outputs=widget_code)
|
| 1398 |
-
|
| 1399 |
# Bind the Video Studio tool
|
| 1400 |
studio_output = gr.Textbox(label="Studio Status")
|
| 1401 |
studio_btn = gr.Button("Open Video Studio")
|
| 1402 |
studio_btn.click(open_video_studio, outputs=studio_output)
|
| 1403 |
-
|
| 1404 |
# Bind the Video Studio widget resource
|
| 1405 |
-
studio_widget_code = gr.Code(
|
|
|
|
| 1406 |
studio_widget_btn = gr.Button("Get Video Studio Widget")
|
| 1407 |
-
studio_widget_btn.click(
|
|
|
|
| 1408 |
|
| 1409 |
|
| 1410 |
if __name__ == "__main__":
|
|
|
|
| 440 |
|
| 441 |
def add_production_wrapper(video_file, mood_override, enable_smart_crop, add_intro_image, add_subtitles, progress=gr.Progress()):
|
| 442 |
"""Production via Modal - uploads video and processes with fresh job_id"""
|
| 443 |
+
|
| 444 |
# Debug: Log what we received from Gradio
|
| 445 |
logger.info(f"video_file type: {type(video_file)}")
|
| 446 |
logger.info(f"video_file value: {video_file}")
|
| 447 |
+
|
| 448 |
actual_video_path = None
|
| 449 |
+
|
| 450 |
# Handle different Gradio 6 input formats
|
| 451 |
if video_file is None:
|
| 452 |
if workflow_state.get('final_video_path'):
|
|
|
|
| 460 |
actual_video_path = video_file
|
| 461 |
elif isinstance(video_file, dict):
|
| 462 |
# Gradio 6 may return dict with 'path' or 'name' key
|
| 463 |
+
actual_video_path = video_file.get('path') or video_file.get(
|
| 464 |
+
'name') or video_file.get('video')
|
| 465 |
logger.info(f"Extracted path from dict: {actual_video_path}")
|
| 466 |
elif hasattr(video_file, 'name'):
|
| 467 |
# File-like object
|
|
|
|
| 469 |
else:
|
| 470 |
yield f"β Unexpected video input type: {type(video_file)}", None
|
| 471 |
return
|
| 472 |
+
|
| 473 |
if not actual_video_path:
|
| 474 |
yield "β Could not determine video file path", None
|
| 475 |
return
|
|
|
|
| 488 |
try:
|
| 489 |
progress(0.1, desc="Preparing video...")
|
| 490 |
yield "π€ Preparing video for processing...", None
|
| 491 |
+
|
| 492 |
# Copy file to our temp directory to prevent Gradio cleanup issues
|
| 493 |
import shutil
|
| 494 |
import uuid
|
| 495 |
+
|
| 496 |
temp_dir = os.path.join(os.getcwd(), "temp")
|
| 497 |
os.makedirs(temp_dir, exist_ok=True)
|
| 498 |
+
|
| 499 |
# Generate a unique filename
|
| 500 |
temp_filename = f"upload_{uuid.uuid4().hex[:8]}_{os.path.basename(actual_video_path)}"
|
| 501 |
local_video_path = os.path.join(temp_dir, temp_filename)
|
| 502 |
+
|
| 503 |
logger.info(f"Copying uploaded file to: {local_video_path}")
|
| 504 |
shutil.copy2(actual_video_path, local_video_path)
|
| 505 |
+
|
| 506 |
# Get the file size for logging
|
| 507 |
file_size = os.path.getsize(local_video_path) / 1024 / 1024
|
| 508 |
+
logger.info(
|
| 509 |
+
f"Processing video: {local_video_path} ({file_size:.1f} MB)")
|
| 510 |
|
| 511 |
# Read video as base64 for transfer to Modal
|
| 512 |
progress(0.2, desc="Reading video file...")
|
| 513 |
yield "π¦ Reading video file...", None
|
| 514 |
+
|
| 515 |
with open(local_video_path, 'rb') as f:
|
| 516 |
video_bytes = f.read()
|
| 517 |
video_base64 = base64.b64encode(video_bytes).decode('utf-8')
|
| 518 |
+
|
| 519 |
+
logger.info(
|
| 520 |
+
f"Video encoded: {len(video_base64) / 1024 / 1024:.1f} MB base64")
|
| 521 |
+
|
| 522 |
progress(0.3, desc="Processing on Modal...")
|
| 523 |
yield "π¬ Processing on Modal (this may take a few minutes)...", None
|
| 524 |
|
|
|
|
| 668 |
) -> str:
|
| 669 |
"""
|
| 670 |
π¬ MAIN VIDEO PROCESSING TOOL - Transform any video into viral-ready content!
|
| 671 |
+
|
| 672 |
β οΈ IMPORTANT: This tool requires a WEB URL (http:// or https://), NOT a local file path!
|
| 673 |
- β
YouTube URLs work: https://youtube.com/watch?v=...
|
| 674 |
- β
Direct video URLs work: https://example.com/video.mp4
|
| 675 |
- β Local paths do NOT work: /mnt/data/file.mp4
|
| 676 |
+
|
| 677 |
If the user uploads a file, tell them to:
|
| 678 |
1. Upload the video to YouTube (unlisted) and provide the URL, OR
|
| 679 |
2. Use a cloud storage link (Google Drive public link, Dropbox, etc.)
|
| 680 |
+
|
| 681 |
This is the PRIMARY tool for video editing. Use this tool when the user wants to:
|
| 682 |
- Process a YouTube video
|
| 683 |
- Add professional production value to a video
|
| 684 |
- Create short-form vertical content for TikTok/Reels/Shorts
|
| 685 |
- Add intros, subtitles, smart crop, or background music
|
| 686 |
+
|
| 687 |
The tool automatically:
|
| 688 |
1. Downloads the video from the URL
|
| 689 |
2. Applies AI-powered 9:16 smart crop for mobile viewing
|
|
|
|
| 691 |
4. Adds auto-generated subtitles using Whisper
|
| 692 |
5. Adds mood-matched background music
|
| 693 |
6. Returns a download link for the finished video
|
| 694 |
+
|
| 695 |
Parameters:
|
| 696 |
video_url: The WEB URL of the video (must start with http:// or https://)
|
| 697 |
- YouTube URL: https://youtube.com/watch?v=VIDEO_ID
|
|
|
|
| 700 |
enable_smart_crop: If True, crops video to 9:16 vertical format for mobile
|
| 701 |
add_intro: If True, generates AI intro with voiceover and title card
|
| 702 |
add_subtitles: If True, adds auto-generated subtitles
|
| 703 |
+
|
| 704 |
Returns:
|
| 705 |
Processing result with a download URL for the produced video
|
| 706 |
"""
|
|
|
|
| 708 |
# Validate URL - must be a web URL, not a local path
|
| 709 |
if not video_url:
|
| 710 |
return "β Error: No video URL provided"
|
| 711 |
+
|
| 712 |
# Check for local file paths (reject these)
|
| 713 |
if video_url.startswith('/mnt/data/') or video_url.startswith('/tmp/') or video_url.startswith('C:\\'):
|
| 714 |
return f"β Error: Local file path detected.\n\nβ οΈ This tool requires a web URL, not a local file.\n\n**Options:**\n1. Use the Video Studio widget to upload files directly\n2. Upload to YouTube (unlisted) and share the URL\n3. Use a cloud storage link (Google Drive, Dropbox)"
|
| 715 |
+
|
| 716 |
if not video_url.startswith(('http://', 'https://')):
|
| 717 |
return f"β Error: Invalid URL '{video_url[:50]}...'\n\nβ οΈ This tool requires a web URL (http:// or https://), not a local file path.\n\n**Please provide:**\n- A YouTube URL: https://youtube.com/watch?v=...\n- Or a direct video URL: https://example.com/video.mp4\n\nIf you uploaded a file, please upload it to YouTube (unlisted) first and share that URL."
|
| 718 |
+
|
| 719 |
+
logger.info(
|
| 720 |
+
f"π¬ Production pipeline starting for: {video_url[:100]}...")
|
| 721 |
+
|
| 722 |
# Call Modal Step 6 with video_url for standalone mode
|
| 723 |
response = call_modal("step", data={
|
| 724 |
"step": 6,
|
|
|
|
| 728 |
"add_intro": add_intro,
|
| 729 |
"add_subtitles": add_subtitles,
|
| 730 |
}, timeout=2400) # 40 min timeout for large videos
|
| 731 |
+
|
| 732 |
if response.get("error"):
|
| 733 |
return f"β Error: {response['error']}"
|
| 734 |
+
|
| 735 |
if response.get("success"):
|
| 736 |
job_id = response.get('job_id')
|
| 737 |
duration = response.get('duration', 0)
|
| 738 |
detected_mood = response.get('mood', mood)
|
| 739 |
+
|
| 740 |
# Build download URL
|
| 741 |
download_url = f"https://tayyabkhn343--directors-cut-download.modal.run?job_id={job_id}"
|
| 742 |
+
|
| 743 |
result = f"""β
Video produced successfully!
|
| 744 |
|
| 745 |
π **Details:**
|
|
|
|
| 752 |
- Background Music: {'β' if response.get('has_music') else 'β'}
|
| 753 |
|
| 754 |
π **Download:** {download_url}"""
|
| 755 |
+
|
| 756 |
return result
|
| 757 |
else:
|
| 758 |
return f"β Processing failed: {response.get('error', 'Unknown error')}"
|
| 759 |
+
|
| 760 |
except Exception as e:
|
| 761 |
logger.error(f"Production pipeline error: {e}")
|
| 762 |
return f"β Error: {str(e)}"
|
|
|
|
| 1255 |
def open_video_studio() -> str:
|
| 1256 |
"""
|
| 1257 |
π¬ Open the Director's Cut Video Studio interface.
|
| 1258 |
+
|
| 1259 |
Use this tool when the user wants to:
|
| 1260 |
- Process or edit a video
|
| 1261 |
- Upload a video file
|
| 1262 |
- Add production value to any video
|
| 1263 |
- Create viral short-form content
|
| 1264 |
+
|
| 1265 |
This opens an interactive studio where users can:
|
| 1266 |
- Paste a YouTube URL OR upload a video file directly
|
| 1267 |
- Choose mood (hype, chill, suspense, or auto-detect)
|
| 1268 |
- Enable/disable smart crop, AI intro, and subtitles
|
| 1269 |
- Process the video and download the result
|
| 1270 |
+
|
| 1271 |
Returns:
|
| 1272 |
str: Confirmation that the studio is ready
|
| 1273 |
"""
|
|
|
|
| 1295 |
# Step 1
|
| 1296 |
with gr.Group():
|
| 1297 |
gr.Markdown("### Step 1: Analyze Video")
|
| 1298 |
+
gr.Markdown("*Downloads video & extracts transcript (~3-4 mins)*", elem_classes=["step-hint"])
|
| 1299 |
step1_btn = gr.Button(
|
| 1300 |
"1οΈβ£ Analyze & Classify", variant="primary")
|
| 1301 |
step1_output = gr.Markdown()
|
|
|
|
| 1381 |
with gr.Tab("π€ ChatGPT Integration", visible=False):
|
| 1382 |
# This tab binds MCP tools and resources to Gradio events
|
| 1383 |
# Required by Gradio MCP to expose them to ChatGPT
|
| 1384 |
+
|
| 1385 |
# Bind the add_production_to_video tool
|
| 1386 |
chatgpt_url_input = gr.Textbox(label="Video URL")
|
| 1387 |
+
chatgpt_mood = gr.Dropdown(
|
| 1388 |
+
choices=["auto", "hype", "suspense", "chill"], value="auto")
|
| 1389 |
chatgpt_crop = gr.Checkbox(value=True)
|
| 1390 |
chatgpt_intro = gr.Checkbox(value=True)
|
| 1391 |
chatgpt_subs = gr.Checkbox(value=True)
|
| 1392 |
chatgpt_output = gr.Textbox(label="Result")
|
| 1393 |
chatgpt_btn = gr.Button("Process Video")
|
| 1394 |
chatgpt_btn.click(
|
| 1395 |
+
add_production_to_video,
|
| 1396 |
+
inputs=[chatgpt_url_input, chatgpt_mood,
|
| 1397 |
+
chatgpt_crop, chatgpt_intro, chatgpt_subs],
|
| 1398 |
outputs=chatgpt_output
|
| 1399 |
)
|
| 1400 |
+
|
| 1401 |
# Bind the production widget resource
|
| 1402 |
+
widget_code = gr.Code(label="Widget HTML",
|
| 1403 |
+
language="html", max_lines=5)
|
| 1404 |
widget_btn = gr.Button("Show Widget")
|
| 1405 |
widget_btn.click(production_widget_html, outputs=widget_code)
|
| 1406 |
+
|
| 1407 |
# Bind the Video Studio tool
|
| 1408 |
studio_output = gr.Textbox(label="Studio Status")
|
| 1409 |
studio_btn = gr.Button("Open Video Studio")
|
| 1410 |
studio_btn.click(open_video_studio, outputs=studio_output)
|
| 1411 |
+
|
| 1412 |
# Bind the Video Studio widget resource
|
| 1413 |
+
studio_widget_code = gr.Code(
|
| 1414 |
+
label="Video Studio Widget", language="html", max_lines=5)
|
| 1415 |
studio_widget_btn = gr.Button("Get Video Studio Widget")
|
| 1416 |
+
studio_widget_btn.click(
|
| 1417 |
+
video_studio_widget_html, outputs=studio_widget_code)
|
| 1418 |
|
| 1419 |
|
| 1420 |
if __name__ == "__main__":
|