AI-video-lifeplan-demo/backend/convert_dialogue_video.py

125 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import os
import subprocess
from pathlib import Path
# Path configuration
PROJECT_ROOT = Path(__file__).parent.parent
PROPS_PATH = PROJECT_ROOT / "assets" / "1_processed" / "render_props.json"
PUBLIC_DIR = PROJECT_ROOT / "public"
VIDEO_PATH = PROJECT_ROOT / "assets" / "0_input" / "0df913b55e79323c3985e9d826af23e6.mp4"
# Use Remotion's bundled ffmpeg if system ffmpeg is not available
FFMPEG_PATH = PROJECT_ROOT / "node_modules" / "@remotion" / "compositor-win32-x64-msvc" / "ffmpeg.exe"
def slice_video_custom(input_path, start, end, output_path):
"""Simple wrapper for ffmpeg slicing"""
duration = end - start
# Choose ffmpeg executable
exe = "ffmpeg"
if FFMPEG_PATH.exists():
exe = str(FFMPEG_PATH)
cmd = [
exe, "-y",
"-ss", str(start),
"-t", str(duration),
"-i", str(input_path),
"-c:v", "libx264", "-crf", "23", "-preset", "veryfast",
"-c:a", "aac", "-b:a", "128k",
str(output_path)
]
print(f"🎬 Executing: {' '.join(cmd)}")
subprocess.run(cmd, check=True, capture_output=True)
return str(output_path)
def convert_dialogue_to_video():
"""
Converts specific 'dialogue' segments to 'video' segments
by matching timestamps from the original extraction.
"""
if not PROPS_PATH.exists():
print(f"❌ Error: {PROPS_PATH} not found.")
return
# Choose ffmpeg executable
exe = "ffmpeg"
if FFMPEG_PATH.exists():
exe = str(FFMPEG_PATH)
print(f"📖 Reading {PROPS_PATH}...")
with open(PROPS_PATH, "r", encoding="utf-8") as f:
props = json.load(f)
# We want to target the client's responses in the opening dialogue
# Based on the file content:
# Segment 2: "はい、私がテスト太郎として" -> Client
# Segment 4: "はい、生年月日の方は..." -> Client
# Segment 6: "はい。" -> Client
# Mapping table (based on №1_基本情報_extraction.json)
# i=1: 00:27 - 00:44 (顧客)
# i=3: 01:04 - 01:46 (顧客) -> Matches "生年月日" topic (actually i=1 has birthdate in text)
# Let's define the new clips to extract
new_clips = [
{"start": 27.0, "end": 35.0, "text": "はい、私がテスト太郎として..."}, # Map to script[1]
{"start": 35.0, "end": 44.0, "text": "生年月日の方は、1980年の1月の1日です。"}, # Map to script[3]
{"start": 69.0, "end": 72.0, "text": "はい。"} # Map to script[5]
]
extracted_clips = []
# 1. Extract the physical audio files (MP3) instead of video
print("✂️ Extracting audio from clips...")
for i, clip_info in enumerate(new_clips):
filename = f"dialogue_clip_{i+1}.mp3"
output_path = PUBLIC_DIR / filename
try:
# -vn: skip video, -acodec libmp3lame: encode as mp3
cmd = [
exe, "-y",
"-ss", str(clip_info["start"]),
"-t", str(clip_info["end"] - clip_info["start"]),
"-i", str(VIDEO_PATH),
"-vn", "-acodec", "libmp3lame", "-ab", "128k",
str(output_path)
]
print(f"🎬 Executing: {' '.join(cmd)}")
subprocess.run(cmd, check=True, capture_output=True)
extracted_clips.append({
"id": f"dialogue_clip_{i+1}",
"audioPath": f"public/{filename}",
"duration": clip_info["end"] - clip_info["start"]
})
except Exception as e:
print(f"⚠️ Failed to extract audio {i+1}: {e}")
# 3. Update script segments to 'dialogue' type with 'audioPath'
print("📝 Updating script segments to 'dialogue' type with original audio...")
mapping = {1: 0, 3: 1, 5: 2} # script[index] -> dialogue_clip index
for script_idx, clip_idx in mapping.items():
if script_idx < len(props["script"]):
seg = props["script"][script_idx]
clip = extracted_clips[clip_idx]
seg["visualType"] = "dialogue"
seg["visualAssetIndex"] = None
seg["duration"] = clip["duration"]
seg["audioPath"] = clip["audioPath"]
print(f" ✅ Updated segment {seg['id']} with original audio {clip['audioPath']}")
# 4. Save
with open(PROPS_PATH, "w", encoding="utf-8") as f:
json.dump(props, f, ensure_ascii=False, indent=2)
print(f"✨ Successfully updated {PROPS_PATH}. Please run Remotion to preview.")
if __name__ == "__main__":
convert_dialogue_to_video()