OUTPUT #689
PARÇA 6 / 6
TOPLAM: 218127 karakter | 2042 satır
BU PARÇA: 18127 karakter
ndering.** The PreviewPlayer scrubs through the overlay timing in real time — verify word sync looks correct and adjust any `start`/`end` values in th
208: ### tracks[0].items — With background video
215: "src": "/abs/path/to/background.mov",
225: ### tracks[0].items — Without background video
231: `lyric-phrase.jsx` renders its own full-screen colored background automatically.
275: ### Background styles
301: Set `"transparent": true` when a background video is in `tracks[0]`.
===== /home/hermes/.hermes/tools/montaj/skills/write-overlay/SKILL.md | hits=32 =====
30: | `useThreeFrame` | hook | Bridges r3f to Montaj's frame-stepped renderer. Mount exactly once inside any `<Canvas>`. |
69: > **Expose text styling as props to make an overlay editable.** A text overlay is only restyleable in the editor's properties panel for the props it declares — see "Make text overlays editable in the properties panel" below, which applies t
71: The default aesthetic is **plain bold text directly on video** — no card, no background, just a text shadow for legibility. Big text (96–160px) that covers the footage, including the speaker's face if needed.
74: // overlays/hook.jsx — plain text on video, no background
99: Only add a card or background when the prompt explicitly asks, or when a specific overlay type genuinely requires it (e.g. a logo lockup, an opaque title card). When you do need a background, prefer a solid semi-transparent color over `back
105: - **Frame-driven** — all animation must derive from `frame`. No `setTimeout`, `setInterval`, CSS `animation`, or `transition`.
106: - **Transparent background (default)** — overlays render with a transparent background by default. Do not set `background` on the root element; it will obscure whatever is beneath it.
107: - **Opaque overlays** — when `"opaque": true` is set on the item in project.json, the root element's CSS controls the entire frame. You may freely set `background`, gradients, images, or any CSS on the root. Use this for full-frame covers,
110: - **`backdropFilter` caution** — `backdrop-filter: blur(...)` causes Chrome to create a separate GPU compositor layer that can be cached and replayed as a stale frame during rendering. Avoid putting `backdrop-filter` on any element whose ch
140: background: props.bgColor ?? 'transparent',
166: ## Splitting background from content across tracks
168: The most reliable way to use frosted-glass / blurred card backgrounds is to **put the background on a separate, lower track** and the animated content on a higher track. The render pipeline composites tracks in order, so the content renders
170: **Why this works:** A background card with `backdrop-filter` is essentially static — it fades in, then stays put. When Chrome's headless compositor caches the GPU layer for it, the cache is *correct* (the layer genuinely hasn't changed). Th
174: | Background behavior | Animated content | Verdict |
177: | Shakes, bounces, or translates together with content | Content must move with the background | **Keep together** (no backdrop-filter, use solid `background` instead) |
213: **Background component — no animated children:**
224: background: 'rgba(0,0,0,0.84)',
241: // Animated items rendered on top of the background card.
257: **When you can't split** (background and content animate together as one unit — e.g., a card that shakes on impact), skip `backdrop-filter` entirely and use a solid or semi-transparent `background` instead:
261: background: 'rgba(10,10,10,0.88)' // solid dark — visually similar, no GPU layer caching
375: **System font fallbacks for common Google Fonts:**
377: | Google Font | System fallback |
384: If visual fidelity isn't critical, the system fallback avoids the network fetch entirely.
397: 2. **Mount `useThreeFrame()` exactly once inside the Canvas.** This hook registers the synchronous render trigger the shim calls every frame. Without it, Three never draws.
464: Make sure `gl={{ alpha: true }}` is set — the Canvas DOM element defaults to opaque, which would paint a black or white box over the underlying footage. With `alpha: true` + the renderer's default transparent page background, only the drawn
488: Set `isAnimationActive={false}` on every chart primitive (`<Bar>`, `<Line>`, `<Pie>`, etc.). Recharts animates by default; without this, the carousel renderer captures a mid-animation frame and the chart looks half-drawn. If you see fuzzy t
583: Reference assets by their workspace path (e.g. `/abs/path/to/project/assets/logo.png`). How that path is resolved for preview or render is the interface's concern — the component always receives a usable URL for the path it was given.
621: - **No backgrounds by default** — plain text on video with `textShadow` for legibility is the house style. No dark cards, no frosted glass, no semi-transparent boxes unless the prompt asks. A well-placed `textShadow` works on any footage.
627: - **Avoid the bottom ~350px** — captions render here, and platform UI (TikTok progress bar, Instagram controls) sits in this zone. Use `bottom: 350` or higher, or anchor from the top instead.
638: **Write all of your overlay JSX first. Then sample them in a single batch pass — do not sample after each file.** Per-file sampling stalls authoring and spins up a fresh Puppeteer process each time; one pass at the end over the finished set
640: Once every JSX file is written, loop over them in one pass — for each JSX file, **run step `sample_overlay`** with args `{ overlay, out, measure: true, google_fonts, props }`:
675: Display fonts like **Syne 800** are 60–70% wider than typical `sans-serif` fallbacks at the same `px` size. Concrete example: `"RECURSIVE"` at `fontSize: 160` measures ~933 px wide in fallback `sans-serif`, but ~1594 px wide in Syne 800 — 5
===== /home/hermes/.hermes/tools/montaj/skills/overlay/SKILL.md | hits=8 =====
43: **Plain text directly on video is almost always the right call.** Skip the card. Skip the frosted glass. Big, bold text sitting right on the footage is more dynamic and feels native — not slapped on top.
46: - **No backgrounds** — avoid dark cards, frosted panels, and semi-transparent boxes unless the prompt asks for them. A text shadow (`textShadow: '0 2px 16px rgba(0,0,0,0.9)'`) is enough to ensure legibility on any footage without boxing the
50: - **Avoid the bottom ~350px** — that's where captions render and where platform UI lives (TikTok progress bar, Instagram controls). Keep `bottom` values above 350px, or use `top`-anchored placement instead.
99: - **To cover footage fully**, set `"opaque": true` on the item — the render engine removes transparency and lets the JSX root's CSS define the background. The audio track is unaffected.
101: - **No backgrounds by default** — plain text on video with a text shadow is the preferred style. Only use cards or panels when the prompt explicitly asks, or when legibility genuinely requires it
106: - **Expose text styling as props** — a text overlay you want editable in the editor's properties panel must READ its font, size, weight, style, color, alignment, transform, and background from `props` (the nine standard text props) with sen
110: - Canvas is **1080 on the short edge** with the aspect ratio of `project.settings.resolution` (default `[1080, 1920]` portrait) — always, regardless of output resolution. The render pipeline captures overlay segments at design resolution (P
114: - **Split background from animated content** — never put `backdrop-filter: blur()` on a container whose children animate. It creates a GPU compositor layer that Chrome caches, producing stale/flashing frames in the rendered output. Put the
===== /home/hermes/.hermes/tools/montaj/skills/workflow-builder/SKILL.md | hits=15 =====
40: | `caption` | transcript | caption track data |
50: **Most of the clean-and-trim chain never encodes.** `waveform_trim` starts the chain by emitting a trim spec — `{"input": "...", "keeps": [[s, e], ...]}` — and `rm_nonspeech`, `rm_fillers` and `crop_spec` each take a spec and return a refin
67: - Quality level (quick cut vs. full clean + captions + overlays)
76: probe, snapshot, transcribe, rm_fillers, waveform_trim, caption, overlays, resize
91: - `caption` needs `transcribe` (uses word timings)
95: - `caption` — if it should caption the cleaned video (not the original), add the last cleaning step as a need
99: - `caption` commonly needs both `transcribe` AND the last cleaning step (e.g. `waveform_trim`)
122: Wave 4: caption (needs transcribe + waveform_trim)
123: Wave 5: overlays (needs caption)
141: { "id": "caption", "uses": "montaj/caption", "needs": ["transcribe", "silence"], "params": { "style": "word-by-word" } },
142: { "id": "overlays", "uses": "montaj/overlay", "needs": ["caption"], "params": { "style": "auto" } },
177: **Fastest possible clean + caption (max parallelism):**
182: Wave 4: caption (needs transcribe + waveform_trim), normalize (needs waveform_trim)
183: Wave 5: resize (needs caption + normalize)
195: caption, overlays, normalize
===== /home/hermes/.hermes/tools/montaj/skills/editable-text/SKILL.md | hits=10 =====
12: This skill applies to carousel projects only. It covers every overlay that renders text the operator should be able to edit — including overlays that destructure common text-content prop names (`headline`, `body`, `eyebrow`, `subtitle`, `ti
39: - `fontSize` is coerced to a number with NaN-fallback (per `static-text.jsx`).
41: - `bgColor` controls the overlay's backdrop (set via `style.background` on the outer wrapper; `'transparent'` is the no-backdrop case).
52: The 9 props are a floor, not a ceiling. The agent may add any other props (gradients, accents, icon refs, subtitle blocks, animation timing for non-text-style features, etc.). The contract only says these 9 must be present and applied; noth
77: background: bgColor, padding: '8% 10%', boxSizing: 'border-box',
90: **Body copy** — readable serif, left-aligned, on a card background:
93: // overlays/body.jsx — middle-slide body block.
111: background: bgColor, padding: '8% 9%', boxSizing: 'border-box',
147: background: bgColor, padding: '4% 6%', boxSizing: 'border-box',
149: <span style={{ display: 'inline-block', width: 32, height: 2, background: accentColor, marginRight: 12 }} />
===== /home/hermes/.hermes/tools/montaj/skills/camera-vocabulary/SKILL.md | hits=2 =====
65: | `rack_focus` | Focus shifts between foreground/background | Reveal connection, redirect attention |
93: - **Subject is still** (posing, landscape, interior, still object) → pick an **assertive** camera move: `push_in`, `pull_out`, `orbit`, `arc`, `tracking`, `handheld_drift`, `crane_up/down`, `whip_pan`, `crash_zoom`, `rack_focus`. A static s
===== /home/hermes/.hermes/tools/montaj/lib/types/colorspace.py | hits=1 =====
85: _build_color_conversion_vf). This matches FCP/Resolve behavior: a single
===== /home/hermes/.hermes/tools/montaj/lib/types/carousel.py | hits=2 =====
24: None → silent fallback.
25: Unknown string → warn + fallback.
===== /home/hermes/.hermes/tools/montaj/lib/types/kling.py | hits=2 =====
21: None → silent fallback.
22: Unknown string → warn + fallback.
===== /home/hermes/.hermes/tools/montaj/lib/types/project.py | hits=3 =====
10: others skip storyboard_ready).
25: None → silent fallback.
26: Unknown string → warn + fallback.
===== /home/hermes/.hermes/tools/montaj/serve/routes/files.py | hits=14 =====
1: """File-serving + asset endpoints: /files, /files/stream, /upload, /pick-files, /caption-template."""
20: CAPTION_STYLES = {"word-by-word", "pop", "karaoke", "subtitle", "highlight-box", "outline", "clean"}
58: """Blocking file-picker — runs in a thread pool so it doesn't block the event loop."""
70: r = subprocess.run(["osascript", "-e", script], capture_output=True, text=True)
96: @router.get("/caption-template/{style}")
97: async def get_caption_template(style: str):
98: """Serve a built-in caption template JSX file for in-browser preview."""
99: if style not in CAPTION_STYLES:
100: raise not_found("not_found", f"Unknown caption style: {style}")
101: p = Path(render_runtime_dir()) / "templates" / "captions" / f"{style}.jsx"
115: read-only). Rejects paths that are not absolute, that contain .. escaping
183: # Scope check — runs after any NBSP-fallback reassignment of p, before any
189: # A symlink whose target escapes the roots still 403s (resolve catches it).
212: single-file preview).
===== /home/hermes/.hermes/tools/montaj/serve/routes/projects.py | hits=196 =====
32: from serve.caption_job import build_audio_mix_spec
55: # React effect re-run, SSE reconnect retry); without this, two render.js processes
58: # defense at the serve layer (single Python process, single asyncio loop, set
108: self.phase: str = "preparing" # preparing | captions | rendering | encoding | done
114: render.js / compose.js. Captions is checked FIRST because a captions segment
116: if "bundling segment" in line and "(captions)" in line:
117: return "captions"
139: # underscore, dot. No path separators, so a sanitized name can never escape
144: def _sanitize_output_name(name: str, fallback: str) -> str:
148: whitelist; falls back to ``fallback`` (the project dir name) if nothing
155: return base or fallback
204: limit=10 * 1024 * 1024, # ffmpeg config/filter lines exceed the 64KB default
245: # In-flight caption-generation dedup. Same rationale as _active_renders: the UI
246: # (or an SSE reconnect) can fire the same caption job twice, and two concurrent
247: # jobs would race on the shared _caption_* scratch files in the project dir.
248: _active_caption_jobs: set[str] = set()
251: class _CaptionJob:
252: """Live state of a detached caption job, polled by GET /captions/status. The
259: self.result: dict | None = None # caption track dict (done)
263: _caption_jobs: dict[str, _CaptionJob] = {}
265: # Strong refs to in-flight detached caption tasks. asyncio only weakly tracks
266: # fire-and-forget tasks, so without this a caption task could be GC'd mid-run.
267: _caption_task_refs: set = set()
270: async def _run_caption_detached(
278: job: _CaptionJob,
280: """Run the caption pipeline to completion regardless of any client. Owns the
281: `_active_caption_jobs` slot until the pipeline actually finishes, so a dropped
284: track = await _run_caption_pipeline(
296: except CaptionPipelineError as e:
302: # NOTE: the terminal job is intentionally NOT popped from _caption_jobs —
303: # it persists so a post-completion GET /captions/status can still read it.
304: # A new job overwrites _caption_jobs[project_id] after the 409 check clears.
305: _active_caption_jobs.discard(project_id)
308: project_dir / "_caption_mix.json",
309: project_dir / "_caption_mix.wav",
===== /home/hermes/.hermes/tools/montaj/serve/routes/skills.py | hits=2 =====
15: fallback: returns 'dev' when running from source (package not installed)."""
48: # Parse YAML frontmatter between --- delimiters
===== /home/hermes/.hermes/tools/montaj/serve/routes/steps.py | hits=47 =====
22: STEP_TIMEOUT_S = int(os.environ.get("MONTAJ_STEP_TIMEOUT", "900"))
71: # Also accept singular "input" for convenience (UI sends singular for single-file calls)
94: # Repeatable params: emit the flag once per element (matches MCP buildCliArgs).
115: # quietly skipped them, and the step ignored the intended trim window.
180: async def _execute_step(name: str, schema: dict, py_path: Path, body: dict, *, timeout: int = STEP_TIMEOUT_S) -> dict:
187: `timeout` defaults to STEP_TIMEOUT_S for every caller except the proxy job
188: driver, which forwards a duration-scaled timeout (see proxy_video) so a
217: # Non-blocking subprocess — allows the server to keep serving UI, SSE,
233: timeout=timeout,
268: """Background driver: run a step and record its result/error on the job."""
279: # fire-and-forget background job can be garbage-collected mid-run ("Task was
282: _BACKGROUND_TASKS: set[asyncio.Task] = set()
299: _BACKGROUND_TASKS.add(task)
300: task.add_done_callback(_BACKGROUND_TASKS.discard)
316: # json.dumps may have escaped characters in the secret; scrub the
329: Response: { "path": "/abs/path/to/output.mp4", "skipped": false }
349: """Blocking normalize — probe, freshness short-circuit, encode.
353: (synchronous) HTTP route. Returns the route's `{"path", "skipped"}` payload;
364: return {"path": input_path, "skipped": True}
374: return {"path": result_path or out, "skipped": False}
378: """Background driver for a master re-encode: run `_normalize_sync` off the
380: `_run_proxy_to_job` produces (`{"path": ..., "skipped": ...}`).
383: it blocks the caller for the length of the encode. The project-open look
384: migration (serve/routes/projects.py) can't block a GET on a multi-minute
397: async def _run_proxy_to_job(job_id: str, schema: dict, py_path: Path, body: dict, *, timeout: int = STEP_TIMEOUT_S) -> None:
398: """Background driver for /api/proxy: run the proxy step and record its
399: result/error on the job — same shape as _run_to_job, plus a `skipped:
400: false` field so a completed job's result matches the fresh-skip response
401: (both are `{"path": ..., "skipped": ...}`)."""
403: result = await _execute_step("proxy", schema, py_path, body, timeout=timeout)
405: result.setdefault("skipped", False)
423: Proxy encodes run for minutes, so — unlike /api/normalize's blocking
426: a background job (202 + job_id), polled via the existing
430: - fresh cache hit: 200 {"path": ..., "skipped": true} — no job started
433: "result": {"path": ..., "skipped": false}}
===== /home/hermes/.hermes/tools/montaj/serve/routes/profile_assets.py | hits=2 =====
67: raise forbidden("traversal", "Path escapes assets dir")
144: raise forbidden("traversal", "Path escapes assets dir")
===== /home/hermes/.hermes/tools/montaj/serve/routes/profiles.py | hits=2 =====
21: block = text[3:end].strip()
23: for line in block.splitlines():
root@213-238-170-219:~#