93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from wov_sdk.models import InvokeRequest, InvokeResponse, NodeManifest
|
|
from wov_sdk.server import run_node
|
|
|
|
|
|
def _ass_header(resolution: str) -> str:
|
|
width, height = resolution.lower().split("x", 1)
|
|
left_margin = 50
|
|
right_margin = int(width) - 50
|
|
return f"""[Script Info]
|
|
Title: VR Dual-Eye Subtitle
|
|
ScriptType: v4.00+
|
|
Collisions: Normal
|
|
PlayResX: {width}
|
|
PlayResY: {height}
|
|
WrapStyle: 1
|
|
ScaledBorderAndShadow: yes
|
|
|
|
[V4+ Styles]
|
|
Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding
|
|
Style: LeftEye,Arial,50,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,0,0,0,0,50,100,0,0,1,4,0,2,{left_margin},{int(width) // 2},{int(height) // 2 + 60},1
|
|
Style: RightEye,Arial,50,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,0,0,0,0,50,100,0,0,1,4,0,2,{int(width) // 2},{right_margin},{int(height) // 2 + 60},1
|
|
|
|
[Events]
|
|
Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text
|
|
"""
|
|
|
|
|
|
def parse_srt(text: str) -> list[tuple[str, str, str]]:
|
|
entries: list[tuple[str, str, str]] = []
|
|
lines = text.splitlines()
|
|
index = 0
|
|
while index < len(lines):
|
|
if not lines[index].strip():
|
|
index += 1
|
|
continue
|
|
index += 1
|
|
if index >= len(lines):
|
|
break
|
|
time_line = lines[index].strip()
|
|
index += 1
|
|
if " --> " not in time_line:
|
|
continue
|
|
start, end = [part.replace(",", ".") for part in time_line.split(" --> ")]
|
|
text_lines: list[str] = []
|
|
while index < len(lines) and lines[index].strip():
|
|
text_lines.append(lines[index])
|
|
index += 1
|
|
entries.append((start, end, r"\N".join(text_lines)))
|
|
index += 1
|
|
return entries
|
|
|
|
|
|
def write_ass(entries: list[tuple[str, str, str]], output_path: Path, resolution: str) -> None:
|
|
lines = [_ass_header(resolution)]
|
|
for start, end, text in entries:
|
|
lines.append(f"Dialogue: 0,{start},{end},LeftEye,,0,0,0,,{{\\an2}}{text}")
|
|
lines.append(f"Dialogue: 0,{start},{end},RightEye,,0,0,0,,{{\\an2}}{text}")
|
|
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
|
|
|
|
def invoke(request: InvokeRequest) -> InvokeResponse:
|
|
srt_uri = request.inputs.get("cn_srt_uri")
|
|
if not srt_uri:
|
|
return InvokeResponse(status="failed", error="cn_srt_uri is required")
|
|
|
|
srt_path = Path(srt_uri)
|
|
if not srt_path.is_file():
|
|
return InvokeResponse(status="failed", error="srt file not found")
|
|
|
|
entries = parse_srt(srt_path.read_text(encoding="utf-8"))
|
|
output_dir = Path(request.output_dir)
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
output_path = output_dir / "dual_eye.ass"
|
|
resolution = str(request.params.get("resolution", "3840x1920"))
|
|
write_ass(entries, output_path, resolution)
|
|
return InvokeResponse(status="completed", outputs={"ass_uri": str(output_path)})
|
|
|
|
|
|
def main() -> None:
|
|
manifest_path = Path(__file__).resolve().parent.parent / "node.manifest.json"
|
|
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
manifest = NodeManifest.from_dict(json.load(f))
|
|
run_node(manifest, invoke)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|