fluncle 0.76.0 → 0.78.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 (2) hide show
  1. package/bin/fluncle.mjs +140 -27
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -482,6 +482,20 @@ function resolveClipTracks(options) {
482
482
  startMs: member.startMs
483
483
  }));
484
484
  }
485
+ function mixcloudSections(members) {
486
+ return members.filter((member) => member.startMs != null).sort((a, b) => a.startMs - b.startMs).map((member) => ({
487
+ artist: member.artists.join(", "),
488
+ song: member.title,
489
+ start_time: Math.floor(member.startMs / 1000)
490
+ }));
491
+ }
492
+ function mixcloudSectionFields(sections) {
493
+ return sections.flatMap((section, index) => [
494
+ [`sections-${index}-artist`, section.artist],
495
+ [`sections-${index}-song`, section.song],
496
+ [`sections-${index}-start_time`, String(section.start_time)]
497
+ ]);
498
+ }
485
499
  function baseTitleMatches(findingTitle, candidateTitle) {
486
500
  const want = new Set(tokenize(stripVersionSuffix(findingTitle)));
487
501
  const have = new Set(tokenize(stripVersionSuffix(candidateTitle)));
@@ -544,7 +558,7 @@ function parseVersion(version) {
544
558
  var currentVersion;
545
559
  var init_version = __esm(() => {
546
560
  init_output();
547
- currentVersion = "0.76.0".trim() ? "0.76.0".trim() : "0.1.0";
561
+ currentVersion = "0.78.0".trim() ? "0.78.0".trim() : "0.1.0";
548
562
  });
549
563
 
550
564
  // src/update-notifier.ts
@@ -1029,6 +1043,7 @@ var init_mixtape_api = __esm(() => {
1029
1043
  // src/commands/mixtape-youtube.ts
1030
1044
  var exports_mixtape_youtube = {};
1031
1045
  __export(exports_mixtape_youtube, {
1046
+ resyncYoutube: () => resyncYoutube,
1032
1047
  publishYoutubeCommand: () => publishYoutubeCommand,
1033
1048
  nextOffset: () => nextOffset,
1034
1049
  distributeYoutube: () => distributeYoutube,
@@ -1084,6 +1099,10 @@ async function publishYoutubeCommand(idOrLogId) {
1084
1099
  const response = await adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/youtube/publish`);
1085
1100
  return { url: response.url };
1086
1101
  }
1102
+ async function resyncYoutube(mixtapeId) {
1103
+ const response = await adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/youtube/resync`);
1104
+ return { url: response.url, videoId: response.videoId };
1105
+ }
1087
1106
  async function authYoutubeCommand() {
1088
1107
  const response = await adminApiGet("/api/admin/youtube/auth/start");
1089
1108
  console.log(`Open this YouTube authorization URL:
@@ -1189,8 +1208,8 @@ var init_mixtape_youtube = __esm(() => {
1189
1208
  // src/commands/mixtape-mixcloud.ts
1190
1209
  var exports_mixtape_mixcloud = {};
1191
1210
  __export(exports_mixtape_mixcloud, {
1211
+ resyncMixcloud: () => resyncMixcloud,
1192
1212
  mixtapeDescription: () => mixtapeDescription,
1193
- mixcloudSections: () => mixcloudSections,
1194
1213
  distributeMixcloud: () => distributeMixcloud,
1195
1214
  authMixcloudCommand: () => authMixcloudCommand
1196
1215
  });
@@ -1218,10 +1237,8 @@ async function distributeMixcloud(mixtapeId, audioPath, onProgress, unlisted = f
1218
1237
  form.append(`tags-${index}-tag`, tag);
1219
1238
  }
1220
1239
  const sections = mixcloudSections(mixtape.members);
1221
- for (const [index, section] of sections.entries()) {
1222
- form.append(`sections-${index}-artist`, section.artist);
1223
- form.append(`sections-${index}-song`, section.song);
1224
- form.append(`sections-${index}-start_time`, String(section.start_time));
1240
+ for (const [name, value] of mixcloudSectionFields(sections)) {
1241
+ form.append(name, value);
1225
1242
  }
1226
1243
  if (unlisted) {
1227
1244
  form.append("unlisted", "1");
@@ -1253,6 +1270,10 @@ async function distributeMixcloud(mixtapeId, audioPath, onProgress, unlisted = f
1253
1270
  });
1254
1271
  return { url };
1255
1272
  }
1273
+ async function resyncMixcloud(mixtapeId) {
1274
+ const response = await adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/mixcloud/resync`);
1275
+ return { url: response.url };
1276
+ }
1256
1277
  async function authMixcloudCommand() {
1257
1278
  const response = await adminApiGet("/api/admin/mixcloud/auth/start");
1258
1279
  console.log(`Open this Mixcloud authorization URL:
@@ -1276,13 +1297,6 @@ ${breadcrumb}` : breadcrumb;
1276
1297
 
1277
1298
  ${breadcrumb}` : breadcrumb;
1278
1299
  }
1279
- function mixcloudSections(members) {
1280
- return members.filter((member) => typeof member.startMs === "number").sort((a, b) => a.startMs - b.startMs).map((member) => ({
1281
- artist: member.artists.join(", "),
1282
- song: member.title,
1283
- start_time: Math.floor(member.startMs / 1000)
1284
- }));
1285
- }
1286
1300
  function mixtapeTags(_mixtape) {
1287
1301
  return ["Drum & Bass", "Fluncle"];
1288
1302
  }
@@ -1327,6 +1341,7 @@ function throwMixcloudError(status, body) {
1327
1341
  }
1328
1342
  var MIXCLOUD_API = "https://api.mixcloud.com", DESCRIPTION_MAX = 1000, PICTURE_MAX_BYTES, COVER_BASE = "https://www.fluncle.com/api/mixtape-cover";
1329
1343
  var init_mixtape_mixcloud = __esm(() => {
1344
+ init_util();
1330
1345
  init_api();
1331
1346
  init_output();
1332
1347
  init_mixtape_api();
@@ -1509,6 +1524,7 @@ var exports_mixtapes = {};
1509
1524
  __export(exports_mixtapes, {
1510
1525
  mixtapesCommand: () => mixtapesCommand,
1511
1526
  mixtapeUpdateCommand: () => mixtapeUpdateCommand,
1527
+ mixtapeResyncCommand: () => mixtapeResyncCommand,
1512
1528
  mixtapePublishCommand: () => mixtapePublishCommand,
1513
1529
  mixtapeMembersCommand: () => mixtapeMembersCommand,
1514
1530
  mixtapeListCommand: () => mixtapeListCommand,
@@ -1615,6 +1631,44 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
1615
1631
  }
1616
1632
  return { logId, mixtapeId, results };
1617
1633
  }
1634
+ async function mixtapeResyncCommand(idOrLogId, options, onProgress = () => {}) {
1635
+ const mixtape = await mixtapeGetCommand(idOrLogId);
1636
+ if (!mixtape.id) {
1637
+ throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
1638
+ }
1639
+ if (!mixtape.logId) {
1640
+ throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet — distribute it before re-syncing.");
1641
+ }
1642
+ const mixtapeId = mixtape.id;
1643
+ const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
1644
+ let doYoutube = Boolean(options.youtube);
1645
+ let doMixcloud = Boolean(options.mixcloud);
1646
+ if (!explicit) {
1647
+ const social = await adminApiGet(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/social`);
1648
+ const platforms = new Set(social.posts.map((post) => post.platform));
1649
+ doYoutube = platforms.has("youtube");
1650
+ doMixcloud = platforms.has("mixcloud");
1651
+ if (!doYoutube && !doMixcloud) {
1652
+ throw new CliError2("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
1653
+ }
1654
+ }
1655
+ const results = [];
1656
+ if (doYoutube) {
1657
+ onProgress("YouTube: re-syncing description + chapters…");
1658
+ const { resyncYoutube: resyncYoutube2 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
1659
+ const result = await resyncYoutube2(mixtapeId);
1660
+ results.push({ platform: "youtube", url: result.url });
1661
+ onProgress(`YouTube: ${result.url}`);
1662
+ }
1663
+ if (doMixcloud) {
1664
+ onProgress("Mixcloud: re-syncing sections…");
1665
+ const { resyncMixcloud: resyncMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
1666
+ const result = await resyncMixcloud2(mixtapeId);
1667
+ results.push({ platform: "mixcloud", url: result.url });
1668
+ onProgress(`Mixcloud: ${result.url}`);
1669
+ }
1670
+ return { logId: mixtape.logId, mixtapeId, results };
1671
+ }
1618
1672
  async function probeDurationMs(filePath) {
1619
1673
  try {
1620
1674
  const proc = Bun.spawn(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", filePath], { stderr: "ignore", stdout: "pipe" });
@@ -2401,7 +2455,7 @@ function parseVersion2(version) {
2401
2455
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2402
2456
  var init_version2 = __esm(() => {
2403
2457
  init_output();
2404
- currentVersion2 = "0.76.0".trim() ? "0.76.0".trim() : "0.1.0";
2458
+ currentVersion2 = "0.78.0".trim() ? "0.78.0".trim() : "0.1.0";
2405
2459
  });
2406
2460
 
2407
2461
  // ../../packages/registry/src/index.ts
@@ -3626,6 +3680,7 @@ var init_preview_archive = __esm(() => {
3626
3680
  // src/commands/mixtape-youtube.ts
3627
3681
  var exports_mixtape_youtube2 = {};
3628
3682
  __export(exports_mixtape_youtube2, {
3683
+ resyncYoutube: () => resyncYoutube2,
3629
3684
  publishYoutubeCommand: () => publishYoutubeCommand2,
3630
3685
  nextOffset: () => nextOffset2,
3631
3686
  distributeYoutube: () => distributeYoutube2,
@@ -3681,6 +3736,10 @@ async function publishYoutubeCommand2(idOrLogId) {
3681
3736
  const response = await adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/youtube/publish`);
3682
3737
  return { url: response.url };
3683
3738
  }
3739
+ async function resyncYoutube2(mixtapeId) {
3740
+ const response = await adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/youtube/resync`);
3741
+ return { url: response.url, videoId: response.videoId };
3742
+ }
3684
3743
  async function authYoutubeCommand2() {
3685
3744
  const response = await adminApiGet("/api/admin/youtube/auth/start");
3686
3745
  console.log(`Open this YouTube authorization URL:
@@ -3788,6 +3847,7 @@ var exports_mixtapes2 = {};
3788
3847
  __export(exports_mixtapes2, {
3789
3848
  mixtapesCommand: () => mixtapesCommand2,
3790
3849
  mixtapeUpdateCommand: () => mixtapeUpdateCommand2,
3850
+ mixtapeResyncCommand: () => mixtapeResyncCommand2,
3791
3851
  mixtapePublishCommand: () => mixtapePublishCommand2,
3792
3852
  mixtapeMembersCommand: () => mixtapeMembersCommand2,
3793
3853
  mixtapeListCommand: () => mixtapeListCommand,
@@ -3894,6 +3954,44 @@ async function mixtapeDistributeCommand2(idOrLogId, options, onProgress = () =>
3894
3954
  }
3895
3955
  return { logId, mixtapeId, results };
3896
3956
  }
3957
+ async function mixtapeResyncCommand2(idOrLogId, options, onProgress = () => {}) {
3958
+ const mixtape = await mixtapeGetCommand(idOrLogId);
3959
+ if (!mixtape.id) {
3960
+ throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
3961
+ }
3962
+ if (!mixtape.logId) {
3963
+ throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet — distribute it before re-syncing.");
3964
+ }
3965
+ const mixtapeId = mixtape.id;
3966
+ const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
3967
+ let doYoutube = Boolean(options.youtube);
3968
+ let doMixcloud = Boolean(options.mixcloud);
3969
+ if (!explicit) {
3970
+ const social = await adminApiGet(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/social`);
3971
+ const platforms = new Set(social.posts.map((post) => post.platform));
3972
+ doYoutube = platforms.has("youtube");
3973
+ doMixcloud = platforms.has("mixcloud");
3974
+ if (!doYoutube && !doMixcloud) {
3975
+ throw new CliError2("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
3976
+ }
3977
+ }
3978
+ const results = [];
3979
+ if (doYoutube) {
3980
+ onProgress("YouTube: re-syncing description + chapters…");
3981
+ const { resyncYoutube: resyncYoutube3 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
3982
+ const result = await resyncYoutube3(mixtapeId);
3983
+ results.push({ platform: "youtube", url: result.url });
3984
+ onProgress(`YouTube: ${result.url}`);
3985
+ }
3986
+ if (doMixcloud) {
3987
+ onProgress("Mixcloud: re-syncing sections…");
3988
+ const { resyncMixcloud: resyncMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
3989
+ const result = await resyncMixcloud2(mixtapeId);
3990
+ results.push({ platform: "mixcloud", url: result.url });
3991
+ onProgress(`Mixcloud: ${result.url}`);
3992
+ }
3993
+ return { logId: mixtape.logId, mixtapeId, results };
3994
+ }
3897
3995
  async function probeDurationMs2(filePath) {
3898
3996
  try {
3899
3997
  const proc = Bun.spawn(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", filePath], { stderr: "ignore", stdout: "pipe" });
@@ -4480,8 +4578,8 @@ var init_auth = __esm(() => {
4480
4578
  // src/commands/mixtape-mixcloud.ts
4481
4579
  var exports_mixtape_mixcloud2 = {};
4482
4580
  __export(exports_mixtape_mixcloud2, {
4581
+ resyncMixcloud: () => resyncMixcloud2,
4483
4582
  mixtapeDescription: () => mixtapeDescription2,
4484
- mixcloudSections: () => mixcloudSections2,
4485
4583
  distributeMixcloud: () => distributeMixcloud2,
4486
4584
  authMixcloudCommand: () => authMixcloudCommand2
4487
4585
  });
@@ -4508,11 +4606,9 @@ async function distributeMixcloud2(mixtapeId, audioPath, onProgress, unlisted =
4508
4606
  for (const [index, tag] of mixtapeTags2(mixtape).entries()) {
4509
4607
  form.append(`tags-${index}-tag`, tag);
4510
4608
  }
4511
- const sections = mixcloudSections2(mixtape.members);
4512
- for (const [index, section] of sections.entries()) {
4513
- form.append(`sections-${index}-artist`, section.artist);
4514
- form.append(`sections-${index}-song`, section.song);
4515
- form.append(`sections-${index}-start_time`, String(section.start_time));
4609
+ const sections = mixcloudSections(mixtape.members);
4610
+ for (const [name, value] of mixcloudSectionFields(sections)) {
4611
+ form.append(name, value);
4516
4612
  }
4517
4613
  if (unlisted) {
4518
4614
  form.append("unlisted", "1");
@@ -4544,6 +4640,10 @@ async function distributeMixcloud2(mixtapeId, audioPath, onProgress, unlisted =
4544
4640
  });
4545
4641
  return { url };
4546
4642
  }
4643
+ async function resyncMixcloud2(mixtapeId) {
4644
+ const response = await adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/mixcloud/resync`);
4645
+ return { url: response.url };
4646
+ }
4547
4647
  async function authMixcloudCommand2() {
4548
4648
  const response = await adminApiGet("/api/admin/mixcloud/auth/start");
4549
4649
  console.log(`Open this Mixcloud authorization URL:
@@ -4567,13 +4667,6 @@ ${breadcrumb}` : breadcrumb;
4567
4667
 
4568
4668
  ${breadcrumb}` : breadcrumb;
4569
4669
  }
4570
- function mixcloudSections2(members) {
4571
- return members.filter((member) => typeof member.startMs === "number").sort((a, b) => a.startMs - b.startMs).map((member) => ({
4572
- artist: member.artists.join(", "),
4573
- song: member.title,
4574
- start_time: Math.floor(member.startMs / 1000)
4575
- }));
4576
- }
4577
4670
  function mixtapeTags2(_mixtape) {
4578
4671
  return ["Drum & Bass", "Fluncle"];
4579
4672
  }
@@ -4618,6 +4711,7 @@ function throwMixcloudError2(status, body) {
4618
4711
  }
4619
4712
  var MIXCLOUD_API2 = "https://api.mixcloud.com", DESCRIPTION_MAX2 = 1000, PICTURE_MAX_BYTES2, COVER_BASE2 = "https://www.fluncle.com/api/mixtape-cover";
4620
4713
  var init_mixtape_mixcloud2 = __esm(() => {
4714
+ init_util();
4621
4715
  init_api();
4622
4716
  init_output();
4623
4717
  init_mixtape_api();
@@ -7295,6 +7389,10 @@ function addAdminCommands(program2) {
7295
7389
  const { publishYoutubeCommand: publishYoutubeCommand3 } = await Promise.resolve().then(() => (init_mixtape_youtube2(), exports_mixtape_youtube2));
7296
7390
  await runMixtapePublishYoutube(idOrLogId, options, publishYoutubeCommand3);
7297
7391
  });
7392
+ 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) => {
7393
+ const { mixtapeResyncCommand: mixtapeResyncCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7394
+ await runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3);
7395
+ });
7298
7396
  const adminClips = configureCommand(admin.command("clips").description("Mixtape clip (Fluncle Studio) commands"));
7299
7397
  adminClips.action(() => {
7300
7398
  adminClips.outputHelp();
@@ -7854,6 +7952,21 @@ async function runMixtapePublishYoutube(idOrLogId, options, publishYoutubeComman
7854
7952
  }
7855
7953
  console.log(`YouTube video is now public: ${result.url}`);
7856
7954
  }
7955
+ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3) {
7956
+ if (!idOrLogId) {
7957
+ throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes resync <idOrLogId>");
7958
+ }
7959
+ const onProgress = options.json ? () => {} : (message) => console.log(message);
7960
+ const result = await mixtapeResyncCommand3(idOrLogId, options, onProgress);
7961
+ if (options.json) {
7962
+ printJson(result);
7963
+ return;
7964
+ }
7965
+ const links = result.results.map((r) => ` ${r.platform}: ${r.url}`).join(`
7966
+ `);
7967
+ console.log(`Re-synced ${result.logId} (fluncle://${result.logId}).
7968
+ ${links}`);
7969
+ }
7857
7970
  async function runMixtapeDelete(id, options, mixtapeDeleteCommand3) {
7858
7971
  if (!id) {
7859
7972
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes delete <id> --yes");
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.76.0"
34
+ "version": "0.78.0"
35
35
  }