Update app.py
Browse files
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 |
-
|
| 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 |
-
|
|
|
|
| 1628 |
if len(kb_clips) == 1:
|
| 1629 |
-
|
| 1630 |
else:
|
| 1631 |
all_transitions = ["fade", "wipeleft", "wiperight", "wipeup", "wipedown", "slideleft", "slideright", "slideup", "slidedown", "dissolve"]
|
|
|
|
|
|
|
|
|
|
| 1632 |
|
| 1633 |
-
|
| 1634 |
-
|
| 1635 |
-
running_duration = kb_clips[0]['duration']
|
| 1636 |
|
| 1637 |
for i in range(len(kb_clips) - 1):
|
| 1638 |
-
|
| 1639 |
-
|
| 1640 |
-
|
| 1641 |
-
|
| 1642 |
transition = random.choice(all_transitions) if transition_style == "Random" else transition_style.lower()
|
| 1643 |
-
offset = running_duration - transition_duration
|
| 1644 |
-
|
| 1645 |
-
|
| 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 |
-
|
| 1650 |
-
|
| 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 |
-
|
| 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=
|
| 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=
|
| 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=
|
| 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 |
-
|
|
|
|
|
|
|
|
|
| 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=["."])
|