fluncle 0.74.0 → 0.76.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 +134 -21
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -462,6 +462,26 @@ 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
|
+
}
|
|
465
485
|
function baseTitleMatches(findingTitle, candidateTitle) {
|
|
466
486
|
const want = new Set(tokenize(stripVersionSuffix(findingTitle)));
|
|
467
487
|
const have = new Set(tokenize(stripVersionSuffix(candidateTitle)));
|
|
@@ -524,7 +544,7 @@ function parseVersion(version) {
|
|
|
524
544
|
var currentVersion;
|
|
525
545
|
var init_version = __esm(() => {
|
|
526
546
|
init_output();
|
|
527
|
-
currentVersion = "0.
|
|
547
|
+
currentVersion = "0.76.0".trim() ? "0.76.0".trim() : "0.1.0";
|
|
528
548
|
});
|
|
529
549
|
|
|
530
550
|
// src/update-notifier.ts
|
|
@@ -2381,7 +2401,7 @@ function parseVersion2(version) {
|
|
|
2381
2401
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2382
2402
|
var init_version2 = __esm(() => {
|
|
2383
2403
|
init_output();
|
|
2384
|
-
currentVersion2 = "0.
|
|
2404
|
+
currentVersion2 = "0.76.0".trim() ? "0.76.0".trim() : "0.1.0";
|
|
2385
2405
|
});
|
|
2386
2406
|
|
|
2387
2407
|
// ../../packages/registry/src/index.ts
|
|
@@ -3001,7 +3021,7 @@ var init_src = __esm(() => {
|
|
|
3001
3021
|
],
|
|
3002
3022
|
kind: "cron",
|
|
3003
3023
|
name: "cron.healthcheck",
|
|
3004
|
-
operatorNotes: "every 10m. Pure probing, zero LLM tokens. POSTs to the agent-tier record_health op that /status reads.",
|
|
3024
|
+
operatorNotes: "every 10m, run by a rave-02 host systemd timer (docs/agents/hermes/healthcheck-timer/) — decoupled from the Hermes cron gateway so the prober isn't starved by the scheduler it monitors. Pure probing, zero LLM tokens. POSTs to the agent-tier record_health op that /status reads.",
|
|
3005
3025
|
probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-healthcheck", kind: "cron" },
|
|
3006
3026
|
weights: { status: "hidden" }
|
|
3007
3027
|
},
|
|
@@ -3975,47 +3995,132 @@ var init_mixtapes2 = __esm(() => {
|
|
|
3975
3995
|
var exports_clips = {};
|
|
3976
3996
|
__export(exports_clips, {
|
|
3977
3997
|
setVideoUrl: () => setVideoUrl,
|
|
3998
|
+
resolveClipSansFontFile: () => resolveClipSansFontFile,
|
|
3999
|
+
resolveClipFontFile: () => resolveClipFontFile,
|
|
3978
4000
|
escapeDrawtextValue: () => escapeDrawtextValue,
|
|
3979
4001
|
clipsListCommand: () => clipsListCommand,
|
|
3980
4002
|
clipFootageKey: () => clipFootageKey,
|
|
3981
|
-
|
|
4003
|
+
clipCutFilterComplex: () => clipCutFilterComplex,
|
|
3982
4004
|
clipCutFfmpegArgs: () => clipCutFfmpegArgs,
|
|
3983
4005
|
clipCutCommand: () => clipCutCommand,
|
|
4006
|
+
brandDrawtext: () => brandDrawtext,
|
|
3984
4007
|
MAX_CLIP_BYTES: () => MAX_CLIP_BYTES,
|
|
3985
4008
|
CLIP_WIDTH: () => CLIP_WIDTH,
|
|
4009
|
+
CLIP_TITLE_SIZE: () => CLIP_TITLE_SIZE,
|
|
4010
|
+
CLIP_TITLE_COLOR: () => CLIP_TITLE_COLOR,
|
|
4011
|
+
CLIP_SHARP_BORDERW: () => CLIP_SHARP_BORDERW,
|
|
4012
|
+
CLIP_SAFE_BOTTOM: () => CLIP_SAFE_BOTTOM,
|
|
3986
4013
|
CLIP_MAXRATE: () => CLIP_MAXRATE,
|
|
4014
|
+
CLIP_MARGIN_X: () => CLIP_MARGIN_X,
|
|
4015
|
+
CLIP_LINE_GAP: () => CLIP_LINE_GAP,
|
|
3987
4016
|
CLIP_HEIGHT: () => CLIP_HEIGHT,
|
|
4017
|
+
CLIP_HALO_FEATHER_SIGMA: () => CLIP_HALO_FEATHER_SIGMA,
|
|
4018
|
+
CLIP_HALO_CORE_SIGMA: () => CLIP_HALO_CORE_SIGMA,
|
|
4019
|
+
CLIP_HALO_COLOR: () => CLIP_HALO_COLOR,
|
|
3988
4020
|
CLIP_CRF: () => CLIP_CRF,
|
|
4021
|
+
CLIP_COORDINATE_SIZE: () => CLIP_COORDINATE_SIZE,
|
|
4022
|
+
CLIP_COORDINATE_COLOR: () => CLIP_COORDINATE_COLOR,
|
|
3989
4023
|
CLIP_BUFSIZE: () => CLIP_BUFSIZE,
|
|
3990
|
-
CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE
|
|
4024
|
+
CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE,
|
|
4025
|
+
BOX_CLIP_SANS_FONT_FILE: () => BOX_CLIP_SANS_FONT_FILE,
|
|
4026
|
+
BOX_CLIP_FONT_FILE: () => BOX_CLIP_FONT_FILE
|
|
3991
4027
|
});
|
|
3992
4028
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3993
|
-
import { rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
4029
|
+
import { existsSync as existsSync4, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
3994
4030
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
3995
4031
|
import { join as join5 } from "node:path";
|
|
3996
4032
|
function clipFootageKey(clipId) {
|
|
3997
4033
|
return `${clipId}/footage.mp4`;
|
|
3998
4034
|
}
|
|
4035
|
+
function resolveFontFile(envVar, bakedPath) {
|
|
4036
|
+
const explicit = process.env[envVar];
|
|
4037
|
+
if (explicit) {
|
|
4038
|
+
return explicit;
|
|
4039
|
+
}
|
|
4040
|
+
return existsSync4(bakedPath) ? bakedPath : undefined;
|
|
4041
|
+
}
|
|
4042
|
+
function resolveClipFontFile() {
|
|
4043
|
+
return resolveFontFile("CLIP_FONT_FILE", BOX_CLIP_FONT_FILE);
|
|
4044
|
+
}
|
|
4045
|
+
function resolveClipSansFontFile() {
|
|
4046
|
+
return resolveFontFile("CLIP_SANS_FONT_FILE", BOX_CLIP_SANS_FONT_FILE);
|
|
4047
|
+
}
|
|
3999
4048
|
function setVideoUrl(logId) {
|
|
4000
4049
|
return `${FOUND_BASE3}/${encodeURIComponent(logId)}/set.mp4`;
|
|
4001
4050
|
}
|
|
4002
4051
|
function escapeDrawtextValue(text) {
|
|
4003
4052
|
return text.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/,/g, "\\,").replace(/%/g, "\\%").replace(/'/g, "'\\''");
|
|
4004
4053
|
}
|
|
4005
|
-
function
|
|
4054
|
+
function brandDrawtext(options) {
|
|
4055
|
+
const value = escapeDrawtextValue(options.text);
|
|
4056
|
+
const font = options.fontFile ? `:fontfile='${options.fontFile}'` : "";
|
|
4057
|
+
const border = options.borderw && options.borderw > 0 ? `:borderw=${options.borderw}:bordercolor=${CLIP_HALO_COLOR}` : "";
|
|
4058
|
+
const enable = options.enable ? `:enable='${options.enable}'` : "";
|
|
4059
|
+
return `drawtext=text='${value}'${font}:` + `fontcolor=${options.color}:fontsize=${options.size}:` + `x=${options.x}:y=${options.y}${border}${enable}`;
|
|
4060
|
+
}
|
|
4061
|
+
function resolveTitleLines(options, y) {
|
|
4062
|
+
const inMs = options.inMs ?? 0;
|
|
4063
|
+
const outMs = options.outMs ?? 0;
|
|
4064
|
+
const clipDur = Math.max(0, (outMs - inMs) / 1000);
|
|
4065
|
+
const base = { fontFile: options.sansFontFile, size: CLIP_TITLE_SIZE, x: CLIP_MARGIN_X, y };
|
|
4066
|
+
const resolved = options.members && options.members.length > 0 ? resolveClipTracks({
|
|
4067
|
+
inMs,
|
|
4068
|
+
members: options.members,
|
|
4069
|
+
outMs,
|
|
4070
|
+
setDurationMs: options.setDurationMs ?? 0
|
|
4071
|
+
}) : [];
|
|
4072
|
+
if (resolved.length === 0) {
|
|
4073
|
+
return [{ ...base, text: options.title }];
|
|
4074
|
+
}
|
|
4075
|
+
const clamp = (seconds) => Math.min(Math.max(seconds, 0), clipDur);
|
|
4076
|
+
return resolved.map((track, index) => {
|
|
4077
|
+
const relStart = clamp((track.startMs - inMs) / 1000);
|
|
4078
|
+
const next = resolved[index + 1];
|
|
4079
|
+
const relEnd = next ? clamp((next.startMs - inMs) / 1000) : clipDur;
|
|
4080
|
+
return {
|
|
4081
|
+
...base,
|
|
4082
|
+
enable: `between(t,${relStart.toFixed(3)},${relEnd.toFixed(3)})`,
|
|
4083
|
+
text: track.label
|
|
4084
|
+
};
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
4087
|
+
function clipCutFilterComplex(options) {
|
|
4006
4088
|
const xOffset = Math.max(0, Math.round(options.xOffset));
|
|
4007
4089
|
const crop = `crop=ih*9/16:ih:${xOffset}:0,scale=${CLIP_WIDTH}:${CLIP_HEIGHT}`;
|
|
4008
|
-
const
|
|
4009
|
-
const
|
|
4010
|
-
const
|
|
4011
|
-
const
|
|
4012
|
-
const
|
|
4013
|
-
|
|
4090
|
+
const coordinateBox = Math.round(CLIP_COORDINATE_SIZE * 1.18);
|
|
4091
|
+
const titleBottomOffset = CLIP_SAFE_BOTTOM + coordinateBox + CLIP_LINE_GAP;
|
|
4092
|
+
const titleY = `h-${titleBottomOffset}-th`;
|
|
4093
|
+
const titleLines = resolveTitleLines(options, titleY);
|
|
4094
|
+
const coordinate2 = {
|
|
4095
|
+
fontFile: options.oxaniumFontFile,
|
|
4096
|
+
size: CLIP_COORDINATE_SIZE,
|
|
4097
|
+
text: `fluncle://${options.logId}`,
|
|
4098
|
+
x: CLIP_MARGIN_X,
|
|
4099
|
+
y: `h-${CLIP_SAFE_BOTTOM}-th`
|
|
4100
|
+
};
|
|
4101
|
+
const haloGlyphs = [
|
|
4102
|
+
...titleLines.map((line) => brandDrawtext({ ...line, color: CLIP_HALO_COLOR })),
|
|
4103
|
+
brandDrawtext({ ...coordinate2, color: CLIP_HALO_COLOR })
|
|
4104
|
+
].join(",");
|
|
4105
|
+
const sharpGlyphs = [
|
|
4106
|
+
...titleLines.map((line) => brandDrawtext({ ...line, borderw: CLIP_SHARP_BORDERW, color: CLIP_TITLE_COLOR })),
|
|
4107
|
+
brandDrawtext({ ...coordinate2, borderw: CLIP_SHARP_BORDERW, color: CLIP_COORDINATE_COLOR })
|
|
4108
|
+
].join(",");
|
|
4109
|
+
return [
|
|
4110
|
+
`[0:v]${crop},setsar=1[base]`,
|
|
4111
|
+
`color=c=black@0:s=${CLIP_WIDTH}x${CLIP_HEIGHT},format=rgba,${haloGlyphs}[ink]`,
|
|
4112
|
+
`[ink]split[ink1][ink2]`,
|
|
4113
|
+
`[ink1]gblur=sigma=${CLIP_HALO_CORE_SIGMA}[core]`,
|
|
4114
|
+
`[ink2]gblur=sigma=${CLIP_HALO_FEATHER_SIGMA}[feather]`,
|
|
4115
|
+
`[base][feather]overlay=0:0[b1]`,
|
|
4116
|
+
`[b1][core]overlay=0:0[b2]`,
|
|
4117
|
+
`[b2]${sharpGlyphs}[out]`
|
|
4118
|
+
].join(";");
|
|
4014
4119
|
}
|
|
4015
4120
|
function clipCutFfmpegArgs(options) {
|
|
4016
4121
|
const inSeconds = (options.inMs / 1000).toFixed(3);
|
|
4017
4122
|
const durationSeconds = ((options.outMs - options.inMs) / 1000).toFixed(3);
|
|
4018
|
-
const filter =
|
|
4123
|
+
const filter = clipCutFilterComplex(options);
|
|
4019
4124
|
return [
|
|
4020
4125
|
"-y",
|
|
4021
4126
|
"-ss",
|
|
@@ -4024,8 +4129,12 @@ function clipCutFfmpegArgs(options) {
|
|
|
4024
4129
|
options.setUrl,
|
|
4025
4130
|
"-t",
|
|
4026
4131
|
durationSeconds,
|
|
4027
|
-
"-
|
|
4132
|
+
"-filter_complex",
|
|
4028
4133
|
filter,
|
|
4134
|
+
"-map",
|
|
4135
|
+
"[out]",
|
|
4136
|
+
"-map",
|
|
4137
|
+
"0:a?",
|
|
4029
4138
|
"-c:v",
|
|
4030
4139
|
"libx264",
|
|
4031
4140
|
"-preset",
|
|
@@ -4079,11 +4188,14 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
|
|
|
4079
4188
|
try {
|
|
4080
4189
|
onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms] from ${mixtape.logId}…`);
|
|
4081
4190
|
await runClipCut({
|
|
4082
|
-
fontFile: process.env.CLIP_FONT_FILE,
|
|
4083
4191
|
inMs: clip.inMs,
|
|
4084
4192
|
logId: mixtape.logId,
|
|
4193
|
+
members: mixtape.members,
|
|
4085
4194
|
outMs: clip.outMs,
|
|
4086
4195
|
outputPath,
|
|
4196
|
+
oxaniumFontFile: resolveClipFontFile(),
|
|
4197
|
+
sansFontFile: resolveClipSansFontFile(),
|
|
4198
|
+
setDurationMs: mixtape.durationMs ?? 0,
|
|
4087
4199
|
setUrl,
|
|
4088
4200
|
title: mixtape.title,
|
|
4089
4201
|
xOffset: clip.xOffset
|
|
@@ -4144,8 +4256,9 @@ async function runClipCut(options) {
|
|
|
4144
4256
|
throw new CliError2("ffmpeg_failed", `ffmpeg failed to cut the clip${detail ? `: ${detail}` : ""}`);
|
|
4145
4257
|
}
|
|
4146
4258
|
}
|
|
4147
|
-
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;
|
|
4259
|
+
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";
|
|
4148
4260
|
var init_clips = __esm(() => {
|
|
4261
|
+
init_util();
|
|
4149
4262
|
init_api();
|
|
4150
4263
|
init_output();
|
|
4151
4264
|
MAX_CLIP_BYTES = 100 * 1024 * 1024;
|
|
@@ -4160,7 +4273,7 @@ __export(exports_newsletter, {
|
|
|
4160
4273
|
newsletterDraftCommand: () => newsletterDraftCommand,
|
|
4161
4274
|
newsletterDeleteCommand: () => newsletterDeleteCommand
|
|
4162
4275
|
});
|
|
4163
|
-
import { existsSync as
|
|
4276
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
4164
4277
|
function buildBody3(options, { requireContent }) {
|
|
4165
4278
|
const body = {};
|
|
4166
4279
|
if (options.contentFile !== undefined) {
|
|
@@ -4180,7 +4293,7 @@ function buildBody3(options, { requireContent }) {
|
|
|
4180
4293
|
return body;
|
|
4181
4294
|
}
|
|
4182
4295
|
function readContentFile(filePath) {
|
|
4183
|
-
if (!
|
|
4296
|
+
if (!existsSync5(filePath)) {
|
|
4184
4297
|
throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
|
|
4185
4298
|
}
|
|
4186
4299
|
const text = readFileSync4(filePath, "utf-8");
|
|
@@ -4822,7 +4935,7 @@ var init_format2 = __esm(() => {
|
|
|
4822
4935
|
});
|
|
4823
4936
|
|
|
4824
4937
|
// src/cli.ts
|
|
4825
|
-
import { existsSync as
|
|
4938
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
4826
4939
|
import path2 from "path";
|
|
4827
4940
|
|
|
4828
4941
|
// ../../node_modules/commander/lib/error.js
|
|
@@ -7499,7 +7612,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
7499
7612
|
return;
|
|
7500
7613
|
}
|
|
7501
7614
|
const candidate = path2.join(dir, name);
|
|
7502
|
-
return
|
|
7615
|
+
return existsSync6(candidate) ? candidate : undefined;
|
|
7503
7616
|
};
|
|
7504
7617
|
const resolveFile = (explicit, name) => {
|
|
7505
7618
|
if (explicit) {
|
package/package.json
CHANGED