fix(render): open each sound file once in the final pass
Every placed sound used to be its own ffmpeg input: a project with hundreds of sfx exceeded the Windows command-line limit (WinError 206). Each file is now an input once, split and trimmed in the filter graph.
3723761 parent: 60f7193 modified
backend/studio/modules/projects/render.py +17 -6 | @@ -474,14 +474,25 @@ def final_args(p: dict, aroll: Path, dest: Path, script: Path) -> list[str]: | ||
| 474 | 474 | graph.append(f"{video}[t{n}]overlay=x={round((x['x'] - 0.5) * w)}:y={round((x['y'] - 0.5) * h)}:" |
| 475 | 475 | f"eof_action=pass:format=auto[w{n}]") |
| 476 | 476 | video = f"[w{n}]" |
| 477 | + # un fichier son n'est ouvert qu'une fois, puis découpé dans le graphe : un même whoosh placé des dizaines de | |
| 478 | + # fois ferait sinon autant d'entrées, et la ligne de commande dépasserait la limite de Windows (32 767 caractères) | |
| 479 | + uses: dict[str, list[dict]] = {} | |
| 477 | 480 | for s in p["sounds"]: |
| 478 | - info = media.probe(Path(s["path"])) | |
| 479 | - if not info.get("audio"): | |
| 481 | + uses.setdefault(s["path"], []).append(s) | |
| 482 | + for path, group in uses.items(): | |
| 483 | + if not media.probe(Path(path)).get("audio"): | |
| 480 | 484 | continue |
| 481 | - args += ["-ss", f"{s['in']:.6f}", "-t", f"{s['out'] - s['in']:.6f}", "-i", s["path"]] | |
| 482 | - graph.append(f"[{index}:a]aresample={AUDIO_RATE},aformat=sample_fmts=fltp:channel_layouts=stereo," | |
| 483 | - f"volume={s['gain_db']}dB,adelay=delays={round(s['at'] * 1000)}:all=1[s{index}]") | |
| 484 | - sounds.append(f"[s{index}]") | |
| 485 | + args += ["-i", path] | |
| 486 | + sources = [f"[{index}:a]"] | |
| 487 | + if len(group) > 1: | |
| 488 | + sources = [f"[a{index}k{k}]" for k in range(len(group))] | |
| 489 | + graph.append(f"[{index}:a]asplit={len(group)}{''.join(sources)}") | |
| 490 | + for source, s in zip(sources, group): | |
| 491 | + label = f"[s{len(sounds)}]" | |
| 492 | + graph.append(f"{source}atrim=start={s['in']:.6f}:duration={s['out'] - s['in']:.6f},asetpts=PTS-STARTPTS," | |
| 493 | + f"aresample={AUDIO_RATE},aformat=sample_fmts=fltp:channel_layouts=stereo," | |
| 494 | + f"volume={s['gain_db']}dB,adelay=delays={round(s['at'] * 1000)}:all=1{label}") | |
| 495 | + sounds.append(label) | |
| 485 | 496 | index += 1 |
| 486 | 497 | graph.append(f"{video}format=yuv420p[vout]") |
| 487 | 498 | if len(sounds) > 1: |
| @@ -474,14 +474,25 @@ def final_args(p: dict, aroll: Path, dest: Path, script: Path) -> list[str]: | |||
| 474 | graph.append(f"{video}[t{n}]overlay=x={round((x['x'] - 0.5) * w)}:y={round((x['y'] - 0.5) * h)}:" | 474 | graph.append(f"{video}[t{n}]overlay=x={round((x['x'] - 0.5) * w)}:y={round((x['y'] - 0.5) * h)}:" |
| 475 | f"eof_action=pass:format=auto[w{n}]") | 475 | f"eof_action=pass:format=auto[w{n}]") |
| 476 | video = f"[w{n}]" | 476 | video = f"[w{n}]" |
| 477 | + # un fichier son n'est ouvert qu'une fois, puis découpé dans le graphe : un même whoosh placé des dizaines de | ||
| 478 | + # fois ferait sinon autant d'entrées, et la ligne de commande dépasserait la limite de Windows (32 767 caractères) | ||
| 479 | + uses: dict[str, list[dict]] = {} | ||
| 477 | for s in p["sounds"]: | 480 | for s in p["sounds"]: |
| 478 | - info = media.probe(Path(s["path"])) | 481 | + uses.setdefault(s["path"], []).append(s) |
| 479 | - if not info.get("audio"): | 482 | + for path, group in uses.items(): |
| 483 | + if not media.probe(Path(path)).get("audio"): | ||
| 480 | continue | 484 | continue |
| 481 | - args += ["-ss", f"{s['in']:.6f}", "-t", f"{s['out'] - s['in']:.6f}", "-i", s["path"]] | 485 | + args += ["-i", path] |
| 482 | - graph.append(f"[{index}:a]aresample={AUDIO_RATE},aformat=sample_fmts=fltp:channel_layouts=stereo," | 486 | + sources = [f"[{index}:a]"] |
| 483 | - f"volume={s['gain_db']}dB,adelay=delays={round(s['at'] * 1000)}:all=1[s{index}]") | 487 | + if len(group) > 1: |
| 484 | - sounds.append(f"[s{index}]") | 488 | + sources = [f"[a{index}k{k}]" for k in range(len(group))] |
| 489 | + graph.append(f"[{index}:a]asplit={len(group)}{''.join(sources)}") | ||
| 490 | + for source, s in zip(sources, group): | ||
| 491 | + label = f"[s{len(sounds)}]" | ||
| 492 | + graph.append(f"{source}atrim=start={s['in']:.6f}:duration={s['out'] - s['in']:.6f},asetpts=PTS-STARTPTS," | ||
| 493 | + f"aresample={AUDIO_RATE},aformat=sample_fmts=fltp:channel_layouts=stereo," | ||
| 494 | + f"volume={s['gain_db']}dB,adelay=delays={round(s['at'] * 1000)}:all=1{label}") | ||
| 495 | + sounds.append(label) | ||
| 485 | index += 1 | 496 | index += 1 |
| 486 | graph.append(f"{video}format=yuv420p[vout]") | 497 | graph.append(f"{video}format=yuv420p[vout]") |
| 487 | if len(sounds) > 1: | 498 | if len(sounds) > 1: |