3D-VIDEO / app.py
ginipick's picture
Update app.py
3aff1e6 verified
raw
history blame
12.6 kB
import os
import time
import glob
import json
import numpy as np
import trimesh
from PIL import Image, ImageDraw
import math
import trimesh.transformations as tf
os.environ['PYOPENGL_PLATFORM'] = 'egl'
import gradio as gr
import spaces
# κ²°κ³Ό μ €μž₯ 경둜
LOG_PATH = './results/demo'
os.makedirs(LOG_PATH, exist_ok=True)
def create_textual_animation_gif(output_path, model_name, animation_type, duration=3.0, fps=30):
"""ν…μŠ€νŠΈ 기반의 κ°„λ‹¨ν•œ μ• λ‹ˆλ©”μ΄μ…˜ GIF 생성"""
try:
frames = []
num_frames = int(duration * fps)
if num_frames > 60:
num_frames = 60
for i in range(num_frames):
t = i / (num_frames - 1)
angle = t * 360
img = Image.new('RGB', (640, 480), color=(240, 240, 240))
draw = ImageDraw.Draw(img)
draw.text((50, 50), f"Model: {os.path.basename(model_name)}", fill=(0, 0, 0))
draw.text((50, 100), f"Animation Type: {animation_type}", fill=(0, 0, 0))
draw.text((50, 150), f"Frame: {i+1}/{num_frames}", fill=(0, 0, 0))
# μ• λ‹ˆλ©”μ΄μ…˜ μœ ν˜•λ³„ μ‹œκ°ν™”
center_x, center_y = 320, 240
if animation_type == 'rotate':
radius = 100
x = center_x + radius * math.cos(math.radians(angle))
y = center_y + radius * math.sin(math.radians(angle))
draw.rectangle((x-40, y-40, x+40, y+40), outline=(0, 0, 0), fill=(255, 0, 0))
elif animation_type == 'float':
offset_y = 50 * math.sin(2 * math.pi * t)
draw.ellipse((center_x-50, center_y-50+offset_y, center_x+50, center_y+50+offset_y),
outline=(0, 0, 0), fill=(0, 0, 255))
elif animation_type in ['explode', 'assemble']:
scale = t if animation_type == 'explode' else 1 - t
for j in range(8):
angle_j = j * 45
dist = 120 * scale
x = center_x + dist * math.cos(math.radians(angle_j))
y = center_y + dist * math.sin(math.radians(angle_j))
if j % 3 == 0:
draw.rectangle((x-20, y-20, x+20, y+20), outline=(0, 0, 0), fill=(255, 0, 0))
elif j % 3 == 1:
draw.ellipse((x-20, y-20, x+20, y+20), outline=(0, 0, 0), fill=(0, 255, 0))
else:
draw.polygon([(x, y-20), (x+20, y+20), (x-20, y+20)], outline=(0, 0, 0), fill=(0, 0, 255))
elif animation_type == 'pulse':
scale = 0.5 + 0.5 * math.sin(2 * math.pi * t)
radius = 100 * scale
draw.ellipse((center_x-radius, center_y-radius, center_x+radius, center_y+radius),
outline=(0, 0, 0), fill=(0, 255, 0))
elif animation_type == 'swing':
angle_offset = 30 * math.sin(2 * math.pi * t)
points = [
(center_x + 100 * math.cos(math.radians(angle_offset)), center_y - 80),
(center_x + 100 * math.cos(math.radians(120 + angle_offset)), center_y + 40),
(center_x + 100 * math.cos(math.radians(240 + angle_offset)), center_y + 40)
]
draw.polygon(points, outline=(0, 0, 0), fill=(255, 165, 0))
frames.append(img)
frames[0].save(
output_path,
save_all=True,
append_images=frames[1:],
optimize=False,
duration=int(1000 / fps),
loop=0
)
print(f"Created textual animation GIF at {output_path}")
return output_path
except Exception as e:
print(f"Error creating textual animation: {str(e)}")
return None
def modify_glb_file(input_glb_path, output_glb_path, animation_type='rotate'):
"""μ—…λ‘œλ“œλœ GLB νŒŒμΌμ„ μˆ˜μ •ν•˜μ—¬ μ• λ‹ˆλ©”μ΄μ…˜ 효과λ₯Ό μ£ΌλŠ” ν•¨μˆ˜"""
try:
# κ°„λ‹¨ν•œ 방법: 원본 νŒŒμΌμ„ κ·ΈλŒ€λ‘œ λ‘œλ“œν•˜μ—¬ 단일 λ³€ν™˜ 적용 ν›„ μ €μž₯
# μ—¬λŸ¬ λ‹¨κ³„λ‘œ λ‚˜λˆ„μ–΄ μ²˜λ¦¬ν•˜μ—¬ 각 λ‹¨κ³„μ—μ„œ 였λ₯˜λ₯Ό 확인할 수 μžˆλ„λ‘ 함
print(f"Step 1: Loading GLB file '{input_glb_path}'")
scene = trimesh.load(input_glb_path)
print(f"Loaded GLB file, type: {type(scene)}")
print(f"Step 2: Preparing transformation for animation type: {animation_type}")
# λ‹¨μˆœν™”λœ λ³€ν™˜ μ€€λΉ„ - μ• λ‹ˆλ©”μ΄μ…˜ μœ ν˜•λ³„λ‘œ λ‹€λ₯Έ λ³€ν™˜ 적용
transform = np.eye(4) # κΈ°λ³Έ ν•­λ“± λ³€ν™˜ (아무 λ³€ν™” μ—†μŒ)
if animation_type == 'rotate':
# YμΆ• κΈ°μ€€ 45도 νšŒμ „
transform = tf.rotation_matrix(math.pi/4, [0, 1, 0])
elif animation_type == 'float':
# μœ„λ‘œ 이동
transform = tf.translation_matrix([0, 0.5, 0])
elif animation_type == 'pulse':
# 크기 ν™•λŒ€
transform = tf.scale_matrix(1.2)
elif animation_type == 'explode':
# XμΆ• λ°©ν–₯으둜 μ•½κ°„ 이동
transform = tf.translation_matrix([0.5, 0, 0])
elif animation_type == 'assemble':
# XμΆ• λ°©ν–₯으둜 μ•½κ°„ 이동 (λ°˜λŒ€ λ°©ν–₯)
transform = tf.translation_matrix([-0.5, 0, 0])
elif animation_type == 'swing':
# ZμΆ• κΈ°μ€€ μ•½κ°„ νšŒμ „
transform = tf.rotation_matrix(math.pi/8, [0, 0, 1])
print(f"Step 3: Applying transformation to scene")
if isinstance(scene, trimesh.Scene):
# Scene인 경우 직접 λ³€ν™˜ 적용
scene.apply_transform(transform)
print(f"Applied transform to scene")
else:
print(f"Scene is not trimesh.Scene, but {type(scene)}")
print(f"Step 4: Exporting modified scene to '{output_glb_path}'")
scene.export(output_glb_path)
print(f"Successfully exported modified GLB to '{output_glb_path}'")
return output_glb_path
except Exception as e:
print(f"Error modifying GLB file: {str(e)}")
# 였λ₯˜ λ°œμƒ μ‹œ 원본 파일 볡사
try:
import shutil
print(f"Copying original GLB file as fallback")
shutil.copy(input_glb_path, output_glb_path)
print(f"Successfully copied original GLB to '{output_glb_path}'")
return output_glb_path
except Exception as copy_error:
print(f"Error copying GLB: {copy_error}")
return input_glb_path # 원본 파일 경둜 λ°˜ν™˜
@spaces.GPU
def process_3d_model(input_3d, animation_type, animation_duration, fps):
"""Process a 3D model and apply animation"""
print(f"Processing: {input_3d} with animation type: {animation_type}")
try:
# 파일λͺ… μ€€λΉ„
base_filename = os.path.basename(input_3d).rsplit('.', 1)[0]
animated_glb_path = os.path.join(LOG_PATH, f'animated_{base_filename}.glb')
animated_gif_path = os.path.join(LOG_PATH, f'text_animated_{base_filename}.gif')
json_path = os.path.join(LOG_PATH, f'metadata_{base_filename}.json')
# 1. GLB 파일 μˆ˜μ •
print(f"Modifying GLB file")
modified_glb = modify_glb_file(input_3d, animated_glb_path, animation_type)
# 2. μ• λ‹ˆλ©”μ΄μ…˜ GIF 생성 (μ–΄λ–€ κ²½μš°μ—λ„ 항상 생성)
print(f"Creating animation GIF")
animated_gif = create_textual_animation_gif(
animated_gif_path,
os.path.basename(input_3d),
animation_type,
animation_duration,
fps
)
# 3. 메타데이터 생성
print(f"Creating metadata")
metadata = {
"animation_type": animation_type,
"duration": animation_duration,
"fps": fps,
"original_model": os.path.basename(input_3d),
"created_at": time.strftime("%Y-%m-%d %H:%M:%S")
}
with open(json_path, 'w') as f:
json.dump(metadata, f, indent=4)
print(f"Processing complete, returning results")
return modified_glb, animated_gif, json_path
except Exception as e:
print(f"Error in process_3d_model: {str(e)}")
# μ‹¬κ°ν•œ 였λ₯˜ λ°œμƒ μ‹œ 원본 파일과 κΈ°λ³Έ GIF λ°˜ν™˜
try:
base_filename = os.path.basename(input_3d).rsplit('.', 1)[0]
animated_gif_path = os.path.join(LOG_PATH, f'text_animated_{base_filename}.gif')
create_textual_animation_gif(
animated_gif_path,
os.path.basename(input_3d),
animation_type,
animation_duration,
fps
)
return input_3d, animated_gif_path, None
except:
return f"Error processing file: {str(e)}", None, None
# Gradio μΈν„°νŽ˜μ΄μŠ€ μ„€μ •
with gr.Blocks(title="GLB μ• λ‹ˆλ©”μ΄μ…˜ 생성기") as demo:
# 제λͺ© μ„Ήμ…˜
gr.Markdown("""
<h2><b>GLB μ• λ‹ˆλ©”μ΄μ…˜ 생성기 - 3D λͺ¨λΈ μ›€μ§μž„ 효과</b></h2>
이 데λͺ¨λ₯Ό 톡해 정적인 3D λͺ¨λΈ(GLB 파일)에 λ‹€μ–‘ν•œ μ• λ‹ˆλ©”μ΄μ…˜ 효과λ₯Ό μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
❗️❗️❗️**μ€‘μš”μ‚¬ν•­:**
- 이 데λͺ¨λŠ” μ—…λ‘œλ“œλœ GLB νŒŒμΌμ— μ• λ‹ˆλ©”μ΄μ…˜μ„ μ μš©ν•©λ‹ˆλ‹€.
- λ‹€μ–‘ν•œ μ• λ‹ˆλ©”μ΄μ…˜ μŠ€νƒ€μΌ μ€‘μ—μ„œ μ„ νƒν•˜μ„Έμš”: νšŒμ „, λΆ€μœ , 폭발, 쑰립, νŽ„μŠ€, μŠ€μœ™.
- κ²°κ³ΌλŠ” μ• λ‹ˆλ©”μ΄μ…˜λœ GLB 파일과 미리보기용 GIF 파일둜 μ œκ³΅λ©λ‹ˆλ‹€.
""")
with gr.Row():
with gr.Column():
# μž…λ ₯ μ»΄ν¬λ„ŒνŠΈ
input_3d = gr.Model3D(label="3D λͺ¨λΈ 파일 μ—…λ‘œλ“œ (GLB 포맷)")
with gr.Row():
animation_type = gr.Dropdown(
label="μ• λ‹ˆλ©”μ΄μ…˜ μœ ν˜•",
choices=["rotate", "float", "explode", "assemble", "pulse", "swing"],
value="rotate"
)
with gr.Row():
animation_duration = gr.Slider(
label="μ• λ‹ˆλ©”μ΄μ…˜ 길이 (초)",
minimum=1.0,
maximum=10.0,
value=3.0,
step=0.5
)
fps = gr.Slider(
label="μ΄ˆλ‹Ή ν”„λ ˆμž„ 수",
minimum=15,
maximum=60,
value=30,
step=1
)
submit_btn = gr.Button("λͺ¨λΈ 처리 및 μ• λ‹ˆλ©”μ΄μ…˜ 생성")
with gr.Column():
# 좜λ ₯ μ»΄ν¬λ„ŒνŠΈ
output_3d = gr.Model3D(label="μ• λ‹ˆλ©”μ΄μ…˜ 적용된 3D λͺ¨λΈ")
output_gif = gr.Image(label="μ• λ‹ˆλ©”μ΄μ…˜ 미리보기 (GIF)")
output_json = gr.File(label="메타데이터 파일 λ‹€μš΄λ‘œλ“œ")
# μ• λ‹ˆλ©”μ΄μ…˜ μœ ν˜• μ„€λͺ…
gr.Markdown("""
### μ• λ‹ˆλ©”μ΄μ…˜ μœ ν˜• μ„€λͺ…
- **νšŒμ „(rotate)**: λͺ¨λΈμ΄ Y좕을 μ€‘μ‹¬μœΌλ‘œ νšŒμ „ν•©λ‹ˆλ‹€.
- **λΆ€μœ (float)**: λͺ¨λΈμ΄ μœ„μ•„λž˜λ‘œ λΆ€λ“œλŸ½κ²Œ λ– λ‹€λ‹™λ‹ˆλ‹€.
- **폭발(explode)**: λͺ¨λΈμ˜ 각 뢀뢄이 μ€‘μ‹¬μ—μ„œ λ°”κΉ₯μͺ½μœΌλ‘œ νΌμ Έλ‚˜κ°‘λ‹ˆλ‹€.
- **쑰립(assemble)**: 폭발 μ• λ‹ˆλ©”μ΄μ…˜μ˜ λ°˜λŒ€ - λΆ€ν’ˆλ“€μ΄ ν•¨κ»˜ λͺ¨μž…λ‹ˆλ‹€.
- **νŽ„μŠ€(pulse)**: λͺ¨λΈμ΄ 크기가 μ»€μ‘Œλ‹€ μž‘μ•„μ‘Œλ‹€λ₯Ό λ°˜λ³΅ν•©λ‹ˆλ‹€.
- **μŠ€μœ™(swing)**: λͺ¨λΈμ΄ 쒌우둜 λΆ€λ“œλŸ½κ²Œ ν”λ“€λ¦½λ‹ˆλ‹€.
### 팁
- μ• λ‹ˆλ©”μ΄μ…˜ 길이와 FPSλ₯Ό μ‘°μ ˆν•˜μ—¬ μ›€μ§μž„μ˜ 속도와 λΆ€λ“œλŸ¬μ›€μ„ μ‘°μ ˆν•  수 μžˆμŠ΅λ‹ˆλ‹€.
- λ³΅μž‘ν•œ λͺ¨λΈμ€ 처리 μ‹œκ°„μ΄ 더 였래 걸릴 수 μžˆμŠ΅λ‹ˆλ‹€.
- GIF λ―Έλ¦¬λ³΄κΈ°λŠ” λΉ λ₯Έ 참쑰용이며, κ³ ν’ˆμ§ˆ κ²°κ³Όλ₯Ό μœ„ν•΄μ„œλŠ” μ• λ‹ˆλ©”μ΄μ…˜λœ GLB νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œν•˜μ„Έμš”.
""")
# λ²„νŠΌ λ™μž‘ μ„€μ •
submit_btn.click(
fn=process_3d_model,
inputs=[input_3d, animation_type, animation_duration, fps],
outputs=[output_3d, output_gif, output_json]
)
# 예제 μ€€λΉ„
example_files = [[f] for f in glob.glob('./data/demo_glb/*.glb')]
if example_files:
gr.Examples(
examples=example_files,
inputs=[input_3d],
examples_per_page=10,
)
# μ•± μ‹€ν–‰
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)