Skip to main content

pocketjs-video-outro

Append a short PocketJS animated end card to a local video (screen recording, demo capture, phone clip). Preserves the full source before transitioning to the dark brand card with the logo, wordmark, landing-page VT323 headline and pocketjs.dev. Use when asked to add an outro / end card / 片尾, brand a recording, or produce a shareable PocketJS clip.

Ir a la instalación

Datos de origen

Repositorio
pocket-nexus/pocketjs
Última actividad en el origen
18 de septiembre de 2026 a las 15:31
Idioma detectado de SKILL.md
inglés
Estrellas
1632
Forks
91

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
5 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
pocketjs-video-outro
description
Append a short PocketJS animated end card to a local video (screen recording, demo capture, phone clip). Preserves the full source before transitioning to the dark brand card with the logo, wordmark, landing-page VT323 headline and pocketjs.dev. Use when asked to add an outro / end card / 片尾, brand a recording, or produce a shareable PocketJS clip.
# PocketJS Video Outro ## Overview Turns any local video into a shareable clip that ends on the PocketJS brand card. The card uses the current landing-page treatment: a dark `#171226` field, faint blueprint grid, yellow/pink corner glows, the lens/viewfinder logo glyph, the wordmark, and the uppercase VT323 headline. The default positioning is rendered on three deliberate lines: `UI FOR` / `EVERY KIND OF` / `COMPUTER`. The exact VT323 font file is bundled with the skill, so card rendering does not depend on a network font request. Headless Chrome renders the layers, then `ffmpeg` appends the card and animates the text in. **The default ending adds 1.5 seconds after the full source, including a 0.35-second crossfade from its held final frame.** Use this ending unless the user requests another duration; `--outro` and `--xfade` remain available for that choice. Design choices baked into the pipeline: - **Preserve the source ending.** Play every source frame before starting the transition. Hold the final frame during the appended crossfade; do not dim or consume the source's last moments to make room for the card. - **Crossfade first, text second.** The held final frame dissolves into the *empty* branded background; the type only starts animating once the transition has settled, so it never fights the crossfade. - **Staggered entrance.** Logo → tagline → URL start 0.12 seconds apart after the crossfade. Fades last 0.25 / 0.25 / 0.20 seconds, with a 12–20px rise at 1080p. The default card settles at 0.79 seconds and holds for 0.71 seconds. Short custom cards compress the entrance to leave at least half the time after the transition for the complete lockup. - **Landing-page headline.** The positioning uses the same VT323 face, uppercase transform, `0.92` line height and `0.005em` tracking as the site hero. Preserve explicit line breaks instead of letting the browser choose the default lockup. - **Audio is the source's, never synthesized.** The card is silent; the original track is preserved and faded out by the original source's end (no voiceover). - **Shareable SDR output.** HLG/PQ phone footage is perceptually tone-mapped to BT.709 before compositing, and the browser-rendered sRGB card is converted into the same color space. This avoids mixing an SDR card directly into HDR code values. ## Requirements - `ffmpeg` / `ffprobe` on PATH. HDR inputs require FFmpeg 8+ for swscale's transfer/primaries conversion and perceptual tone mapping; SDR inputs do not. - A Chromium-family browser (Google Chrome, Chromium, Edge, or Brave) — used only to screenshot the card layers. The script auto-detects it. ## Standard workflow One Bun command produces the finished file (the driver is Bun TypeScript — this repo keeps command wrappers in Bun, not shell scripts): ```bash bun skills/pocketjs-video-outro/scripts/make-outro.ts -i ~/Downloads/clip.mov # writes ~/Downloads/clip_outro.mp4 (H.264 high, yuv420p, +faststart, AAC 192k) # default card: UI FOR / EVERY KIND OF / COMPUTER # adds 1.5 seconds after the full source, including a 0.35-second transition # prints the output path on stdout; progress/summary on stderr ``` If the user supplies different positioning, preserve that copy exactly. Pass explicit line breaks when the lockup requires them; the template keeps them: ```bash bun skills/pocketjs-video-outro/scripts/make-outro.ts \ -i ~/Downloads/clip.mov \ --tagline $'UI runtime for\nevery kind of\ncomputer' ``` For an X upload, enable the compatibility mode. It produces 30 fps CFR video, uses a conventional 30 kHz track timebase, closes GOPs, caps bitrate, and scales landscape/portrait footage within 1920x1080 or 1080x1900: ```bash bun skills/pocketjs-video-outro/scripts/make-outro.ts -i ~/Downloads/clip.mov --x # writes ~/Downloads/clip_outro_x.mp4, leaving a standard outro export untouched ``` For X-mode verification, both reported rates must be `30/1`, the timebase must be `1/30000`, and the dimensions must stay within the orientation-aware 1080p bounds: ```bash ffprobe -v error -select_streams v:0 \ -show_entries stream=width,height,r_frame_rate,avg_frame_rate,time_base \ -of default=nw=1 ~/Downloads/clip_outro_x.mp4 ``` Do not report a finished video from command success alone. Verify five independent properties: full-file decode, delivery metadata, the source ending, the visible card and its entrance, and body-versus-tail audio. Choose a body sample that contains source sound. The render log gives the crossfade offset, which must match the source duration. Subtract it from the final probed duration to check that the default ending adds 1.5 seconds, allowing one frame for rounding. A ten-second source produces about 11.5 seconds in total. ```bash OUTRO_VIDEO=~/Downloads/clip_outro.mp4 ffmpeg -v error -i "$OUTRO_VIDEO" -f null - # full decode ffprobe -v error -select_streams v:0 \ -show_entries \ stream=codec_name,profile,width,height,pix_fmt,r_frame_rate,avg_frame_rate,time_base,color_range,color_space,color_transfer,color_primaries \ -of default=nw=1 "$OUTRO_VIDEO" ffmpeg -v error -y -sseof -0.6 -i "$OUTRO_VIDEO" \ -frames:v 1 /tmp/pocketjs-outro-final.png ffmpeg -v error -y -sseof -3 -i "$OUTRO_VIDEO" \ -vf 'fps=8,scale=240:-2,tile=6x4' -frames:v 1 /tmp/pocketjs-outro-motion.jpg ffmpeg -hide_banner -ss 2 -t 3 -i "$OUTRO_VIDEO" \ -af volumedetect -f null - 2>&1 | rg 'mean_volume|max_volume' ffmpeg -hide_banner -sseof -0.7 -i "$OUTRO_VIDEO" \ -af volumedetect -f null - 2>&1 | rg 'mean_volume|max_volume' ``` Inspect both images. The final frame must use the bundled VT323 face and show the requested copy without clipping; the motion sheet must show the source at full brightness through its end, followed by the crossfade and the logo → headline → URL stagger. Compare the source's last frames against the export before the transition; for HDR, apply the same tone mapping to the reference. HDR inputs must finish as `tv`, `yuv420p`, and `bt709/bt709/bt709`. The body sample must retain audio and the final 0.7 seconds must be effectively silent with the defaults. For a shorter custom card, sample only the tail after the original source has ended. ## Options | Flag | Default | Purpose | |------|---------|---------| | `-i` / `--input` | — (required) | input video | | `-o` / `--output` | `<input>_outro.mp4` next to input | output path (`_outro_x.mp4` with `--x`) | | `--tagline` | `UI for` / `every kind of` / `computer` | hero line; explicit newlines are preserved | | `--brand` | `PocketJS` | wordmark next to the glyph | | `--url` | `pocketjs.dev` | footer line; pass `--url ""` to hide it | | `--outro` | `1.5` | appended end-card length in seconds, including the transition | | `--xfade` | `0.35` | crossfade length; must be shorter than the card; `0` cuts to it | | `--crf` / `--preset` | `18` / `medium` | x264 quality/speed | | `--x` / `--x-compatible` | off | emit an X-safe 30fps CFR social upload | ## How it adapts to the input - Probes width/height/fps/duration; VFR inputs use `avg_frame_rate` rather than treating `r_frame_rate` as the real cadence. The selected rational rate is passed directly to FFmpeg, avoiding floating-point timebases. - By default the card uses the source's native resolution and selected frame rate. `--x` switches to 30 fps CFR and orientation-aware 1920x1080/1080x1900 bounds. - The crossfade starts at the source duration, using cloned final frames for its full requested length even when the source is shorter than the transition. - Display-matrix rotation is applied to the probed dimensions before rendering the card, matching FFmpeg's default autorotation for portrait phone footage. - **Color:** SDR inputs keep the existing path. HLG and PQ inputs (including the HLG base layer in iPhone Dolby Vision clips) are tone-mapped to 8-bit BT.709 SDR; Dolby Vision metadata is intentionally not carried into the shareable H.264 file. - **Type scales** with `scale = min(W,H)/1080`, so 720p, 1080p, and 4K all look proportional. The default headline keeps its three authored lines; custom copy preserves explicit newlines and still wraps before the frame edge. - **Audio:** maps the source's *first* audio stream (`0:a:0`) and downmixes to stereo. This is deliberate — iPhone `.mov` captures carry an extra multi-channel spatial-audio track plus several data streams; `a:0` is the standard stereo mix. If the source has no audio, the output is video-only. ## Publishing a video on pocketjs.dev There is no separate upload step — videos ship with the site deploy as static Worker assets (Cloudflare, `site/wrangler.jsonc`; keep each file well under the 25 MiB per-asset limit): 1. Commit the mp4 into git at `site/assets/<name>.mp4` (existing examples: `pocketjs-hardware-demo.mp4`, `pocketjs-demo-wall.mp4`). 2. Add a `copy(SITE + "assets/<name>.mp4", "assets/<name>.mp4")` line in `site/build.ts` step 4, next to the other mp4 copies. Files under `site/assets/blog/` (e.g. poster frames) are directory-copied automatically and need no explicit line. 3. Embed with a raw `<video>` tag in the page or post markdown. House classes: `class="w-full rounded-xl border border-line"`. Silent loops use `autoplay muted loop playsinline`; anything with a soundtrack must use `controls playsinline preload="metadata"` plus a `poster` (browsers block un-muted autoplay). Bake the poster with `ffmpeg -ss <t> -i in.mp4 -frames:v 1 -q:v 3 site/assets/blog/<name>-poster.jpg`. 4. Verify locally with `bun run site:build`, then merge to main — `.github/workflows/deploy.yml` runs `site:build` and `bunx wrangler deploy -c site/wrangler.jsonc`. Manual deploy is those same two commands. ## Customization & internals - The card is `assets/outro.html`, parameterized via query string (`?layer=…&scale=…&brand=…&tagline=…&url=…`). Edit it to restyle; every dimension is in `rem` and the script sets root font-size to `10px * scale`. The landing headline face and its OFL licence are `assets/VT323-Regular.ttf` and `assets/OFL-VT323.txt`; keep the template local-font path intact. - Entrance/animation is entirely in `ffmpeg`, orchestrated by `scripts/make-outro.ts` (Bun TypeScript, `import { $ } from "bun"`): each element is screenshotted as its **own transparent layer** with the *others kept in place via `visibility: hidden`* (so absolute positions never shift), then composited with per-layer `fade` (alpha) + `overlay` (ease-out slide). Change slide distances, stagger, or easing there. - Keep new wrappers/tooling for this skill in Bun TypeScript — do not add `.sh` scripts (repo convention; see also `pocketjs-gba-imagegen`). - To preview just the card without a video, screenshot the template directly: `"<chrome>" --headless --screenshot=card.png --window-size=1920,1080 "file://$PWD/skills/pocketjs-video-outro/assets/outro.html"`. ## Gotchas - Chrome's headless screenshot honors `--force-device-scale-factor=1` and `--default-background-color=00000000`; the text layers rely on that alpha to composite. Don't drop those flags. - Keep the output `yuv420p` + `+faststart` (already set) — some players choke on 4:4:4 or non-faststart MP4s. - `xfade` needs both sides normalized to identical size/fps/sar/pix_fmt; the graph does this. If you feed a variable-frame-rate capture, the `fps` filter conforms it. - ReplayKit and other VFR captures can report a high `r_frame_rate` that represents their smallest frame interval, not their real cadence. Keep average-rate priority and the original rational expression when changing probe logic. - Do not remove the explicit sRGB-to-BT.709 conversion from the card layers when changing HDR handling. Retagging Chrome's SDR PNG values as HLG/PQ is not a valid color conversion and makes the card shift on an HDR-aware display. - The tagline on a very wide single line can approach the frame edge; it has `max-width: 92vw` and will wrap before overflowing.
Ver en GitHub