skylinkd commited on
Commit
bba812d
Β·
verified Β·
1 Parent(s): 84ec9ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +294 -60
app.py CHANGED
@@ -6,6 +6,10 @@ import random
6
 
7
  # THIS IS THE FIX - PART 1
8
  os.environ['GRADIO_SUPPRESS_PROGRESS'] = 'true'
 
 
 
 
9
 
10
  import cv2
11
  import numpy as np
@@ -54,6 +58,10 @@ os.makedirs(TEMP_DIR, exist_ok=True)
54
  model_load_lock = Lock()
55
  loaded_detectors = {}
56
  whisper_model = None
 
 
 
 
57
 
58
  # --- Default Presets for Transfer Tab (Flat Dictionary) ---
59
  DEFAULT_LINK_PRESETS = {
@@ -1367,6 +1375,144 @@ def create_rhythmic_animatic(timeline_data, audio_path, measure_choice, out_w, o
1367
 
1368
  # --- NEW CREATIVE FUNCTIONS ---
1369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1370
  def generate_waveform_video(video_path, style, size, position, color):
1371
  if not video_path: raise gr.Error("Please upload a video first.")
1372
 
@@ -1574,8 +1720,7 @@ def _create_automated_slideshow_impl(images, audio_path, kb_effect_style, transi
1574
  looped_image_paths = [image_paths[i % len(image_paths)] for i in range(num_clips)]
1575
 
1576
  kb_clips = []
1577
- # <-- FIX: Use a proper progress bar that tracks the whole process -->
1578
- total_steps = num_clips * 2 # Roughly 1 step for creating, 1 for joining
1579
  current_step = 0
1580
 
1581
  for i in range(num_clips):
@@ -1624,33 +1769,30 @@ def _create_automated_slideshow_impl(images, audio_path, kb_effect_style, transi
1624
 
1625
  if not kb_clips: raise gr.Error("No clips were generated. The audio may be too short or the rhythm settings too fast.")
1626
 
1627
- # --- FIX: New iterative chaining logic ---
 
1628
  if len(kb_clips) == 1:
1629
- final_silent_path = kb_clips[0]['path']
1630
  else:
1631
  all_transitions = ["fade", "wipeleft", "wiperight", "wipeup", "wipedown", "slideleft", "slideright", "slideup", "slidedown", "dissolve"]
 
 
 
1632
 
1633
- # Start with the first clip
1634
- current_video = kb_clips[0]['path']
1635
- running_duration = kb_clips[0]['duration']
1636
 
1637
  for i in range(len(kb_clips) - 1):
1638
- progress(current_step / total_steps, desc=f"Applying transition {i+1}/{len(kb_clips)-1}")
1639
- next_clip = kb_clips[i+1]['path']
1640
- output_path = os.path.join(job_temp_dir, f"temp_{i}.mp4")
1641
-
1642
  transition = random.choice(all_transitions) if transition_style == "Random" else transition_style.lower()
1643
- offset = running_duration - transition_duration
1644
-
1645
- filter_complex = f"[0:v][1:v]xfade=transition={transition}:duration={transition_duration}:offset={offset}[v]"
1646
- cmd_transition = ["ffmpeg", "-y", "-i", current_video, "-i", next_clip, "-filter_complex", filter_complex, "-map", "[v]", "-c:v", "libx264", "-pix_fmt", "yuv420p", output_path]
1647
- run_ffmpeg_command(cmd_transition)
1648
 
1649
- current_video = output_path
1650
- running_duration += kb_clips[i+1]['duration'] - transition_duration
1651
- current_step += 1
1652
-
1653
- final_silent_path = current_video
1654
 
1655
  progress(0.98, desc="Muxing final audio...")
1656
  final_output_path = os.path.join(TEMP_DIR, f"slideshow_final_{timestamp}.mp4")
@@ -1827,10 +1969,68 @@ bling_js = """
1827
  const a=document.createElement("script");a.setAttribute("src","https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js"),document.head.appendChild(a),a.onload=()=>{var e=confetti.create(null,{resize:!0,useWorker:!0});e({particleCount:150,spread:90,origin:{y:.6}})}
1828
  }
1829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1830
  // --- Dynamic Page Title ---
1831
  function update_title(tab_name) {
1832
  if (tab_name) {
1833
- // Strip emoji from tab name for a cleaner title
1834
  const clean_name = tab_name.replace(/[\\u{1F600}-\\u{1F64F}\\u{1F300}-\\u{1F5FF}\\u{1F680}-\\u{1F6FF}\\u{1F700}-\\u{1F77F}\\u{1F780}-\\u{1F7FF}\\u{1F800}-\\u{1F8FF}\\u{1F900}-\\u{1F9FF}\\u{1FA00}-\\u{1FA6F}\\u{1FA70}-\\u{1FAFF}\\u{2600}-\\u{26FF}\\u{2700}-\\u{27BF}]/gu, '').trim();
1835
  document.title = `Skriptz - ${clean_name}`;
1836
  } else {
@@ -1844,7 +2044,6 @@ bling_js = """
1844
  if(text_area) {
1845
  text_area.select();
1846
  document.execCommand('copy');
1847
- // Show a temporary "Copied!" message
1848
  const original_button = this.event.target;
1849
  const original_text = original_button.innerText;
1850
  original_button.innerText = 'Copied!';
@@ -1863,6 +2062,7 @@ bling_js = """
1863
  show_overlay,
1864
  hide_overlay,
1865
  fire_confetti,
 
1866
  update_title,
1867
  copy_to_clipboard,
1868
  storyboard_get_time
@@ -1892,7 +2092,7 @@ with gr.Blocks(
1892
  storyboard_get_time_js = "() => { return window.skriptz_bling.storyboard_get_time(); }"
1893
  show_overlay_js = "() => { window.skriptz_bling.show_overlay('Working hard... this may take a moment!'); }"
1894
  hide_overlay_js = "() => { window.skriptz_bling.hide_overlay(); }"
1895
- fire_confetti_js = "() => { window.skriptz_bling.fire_confetti(); }"
1896
  copy_transcription_js = "() => { window.skriptz_bling.copy_to_clipboard('transcription_textbox'); }"
1897
 
1898
  with gr.Tabs(elem_id="main_tabs") as main_tabs:
@@ -1993,11 +2193,11 @@ with gr.Blocks(
1993
  clear_assets_btn.click(lambda: ([], gr.update(value=None), None), outputs=[assets_state, asset_gallery, asset_preview_gallery])
1994
  clear_timeline_btn.click(lambda: [], outputs=[timeline_state])
1995
 
1996
- generate_animatic_btn.click(fn=create_animatic, inputs=[timeline_state, animatic_audio, animatic_out_w, animatic_out_h], outputs=animatic_output_video).then(fn=None, js=fire_confetti_js)
1997
 
1998
  analyze_bpm_btn.click(detect_bpm, animatic_audio, bpm_display)
1999
 
2000
- generate_rhythmic_btn.click(fn=create_rhythmic_animatic, inputs=[timeline_state, animatic_audio, measure_dropdown, animatic_out_w, animatic_out_h], outputs=animatic_output_video).then(fn=None, js=fire_confetti_js)
2001
 
2002
 
2003
  with gr.TabItem("🎨 Creative Suite", elem_id="creative_tab"):
@@ -2027,8 +2227,40 @@ with gr.Blocks(
2027
  fn=slideshow_wrapper,
2028
  inputs=[slideshow_input_images, slideshow_audio, slideshow_kb_effect, slideshow_transition, slideshow_rhythm, slideshow_out_w, slideshow_out_h],
2029
  outputs=slideshow_output_video
2030
- ).then(fn=None, js=fire_confetti_js)
2031
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2032
  with gr.TabItem("🎡 Audio Waveform"):
2033
  gr.Markdown("## Generate & Overlay Audio Waveforms")
2034
  gr.Info("Upload a video with an audio track to generate a dynamic waveform visualization.")
@@ -2048,7 +2280,7 @@ with gr.Blocks(
2048
  fn=generate_waveform_video,
2049
  inputs=[waveform_input_video, waveform_style, waveform_size, waveform_position, waveform_color],
2050
  outputs=waveform_output_video
2051
- )
2052
 
2053
  with gr.TabItem("πŸ–ΌοΈ Picture-in-Picture"):
2054
  gr.Markdown("## Create Picture-in-Picture (PiP) & Image Overlays")
@@ -2070,7 +2302,7 @@ with gr.Blocks(
2070
  fn=create_pip_video,
2071
  inputs=[pip_main_video, pip_overlay_media, pip_position, pip_scale],
2072
  outputs=pip_output_video
2073
- )
2074
 
2075
  with gr.TabItem("πŸ˜‚ Meme Creator"):
2076
  gr.Markdown("## Classic Meme & Text Overlay Creator")
@@ -2096,7 +2328,7 @@ with gr.Blocks(
2096
  fn=create_meme,
2097
  inputs=[meme_input_image, meme_text, meme_position, meme_font, meme_font_size, meme_text_color, meme_outline_color],
2098
  outputs=meme_output_image
2099
- )
2100
 
2101
  with gr.TabItem("πŸ–ΌοΈ Image Utilities", elem_id="image_tab"):
2102
  gr.Markdown("## Tools for processing and generating single images.")
@@ -2114,7 +2346,7 @@ with gr.Blocks(
2114
  manip_apply_btn = gr.Button("🎨 Apply Manipulation", variant="primary")
2115
  with gr.Column():
2116
  manip_output_image = gr.Image(label="Output Image", interactive=True)
2117
- manip_apply_btn.click(fn=manipulate_image, inputs=[manip_input_image, manip_operation_radio], outputs=manip_output_image)
2118
 
2119
  with gr.TabItem("πŸ“Ή Image to Video"):
2120
  gr.Markdown("### Create a short, looping video from a single static image.")
@@ -2126,7 +2358,7 @@ with gr.Blocks(
2126
  compile_i2v_btn = gr.Button("🎬 Create Looping Video", variant="primary")
2127
  with gr.Column():
2128
  output_video_i2v = gr.Video(label="Output Looping Video", interactive=True, show_download_button=True)
2129
- compile_i2v_btn.click(image_to_looping_video, [input_image_i2v, duration_slider_i2v, input_audio_i2v], output_video_i2v)
2130
 
2131
  with gr.TabItem("πŸ”Ž Zoom Video"):
2132
  gr.Markdown("### Create a 'Ken Burns' style zoom/pan video from an image.")
@@ -2157,7 +2389,7 @@ with gr.Blocks(
2157
  fn=create_zoom_videos,
2158
  inputs=[i2zv_input_images, i2zv_duration, i2zv_zoom_ratio, i2zv_zoom_dir, i2zv_combine, i2zv_audio],
2159
  outputs=[i2zv_output_gallery, i2zv_output_video, i2zv_output_zip]
2160
- )
2161
 
2162
  with gr.TabItem("βœ‚οΈ Batch BG Remover"):
2163
  gr.Markdown("### Remove the background from a batch of images.")
@@ -2168,7 +2400,7 @@ with gr.Blocks(
2168
  with gr.Column():
2169
  output_gallery_bg = gr.Gallery(label="Images with Transparent Background", columns=4, object_fit="contain", height="auto")
2170
  output_zip_bg = gr.File(label="Download All as .zip", interactive=False)
2171
- remove_bg_btn.click(remove_background_batch, input_images_bg, [output_gallery_bg, output_zip_bg])
2172
 
2173
  with gr.TabItem("πŸ–‹οΈ Batch Watermarker"):
2174
  gr.Markdown("### Apply a text watermark to a batch of images.")
@@ -2182,7 +2414,7 @@ with gr.Blocks(
2182
  with gr.Column():
2183
  output_gallery_wm = gr.Gallery(label="Watermarked Images", columns=4, object_fit="contain", height="auto")
2184
  output_zip_wm = gr.File(label="Download All as .zip", interactive=False)
2185
- watermark_btn.click(apply_watermark_batch, [input_images_wm, watermark_text, watermark_pos, watermark_opacity], [output_gallery_wm, output_zip_wm])
2186
 
2187
  with gr.TabItem("↔️ Batch Resizer"):
2188
  gr.Markdown("### Convert, resize, and compress a batch of images.")
@@ -2208,7 +2440,7 @@ with gr.Blocks(
2208
  batch_resize_convert_images,
2209
  [brc_input_images, brc_format, brc_quality, brc_enable_resize, brc_max_w, brc_max_h, brc_resize_mode],
2210
  [brc_output_gallery, brc_output_zip]
2211
- )
2212
 
2213
  with gr.TabItem("πŸ€– FLUX.1 API"):
2214
  gr.Markdown("### Generate an image using `FLUX.1` models via Gradio Client.")
@@ -2224,7 +2456,7 @@ with gr.Blocks(
2224
  flux_btn = gr.Button("β˜„οΈ Generate Image", variant="primary")
2225
  with gr.Column():
2226
  output_image_flux = gr.Image(label="Generated Image", interactive=True)
2227
- flux_btn.click(call_flux_api, [prompt_input_flux, flux_model_dropdown, flux_width_slider, flux_height_slider, hf_token_input], output_image_flux)
2228
 
2229
  with gr.TabItem("πŸŽ₯ Video Utilities", elem_id="video_tab"):
2230
  gr.Markdown("## A collection of useful video tools.")
@@ -2239,7 +2471,7 @@ with gr.Blocks(
2239
  extract_fl_btn = gr.Button("🎬 Extract Frames", variant="primary")
2240
  with gr.Column():
2241
  output_gallery_fl = gr.Gallery(label="Output Frames (First, Last)", columns=2, object_fit="contain", height="auto")
2242
- extract_fl_btn.click(fn=extract_first_last_frame, inputs=input_video_fl, outputs=output_gallery_fl)
2243
 
2244
  with gr.TabItem("Video to Frames"):
2245
  gr.Markdown("### Extract all individual frames from a video file.")
@@ -2263,7 +2495,7 @@ with gr.Blocks(
2263
  input_video_v2f.upload(lambda v: f"{get_video_fps(v):.2f} FPS", input_video_v2f, v2f_fps_display)
2264
  v2f_resize.change(lambda x: [gr.update(interactive=x), gr.update(interactive=x)], v2f_resize, [v2f_width, v2f_height])
2265
  v2f_format.change(lambda x: gr.update(interactive=(x=="JPG")), v2f_format, v2f_jpg_quality)
2266
- extract_v2f_btn.click(video_to_frames_extractor, [input_video_v2f, v2f_skip_rate, v2f_rotation, v2f_resize, v2f_width, v2f_height, v2f_format, v2f_jpg_quality], [output_gallery_v2f, output_zip_v2f])
2267
 
2268
  with gr.TabItem("Frames to Video"):
2269
  gr.Markdown("### Compile a sequence of image frames into a video file.")
@@ -2281,7 +2513,7 @@ with gr.Blocks(
2281
  with gr.Column():
2282
  output_video_f2v = gr.Video(label="Compiled Video", interactive=True, show_download_button=True)
2283
  f2v_resize.change(lambda x: [gr.update(interactive=x), gr.update(interactive=x)], f2v_resize, [f2v_width, f2v_height])
2284
- compile_f2v_btn.click(create_video_from_frames, [input_frames_f2v, fps_slider_f2v, f2v_rotation, f2v_resize, f2v_width, f2v_height], output_video_f2v)
2285
 
2286
  with gr.TabItem("βœ‚οΈ Editing"):
2287
  with gr.Tabs():
@@ -2299,7 +2531,7 @@ with gr.Blocks(
2299
  with gr.Column():
2300
  vmanip_output_video = gr.Video(label="Output Video", interactive=True, show_download_button=True)
2301
 
2302
- vmanip_apply_btn.click(fn=manipulate_video, inputs=[vmanip_input_video, vmanip_operation_radio], outputs=vmanip_output_video)
2303
 
2304
  with gr.TabItem("Ping-Pong"):
2305
  gr.Markdown("### Create a forward-then-reverse video loop (Boomerang).")
@@ -2310,7 +2542,7 @@ with gr.Blocks(
2310
  pingpong_btn = gr.Button("πŸ“ Create Ping-Pong Video", variant="primary")
2311
  with gr.Column():
2312
  output_video_pingpong = gr.Video(label="Ping-Pong Video", interactive=True, show_download_button=True)
2313
- pingpong_btn.click(fn=ping_pong_video, inputs=[input_video_pingpong, audio_option_pingpong], outputs=output_video_pingpong)
2314
 
2315
  with gr.TabItem("Reverse"):
2316
  gr.Markdown("### Reverse a video clip.")
@@ -2321,7 +2553,7 @@ with gr.Blocks(
2321
  reverse_btn = gr.Button("πŸ”„ Reverse Video", variant="primary")
2322
  with gr.Column():
2323
  output_video_reverse = gr.Video(label="Reversed Video", interactive=True, show_download_button=True)
2324
- reverse_btn.click(fn=reverse_video, inputs=[input_video_reverse, audio_option_reverse], outputs=output_video_reverse)
2325
 
2326
  with gr.TabItem("Merger"):
2327
  gr.Markdown("### Simple Video Merger")
@@ -2336,7 +2568,7 @@ with gr.Blocks(
2336
  fn=merge_videos,
2337
  inputs=merger_input_videos,
2338
  outputs=merger_output_video
2339
- )
2340
 
2341
  with gr.TabItem("Trimmer"):
2342
  gr.Markdown("### Visually trim a video. Use the player to find a frame, then set it as the start or end point.")
@@ -2364,7 +2596,7 @@ with gr.Blocks(
2364
  set_end_btn.click(fn=None, js=get_current_time_js, outputs=[trim_end_time])
2365
  trim_start_time.change(fn=get_frame_from_time_wrapper, inputs=[input_video_trim, trim_start_time], outputs=[start_frame_img, trim_start_time_display])
2366
  trim_end_time.change(fn=get_frame_from_time_wrapper, inputs=[input_video_trim, trim_end_time], outputs=[end_frame_img, trim_end_time_display])
2367
- trim_btn.click(fn=trim_video, inputs=[input_video_trim, trim_start_time, trim_end_time], outputs=output_video_trim)
2368
  input_video_trim.clear(fn=lambda: (None, "0.00", None, "0.00", 0, 0), outputs=[start_frame_img, trim_start_time_display, end_frame_img, trim_end_time_display, trim_start_time, trim_end_time])
2369
 
2370
  with gr.TabItem("Crop & Resize"):
@@ -2390,7 +2622,7 @@ with gr.Blocks(
2390
  output_video_crop = gr.Video(label="Cropped Video", interactive=True, show_download_button=True)
2391
  crop_input_video.upload(fn=get_frame_at_time, inputs=crop_input_video, outputs=crop_preview_image)
2392
  crop_do_resize.change(lambda x: [gr.update(interactive=x), gr.update(interactive=x)], inputs=crop_do_resize, outputs=[crop_resize_w, crop_resize_h])
2393
- crop_btn.click(fn=crop_video, inputs=[crop_input_video, crop_x, crop_y, crop_w, crop_h, crop_do_resize, crop_resize_w, crop_resize_h], outputs=output_video_crop)
2394
 
2395
  with gr.TabItem("Change Speed"):
2396
  gr.Markdown("### Create slow-motion or fast-forward videos.")
@@ -2401,7 +2633,7 @@ with gr.Blocks(
2401
  speed_btn = gr.Button("πŸƒ Change Speed", variant="primary")
2402
  with gr.Column():
2403
  output_video_speed = gr.Video(label="Modified Video", interactive=True, show_download_button=True)
2404
- speed_btn.click(fn=change_video_speed, inputs=[input_video_speed, speed_multiplier], outputs=output_video_speed)
2405
 
2406
  with gr.TabItem("🎨 Effects & Overlays"):
2407
  with gr.Tabs():
@@ -2416,7 +2648,7 @@ with gr.Blocks(
2416
  fade_video_btn = gr.Button("✨ Apply Fade", variant="primary")
2417
  with gr.Column():
2418
  fade_output_video = gr.Video(label="Faded Video", interactive=True, show_download_button=True)
2419
- fade_video_btn.click(apply_video_fade, [fade_input_video, fade_in_slider, fade_out_slider], fade_output_video)
2420
 
2421
  with gr.TabItem("BG Remover"):
2422
  gr.Markdown("## Video Background Remover")
@@ -2427,7 +2659,7 @@ with gr.Blocks(
2427
  vbg_btn = gr.Button("βœ‚οΈ Remove Video Background", variant="primary")
2428
  with gr.Column():
2429
  vbg_output_video = gr.Video(label="Output Video with Transparency (.webm)", interactive=True, show_download_button=True)
2430
- vbg_btn.click(remove_video_background, vbg_input_video, vbg_output_video)
2431
 
2432
  with gr.TabItem("Watermark"):
2433
  gr.Markdown("### Apply a text watermark to a video.")
@@ -2443,7 +2675,7 @@ with gr.Blocks(
2443
  wm_btn = gr.Button("πŸ–‹οΈ Apply Watermark", variant="primary")
2444
  with gr.Column():
2445
  wm_output_video = gr.Video(label="Watermarked Video", interactive=True, show_download_button=True)
2446
- wm_btn.click(apply_video_watermark, [wm_input_video, wm_text, wm_pos, wm_opacity, wm_size, wm_color], wm_output_video)
2447
 
2448
  with gr.TabItem("Create GIF"):
2449
  gr.Markdown("### Convert a video clip into a high-quality animated GIF.")
@@ -2456,7 +2688,7 @@ with gr.Blocks(
2456
  gif_btn = gr.Button("πŸ–ΌοΈ Create GIF", variant="primary")
2457
  with gr.Column():
2458
  output_gif = gr.Image(label="Output GIF", interactive=True)
2459
- gif_btn.click(create_gif_from_video, [input_video_gif, gif_start_time, gif_end_time], output_gif)
2460
 
2461
  with gr.TabItem("πŸ”Š Audio & Transcription"):
2462
  with gr.Tabs():
@@ -2469,7 +2701,7 @@ with gr.Blocks(
2469
  add_audio_btn = gr.Button("🎢 Add Audio", variant="primary")
2470
  with gr.Column():
2471
  output_video_audio = gr.Video(label="Final Video with Audio", interactive=True, show_download_button=True)
2472
- add_audio_btn.click(add_audio_to_video, [input_video_audio, input_audio], output_video_audio)
2473
 
2474
  with gr.TabItem("Extract Audio"):
2475
  gr.Markdown("### Strip the audio track from a video file.")
@@ -2480,7 +2712,7 @@ with gr.Blocks(
2480
  extract_audio_btn = gr.Button("🎡 Extract Audio", variant="primary")
2481
  with gr.Column():
2482
  extract_audio_output = gr.Audio(label="Extracted Audio", type="filepath")
2483
- extract_audio_btn.click(extract_audio, [extract_audio_input_video, extract_audio_format], extract_audio_output)
2484
 
2485
  with gr.TabItem("Audio Editor"):
2486
  gr.Markdown("### Trim and Apply Fades to an Audio File")
@@ -2497,7 +2729,7 @@ with gr.Blocks(
2497
  audio_trim_fade_btn = gr.Button("βœ‚οΈ Process Audio", variant="primary")
2498
  with gr.Column():
2499
  audio_trim_output = gr.Audio(label="Processed Audio", type="filepath")
2500
- audio_trim_fade_btn.click(trim_and_fade_audio, [audio_trim_input, audio_start_time, audio_end_time, audio_fade_in, audio_fade_out], audio_trim_output)
2501
 
2502
  with gr.TabItem("Transcription", visible=(whisper is not None)):
2503
  gr.Markdown("## Transcribe Speech and Burn Subtitles")
@@ -2527,12 +2759,12 @@ with gr.Blocks(
2527
  fn=transcribe_and_prep_burn,
2528
  inputs=[transcribe_input, transcribe_model],
2529
  outputs=[transcribe_text, transcribe_files, transcribed_video_path_state, burn_accordion]
2530
- )
2531
  burn_btn.click(
2532
  fn=burn_subtitles,
2533
  inputs=[transcribed_video_path_state, transcribe_files, burn_font_size, burn_font_color],
2534
  outputs=burn_output_video
2535
- )
2536
 
2537
  with gr.TabItem("🧠 ControlNet", elem_id="controlnet_tab"):
2538
  gr.Markdown("## ControlNet Preprocessing")
@@ -2546,7 +2778,7 @@ with gr.Blocks(
2546
  process_btn_cn = gr.Button("✨ Process Video", variant="primary")
2547
  with gr.Column():
2548
  output_video_cn = gr.Video(label="Output ControlNet Video", interactive=True, show_download_button=True)
2549
- process_btn_cn.click(fn=process_video_with_detector, inputs=[input_video_cn, detector_dropdown_cn], outputs=output_video_cn)
2550
  with gr.TabItem("Process Batch Images"):
2551
  gr.Markdown("### Generate ControlNet Maps from one or more images.")
2552
  with gr.Row():
@@ -2557,7 +2789,7 @@ with gr.Blocks(
2557
  with gr.Column():
2558
  output_gallery_cn = gr.Gallery(label="Output ControlNet Images", columns=4, object_fit="contain", height="auto")
2559
  output_zip_cn = gr.File(label="Download All as .zip", interactive=False)
2560
- process_btn_img.click(fn=process_batch_images_with_detector, inputs=[input_images_cn, detector_dropdown_img], outputs=[output_gallery_cn, output_zip_cn])
2561
 
2562
  with gr.TabItem("πŸ—œοΈ Converter", elem_id="converter_tab"):
2563
  gr.Markdown("## Universal Video Converter & Compressor")
@@ -2579,7 +2811,7 @@ with gr.Blocks(
2579
  conv_acodec = gr.Dropdown(["copy", "aac", "opus"], value="copy", label="Audio Codec", info="'copy' is fastest and preserves quality.")
2580
  conv_abitrate = gr.Dropdown([96, 128, 192, 256, 320], value=192, label="Audio Bitrate (kbps)", interactive=False)
2581
  conv_acodec.change(lambda x: gr.update(interactive=(x != "copy")), conv_acodec, conv_abitrate)
2582
- conv_btn.click(fn=convert_compress_video, inputs=[conv_input_video, conv_format, conv_vcodec, conv_crf, conv_scale, conv_acodec, conv_abitrate], outputs=conv_output_video)
2583
 
2584
  with gr.TabItem("πŸ”— Transfer", elem_id="transfer_tab"):
2585
  gr.Markdown("## Image & Link Transfer Utility")
@@ -2627,4 +2859,6 @@ if __name__ == "__main__":
2627
  try: shutil.rmtree(TEMP_DIR)
2628
  except OSError as e: print(f"Error removing temp directory {TEMP_DIR}: {e}")
2629
  os.makedirs(TEMP_DIR, exist_ok=True)
2630
- demo.launch(inbrowser=True)
 
 
 
6
 
7
  # THIS IS THE FIX - PART 1
8
  os.environ['GRADIO_SUPPRESS_PROGRESS'] = 'true'
9
+ # THIS IS THE FIX - PART 2: Clean up console logs from Gradio
10
+ import logging
11
+ logging.getLogger('gradio').setLevel(logging.ERROR)
12
+
13
 
14
  import cv2
15
  import numpy as np
 
58
  model_load_lock = Lock()
59
  loaded_detectors = {}
60
  whisper_model = None
61
+ # --- ACTION REQUIRED ---
62
+ # Please download a sound file, name it 'finish_sound.mp3',
63
+ # and place it in the same directory as this app.py file.
64
+ # A good one can be found here: https://cdn.pixabay.com/download/audio/2022/03/15/audio_2289a72212.mp3?filename=whistle-108713.mp3
65
 
66
  # --- Default Presets for Transfer Tab (Flat Dictionary) ---
67
  DEFAULT_LINK_PRESETS = {
 
1375
 
1376
  # --- NEW CREATIVE FUNCTIONS ---
1377
 
1378
+ def _create_auto_trailer_impl(video_path, trailer_duration, clip_duration, analysis_method, transition_style, music_path, out_w, out_h, progress: Progress):
1379
+ """Internal implementation of the auto-trailer creator."""
1380
+ if not video_path: raise gr.Error("Please upload a source video.")
1381
+
1382
+ source_duration = get_media_duration(video_path)
1383
+
1384
+ if source_duration < trailer_duration:
1385
+ gr.Warning(f"Source video is only {source_duration:.1f}s long. The trailer duration will be capped at the source video length.")
1386
+ trailer_duration = source_duration
1387
+
1388
+ if clip_duration > trailer_duration:
1389
+ new_clip_duration = trailer_duration / 2 if trailer_duration > 2 else trailer_duration
1390
+ gr.Warning(f"Clip duration ({clip_duration}s) is longer than the trailer duration ({trailer_duration:.1f}s). Adjusting clip duration to {new_clip_duration:.1f}s.")
1391
+ clip_duration = new_clip_duration
1392
+
1393
+
1394
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1395
+ job_temp_dir = os.path.join(TEMP_DIR, f"trailer_{timestamp}")
1396
+ os.makedirs(job_temp_dir, exist_ok=True)
1397
+
1398
+ try:
1399
+ # --- 1. Analyze Video to Find Interesting Clips ---
1400
+ progress(0, desc="Analyzing video for high-motion scenes...")
1401
+
1402
+ cap = cv2.VideoCapture(video_path)
1403
+ fps = cap.get(cv2.CAP_PROP_FPS)
1404
+ if fps == 0: fps = 30 # fallback
1405
+
1406
+ chunk_duration_frames = int(clip_duration * fps)
1407
+ video_total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
1408
+
1409
+ chunk_scores = []
1410
+ prev_frame = None
1411
+
1412
+ frame_num = 0
1413
+ while cap.isOpened():
1414
+ ret, frame = cap.read()
1415
+ if not ret: break
1416
+
1417
+ frame_skip = max(1, int(fps / 5)) # Analyze ~5 frames per second
1418
+ if frame_num % frame_skip == 0:
1419
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
1420
+ gray = cv2.GaussianBlur(gray, (21, 21), 0)
1421
+
1422
+ if prev_frame is not None:
1423
+ frame_delta = cv2.absdiff(prev_frame, gray)
1424
+ thresh = cv2.threshold(frame_delta, 25, 255, cv2.THRESH_BINARY)[1]
1425
+ motion_score = np.sum(thresh)
1426
+
1427
+ chunk_index = frame_num // chunk_duration_frames
1428
+ while len(chunk_scores) <= chunk_index:
1429
+ chunk_scores.append({'start_time': (len(chunk_scores) * clip_duration), 'scores': []})
1430
+ chunk_scores[chunk_index]['scores'].append(motion_score)
1431
+
1432
+ prev_frame = gray
1433
+
1434
+ frame_num += 1
1435
+ if frame_num % 100 == 0:
1436
+ progress(0.2 * (frame_num / video_total_frames), desc=f"Analyzing frame {frame_num}/{video_total_frames}...")
1437
+
1438
+ cap.release()
1439
+
1440
+ final_chunks = [{'start_time': chunk['start_time'], 'score': sum(chunk['scores']) / len(chunk['scores'])} for chunk in chunk_scores if chunk['scores']]
1441
+
1442
+ if not final_chunks: raise gr.Error("Could not analyze video for motion. Is the video very short or static?")
1443
+
1444
+ # --- 2. Select the Best Clips ---
1445
+ progress(0.2, desc="Selecting the best clips...")
1446
+ num_clips_to_select = max(1, int(trailer_duration / clip_duration))
1447
+ selected_clips_info = sorted(sorted(final_chunks, key=lambda x: x['score'], reverse=True)[:num_clips_to_select], key=lambda x: x['start_time'])
1448
+
1449
+ # --- 3. Extract and Process Clips ---
1450
+ extracted_clips, out_w, out_h = [], int(out_w) - (int(out_w) % 2), int(out_h) - (int(out_h) % 2)
1451
+
1452
+ for i, clip_info in enumerate(selected_clips_info):
1453
+ progress(0.2 + (0.5 * (i / len(selected_clips_info))), desc=f"Extracting clip {i+1}/{len(selected_clips_info)}...")
1454
+ output_clip_path = os.path.join(job_temp_dir, f"clip_{i:03d}.mp4")
1455
+ vf_filter = f"scale={out_w}:{out_h}:force_original_aspect_ratio=decrease,pad={out_w}:{out_h}:(ow-iw)/2:(oh-ih)/2,setsar=1"
1456
+ cmd = ["ffmpeg", "-y", "-ss", str(clip_info['start_time']), "-i", video_path, "-t", str(clip_duration), "-vf", vf_filter, "-an", "-c:v", "libx264", "-pix_fmt", "yuv420p", output_clip_path]
1457
+ run_ffmpeg_command(cmd)
1458
+ extracted_clips.append(output_clip_path)
1459
+
1460
+ # --- 4. Stitch Clips with Transitions ---
1461
+ if not extracted_clips: raise gr.Error("Failed to extract any clips.")
1462
+ progress(0.7, desc="Stitching clips together...")
1463
+
1464
+ final_silent_path = os.path.join(job_temp_dir, "final_silent.mp4")
1465
+
1466
+ if transition_style == "None" or len(extracted_clips) == 1:
1467
+ if len(extracted_clips) == 1:
1468
+ shutil.copy(extracted_clips[0], final_silent_path)
1469
+ else:
1470
+ file_list_path = os.path.join(job_temp_dir, "files.txt")
1471
+ with open(file_list_path, 'w', encoding='utf-8') as f:
1472
+ for path in extracted_clips: f.write(f"file '{os.path.abspath(path)}'\n")
1473
+ run_ffmpeg_command(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", file_list_path, "-c", "copy", final_silent_path], "Concatenating clips...")
1474
+ else:
1475
+ # --- IMPROVEMENT: Build a single, efficient FFMPEG command ---
1476
+ transition_duration = 0.5
1477
+ cmd = ["ffmpeg", "-y"]
1478
+ filter_complex = []
1479
+ running_duration = 0
1480
+
1481
+ for i, clip_path in enumerate(extracted_clips):
1482
+ cmd.extend(["-i", clip_path])
1483
+
1484
+ for i in range(len(extracted_clips) - 1):
1485
+ input1 = f"[{i}:v]" if i == 0 else f"[v{i-1}]"
1486
+ input2 = f"[{i+1}:v]"
1487
+ output = f"[v{i}]"
1488
+ offset = max(0, running_duration + clip_duration - transition_duration)
1489
+ filter_complex.append(f"{input1}{input2}xfade=transition={transition_style.lower()}:duration={transition_duration}:offset={offset}{output}")
1490
+ running_duration += clip_duration - transition_duration
1491
+
1492
+ cmd.extend([
1493
+ "-filter_complex", ";".join(filter_complex),
1494
+ "-map", f"[v{len(extracted_clips)-2}]",
1495
+ "-c:v", "libx264", "-pix_fmt", "yuv420p",
1496
+ final_silent_path
1497
+ ])
1498
+ run_ffmpeg_command(cmd, "Applying transitions...")
1499
+
1500
+ # --- 5. Mux Audio ---
1501
+ progress(0.95, desc="Adding background music...")
1502
+ final_output_path = os.path.join(TEMP_DIR, f"trailer_final_{timestamp}.mp4")
1503
+ if music_path:
1504
+ run_ffmpeg_command(["ffmpeg", "-y", "-i", final_silent_path, "-i", music_path, "-c:v", "copy", "-c:a", "aac", "-map", "0:v:0", "-map", "1:a:0", "-shortest", final_output_path], "Muxing audio")
1505
+ else:
1506
+ shutil.move(final_silent_path, final_output_path)
1507
+ return final_output_path
1508
+ finally:
1509
+ if os.path.exists(job_temp_dir): shutil.rmtree(job_temp_dir)
1510
+
1511
+
1512
+ def auto_trailer_wrapper(video_path, trailer_duration, clip_duration, analysis_method, transition_style, music_path, out_w, out_h, progress=gr.Progress(track_tqdm=True)):
1513
+ return _create_auto_trailer_impl(video_path, trailer_duration, clip_duration, analysis_method, transition_style, music_path, out_w, out_h, progress)
1514
+
1515
+
1516
  def generate_waveform_video(video_path, style, size, position, color):
1517
  if not video_path: raise gr.Error("Please upload a video first.")
1518
 
 
1720
  looped_image_paths = [image_paths[i % len(image_paths)] for i in range(num_clips)]
1721
 
1722
  kb_clips = []
1723
+ total_steps = num_clips + 1
 
1724
  current_step = 0
1725
 
1726
  for i in range(num_clips):
 
1769
 
1770
  if not kb_clips: raise gr.Error("No clips were generated. The audio may be too short or the rhythm settings too fast.")
1771
 
1772
+ progress(current_step / total_steps, desc=f"Applying transitions...")
1773
+ final_silent_path = os.path.join(job_temp_dir, "final_silent.mp4")
1774
  if len(kb_clips) == 1:
1775
+ shutil.copy(kb_clips[0]['path'], final_silent_path)
1776
  else:
1777
  all_transitions = ["fade", "wipeleft", "wiperight", "wipeup", "wipedown", "slideleft", "slideright", "slideup", "slidedown", "dissolve"]
1778
+ cmd = ["ffmpeg", "-y"]
1779
+ filter_complex = []
1780
+ running_duration = 0
1781
 
1782
+ for i, clip in enumerate(kb_clips):
1783
+ cmd.extend(["-i", clip['path']])
 
1784
 
1785
  for i in range(len(kb_clips) - 1):
1786
+ input1 = f"[{i}:v]" if i == 0 else f"[v{i-1}]"
1787
+ input2 = f"[{i+1}:v]"
1788
+ output = f"[v{i}]"
 
1789
  transition = random.choice(all_transitions) if transition_style == "Random" else transition_style.lower()
1790
+ offset = running_duration + kb_clips[i]['duration'] - transition_duration
1791
+ filter_complex.append(f"{input1}{input2}xfade=transition={transition}:duration={transition_duration}:offset={offset}{output}")
1792
+ running_duration += kb_clips[i]['duration'] - transition_duration
 
 
1793
 
1794
+ cmd.extend(["-filter_complex", ";".join(filter_complex), "-map", f"[v{len(kb_clips)-2}]", "-c:v", "libx264", "-pix_fmt", "yuv420p", final_silent_path])
1795
+ run_ffmpeg_command(cmd)
 
 
 
1796
 
1797
  progress(0.98, desc="Muxing final audio...")
1798
  final_output_path = os.path.join(TEMP_DIR, f"slideshow_final_{timestamp}.mp4")
 
1969
  const a=document.createElement("script");a.setAttribute("src","https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js"),document.head.appendChild(a),a.onload=()=>{var e=confetti.create(null,{resize:!0,useWorker:!0});e({particleCount:150,spread:90,origin:{y:.6}})}
1970
  }
1971
 
1972
+ // --- Audio Feedback with Delay and Echo ---
1973
+ const skriptz_audio = {
1974
+ context: null,
1975
+ buffer: null,
1976
+ isInitialized: false,
1977
+ };
1978
+
1979
+ async function init_audio() {
1980
+ if (skriptz_audio.isInitialized) return;
1981
+ try {
1982
+ skriptz_audio.context = new (window.AudioContext || window.webkitAudioContext)();
1983
+ // --- FIX FOR BROWSER AUTOPLAY POLICY ---
1984
+ if (skriptz_audio.context.state === 'suspended') {
1985
+ await skriptz_audio.context.resume();
1986
+ }
1987
+ const response = await fetch('/file=finish_sound.mp3');
1988
+ if (!response.ok) {
1989
+ console.warn(`Finish sound not loaded: ${response.status} ${response.statusText}. Ensure 'finish_sound.mp3' is in the root directory and 'allowed_paths' is set in demo.launch().`);
1990
+ skriptz_audio.isInitialized = true;
1991
+ return;
1992
+ }
1993
+ const arrayBuffer = await response.arrayBuffer();
1994
+ skriptz_audio.buffer = await skriptz_audio.context.decodeAudioData(arrayBuffer);
1995
+ } catch (e) {
1996
+ console.error('Error initializing or decoding audio:', e);
1997
+ }
1998
+ skriptz_audio.isInitialized = true;
1999
+ }
2000
+
2001
+ async function play_finish_sound() {
2002
+ if (!skriptz_audio.isInitialized || !skriptz_audio.context) {
2003
+ await init_audio();
2004
+ }
2005
+ if (!skriptz_audio.buffer || !skriptz_audio.context) return;
2006
+
2007
+ // --- FIX FOR BROWSER AUTOPLAY POLICY ---
2008
+ if (skriptz_audio.context.state === 'suspended') {
2009
+ await skriptz_audio.context.resume();
2010
+ }
2011
+
2012
+ const context = skriptz_audio.context;
2013
+ const source = context.createBufferSource();
2014
+ source.buffer = skriptz_audio.buffer;
2015
+
2016
+ const delay = context.createDelay();
2017
+ delay.delayTime.value = 0.25;
2018
+
2019
+ const feedback = context.createGain();
2020
+ feedback.gain.value = 0.4;
2021
+
2022
+ source.connect(context.destination);
2023
+ source.connect(delay);
2024
+ delay.connect(feedback);
2025
+ feedback.connect(delay);
2026
+ delay.connect(context.destination);
2027
+
2028
+ source.start(0);
2029
+ }
2030
+
2031
  // --- Dynamic Page Title ---
2032
  function update_title(tab_name) {
2033
  if (tab_name) {
 
2034
  const clean_name = tab_name.replace(/[\\u{1F600}-\\u{1F64F}\\u{1F300}-\\u{1F5FF}\\u{1F680}-\\u{1F6FF}\\u{1F700}-\\u{1F77F}\\u{1F780}-\\u{1F7FF}\\u{1F800}-\\u{1F8FF}\\u{1F900}-\\u{1F9FF}\\u{1FA00}-\\u{1FA6F}\\u{1FA70}-\\u{1FAFF}\\u{2600}-\\u{26FF}\\u{2700}-\\u{27BF}]/gu, '').trim();
2035
  document.title = `Skriptz - ${clean_name}`;
2036
  } else {
 
2044
  if(text_area) {
2045
  text_area.select();
2046
  document.execCommand('copy');
 
2047
  const original_button = this.event.target;
2048
  const original_text = original_button.innerText;
2049
  original_button.innerText = 'Copied!';
 
2062
  show_overlay,
2063
  hide_overlay,
2064
  fire_confetti,
2065
+ play_finish_sound,
2066
  update_title,
2067
  copy_to_clipboard,
2068
  storyboard_get_time
 
2092
  storyboard_get_time_js = "() => { return window.skriptz_bling.storyboard_get_time(); }"
2093
  show_overlay_js = "() => { window.skriptz_bling.show_overlay('Working hard... this may take a moment!'); }"
2094
  hide_overlay_js = "() => { window.skriptz_bling.hide_overlay(); }"
2095
+ fire_confetti_and_sound_js = "() => { window.skriptz_bling.fire_confetti(); window.skriptz_bling.play_finish_sound(); }"
2096
  copy_transcription_js = "() => { window.skriptz_bling.copy_to_clipboard('transcription_textbox'); }"
2097
 
2098
  with gr.Tabs(elem_id="main_tabs") as main_tabs:
 
2193
  clear_assets_btn.click(lambda: ([], gr.update(value=None), None), outputs=[assets_state, asset_gallery, asset_preview_gallery])
2194
  clear_timeline_btn.click(lambda: [], outputs=[timeline_state])
2195
 
2196
+ generate_animatic_btn.click(fn=create_animatic, inputs=[timeline_state, animatic_audio, animatic_out_w, animatic_out_h], outputs=animatic_output_video).then(fn=None, js=fire_confetti_and_sound_js)
2197
 
2198
  analyze_bpm_btn.click(detect_bpm, animatic_audio, bpm_display)
2199
 
2200
+ generate_rhythmic_btn.click(fn=create_rhythmic_animatic, inputs=[timeline_state, animatic_audio, measure_dropdown, animatic_out_w, animatic_out_h], outputs=animatic_output_video).then(fn=None, js=fire_confetti_and_sound_js)
2201
 
2202
 
2203
  with gr.TabItem("🎨 Creative Suite", elem_id="creative_tab"):
 
2227
  fn=slideshow_wrapper,
2228
  inputs=[slideshow_input_images, slideshow_audio, slideshow_kb_effect, slideshow_transition, slideshow_rhythm, slideshow_out_w, slideshow_out_h],
2229
  outputs=slideshow_output_video
2230
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2231
 
2232
+ with gr.TabItem("🎞️ Auto-Trailer"):
2233
+ gr.Markdown("## Automatic Trailer Creator")
2234
+ gr.Info("Upload a long video, and this tool will find the most action-packed moments to create a short, dynamic trailer.")
2235
+ with gr.Row():
2236
+ with gr.Column(scale=2):
2237
+ trailer_input_video = gr.Video(label="Upload Source Video")
2238
+ trailer_music = gr.Audio(label="Add Background Music (Optional)", type="filepath")
2239
+
2240
+ with gr.Accordion("βš™οΈ Trailer Settings", open=True):
2241
+ trailer_total_duration = gr.Slider(10, 120, 30, step=5, label="Total Trailer Length (s)")
2242
+ trailer_clip_duration = gr.Slider(1.0, 5.0, 2.0, step=0.5, label="Duration of Each Clip (s)")
2243
+ trailer_analysis_method = gr.Dropdown(["Motion"], value="Motion", label="Scene Analysis Method", info="Currently only motion detection is supported.")
2244
+ trailer_transition = gr.Dropdown(["None", "Fade", "WipeLeft", "WipeRight", "Dissolve", "SlideLeft", "SlideRight"], value="Fade", label="Transition Style")
2245
+
2246
+ with gr.Row():
2247
+ trailer_out_w = gr.Number(label="Output Width", value=1920)
2248
+ trailer_out_h = gr.Number(label="Output Height", value=1080)
2249
+
2250
+ trailer_generate_btn = gr.Button("πŸš€ Generate Trailer", variant="primary")
2251
+ with gr.Column(scale=3):
2252
+ trailer_output_video = gr.Video(label="Generated Trailer Video", show_download_button=True)
2253
+
2254
+ trailer_generate_btn.click(
2255
+ fn=auto_trailer_wrapper,
2256
+ inputs=[
2257
+ trailer_input_video, trailer_total_duration, trailer_clip_duration,
2258
+ trailer_analysis_method, trailer_transition, trailer_music,
2259
+ trailer_out_w, trailer_out_h
2260
+ ],
2261
+ outputs=trailer_output_video
2262
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2263
+
2264
  with gr.TabItem("🎡 Audio Waveform"):
2265
  gr.Markdown("## Generate & Overlay Audio Waveforms")
2266
  gr.Info("Upload a video with an audio track to generate a dynamic waveform visualization.")
 
2280
  fn=generate_waveform_video,
2281
  inputs=[waveform_input_video, waveform_style, waveform_size, waveform_position, waveform_color],
2282
  outputs=waveform_output_video
2283
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2284
 
2285
  with gr.TabItem("πŸ–ΌοΈ Picture-in-Picture"):
2286
  gr.Markdown("## Create Picture-in-Picture (PiP) & Image Overlays")
 
2302
  fn=create_pip_video,
2303
  inputs=[pip_main_video, pip_overlay_media, pip_position, pip_scale],
2304
  outputs=pip_output_video
2305
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2306
 
2307
  with gr.TabItem("πŸ˜‚ Meme Creator"):
2308
  gr.Markdown("## Classic Meme & Text Overlay Creator")
 
2328
  fn=create_meme,
2329
  inputs=[meme_input_image, meme_text, meme_position, meme_font, meme_font_size, meme_text_color, meme_outline_color],
2330
  outputs=meme_output_image
2331
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2332
 
2333
  with gr.TabItem("πŸ–ΌοΈ Image Utilities", elem_id="image_tab"):
2334
  gr.Markdown("## Tools for processing and generating single images.")
 
2346
  manip_apply_btn = gr.Button("🎨 Apply Manipulation", variant="primary")
2347
  with gr.Column():
2348
  manip_output_image = gr.Image(label="Output Image", interactive=True)
2349
+ manip_apply_btn.click(fn=manipulate_image, inputs=[manip_input_image, manip_operation_radio], outputs=manip_output_image).then(fn=None, js=fire_confetti_and_sound_js)
2350
 
2351
  with gr.TabItem("πŸ“Ή Image to Video"):
2352
  gr.Markdown("### Create a short, looping video from a single static image.")
 
2358
  compile_i2v_btn = gr.Button("🎬 Create Looping Video", variant="primary")
2359
  with gr.Column():
2360
  output_video_i2v = gr.Video(label="Output Looping Video", interactive=True, show_download_button=True)
2361
+ compile_i2v_btn.click(image_to_looping_video, [input_image_i2v, duration_slider_i2v, input_audio_i2v], output_video_i2v).then(fn=None, js=fire_confetti_and_sound_js)
2362
 
2363
  with gr.TabItem("πŸ”Ž Zoom Video"):
2364
  gr.Markdown("### Create a 'Ken Burns' style zoom/pan video from an image.")
 
2389
  fn=create_zoom_videos,
2390
  inputs=[i2zv_input_images, i2zv_duration, i2zv_zoom_ratio, i2zv_zoom_dir, i2zv_combine, i2zv_audio],
2391
  outputs=[i2zv_output_gallery, i2zv_output_video, i2zv_output_zip]
2392
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2393
 
2394
  with gr.TabItem("βœ‚οΈ Batch BG Remover"):
2395
  gr.Markdown("### Remove the background from a batch of images.")
 
2400
  with gr.Column():
2401
  output_gallery_bg = gr.Gallery(label="Images with Transparent Background", columns=4, object_fit="contain", height="auto")
2402
  output_zip_bg = gr.File(label="Download All as .zip", interactive=False)
2403
+ remove_bg_btn.click(remove_background_batch, input_images_bg, [output_gallery_bg, output_zip_bg]).then(fn=None, js=fire_confetti_and_sound_js)
2404
 
2405
  with gr.TabItem("πŸ–‹οΈ Batch Watermarker"):
2406
  gr.Markdown("### Apply a text watermark to a batch of images.")
 
2414
  with gr.Column():
2415
  output_gallery_wm = gr.Gallery(label="Watermarked Images", columns=4, object_fit="contain", height="auto")
2416
  output_zip_wm = gr.File(label="Download All as .zip", interactive=False)
2417
+ watermark_btn.click(apply_watermark_batch, [input_images_wm, watermark_text, watermark_pos, watermark_opacity], [output_gallery_wm, output_zip_wm]).then(fn=None, js=fire_confetti_and_sound_js)
2418
 
2419
  with gr.TabItem("↔️ Batch Resizer"):
2420
  gr.Markdown("### Convert, resize, and compress a batch of images.")
 
2440
  batch_resize_convert_images,
2441
  [brc_input_images, brc_format, brc_quality, brc_enable_resize, brc_max_w, brc_max_h, brc_resize_mode],
2442
  [brc_output_gallery, brc_output_zip]
2443
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2444
 
2445
  with gr.TabItem("πŸ€– FLUX.1 API"):
2446
  gr.Markdown("### Generate an image using `FLUX.1` models via Gradio Client.")
 
2456
  flux_btn = gr.Button("β˜„οΈ Generate Image", variant="primary")
2457
  with gr.Column():
2458
  output_image_flux = gr.Image(label="Generated Image", interactive=True)
2459
+ flux_btn.click(call_flux_api, [prompt_input_flux, flux_model_dropdown, flux_width_slider, flux_height_slider, hf_token_input], output_image_flux).then(fn=None, js=fire_confetti_and_sound_js)
2460
 
2461
  with gr.TabItem("πŸŽ₯ Video Utilities", elem_id="video_tab"):
2462
  gr.Markdown("## A collection of useful video tools.")
 
2471
  extract_fl_btn = gr.Button("🎬 Extract Frames", variant="primary")
2472
  with gr.Column():
2473
  output_gallery_fl = gr.Gallery(label="Output Frames (First, Last)", columns=2, object_fit="contain", height="auto")
2474
+ extract_fl_btn.click(fn=extract_first_last_frame, inputs=input_video_fl, outputs=output_gallery_fl).then(fn=None, js=fire_confetti_and_sound_js)
2475
 
2476
  with gr.TabItem("Video to Frames"):
2477
  gr.Markdown("### Extract all individual frames from a video file.")
 
2495
  input_video_v2f.upload(lambda v: f"{get_video_fps(v):.2f} FPS", input_video_v2f, v2f_fps_display)
2496
  v2f_resize.change(lambda x: [gr.update(interactive=x), gr.update(interactive=x)], v2f_resize, [v2f_width, v2f_height])
2497
  v2f_format.change(lambda x: gr.update(interactive=(x=="JPG")), v2f_format, v2f_jpg_quality)
2498
+ extract_v2f_btn.click(video_to_frames_extractor, [input_video_v2f, v2f_skip_rate, v2f_rotation, v2f_resize, v2f_width, v2f_height, v2f_format, v2f_jpg_quality], [output_gallery_v2f, output_zip_v2f]).then(fn=None, js=fire_confetti_and_sound_js)
2499
 
2500
  with gr.TabItem("Frames to Video"):
2501
  gr.Markdown("### Compile a sequence of image frames into a video file.")
 
2513
  with gr.Column():
2514
  output_video_f2v = gr.Video(label="Compiled Video", interactive=True, show_download_button=True)
2515
  f2v_resize.change(lambda x: [gr.update(interactive=x), gr.update(interactive=x)], f2v_resize, [f2v_width, f2v_height])
2516
+ compile_f2v_btn.click(create_video_from_frames, [input_frames_f2v, fps_slider_f2v, f2v_rotation, f2v_resize, f2v_width, f2v_height], output_video_f2v).then(fn=None, js=fire_confetti_and_sound_js)
2517
 
2518
  with gr.TabItem("βœ‚οΈ Editing"):
2519
  with gr.Tabs():
 
2531
  with gr.Column():
2532
  vmanip_output_video = gr.Video(label="Output Video", interactive=True, show_download_button=True)
2533
 
2534
+ vmanip_apply_btn.click(fn=manipulate_video, inputs=[vmanip_input_video, vmanip_operation_radio], outputs=vmanip_output_video).then(fn=None, js=fire_confetti_and_sound_js)
2535
 
2536
  with gr.TabItem("Ping-Pong"):
2537
  gr.Markdown("### Create a forward-then-reverse video loop (Boomerang).")
 
2542
  pingpong_btn = gr.Button("πŸ“ Create Ping-Pong Video", variant="primary")
2543
  with gr.Column():
2544
  output_video_pingpong = gr.Video(label="Ping-Pong Video", interactive=True, show_download_button=True)
2545
+ pingpong_btn.click(fn=ping_pong_video, inputs=[input_video_pingpong, audio_option_pingpong], outputs=output_video_pingpong).then(fn=None, js=fire_confetti_and_sound_js)
2546
 
2547
  with gr.TabItem("Reverse"):
2548
  gr.Markdown("### Reverse a video clip.")
 
2553
  reverse_btn = gr.Button("πŸ”„ Reverse Video", variant="primary")
2554
  with gr.Column():
2555
  output_video_reverse = gr.Video(label="Reversed Video", interactive=True, show_download_button=True)
2556
+ reverse_btn.click(fn=reverse_video, inputs=[input_video_reverse, audio_option_reverse], outputs=output_video_reverse).then(fn=None, js=fire_confetti_and_sound_js)
2557
 
2558
  with gr.TabItem("Merger"):
2559
  gr.Markdown("### Simple Video Merger")
 
2568
  fn=merge_videos,
2569
  inputs=merger_input_videos,
2570
  outputs=merger_output_video
2571
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2572
 
2573
  with gr.TabItem("Trimmer"):
2574
  gr.Markdown("### Visually trim a video. Use the player to find a frame, then set it as the start or end point.")
 
2596
  set_end_btn.click(fn=None, js=get_current_time_js, outputs=[trim_end_time])
2597
  trim_start_time.change(fn=get_frame_from_time_wrapper, inputs=[input_video_trim, trim_start_time], outputs=[start_frame_img, trim_start_time_display])
2598
  trim_end_time.change(fn=get_frame_from_time_wrapper, inputs=[input_video_trim, trim_end_time], outputs=[end_frame_img, trim_end_time_display])
2599
+ trim_btn.click(fn=trim_video, inputs=[input_video_trim, trim_start_time, trim_end_time], outputs=output_video_trim).then(fn=None, js=fire_confetti_and_sound_js)
2600
  input_video_trim.clear(fn=lambda: (None, "0.00", None, "0.00", 0, 0), outputs=[start_frame_img, trim_start_time_display, end_frame_img, trim_end_time_display, trim_start_time, trim_end_time])
2601
 
2602
  with gr.TabItem("Crop & Resize"):
 
2622
  output_video_crop = gr.Video(label="Cropped Video", interactive=True, show_download_button=True)
2623
  crop_input_video.upload(fn=get_frame_at_time, inputs=crop_input_video, outputs=crop_preview_image)
2624
  crop_do_resize.change(lambda x: [gr.update(interactive=x), gr.update(interactive=x)], inputs=crop_do_resize, outputs=[crop_resize_w, crop_resize_h])
2625
+ crop_btn.click(fn=crop_video, inputs=[crop_input_video, crop_x, crop_y, crop_w, crop_h, crop_do_resize, crop_resize_w, crop_resize_h], outputs=output_video_crop).then(fn=None, js=fire_confetti_and_sound_js)
2626
 
2627
  with gr.TabItem("Change Speed"):
2628
  gr.Markdown("### Create slow-motion or fast-forward videos.")
 
2633
  speed_btn = gr.Button("πŸƒ Change Speed", variant="primary")
2634
  with gr.Column():
2635
  output_video_speed = gr.Video(label="Modified Video", interactive=True, show_download_button=True)
2636
+ speed_btn.click(fn=change_video_speed, inputs=[input_video_speed, speed_multiplier], outputs=output_video_speed).then(fn=None, js=fire_confetti_and_sound_js)
2637
 
2638
  with gr.TabItem("🎨 Effects & Overlays"):
2639
  with gr.Tabs():
 
2648
  fade_video_btn = gr.Button("✨ Apply Fade", variant="primary")
2649
  with gr.Column():
2650
  fade_output_video = gr.Video(label="Faded Video", interactive=True, show_download_button=True)
2651
+ fade_video_btn.click(apply_video_fade, [fade_input_video, fade_in_slider, fade_out_slider], fade_output_video).then(fn=None, js=fire_confetti_and_sound_js)
2652
 
2653
  with gr.TabItem("BG Remover"):
2654
  gr.Markdown("## Video Background Remover")
 
2659
  vbg_btn = gr.Button("βœ‚οΈ Remove Video Background", variant="primary")
2660
  with gr.Column():
2661
  vbg_output_video = gr.Video(label="Output Video with Transparency (.webm)", interactive=True, show_download_button=True)
2662
+ vbg_btn.click(remove_video_background, vbg_input_video, vbg_output_video).then(fn=None, js=fire_confetti_and_sound_js)
2663
 
2664
  with gr.TabItem("Watermark"):
2665
  gr.Markdown("### Apply a text watermark to a video.")
 
2675
  wm_btn = gr.Button("πŸ–‹οΈ Apply Watermark", variant="primary")
2676
  with gr.Column():
2677
  wm_output_video = gr.Video(label="Watermarked Video", interactive=True, show_download_button=True)
2678
+ wm_btn.click(apply_video_watermark, [wm_input_video, wm_text, wm_pos, wm_opacity, wm_size, wm_color], wm_output_video).then(fn=None, js=fire_confetti_and_sound_js)
2679
 
2680
  with gr.TabItem("Create GIF"):
2681
  gr.Markdown("### Convert a video clip into a high-quality animated GIF.")
 
2688
  gif_btn = gr.Button("πŸ–ΌοΈ Create GIF", variant="primary")
2689
  with gr.Column():
2690
  output_gif = gr.Image(label="Output GIF", interactive=True)
2691
+ gif_btn.click(create_gif_from_video, [input_video_gif, gif_start_time, gif_end_time], output_gif).then(fn=None, js=fire_confetti_and_sound_js)
2692
 
2693
  with gr.TabItem("πŸ”Š Audio & Transcription"):
2694
  with gr.Tabs():
 
2701
  add_audio_btn = gr.Button("🎢 Add Audio", variant="primary")
2702
  with gr.Column():
2703
  output_video_audio = gr.Video(label="Final Video with Audio", interactive=True, show_download_button=True)
2704
+ add_audio_btn.click(add_audio_to_video, [input_video_audio, input_audio], output_video_audio).then(fn=None, js=fire_confetti_and_sound_js)
2705
 
2706
  with gr.TabItem("Extract Audio"):
2707
  gr.Markdown("### Strip the audio track from a video file.")
 
2712
  extract_audio_btn = gr.Button("🎡 Extract Audio", variant="primary")
2713
  with gr.Column():
2714
  extract_audio_output = gr.Audio(label="Extracted Audio", type="filepath")
2715
+ extract_audio_btn.click(extract_audio, [extract_audio_input_video, extract_audio_format], extract_audio_output).then(fn=None, js=fire_confetti_and_sound_js)
2716
 
2717
  with gr.TabItem("Audio Editor"):
2718
  gr.Markdown("### Trim and Apply Fades to an Audio File")
 
2729
  audio_trim_fade_btn = gr.Button("βœ‚οΈ Process Audio", variant="primary")
2730
  with gr.Column():
2731
  audio_trim_output = gr.Audio(label="Processed Audio", type="filepath")
2732
+ audio_trim_fade_btn.click(trim_and_fade_audio, [audio_trim_input, audio_start_time, audio_end_time, audio_fade_in, audio_fade_out], audio_trim_output).then(fn=None, js=fire_confetti_and_sound_js)
2733
 
2734
  with gr.TabItem("Transcription", visible=(whisper is not None)):
2735
  gr.Markdown("## Transcribe Speech and Burn Subtitles")
 
2759
  fn=transcribe_and_prep_burn,
2760
  inputs=[transcribe_input, transcribe_model],
2761
  outputs=[transcribe_text, transcribe_files, transcribed_video_path_state, burn_accordion]
2762
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2763
  burn_btn.click(
2764
  fn=burn_subtitles,
2765
  inputs=[transcribed_video_path_state, transcribe_files, burn_font_size, burn_font_color],
2766
  outputs=burn_output_video
2767
+ ).then(fn=None, js=fire_confetti_and_sound_js)
2768
 
2769
  with gr.TabItem("🧠 ControlNet", elem_id="controlnet_tab"):
2770
  gr.Markdown("## ControlNet Preprocessing")
 
2778
  process_btn_cn = gr.Button("✨ Process Video", variant="primary")
2779
  with gr.Column():
2780
  output_video_cn = gr.Video(label="Output ControlNet Video", interactive=True, show_download_button=True)
2781
+ process_btn_cn.click(fn=process_video_with_detector, inputs=[input_video_cn, detector_dropdown_cn], outputs=output_video_cn).then(fn=None, js=fire_confetti_and_sound_js)
2782
  with gr.TabItem("Process Batch Images"):
2783
  gr.Markdown("### Generate ControlNet Maps from one or more images.")
2784
  with gr.Row():
 
2789
  with gr.Column():
2790
  output_gallery_cn = gr.Gallery(label="Output ControlNet Images", columns=4, object_fit="contain", height="auto")
2791
  output_zip_cn = gr.File(label="Download All as .zip", interactive=False)
2792
+ process_btn_img.click(fn=process_batch_images_with_detector, inputs=[input_images_cn, detector_dropdown_img], outputs=[output_gallery_cn, output_zip_cn]).then(fn=None, js=fire_confetti_and_sound_js)
2793
 
2794
  with gr.TabItem("πŸ—œοΈ Converter", elem_id="converter_tab"):
2795
  gr.Markdown("## Universal Video Converter & Compressor")
 
2811
  conv_acodec = gr.Dropdown(["copy", "aac", "opus"], value="copy", label="Audio Codec", info="'copy' is fastest and preserves quality.")
2812
  conv_abitrate = gr.Dropdown([96, 128, 192, 256, 320], value=192, label="Audio Bitrate (kbps)", interactive=False)
2813
  conv_acodec.change(lambda x: gr.update(interactive=(x != "copy")), conv_acodec, conv_abitrate)
2814
+ conv_btn.click(fn=convert_compress_video, inputs=[conv_input_video, conv_format, conv_vcodec, conv_crf, conv_scale, conv_acodec, conv_abitrate], outputs=conv_output_video).then(fn=None, js=fire_confetti_and_sound_js)
2815
 
2816
  with gr.TabItem("πŸ”— Transfer", elem_id="transfer_tab"):
2817
  gr.Markdown("## Image & Link Transfer Utility")
 
2859
  try: shutil.rmtree(TEMP_DIR)
2860
  except OSError as e: print(f"Error removing temp directory {TEMP_DIR}: {e}")
2861
  os.makedirs(TEMP_DIR, exist_ok=True)
2862
+ # --- THIS IS THE MAIN FIX ---
2863
+ # Add allowed_paths to let Gradio serve the sound file from the current directory.
2864
+ demo.launch(inbrowser=True, allowed_paths=["."])