@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.
- package/AGENTS.md +39 -17
- package/README.md +7 -3
- package/SKILL.md +0 -2
- package/docs/edit-mode.md +1 -1
- package/docs/label-mode.md +6 -4
- package/docs/markdown-descriptive.md +34 -1
- package/docs/system-prompt-edit.md +16 -0
- package/package.json +1 -1
- package/src/config.mjs +8 -3
- package/src/descriptive/compiler.test.ts +42 -42
- package/src/descriptive/compiler.ts +41 -52
- package/src/descriptive/markdown.ts +8 -4
- package/src/descriptive/resolve.test.ts +14 -7
- package/src/descriptive/resolve.ts +148 -20
- package/src/player/browser.tsx +178 -54
- package/src/player/bundle/player.js +1168 -566
- package/src/player/components/EditControls.tsx +92 -0
- package/src/player/components/HeaderBar.tsx +60 -0
- package/src/player/components/LabelControls.tsx +367 -0
- package/src/player/components/SceneThumbnails.tsx +87 -0
- package/src/player/components/VariantBar.tsx +39 -0
- package/src/player/components/index.ts +5 -0
- package/src/player/pipeline.mjs +130 -66
- package/src/player/pipeline.ts +3 -1
- package/src/player/server-shared.mjs +5 -7
- package/src/player/server.mjs +194 -187
- package/src/render/cli.mjs +4 -6
- package/src/schema/index.ts +20 -23
- package/src/types/Audio.tsx +25 -33
- package/src/types/Component.tsx +18 -24
- package/src/types/Effect.tsx +31 -39
- package/src/types/Image.tsx +23 -30
- package/src/types/Include.tsx +70 -76
- package/src/types/Map.tsx +48 -44
- package/src/types/Rhythm.tsx +19 -27
- package/src/types/Video.tsx +40 -47
- package/src/utils/index.ts +23 -10
- package/src/vision/cli.mjs +6 -6
- package/templates/courseware/TEMPLATE.md +16 -10
- package/tests/fixtures/audio.json +4 -2
- package/tests/fixtures/basic.json +2 -1
- package/tests/fixtures/component-all.json +4 -2
- package/tests/fixtures/components.json +4 -2
- package/tests/fixtures/effects.json +12 -6
- package/tests/fixtures/full.json +8 -4
- package/tests/fixtures/map.json +2 -1
- package/tests/fixtures/md/dialogue.md +12 -0
- package/tests/fixtures/md/edge-cases.md +9 -0
- package/tests/fixtures/scenes.json +4 -2
- package/tests/fixtures/subtitle.json +6 -3
- package/tests/fixtures/subvideo.json +11 -6
- package/tests/fixtures/templates/courseware.md +61 -4
- package/tests/fixtures/tween-visual.json +2 -6
- package/tests/fixtures/video-series.json +6 -14
- package/tests/md-descriptive.test.ts +170 -0
- package/tests/render.test.ts +32 -16
- package/tests/schema.test.ts +6 -3
- package/tests/server.test.ts +9 -6
- package/tests/utils.ts +4 -4
- package/scripts/artlist-dl.mjs +0 -190
- package/src/player/label-server.mjs +0 -599
package/scripts/artlist-dl.mjs
DELETED
|
@@ -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
|
-
}
|