devorah-ai-2016's picture
Create app.py
8cba2a9 verified
Raw History Blame Contribute Delete
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()