fluncle 0.82.0 → 0.84.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 +18 -163
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -462,26 +462,6 @@ function versionMatches(findingTitle, candidateTitle) {
462
462
  }
463
463
  return !candidateIsRemix;
464
464
  }
465
- function trackLabel(artists, title) {
466
- const joined = artists.join(", ");
467
- return joined && title ? `${joined} — ${title}` : joined || title;
468
- }
469
- function resolveClipTracks(options) {
470
- const { inMs, members, outMs, setDurationMs } = options;
471
- const cued = members.filter((member) => member.startMs != null).sort((a, b) => a.startMs - b.startMs);
472
- if (cued.length === 0) {
473
- return [];
474
- }
475
- const lastIndex = cued.length - 1;
476
- return cued.filter((member, index) => {
477
- const lo = index === 0 ? Math.min(member.startMs, inMs) : member.startMs;
478
- const hi = index === lastIndex ? Math.max(setDurationMs, outMs) : cued[index + 1]?.startMs ?? Number.POSITIVE_INFINITY;
479
- return lo < outMs && inMs < hi;
480
- }).map((member) => ({
481
- label: trackLabel(member.artists, member.title),
482
- startMs: member.startMs
483
- }));
484
- }
485
465
  function mixcloudSections(members) {
486
466
  return members.filter((member) => member.startMs != null).sort((a, b) => a.startMs - b.startMs).map((member) => ({
487
467
  artist: member.artists.join(", "),
@@ -558,7 +538,7 @@ function parseVersion(version) {
558
538
  var currentVersion;
559
539
  var init_version = __esm(() => {
560
540
  init_output();
561
- currentVersion = "0.82.0".trim() ? "0.82.0".trim() : "0.1.0";
541
+ currentVersion = "0.84.0".trim() ? "0.84.0".trim() : "0.1.0";
562
542
  });
563
543
 
564
544
  // src/update-notifier.ts
@@ -2466,7 +2446,7 @@ function parseVersion2(version) {
2466
2446
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2467
2447
  var init_version2 = __esm(() => {
2468
2448
  init_output();
2469
- currentVersion2 = "0.82.0".trim() ? "0.82.0".trim() : "0.1.0";
2449
+ currentVersion2 = "0.84.0".trim() ? "0.84.0".trim() : "0.1.0";
2470
2450
  });
2471
2451
 
2472
2452
  // ../../packages/registry/src/index.ts
@@ -3925,7 +3905,7 @@ async function recordingGetCommand(id, options = {}) {
3925
3905
  return;
3926
3906
  }
3927
3907
  console.log(formatRecordingSummary(recording));
3928
- console.log(` r2Key: ${recording.r2Key}`);
3908
+ console.log(` r2Key: ${recording.r2Key ?? "— (no set video)"}`);
3929
3909
  for (const cue of recording.tracklist) {
3930
3910
  const at = cue.startMs === undefined ? "—" : `${(cue.startMs / 1000).toFixed(1)}s`;
3931
3911
  console.log(` ${at} ${cue.artists.join(", ")} — ${cue.title}`);
@@ -4271,135 +4251,32 @@ var init_mixtapes2 = __esm(() => {
4271
4251
  var exports_clips = {};
4272
4252
  __export(exports_clips, {
4273
4253
  setVideoUrl: () => setVideoUrl,
4274
- resolveClipSansFontFile: () => resolveClipSansFontFile,
4275
- resolveClipFontFile: () => resolveClipFontFile,
4276
- escapeDrawtextValue: () => escapeDrawtextValue,
4277
4254
  clipsListCommand: () => clipsListCommand,
4278
4255
  clipFootageKey: () => clipFootageKey,
4279
4256
  clipCutFilterComplex: () => clipCutFilterComplex,
4280
4257
  clipCutFfmpegArgs: () => clipCutFfmpegArgs,
4281
4258
  clipCutCommand: () => clipCutCommand,
4282
- brandDrawtext: () => brandDrawtext,
4283
4259
  MAX_CLIP_BYTES: () => MAX_CLIP_BYTES,
4284
4260
  CLIP_WIDTH: () => CLIP_WIDTH,
4285
- CLIP_TITLE_SIZE: () => CLIP_TITLE_SIZE,
4286
- CLIP_TITLE_COLOR: () => CLIP_TITLE_COLOR,
4287
- CLIP_SHARP_BORDERW: () => CLIP_SHARP_BORDERW,
4288
- CLIP_SAFE_BOTTOM: () => CLIP_SAFE_BOTTOM,
4289
4261
  CLIP_MAXRATE: () => CLIP_MAXRATE,
4290
- CLIP_MARGIN_X: () => CLIP_MARGIN_X,
4291
- CLIP_LINE_GAP: () => CLIP_LINE_GAP,
4292
4262
  CLIP_HEIGHT: () => CLIP_HEIGHT,
4293
- CLIP_HALO_FEATHER_SIGMA: () => CLIP_HALO_FEATHER_SIGMA,
4294
- CLIP_HALO_CORE_SIGMA: () => CLIP_HALO_CORE_SIGMA,
4295
- CLIP_HALO_COLOR: () => CLIP_HALO_COLOR,
4296
4263
  CLIP_CRF: () => CLIP_CRF,
4297
- CLIP_COORDINATE_SIZE: () => CLIP_COORDINATE_SIZE,
4298
- CLIP_COORDINATE_COLOR: () => CLIP_COORDINATE_COLOR,
4299
4264
  CLIP_BUFSIZE: () => CLIP_BUFSIZE,
4300
- CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE,
4301
- BOX_CLIP_SANS_FONT_FILE: () => BOX_CLIP_SANS_FONT_FILE,
4302
- BOX_CLIP_FONT_FILE: () => BOX_CLIP_FONT_FILE
4265
+ CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE
4303
4266
  });
4304
4267
  import { randomUUID as randomUUID2 } from "node:crypto";
4305
- import { existsSync as existsSync5, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
4268
+ import { rmSync as rmSync3, statSync as statSync4 } from "node:fs";
4306
4269
  import { tmpdir as tmpdir2 } from "node:os";
4307
4270
  import { join as join5 } from "node:path";
4308
4271
  function clipFootageKey(clipId) {
4309
4272
  return `${clipId}/footage.mp4`;
4310
4273
  }
4311
- function resolveFontFile(envVar, bakedPath) {
4312
- const explicit = process.env[envVar];
4313
- if (explicit) {
4314
- return explicit;
4315
- }
4316
- return existsSync5(bakedPath) ? bakedPath : undefined;
4317
- }
4318
- function resolveClipFontFile() {
4319
- return resolveFontFile("CLIP_FONT_FILE", BOX_CLIP_FONT_FILE);
4320
- }
4321
- function resolveClipSansFontFile() {
4322
- return resolveFontFile("CLIP_SANS_FONT_FILE", BOX_CLIP_SANS_FONT_FILE);
4323
- }
4324
4274
  function setVideoUrl(logId) {
4325
4275
  return `${FOUND_BASE3}/${encodeURIComponent(logId)}/set.mp4`;
4326
4276
  }
4327
- function escapeDrawtextValue(text) {
4328
- return text.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/,/g, "\\,").replace(/%/g, "\\%").replace(/'/g, "'\\''");
4329
- }
4330
- function brandDrawtext(options) {
4331
- const value = escapeDrawtextValue(options.text);
4332
- const font = options.fontFile ? `:fontfile='${options.fontFile}'` : "";
4333
- const border = options.borderw && options.borderw > 0 ? `:borderw=${options.borderw}:bordercolor=${CLIP_HALO_COLOR}` : "";
4334
- const enable = options.enable ? `:enable='${options.enable}'` : "";
4335
- return `drawtext=text='${value}'${font}:` + `fontcolor=${options.color}:fontsize=${options.size}:` + `x=${options.x}:y=${options.y}${border}${enable}`;
4336
- }
4337
- function resolveTitleLines(options, y) {
4338
- const inMs = options.inMs ?? 0;
4339
- const outMs = options.outMs ?? 0;
4340
- const clipDur = Math.max(0, (outMs - inMs) / 1000);
4341
- const base = { fontFile: options.sansFontFile, size: CLIP_TITLE_SIZE, x: CLIP_MARGIN_X, y };
4342
- const resolved = options.members && options.members.length > 0 ? resolveClipTracks({
4343
- inMs,
4344
- members: options.members,
4345
- outMs,
4346
- setDurationMs: options.setDurationMs ?? 0
4347
- }) : [];
4348
- if (resolved.length === 0) {
4349
- return [{ ...base, text: options.title }];
4350
- }
4351
- const clamp = (seconds) => Math.min(Math.max(seconds, 0), clipDur);
4352
- return resolved.map((track, index) => {
4353
- const relStart = clamp((track.startMs - inMs) / 1000);
4354
- const next = resolved[index + 1];
4355
- const relEnd = next ? clamp((next.startMs - inMs) / 1000) : clipDur;
4356
- return {
4357
- ...base,
4358
- enable: `between(t,${relStart.toFixed(3)},${relEnd.toFixed(3)})`,
4359
- text: track.label
4360
- };
4361
- });
4362
- }
4363
4277
  function clipCutFilterComplex(options) {
4364
4278
  const xOffset = Math.max(0, Math.round(options.xOffset));
4365
- const crop = `crop=ih*9/16:ih:${xOffset}:0,scale=${CLIP_WIDTH}:${CLIP_HEIGHT}`;
4366
- const logId = options.logId?.trim();
4367
- const hasCoordinate = Boolean(logId);
4368
- const coordinateBox = Math.round(CLIP_COORDINATE_SIZE * 1.18);
4369
- const titleBottomOffset = hasCoordinate ? CLIP_SAFE_BOTTOM + coordinateBox + CLIP_LINE_GAP : CLIP_SAFE_BOTTOM;
4370
- const titleY = `h-${titleBottomOffset}-th`;
4371
- const titleLines = resolveTitleLines(options, titleY);
4372
- const coordinate2 = hasCoordinate ? {
4373
- fontFile: options.oxaniumFontFile,
4374
- size: CLIP_COORDINATE_SIZE,
4375
- text: `fluncle://${logId}`,
4376
- x: CLIP_MARGIN_X,
4377
- y: `h-${CLIP_SAFE_BOTTOM}-th`
4378
- } : undefined;
4379
- const haloGlyphs = [
4380
- ...titleLines.map((line) => brandDrawtext({ ...line, color: CLIP_HALO_COLOR })),
4381
- ...coordinate2 ? [brandDrawtext({ ...coordinate2, color: CLIP_HALO_COLOR })] : []
4382
- ].join(",");
4383
- const sharpGlyphs = [
4384
- ...titleLines.map((line) => brandDrawtext({ ...line, borderw: CLIP_SHARP_BORDERW, color: CLIP_TITLE_COLOR })),
4385
- ...coordinate2 ? [
4386
- brandDrawtext({
4387
- ...coordinate2,
4388
- borderw: CLIP_SHARP_BORDERW,
4389
- color: CLIP_COORDINATE_COLOR
4390
- })
4391
- ] : []
4392
- ].join(",");
4393
- return [
4394
- `[0:v]${crop},setsar=1[base]`,
4395
- `color=c=black@0:s=${CLIP_WIDTH}x${CLIP_HEIGHT},format=rgba,${haloGlyphs}[ink]`,
4396
- `[ink]split[ink1][ink2]`,
4397
- `[ink1]gblur=sigma=${CLIP_HALO_CORE_SIGMA}[core]`,
4398
- `[ink2]gblur=sigma=${CLIP_HALO_FEATHER_SIGMA}[feather]`,
4399
- `[base][feather]overlay=0:0[b1]`,
4400
- `[b1][core]overlay=0:0[b2]`,
4401
- `[b2]${sharpGlyphs}[out]`
4402
- ].join(";");
4279
+ return `[0:v]crop=ih*9/16:ih:${xOffset}:0,scale=${CLIP_WIDTH}:${CLIP_HEIGHT},setsar=1[out]`;
4403
4280
  }
4404
4281
  function clipCutFfmpegArgs(options) {
4405
4282
  const inSeconds = (options.inMs / 1000).toFixed(3);
@@ -4463,15 +4340,7 @@ async function resolveClipSource(clip) {
4463
4340
  throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
4464
4341
  }
4465
4342
  return {
4466
- logId: recording.logId,
4467
- members: recording.tracklist.map((cue) => ({
4468
- artists: cue.artists,
4469
- startMs: cue.startMs,
4470
- title: cue.title
4471
- })),
4472
- setDurationMs: recording.durationMs ?? 0,
4473
- setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`,
4474
- title: recording.title
4343
+ setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
4475
4344
  };
4476
4345
  }
4477
4346
  if (!clip.mixtapeId) {
@@ -4485,13 +4354,7 @@ async function resolveClipSource(clip) {
4485
4354
  if (!mixtape.setVideoAt) {
4486
4355
  throw new CliError2("set_not_staged", `Mixtape ${mixtape.logId} has no staged set video — run \`distribute --set-video\` first`);
4487
4356
  }
4488
- return {
4489
- logId: mixtape.logId,
4490
- members: mixtape.members,
4491
- setDurationMs: mixtape.durationMs ?? 0,
4492
- setUrl: setVideoUrl(mixtape.logId),
4493
- title: mixtape.title
4494
- };
4357
+ return { setUrl: setVideoUrl(mixtape.logId) };
4495
4358
  }
4496
4359
  async function clipCutCommand(clipId, onProgress = () => {}) {
4497
4360
  const clips = await clipsListCommand();
@@ -4503,18 +4366,12 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4503
4366
  await assertFfmpeg2();
4504
4367
  const outputPath = join5(tmpdir2(), `fluncle-clip-${randomUUID2()}.mp4`);
4505
4368
  try {
4506
- onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms]${source.logId ? ` from ${source.logId}` : ""}…`);
4369
+ onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms]…`);
4507
4370
  await runClipCut({
4508
4371
  inMs: clip.inMs,
4509
- logId: source.logId,
4510
- members: source.members,
4511
4372
  outMs: clip.outMs,
4512
4373
  outputPath,
4513
- oxaniumFontFile: resolveClipFontFile(),
4514
- sansFontFile: resolveClipSansFontFile(),
4515
- setDurationMs: source.setDurationMs,
4516
4374
  setUrl: source.setUrl,
4517
- title: source.title,
4518
4375
  xOffset: clip.xOffset
4519
4376
  });
4520
4377
  const sizeBytes = statSync4(outputPath).size;
@@ -4529,7 +4386,6 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4529
4386
  return {
4530
4387
  clipId,
4531
4388
  key: presign.key,
4532
- logId: source.logId,
4533
4389
  sizeBytes,
4534
4390
  url: `${FOUND_BASE3}/${presign.key}`
4535
4391
  };
@@ -4573,9 +4429,8 @@ async function runClipCut(options) {
4573
4429
  throw new CliError2("ffmpeg_failed", `ffmpeg failed to cut the clip${detail ? `: ${detail}` : ""}`);
4574
4430
  }
4575
4431
  }
4576
- var FOUND_BASE3 = "https://found.fluncle.com", CLIP_WIDTH = 1080, CLIP_HEIGHT = 1920, CLIP_CRF = 21, CLIP_MAXRATE = "10M", CLIP_BUFSIZE = "20M", CLIP_AUDIO_BITRATE = "192k", MAX_CLIP_BYTES, CLIP_TITLE_COLOR = "0xf4ead7", CLIP_COORDINATE_COLOR = "0xb7ab95", CLIP_HALO_COLOR = "0x090a0b", CLIP_HALO_CORE_SIGMA = 3, CLIP_HALO_FEATHER_SIGMA = 9, CLIP_SHARP_BORDERW = 1, CLIP_MARGIN_X = 96, CLIP_SAFE_BOTTOM = 230, CLIP_LINE_GAP = 10, CLIP_TITLE_SIZE = 40, CLIP_COORDINATE_SIZE = 22, BOX_CLIP_FONT_FILE = "/opt/fonts/Oxanium-SemiBold.ttf", BOX_CLIP_SANS_FONT_FILE = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf";
4432
+ var FOUND_BASE3 = "https://found.fluncle.com", CLIP_WIDTH = 1080, CLIP_HEIGHT = 1920, CLIP_CRF = 21, CLIP_MAXRATE = "10M", CLIP_BUFSIZE = "20M", CLIP_AUDIO_BITRATE = "192k", MAX_CLIP_BYTES;
4577
4433
  var init_clips = __esm(() => {
4578
- init_util();
4579
4434
  init_api();
4580
4435
  init_output();
4581
4436
  MAX_CLIP_BYTES = 100 * 1024 * 1024;
@@ -4592,7 +4447,7 @@ __export(exports_recordings2, {
4592
4447
  recordingDeleteCommand: () => recordingDeleteCommand2,
4593
4448
  recordingCreateCommand: () => recordingCreateCommand2
4594
4449
  });
4595
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
4450
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
4596
4451
  async function recordingGet2(id) {
4597
4452
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
4598
4453
  return response.recording;
@@ -4625,7 +4480,7 @@ async function recordingGetCommand2(id, options = {}) {
4625
4480
  return;
4626
4481
  }
4627
4482
  console.log(formatRecordingSummary2(recording));
4628
- console.log(` r2Key: ${recording.r2Key}`);
4483
+ console.log(` r2Key: ${recording.r2Key ?? "— (no set video)"}`);
4629
4484
  for (const cue of recording.tracklist) {
4630
4485
  const at = cue.startMs === undefined ? "—" : `${(cue.startMs / 1000).toFixed(1)}s`;
4631
4486
  console.log(` ${at} ${cue.artists.join(", ")} — ${cue.title}`);
@@ -4639,7 +4494,7 @@ async function recordingCreateCommand2(options = {}) {
4639
4494
  if (!options.video) {
4640
4495
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
4641
4496
  }
4642
- if (!existsSync6(options.video)) {
4497
+ if (!existsSync5(options.video)) {
4643
4498
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
4644
4499
  }
4645
4500
  const created = await adminApiPost("/api/admin/recordings", {
@@ -4672,7 +4527,7 @@ async function recordingUpdateCommand2(id, options = {}) {
4672
4527
  body.recordedAt = options.recordedAt;
4673
4528
  }
4674
4529
  if (options.tracklistFile !== undefined) {
4675
- if (!existsSync6(options.tracklistFile)) {
4530
+ if (!existsSync5(options.tracklistFile)) {
4676
4531
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
4677
4532
  }
4678
4533
  try {
@@ -4729,7 +4584,7 @@ __export(exports_newsletter, {
4729
4584
  newsletterDraftCommand: () => newsletterDraftCommand,
4730
4585
  newsletterDeleteCommand: () => newsletterDeleteCommand
4731
4586
  });
4732
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
4587
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
4733
4588
  function buildBody3(options, { requireContent }) {
4734
4589
  const body = {};
4735
4590
  if (options.contentFile !== undefined) {
@@ -4749,7 +4604,7 @@ function buildBody3(options, { requireContent }) {
4749
4604
  return body;
4750
4605
  }
4751
4606
  function readContentFile(filePath) {
4752
- if (!existsSync7(filePath)) {
4607
+ if (!existsSync6(filePath)) {
4753
4608
  throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
4754
4609
  }
4755
4610
  const text = readFileSync6(filePath, "utf-8");
@@ -5387,7 +5242,7 @@ var init_format2 = __esm(() => {
5387
5242
  });
5388
5243
 
5389
5244
  // src/cli.ts
5390
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
5245
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
5391
5246
  import path2 from "path";
5392
5247
 
5393
5248
  // ../../node_modules/commander/lib/error.js
@@ -8100,7 +7955,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
8100
7955
  return;
8101
7956
  }
8102
7957
  const candidate = path2.join(dir, name);
8103
- return existsSync8(candidate) ? candidate : undefined;
7958
+ return existsSync7(candidate) ? candidate : undefined;
8104
7959
  };
8105
7960
  const resolveFile = (explicit, name) => {
8106
7961
  if (explicit) {
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.82.0"
34
+ "version": "0.84.0"
35
35
  }