hlassiege/darylpublic Fork
⑂ 750e515
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/daryl.git
git clone ssh://git@rickub.com/hlassiege/daryl.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

fix(montage): render each motion from its own page, with the stage text color

HyperFrames drops the id=stage of an embedded composition, so style.css lost its text color and font: the render page now sets them. A shared index.html once gave a motion another one's render: each motion gets its own page. The page version is part of the motion signature, so existing renders are redone.
hlassiege committed 2026-10-11T21:35:12+02:00 Browse files
750e515 parent: 079a7ba
modified backend/studio/modules/projects/montage.py +4 -1
@@ -64,6 +64,9 @@ TIMELINE_FILE = "timeline.json"
6464 INSERTIONS_FILE = "insertions.json"
6565 MOTION_DIR = "motion"
6666 RENDERS_DIR = "renders"
67+# la page qui rend un motion (montage_tasks._index) entre dans sa signature : à monter quand elle change,
68+# les motions déjà rendus se refont
69+MOTION_PAGE_VERSION = 2
6770 # les médias posés dans les compositions, copiés à côté d'elles (le rendu ne lit que montage/motion/)
6871 MEDIA_DIR = "media"
6972 HISTORY_DIR = ".history"
@@ -831,7 +834,7 @@ def motion_signature(pid: str, item: dict) -> str | None:
831834 comp = motion_dir(pid) / f"{item['id']}.html"
832835 if not comp.is_file():
833836 return None
834- body = comp.read_bytes() + json.dumps([item["dur"]]).encode()
837+ body = comp.read_bytes() + json.dumps([item["dur"], MOTION_PAGE_VERSION]).encode()
835838 style = motion_dir(pid) / "style.css"
836839 if style.is_file():
837840 body += style.read_bytes()
@@ -64,6 +64,9 @@ TIMELINE_FILE = "timeline.json"
64 INSERTIONS_FILE = "insertions.json"64 INSERTIONS_FILE = "insertions.json"
65 MOTION_DIR = "motion"65 MOTION_DIR = "motion"
66 RENDERS_DIR = "renders"66 RENDERS_DIR = "renders"
67+# la page qui rend un motion (montage_tasks._index) entre dans sa signature : à monter quand elle change,
68+# les motions déjà rendus se refont
69+MOTION_PAGE_VERSION = 2
67 # les médias posés dans les compositions, copiés à côté d'elles (le rendu ne lit que montage/motion/)70 # les médias posés dans les compositions, copiés à côté d'elles (le rendu ne lit que montage/motion/)
68 MEDIA_DIR = "media"71 MEDIA_DIR = "media"
69 HISTORY_DIR = ".history"72 HISTORY_DIR = ".history"
@@ -831,7 +834,7 @@ def motion_signature(pid: str, item: dict) -> str | None:
831 comp = motion_dir(pid) / f"{item['id']}.html"834 comp = motion_dir(pid) / f"{item['id']}.html"
832 if not comp.is_file():835 if not comp.is_file():
833 return None836 return None
834- body = comp.read_bytes() + json.dumps([item["dur"]]).encode()837+ body = comp.read_bytes() + json.dumps([item["dur"], MOTION_PAGE_VERSION]).encode()
835 style = motion_dir(pid) / "style.css"838 style = motion_dir(pid) / "style.css"
836 if style.is_file():839 if style.is_file():
837 body += style.read_bytes()840 body += style.read_bytes()
modified backend/studio/modules/projects/montage_tasks.py +18 -6
@@ -441,8 +441,13 @@ def _apply(pid: str, task: str) -> None:
441441
442442 # --- Rendu d'un motion design --------------------------------------------------------------
443443
444-def _index(directory: Path, comp: str, duration: float) -> None:
445- (directory / "index.html").write_text(f"""<!doctype html>
444+def _index(directory: Path, comp: str, duration: float) -> Path:
445+ """
446+ La page qui rend un motion, propre à lui : un index.html commun à tous les motions a déjà donné à un motion
447+ le rendu d'un autre. Une modification ici : monter montage.MOTION_PAGE_VERSION.
448+ """
449+ page = directory / f"render-{comp}.html"
450+ page.write_text(f"""<!doctype html>
446451 <html lang="{language()}">
447452 <head>
448453 <meta charset="utf-8">
@@ -450,7 +455,10 @@ def _index(directory: Path, comp: str, duration: float) -> None:
450455 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
451456 <style>
452457 html, body {{ margin: 0; padding: 0; background: transparent; overflow: hidden; }}
453-.layer {{ position: absolute; inset: 0; font-family: "Space Grotesk", "Inter", system-ui, sans-serif; }}
458+/* HyperFrames retire l'id="stage" du motion en l'intégrant : la règle #stage de style.css (police, couleur
459+ du texte) ne s'applique plus. Le calque la remplace, avec les variables de la charte. */
460+.layer {{ position: absolute; inset: 0; font-family: var(--font, "Space Grotesk", "Inter", system-ui, sans-serif);
461+ color: var(--text, #f4f1ea); -webkit-font-smoothing: antialiased; }}
454462 .fonts-charte-mono {{ font-family: "JetBrains Mono"; }}
455463 </style>
456464 </head>
@@ -467,6 +475,7 @@ html, body {{ margin: 0; padding: 0; background: transparent; overflow: hidden;
467475 </body>
468476 </html>
469477 """, encoding="utf-8")
478+ return page
470479
471480
472481 def render_motion(ctx: TaskContext) -> None:
@@ -477,7 +486,7 @@ def render_motion(ctx: TaskContext) -> None:
477486 missing = montage.bring_media(pid, f"{item_id}.html")
478487 if missing:
479488 raise RuntimeError("; ".join(missing) + " : indique son src dans data-from")
480- _index(directory, item_id, p["duration"])
489+ page = _index(directory, item_id, p["duration"])
481490 ctx.set_status("rendering")
482491
483492 def on_progress(value: float, stage: str) -> None:
@@ -486,10 +495,13 @@ def render_motion(ctx: TaskContext) -> None:
486495 ctx.save()
487496
488497 output.parent.mkdir(parents=True, exist_ok=True)
489- extra = ["--format", "mov"]
498+ extra = ["--format", "mov", "--composition", page.name]
490499 if p.get("width", 1920) >= 3840:
491500 extra += ["--resolution", "landscape-4k"]
492- hyperframes.render(directory, output, on_progress, ctx.check_cancel, extra)
501+ try:
502+ hyperframes.render(directory, output, on_progress, ctx.check_cancel, extra)
503+ finally:
504+ page.unlink(missing_ok=True)
493505 # les rendus précédents de ce motion ne servent plus
494506 for old in output.parent.glob(f"{item_id}-*.mov"):
495507 if old != output:
@@ -441,8 +441,13 @@ def _apply(pid: str, task: str) -> None:
441 441
442 # --- Rendu d'un motion design --------------------------------------------------------------442 # --- Rendu d'un motion design --------------------------------------------------------------
443 443
444-def _index(directory: Path, comp: str, duration: float) -> None:444+def _index(directory: Path, comp: str, duration: float) -> Path:
445- (directory / "index.html").write_text(f"""<!doctype html>445+ """
446+ La page qui rend un motion, propre à lui : un index.html commun à tous les motions a déjà donné à un motion
447+ le rendu d'un autre. Une modification ici : monter montage.MOTION_PAGE_VERSION.
448+ """
449+ page = directory / f"render-{comp}.html"
450+ page.write_text(f"""<!doctype html>
446 <html lang="{language()}">451 <html lang="{language()}">
447 <head>452 <head>
448 <meta charset="utf-8">453 <meta charset="utf-8">
@@ -450,7 +455,10 @@ def _index(directory: Path, comp: str, duration: float) -> None:
450 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>455 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
451 <style>456 <style>
452 html, body {{ margin: 0; padding: 0; background: transparent; overflow: hidden; }}457 html, body {{ margin: 0; padding: 0; background: transparent; overflow: hidden; }}
453-.layer {{ position: absolute; inset: 0; font-family: "Space Grotesk", "Inter", system-ui, sans-serif; }}458+/* HyperFrames retire l'id="stage" du motion en l'intégrant : la règle #stage de style.css (police, couleur
459+ du texte) ne s'applique plus. Le calque la remplace, avec les variables de la charte. */
460+.layer {{ position: absolute; inset: 0; font-family: var(--font, "Space Grotesk", "Inter", system-ui, sans-serif);
461+ color: var(--text, #f4f1ea); -webkit-font-smoothing: antialiased; }}
454 .fonts-charte-mono {{ font-family: "JetBrains Mono"; }}462 .fonts-charte-mono {{ font-family: "JetBrains Mono"; }}
455 </style>463 </style>
456 </head>464 </head>
@@ -467,6 +475,7 @@ html, body {{ margin: 0; padding: 0; background: transparent; overflow: hidden;
467 </body>475 </body>
468 </html>476 </html>
469 """, encoding="utf-8")477 """, encoding="utf-8")
478+ return page
470 479
471 480
472 def render_motion(ctx: TaskContext) -> None:481 def render_motion(ctx: TaskContext) -> None:
@@ -477,7 +486,7 @@ def render_motion(ctx: TaskContext) -> None:
477 missing = montage.bring_media(pid, f"{item_id}.html")486 missing = montage.bring_media(pid, f"{item_id}.html")
478 if missing:487 if missing:
479 raise RuntimeError("; ".join(missing) + " : indique son src dans data-from")488 raise RuntimeError("; ".join(missing) + " : indique son src dans data-from")
480- _index(directory, item_id, p["duration"])489+ page = _index(directory, item_id, p["duration"])
481 ctx.set_status("rendering")490 ctx.set_status("rendering")
482 491
483 def on_progress(value: float, stage: str) -> None:492 def on_progress(value: float, stage: str) -> None:
@@ -486,10 +495,13 @@ def render_motion(ctx: TaskContext) -> None:
486 ctx.save()495 ctx.save()
487 496
488 output.parent.mkdir(parents=True, exist_ok=True)497 output.parent.mkdir(parents=True, exist_ok=True)
489- extra = ["--format", "mov"]498+ extra = ["--format", "mov", "--composition", page.name]
490 if p.get("width", 1920) >= 3840:499 if p.get("width", 1920) >= 3840:
491 extra += ["--resolution", "landscape-4k"]500 extra += ["--resolution", "landscape-4k"]
492- hyperframes.render(directory, output, on_progress, ctx.check_cancel, extra)501+ try:
502+ hyperframes.render(directory, output, on_progress, ctx.check_cancel, extra)
503+ finally:
504+ page.unlink(missing_ok=True)
493 # les rendus précédents de ce motion ne servent plus505 # les rendus précédents de ce motion ne servent plus
494 for old in output.parent.glob(f"{item_id}-*.mov"):506 for old in output.parent.glob(f"{item_id}-*.mov"):
495 if old != output:507 if old != output: