125 lines
4.5 KiB
Python
125 lines
4.5 KiB
Python
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()
|