James040's picture
Update app.py
549107e verified
import cv2
import numpy as np
import gradio as gr
import subprocess
import urllib.request
import os
import json
# 1. Modern Tasks API
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
# Auto-Download Model
MODEL_PATH = "pose_landmarker_lite.task"
MODEL_URL = "https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task"
if not os.path.exists(MODEL_PATH):
print("Downloading MediaPipe Pose Model...")
urllib.request.urlretrieve(MODEL_URL, MODEL_PATH)
POSE_CONNECTIONS = [
(0, 1), (1, 2), (2, 3), (3, 7), (0, 4), (4, 5), (5, 6), (6, 8), (9, 10),
(11, 12), (11, 13), (13, 15), (15, 17), (15, 19), (15, 21), (17, 19),
(12, 14), (14, 16), (16, 18), (16, 20), (16, 22), (18, 20), (11, 23),
(12, 24), (23, 24), (23, 25), (24, 26), (25, 27), (26, 28), (27, 29),
(28, 30), (29, 31), (30, 32), (27, 31), (28, 32)
]
def extract_pose_and_data(video_path):
if video_path is None:
return None, None, None
output_video_path = "final_output.mp4"
temp_video = "temp_silent.mp4"
output_json_path = "pose_data.json"
cap = cv2.VideoCapture(video_path)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(temp_video, fourcc, fps, (width, height))
base_options = python.BaseOptions(model_asset_path=MODEL_PATH)
options = vision.PoseLandmarkerOptions(
base_options=base_options,
running_mode=vision.RunningMode.VIDEO
)
# Storage for Blender Data
all_frames_data = []
with vision.PoseLandmarker.create_from_options(options) as landmarker:
frame_idx = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_frame)
timestamp_ms = int((frame_idx / fps) * 1000)
result = landmarker.detect_for_video(mp_image, timestamp_ms)
canvas = np.zeros((height, width, 3), dtype=np.uint8)
frame_entry = {
"frame": frame_idx,
"timestamp_ms": timestamp_ms,
"landmarks": []
}
if result.pose_landmarks and result.pose_world_landmarks:
# 1. Extract 3D World Data for JSON (For Blender)
for landmark in result.pose_world_landmarks[0]:
frame_entry["landmarks"].append({
"x": landmark.x,
"y": landmark.y,
"z": landmark.z,
"visibility": landmark.visibility
})
# 2. Draw 2D Data for Video (For EbSynth)
pose = result.pose_landmarks[0]
for connection in POSE_CONNECTIONS:
start_idx, end_idx = connection
start_pt, end_pt = pose[start_idx], pose[end_idx]
start_px = (int(start_pt.x * width), int(start_pt.y * height))
end_px = (int(end_pt.x * width), int(end_pt.y * height))
cv2.line(canvas, start_px, end_px, (0, 255, 0), 10)
for landmark in pose:
px = (int(landmark.x * width), int(landmark.y * height))
cv2.circle(canvas, px, 15, (255, 255, 255), -1)
all_frames_data.append(frame_entry)
out.write(canvas)
frame_idx += 1
cap.release()
out.release()
# Save the JSON file
with open(output_json_path, 'w') as f:
json.dump(all_frames_data, f, indent=4)
# Merge Audio Native FFmpeg
try:
command = [
"ffmpeg", "-y", "-i", temp_video, "-i", video_path,
"-c:v", "copy", "-c:a", "aac", "-map", "0:v:0", "-map", "1:a:0?",
"-shortest", output_video_path
]
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except Exception as e:
print("FFmpeg error:", e)
output_video_path = temp_video
# Return: Video File, JSON File (for download), JSON Dictionary (for UI Copying)
return output_video_path, output_json_path, all_frames_data
# Gradio UI Setup
with gr.Blocks(title="Pose & 3D Data Extractor") as interface:
gr.Markdown("# 🕺 Pose Video & 3D JSON Extractor")
gr.Markdown("Generates a thick stickman for EbSynth and extracts `pose_world_landmarks` (x, y, z) for Blender IK.")
with gr.Row():
with gr.Column():
video_input = gr.Video(label="Upload Dancing Clip (15-30s)")
submit_btn = gr.Button("Extract Pose & Data", variant="primary")
with gr.Column():
video_output = gr.Video(label="Meaty Stickman Output")
file_output = gr.File(label="Download 3D JSON Data")
with gr.Row():
# The gr.JSON component automatically includes a "Copy" button in the top right
json_output = gr.JSON(label="Raw JSON Data (Click top right to Copy)")
submit_btn.click(
fn=extract_pose_and_data,
inputs=video_input,
outputs=[video_output, file_output, json_output]
)
if __name__ == "__main__":
interface.launch()