Download app.py from devorah-ai-2016/ShortsVideoGenerater: direct link, hf CLI and curl.
- Browser
- Download file 5.86 kB
-
https://huggingface.co/spaces/devorah-ai-2016/ShortsVideoGenerater/resolve/main/app.py
- Command line
-
hf download hf://spaces/devorah-ai-2016/ShortsVideoGenerater/app.py
-
curl -L -o app.py https://huggingface.co/spaces/devorah-ai-2016/ShortsVideoGenerater/resolve/main/app.py
5.86 kB
| import os | |
| import uuid | |
| from PIL import Image, ImageOps | |
| # Pillow μ΅μ λ²μ νΈν ν¨μΉ | |
| if not hasattr(Image, "ANTIALIAS"): | |
| Image.ANTIALIAS = Image.Resampling.LANCZOS | |
| import gradio as gr | |
| from moviepy.editor import ImageClip, concatenate_videoclips | |
| OUTPUT_DIR = "outputs" | |
| os.makedirs(OUTPUT_DIR, exist_ok=True) | |
| WIDTH, HEIGHT = 1080, 1920 | |
| def get_file_path(file_obj): | |
| if file_obj is None: | |
| return None | |
| if isinstance(file_obj, str): | |
| return file_obj | |
| if hasattr(file_obj, "name"): | |
| return file_obj.name | |
| return str(file_obj) | |
| def resize_crop_image(image_path): | |
| img = Image.open(image_path).convert("RGB") | |
| img = ImageOps.exif_transpose(img) | |
| target_ratio = WIDTH / HEIGHT | |
| img_ratio = img.width / img.height | |
| if img_ratio > target_ratio: | |
| new_h = HEIGHT | |
| new_w = int(new_h * img_ratio) | |
| else: | |
| new_w = WIDTH | |
| new_h = int(new_w / img_ratio) | |
| img = img.resize((new_w, new_h), Image.LANCZOS) | |
| left = (new_w - WIDTH) // 2 | |
| top = (new_h - HEIGHT) // 2 | |
| return img.crop((left, top, left + WIDTH, top + HEIGHT)) | |
| def save_scene_image(file_obj): | |
| image_path = get_file_path(file_obj) | |
| img = resize_crop_image(image_path) | |
| path = os.path.join(OUTPUT_DIR, f"scene_{uuid.uuid4()}.jpg") | |
| img.save(path, quality=95) | |
| return path | |
| def apply_motion(clip, motion_type, duration): | |
| if motion_type == "μλ μΆμ²": | |
| motion_type = "μ²μ²ν μ€μΈ" | |
| if motion_type == "μ²μ²ν μ€μΈ": | |
| clip = clip.resize(lambda t: 1 + 0.14 * (t / duration)) | |
| clip = clip.crop( | |
| x_center=WIDTH / 2, | |
| y_center=HEIGHT / 2, | |
| width=WIDTH, | |
| height=HEIGHT | |
| ) | |
| elif motion_type == "μ²μ²ν μ€μμ": | |
| clip = clip.resize(1.16) | |
| clip = clip.resize(lambda t: 1.16 - 0.14 * (t / duration)) | |
| clip = clip.crop( | |
| x_center=WIDTH / 2, | |
| y_center=HEIGHT / 2, | |
| width=WIDTH, | |
| height=HEIGHT | |
| ) | |
| elif motion_type == "μ’μ° μ΄λ": | |
| clip = clip.resize(1.20) | |
| clip = clip.crop( | |
| x1=lambda t: int(180 * (t / duration)), | |
| y_center=HEIGHT / 2, | |
| width=WIDTH, | |
| height=HEIGHT | |
| ) | |
| elif motion_type == "μμμ μλλ‘ μ΄λ": | |
| clip = clip.resize(1.20) | |
| clip = clip.crop( | |
| x_center=WIDTH / 2, | |
| y1=lambda t: int(200 * (t / duration)), | |
| width=WIDTH, | |
| height=HEIGHT | |
| ) | |
| elif motion_type == "μλμμ μλ‘ μ΄λ": | |
| clip = clip.resize(1.20) | |
| clip = clip.crop( | |
| x_center=WIDTH / 2, | |
| y1=lambda t: int(200 - 200 * (t / duration)), | |
| width=WIDTH, | |
| height=HEIGHT | |
| ) | |
| elif motion_type == "νλμμ λ΄λ €λ€λ³΄κΈ°": | |
| clip = clip.resize(lambda t: 1.22 - 0.12 * (t / duration)) | |
| clip = clip.crop( | |
| x_center=WIDTH / 2, | |
| y_center=lambda t: HEIGHT / 2 - 160 + 240 * (t / duration), | |
| width=WIDTH, | |
| height=HEIGHT | |
| ) | |
| return clip | |
| def make_video(img1, img2, img3, img4, motion_type, seconds_per_scene): | |
| images = [img1, img2, img3, img4] | |
| images = [img for img in images if img is not None] | |
| if not images: | |
| raise gr.Error("μ¬μ§μ 1μ₯ μ΄μ μ λ‘λν΄μ£ΌμΈμ.") | |
| clips = [] | |
| for img_file in images: | |
| scene_path = save_scene_image(img_file) | |
| clip = ImageClip(scene_path).set_duration(int(seconds_per_scene)) | |
| clip = apply_motion(clip, motion_type, int(seconds_per_scene)) | |
| clips.append(clip) | |
| final = concatenate_videoclips(clips, method="compose") | |
| output_path = os.path.join(OUTPUT_DIR, f"shorts_{uuid.uuid4()}.mp4") | |
| final.write_videofile( | |
| output_path, | |
| fps=24, | |
| codec="libx264", | |
| audio=False, | |
| preset="ultrafast", | |
| threads=2 | |
| ) | |
| return output_path, output_path | |
| with gr.Blocks(title="ShortsVideoGenerater") as demo: | |
| gr.Markdown("# ShortsVideoGenerater") | |
| gr.Markdown( | |
| """ | |
| μ¬μ§ 4μ₯μ μ λ‘λνλ©΄ **μ νλΈ μΌμΈ μ© 9:16 μμ**μΌλ‘ λ³νν©λλ€. | |
| - μ¬μ§μ ν μ₯μ© μμλλ‘ μ¬μλ©λλ€. | |
| - κΈ°λ³Έκ°: μ¬μ§ 1μ₯λΉ 5μ΄ | |
| - μ¬μ§ 4μ₯ μ λ‘λ μ μ΄ 20μ΄ μμ | |
| - μλ§ μμ΄ μ¬μ§ μμλ§ μμ±λ©λλ€. | |
| """ | |
| ) | |
| with gr.Row(): | |
| img1 = gr.File(label="μ₯λ©΄ 1 μ¬μ§", file_types=["image"]) | |
| img2 = gr.File(label="μ₯λ©΄ 2 μ¬μ§", file_types=["image"]) | |
| with gr.Row(): | |
| img3 = gr.File(label="μ₯λ©΄ 3 μ¬μ§", file_types=["image"]) | |
| img4 = gr.File(label="μ₯λ©΄ 4 μ¬μ§", file_types=["image"]) | |
| with gr.Row(): | |
| motion_type = gr.Radio( | |
| choices=[ | |
| "μλ μΆμ²", | |
| "μ²μ²ν μ€μΈ", | |
| "μ²μ²ν μ€μμ", | |
| "μ’μ° μ΄λ", | |
| "μμμ μλλ‘ μ΄λ", | |
| "μλμμ μλ‘ μ΄λ", | |
| "νλμμ λ΄λ €λ€λ³΄κΈ°", | |
| ], | |
| value="μλ μΆμ²", | |
| label="νλ©΄ μμ§μ" | |
| ) | |
| seconds_per_scene = gr.Radio( | |
| choices=[4, 5, 6, 7], | |
| value=5, | |
| label="μ¬μ§ 1μ₯λΉ μκ°" | |
| ) | |
| create_btn = gr.Button("μ νλΈ μΌμΈ μμ±νκΈ°", variant="primary") | |
| output_video = gr.Video(label="μμ±λ μΌμΈ μμ") | |
| download_file = gr.File(label="MP4 λ€μ΄λ‘λ") | |
| create_btn.click( | |
| fn=make_video, | |
| inputs=[ | |
| img1, img2, img3, img4, | |
| motion_type, | |
| seconds_per_scene | |
| ], | |
| outputs=[ | |
| output_video, | |
| download_file | |
| ] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |