Commit Graph

28 Commits

Author SHA1 Message Date
mikkeli 10949dfa18 style(274): retune the dark transect palette
Colour choices for the dark palette, made while previewing in the Studio:
- roadCovered #9dcde4 -> #fdb021 (the covered road now reads amber)
- bridge #79d1c5 -> #9be9de
- place / railway #129252 -> #68ad7f
- shrine #b32c2c -> #a05050
- other #ff9900 -> #a7b7d6

The light palette is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
2026-09-17 10:32:20 +09:00
mikkeli 561c930620 fix(274): separate nodes that share exact coordinates
Three pairs of sung nodes have identical roster coordinates, so one circle fully
eclipsed the other: 草木橋 / 滝の上こ線橋, ポン久留喜橋 / 久留喜橋, and
モトツ橋 / モトツシェルター (a bridge roofed by a shelter). Editing the lyrics
index's lat/lon could not fix it: the video takes positions from the roster
(274-lookup.csv), joined on object.

- layout.ts: nodePositions() groups nodes by exact coordinate and spreads each
  group COINCIDENT_SPREAD (1.5 map units, about 35 m) apart along the road's
  direction there, in road order, centred on the shared point. All such pairs
  are on-road structures, so they stay on the road (within 0.06 units) and
  read as a doubled dot; partial overlap is intended, full eclipse is not.
  Along rather than across the road, and small: at 6 units モトツシェルター
  landed on 福山トンネル, 90 m further on.
- data.ts: RosterItem carries the roster's km, for road order and direction.
- Transect.tsx: draws nodes at those positions.

Verified: eslint + tsc clean on this commit's tree; close-ups at モトツ, resting
and lit, show both nodes on the road in order after モトツトンネル.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
2026-09-17 10:32:19 +09:00
mikkeli 04963c929a feat(274): spectrum outro for the transect
From SPECTRUM_START_S the nodes stop following the lyric and behave as a
spectrum analyser: every sung node is given one log-spaced frequency band
(40 Hz–12 kHz), dealt out in a seeded shuffle, and sizes up and down with that
band's amplitude.

- spectrum.ts: band maths and every tunable. Levels come from
  @remotion/media-utils visualizeAudio on audio loaded by
  useWindowedAudioData (10 s windows, so the 227 MB WAV is never decoded
  whole; before the outro the window stays parked at its start). Bands average
  the FFT bins they span, or interpolate for bands narrower than a bin, so
  neighbouring bass nodes never move in lockstep. Level =
  (raw × GAIN × (f/40 Hz)^TREBLE_TILT)^CURVE with a release that looks back
  RELEASE_FRAMES frames at RELEASE_DECAY per frame. Defaults were calibrated
  on the release2 outro (nodes pinned at full size 0.6% of the time). The
  node→band mapping uses Remotion's seeded random() (BAND_SEED), not
  Math.random(), so render tabs and the Studio agree.
- nodeAnimation.ts: SPECTRUM_PEAK and spectrumLook(level), alongside lightUp
  and switchOff, plus a levelExponential shaping curve.
- Transect.tsx: hands each node over from its lyric look to its spectrum look
  over SPECTRUM_FADE_S.
- package.json: declare @remotion/media-utils 4.0.522 (already installed as a
  dependency of the CLI; now pinned alongside the other Remotion packages).

Verified: eslint + tsc clean on this commit's tree; outro stills show nodes
sized by band level, so the windowed audio loads during renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
2026-09-17 10:31:58 +09:00
mikkeli fac282bcbc feat(274): show the east stub ahead of time; slow its paint run
- Transect.tsx: the dimmed "road ahead" now includes the east continuation
  (274-route-east.json), so it is visible all along instead of appearing only
  once the paint reaches it after 東に続く.
- progress.ts: EAST_RUN_S 4 -> 75. The paint used to dash along the east stub
  (5.25 km in 4 s, about 1.3 km/s, after 0.55 km/s for the leg before it); it now
  takes 75 s from 東に続く, well into the outro.

Verified: eslint + tsc clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
2026-09-17 09:56:16 +09:00
mikkeli c965fbf5fe feat(274): route data for the transect; lyric fixes the transect exposed
Building the transect visualisation (Song274Transect) is what exposed problems
in the lyric and its index: syncing nodes to the lyric showed 紅葉山橋 lighting
up although it is not on this road (it is on R452), 下福山橋 and オロロップ橋
sitting at the wrong km, and lines timed against the wrong stretch. They were
fixed upstream in the wiki (ncmr-songs 7af0f48, "274: drop 紅葉山橋, fix
オロロップ橋 and 下福山橋, re-timed lyric"), and the lyric deliverables here
follow.

274.lrc — byte-identical copy of the wiki LRC at 7af0f48: 紅葉山橋 removed;
  紅楓橋 through 楓信号場 re-timed (1:35.24–1:55.54) with 夕張市楓 at 1:45.77;
  登川トンネル at 2:05.23 and its section end at 2:06.62; 稲里トンネル's section
  end at 4:25.27. 下福山橋 and オロロップ橋 keep their timing; their km were
  corrected in the roster and index.
274.srt — regenerated with scripts/lrc2srt.py, 116 cues.
274.ass — events follow the LRC: district headers as transparent_title spanning
  their section, 東に続く as transparent_lyrics_jp, spacers as empty events, the
  final line held 5 s. The Aegisub header and all 17 styles are untouched.
  117 events -> 116.

route/ — copied from novoyuuparosk-wiki/ncmr-songs wip/274, where it is
generated; read-only here, and its README says so. src/song-274/data.ts loads
274-route.json, 274-lookup.csv and 274-lyrics-index.csv, plus the two approach
stubs: 274-route-west.json (always drawn as covered) and 274-route-east.json
(painted after 東に続く). Also carries the district bands, the GeoJSON and the
reference page. The route shapes are single clean polylines: no
second-carriageway points in the divided sections, km strictly increasing.

Verified: all 116 ASS events and SRT cues match the LRC's order, text and
timestamps; every sung node in the index joins to the roster at matching km.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
2026-09-17 09:48:51 +09:00
mikkeli 5ad82b1b98 chore(274): record the release2 master in the BOM
274_release2.wav (2026-09-17) supersedes 274_release1.wav and is the master
used by the white/lyrics renders and Song274Transect. Same length, 10:19.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 09:44:46 +09:00
mikkeli bc39455cfe feat(274): transect composition synced to the lyric
Song274Transect draws Route 274 in its true shape (the "Ground" view of
song-274/route/274-transect.html) and plays it along with the song.

- data.ts: reads song-274/route/ at load time (lyrics index, roster, route and
  the optional east/west stubs); nothing is copied into the code. Only sung
  nodes are shown; districts and 東に続く light nothing.
- progress.ts: a "covered so far" paint over the dimmed road. Its head is pinned
  to each sung object's km as that line starts and glides between pins on a
  monotone cubic, so it never runs backwards. After 東に続く it runs out along
  the east stub; the west stub is always painted.
- camera.ts: fixed close zoom during each narration part (lines under 20 s
  apart), following a Gaussian-smoothed head; pull back to frame the covered
  road plus some road ahead during breaks, and to the full map after the lyric.
- nodeAnimation.ts: lightUp / switchOff as reusable functions over a NodeLook,
  with long lines switching off after MAX_LIT_S.
- palette.ts: every colour as a named token (dark default, light alongside),
  including shrine and railway families.

remotion.config.ts sets song-274/ as the public dir so staticFile() reaches the
song's audio/ and route/ in place.

Verified: eslint + tsc clean; stills checked across parts, breaks and the ending.
Render with --muted and add audio with ffmpeg: Remotion's own AAC lands 42.75 ms
late.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
2026-09-17 09:40:53 +09:00
mikkeli 7da9a6df25 feat(274): add lyrics, subtitles and asset BOM
Text-based deliverables for the 274 video, synced to the lyrics source of
truth in the wiki repo (ncmr-songs wip/274/274.lrc at c7e2494, "put the lyric
back into road order").

- 274.lrc: byte-identical copy of the wiki LRC.
- 274.srt: generated from it with scripts/lrc2srt.py (117 cues).
- 274.ass: hand-styled in Aegisub; every event re-timed to the LRC, with each
  address shown from its own timestamp until its section's last place ends.
- BOM.md: untracked assets with BLAKE2b hashes — release audio
  274_release1.wav, the white background plate, and the Noto Serif JP
  Medium/SemiBold fonts the ASS uses.

Verified: all 117 ASS events and SRT cues match the LRC's order, text and
timestamps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLt25nCVMsoroKALYUBu6u
2026-09-16 19:22:50 +09:00
irene be81469c7c chore: ignore package-lock.json, and record why
Work happens on both Windows and Linux, so a committed lockfile would do harm
rather than good here:

- Every dependency in package.json is pinned exactly (no ^ or ~), so a lockfile
  has no floating ranges to guard. A plain `npm install` resolves identically
  without it.
- The lock records platform-specific optional binaries for whichever OS built it
  (@rollup/rollup-linux-x64-gnu vs -win32-x64-msvc, for instance). Committed from
  one platform it is wrong on the other, and `npm ci` refuses rather than
  adapting, since reproducing the recorded tree exactly is its entire purpose.

That is a strict mechanism pointed at the wrong target: it would break installs
across machines while guaranteeing versions that were already exact. Cloning is
rare here, so the reproducibility upside is small and the cross-platform cost is
immediate.

Both .gitignore and README now state the reasoning, including when to revisit (a
dependency loosened to a range, or transitive drift exact pinning cannot control).
Silently ignoring it would have invited the same question again.

Verified: `git check-ignore -v package-lock.json` matches, and a regenerated
lockfile leaves `git status` clean.
2026-09-10 23:11:25 +09:00
irene 4bc2346480 docs: note what the archive tag is for
Records why archive/feat-bom-generator exists and what was in it, so a future
reader does not have to reconstruct the reasoning from the branch name. Also
notes that archive tags need an explicit push.
2026-09-10 18:41:01 +09:00
irene 2c6e227c82 feat(bom): add --hash, and fix the BOM marker breaking YAML
`feat/bom-generator` carried one commit that never landed on master: file
content hashes in the BOM ("v2 with hashes"). The Python rewrite dropped that
capability, so port it forward rather than losing it when the branch goes.

- `--hash` records a short BLAKE2b digest per file. Opt-in, because it reads
  every asset in full and that is wasted work for a plain inventory.
- Hashes are streamed in chunks with `hashlib.blake2b`, not `md5` via haveged
  imports, and not whole-file reads — these assets run to hundreds of megabytes.
- YAML gains `hash:` after `size:`; Markdown gains a hash column only when
  hashes were requested, so the common table stays readable.

Fix a pre-existing bug found while testing: the BOM marker was written as
`; generated by ...`, but `;` is not a YAML comment, so every generated BOM
failed to parse with "mapping values are not allowed here". Now `#`. Verified by
re-parsing output from both the PyYAML path and the built-in fallback writer.

Sizes alone cannot tell you whether an asset changed; a same-length re-render
keeps the byte count identical. That is the gap the hash closes.
2026-09-10 18:37:18 +09:00
irene afe397da3f refactor: draw the src/scripts boundary, drop dead files
Make each directory answerable by path alone: src/ is what the Remotion build
compiles, scripts/ is what you run by hand.

Removed:
- `ffmpeg.sh` — never worked; every parameter was assigned `$1`, so bake/bypass
  were broken from the start. Superseded by `scripts/ffmpeg_tools.py bake|still`.
- `src/lrc2srt.ts` — the TypeScript port; `scripts/lrc2srt.py` is the script that
  was actually maintained, and it now handles the multi-timestamp cases the TS
  version did.
- `scripts/bom-gen.ts` — superseded by `scripts/bom_gen.py`.
- `scripts/ffmpegusage.txt` — legacy command notebook. Every recipe in it is now
  reproduced by `ffmpeg_tools.py`, including the `-map 0:v:0 -map 1:a:0 -c:v copy`
  mux and the FLAC `-compression_level 12` case, so it has no unique content left.

Also drops the `bom-gen` alias from the dispatcher (dash-case was TypeScript
naming) and documents the boundary in README.md and REMOTION-QUICKSTART.md.

`npm run lint` (eslint src && tsc) passes with src/ reduced to Remotion input.
2026-09-10 18:18:04 +09:00
irene a1fbd64f87 docs: note master is not locked in yet
The repo is still being assembled as a templated workspace, so the branch
conventions are the intended end state rather than a current rule. Recording
that in the README so the 'master is protected and immutable' line is not read
as an active restriction while the template is still taking shape.
2026-09-10 17:50:59 +09:00
irene 68c12caef9 feat(scripts): unified Python toolchain, stdlib only
Replaces the scattered TypeScript/shell helpers with one Python toolchain that
runs as plain files (`python3 scripts/tools.py <tool> ...`) with no third-party
dependencies.

New tools
- `_common.py`     shared plumbing (Report, run, ffmpeg discovery, argparse ergonomics)
- `hwprobe.py`     what this machine can actually encode (software + hardware)
- `srt2ass.py`     SRT -> ASS, the missing step between lrc2srt and baking
- `ffmpeg_tools.py` mux / bake / still / strip / probe, with hardware encode
- `bom_gen.py`     BOM generator, ported from scripts/bom-gen.ts
- `tools.py`       dispatcher so any tool is one command away
- `README.md`      usage for all of the above

Rewritten
- `lrc2srt.py` now handles multiple timestamps per line, timestamp-less cue lines
  and blank/comment lines, and survives malformed input. The legacy version
  emitted an empty cue plus a stray cue for every line.

Hardware encoding
This host is AMD (Strix Halo, VCN 4.0.5), so nvenc in the legacy notes is
reference only. `hwprobe.py` probes and reports what genuinely works here:
VA-API via /dev/dri/renderD128 gives h264_vaapi, hevc_vaapi and av1_vaapi.
`ffmpeg_tools.py --video auto` picks the hardware encoder when the render node
is usable and falls back to libx264/libx265 when it is not.

Two bugs found and fixed while wiring this up:
- the VA-API capability probe fed frames straight to the encoder without
  `format=nv12,hwupload`, so ffmpeg failed with "Function not implemented" and
  the toolchain silently fell back to software on a machine where hardware
  works. VA-API encoders only accept GPU-resident frames; the probe now uploads.
- `bake --video copy` only failed when the copied stream happened to be VA-API.
  Burning subtitles always changes pixels, so it now refuses `copy` outright
  with a clear message.

Also makes -q/-n usable on either side of the mode word, and ignores
__pycache__ in .gitignore.
2026-09-10 17:47:02 +09:00
mikkeli 51b878486f added legacy scripts as reference 2026-09-10 07:41:07 +00:00
irene 78e8a7decf merge: feat/bom-generator into master 2026-09-09 17:53:11 +09:00
irene 475783556f feat: add BOM generator script 2026-09-09 17:50:46 +09:00
irene a7823f1747 docs: add port pinning option to quickstart 2026-09-09 17:46:12 +09:00
irene 18de75ee28 docs: note port auto-increment in quickstart 2026-09-09 17:45:42 +09:00
irene edb5f85a75 merge: init/remotion-template into master 2026-09-09 17:45:04 +09:00
irene d25e693a4a docs: add Remotion quickstart guide 2026-09-09 17:38:21 +09:00
irene 74b70967a1 feat: add Remotion project scaffolding and subtitle tools
Remotion project:
- package.json with Remotion 4.0.522, React 19, Tailwind v4
- tsconfig.json (strict, ES2018, Bundler module resolution)
- remotion.config.ts (rspack, jpeg image format)
- ESLint flat config, Prettier config
- Root component and MyComposition placeholder (1280x720, 30fps, 60 frames)

Subtitle tools:
- src/lrc2srt.ts — LRC to SRT converter (minimal, no dependencies)
- ffmpeg.sh — helper for baking ASS subtitles or static-image+audio bypass
2026-09-09 17:35:35 +09:00
irene 0de9c41c7d docs: add .gitignore for binary assets and output files 2026-09-09 17:27:28 +09:00
irene 191077af1c docs: add .gitignore for binary assets and output files 2026-09-09 17:24:39 +09:00
irene c86afdc75a docs: add .gitignore for binary assets and video output 2026-09-09 17:24:21 +09:00
mikkeli b73e14cdd1 chore: fixed README.md link syntax error again 2026-09-09 08:00:36 +00:00
mikkeli dd5d1aaf12 chore: fixed README.md link syntax error 2026-09-09 08:00:16 +00:00
mikkeli f147ef8e4c initial commit
Added README.md
2026-09-09 07:56:53 +00:00