Spaces:
Sleeping
Sleeping
File size: 5,609 Bytes
a59a9fc a07a00e 42187ab a07a00e 549107e a59a9fc 549107e 42187ab a59a9fc 549107e 42187ab a59a9fc 549107e a59a9fc 549107e a59a9fc 549107e a07a00e 549107e a07a00e a59a9fc a07a00e a59a9fc 42187ab 549107e 42187ab a59a9fc 42187ab a59a9fc 549107e a59a9fc 549107e a59a9fc 42187ab a59a9fc 549107e 42187ab a59a9fc a07a00e 42187ab 549107e a07a00e a59a9fc 42187ab 549107e a59a9fc 549107e a59a9fc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | 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() |