File size: 7,579 Bytes
1f1d2ca
 
fc64c8f
1f1d2ca
0018a37
ca315bd
1f1d2ca
 
0018a37
 
ca315bd
0018a37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca315bd
0018a37
ca315bd
0018a37
ca315bd
0018a37
 
 
ca315bd
 
0018a37
ca315bd
63b0612
0018a37
34717dc
0018a37
 
 
 
 
34717dc
0018a37
 
 
 
34717dc
 
 
0018a37
 
 
 
 
 
 
34717dc
63b0612
0018a37
34717dc
 
0018a37
 
 
 
 
 
 
 
 
ca315bd
0018a37
34717dc
0018a37
ca315bd
0018a37
34717dc
0018a37
 
 
 
 
34717dc
0018a37
 
 
 
 
 
 
 
 
 
 
1f1d2ca
0018a37
ca315bd
0018a37
34717dc
 
 
0018a37
 
 
ca315bd
0018a37
 
 
 
ca315bd
34717dc
 
36217b2
0018a37
 
36217b2
34717dc
36217b2
0018a37
 
34717dc
ca315bd
34717dc
ca315bd
34717dc
ca315bd
 
34717dc
0018a37
34717dc
 
0018a37
 
 
 
 
34717dc
0018a37
 
ca315bd
34717dc
 
ca315bd
0018a37
 
 
 
 
ca315bd
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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()