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()