Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import yt_dlp | |
| import os | |
| import tempfile | |
| import math | |
| import logging | |
| # Imports for moviepy version 1.0.3 | |
| from moviepy.editor import VideoFileClip | |
| import moviepy.video.fx.all as vfx | |
| from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip | |
| # Configure logging to see more details from moviepy in the console | |
| logging.basicConfig(level=logging.INFO) | |
| LOGGER = logging.getLogger(__name__) | |
| def download_video(url, uploaded_file, state): | |
| video_path = None | |
| if uploaded_file is not None: | |
| video_path = uploaded_file | |
| elif url and url.strip(): | |
| try: | |
| with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_video_file: | |
| video_path = temp_video_file.name | |
| ydl_opts = { | |
| 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', | |
| 'outtmpl': video_path, | |
| 'overwrites': True, | |
| } | |
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
| ydl.download([url]) | |
| except Exception as e: | |
| raise gr.Error(f"Failed to download video: {str(e)}") | |
| else: | |
| return None, None, state, "0", "0", "0:00:00", gr.update(maximum=1, value=1) | |
| if not video_path or not os.path.exists(video_path): | |
| raise gr.Error("Video file could not be created or found.") | |
| try: | |
| with VideoFileClip(video_path) as clip: | |
| fps = clip.fps | |
| duration = clip.duration | |
| frame_count = int(duration * fps) | |
| hours, rem = divmod(duration, 3600) | |
| minutes, seconds = divmod(rem, 60) | |
| vid_len_str = f"{int(hours):01}:{int(minutes):02}:{math.floor(seconds):02}" | |
| state['video_path'] = video_path | |
| state['active_path'] = video_path | |
| state['fps'] = fps | |
| gif_fps_update = gr.update(maximum=math.ceil(fps), value=min(15, math.ceil(fps))) | |
| return video_path, video_path, state, str(frame_count), str(fps), vid_len_str, gif_fps_update | |
| except Exception as e: | |
| if video_path and os.path.exists(video_path): | |
| os.remove(video_path) | |
| raise gr.Error(f"Failed to process video: {str(e)}") | |
| def parse_time(time_str): | |
| parts = time_str.split(':') | |
| try: | |
| if len(parts) == 3: | |
| h, m, s = map(float, parts) | |
| return h * 3600 + m * 60 + s | |
| elif len(parts) == 2: | |
| m, s = map(float, parts) | |
| return m * 60 + s | |
| elif len(parts) == 1: | |
| return float(parts[0]) | |
| else: | |
| raise ValueError("Invalid time format") | |
| except ValueError: | |
| raise gr.Error("Invalid time format. Please use HH:MM:SS, MM:SS, or seconds.") | |
| def trim_video(state, start_time, end_time): | |
| video_path = state.get('video_path') | |
| if not video_path or not os.path.exists(video_path): | |
| raise gr.Error("No video loaded to trim.") | |
| try: | |
| start_s = parse_time(start_time) | |
| end_s = parse_time(end_time) | |
| if start_s >= end_s: | |
| raise gr.Error("Start time must be before end time.") | |
| with VideoFileClip(video_path) as clip: | |
| if end_s > clip.duration: | |
| end_s = clip.duration | |
| gr.Warning(f"End time exceeded video duration. It has been adjusted to {clip.duration:.2f}s.") | |
| with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_clip_file: | |
| clipped_path = temp_clip_file.name | |
| ffmpeg_extract_subclip(video_path, start_s, end_s, targetname=clipped_path) | |
| with VideoFileClip(clipped_path) as clip: | |
| frame_count = int(clip.duration * clip.fps) | |
| state['active_path'] = clipped_path | |
| return clipped_path, state, str(frame_count) | |
| except Exception as e: | |
| raise gr.Error(f"Failed to trim video: {str(e)}") | |
| def update_speed(state, clip_speed): | |
| video_path = state.get('active_path') | |
| if not video_path or not os.path.exists(video_path): | |
| raise gr.Error("No video loaded to change speed. Please load or trim a video first.") | |
| try: | |
| with VideoFileClip(video_path) as clip: | |
| # Using the .fx() method, standard for moviepy v1.0.3 | |
| sped_up_clip = clip.fx(vfx.speedx, clip_speed) | |
| with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_speed_file: | |
| sped_up_path = temp_speed_file.name | |
| new_fps = clip.fps * clip_speed | |
| sped_up_clip.write_videofile(sped_up_path, fps=new_fps) | |
| state['active_path'] = sped_up_path | |
| state['fps'] = new_fps | |
| gif_fps_update = gr.update(maximum=math.ceil(new_fps), value=min(15, math.ceil(new_fps))) | |
| return sped_up_path, state, f"{new_fps:.2f}", gif_fps_update | |
| except Exception as e: | |
| raise gr.Error(f"Failed to update speed: {str(e)}") | |
| def make_gif(state, resize_percent, gif_fps): | |
| yield None, None, "Gathering video data..." | |
| video_path = state.get('active_path') | |
| if not video_path or not os.path.exists(video_path): | |
| raise gr.Error("No video available to create a GIF. Please load or trim a video first.") | |
| try: | |
| yield None, None, "Processing... Check console for ffmpeg progress bar." | |
| with VideoFileClip(video_path) as clip: | |
| # Using the .fx() method, standard for moviepy v1.0.3 | |
| if resize_percent != 100: | |
| clip = clip.fx(vfx.resize, resize_percent / 100.0) | |
| with tempfile.NamedTemporaryFile(suffix=".gif", delete=False) as temp_gif_file: | |
| gif_path = temp_gif_file.name | |
| clip.write_gif(gif_path, fps=gif_fps, program='ffmpeg', logger='bar') | |
| yield gif_path, gr.File(value=gif_path, visible=True), "GIF creation complete." | |
| except Exception as e: | |
| detailed_error = f"Failed to create GIF: {str(e)}. This often means 'ffmpeg' is not installed or not found in your system's PATH. Please ensure ffmpeg is correctly installed and check the console output for more details." | |
| LOGGER.error(detailed_error, exc_info=True) | |
| raise gr.Error(detailed_error) | |
| css = ''' | |
| .padded.svelte-90oupt { background: cornflowerblue; } | |
| .dark .gr-box { background-color: #344d74; } | |
| .p-2 { background-color: #344d74; } | |
| .gap-4 { background-color: #6681ab; } | |
| .dark .gr-padded { background-color: #21314a; } | |
| ''' | |
| with gr.Blocks() as app: | |
| state = gr.State(value={'video_path': None, 'active_path': None, 'fps': None}) | |
| gr.Markdown("# Video to GIF Converter") | |
| with gr.Row(): | |
| gr.Column(scale=1) | |
| with gr.Column(scale=2): | |
| with gr.Group(): | |
| gr.Markdown("### 1. Load Video") | |
| inp_url = gr.Textbox(label="Enter Video URL") | |
| inp_upload = gr.File(label="Or Upload Video File", type="filepath") | |
| load_btn = gr.Button("Load Video", variant="primary") | |
| with gr.Accordion("Loaded Video Preview & Details", open=False) as preview_accordion: | |
| outp_vid = gr.Video(label="Current Video Preview") | |
| with gr.Row(): | |
| frame_count = gr.Textbox(label="Frame Count", interactive=False) | |
| fps = gr.Textbox(label="Video FPS", interactive=False) | |
| vid_len = gr.Textbox(label="Video Length", interactive=False) | |
| with gr.Group(): | |
| gr.Markdown("### 2. Modify Video (Optional)") | |
| gr.Markdown("#### Trim Video") | |
| with gr.Row(): | |
| start_f = gr.Textbox(label="Start Time (HH:MM:SS)", value="0:00:00") | |
| end_f = gr.Textbox(label="End Time (HH:MM:SS)", value="0:00:05") | |
| trim_btn = gr.Button("Trim Video") | |
| trim_count = gr.Textbox(label="Trimmed Frame Count", interactive=False) | |
| gr.Markdown("#### Adjust Speed") | |
| clip_speed = gr.Slider(label="Playback Speed Multiplier", minimum=0.1, maximum=4, value=1, step=0.1) | |
| speed_btn = gr.Button("Update Speed") | |
| with gr.Group(): | |
| gr.Markdown("### 3. Generate GIF") | |
| resize_slider = gr.Slider(label="Resize Percentage", minimum=10, maximum=100, value=100, step=1) | |
| gif_fps_slider = gr.Slider(label="GIF Frame Rate (FPS)", minimum=1, maximum=60, value=15, step=1, | |
| info="Lower FPS results in a smaller file size.") | |
| gif_btn = gr.Button("Make GIF", variant="primary") | |
| gif_stat = gr.Textbox(label="Status", interactive=False) | |
| with gr.Row(): | |
| gif_show = gr.Image(label="Output GIF", interactive=False) | |
| gif_file = gr.File(label="Download GIF", visible=False) | |
| gr.Column(scale=1) | |
| load_btn.click( | |
| fn=download_video, | |
| inputs=[inp_url, inp_upload, state], | |
| outputs=[outp_vid, inp_upload, state, frame_count, fps, vid_len, gif_fps_slider] | |
| ).then(lambda: gr.update(open=True), None, preview_accordion) | |
| trim_btn.click( | |
| fn=trim_video, | |
| inputs=[state, start_f, end_f], | |
| outputs=[outp_vid, state, trim_count] | |
| ).then(lambda: gr.update(open=True), None, preview_accordion) | |
| speed_btn.click( | |
| fn=update_speed, | |
| inputs=[state, clip_speed], | |
| outputs=[outp_vid, state, fps, gif_fps_slider] | |
| ) | |
| gif_btn.click( | |
| fn=make_gif, | |
| inputs=[state, resize_slider, gif_fps_slider], | |
| outputs=[gif_show, gif_file, gif_stat] | |
| ) | |
| app.queue().launch(debug=True) |