batch_paint / app.py
jyothyprakash90's picture
Update app.py
34717dc verified
Raw
History Blame Contribute Delete
7.58 kB
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()