Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| import gradio as gr | |
| import subprocess | |
| import os | |
| import time | |
| from pathlib import Path | |
| # --- CORE ARTISTIC FILTERS --- | |
| def apply_artistic_filter(img, style): | |
| try: | |
| if style == "Original (Direct)": return img | |
| if "Pencil" in style: | |
| gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) | |
| inv = 255 - gray | |
| blur = cv2.GaussianBlur(inv, (31, 31), 0) | |
| return cv2.cvtColor(cv2.divide(gray, 255 - blur, scale=256), cv2.COLOR_GRAY2RGB) | |
| elif "Charcoal" in style: | |
| gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) | |
| blur = cv2.medianBlur(gray, 7) | |
| return cv2.cvtColor(cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 5), cv2.COLOR_GRAY2RGB) | |
| elif "Watercolor" in style: | |
| if hasattr(cv2, 'stylization'): return cv2.stylization(img, sigma_s=60, sigma_r=0.6) | |
| return cv2.bilateralFilter(img, 15, 75, 75) | |
| elif style == "Neon Glow": | |
| edges = cv2.Canny(img, 100, 200) | |
| neon = cv2.cvtColor(edges, cv2.COLOR_GRAY2RGB) | |
| neon[:, :, 1], neon[:, :, 2] = edges, 255 | |
| return cv2.bitwise_and(neon, neon, mask=edges) | |
| return img | |
| except: return img | |
| # --- BRUSH TEXTURES --- | |
| def get_brush_mask(length, brush_type): | |
| if brush_type == "Sharp Row Reveal": return np.zeros(length, dtype=np.int32) | |
| elif brush_type == "Flat Brush": return np.random.randint(-2, 3, length) | |
| elif brush_type == "Rough Bristle": return np.random.randint(-15, 16, length) | |
| elif brush_type == "Fan Brush": | |
| x = np.linspace(0, 4 * np.pi, length) | |
| return (np.sin(x) * 15).astype(np.int32) | |
| return np.zeros(length, dtype=np.int32) | |
| # --- VIDEO GENERATION ENGINE --- | |
| def generate_video_engine(img_array, art_style, brush_texture, duration, direction, resolution, motion_effect, original_name): | |
| res_map = {"720p (HD)": (1280, 720), "1080p (Full HD)": (1920, 1080), "4K (Ultra HD)": (3840, 2160)} | |
| target_w, target_h = res_map[resolution] | |
| fps = 30 | |
| total_frames = int(duration * fps) | |
| paint_frames = int(total_frames * 0.8) | |
| resized_img = cv2.resize(img_array, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4) | |
| target_img = apply_artistic_filter(resized_img, art_style) | |
| # Filename Logic | |
| temp_path = f'temp_{int(time.time() * 1000)}.mp4' | |
| final_filename = f"{original_name}.mp4" | |
| video = cv2.VideoWriter(temp_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (target_w, target_h)) | |
| mask_len = target_h if "Horizontal" in direction else target_w | |
| brush_offsets = get_brush_mask(mask_len, brush_texture) | |
| for i in range(total_frames): | |
| t = i / total_frames | |
| p_t = min(1.0, i / paint_frames) | |
| canvas_img = target_img.copy() | |
| # MOTION EFFECTS | |
| if motion_effect == "CapCut 3D Zoom": | |
| scale = 1.0 + (pow(t, 2) * 0.3) | |
| offset = int(target_w * 0.03 * t) | |
| src_pts = np.float32([[0,0], [target_w,0], [0,target_h], [target_w,target_h]]) | |
| dst_pts = np.float32([[-offset, -offset], [target_w+offset, -offset], [0,target_h], [target_w,target_h]]) | |
| M_3d = cv2.getPerspectiveTransform(src_pts, dst_pts) | |
| canvas_img = cv2.warpPerspective(target_img, M_3d, (target_w, target_h), borderMode=cv2.BORDER_REPLICATE) | |
| M_scale = cv2.getRotationMatrix2D((target_w/2, target_h/2), 0, scale) | |
| canvas_img = cv2.warpAffine(canvas_img, M_scale, (target_w, target_h), borderMode=cv2.BORDER_REPLICATE) | |
| elif motion_effect == "Spiral Cinematic": | |
| M = cv2.getRotationMatrix2D((target_w/2, target_h/2), t * 15, 1.0 + (t * 0.15)) | |
| canvas_img = cv2.warpAffine(target_img, M, (target_w, target_h), borderMode=cv2.BORDER_REPLICATE) | |
| elif motion_effect == "3D Parallax Tilt": | |
| shift = int(target_w * 0.05 * t) | |
| pts1 = np.float32([[0,0], [target_w,0], [0,target_h], [target_w,target_h]]) | |
| pts2 = np.float32([[shift,shift], [target_w-shift,0], [0,target_h], [target_w-shift,target_h-shift]]) | |
| M_p = cv2.getPerspectiveTransform(pts1, pts2) | |
| canvas_img = cv2.warpPerspective(target_img, M_p, (target_w, target_h), borderMode=cv2.BORDER_REPLICATE) | |
| # REVEAL LOGIC | |
| frame = np.ones((target_h, target_w, 3), dtype=np.uint8) * 255 | |
| if "Horizontal" in direction: | |
| base_w = int(p_t * target_w) | |
| for y in range(target_h): | |
| reveal_w = max(0, min(target_w, base_w + brush_offsets[y])) | |
| if reveal_w > 0: frame[y, 0:reveal_w] = canvas_img[y, 0:reveal_w] | |
| else: | |
| base_h = int(p_t * target_h) | |
| for x in range(target_w): | |
| reveal_h = max(0, min(target_h, base_h + brush_offsets[x])) | |
| if reveal_h > 0: frame[0:reveal_h, x] = canvas_img[0:reveal_h, x] | |
| video.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)) | |
| video.release() | |
| # Final Encode | |
| if os.path.exists(final_filename): os.remove(final_filename) | |
| subprocess.call(['ffmpeg', '-y', '-i', temp_path, '-vcodec', 'libx264', '-crf', '18', '-pix_fmt', 'yuv420p', final_filename]) | |
| if os.path.exists(temp_path): os.remove(temp_path) | |
| return final_filename | |
| # --- BATCH HANDLER --- | |
| def batch_handler(files, art_style, brush, duration, direction, resolution, motion): | |
| if not files: return None, None | |
| processed_videos = [] | |
| for f in files: | |
| # Extract original filename | |
| original_name = Path(f.name).stem | |
| img = cv2.imread(f.name) | |
| img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # Pass name to engine | |
| video_path = generate_video_engine(img, art_style, brush, duration, direction, resolution, motion, original_name) | |
| processed_videos.append(video_path) | |
| return processed_videos, processed_videos[0] if processed_videos else None | |
| # --- UI --- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# 🎌 Ultimate Batch SpeedPaint Master") | |
| with gr.Row(): | |
| with gr.Column(): | |
| file_input = gr.File(file_count="multiple", label="Upload Images") | |
| motion_opt = gr.Dropdown(["None", "CapCut 3D Zoom", "Spiral Cinematic", "3D Parallax Tilt"], value="CapCut 3D Zoom", label="Motion Style") | |
| art_opt = gr.Dropdown(["Original (Direct)", "Soft Pencil", "Pencil Sketch", "Charcoal Dust", "Watercolor Wash", "Neon Glow"], value="Original (Direct)", label="Filter") | |
| brush_opt = gr.Dropdown(["Sharp Row Reveal", "Flat Brush", "Rough Bristle", "Fan Brush"], value="Rough Bristle", label="Brush") | |
| with gr.Row(): | |
| dur_slider = gr.Slider(2, 20, value=6, step=1, label="Seconds") | |
| res_opt = gr.Dropdown(["720p (HD)", "1080p (Full HD)", "4K (Ultra HD)"], value="1080p (Full HD)", label="Res") | |
| dir_opt = gr.Radio(["Vertical (Top-Bottom)", "Horizontal (Left-Right)"], value="Vertical (Top-Bottom)", label="Direction") | |
| run_btn = gr.Button("🚀 Start Batch Render", variant="primary") | |
| with gr.Column(): | |
| video_preview = gr.Video(label="Preview") | |
| file_download = gr.File(label="Download Videos (Named as Original Images)") | |
| run_btn.click( | |
| fn=batch_handler, | |
| inputs=[file_input, art_opt, brush_opt, dur_slider, dir_opt, res_opt, motion_opt], | |
| outputs=[file_download, video_preview] | |
| ) | |
| demo.launch() | |