nixamp 0.66.3 → 0.67.1
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/dist/ai-label.d.ts +10 -5
- package/dist/ai-label.js +102 -37
- package/dist/station-cli.js +1 -0
- package/dist/station-mcp.d.ts +15 -0
- package/dist/station-mcp.js +2 -1
- package/package.json +1 -1
- package/src/ai-label.ts +106 -36
- package/src/station-cli.ts +1 -0
- package/src/station-mcp.ts +2 -1
- package/web/dist/assets/{ar-BQfRAPxT.js → ar-DPe7Pp8y.js} +1 -1
- package/web/dist/assets/{cs-CIlyIUyM.js → cs-CJcjEsDV.js} +1 -1
- package/web/dist/assets/{da-B73sEH_l.js → da-Bd1GXRyz.js} +1 -1
- package/web/dist/assets/{de-CR_qS1kD.js → de-CLZfazCs.js} +1 -1
- package/web/dist/assets/{es-CWcxhMQ1.js → es-BLqEfqLZ.js} +1 -1
- package/web/dist/assets/{fi-sw-rgeRG.js → fi-anv-MFsC.js} +1 -1
- package/web/dist/assets/{fr-DWwCpFG-.js → fr-BzD2HDGv.js} +1 -1
- package/web/dist/assets/{hi-BDa2YD-x.js → hi-D71Abrr0.js} +1 -1
- package/web/dist/assets/{hu-CsYXXp0t.js → hu-BmLNPlTb.js} +1 -1
- package/web/dist/assets/{id-CzrnSbAL.js → id-D804zCDF.js} +1 -1
- package/web/dist/assets/{it-DT6eQX_8.js → it-BAuY8mMB.js} +1 -1
- package/web/dist/assets/{main-B-WZ8VbE.js → main-DbQrVbPR.js} +2 -2
- package/web/dist/assets/{nl-C57z9Sux.js → nl-D8x7GCIF.js} +1 -1
- package/web/dist/assets/{ru-CW9If0BU.js → ru-Ci9fQdcv.js} +1 -1
- package/web/dist/assets/{sv-CiW_iXWQ.js → sv-CZtgl4E6.js} +1 -1
- package/web/dist/assets/{uk-D_agLCru.js → uk-VhX0Jvpy.js} +1 -1
- package/web/dist/assets/{vi-C4wrS-qx.js → vi-TdUCiab8.js} +1 -1
- package/web/dist/assets/{zh-TOIwUGfV.js → zh-BMJXXIBE.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/dist/sw.js +19 -19
package/dist/ai-label.d.ts
CHANGED
|
@@ -90,8 +90,10 @@ export interface LabelSettings {
|
|
|
90
90
|
videos: boolean;
|
|
91
91
|
/** Each album's single (song 1) is filmed with Veo, while the month's Veo budget lasts. */
|
|
92
92
|
veo: boolean;
|
|
93
|
-
/** How many 8 s Veo shots of the band a single is cut from. */
|
|
93
|
+
/** How many 8 s Veo shots of the band playing (never the singer) a single is cut from. */
|
|
94
94
|
veoShots: number;
|
|
95
|
+
/** How many 8 s Veo story scenes (a short film from the song's words, nobody singing) a single gets. */
|
|
96
|
+
storyShots: number;
|
|
95
97
|
/** How many close-ups of the singer a single gets, lip-synced to the song (0: none, the band only). */
|
|
96
98
|
lipsyncShots: number;
|
|
97
99
|
/** Public, listed and owned, so the genre stations are on the guide. */
|
|
@@ -166,6 +168,8 @@ export declare function syncLines(lyrics: string, timed: TimedWord[]): SyncedLin
|
|
|
166
168
|
export declare function writeStoryboard(artist: Pick<LabelArtist, "name" | "genre" | "sound"> & {
|
|
167
169
|
cast?: string;
|
|
168
170
|
}, album: Pick<LabelAlbum, "title" | "concept">, song: Pick<LabelSong, "title" | "style" | "lyrics">, scenes: number, ask?: typeof askJson): Promise<string[]>;
|
|
171
|
+
/** A single's story scenes for Veo: a short film from its words, nobody singing, people by look not name. */
|
|
172
|
+
export declare function writeVeoStory(artist: Pick<LabelArtist, "genre" | "sound">, members: Array<Pick<Member, "role" | "look">>, album: Pick<LabelAlbum, "title" | "concept">, song: Pick<LabelSong, "title" | "style" | "lyrics">, scenes: number, ask?: typeof askJson): Promise<string[]>;
|
|
169
173
|
/** Thrown by a Veo dependency when this month's budget is spent: no more shots until next month. */
|
|
170
174
|
export declare class VeoUnavailable extends Error {
|
|
171
175
|
}
|
|
@@ -203,11 +207,12 @@ export declare function closeUpTimes(seconds: number, lines: Array<{
|
|
|
203
207
|
start: number;
|
|
204
208
|
}>, count: number, length?: number): number[];
|
|
205
209
|
/**
|
|
206
|
-
* The version of a video cut from Veo footage:
|
|
207
|
-
* singer only in close-ups lip-synced to the song. Anything older
|
|
208
|
-
*
|
|
210
|
+
* The version of a video cut from Veo footage: story scenes and the band playing with
|
|
211
|
+
* nobody singing, the singer only in close-ups lip-synced to the song. Anything older
|
|
212
|
+
* is not shown: a slideshow, 4 (singers out of sync) or 5 (all stage, the singer at
|
|
213
|
+
* the mic in the wide shots).
|
|
209
214
|
*/
|
|
210
|
-
export declare const FILMED =
|
|
215
|
+
export declare const FILMED = 6;
|
|
211
216
|
/**
|
|
212
217
|
* A single's music video, cut from its filmed shots alone: 4 s cuts, each
|
|
213
218
|
* shot's first half then its second, round and round the shots until the song
|
package/dist/ai-label.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { execFile } from "node:child_process";
|
|
30
30
|
import { randomBytes } from "node:crypto";
|
|
31
|
-
import { mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
32
32
|
import { dirname, join, resolve, sep } from "node:path";
|
|
33
33
|
import { aiKey } from "./ai-keys.js";
|
|
34
34
|
import { composerFromEnv, OutOfCredit } from "./ai-radio.js";
|
|
@@ -273,7 +273,8 @@ export const DEFAULTS = {
|
|
|
273
273
|
publish: true,
|
|
274
274
|
videos: true,
|
|
275
275
|
veo: false,
|
|
276
|
-
veoShots:
|
|
276
|
+
veoShots: 2,
|
|
277
|
+
storyShots: 4,
|
|
277
278
|
lipsyncShots: 2,
|
|
278
279
|
parallel: 1,
|
|
279
280
|
genreOrder: ["Punk", "Blues", "Country", "Jazz", "Classic Rock", "Metal", "Hip-Hop", "Pop", "R&B"],
|
|
@@ -489,6 +490,34 @@ export async function writeStoryboard(artist, album, song, scenes, ask = askJson
|
|
|
489
490
|
throw new Error("the model wrote no storyboard");
|
|
490
491
|
return prompts;
|
|
491
492
|
}
|
|
493
|
+
const VEO_STORY_SYSTEM = [
|
|
494
|
+
"You write the story scenes of a music video for an original song, as prompts for a video model that films 8-second moving shots.",
|
|
495
|
+
"Tell the song's story in cinematic scenes away from any stage: characters, places, action, weather, small details. Keep the same main character(s), setting and color palette across scenes so it reads as one short film; vary the shots (wide, tracking, close detail, aerial) and say how the camera moves.",
|
|
496
|
+
"Describe people by how they look, never by name. Nobody sings, speaks or lip-syncs, and nobody holds a microphone. Each prompt at most 350 characters. No text, letters, logos, real people or real brands.",
|
|
497
|
+
"Answer as JSON: {\"scenes\": [\"prompt\", ...]}.",
|
|
498
|
+
].join(" ");
|
|
499
|
+
/** A single's story scenes for Veo: a short film from its words, nobody singing, people by look not name. */
|
|
500
|
+
export async function writeVeoStory(artist, members, album, song, scenes, ask = askJson) {
|
|
501
|
+
const cast = members.slice(0, 4).map((one) => `${one.role}: ${one.look.replace(/\b[A-Z][a-z]+ [A-Z][a-z]+\b/g, "")}`).join("; ");
|
|
502
|
+
const answer = await ask({
|
|
503
|
+
system: VEO_STORY_SYSTEM,
|
|
504
|
+
prompt: [
|
|
505
|
+
`Song: "${song.title}" (${artist.genre}; ${artist.sound}), from "${album.title}"${album.concept ? `: ${album.concept}` : ""}`,
|
|
506
|
+
cast ? `The band may appear as characters, described by look: ${cast}` : "",
|
|
507
|
+
`Mood and arrangement: ${song.style}`,
|
|
508
|
+
song.lyrics ? `Lyrics:\n${song.lyrics.slice(0, 1800)}` : "",
|
|
509
|
+
`Exactly ${scenes} scenes.`,
|
|
510
|
+
].filter(Boolean).join("\n"),
|
|
511
|
+
maxTokens: 2500,
|
|
512
|
+
timeoutMs: 90_000,
|
|
513
|
+
});
|
|
514
|
+
const list = Array.isArray(answer.value["scenes"]) ? answer.value["scenes"] : [];
|
|
515
|
+
const prompts = list.map((one) => clean(one, 400)).filter((one) => one.length > 10).slice(0, scenes);
|
|
516
|
+
if (prompts.length < 1)
|
|
517
|
+
throw new Error("the model wrote no story");
|
|
518
|
+
return prompts.map((scene) => `Music video for a fictional ${artist.genre} song. ${scene} Mood: ${song.style}`.slice(0, 900)
|
|
519
|
+
+ " Nobody sings, speaks or lip-syncs on camera. Cinematic, 35mm film look. No text, no logos.");
|
|
520
|
+
}
|
|
492
521
|
/** Thrown by a Veo dependency when this month's budget is spent: no more shots until next month. */
|
|
493
522
|
export class VeoUnavailable extends Error {
|
|
494
523
|
}
|
|
@@ -498,14 +527,12 @@ const TRANSIENT = /\b50[234]\b|fetch failed|ECONN|timed? ?out/i;
|
|
|
498
527
|
// so a mouth in shot is out of sync. The band plays; the vocalist is far off, from
|
|
499
528
|
// behind, in silhouette or between lines, never a close-up of singing.
|
|
500
529
|
const SHOTS = [
|
|
501
|
-
"Wide shot of the whole band performing on a big stage at night, seen from the back of the crowd, the players small against the lights",
|
|
502
530
|
"Close-up of the guitarist's hands on the fretboard and the strings, shallow focus, stage lights flaring",
|
|
503
|
-
"The camera glides behind the band members as they play hard, seen from behind, looking out over the crowd",
|
|
504
531
|
"Low-angle shot of the drummer hitting the cymbals at the song's climax, sticks blurred, sparks of light",
|
|
505
|
-
"
|
|
506
|
-
"
|
|
507
|
-
"
|
|
508
|
-
"
|
|
532
|
+
"Close-up of the bass player's fingers and the amplifier, then a tilt up to the bassist in silhouette",
|
|
533
|
+
"The guitarist and the bassist seen from behind, playing hard toward a huge crowd, backlit",
|
|
534
|
+
"Slow push-in on the drummer in a quiet moment, moody light, sweat and smoke",
|
|
535
|
+
"Slow aerial push over the crowd toward the stage, fans with hands up, the stage tiny in the distance",
|
|
509
536
|
];
|
|
510
537
|
// The singer's close-ups, which are lip-synced to the song afterwards (fal.ai). The mouth
|
|
511
538
|
// must be clear: whatever covers it (a microphone, a hand) is smeared into the face when
|
|
@@ -525,7 +552,7 @@ export function veoPrompts(artist, song, members, count, kind = "band") {
|
|
|
525
552
|
const cast = members.slice(0, 3).map((one, i) => `the ${one.role} from reference image ${i + 1} (${one.look.replace(/\b[A-Z][a-z]+ [A-Z][a-z]+\b/g, "")})`).join("; ");
|
|
526
553
|
const rule = kind === "singer"
|
|
527
554
|
? "The vocalist sings clearly, the whole mouth always visible, never covered by a microphone or a hand. Cinematic, 35mm film look. No text, no logos."
|
|
528
|
-
: "
|
|
555
|
+
: "The lead vocalist is not in this shot, and nobody sings, speaks or lip-syncs on camera. Cinematic, 35mm film look, dynamic camera. No text, no logos.";
|
|
529
556
|
return (kind === "singer" ? CLOSE_UPS : SHOTS).slice(0, count).map((shot) => [
|
|
530
557
|
`Music video for a fictional ${artist.genre} band. ${shot}.`,
|
|
531
558
|
cast ? `The performers are exactly the people in the reference images: ${cast}.` : "",
|
|
@@ -645,11 +672,12 @@ export function closeUpTimes(seconds, lines, count, length = 8) {
|
|
|
645
672
|
return chosen.sort((a, b) => a - b);
|
|
646
673
|
}
|
|
647
674
|
/**
|
|
648
|
-
* The version of a video cut from Veo footage:
|
|
649
|
-
* singer only in close-ups lip-synced to the song. Anything older
|
|
650
|
-
*
|
|
675
|
+
* The version of a video cut from Veo footage: story scenes and the band playing with
|
|
676
|
+
* nobody singing, the singer only in close-ups lip-synced to the song. Anything older
|
|
677
|
+
* is not shown: a slideshow, 4 (singers out of sync) or 5 (all stage, the singer at
|
|
678
|
+
* the mic in the wide shots).
|
|
651
679
|
*/
|
|
652
|
-
export const FILMED =
|
|
680
|
+
export const FILMED = 6;
|
|
653
681
|
/**
|
|
654
682
|
* A single's music video, cut from its filmed shots alone: 4 s cuts, each
|
|
655
683
|
* shot's first half then its second, round and round the shots until the song
|
|
@@ -940,9 +968,12 @@ export class AiLabel {
|
|
|
940
968
|
this.settings.videos = input["videos"] !== false;
|
|
941
969
|
if (input["veo"] !== undefined)
|
|
942
970
|
this.settings.veo = input["veo"] === true;
|
|
943
|
-
const shots = whole("veoShots",
|
|
971
|
+
const shots = whole("veoShots", 0, 8);
|
|
944
972
|
if (shots !== undefined)
|
|
945
973
|
this.settings.veoShots = shots;
|
|
974
|
+
const story = whole("storyShots", 0, 8);
|
|
975
|
+
if (story !== undefined)
|
|
976
|
+
this.settings.storyShots = story;
|
|
946
977
|
const close = whole("lipsyncShots", 0, 4);
|
|
947
978
|
if (close !== undefined)
|
|
948
979
|
this.settings.lipsyncShots = close;
|
|
@@ -1208,9 +1239,10 @@ export class AiLabel {
|
|
|
1208
1239
|
catch { /* the asked-for length */ }
|
|
1209
1240
|
// Paid-for shots live beside the album until the video is cut, so nothing filmed is filmed twice.
|
|
1210
1241
|
const shots = join(_album.folder, `.veo-${song.n}`);
|
|
1211
|
-
const { band: clips, singer } = await this.filmShots(artist, song, shots, scenes);
|
|
1212
|
-
|
|
1213
|
-
|
|
1242
|
+
const { band: clips, singer } = await this.filmShots(artist, _album, song, shots, scenes);
|
|
1243
|
+
const wanted = this.settings.veoShots + this.settings.storyShots;
|
|
1244
|
+
if (clips.length < Math.max(2, Math.ceil(wanted / 2)))
|
|
1245
|
+
throw new Error(`only ${clips.length} of ${wanted} shots filmed so far`);
|
|
1214
1246
|
const pinned = await this.syncCloseUps(song, singer, seconds, shots);
|
|
1215
1247
|
await (this.deps.filmedVideo ?? ((input) => renderFilmedVideo(this.deps.ffmpeg, input)))({ audio: song.file, clips, seconds, artist: artist.name, title: song.title, out, ...(pinned.length ? { pinned } : {}) });
|
|
1216
1248
|
rmSync(shots, { recursive: true, force: true });
|
|
@@ -1288,34 +1320,57 @@ export class AiLabel {
|
|
|
1288
1320
|
* Veo's reference images), on stage, in the song's mood. Stops for the
|
|
1289
1321
|
* month when the budget is spent; a shot Veo will not make is skipped.
|
|
1290
1322
|
*/
|
|
1291
|
-
async filmShots(artist, song, keep, dir) {
|
|
1323
|
+
async filmShots(artist, album, song, keep, dir) {
|
|
1292
1324
|
const veo = this.deps.veo;
|
|
1293
1325
|
if (!veo)
|
|
1294
1326
|
return { band: [], singer: [] };
|
|
1295
1327
|
mkdirSync(dir, { recursive: true });
|
|
1296
1328
|
mkdirSync(keep, { recursive: true });
|
|
1297
1329
|
const members = this.cast(artist);
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1330
|
+
// The players for the band shots, the voice for the close-ups: a singer's portrait in a band
|
|
1331
|
+
// shot puts the singer at a microphone, singing out of sync.
|
|
1332
|
+
const voice = (member) => /vocal|sing/i.test(member.role);
|
|
1333
|
+
const players = members.filter((member) => !voice(member));
|
|
1334
|
+
const singers = members.filter(voice).length ? members.filter(voice) : members.slice(0, 1);
|
|
1335
|
+
const portraits = async (list) => {
|
|
1336
|
+
const out = [];
|
|
1337
|
+
for (const member of list.slice(0, 3)) {
|
|
1338
|
+
const portrait = join(this.folderOf(artist), "members", `${member.slug}.jpg`);
|
|
1339
|
+
const small = join(dir, `ref-${member.slug}.jpg`);
|
|
1340
|
+
try {
|
|
1341
|
+
// 512 px is plenty for a face and keeps the request small.
|
|
1342
|
+
await runLow(this.deps.ffmpeg, [...this.deps.ffmpeg.slice(1), "-hide_banner", "-loglevel", "error", "-y", "-i", portrait, "-vf", "scale=512:-2", "-q:v", "4", small], 60_000, "the portrait did not shrink");
|
|
1343
|
+
out.push(readFileSync(small));
|
|
1344
|
+
}
|
|
1345
|
+
catch {
|
|
1346
|
+
// A member without a portrait is described in words only.
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return out;
|
|
1350
|
+
};
|
|
1351
|
+
const [playerRefs, singerRefs] = [await portraits(players), await portraits(singers)];
|
|
1352
|
+
const band = [];
|
|
1353
|
+
const story = [];
|
|
1354
|
+
const singer = [];
|
|
1355
|
+
// The story is written only while some of its scenes are still to film.
|
|
1356
|
+
const storyFiles = Array.from({ length: this.settings.storyShots }, (_, i) => join(keep, `story-${i}.mp4`));
|
|
1357
|
+
let storyPrompts = [];
|
|
1358
|
+
if (storyFiles.some((file) => !existsSync(file)) && this.canAsk()) {
|
|
1302
1359
|
try {
|
|
1303
|
-
|
|
1304
|
-
await runLow(this.deps.ffmpeg, [...this.deps.ffmpeg.slice(1), "-hide_banner", "-loglevel", "error", "-y", "-i", portrait, "-vf", "scale=512:-2", "-q:v", "4", small], 60_000, "the portrait did not shrink");
|
|
1305
|
-
references.push(readFileSync(small));
|
|
1360
|
+
storyPrompts = await writeVeoStory(artist, members, album, song, storyFiles.length, this.deps.ask ?? askJson);
|
|
1306
1361
|
}
|
|
1307
|
-
catch {
|
|
1308
|
-
|
|
1362
|
+
catch (error) {
|
|
1363
|
+
this.log(`no story for "${song.title}" yet: ${error.message}`);
|
|
1309
1364
|
}
|
|
1310
1365
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
// The singer's close-ups only where they can be lip-synced afterwards.
|
|
1366
|
+
// The singer's close-ups only where they can be lip-synced afterwards. Story scenes go without
|
|
1367
|
+
// the portraits: they would pull the whole band into every scene.
|
|
1314
1368
|
const plan = [
|
|
1315
|
-
...
|
|
1316
|
-
...
|
|
1369
|
+
...storyFiles.map((file, i) => ({ prompt: storyPrompts[i] ?? "", file, into: story, refs: [] })),
|
|
1370
|
+
...veoPrompts(artist, song, players, this.settings.veoShots).map((prompt, i) => ({ prompt, file: join(keep, `shot-${i}.mp4`), into: band, refs: playerRefs })),
|
|
1371
|
+
...(this.deps.lipsync ? veoPrompts(artist, song, singers, this.settings.lipsyncShots, "singer").map((prompt, i) => ({ prompt, file: join(keep, `singer-${i}.mp4`), into: singer, refs: singerRefs })) : []),
|
|
1317
1372
|
];
|
|
1318
|
-
for (const [i, { prompt, file, into }] of plan.entries()) {
|
|
1373
|
+
for (const [i, { prompt, file, into, refs }] of plan.entries()) {
|
|
1319
1374
|
try {
|
|
1320
1375
|
if (statSync(file).size > 10_000) {
|
|
1321
1376
|
into.push({ path: file, seconds: 8 });
|
|
@@ -1323,12 +1378,14 @@ export class AiLabel {
|
|
|
1323
1378
|
}
|
|
1324
1379
|
}
|
|
1325
1380
|
catch { /* not filmed yet */ }
|
|
1381
|
+
if (!prompt)
|
|
1382
|
+
continue;
|
|
1326
1383
|
try {
|
|
1327
1384
|
// nixamp.com briefly away (a deploy: 502/503) or the network: twice more, a minute apart.
|
|
1328
1385
|
let bytes = null;
|
|
1329
1386
|
for (let attempt = 0;; attempt++) {
|
|
1330
1387
|
try {
|
|
1331
|
-
bytes = await veo({ prompt, seconds: 8, references });
|
|
1388
|
+
bytes = await veo({ prompt, seconds: 8, references: refs });
|
|
1332
1389
|
break;
|
|
1333
1390
|
}
|
|
1334
1391
|
catch (error) {
|
|
@@ -1351,9 +1408,17 @@ export class AiLabel {
|
|
|
1351
1408
|
this.log(`Veo shot ${i + 1} for "${song.title}": ${error.message}`);
|
|
1352
1409
|
}
|
|
1353
1410
|
}
|
|
1354
|
-
if (band.length || singer.length)
|
|
1355
|
-
this.log(`Veo: ${band.length + singer.length} filmed shots for "${song.title}" (${artist.name})`);
|
|
1356
|
-
|
|
1411
|
+
if (band.length || story.length || singer.length)
|
|
1412
|
+
this.log(`Veo: ${band.length + story.length + singer.length} filmed shots for "${song.title}" (${artist.name})`);
|
|
1413
|
+
// The cut takes them in turn: a story scene, then the band playing, and so on.
|
|
1414
|
+
const mixed = [];
|
|
1415
|
+
for (let i = 0; i < Math.max(story.length, band.length); i++) {
|
|
1416
|
+
if (story[i])
|
|
1417
|
+
mixed.push(story[i]);
|
|
1418
|
+
if (band[i])
|
|
1419
|
+
mixed.push(band[i]);
|
|
1420
|
+
}
|
|
1421
|
+
return { band: mixed, singer };
|
|
1357
1422
|
}
|
|
1358
1423
|
/** The label's music videos as one TV station: free to watch, with its breaks. */
|
|
1359
1424
|
placeVideos() {
|
package/dist/station-cli.js
CHANGED
|
@@ -541,6 +541,7 @@ async function labelCommand(argv, call, show) {
|
|
|
541
541
|
veo: has(argv, "--veo") ? true : has(argv, "--no-veo") ? false : undefined,
|
|
542
542
|
veoShots: number("--veo-shots"),
|
|
543
543
|
lipsyncShots: number("--lipsync-shots"),
|
|
544
|
+
storyShots: number("--story-shots"),
|
|
544
545
|
genreOrder: flag(argv, "--genres"),
|
|
545
546
|
enabled: has(argv, "--off") ? false : has(argv, "--on") ? true : undefined,
|
|
546
547
|
publish: has(argv, "--unlisted") ? false : has(argv, "--public") ? true : undefined,
|
package/dist/station-mcp.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ export declare const STATION_TOOLS: ({
|
|
|
53
53
|
veo?: undefined;
|
|
54
54
|
veoShots?: undefined;
|
|
55
55
|
lipsyncShots?: undefined;
|
|
56
|
+
storyShots?: undefined;
|
|
56
57
|
genreOrder?: undefined;
|
|
57
58
|
state?: undefined;
|
|
58
59
|
reason?: undefined;
|
|
@@ -132,6 +133,7 @@ export declare const STATION_TOOLS: ({
|
|
|
132
133
|
veo?: undefined;
|
|
133
134
|
veoShots?: undefined;
|
|
134
135
|
lipsyncShots?: undefined;
|
|
136
|
+
storyShots?: undefined;
|
|
135
137
|
genreOrder?: undefined;
|
|
136
138
|
state?: undefined;
|
|
137
139
|
reason?: undefined;
|
|
@@ -234,6 +236,7 @@ export declare const STATION_TOOLS: ({
|
|
|
234
236
|
veo?: undefined;
|
|
235
237
|
veoShots?: undefined;
|
|
236
238
|
lipsyncShots?: undefined;
|
|
239
|
+
storyShots?: undefined;
|
|
237
240
|
genreOrder?: undefined;
|
|
238
241
|
state?: undefined;
|
|
239
242
|
reason?: undefined;
|
|
@@ -333,6 +336,7 @@ export declare const STATION_TOOLS: ({
|
|
|
333
336
|
veo?: undefined;
|
|
334
337
|
veoShots?: undefined;
|
|
335
338
|
lipsyncShots?: undefined;
|
|
339
|
+
storyShots?: undefined;
|
|
336
340
|
genreOrder?: undefined;
|
|
337
341
|
state?: undefined;
|
|
338
342
|
reason?: undefined;
|
|
@@ -414,6 +418,7 @@ export declare const STATION_TOOLS: ({
|
|
|
414
418
|
veo?: undefined;
|
|
415
419
|
veoShots?: undefined;
|
|
416
420
|
lipsyncShots?: undefined;
|
|
421
|
+
storyShots?: undefined;
|
|
417
422
|
genreOrder?: undefined;
|
|
418
423
|
state?: undefined;
|
|
419
424
|
reason?: undefined;
|
|
@@ -502,6 +507,7 @@ export declare const STATION_TOOLS: ({
|
|
|
502
507
|
veo?: undefined;
|
|
503
508
|
veoShots?: undefined;
|
|
504
509
|
lipsyncShots?: undefined;
|
|
510
|
+
storyShots?: undefined;
|
|
505
511
|
genreOrder?: undefined;
|
|
506
512
|
state?: undefined;
|
|
507
513
|
reason?: undefined;
|
|
@@ -590,6 +596,7 @@ export declare const STATION_TOOLS: ({
|
|
|
590
596
|
veo?: undefined;
|
|
591
597
|
veoShots?: undefined;
|
|
592
598
|
lipsyncShots?: undefined;
|
|
599
|
+
storyShots?: undefined;
|
|
593
600
|
genreOrder?: undefined;
|
|
594
601
|
state?: undefined;
|
|
595
602
|
reason?: undefined;
|
|
@@ -695,6 +702,7 @@ export declare const STATION_TOOLS: ({
|
|
|
695
702
|
veo?: undefined;
|
|
696
703
|
veoShots?: undefined;
|
|
697
704
|
lipsyncShots?: undefined;
|
|
705
|
+
storyShots?: undefined;
|
|
698
706
|
genreOrder?: undefined;
|
|
699
707
|
state?: undefined;
|
|
700
708
|
reason?: undefined;
|
|
@@ -829,6 +837,10 @@ export declare const STATION_TOOLS: ({
|
|
|
829
837
|
type: string;
|
|
830
838
|
description: string;
|
|
831
839
|
};
|
|
840
|
+
storyShots: {
|
|
841
|
+
type: string;
|
|
842
|
+
description: string;
|
|
843
|
+
};
|
|
832
844
|
genreOrder: {
|
|
833
845
|
type: string;
|
|
834
846
|
items: {
|
|
@@ -903,6 +915,7 @@ export declare const STATION_TOOLS: ({
|
|
|
903
915
|
veo?: undefined;
|
|
904
916
|
veoShots?: undefined;
|
|
905
917
|
lipsyncShots?: undefined;
|
|
918
|
+
storyShots?: undefined;
|
|
906
919
|
genreOrder?: undefined;
|
|
907
920
|
server: {
|
|
908
921
|
type: "string";
|
|
@@ -1000,6 +1013,7 @@ export declare const STATION_TOOLS: ({
|
|
|
1000
1013
|
veo?: undefined;
|
|
1001
1014
|
veoShots?: undefined;
|
|
1002
1015
|
lipsyncShots?: undefined;
|
|
1016
|
+
storyShots?: undefined;
|
|
1003
1017
|
genreOrder?: undefined;
|
|
1004
1018
|
state?: undefined;
|
|
1005
1019
|
reason?: undefined;
|
|
@@ -1099,6 +1113,7 @@ export declare const STATION_TOOLS: ({
|
|
|
1099
1113
|
veo?: undefined;
|
|
1100
1114
|
veoShots?: undefined;
|
|
1101
1115
|
lipsyncShots?: undefined;
|
|
1116
|
+
storyShots?: undefined;
|
|
1102
1117
|
genreOrder?: undefined;
|
|
1103
1118
|
state?: undefined;
|
|
1104
1119
|
reason?: undefined;
|
package/dist/station-mcp.js
CHANGED
|
@@ -125,6 +125,7 @@ export const STATION_TOOLS = [
|
|
|
125
125
|
veo: { type: "boolean", description: "Each album's single gets filmed Veo shots of the band (nixamp.com's monthly Veo cap applies)." },
|
|
126
126
|
veoShots: { type: "integer", description: "8 s Veo shots of the band per single (1-8); nobody sings in them." },
|
|
127
127
|
lipsyncShots: { type: "integer", description: "Close-ups of the singer per single, lip-synced to the song on nixamp.com (0-4; 0 = the band only)." },
|
|
128
|
+
storyShots: { type: "integer", description: "Cinematic story scenes per single, a short film from the song's words, nobody singing (0-8)." },
|
|
128
129
|
genreOrder: { type: "array", items: STRING, description: "Genres whose artists get albums first, in order." },
|
|
129
130
|
},
|
|
130
131
|
},
|
|
@@ -244,7 +245,7 @@ export async function callStationTool(name, args, options = {}) {
|
|
|
244
245
|
const key = typeof args["key"] === "string" && /^[a-f0-9]{10}$/.test(args["key"]) ? args["key"] : "";
|
|
245
246
|
const answer = action === "status" ? await call("GET", "/api/v1/stations/label")
|
|
246
247
|
: action === "sign" ? await call("POST", "/api/v1/stations/label/artists", pick(["preset", "roster", "name", "genre", "sound", "voice", "bio", "themes", "enabled"]))
|
|
247
|
-
: action === "configure" ? await call("POST", "/api/v1/stations/label", pick(["holder", "monthlySeconds", "everyDays", "seedAlbums", "songsPerAlbum", "songSeconds", "parallel", "genreOrder", "albumsPerDay", "newArtistChance", "resetDay", "veo", "veoShots", "lipsyncShots", "enabled", "publish"]))
|
|
248
|
+
: action === "configure" ? await call("POST", "/api/v1/stations/label", pick(["holder", "monthlySeconds", "everyDays", "seedAlbums", "songsPerAlbum", "songSeconds", "parallel", "genreOrder", "albumsPerDay", "newArtistChance", "resetDay", "veo", "veoShots", "lipsyncShots", "storyShots", "enabled", "publish"]))
|
|
248
249
|
: action === "run" ? await call("POST", "/api/v1/stations/label/run", {})
|
|
249
250
|
: action === "resume" ? await call("POST", "/api/v1/stations/label", { resume: true })
|
|
250
251
|
: !key ? null
|