@lalalic/markcut 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +39 -17
  2. package/README.md +7 -3
  3. package/SKILL.md +0 -2
  4. package/docs/edit-mode.md +1 -1
  5. package/docs/label-mode.md +6 -4
  6. package/docs/markdown-descriptive.md +34 -1
  7. package/docs/system-prompt-edit.md +16 -0
  8. package/package.json +1 -1
  9. package/src/config.mjs +8 -3
  10. package/src/descriptive/compiler.test.ts +42 -42
  11. package/src/descriptive/compiler.ts +41 -52
  12. package/src/descriptive/markdown.ts +8 -4
  13. package/src/descriptive/resolve.test.ts +14 -7
  14. package/src/descriptive/resolve.ts +148 -20
  15. package/src/player/browser.tsx +178 -54
  16. package/src/player/bundle/player.js +1168 -566
  17. package/src/player/components/EditControls.tsx +92 -0
  18. package/src/player/components/HeaderBar.tsx +60 -0
  19. package/src/player/components/LabelControls.tsx +367 -0
  20. package/src/player/components/SceneThumbnails.tsx +87 -0
  21. package/src/player/components/VariantBar.tsx +39 -0
  22. package/src/player/components/index.ts +5 -0
  23. package/src/player/pipeline.mjs +130 -66
  24. package/src/player/pipeline.ts +3 -1
  25. package/src/player/server-shared.mjs +5 -7
  26. package/src/player/server.mjs +194 -187
  27. package/src/render/cli.mjs +4 -6
  28. package/src/schema/index.ts +20 -23
  29. package/src/types/Audio.tsx +25 -33
  30. package/src/types/Component.tsx +18 -24
  31. package/src/types/Effect.tsx +31 -39
  32. package/src/types/Image.tsx +23 -30
  33. package/src/types/Include.tsx +70 -76
  34. package/src/types/Map.tsx +48 -44
  35. package/src/types/Rhythm.tsx +19 -27
  36. package/src/types/Video.tsx +40 -47
  37. package/src/utils/index.ts +23 -10
  38. package/src/vision/cli.mjs +6 -6
  39. package/templates/courseware/TEMPLATE.md +16 -10
  40. package/tests/fixtures/audio.json +4 -2
  41. package/tests/fixtures/basic.json +2 -1
  42. package/tests/fixtures/component-all.json +4 -2
  43. package/tests/fixtures/components.json +4 -2
  44. package/tests/fixtures/effects.json +12 -6
  45. package/tests/fixtures/full.json +8 -4
  46. package/tests/fixtures/map.json +2 -1
  47. package/tests/fixtures/md/dialogue.md +12 -0
  48. package/tests/fixtures/md/edge-cases.md +9 -0
  49. package/tests/fixtures/scenes.json +4 -2
  50. package/tests/fixtures/subtitle.json +6 -3
  51. package/tests/fixtures/subvideo.json +11 -6
  52. package/tests/fixtures/templates/courseware.md +61 -4
  53. package/tests/fixtures/tween-visual.json +2 -6
  54. package/tests/fixtures/video-series.json +6 -14
  55. package/tests/md-descriptive.test.ts +170 -0
  56. package/tests/render.test.ts +32 -16
  57. package/tests/schema.test.ts +6 -3
  58. package/tests/server.test.ts +9 -6
  59. package/tests/utils.ts +4 -4
  60. package/scripts/artlist-dl.mjs +0 -190
  61. package/src/player/label-server.mjs +0 -599
@@ -1,190 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * artlist-dl — Download BGM/SFX from artlist.io CDN URLs or track pages.
4
- *
5
- * Usage:
6
- * node scripts/artlist-dl.mjs bgm <cdnUrl> <name> → public/assets/bgm/<name>.mp3
7
- * node scripts/artlist-dl.mjs sfx <cdnUrl> <name> [s] → public/assets/sfx/<name>.mp3 (trimmed to s seconds)
8
- * node scripts/artlist-dl.mjs grab <trackPageUrl> <name> [bgm|sfx] [s] → auto-extract CDN URL from track page
9
- * node scripts/artlist-dl.mjs list → show current assets
10
- *
11
- * CDN URLs are captured from artlist.io when playing a track (see SKILL.md).
12
- * Pattern: https://cms-public-artifacts.artlist.io/content/...
13
- *
14
- * The `grab` command scrapes the track page HTML for base64-encoded CDN paths,
15
- * no browser or login needed — works with just curl.
16
- */
17
- import { execSync } from "node:child_process";
18
- import { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
19
- import { resolve, dirname } from "node:path";
20
- import { fileURLToPath } from "node:url";
21
-
22
- const __dirname = dirname(fileURLToPath(import.meta.url));
23
- const ROOT = resolve(__dirname, "..");
24
-
25
- const BGM_DIR = resolve(ROOT, "public/assets/bgm");
26
- const SFX_DIR = resolve(ROOT, "public/assets/sfx");
27
-
28
- function ensureDir(dir) {
29
- mkdirSync(dir, { recursive: true });
30
- }
31
-
32
- function download(url, outPath, trimSeconds) {
33
- const tmpPath = resolve(ROOT, ".tmp", `dl-${Date.now()}.aac`);
34
- ensureDir(dirname(tmpPath));
35
- ensureDir(dirname(outPath));
36
-
37
- console.log(`⬇ Downloading: ${url.substring(0, 80)}...`);
38
- execSync(`curl -sL "${url}" -o "${tmpPath}"`, { stdio: "inherit" });
39
-
40
- const trimFlag = trimSeconds ? `-t ${trimSeconds}` : "";
41
- console.log(`🔄 Converting to MP3${trimSeconds ? ` (${trimSeconds}s)` : ""}...`);
42
- execSync(`ffmpeg -y -i "${tmpPath}" ${trimFlag} "${outPath}" 2>/dev/null`, { stdio: "inherit" });
43
-
44
- execSync(`rm "${tmpPath}"`);
45
-
46
- const size = statSync(outPath).size;
47
- console.log(`✅ ${outPath} (${(size / 1024).toFixed(1)} KB)`);
48
- }
49
-
50
- function list() {
51
- console.log("\n📁 BGM (public/assets/bgm/):");
52
- if (existsSync(BGM_DIR)) {
53
- for (const f of readdirSync(BGM_DIR)) {
54
- const size = statSync(resolve(BGM_DIR, f)).size;
55
- console.log(` 🎵 ${f} (${(size / 1024).toFixed(1)} KB)`);
56
- }
57
- }
58
- console.log("\n📁 SFX (public/assets/sfx/):");
59
- if (existsSync(SFX_DIR)) {
60
- for (const f of readdirSync(SFX_DIR)) {
61
- const size = statSync(resolve(SFX_DIR, f)).size;
62
- console.log(` 🔊 ${f} (${(size / 1024).toFixed(1)} KB)`);
63
- }
64
- }
65
- }
66
-
67
- function search(term, type = "sfx", count = 10) {
68
- const GRAPHQL = "https://search-api.artlist.io/v1/graphql";
69
- let query, variables;
70
-
71
- if (type === "bgm" || type === "music") {
72
- query = `query SongList($page: Int!, $songSortType: Int!, $take: Int!, $vocalMenuId: Int!, $searchTerm: String) {
73
- songList(page: $page, songSortType: $songSortType, take: $take, vocalMenuId: $vocalMenuId, searchTerm: $searchTerm) {
74
- songs { songId songName artistName duration sitePlayableFilePath nameForURL }
75
- }
76
- }`;
77
- variables = { page: 1, songSortType: 0, take: count, vocalMenuId: 0, searchTerm: term };
78
- } else {
79
- query = `query SfxList($categoryIds: String!, $page: Float!, $tags: String!, $term: String!, $sortBy: SfxListRequestSortByOptions!) {
80
- sfxList(categoryIds: $categoryIds, page: $page, tags: $tags, term: $term, sortBy: $sortBy) {
81
- songs { songId songName artistName durationTime sitePlayableFilePath nameForURL }
82
- }
83
- }`;
84
- variables = { categoryIds: "", page: 1, tags: "", term, sortBy: "STAFF_PICKS" };
85
- }
86
-
87
- const body = JSON.stringify({ query, variables });
88
- const result = execSync(
89
- `curl -s '${GRAPHQL}' -H 'Content-Type: application/json' -d '${body.replace(/'/g, "'\\''")}'`,
90
- { encoding: "utf-8" }
91
- );
92
- const data = JSON.parse(result);
93
- const songs = type === "bgm" || type === "music"
94
- ? data.data?.songList?.songs || []
95
- : data.data?.sfxList?.songs || [];
96
-
97
- if (!songs.length) {
98
- console.log(`No results for "${term}"`);
99
- return;
100
- }
101
-
102
- const typeLabel = type === "bgm" || type === "music" ? "Music" : "SFX";
103
- console.log(`\n🔍 ${typeLabel} results for "${term}" (${songs.length} tracks):\n`);
104
- for (const s of songs) {
105
- const dur = s.duration || `${s.durationTime}s`;
106
- const url = type === "bgm" || type === "music"
107
- ? `https://artlist.io/royalty-free-music/song/${s.nameForURL}/${s.songId}`
108
- : `https://artlist.io/sfx/track/${s.nameForURL}/${s.songId}`;
109
- console.log(` ${s.songId.padEnd(8)} ${s.songName} — ${s.artistName} (${dur})`);
110
- console.log(` ${url}`);
111
- }
112
- console.log(`\nTo download: node scripts/artlist-dl.mjs grab <url> <name> [bgm|sfx]`);
113
- }
114
-
115
- // CLI
116
- const [,, cmd, ...args] = process.argv;
117
-
118
- if (cmd === "list") {
119
- list();
120
- } else if (cmd === "search" && args.length >= 1) {
121
- const [term, type = "sfx", count] = args;
122
- search(term, type, count ? Number(count) : 10);
123
- } else if (cmd === "grab" && args.length >= 2) {
124
- const [trackUrl, name, type = "sfx", trim] = args;
125
- // Parse track ID and type from URL
126
- // SFX: artlist.io/sfx/track/.../81139 | Music: artlist.io/royalty-free-music/song/.../6000953
127
- const sfxMatch = trackUrl.match(/\/sfx\/track\/[^/]+\/(\d+)/);
128
- const musicMatch = trackUrl.match(/\/(?:royalty-free-music|song)\/[^/]+\/[^/]+\/(\d+)/);
129
- const trackId = sfxMatch?.[1] || musicMatch?.[1];
130
- const isSfx = !!sfxMatch || type === "sfx";
131
- const isMusic = !!musicMatch || type === "bgm";
132
-
133
- if (!trackId) {
134
- console.error("❌ Could not parse track ID from URL. Expected format:");
135
- console.error(" SFX: artlist.io/sfx/track/<name>/<id>");
136
- console.error(" Music: artlist.io/royalty-free-music/song/<name>/<id>");
137
- process.exit(1);
138
- }
139
-
140
- console.log(`🔍 Fetching ${isMusic ? "music" : "sfx"} track #${trackId} via GraphQL API...`);
141
- try {
142
- const query = isMusic
143
- ? `query Songs($ids: [String!]!) { songs(ids: $ids) { songId songName sitePlayableFilePath duration artistName } }`
144
- : `query Sfxs($ids: [Int!]!) { sfxs(ids: $ids) { songId songName sitePlayableFilePath duration artistName } }`;
145
- const variables = isMusic ? { ids: [trackId] } : { ids: [Number(trackId)] };
146
- const body = JSON.stringify({ query, variables });
147
-
148
- const result = execSync(
149
- `curl -s 'https://search-api.artlist.io/v1/graphql' -H 'Content-Type: application/json' -d '${body.replace(/'/g, "'\\''")}'`,
150
- { encoding: "utf-8" }
151
- );
152
- const data = JSON.parse(result);
153
- const track = isMusic ? data.data?.songs?.[0] : data.data?.sfxs?.[0];
154
-
155
- if (!track?.sitePlayableFilePath) {
156
- console.error("❌ Track not found or no audio URL available.");
157
- process.exit(1);
158
- }
159
-
160
- console.log(`✅ "${track.songName}" by ${track.artistName} (${track.duration})`);
161
- const cdnUrl = track.sitePlayableFilePath;
162
- const outDir = isMusic ? BGM_DIR : SFX_DIR;
163
- const trimSec = trim ? Number(trim) : (isSfx && !isMusic ? 3 : undefined);
164
- download(cdnUrl, resolve(outDir, `${name}.mp3`), trimSec);
165
- } catch (e) {
166
- console.error(`❌ Failed: ${e.message}`);
167
- process.exit(1);
168
- }
169
- } else if (cmd === "bgm" && args.length >= 2) {
170
- const [url, name] = args;
171
- download(url, resolve(BGM_DIR, `${name}.mp3`));
172
- } else if (cmd === "sfx" && args.length >= 2) {
173
- const [url, name, trim] = args;
174
- download(url, resolve(SFX_DIR, `${name}.mp3`), trim ? Number(trim) : 3);
175
- } else {
176
- console.log(`
177
- Usage:
178
- node scripts/artlist-dl.mjs search <term> [sfx|bgm] [count] Search artlist.io
179
- node scripts/artlist-dl.mjs grab <trackPageUrl> <name> [bgm|sfx] Download from track page URL
180
- node scripts/artlist-dl.mjs bgm <cdnUrl> <name> Download BGM from CDN URL
181
- node scripts/artlist-dl.mjs sfx <cdnUrl> <name> [sec] Download SFX from CDN URL (trim to sec)
182
- node scripts/artlist-dl.mjs list List current audio assets
183
-
184
- Examples:
185
- node scripts/artlist-dl.mjs search "whoosh" sfx
186
- node scripts/artlist-dl.mjs search "cinematic epic" bgm
187
- node scripts/artlist-dl.mjs grab https://artlist.io/sfx/track/sci-fi-transitions---powerful-whoosh/81139 whoosh sfx
188
- node scripts/artlist-dl.mjs grab https://artlist.io/royalty-free-music/song/dominion/6000953 dominion bgm
189
- `);
190
- }