fluncle 0.86.0 → 0.88.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/bin/fluncle.mjs +62 -455
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -538,7 +538,7 @@ function parseVersion(version) {
|
|
|
538
538
|
var currentVersion;
|
|
539
539
|
var init_version = __esm(() => {
|
|
540
540
|
init_output();
|
|
541
|
-
currentVersion = "0.
|
|
541
|
+
currentVersion = "0.88.0".trim() ? "0.88.0".trim() : "0.1.0";
|
|
542
542
|
});
|
|
543
543
|
|
|
544
544
|
// src/update-notifier.ts
|
|
@@ -1335,47 +1335,22 @@ __export(exports_mixtapes, {
|
|
|
1335
1335
|
mixtapesCommand: () => mixtapesCommand,
|
|
1336
1336
|
mixtapeUpdateCommand: () => mixtapeUpdateCommand,
|
|
1337
1337
|
mixtapeResyncCommand: () => mixtapeResyncCommand,
|
|
1338
|
-
mixtapePublishCommand: () => mixtapePublishCommand,
|
|
1339
|
-
mixtapeMembersCommand: () => mixtapeMembersCommand,
|
|
1340
1338
|
mixtapeListCommand: () => mixtapeListCommand,
|
|
1341
1339
|
mixtapeGetCommand: () => mixtapeGetCommand,
|
|
1342
|
-
mixtapeDistributeCommand: () => mixtapeDistributeCommand
|
|
1343
|
-
mixtapeDeleteCommand: () => mixtapeDeleteCommand,
|
|
1344
|
-
mixtapeCreateCommand: () => mixtapeCreateCommand
|
|
1340
|
+
mixtapeDistributeCommand: () => mixtapeDistributeCommand
|
|
1345
1341
|
});
|
|
1346
|
-
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1347
1342
|
async function mixtapesCommand() {
|
|
1348
1343
|
const response = await publicApiGet("/api/mixtapes");
|
|
1349
1344
|
return response.mixtapes;
|
|
1350
1345
|
}
|
|
1351
|
-
async function mixtapeCreateCommand(options) {
|
|
1352
|
-
return adminApiPost("/api/admin/mixtapes", buildBody(options));
|
|
1353
|
-
}
|
|
1354
1346
|
async function mixtapeUpdateCommand(id, options) {
|
|
1355
1347
|
return adminApiPatch(`/api/admin/mixtapes/${encodeURIComponent(id)}`, buildBody(options));
|
|
1356
1348
|
}
|
|
1357
|
-
async function mixtapeMembersCommand(id, refs, options) {
|
|
1358
|
-
const members = refs.map((ref) => ({ ref }));
|
|
1359
|
-
if (options.from) {
|
|
1360
|
-
members.push(...parseCueFile(options.from));
|
|
1361
|
-
}
|
|
1362
|
-
return adminApiPut(`/api/admin/mixtapes/${encodeURIComponent(id)}/members`, { members });
|
|
1363
|
-
}
|
|
1364
|
-
async function mixtapePublishCommand(id) {
|
|
1365
|
-
return adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(id)}/publish`);
|
|
1366
|
-
}
|
|
1367
|
-
async function mixtapeDeleteCommand(id) {
|
|
1368
|
-
return adminApiDelete(`/api/admin/mixtapes/${encodeURIComponent(id)}`);
|
|
1369
|
-
}
|
|
1370
1349
|
async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {}) {
|
|
1371
1350
|
const mixtape = await mixtapeGetCommand(idOrLogId);
|
|
1372
1351
|
if (!mixtape.id) {
|
|
1373
1352
|
throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
|
|
1374
1353
|
}
|
|
1375
|
-
if (mixtape.status === "draft") {
|
|
1376
|
-
throw new CliError2("mixtape_not_promoted", `${mixtape.id} is still a draft — promote its recording first:
|
|
1377
|
-
` + " fluncle admin recordings promote <recordingId>");
|
|
1378
|
-
}
|
|
1379
1354
|
const both = !options.youtube && !options.mixcloud;
|
|
1380
1355
|
const doYoutube = both || Boolean(options.youtube);
|
|
1381
1356
|
const doMixcloud = both || Boolean(options.mixcloud);
|
|
@@ -1388,7 +1363,8 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
|
|
|
1388
1363
|
const mixtapeId = mixtape.id;
|
|
1389
1364
|
const logId = mixtape.logId;
|
|
1390
1365
|
if (!logId) {
|
|
1391
|
-
throw new CliError2("
|
|
1366
|
+
throw new CliError2("mixtape_not_promoted", `${mixtapeId} has no coordinate yet — promote its recording first:
|
|
1367
|
+
` + " fluncle admin recordings promote <recordingId>");
|
|
1392
1368
|
}
|
|
1393
1369
|
if (!mixtape.durationMs) {
|
|
1394
1370
|
const source = options.audio ?? options.video;
|
|
@@ -1495,65 +1471,6 @@ function buildBody(options) {
|
|
|
1495
1471
|
}
|
|
1496
1472
|
return body;
|
|
1497
1473
|
}
|
|
1498
|
-
function parseCueFile(filePath) {
|
|
1499
|
-
if (!existsSync(filePath)) {
|
|
1500
|
-
throw new CliError2("file_not_found", `Cue file not found: ${filePath}`);
|
|
1501
|
-
}
|
|
1502
|
-
const text = readFileSync2(filePath, "utf-8");
|
|
1503
|
-
const trimmed = text.trim();
|
|
1504
|
-
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
1505
|
-
try {
|
|
1506
|
-
const parsed = JSON.parse(trimmed);
|
|
1507
|
-
if (!Array.isArray(parsed)) {
|
|
1508
|
-
throw new Error("not an array");
|
|
1509
|
-
}
|
|
1510
|
-
return parsed.map((entry, index) => {
|
|
1511
|
-
if (typeof entry === "string") {
|
|
1512
|
-
return { ref: entry.trim() };
|
|
1513
|
-
}
|
|
1514
|
-
const obj = entry;
|
|
1515
|
-
if (typeof obj?.ref !== "string") {
|
|
1516
|
-
throw new CliError2("invalid_cue_json", `Entry ${index + 1} missing "ref" string`);
|
|
1517
|
-
}
|
|
1518
|
-
const cue = { ref: obj.ref.trim() };
|
|
1519
|
-
if (typeof obj.startMs === "number" && Number.isInteger(obj.startMs) && obj.startMs >= 0) {
|
|
1520
|
-
cue.startMs = obj.startMs;
|
|
1521
|
-
}
|
|
1522
|
-
return cue;
|
|
1523
|
-
});
|
|
1524
|
-
} catch (error) {
|
|
1525
|
-
if (error instanceof CliError2) {
|
|
1526
|
-
throw error;
|
|
1527
|
-
}
|
|
1528
|
-
throw new CliError2("invalid_cue_json", `Cue JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
return parseCueSheet(text);
|
|
1532
|
-
}
|
|
1533
|
-
function parseCueSheet(text) {
|
|
1534
|
-
const entries = [];
|
|
1535
|
-
for (const line of text.split(/\r?\n/)) {
|
|
1536
|
-
const trimmed = line.trim();
|
|
1537
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
1538
|
-
continue;
|
|
1539
|
-
}
|
|
1540
|
-
const match = trimmed.match(/^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+)$/);
|
|
1541
|
-
if (match) {
|
|
1542
|
-
const [, time, ref] = match;
|
|
1543
|
-
if (time === undefined || ref === undefined) {
|
|
1544
|
-
continue;
|
|
1545
|
-
}
|
|
1546
|
-
const startMs = parseDuration(time);
|
|
1547
|
-
if (startMs === null) {
|
|
1548
|
-
continue;
|
|
1549
|
-
}
|
|
1550
|
-
entries.push({ ref: ref.trim(), startMs });
|
|
1551
|
-
} else {
|
|
1552
|
-
entries.push({ ref: trimmed });
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
return entries;
|
|
1556
|
-
}
|
|
1557
1474
|
var init_mixtapes = __esm(() => {
|
|
1558
1475
|
init_util();
|
|
1559
1476
|
init_api();
|
|
@@ -2251,7 +2168,7 @@ function parseVersion2(version) {
|
|
|
2251
2168
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2252
2169
|
var init_version2 = __esm(() => {
|
|
2253
2170
|
init_output();
|
|
2254
|
-
currentVersion2 = "0.
|
|
2171
|
+
currentVersion2 = "0.88.0".trim() ? "0.88.0".trim() : "0.1.0";
|
|
2255
2172
|
});
|
|
2256
2173
|
|
|
2257
2174
|
// ../../packages/registry/src/index.ts
|
|
@@ -2773,7 +2690,7 @@ var init_src = __esm(() => {
|
|
|
2773
2690
|
{
|
|
2774
2691
|
command: "fluncle admin",
|
|
2775
2692
|
exposedContent: [
|
|
2776
|
-
"the operator/agent command group (hidden): tracks publish|update|enrich|video|draft|social|preview|observe|context|note,
|
|
2693
|
+
"the operator/agent command group (hidden): tracks publish|update|enrich|video|draft|social|preview|observe|context|note, recordings create|promote, mixtapes update|distribute|resync, newsletter draft|update|send|list, backfills, auth"
|
|
2777
2694
|
],
|
|
2778
2695
|
kind: "cli",
|
|
2779
2696
|
name: "cli.admin",
|
|
@@ -3668,7 +3585,7 @@ var init_mixtape_youtube2 = __esm(() => {
|
|
|
3668
3585
|
|
|
3669
3586
|
// src/commands/mixtape-set-video.ts
|
|
3670
3587
|
import { randomUUID } from "node:crypto";
|
|
3671
|
-
import { existsSync
|
|
3588
|
+
import { existsSync, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
|
|
3672
3589
|
import { tmpdir } from "node:os";
|
|
3673
3590
|
import { join as join4 } from "node:path";
|
|
3674
3591
|
function planMultipart(contentLength, partSize = DEFAULT_PART_SIZE) {
|
|
@@ -3725,7 +3642,7 @@ function renditionFfmpegArgs(inputPath, outputPath) {
|
|
|
3725
3642
|
];
|
|
3726
3643
|
}
|
|
3727
3644
|
async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
|
|
3728
|
-
if (!
|
|
3645
|
+
if (!existsSync(masterPath)) {
|
|
3729
3646
|
throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
|
|
3730
3647
|
}
|
|
3731
3648
|
await assertFfmpeg();
|
|
@@ -3838,7 +3755,7 @@ __export(exports_recordings, {
|
|
|
3838
3755
|
recordingDeleteCommand: () => recordingDeleteCommand,
|
|
3839
3756
|
recordingCreateCommand: () => recordingCreateCommand
|
|
3840
3757
|
});
|
|
3841
|
-
import { existsSync as
|
|
3758
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
3842
3759
|
async function recordingGet(id) {
|
|
3843
3760
|
const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
|
|
3844
3761
|
return response.recording;
|
|
@@ -3907,7 +3824,7 @@ async function recordingCreateCommand(options = {}) {
|
|
|
3907
3824
|
if (!options.video) {
|
|
3908
3825
|
throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
|
|
3909
3826
|
}
|
|
3910
|
-
if (!
|
|
3827
|
+
if (!existsSync2(options.video)) {
|
|
3911
3828
|
throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
|
|
3912
3829
|
}
|
|
3913
3830
|
const created = await adminApiPost("/api/admin/recordings", {
|
|
@@ -3943,11 +3860,11 @@ async function recordingUpdateCommand(id, options = {}) {
|
|
|
3943
3860
|
body.parentId = options.parentId;
|
|
3944
3861
|
}
|
|
3945
3862
|
if (options.tracklistFile !== undefined) {
|
|
3946
|
-
if (!
|
|
3863
|
+
if (!existsSync2(options.tracklistFile)) {
|
|
3947
3864
|
throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
|
|
3948
3865
|
}
|
|
3949
3866
|
try {
|
|
3950
|
-
body.tracklistJson = JSON.parse(
|
|
3867
|
+
body.tracklistJson = JSON.parse(readFileSync2(options.tracklistFile, "utf8"));
|
|
3951
3868
|
} catch {
|
|
3952
3869
|
throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
|
|
3953
3870
|
}
|
|
@@ -3992,12 +3909,12 @@ async function recordingReplaceCuesCommand(id, options = {}) {
|
|
|
3992
3909
|
if (!options.cuesFile) {
|
|
3993
3910
|
throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
|
|
3994
3911
|
}
|
|
3995
|
-
if (!
|
|
3912
|
+
if (!existsSync2(options.cuesFile)) {
|
|
3996
3913
|
throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
|
|
3997
3914
|
}
|
|
3998
3915
|
let cues;
|
|
3999
3916
|
try {
|
|
4000
|
-
cues = JSON.parse(
|
|
3917
|
+
cues = JSON.parse(readFileSync2(options.cuesFile, "utf8"));
|
|
4001
3918
|
} catch {
|
|
4002
3919
|
throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
|
|
4003
3920
|
}
|
|
@@ -4014,238 +3931,6 @@ var init_recordings = __esm(() => {
|
|
|
4014
3931
|
init_mixtape_set_video();
|
|
4015
3932
|
});
|
|
4016
3933
|
|
|
4017
|
-
// src/commands/mixtapes.ts
|
|
4018
|
-
var exports_mixtapes2 = {};
|
|
4019
|
-
__export(exports_mixtapes2, {
|
|
4020
|
-
mixtapesCommand: () => mixtapesCommand2,
|
|
4021
|
-
mixtapeUpdateCommand: () => mixtapeUpdateCommand2,
|
|
4022
|
-
mixtapeResyncCommand: () => mixtapeResyncCommand2,
|
|
4023
|
-
mixtapePublishCommand: () => mixtapePublishCommand2,
|
|
4024
|
-
mixtapeMembersCommand: () => mixtapeMembersCommand2,
|
|
4025
|
-
mixtapeListCommand: () => mixtapeListCommand,
|
|
4026
|
-
mixtapeGetCommand: () => mixtapeGetCommand,
|
|
4027
|
-
mixtapeDistributeCommand: () => mixtapeDistributeCommand2,
|
|
4028
|
-
mixtapeDeleteCommand: () => mixtapeDeleteCommand2,
|
|
4029
|
-
mixtapeCreateCommand: () => mixtapeCreateCommand2
|
|
4030
|
-
});
|
|
4031
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
4032
|
-
async function mixtapesCommand2() {
|
|
4033
|
-
const response = await publicApiGet("/api/mixtapes");
|
|
4034
|
-
return response.mixtapes;
|
|
4035
|
-
}
|
|
4036
|
-
async function mixtapeCreateCommand2(options) {
|
|
4037
|
-
return adminApiPost("/api/admin/mixtapes", buildBody2(options));
|
|
4038
|
-
}
|
|
4039
|
-
async function mixtapeUpdateCommand2(id, options) {
|
|
4040
|
-
return adminApiPatch(`/api/admin/mixtapes/${encodeURIComponent(id)}`, buildBody2(options));
|
|
4041
|
-
}
|
|
4042
|
-
async function mixtapeMembersCommand2(id, refs, options) {
|
|
4043
|
-
const members = refs.map((ref) => ({ ref }));
|
|
4044
|
-
if (options.from) {
|
|
4045
|
-
members.push(...parseCueFile2(options.from));
|
|
4046
|
-
}
|
|
4047
|
-
return adminApiPut(`/api/admin/mixtapes/${encodeURIComponent(id)}/members`, { members });
|
|
4048
|
-
}
|
|
4049
|
-
async function mixtapePublishCommand2(id) {
|
|
4050
|
-
return adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(id)}/publish`);
|
|
4051
|
-
}
|
|
4052
|
-
async function mixtapeDeleteCommand2(id) {
|
|
4053
|
-
return adminApiDelete(`/api/admin/mixtapes/${encodeURIComponent(id)}`);
|
|
4054
|
-
}
|
|
4055
|
-
async function mixtapeDistributeCommand2(idOrLogId, options, onProgress = () => {}) {
|
|
4056
|
-
const mixtape = await mixtapeGetCommand(idOrLogId);
|
|
4057
|
-
if (!mixtape.id) {
|
|
4058
|
-
throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
|
|
4059
|
-
}
|
|
4060
|
-
if (mixtape.status === "draft") {
|
|
4061
|
-
throw new CliError2("mixtape_not_promoted", `${mixtape.id} is still a draft — promote its recording first:
|
|
4062
|
-
` + " fluncle admin recordings promote <recordingId>");
|
|
4063
|
-
}
|
|
4064
|
-
const both = !options.youtube && !options.mixcloud;
|
|
4065
|
-
const doYoutube = both || Boolean(options.youtube);
|
|
4066
|
-
const doMixcloud = both || Boolean(options.mixcloud);
|
|
4067
|
-
if (doYoutube && !options.video) {
|
|
4068
|
-
throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
|
|
4069
|
-
}
|
|
4070
|
-
if (doMixcloud && !options.audio) {
|
|
4071
|
-
throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
|
|
4072
|
-
}
|
|
4073
|
-
const mixtapeId = mixtape.id;
|
|
4074
|
-
const logId = mixtape.logId;
|
|
4075
|
-
if (!logId) {
|
|
4076
|
-
throw new CliError2("missing_log_id", "The mixtape has no Log ID — was it promoted successfully?");
|
|
4077
|
-
}
|
|
4078
|
-
if (!mixtape.durationMs) {
|
|
4079
|
-
const source = options.audio ?? options.video;
|
|
4080
|
-
const durationMs = source ? await probeDurationMs2(source) : undefined;
|
|
4081
|
-
if (durationMs) {
|
|
4082
|
-
await mixtapeUpdateCommand2(mixtapeId, { durationMs: String(durationMs), json: false });
|
|
4083
|
-
onProgress(`Duration: ${Math.round(durationMs / 60000)} min (from the upload).`);
|
|
4084
|
-
}
|
|
4085
|
-
}
|
|
4086
|
-
if (mixtape.status === "published") {
|
|
4087
|
-
onProgress(`Already published (${logId}); re-distributing.`);
|
|
4088
|
-
} else {
|
|
4089
|
-
onProgress(`Distributing ${logId} (promoted — coordinate already minted).`);
|
|
4090
|
-
}
|
|
4091
|
-
const results = [];
|
|
4092
|
-
if (doYoutube) {
|
|
4093
|
-
if (!options.video) {
|
|
4094
|
-
throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
|
|
4095
|
-
}
|
|
4096
|
-
onProgress("YouTube: uploading video…");
|
|
4097
|
-
const { distributeYoutube: distributeYoutube3 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
|
|
4098
|
-
const result = await distributeYoutube3(mixtapeId, options.video, onProgress);
|
|
4099
|
-
results.push({ platform: "youtube", url: result.url });
|
|
4100
|
-
onProgress(`YouTube: ${result.url}`);
|
|
4101
|
-
}
|
|
4102
|
-
if (doMixcloud) {
|
|
4103
|
-
if (!options.audio) {
|
|
4104
|
-
throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
|
|
4105
|
-
}
|
|
4106
|
-
onProgress("Mixcloud: uploading audio…");
|
|
4107
|
-
const { distributeMixcloud: distributeMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
|
|
4108
|
-
const result = await distributeMixcloud2(mixtapeId, options.audio, onProgress, options.unlisted);
|
|
4109
|
-
results.push({ platform: "mixcloud", url: result.url });
|
|
4110
|
-
onProgress(`Mixcloud: ${result.url}`);
|
|
4111
|
-
}
|
|
4112
|
-
return { logId, mixtapeId, results };
|
|
4113
|
-
}
|
|
4114
|
-
async function mixtapeResyncCommand2(idOrLogId, options, onProgress = () => {}) {
|
|
4115
|
-
const mixtape = await mixtapeGetCommand(idOrLogId);
|
|
4116
|
-
if (!mixtape.id) {
|
|
4117
|
-
throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
|
|
4118
|
-
}
|
|
4119
|
-
if (!mixtape.logId) {
|
|
4120
|
-
throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet — distribute it before re-syncing.");
|
|
4121
|
-
}
|
|
4122
|
-
const mixtapeId = mixtape.id;
|
|
4123
|
-
const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
|
|
4124
|
-
let doYoutube = Boolean(options.youtube);
|
|
4125
|
-
let doMixcloud = Boolean(options.mixcloud);
|
|
4126
|
-
if (!explicit) {
|
|
4127
|
-
const social = await adminApiGet(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/social`);
|
|
4128
|
-
const platforms = new Set(social.posts.map((post) => post.platform));
|
|
4129
|
-
doYoutube = platforms.has("youtube");
|
|
4130
|
-
doMixcloud = platforms.has("mixcloud");
|
|
4131
|
-
if (!doYoutube && !doMixcloud) {
|
|
4132
|
-
throw new CliError2("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
const results = [];
|
|
4136
|
-
if (doYoutube) {
|
|
4137
|
-
onProgress("YouTube: re-syncing description + chapters…");
|
|
4138
|
-
const { resyncYoutube: resyncYoutube3 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
|
|
4139
|
-
const result = await resyncYoutube3(mixtapeId);
|
|
4140
|
-
results.push({ platform: "youtube", url: result.url });
|
|
4141
|
-
onProgress(`YouTube: ${result.url}`);
|
|
4142
|
-
}
|
|
4143
|
-
if (doMixcloud) {
|
|
4144
|
-
onProgress("Mixcloud: re-syncing sections…");
|
|
4145
|
-
const { resyncMixcloud: resyncMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
|
|
4146
|
-
const result = await resyncMixcloud2(mixtapeId);
|
|
4147
|
-
results.push({ platform: "mixcloud", url: result.url });
|
|
4148
|
-
onProgress(`Mixcloud: ${result.url}`);
|
|
4149
|
-
}
|
|
4150
|
-
return { logId: mixtape.logId, mixtapeId, results };
|
|
4151
|
-
}
|
|
4152
|
-
async function probeDurationMs2(filePath) {
|
|
4153
|
-
try {
|
|
4154
|
-
const proc = Bun.spawn(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", filePath], { stderr: "ignore", stdout: "pipe" });
|
|
4155
|
-
const out = await new Response(proc.stdout).text();
|
|
4156
|
-
await proc.exited;
|
|
4157
|
-
const seconds = Number.parseFloat(out.trim());
|
|
4158
|
-
return Number.isFinite(seconds) && seconds > 0 ? Math.round(seconds * 1000) : undefined;
|
|
4159
|
-
} catch {
|
|
4160
|
-
return;
|
|
4161
|
-
}
|
|
4162
|
-
}
|
|
4163
|
-
function buildBody2(options) {
|
|
4164
|
-
const body = {};
|
|
4165
|
-
if (options.note !== undefined) {
|
|
4166
|
-
body.note = options.note;
|
|
4167
|
-
}
|
|
4168
|
-
if (options.recordedAt !== undefined) {
|
|
4169
|
-
body.recordedAt = options.recordedAt;
|
|
4170
|
-
}
|
|
4171
|
-
if (options.soundcloudUrl !== undefined) {
|
|
4172
|
-
body.soundcloudUrl = options.soundcloudUrl;
|
|
4173
|
-
}
|
|
4174
|
-
if (options.durationMs !== undefined) {
|
|
4175
|
-
const parsed = parseDuration(options.durationMs);
|
|
4176
|
-
if (parsed === null) {
|
|
4177
|
-
throw new CliError2("invalid_duration", "Duration must be mm:ss or h:mm:ss, or a millisecond count");
|
|
4178
|
-
}
|
|
4179
|
-
body.durationMs = parsed;
|
|
4180
|
-
}
|
|
4181
|
-
return body;
|
|
4182
|
-
}
|
|
4183
|
-
function parseCueFile2(filePath) {
|
|
4184
|
-
if (!existsSync4(filePath)) {
|
|
4185
|
-
throw new CliError2("file_not_found", `Cue file not found: ${filePath}`);
|
|
4186
|
-
}
|
|
4187
|
-
const text = readFileSync4(filePath, "utf-8");
|
|
4188
|
-
const trimmed = text.trim();
|
|
4189
|
-
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
4190
|
-
try {
|
|
4191
|
-
const parsed = JSON.parse(trimmed);
|
|
4192
|
-
if (!Array.isArray(parsed)) {
|
|
4193
|
-
throw new Error("not an array");
|
|
4194
|
-
}
|
|
4195
|
-
return parsed.map((entry, index) => {
|
|
4196
|
-
if (typeof entry === "string") {
|
|
4197
|
-
return { ref: entry.trim() };
|
|
4198
|
-
}
|
|
4199
|
-
const obj = entry;
|
|
4200
|
-
if (typeof obj?.ref !== "string") {
|
|
4201
|
-
throw new CliError2("invalid_cue_json", `Entry ${index + 1} missing "ref" string`);
|
|
4202
|
-
}
|
|
4203
|
-
const cue = { ref: obj.ref.trim() };
|
|
4204
|
-
if (typeof obj.startMs === "number" && Number.isInteger(obj.startMs) && obj.startMs >= 0) {
|
|
4205
|
-
cue.startMs = obj.startMs;
|
|
4206
|
-
}
|
|
4207
|
-
return cue;
|
|
4208
|
-
});
|
|
4209
|
-
} catch (error) {
|
|
4210
|
-
if (error instanceof CliError2) {
|
|
4211
|
-
throw error;
|
|
4212
|
-
}
|
|
4213
|
-
throw new CliError2("invalid_cue_json", `Cue JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
return parseCueSheet2(text);
|
|
4217
|
-
}
|
|
4218
|
-
function parseCueSheet2(text) {
|
|
4219
|
-
const entries = [];
|
|
4220
|
-
for (const line of text.split(/\r?\n/)) {
|
|
4221
|
-
const trimmed = line.trim();
|
|
4222
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
4223
|
-
continue;
|
|
4224
|
-
}
|
|
4225
|
-
const match = trimmed.match(/^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+)$/);
|
|
4226
|
-
if (match) {
|
|
4227
|
-
const [, time, ref] = match;
|
|
4228
|
-
if (time === undefined || ref === undefined) {
|
|
4229
|
-
continue;
|
|
4230
|
-
}
|
|
4231
|
-
const startMs = parseDuration(time);
|
|
4232
|
-
if (startMs === null) {
|
|
4233
|
-
continue;
|
|
4234
|
-
}
|
|
4235
|
-
entries.push({ ref: ref.trim(), startMs });
|
|
4236
|
-
} else {
|
|
4237
|
-
entries.push({ ref: trimmed });
|
|
4238
|
-
}
|
|
4239
|
-
}
|
|
4240
|
-
return entries;
|
|
4241
|
-
}
|
|
4242
|
-
var init_mixtapes2 = __esm(() => {
|
|
4243
|
-
init_util();
|
|
4244
|
-
init_api();
|
|
4245
|
-
init_mixtape_api();
|
|
4246
|
-
init_output();
|
|
4247
|
-
});
|
|
4248
|
-
|
|
4249
3934
|
// src/commands/clips.ts
|
|
4250
3935
|
var exports_clips = {};
|
|
4251
3936
|
__export(exports_clips, {
|
|
@@ -4321,9 +4006,6 @@ async function clipsListCommand(filter = {}) {
|
|
|
4321
4006
|
if (filter.recordingId) {
|
|
4322
4007
|
params.set("recordingId", filter.recordingId);
|
|
4323
4008
|
}
|
|
4324
|
-
if (filter.mixtapeId) {
|
|
4325
|
-
params.set("mixtapeId", filter.mixtapeId);
|
|
4326
|
-
}
|
|
4327
4009
|
if (filter.status) {
|
|
4328
4010
|
params.set("status", filter.status);
|
|
4329
4011
|
}
|
|
@@ -4332,28 +4014,17 @@ async function clipsListCommand(filter = {}) {
|
|
|
4332
4014
|
return response.clips;
|
|
4333
4015
|
}
|
|
4334
4016
|
async function resolveClipSource(clip) {
|
|
4335
|
-
if (clip.recordingId) {
|
|
4336
|
-
|
|
4337
|
-
const recording = await recordingGet2(clip.recordingId);
|
|
4338
|
-
if (!recording.r2Key) {
|
|
4339
|
-
throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
|
|
4340
|
-
}
|
|
4341
|
-
return {
|
|
4342
|
-
setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
|
|
4343
|
-
};
|
|
4017
|
+
if (!clip.recordingId) {
|
|
4018
|
+
throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
|
|
4344
4019
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4020
|
+
const { recordingGet: recordingGet2 } = await Promise.resolve().then(() => (init_recordings(), exports_recordings));
|
|
4021
|
+
const recording = await recordingGet2(clip.recordingId);
|
|
4022
|
+
if (!recording.r2Key) {
|
|
4023
|
+
throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
|
|
4347
4024
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
throw new CliError2("mixtape_no_log_id", `Mixtape ${clip.mixtapeId} has no committed Log ID`);
|
|
4352
|
-
}
|
|
4353
|
-
if (!mixtape.setVideoAt) {
|
|
4354
|
-
throw new CliError2("set_not_staged", `Mixtape ${mixtape.logId} has no staged set video — run \`recordings promote <recordingId>\` first`);
|
|
4355
|
-
}
|
|
4356
|
-
return { setUrl: setVideoUrl(mixtape.logId) };
|
|
4025
|
+
return {
|
|
4026
|
+
setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
|
|
4027
|
+
};
|
|
4357
4028
|
}
|
|
4358
4029
|
async function clipCutCommand(clipId, onProgress = () => {}) {
|
|
4359
4030
|
const clips = await clipsListCommand();
|
|
@@ -4447,7 +4118,7 @@ __export(exports_recordings2, {
|
|
|
4447
4118
|
recordingDeleteCommand: () => recordingDeleteCommand2,
|
|
4448
4119
|
recordingCreateCommand: () => recordingCreateCommand2
|
|
4449
4120
|
});
|
|
4450
|
-
import { existsSync as
|
|
4121
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
4451
4122
|
async function recordingGet2(id) {
|
|
4452
4123
|
const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
|
|
4453
4124
|
return response.recording;
|
|
@@ -4516,7 +4187,7 @@ async function recordingCreateCommand2(options = {}) {
|
|
|
4516
4187
|
if (!options.video) {
|
|
4517
4188
|
throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
|
|
4518
4189
|
}
|
|
4519
|
-
if (!
|
|
4190
|
+
if (!existsSync3(options.video)) {
|
|
4520
4191
|
throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
|
|
4521
4192
|
}
|
|
4522
4193
|
const created = await adminApiPost("/api/admin/recordings", {
|
|
@@ -4552,11 +4223,11 @@ async function recordingUpdateCommand2(id, options = {}) {
|
|
|
4552
4223
|
body.parentId = options.parentId;
|
|
4553
4224
|
}
|
|
4554
4225
|
if (options.tracklistFile !== undefined) {
|
|
4555
|
-
if (!
|
|
4226
|
+
if (!existsSync3(options.tracklistFile)) {
|
|
4556
4227
|
throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
|
|
4557
4228
|
}
|
|
4558
4229
|
try {
|
|
4559
|
-
body.tracklistJson = JSON.parse(
|
|
4230
|
+
body.tracklistJson = JSON.parse(readFileSync3(options.tracklistFile, "utf8"));
|
|
4560
4231
|
} catch {
|
|
4561
4232
|
throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
|
|
4562
4233
|
}
|
|
@@ -4601,12 +4272,12 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
|
|
|
4601
4272
|
if (!options.cuesFile) {
|
|
4602
4273
|
throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
|
|
4603
4274
|
}
|
|
4604
|
-
if (!
|
|
4275
|
+
if (!existsSync3(options.cuesFile)) {
|
|
4605
4276
|
throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
|
|
4606
4277
|
}
|
|
4607
4278
|
let cues;
|
|
4608
4279
|
try {
|
|
4609
|
-
cues = JSON.parse(
|
|
4280
|
+
cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
|
|
4610
4281
|
} catch {
|
|
4611
4282
|
throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
|
|
4612
4283
|
}
|
|
@@ -4632,8 +4303,8 @@ __export(exports_newsletter, {
|
|
|
4632
4303
|
newsletterDraftCommand: () => newsletterDraftCommand,
|
|
4633
4304
|
newsletterDeleteCommand: () => newsletterDeleteCommand
|
|
4634
4305
|
});
|
|
4635
|
-
import { existsSync as
|
|
4636
|
-
function
|
|
4306
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
4307
|
+
function buildBody2(options, { requireContent }) {
|
|
4637
4308
|
const body = {};
|
|
4638
4309
|
if (options.contentFile !== undefined) {
|
|
4639
4310
|
body.contentJson = readContentFile(options.contentFile);
|
|
@@ -4652,10 +4323,10 @@ function buildBody3(options, { requireContent }) {
|
|
|
4652
4323
|
return body;
|
|
4653
4324
|
}
|
|
4654
4325
|
function readContentFile(filePath) {
|
|
4655
|
-
if (!
|
|
4326
|
+
if (!existsSync4(filePath)) {
|
|
4656
4327
|
throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
|
|
4657
4328
|
}
|
|
4658
|
-
const text =
|
|
4329
|
+
const text = readFileSync4(filePath, "utf-8");
|
|
4659
4330
|
try {
|
|
4660
4331
|
return JSON.parse(text);
|
|
4661
4332
|
} catch (error) {
|
|
@@ -4663,10 +4334,10 @@ function readContentFile(filePath) {
|
|
|
4663
4334
|
}
|
|
4664
4335
|
}
|
|
4665
4336
|
async function newsletterDraftCommand(options) {
|
|
4666
|
-
return adminApiPost("/api/admin/newsletter/editions",
|
|
4337
|
+
return adminApiPost("/api/admin/newsletter/editions", buildBody2(options, { requireContent: true }));
|
|
4667
4338
|
}
|
|
4668
4339
|
async function newsletterUpdateCommand(id, options) {
|
|
4669
|
-
return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`,
|
|
4340
|
+
return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`, buildBody2(options, { requireContent: false }));
|
|
4670
4341
|
}
|
|
4671
4342
|
async function newsletterSendCommand(id) {
|
|
4672
4343
|
return adminApiPost(`/api/admin/newsletter/editions/${encodeURIComponent(id)}/send`);
|
|
@@ -5290,7 +4961,7 @@ var init_format2 = __esm(() => {
|
|
|
5290
4961
|
});
|
|
5291
4962
|
|
|
5292
4963
|
// src/cli.ts
|
|
5293
|
-
import { existsSync as
|
|
4964
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
5294
4965
|
import path2 from "path";
|
|
5295
4966
|
|
|
5296
4967
|
// ../../node_modules/commander/lib/error.js
|
|
@@ -7456,8 +7127,8 @@ function addListenCommands(program2) {
|
|
|
7456
7127
|
await runRecent(options, recentCommand3);
|
|
7457
7128
|
});
|
|
7458
7129
|
program2.command("mixtapes").description("Fluncle's checkpoint sets").option("--json", "Print JSON", false).action(async (options) => {
|
|
7459
|
-
const { mixtapesCommand:
|
|
7460
|
-
await runMixtapes(options,
|
|
7130
|
+
const { mixtapesCommand: mixtapesCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7131
|
+
await runMixtapes(options, mixtapesCommand2);
|
|
7461
7132
|
});
|
|
7462
7133
|
program2.command("open").description("Pick a track, open it in Spotify").argument("[target]").option("--app", "Open in the native app", false).option("--browser", "Open in the browser", false).option("--limit <limit>", "Number of recent tracks to choose from", "20").allowExcessArguments().action(async (target, options) => {
|
|
7463
7134
|
const openCommands = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
@@ -7618,27 +7289,11 @@ function addAdminCommands(program2) {
|
|
|
7618
7289
|
adminMixtapes.action(() => {
|
|
7619
7290
|
adminMixtapes.outputHelp();
|
|
7620
7291
|
});
|
|
7621
|
-
adminMixtapes.command("create").description("Log a new mixtape draft").option("--duration-ms <duration>", "Duration (mm:ss, h:mm:ss, or ms)").option("--json", "Print JSON", false).option("--note <text>", "Operator note").option("--recorded-at <date>", "Recorded date (ISO)").option("--soundcloud-url <url>", "SoundCloud URL (manual; YouTube + Mixcloud come from distribute)").allowExcessArguments().action(async (options) => {
|
|
7622
|
-
const { mixtapeCreateCommand: mixtapeCreateCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7623
|
-
await runMixtapeCreate(options, mixtapeCreateCommand3);
|
|
7624
|
-
});
|
|
7625
7292
|
adminMixtapes.command("update").description("Update a mixtape's fields").argument("[id]").option("--duration-ms <duration>", "Duration (mm:ss, h:mm:ss, or ms)").option("--json", "Print JSON", false).option("--note <text>", "Operator note").option("--recorded-at <date>", "Recorded date (ISO)").option("--soundcloud-url <url>", "SoundCloud URL (manual; YouTube + Mixcloud come from distribute)").allowExcessArguments().action(async (id, options) => {
|
|
7626
|
-
const { mixtapeUpdateCommand:
|
|
7627
|
-
await runMixtapeUpdate(id, options,
|
|
7628
|
-
});
|
|
7629
|
-
adminMixtapes.command("members").description("Set a mixtape's tracklist (refs and/or a cue-sheet file)").argument("[id]").argument("[refs...]").option("--from <file>", "Cue-sheet or JSON file with members").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, refs, options) => {
|
|
7630
|
-
const { mixtapeMembersCommand: mixtapeMembersCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7631
|
-
await runMixtapeMembers(id, refs, options, mixtapeMembersCommand3);
|
|
7293
|
+
const { mixtapeUpdateCommand: mixtapeUpdateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7294
|
+
await runMixtapeUpdate(id, options, mixtapeUpdateCommand2);
|
|
7632
7295
|
});
|
|
7633
|
-
adminMixtapes.command("
|
|
7634
|
-
const { mixtapePublishCommand: mixtapePublishCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7635
|
-
await runMixtapePublish(id, options, mixtapePublishCommand3);
|
|
7636
|
-
});
|
|
7637
|
-
adminMixtapes.command("delete").description("Discard a mixtape draft (published mixtapes can't be deleted)").argument("[id]").option("--json", "Print JSON", false).option("--yes", "Skip confirmation", false).allowExcessArguments().action(async (id, options) => {
|
|
7638
|
-
const { mixtapeDeleteCommand: mixtapeDeleteCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7639
|
-
await runMixtapeDelete(id, options, mixtapeDeleteCommand3);
|
|
7640
|
-
});
|
|
7641
|
-
adminMixtapes.command("list").description("List all mixtapes (including drafts)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7296
|
+
adminMixtapes.command("list").description("List all mixtapes (including distributing)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7642
7297
|
const { mixtapeListCommand: mixtapeListCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7643
7298
|
await runMixtapeList(options, mixtapeListCommand2);
|
|
7644
7299
|
});
|
|
@@ -7647,22 +7302,22 @@ function addAdminCommands(program2) {
|
|
|
7647
7302
|
await runMixtapeGet(idOrLogId, options, mixtapeGetCommand2);
|
|
7648
7303
|
});
|
|
7649
7304
|
adminMixtapes.command("distribute").description("Push a promoted mixtape to YouTube (video) and Mixcloud (audio). The mixtape must already be promoted (`recordings promote`) \u2014 distribute is push-only.").argument("[idOrLogId]").option("--video <file>", "Video file for YouTube").option("--audio <file>", "Audio file for Mixcloud").option("--youtube", "Only distribute to YouTube").option("--mixcloud", "Only distribute to Mixcloud").option("--unlisted", "Keep Mixcloud private too (YouTube is always unlisted until publish-youtube)").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
7650
|
-
const { mixtapeDistributeCommand:
|
|
7651
|
-
await runMixtapeDistribute(idOrLogId, options,
|
|
7305
|
+
const { mixtapeDistributeCommand: mixtapeDistributeCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7306
|
+
await runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2);
|
|
7652
7307
|
});
|
|
7653
7308
|
adminMixtapes.command("publish-youtube").description("Flip a distributed mixtape's YouTube video from unlisted to public").argument("[idOrLogId]").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
7654
7309
|
const { publishYoutubeCommand: publishYoutubeCommand3 } = await Promise.resolve().then(() => (init_mixtape_youtube2(), exports_mixtape_youtube2));
|
|
7655
7310
|
await runMixtapePublishYoutube(idOrLogId, options, publishYoutubeCommand3);
|
|
7656
7311
|
});
|
|
7657
7312
|
adminMixtapes.command("resync").description("Re-push a published mixtape's YouTube chapters + Mixcloud sections from its current cues (no re-upload)").argument("[idOrLogId]").option("--youtube", "Only re-sync YouTube").option("--mixcloud", "Only re-sync Mixcloud").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
7658
|
-
const { mixtapeResyncCommand:
|
|
7659
|
-
await runMixtapeResync(idOrLogId, options,
|
|
7313
|
+
const { mixtapeResyncCommand: mixtapeResyncCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7314
|
+
await runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2);
|
|
7660
7315
|
});
|
|
7661
7316
|
const adminClips = configureCommand(admin.command("clips").description("Mixtape clip (Fluncle Studio) commands"));
|
|
7662
7317
|
adminClips.action(() => {
|
|
7663
7318
|
adminClips.outputHelp();
|
|
7664
7319
|
});
|
|
7665
|
-
adminClips.command("list").description("List clips (filter by --status pending|done
|
|
7320
|
+
adminClips.command("list").description("List clips (filter by --status pending|done and/or --recording <id>)").option("--status <status>", "Filter by cut status (pending|done)").option("--recording <id>", "Filter by recording id").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7666
7321
|
const { clipsListCommand: clipsListCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
7667
7322
|
await runClipsList(options, clipsListCommand2);
|
|
7668
7323
|
});
|
|
@@ -7805,7 +7460,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
|
|
|
7805
7460
|
console.log(` mime: ${result.mime}`);
|
|
7806
7461
|
}
|
|
7807
7462
|
async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
|
|
7808
|
-
const script = options.scriptFile ?
|
|
7463
|
+
const script = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
|
|
7809
7464
|
if (!idOrLogId || !script || !script.trim()) {
|
|
7810
7465
|
throw new Error("Usage: fluncle admin tracks observe <track_id|log_id> (--script <text> | --script-file <file>) [--voice-id <id>] [--duration-ms <ms>] [--context-note <text>] [--json]");
|
|
7811
7466
|
}
|
|
@@ -7861,7 +7516,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
|
|
|
7861
7516
|
}
|
|
7862
7517
|
}
|
|
7863
7518
|
async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
|
|
7864
|
-
const note = options.scriptFile ?
|
|
7519
|
+
const note = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
|
|
7865
7520
|
if (!idOrLogId || !note || !note.trim()) {
|
|
7866
7521
|
throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
|
|
7867
7522
|
}
|
|
@@ -8007,7 +7662,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
8007
7662
|
return;
|
|
8008
7663
|
}
|
|
8009
7664
|
const candidate = path2.join(dir, name);
|
|
8010
|
-
return
|
|
7665
|
+
return existsSync5(candidate) ? candidate : undefined;
|
|
8011
7666
|
};
|
|
8012
7667
|
const resolveFile = (explicit, name) => {
|
|
8013
7668
|
if (explicit) {
|
|
@@ -8184,56 +7839,23 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand2) {
|
|
|
8184
7839
|
}
|
|
8185
7840
|
console.log(result.noVideo ? `${result.logId} has no video \u2014 nothing to purge.` : `Purging the stale renditions for ${result.logId} from the edge. The next play picks up the fresh render.`);
|
|
8186
7841
|
}
|
|
8187
|
-
async function
|
|
8188
|
-
const result = await mixtapeCreateCommand3(options);
|
|
8189
|
-
if (options.json) {
|
|
8190
|
-
printJson(result);
|
|
8191
|
-
return;
|
|
8192
|
-
}
|
|
8193
|
-
console.log(`Logged draft ${result.mixtape.id}. It stays a draft until you publish it.`);
|
|
8194
|
-
}
|
|
8195
|
-
async function runMixtapeUpdate(id, options, mixtapeUpdateCommand3) {
|
|
7842
|
+
async function runMixtapeUpdate(id, options, mixtapeUpdateCommand2) {
|
|
8196
7843
|
if (!id) {
|
|
8197
7844
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes update <id>");
|
|
8198
7845
|
}
|
|
8199
|
-
const result = await
|
|
7846
|
+
const result = await mixtapeUpdateCommand2(id, options);
|
|
8200
7847
|
if (options.json) {
|
|
8201
7848
|
printJson(result);
|
|
8202
7849
|
return;
|
|
8203
7850
|
}
|
|
8204
7851
|
console.log(`Saved ${result.mixtape.id}.`);
|
|
8205
7852
|
}
|
|
8206
|
-
async function
|
|
8207
|
-
if (!id) {
|
|
8208
|
-
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes members <id> [refs...]");
|
|
8209
|
-
}
|
|
8210
|
-
if (refs.length === 0 && !options.from) {
|
|
8211
|
-
throw new Error("Provide refs as arguments or a cue-sheet file with --from");
|
|
8212
|
-
}
|
|
8213
|
-
const result = await mixtapeMembersCommand3(id, refs, options);
|
|
8214
|
-
if (options.json) {
|
|
8215
|
-
printJson(result);
|
|
8216
|
-
return;
|
|
8217
|
-
}
|
|
8218
|
-
console.log(`Saved the tracklist: ${result.mixtape.memberCount} bangers on ${result.mixtape.id}.`);
|
|
8219
|
-
}
|
|
8220
|
-
async function runMixtapePublish(id, options, mixtapePublishCommand3) {
|
|
8221
|
-
if (!id) {
|
|
8222
|
-
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes publish <id>");
|
|
8223
|
-
}
|
|
8224
|
-
const result = await mixtapePublishCommand3(id);
|
|
8225
|
-
if (options.json) {
|
|
8226
|
-
printJson(result);
|
|
8227
|
-
return;
|
|
8228
|
-
}
|
|
8229
|
-
console.log(`Minted ${result.mixtape.logId} (fluncle://${result.mixtape.logId}) \u2014 distributing. ` + "Run `distribute` to push it to the platforms.");
|
|
8230
|
-
}
|
|
8231
|
-
async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand3) {
|
|
7853
|
+
async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2) {
|
|
8232
7854
|
if (!idOrLogId) {
|
|
8233
7855
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes distribute <idOrLogId> --video <mp4> --audio <file>");
|
|
8234
7856
|
}
|
|
8235
7857
|
const onProgress = options.json ? () => {} : (message) => console.log(message);
|
|
8236
|
-
const result = await
|
|
7858
|
+
const result = await mixtapeDistributeCommand2(idOrLogId, options, onProgress);
|
|
8237
7859
|
if (options.json) {
|
|
8238
7860
|
printJson(result);
|
|
8239
7861
|
return;
|
|
@@ -8254,12 +7876,12 @@ async function runMixtapePublishYoutube(idOrLogId, options, publishYoutubeComman
|
|
|
8254
7876
|
}
|
|
8255
7877
|
console.log(`YouTube video is now public: ${result.url}`);
|
|
8256
7878
|
}
|
|
8257
|
-
async function runMixtapeResync(idOrLogId, options,
|
|
7879
|
+
async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2) {
|
|
8258
7880
|
if (!idOrLogId) {
|
|
8259
7881
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes resync <idOrLogId>");
|
|
8260
7882
|
}
|
|
8261
7883
|
const onProgress = options.json ? () => {} : (message) => console.log(message);
|
|
8262
|
-
const result = await
|
|
7884
|
+
const result = await mixtapeResyncCommand2(idOrLogId, options, onProgress);
|
|
8263
7885
|
if (options.json) {
|
|
8264
7886
|
printJson(result);
|
|
8265
7887
|
return;
|
|
@@ -8269,20 +7891,6 @@ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3) {
|
|
|
8269
7891
|
console.log(`Re-synced ${result.logId} (fluncle://${result.logId}).
|
|
8270
7892
|
${links}`);
|
|
8271
7893
|
}
|
|
8272
|
-
async function runMixtapeDelete(id, options, mixtapeDeleteCommand3) {
|
|
8273
|
-
if (!id) {
|
|
8274
|
-
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes delete <id> --yes");
|
|
8275
|
-
}
|
|
8276
|
-
if (!options.yes) {
|
|
8277
|
-
throw new Error("Pass --yes to confirm the discard. Published mixtapes can't be deleted.");
|
|
8278
|
-
}
|
|
8279
|
-
await mixtapeDeleteCommand3(id);
|
|
8280
|
-
if (options.json) {
|
|
8281
|
-
printJson({ id, ok: true });
|
|
8282
|
-
return;
|
|
8283
|
-
}
|
|
8284
|
-
console.log(`Discarded draft ${id}.`);
|
|
8285
|
-
}
|
|
8286
7894
|
async function runMixtapeList(options, mixtapeListCommand2) {
|
|
8287
7895
|
const mixtapes = await mixtapeListCommand2();
|
|
8288
7896
|
if (options.json) {
|
|
@@ -8294,14 +7902,13 @@ async function runMixtapeList(options, mixtapeListCommand2) {
|
|
|
8294
7902
|
return;
|
|
8295
7903
|
}
|
|
8296
7904
|
for (const mixtape of mixtapes) {
|
|
8297
|
-
const status = mixtape.status ?? "
|
|
8298
|
-
const coordinate3 = mixtape.logId ?? "
|
|
7905
|
+
const status = mixtape.status ?? "distributing";
|
|
7906
|
+
const coordinate3 = mixtape.logId ?? "unminted";
|
|
8299
7907
|
console.log(`${coordinate3} ${status} ${mixtape.memberCount} bangers ${mixtape.title}`);
|
|
8300
7908
|
}
|
|
8301
7909
|
}
|
|
8302
7910
|
async function runClipsList(options, clipsListCommand2) {
|
|
8303
7911
|
const clips = await clipsListCommand2({
|
|
8304
|
-
mixtapeId: options.mixtape,
|
|
8305
7912
|
recordingId: options.recording,
|
|
8306
7913
|
status: options.status
|
|
8307
7914
|
});
|
|
@@ -8314,7 +7921,7 @@ async function runClipsList(options, clipsListCommand2) {
|
|
|
8314
7921
|
return;
|
|
8315
7922
|
}
|
|
8316
7923
|
for (const clip of clips) {
|
|
8317
|
-
const source = clip.recordingId ??
|
|
7924
|
+
const source = clip.recordingId ?? "\u2014";
|
|
8318
7925
|
console.log(`${clip.id} ${clip.status} ${source} ${clip.inMs}-${clip.outMs}ms x=${clip.xOffset}`);
|
|
8319
7926
|
}
|
|
8320
7927
|
}
|
|
@@ -8339,8 +7946,8 @@ async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand2) {
|
|
|
8339
7946
|
printJson(mixtape);
|
|
8340
7947
|
return;
|
|
8341
7948
|
}
|
|
8342
|
-
const coordinate3 = mixtape.logId ?? "
|
|
8343
|
-
const status = mixtape.status ?? "
|
|
7949
|
+
const coordinate3 = mixtape.logId ?? "unminted";
|
|
7950
|
+
const status = mixtape.status ?? "distributing";
|
|
8344
7951
|
console.log(`${mixtape.title}`);
|
|
8345
7952
|
console.log(` ${coordinate3} \xB7 ${status} \xB7 ${mixtape.memberCount} bangers`);
|
|
8346
7953
|
if (mixtape.note) {
|
|
@@ -8477,8 +8084,8 @@ async function runRecent(options, recentCommand3) {
|
|
|
8477
8084
|
console.log(trackRows2(tracks).join(`
|
|
8478
8085
|
`));
|
|
8479
8086
|
}
|
|
8480
|
-
async function runMixtapes(options,
|
|
8481
|
-
const mixtapes = await
|
|
8087
|
+
async function runMixtapes(options, mixtapesCommand2) {
|
|
8088
|
+
const mixtapes = await mixtapesCommand2();
|
|
8482
8089
|
if (options.json) {
|
|
8483
8090
|
printJson({ mixtapes, ok: true });
|
|
8484
8091
|
return;
|
package/package.json
CHANGED