@mediaruntime/cli 1.2.0 → 1.4.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/CHANGELOG.md +28 -0
- package/README.md +247 -1
- package/dist/cli.d.ts +113 -0
- package/dist/cli.js +1079 -23
- package/dist/cli.js.map +1 -1
- package/docs/AUTH.md +8 -0
- package/docs/CLI_SDD.md +43 -0
- package/docs/STICKERS.md +98 -0
- package/docs/clipping.md +113 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
MediaRuntimeApiError,
|
|
21
21
|
MediaRuntimeConnectionError
|
|
22
22
|
} from "@mediaruntime/node";
|
|
23
|
+
import * as MediaRuntimeSdk from "@mediaruntime/node";
|
|
23
24
|
|
|
24
25
|
// src/errors.ts
|
|
25
26
|
var CliError = class extends Error {
|
|
@@ -78,6 +79,7 @@ async function post(baseUrl, path, body, apiKey) {
|
|
|
78
79
|
try {
|
|
79
80
|
response = await fetch(endpoint(baseUrl, path), {
|
|
80
81
|
method: "POST",
|
|
82
|
+
// Never forward a credential or device code through an unexpected redirect.
|
|
81
83
|
redirect: "error",
|
|
82
84
|
headers: {
|
|
83
85
|
"Content-Type": "application/json",
|
|
@@ -606,6 +608,93 @@ async function runJobsCommand(args, dependencies) {
|
|
|
606
608
|
throw new UsageError("Usage: mediaruntime jobs <list|get>");
|
|
607
609
|
}
|
|
608
610
|
|
|
611
|
+
// src/clipTranscript.ts
|
|
612
|
+
import { open } from "fs/promises";
|
|
613
|
+
var MAX_FILE_BYTES = 1024 * 1024;
|
|
614
|
+
function timestamp(value) {
|
|
615
|
+
const match = /^(?:(\d{2,}):)?(\d{2}):(\d{2})[.,](\d{3})$/.exec(value);
|
|
616
|
+
if (!match || Number(match[2]) > 59 || Number(match[3]) > 59) {
|
|
617
|
+
throw new UsageError("Transcript cue times must use HH:MM:SS,mmm (SRT) or MM:SS.mmm / HH:MM:SS.mmm (VTT)");
|
|
618
|
+
}
|
|
619
|
+
return Number(match[1] ?? 0) * 3600 + Number(match[2]) * 60 + Number(match[3]) + Number(match[4]) / 1e3;
|
|
620
|
+
}
|
|
621
|
+
function subtitleSegments(text, vtt) {
|
|
622
|
+
if (vtt && !/^WEBVTT(?:\s|$)/.test(text)) throw new UsageError("VTT transcripts must begin with WEBVTT");
|
|
623
|
+
const segments = [];
|
|
624
|
+
for (const block of text.split(/\n[ \t]*\n/)) {
|
|
625
|
+
if (vtt && /^(WEBVTT(?:\s|$)|NOTE(?:\s|$)|STYLE(?:\s|$)|REGION(?:\s|$))/.test(block)) continue;
|
|
626
|
+
const lines = block.split("\n");
|
|
627
|
+
const timing = lines[0]?.includes("-->") ? 0 : 1;
|
|
628
|
+
const match = /^(\S+)\s+-->\s+(\S+)(?:\s+.*)?$/.exec(lines[timing] ?? "");
|
|
629
|
+
if (!match) throw new UsageError("Transcript contains an invalid SRT/VTT cue");
|
|
630
|
+
const entities = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
|
|
631
|
+
const cue = lines.slice(timing + 1).join("\n").replace(/<[^>]*>/g, "").replace(/&(amp|lt|gt|quot|apos|nbsp);/g, (_, name) => entities[name]);
|
|
632
|
+
segments.push({ startTimeSec: timestamp(match[1]), endTimeSec: timestamp(match[2]), text: cue });
|
|
633
|
+
}
|
|
634
|
+
return segments;
|
|
635
|
+
}
|
|
636
|
+
async function readClipTranscript(path) {
|
|
637
|
+
if (!/\.(json|srt|vtt)$/i.test(path)) throw new UsageError("--clip-transcript accepts a local .json, .srt, or .vtt file");
|
|
638
|
+
let data;
|
|
639
|
+
try {
|
|
640
|
+
const file = await open(path, "r");
|
|
641
|
+
try {
|
|
642
|
+
if (!(await file.stat()).isFile()) throw new UsageError("Clip transcript must be a regular file");
|
|
643
|
+
const buffer = Buffer.alloc(MAX_FILE_BYTES + 1);
|
|
644
|
+
let size = 0;
|
|
645
|
+
while (size < buffer.length) {
|
|
646
|
+
const read = await file.read(buffer, size, buffer.length - size, null);
|
|
647
|
+
if (!read.bytesRead) break;
|
|
648
|
+
size += read.bytesRead;
|
|
649
|
+
}
|
|
650
|
+
if (size > MAX_FILE_BYTES) throw new UsageError("Clip transcript file must be at most 1 MiB");
|
|
651
|
+
data = buffer.subarray(0, size);
|
|
652
|
+
} finally {
|
|
653
|
+
await file.close();
|
|
654
|
+
}
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if (error instanceof UsageError) throw error;
|
|
657
|
+
throw new UsageError("Cannot read clip transcript file; check its path and permissions");
|
|
658
|
+
}
|
|
659
|
+
let text;
|
|
660
|
+
try {
|
|
661
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(data).replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n").trim();
|
|
662
|
+
} catch {
|
|
663
|
+
throw new UsageError("Clip transcript must be valid UTF-8 text");
|
|
664
|
+
}
|
|
665
|
+
let segments;
|
|
666
|
+
if (/\.json$/i.test(path)) {
|
|
667
|
+
let value;
|
|
668
|
+
try {
|
|
669
|
+
value = JSON.parse(text);
|
|
670
|
+
} catch {
|
|
671
|
+
throw new UsageError("Clip transcript must be valid JSON");
|
|
672
|
+
}
|
|
673
|
+
segments = Array.isArray(value) ? value : value && typeof value === "object" && "transcript" in value ? value.transcript : void 0;
|
|
674
|
+
} else segments = subtitleSegments(text, /\.vtt$/i.test(path));
|
|
675
|
+
if (!Array.isArray(segments) || !segments.length || segments.length > 2e3) {
|
|
676
|
+
throw new UsageError("Clip transcript must contain 1 to 2000 segments");
|
|
677
|
+
}
|
|
678
|
+
let previous = -1;
|
|
679
|
+
let totalBytes = 0;
|
|
680
|
+
return segments.map((value) => {
|
|
681
|
+
const segment = value && typeof value === "object" ? value : {};
|
|
682
|
+
const start = segment.start_time_sec ?? segment.startTimeSec;
|
|
683
|
+
const end = segment.end_time_sec ?? segment.endTimeSec;
|
|
684
|
+
const cue = segment.text;
|
|
685
|
+
if (typeof start !== "number" || !Number.isFinite(start) || typeof end !== "number" || !Number.isFinite(end) || start < 0 || end <= start || end > 604800 || start < previous) {
|
|
686
|
+
throw new UsageError("Transcript segments require ordered finite source times with end after start (within 604800 seconds)");
|
|
687
|
+
}
|
|
688
|
+
if (typeof cue !== "string" || !cue.trim() || Buffer.byteLength(cue, "utf8") > 2e3) {
|
|
689
|
+
throw new UsageError("Transcript segments require nonblank text of at most 2000 UTF-8 bytes");
|
|
690
|
+
}
|
|
691
|
+
previous = start;
|
|
692
|
+
totalBytes += Buffer.byteLength(cue, "utf8");
|
|
693
|
+
if (totalBytes > 256 * 1024) throw new UsageError("Transcript text exceeds 256 KiB");
|
|
694
|
+
return { startTimeSec: start, endTimeSec: end, text: cue.trim() };
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
609
698
|
// src/commands/run.ts
|
|
610
699
|
var UNSUCCESSFUL_TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["FAILED", "REJECTED", "PARTIAL"]);
|
|
611
700
|
var OUTPUT_ALIASES = /* @__PURE__ */ new Set([
|
|
@@ -633,6 +722,20 @@ function parseMetadata(value) {
|
|
|
633
722
|
}
|
|
634
723
|
return parsed;
|
|
635
724
|
}
|
|
725
|
+
function boundedNumber(args, index, option, minimum, maximum, integer = false) {
|
|
726
|
+
const value = Number(optionValue2(args, index, option));
|
|
727
|
+
if (!Number.isFinite(value) || value < minimum || value > maximum || integer && !Number.isInteger(value)) {
|
|
728
|
+
throw new UsageError(`${option} must be ${integer ? "an integer " : ""}between ${minimum} and ${maximum}`);
|
|
729
|
+
}
|
|
730
|
+
return value;
|
|
731
|
+
}
|
|
732
|
+
function hexColor(args, index, option) {
|
|
733
|
+
const value = optionValue2(args, index, option);
|
|
734
|
+
if (!/^#[0-9A-Fa-f]{6}$/.test(value)) {
|
|
735
|
+
throw new UsageError(`${option} must be a six-digit hex colour such as #5B5CFF`);
|
|
736
|
+
}
|
|
737
|
+
return value;
|
|
738
|
+
}
|
|
636
739
|
function parseRunOptions(args) {
|
|
637
740
|
let source;
|
|
638
741
|
const outputs = [];
|
|
@@ -644,6 +747,16 @@ function parseRunOptions(args) {
|
|
|
644
747
|
let download;
|
|
645
748
|
let force = false;
|
|
646
749
|
let json = false;
|
|
750
|
+
const animation = {};
|
|
751
|
+
const placeholders = {};
|
|
752
|
+
const contactSheet = {};
|
|
753
|
+
const image = {};
|
|
754
|
+
const audiogram = {};
|
|
755
|
+
const clip = {};
|
|
756
|
+
const clipAnalysis = {};
|
|
757
|
+
let clipTranscriptFile;
|
|
758
|
+
const privacyRedaction = {};
|
|
759
|
+
const privacyDetectors = [];
|
|
647
760
|
for (let index = 0; index < args.length; index += 1) {
|
|
648
761
|
const argument = args[index];
|
|
649
762
|
if (argument === "--output" || argument === "-o") {
|
|
@@ -678,6 +791,234 @@ function parseRunOptions(args) {
|
|
|
678
791
|
force = true;
|
|
679
792
|
} else if (argument === "--json") {
|
|
680
793
|
json = true;
|
|
794
|
+
} else if (argument === "--animation-width") {
|
|
795
|
+
animation.width = boundedNumber(args, index, argument, 1, 1920, true);
|
|
796
|
+
index += 1;
|
|
797
|
+
} else if (argument === "--animation-fps") {
|
|
798
|
+
animation.fps = boundedNumber(args, index, argument, 0.01, 30);
|
|
799
|
+
index += 1;
|
|
800
|
+
} else if (argument === "--animation-start") {
|
|
801
|
+
animation.startTime = boundedNumber(args, index, argument, 0, 604800);
|
|
802
|
+
index += 1;
|
|
803
|
+
} else if (argument === "--animation-duration") {
|
|
804
|
+
animation.duration = boundedNumber(args, index, argument, 0.01, 30);
|
|
805
|
+
index += 1;
|
|
806
|
+
} else if (argument === "--animation-loop") {
|
|
807
|
+
animation.loop = boundedNumber(args, index, argument, 0, 65535, true);
|
|
808
|
+
index += 1;
|
|
809
|
+
} else if (argument === "--animation-quality") {
|
|
810
|
+
animation.quality = boundedNumber(args, index, argument, 1, 100, true);
|
|
811
|
+
index += 1;
|
|
812
|
+
} else if (argument === "--placeholder-max-dimension") {
|
|
813
|
+
placeholders.maxDimension = boundedNumber(args, index, argument, 8, 100, true);
|
|
814
|
+
index += 1;
|
|
815
|
+
} else if (argument === "--placeholder-time") {
|
|
816
|
+
placeholders.sourceTimeSec = boundedNumber(args, index, argument, 0, 604800);
|
|
817
|
+
index += 1;
|
|
818
|
+
} else if (argument === "--lqip-quality") {
|
|
819
|
+
placeholders.lqipQuality = boundedNumber(args, index, argument, 1, 100, true);
|
|
820
|
+
index += 1;
|
|
821
|
+
} else if (argument === "--lqip-max-bytes") {
|
|
822
|
+
placeholders.lqipMaxBytes = boundedNumber(args, index, argument, 256, 16384, true);
|
|
823
|
+
index += 1;
|
|
824
|
+
} else if (argument === "--contact-columns") {
|
|
825
|
+
contactSheet.columns = boundedNumber(args, index, argument, 1, 10, true);
|
|
826
|
+
index += 1;
|
|
827
|
+
} else if (argument === "--contact-rows") {
|
|
828
|
+
contactSheet.rows = boundedNumber(args, index, argument, 1, 10, true);
|
|
829
|
+
index += 1;
|
|
830
|
+
} else if (argument === "--contact-tile-width") {
|
|
831
|
+
contactSheet.tileWidth = boundedNumber(args, index, argument, 64, 640, true);
|
|
832
|
+
index += 1;
|
|
833
|
+
} else if (argument === "--contact-tile-height") {
|
|
834
|
+
contactSheet.tileHeight = boundedNumber(args, index, argument, 36, 640, true);
|
|
835
|
+
index += 1;
|
|
836
|
+
} else if (argument === "--contact-interval") {
|
|
837
|
+
contactSheet.intervalSec = boundedNumber(args, index, argument, 0.5, 3600);
|
|
838
|
+
index += 1;
|
|
839
|
+
} else if (argument === "--contact-start") {
|
|
840
|
+
contactSheet.startTimeSec = boundedNumber(args, index, argument, 0, 604800);
|
|
841
|
+
index += 1;
|
|
842
|
+
} else if (argument === "--contact-duration") {
|
|
843
|
+
contactSheet.durationSec = boundedNumber(args, index, argument, 0, 21600);
|
|
844
|
+
index += 1;
|
|
845
|
+
} else if (argument === "--contact-max-sheets") {
|
|
846
|
+
contactSheet.maxSheets = boundedNumber(args, index, argument, 1, 20, true);
|
|
847
|
+
index += 1;
|
|
848
|
+
} else if (argument === "--contact-format") {
|
|
849
|
+
const value = optionValue2(args, index, argument);
|
|
850
|
+
if (value !== "jpg" && value !== "png" && value !== "webp") {
|
|
851
|
+
throw new UsageError("--contact-format must be jpg, png, or webp");
|
|
852
|
+
}
|
|
853
|
+
contactSheet.format = value;
|
|
854
|
+
index += 1;
|
|
855
|
+
} else if (argument === "--contact-quality") {
|
|
856
|
+
contactSheet.quality = boundedNumber(args, index, argument, 1, 100, true);
|
|
857
|
+
index += 1;
|
|
858
|
+
} else if (argument === "--image-width") {
|
|
859
|
+
image.width = boundedNumber(args, index, argument, 1, 16384, true);
|
|
860
|
+
index += 1;
|
|
861
|
+
} else if (argument === "--image-height") {
|
|
862
|
+
image.height = boundedNumber(args, index, argument, 1, 16384, true);
|
|
863
|
+
index += 1;
|
|
864
|
+
} else if (argument === "--image-mode") {
|
|
865
|
+
const value = optionValue2(args, index, argument);
|
|
866
|
+
if (value !== "fit" && value !== "fill" && value !== "cover" && value !== "contain") {
|
|
867
|
+
throw new UsageError("--image-mode must be fit, fill, cover, or contain");
|
|
868
|
+
}
|
|
869
|
+
image.mode = value;
|
|
870
|
+
index += 1;
|
|
871
|
+
} else if (argument === "--image-format") {
|
|
872
|
+
const value = optionValue2(args, index, argument);
|
|
873
|
+
if (value !== "jpg" && value !== "png" && value !== "webp" && value !== "avif") {
|
|
874
|
+
throw new UsageError("--image-format must be jpg, png, webp, or avif");
|
|
875
|
+
}
|
|
876
|
+
image.format = value;
|
|
877
|
+
index += 1;
|
|
878
|
+
} else if (argument === "--image-quality") {
|
|
879
|
+
image.quality = boundedNumber(args, index, argument, 1, 100, true);
|
|
880
|
+
index += 1;
|
|
881
|
+
} else if (argument === "--image-max-bytes") {
|
|
882
|
+
image.maxBytes = boundedNumber(args, index, argument, 256, 1e8, true);
|
|
883
|
+
index += 1;
|
|
884
|
+
} else if (argument === "--image-min-quality") {
|
|
885
|
+
image.minQuality = boundedNumber(args, index, argument, 1, 100, true);
|
|
886
|
+
index += 1;
|
|
887
|
+
} else if (argument === "--clip-start") {
|
|
888
|
+
clip.startTimeSec = boundedNumber(args, index, argument, 0, 604800);
|
|
889
|
+
index += 1;
|
|
890
|
+
} else if (argument === "--clip-duration") {
|
|
891
|
+
clip.durationSec = boundedNumber(args, index, argument, 0.1, 300);
|
|
892
|
+
index += 1;
|
|
893
|
+
} else if (argument === "--clip-layout") {
|
|
894
|
+
const value = optionValue2(args, index, argument);
|
|
895
|
+
if (value !== "original" && value !== "vertical_blur") {
|
|
896
|
+
throw new UsageError("--clip-layout must be original or vertical_blur");
|
|
897
|
+
}
|
|
898
|
+
clip.layout = value;
|
|
899
|
+
index += 1;
|
|
900
|
+
} else if (argument === "--clip-captions") {
|
|
901
|
+
clip.burnCaptions = true;
|
|
902
|
+
} else if (argument === "--clip-transcript") {
|
|
903
|
+
clipTranscriptFile = optionValue2(args, index, argument);
|
|
904
|
+
index += 1;
|
|
905
|
+
} else if (argument === "--clip-min-duration") {
|
|
906
|
+
clipAnalysis.minDurationSec = boundedNumber(args, index, argument, 1, 300);
|
|
907
|
+
index += 1;
|
|
908
|
+
} else if (argument === "--clip-max-duration") {
|
|
909
|
+
clipAnalysis.maxDurationSec = boundedNumber(args, index, argument, 1, 300);
|
|
910
|
+
index += 1;
|
|
911
|
+
} else if (argument === "--clip-count") {
|
|
912
|
+
clipAnalysis.maxCandidates = boundedNumber(args, index, argument, 1, 20, true);
|
|
913
|
+
index += 1;
|
|
914
|
+
} else if (argument === "--clip-keyword") {
|
|
915
|
+
(clipAnalysis.keywords ??= []).push(optionValue2(args, index, argument));
|
|
916
|
+
index += 1;
|
|
917
|
+
} else if (argument === "--audiogram-artwork") {
|
|
918
|
+
audiogram.artworkSource = optionValue2(args, index, argument);
|
|
919
|
+
index += 1;
|
|
920
|
+
} else if (argument === "--audiogram-captions") {
|
|
921
|
+
audiogram.captionsSource = optionValue2(args, index, argument);
|
|
922
|
+
audiogram.burnCaptions = true;
|
|
923
|
+
index += 1;
|
|
924
|
+
} else if (argument === "--audiogram-layout") {
|
|
925
|
+
const value = optionValue2(args, index, argument);
|
|
926
|
+
if (value !== "square" && value !== "portrait" && value !== "landscape") {
|
|
927
|
+
throw new UsageError("--audiogram-layout must be square, portrait, or landscape");
|
|
928
|
+
}
|
|
929
|
+
audiogram.layout = value;
|
|
930
|
+
index += 1;
|
|
931
|
+
} else if (argument === "--audiogram-background") {
|
|
932
|
+
audiogram.backgroundColor = hexColor(args, index, argument);
|
|
933
|
+
index += 1;
|
|
934
|
+
} else if (argument === "--audiogram-waveform") {
|
|
935
|
+
audiogram.waveformColor = hexColor(args, index, argument);
|
|
936
|
+
index += 1;
|
|
937
|
+
} else if (argument === "--audiogram-fit") {
|
|
938
|
+
const value = optionValue2(args, index, argument);
|
|
939
|
+
if (value !== "contain" && value !== "cover" && value !== "blurred_background") {
|
|
940
|
+
throw new UsageError("--audiogram-fit must be contain, cover, or blurred_background");
|
|
941
|
+
}
|
|
942
|
+
audiogram.artworkFit = value;
|
|
943
|
+
index += 1;
|
|
944
|
+
} else if (argument === "--audiogram-waveform-gain") {
|
|
945
|
+
audiogram.waveformGain = boundedNumber(args, index, argument, 0.5, 4);
|
|
946
|
+
index += 1;
|
|
947
|
+
} else if (argument === "--audiogram-caption-position") {
|
|
948
|
+
const value = optionValue2(args, index, argument);
|
|
949
|
+
if (value !== "top" && value !== "bottom") {
|
|
950
|
+
throw new UsageError("--audiogram-caption-position must be top or bottom");
|
|
951
|
+
}
|
|
952
|
+
audiogram.captionPosition = value;
|
|
953
|
+
index += 1;
|
|
954
|
+
} else if (argument === "--audiogram-caption-scale") {
|
|
955
|
+
audiogram.captionFontScale = boundedNumber(args, index, argument, 0.75, 1.5);
|
|
956
|
+
index += 1;
|
|
957
|
+
} else if (argument === "--audiogram-normalize") {
|
|
958
|
+
audiogram.normalizeAudio = true;
|
|
959
|
+
} else if (argument === "--audiogram-loudness-target") {
|
|
960
|
+
audiogram.loudnessTargetLufs = boundedNumber(args, index, argument, -24, -12);
|
|
961
|
+
index += 1;
|
|
962
|
+
} else if (argument === "--audiogram-start") {
|
|
963
|
+
audiogram.startTimeSec = boundedNumber(args, index, argument, 0, 604800);
|
|
964
|
+
index += 1;
|
|
965
|
+
} else if (argument === "--audiogram-duration") {
|
|
966
|
+
audiogram.durationSec = boundedNumber(args, index, argument, 0.1, 300);
|
|
967
|
+
index += 1;
|
|
968
|
+
} else if (argument === "--audiogram-fps") {
|
|
969
|
+
audiogram.fps = boundedNumber(args, index, argument, 15, 30, true);
|
|
970
|
+
index += 1;
|
|
971
|
+
} else if (argument === "--privacy-detector") {
|
|
972
|
+
const value = optionValue2(args, index, argument);
|
|
973
|
+
if (value !== "face" && value !== "license_plate" && value !== "text") {
|
|
974
|
+
throw new UsageError("--privacy-detector must be face, license_plate, or text");
|
|
975
|
+
}
|
|
976
|
+
if (privacyDetectors.includes(value)) {
|
|
977
|
+
throw new UsageError(`Duplicate --privacy-detector: ${value}`);
|
|
978
|
+
}
|
|
979
|
+
privacyDetectors.push(value);
|
|
980
|
+
index += 1;
|
|
981
|
+
} else if (argument === "--privacy-style") {
|
|
982
|
+
const value = optionValue2(args, index, argument);
|
|
983
|
+
if (value !== "blur" && value !== "pixelate" && value !== "solid") {
|
|
984
|
+
throw new UsageError("--privacy-style must be blur, pixelate, or solid");
|
|
985
|
+
}
|
|
986
|
+
privacyRedaction.style = value;
|
|
987
|
+
index += 1;
|
|
988
|
+
} else if (argument === "--privacy-failure-mode") {
|
|
989
|
+
const value = optionValue2(args, index, argument);
|
|
990
|
+
if (value !== "fail_closed" && value !== "report_only") {
|
|
991
|
+
throw new UsageError("--privacy-failure-mode must be fail_closed or report_only");
|
|
992
|
+
}
|
|
993
|
+
privacyRedaction.failureMode = value;
|
|
994
|
+
index += 1;
|
|
995
|
+
} else if (argument === "--privacy-min-confidence") {
|
|
996
|
+
privacyRedaction.minConfidence = boundedNumber(args, index, argument, 0.3, 0.99);
|
|
997
|
+
index += 1;
|
|
998
|
+
} else if (argument === "--privacy-sample-interval") {
|
|
999
|
+
privacyRedaction.sampleIntervalSec = boundedNumber(args, index, argument, 0.1, 30);
|
|
1000
|
+
index += 1;
|
|
1001
|
+
} else if (argument === "--privacy-max-frames") {
|
|
1002
|
+
privacyRedaction.maxFrames = boundedNumber(args, index, argument, 1, 18e3, true);
|
|
1003
|
+
index += 1;
|
|
1004
|
+
} else if (argument === "--privacy-padding") {
|
|
1005
|
+
privacyRedaction.boxPaddingRatio = boundedNumber(args, index, argument, 0, 0.5);
|
|
1006
|
+
index += 1;
|
|
1007
|
+
} else if (argument === "--privacy-solid-color") {
|
|
1008
|
+
privacyRedaction.solidColor = hexColor(args, index, argument);
|
|
1009
|
+
index += 1;
|
|
1010
|
+
} else if (argument === "--privacy-pixel-block-size") {
|
|
1011
|
+
privacyRedaction.pixelBlockSize = boundedNumber(args, index, argument, 4, 128, true);
|
|
1012
|
+
index += 1;
|
|
1013
|
+
} else if (argument === "--privacy-strength") {
|
|
1014
|
+
const value = optionValue2(args, index, argument);
|
|
1015
|
+
if (value !== "standard" && value !== "strong") {
|
|
1016
|
+
throw new UsageError("--privacy-strength must be standard or strong");
|
|
1017
|
+
}
|
|
1018
|
+
privacyRedaction.privacyStrength = value;
|
|
1019
|
+
index += 1;
|
|
1020
|
+
} else if (argument === "--privacy-debug-observations") {
|
|
1021
|
+
privacyRedaction.includeDebugObservations = true;
|
|
681
1022
|
} else if (argument?.startsWith("-")) {
|
|
682
1023
|
throw new UsageError(`Unknown run option: ${argument}`);
|
|
683
1024
|
} else if (source === void 0) {
|
|
@@ -692,11 +1033,126 @@ function parseRunOptions(args) {
|
|
|
692
1033
|
if (recipe && outputs.length > 0) throw new UsageError("--recipe cannot be combined with --output or --preset");
|
|
693
1034
|
if (!recipe && outputs.length === 0) throw new UsageError("run requires --recipe or at least one --output or --preset");
|
|
694
1035
|
const unsupported = outputs.find(
|
|
695
|
-
(
|
|
1036
|
+
(output3) => output3.kind === "alias" && !OUTPUT_ALIASES.has(output3.value)
|
|
696
1037
|
);
|
|
697
1038
|
if (unsupported) throw new UsageError(`Unsupported output alias: ${unsupported.value}`);
|
|
698
1039
|
if (force && !download) throw new UsageError("--force requires --download");
|
|
699
1040
|
if (timeoutMs !== void 0 && !wait) throw new UsageError("--timeout-ms requires --wait or --download");
|
|
1041
|
+
const hasAnimationOptions = Object.keys(animation).length > 0;
|
|
1042
|
+
if (hasAnimationOptions) {
|
|
1043
|
+
const animationSelections = outputs.filter(
|
|
1044
|
+
(output3) => output3.kind === "preset" && (output3.value === "image_animated_webp_v1" || output3.value === "image_animated_apng_v1")
|
|
1045
|
+
);
|
|
1046
|
+
if (recipe || outputs.length !== 1 || animationSelections.length !== 1) {
|
|
1047
|
+
throw new UsageError("animation options require exactly one animated WebP or APNG --preset");
|
|
1048
|
+
}
|
|
1049
|
+
if (animation.quality !== void 0 && animationSelections[0]?.value === "image_animated_apng_v1") {
|
|
1050
|
+
throw new UsageError("--animation-quality applies to animated WebP only; APNG is lossless");
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
const hasPlaceholderOptions = Object.keys(placeholders).length > 0;
|
|
1054
|
+
if (hasPlaceholderOptions) {
|
|
1055
|
+
const placeholderSelections = outputs.filter(
|
|
1056
|
+
(output3) => output3.kind === "preset" && output3.value === "image_placeholders_v1"
|
|
1057
|
+
);
|
|
1058
|
+
if (recipe || outputs.length !== 1 || placeholderSelections.length !== 1) {
|
|
1059
|
+
throw new UsageError("placeholder options require exactly one image_placeholders_v1 --preset");
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const hasContactSheetOptions = Object.keys(contactSheet).length > 0;
|
|
1063
|
+
if (hasContactSheetOptions) {
|
|
1064
|
+
const selections = outputs.filter(
|
|
1065
|
+
(output3) => output3.kind === "preset" && output3.value === "contact_sheet_v1"
|
|
1066
|
+
);
|
|
1067
|
+
if (recipe || outputs.length !== 1 || selections.length !== 1) {
|
|
1068
|
+
throw new UsageError("contact-sheet options require exactly one contact_sheet_v1 --preset");
|
|
1069
|
+
}
|
|
1070
|
+
const geometry = {
|
|
1071
|
+
columns: 4,
|
|
1072
|
+
rows: 4,
|
|
1073
|
+
tileWidth: 320,
|
|
1074
|
+
tileHeight: 180,
|
|
1075
|
+
...contactSheet
|
|
1076
|
+
};
|
|
1077
|
+
if (geometry.columns * geometry.tileWidth > 4096 || geometry.rows * geometry.tileHeight > 4096) {
|
|
1078
|
+
throw new UsageError("contact-sheet width and height must each be at most 4096 pixels");
|
|
1079
|
+
}
|
|
1080
|
+
if (contactSheet.format === "png" && contactSheet.quality !== void 0) {
|
|
1081
|
+
throw new UsageError("--contact-quality applies to JPG and WebP only; PNG is lossless");
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
const hasImageOptions = Object.keys(image).length > 0;
|
|
1085
|
+
if (hasImageOptions) {
|
|
1086
|
+
const selections = outputs.filter(
|
|
1087
|
+
(output3) => output3.kind === "preset" && output3.value === "image_multi_v1"
|
|
1088
|
+
);
|
|
1089
|
+
if (recipe || outputs.length !== 1 || selections.length !== 1) {
|
|
1090
|
+
throw new UsageError("image rendition options require exactly one image_multi_v1 --preset");
|
|
1091
|
+
}
|
|
1092
|
+
if (image.minQuality !== void 0 && image.maxBytes === void 0) {
|
|
1093
|
+
throw new UsageError("--image-min-quality requires --image-max-bytes");
|
|
1094
|
+
}
|
|
1095
|
+
const quality = image.quality ?? 80;
|
|
1096
|
+
if ((image.minQuality ?? 1) > quality) {
|
|
1097
|
+
throw new UsageError("--image-min-quality must not exceed --image-quality");
|
|
1098
|
+
}
|
|
1099
|
+
const format = image.format ?? "webp";
|
|
1100
|
+
if (image.maxBytes !== void 0 && format !== "jpg" && format !== "webp") {
|
|
1101
|
+
throw new UsageError("--image-max-bytes currently supports --image-format jpg or webp only");
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
const audiogramSelections = outputs.filter(
|
|
1105
|
+
(output3) => output3.kind === "preset" && output3.value === "audiogram_v1"
|
|
1106
|
+
);
|
|
1107
|
+
const hasAudiogramOptions = Object.keys(audiogram).length > 0;
|
|
1108
|
+
if (hasAudiogramOptions || audiogramSelections.length > 0) {
|
|
1109
|
+
if (recipe || outputs.length !== 1 || audiogramSelections.length !== 1) {
|
|
1110
|
+
throw new UsageError("audiogram options require exactly one audiogram_v1 --preset");
|
|
1111
|
+
}
|
|
1112
|
+
if (!audiogram.artworkSource) {
|
|
1113
|
+
throw new UsageError("audiogram_v1 requires --audiogram-artwork <PNG|JPEG|WebP source>");
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
const hasPrivacyOptions = privacyDetectors.length > 0 || Object.keys(privacyRedaction).length > 0;
|
|
1117
|
+
const clipPreset = outputs.length === 1 && outputs[0]?.kind === "preset" ? outputs[0].value : void 0;
|
|
1118
|
+
const hasClip = Object.keys(clip).length > 0 || clipPreset === "video_clip_v1";
|
|
1119
|
+
const hasClipAnalysis = Object.keys(clipAnalysis).length > 0 || clipPreset === "clip_candidates_v1";
|
|
1120
|
+
if (hasClip && (recipe || clipPreset !== "video_clip_v1" || hasClipAnalysis)) {
|
|
1121
|
+
throw new UsageError("clip render options require exactly one video_clip_v1 --preset");
|
|
1122
|
+
}
|
|
1123
|
+
if (hasClip && (clip.startTimeSec === void 0 || clip.durationSec === void 0)) {
|
|
1124
|
+
throw new UsageError("video_clip_v1 requires --clip-start and --clip-duration");
|
|
1125
|
+
}
|
|
1126
|
+
if (hasClipAnalysis && (recipe || clipPreset !== "clip_candidates_v1")) {
|
|
1127
|
+
throw new UsageError("clip analysis options require exactly one clip_candidates_v1 --preset");
|
|
1128
|
+
}
|
|
1129
|
+
if ((clipAnalysis.minDurationSec ?? 15) > (clipAnalysis.maxDurationSec ?? 60)) {
|
|
1130
|
+
throw new UsageError("--clip-min-duration must not exceed --clip-max-duration");
|
|
1131
|
+
}
|
|
1132
|
+
if ((clipAnalysis.keywords?.length ?? 0) > 20) {
|
|
1133
|
+
throw new UsageError("At most 20 --clip-keyword values are supported");
|
|
1134
|
+
}
|
|
1135
|
+
if (clipAnalysis.keywords?.some((keyword) => !keyword.trim() || Buffer.byteLength(keyword, "utf8") > 100)) {
|
|
1136
|
+
throw new UsageError("Each --clip-keyword must contain 1 to 100 UTF-8 bytes of nonblank text");
|
|
1137
|
+
}
|
|
1138
|
+
if (clipTranscriptFile && !hasClip && !hasClipAnalysis) {
|
|
1139
|
+
throw new UsageError("--clip-transcript requires a clipping preset");
|
|
1140
|
+
}
|
|
1141
|
+
if (clip.burnCaptions && !clipTranscriptFile) {
|
|
1142
|
+
throw new UsageError("--clip-captions requires --clip-transcript with source timestamps");
|
|
1143
|
+
}
|
|
1144
|
+
if (hasPrivacyOptions) {
|
|
1145
|
+
if (recipe) throw new UsageError("privacy-redaction options cannot be combined with --recipe");
|
|
1146
|
+
if (privacyDetectors.length === 0) {
|
|
1147
|
+
throw new UsageError("privacy redaction requires at least one --privacy-detector");
|
|
1148
|
+
}
|
|
1149
|
+
if (/\.(?:mp4|mov|mkv|webm|gif|apng)(?:$|[?#])/i.test(source)) {
|
|
1150
|
+
throw new UsageError(
|
|
1151
|
+
"privacy redaction Preview accepts still-image inputs only; video and animated images are not available"
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
privacyRedaction.detectors = privacyDetectors;
|
|
1155
|
+
}
|
|
700
1156
|
return {
|
|
701
1157
|
source,
|
|
702
1158
|
outputs,
|
|
@@ -707,22 +1163,80 @@ function parseRunOptions(args) {
|
|
|
707
1163
|
...timeoutMs === void 0 ? {} : { timeoutMs },
|
|
708
1164
|
...download === void 0 ? {} : { download },
|
|
709
1165
|
force,
|
|
710
|
-
json
|
|
1166
|
+
json,
|
|
1167
|
+
...hasAnimationOptions ? { animation } : {},
|
|
1168
|
+
...hasPlaceholderOptions ? { placeholders } : {},
|
|
1169
|
+
...hasContactSheetOptions ? { contactSheet } : {},
|
|
1170
|
+
...hasImageOptions ? { image } : {},
|
|
1171
|
+
...hasClip ? { clip } : {},
|
|
1172
|
+
...hasClipAnalysis ? { clipAnalysis } : {},
|
|
1173
|
+
...clipTranscriptFile ? { clipTranscriptFile } : {},
|
|
1174
|
+
...hasAudiogramOptions || audiogramSelections.length > 0 ? { audiogram } : {},
|
|
1175
|
+
...hasPrivacyOptions ? { privacyRedaction } : {}
|
|
711
1176
|
};
|
|
712
1177
|
}
|
|
713
|
-
|
|
714
|
-
|
|
1178
|
+
function isHostedAsset(source) {
|
|
1179
|
+
return /^(?:https?|gs):\/\//i.test(source);
|
|
1180
|
+
}
|
|
1181
|
+
async function resolveAudiogramAsset(source, label, uploads) {
|
|
1182
|
+
if (isHostedAsset(source)) return source;
|
|
1183
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(source) && !/^file:\/\//i.test(source)) {
|
|
1184
|
+
throw new UsageError(`Audiogram ${label} must be an HTTP(S) URL, gs:// URI, or local file path`);
|
|
1185
|
+
}
|
|
1186
|
+
if (!uploads) {
|
|
1187
|
+
throw new UsageError(`This client cannot upload local Audiogram ${label}; use an HTTP(S) URL or gs:// URI`);
|
|
1188
|
+
}
|
|
1189
|
+
return await uploads.resolveSource(source);
|
|
1190
|
+
}
|
|
1191
|
+
async function resolveAudiogram(value, uploads) {
|
|
1192
|
+
const artworkSource = await resolveAudiogramAsset(value.artworkSource, "artwork", uploads);
|
|
1193
|
+
const captionsSource = value.captionsSource === void 0 ? void 0 : await resolveAudiogramAsset(value.captionsSource, "captions", uploads);
|
|
1194
|
+
return {
|
|
1195
|
+
...value,
|
|
1196
|
+
artworkSource,
|
|
1197
|
+
...captionsSource === void 0 ? {} : { captionsSource, burnCaptions: true }
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
async function resolveOutputs(selections, capabilitiesClient, animation, placeholders, contactSheet, image, audiogram, privacyRedaction, clip, clipAnalysis) {
|
|
1201
|
+
if (!privacyRedaction && !selections.some((selection) => selection.kind === "preset")) {
|
|
715
1202
|
return selections.map((selection) => selection.value);
|
|
716
1203
|
}
|
|
717
1204
|
const capabilities = await capabilitiesClient.retrieve();
|
|
718
1205
|
const publicPresets = new Set(capabilities.publicPresets);
|
|
719
1206
|
return selections.map((selection) => {
|
|
720
|
-
if (selection.kind === "alias")
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1207
|
+
if (selection.kind === "alias" && !privacyRedaction) {
|
|
1208
|
+
return selection.value;
|
|
1209
|
+
}
|
|
1210
|
+
const selectedName = selection.kind === "alias" ? capabilities.outputAliases[selection.value]?.preset : selection.value;
|
|
1211
|
+
const preset = selectedName ? capabilities.presets[selectedName] : void 0;
|
|
1212
|
+
if (!selectedName || !publicPresets.has(selectedName) || !preset) {
|
|
1213
|
+
throw new UsageError(selection.kind === "alias" ? `Gateway did not provide a public expansion for alias: ${selection.value}` : `Unknown or non-public preset: ${selection.value}`);
|
|
1214
|
+
}
|
|
1215
|
+
if (privacyRedaction && preset.outputType !== "image") {
|
|
1216
|
+
throw new UsageError(
|
|
1217
|
+
"privacy redaction Preview can be attached to image outputs only"
|
|
1218
|
+
);
|
|
724
1219
|
}
|
|
725
|
-
|
|
1220
|
+
const output3 = { type: preset.outputType, preset: selectedName };
|
|
1221
|
+
if (animation) output3.animation = animation;
|
|
1222
|
+
if (placeholders) output3.placeholders = placeholders;
|
|
1223
|
+
if (contactSheet) output3.contactSheet = contactSheet;
|
|
1224
|
+
if (image) {
|
|
1225
|
+
output3.images = [{
|
|
1226
|
+
width: image.width ?? 1280,
|
|
1227
|
+
height: image.height ?? 720,
|
|
1228
|
+
mode: image.mode ?? "fit",
|
|
1229
|
+
format: image.format ?? "webp",
|
|
1230
|
+
quality: image.quality ?? 80,
|
|
1231
|
+
...image.maxBytes === void 0 ? {} : { maxBytes: image.maxBytes },
|
|
1232
|
+
...image.maxBytes === void 0 ? {} : { minQuality: image.minQuality ?? 1 }
|
|
1233
|
+
}];
|
|
1234
|
+
}
|
|
1235
|
+
if (audiogram) output3.audiogram = audiogram;
|
|
1236
|
+
if (privacyRedaction) output3.privacyRedaction = privacyRedaction;
|
|
1237
|
+
if (clip) output3.clip = clip;
|
|
1238
|
+
if (clipAnalysis) output3.clipAnalysis = clipAnalysis;
|
|
1239
|
+
return output3;
|
|
726
1240
|
});
|
|
727
1241
|
}
|
|
728
1242
|
function receiptProjection(job) {
|
|
@@ -778,19 +1292,40 @@ Bundle: ${job.bundle.available ? "available" : "unavailable"}${error}
|
|
|
778
1292
|
}
|
|
779
1293
|
async function runCommand(args, dependencies) {
|
|
780
1294
|
const options = parseRunOptions(args);
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
source: options.source,
|
|
784
|
-
...outputs === void 0 ? {} : { outputs },
|
|
785
|
-
...options.recipe === void 0 ? {} : { recipe: options.recipe },
|
|
786
|
-
...options.metadata === void 0 ? {} : { metadata: options.metadata },
|
|
787
|
-
...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey }
|
|
788
|
-
};
|
|
789
|
-
const isLocalSource = !/^(?:https?|gs):\/\//i.test(options.source);
|
|
1295
|
+
const hasLocalAudiogramAsset = options.audiogram !== void 0 && (!isHostedAsset(options.audiogram.artworkSource) || options.audiogram.captionsSource !== void 0 && !isHostedAsset(options.audiogram.captionsSource));
|
|
1296
|
+
const isLocalSource = !isHostedAsset(options.source);
|
|
790
1297
|
dependencies.activity.start(
|
|
791
|
-
isLocalSource ? "Uploading local
|
|
1298
|
+
isLocalSource || hasLocalAudiogramAsset ? "Uploading local media and creating job\u2026" : "Creating job\u2026"
|
|
792
1299
|
);
|
|
793
1300
|
try {
|
|
1301
|
+
if ((options.clip || options.clipAnalysis) && !("getClipCandidates" in dependencies.jobs)) {
|
|
1302
|
+
throw new UsageError("Clipping requires the MediaRuntime Node SDK release with getClipCandidates support");
|
|
1303
|
+
}
|
|
1304
|
+
if (options.clipTranscriptFile) {
|
|
1305
|
+
const transcript = await readClipTranscript(options.clipTranscriptFile);
|
|
1306
|
+
if (options.clip) options.clip.transcript = transcript;
|
|
1307
|
+
if (options.clipAnalysis) options.clipAnalysis.transcript = transcript;
|
|
1308
|
+
}
|
|
1309
|
+
const audiogram = options.audiogram === void 0 ? void 0 : await resolveAudiogram(options.audiogram, dependencies.uploads);
|
|
1310
|
+
const outputs = options.recipe ? void 0 : await resolveOutputs(
|
|
1311
|
+
options.outputs,
|
|
1312
|
+
dependencies.capabilities,
|
|
1313
|
+
options.animation,
|
|
1314
|
+
options.placeholders,
|
|
1315
|
+
options.contactSheet,
|
|
1316
|
+
options.image,
|
|
1317
|
+
audiogram,
|
|
1318
|
+
options.privacyRedaction,
|
|
1319
|
+
options.clip,
|
|
1320
|
+
options.clipAnalysis
|
|
1321
|
+
);
|
|
1322
|
+
const params = {
|
|
1323
|
+
source: options.source,
|
|
1324
|
+
...outputs === void 0 ? {} : { outputs },
|
|
1325
|
+
...options.recipe === void 0 ? {} : { recipe: options.recipe },
|
|
1326
|
+
...options.metadata === void 0 ? {} : { metadata: options.metadata },
|
|
1327
|
+
...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey }
|
|
1328
|
+
};
|
|
794
1329
|
const submitted = await dependencies.jobs.create(params);
|
|
795
1330
|
if (!options.wait) {
|
|
796
1331
|
dependencies.activity.stop();
|
|
@@ -959,6 +1494,7 @@ async function runRecipesCommand(args, dependencies) {
|
|
|
959
1494
|
const value = await recipeFile(file);
|
|
960
1495
|
const template = value.template ?? value;
|
|
961
1496
|
output(await dependencies.recipes.createVersion(name, {
|
|
1497
|
+
// Optimistic concurrency prevents two maintainers from replacing the same latest version.
|
|
962
1498
|
expectedLatestVersion: expected,
|
|
963
1499
|
...typeof value.description === "string" ? { description: value.description } : {},
|
|
964
1500
|
template
|
|
@@ -977,6 +1513,452 @@ async function runRecipesCommand(args, dependencies) {
|
|
|
977
1513
|
throw new UsageError("Usage: mediaruntime recipes <list|get|create|version|archive>");
|
|
978
1514
|
}
|
|
979
1515
|
|
|
1516
|
+
// src/commands/stickers.ts
|
|
1517
|
+
var COLLECTION_PATTERN = /^stc_[0-9a-f]{32}$/;
|
|
1518
|
+
var TOKEN_SCOPES = /* @__PURE__ */ new Set(["packs:read", "stickers:search", "stickers:read", "assets:resolve"]);
|
|
1519
|
+
var STICKER_VARIANTS = /* @__PURE__ */ new Set([
|
|
1520
|
+
"animated",
|
|
1521
|
+
"reduced_motion",
|
|
1522
|
+
"small_80",
|
|
1523
|
+
"small_100",
|
|
1524
|
+
"small_160",
|
|
1525
|
+
"thumbnail"
|
|
1526
|
+
]);
|
|
1527
|
+
var STICKERS_HELP = `MediaRuntime Hosted Sticker Runtime
|
|
1528
|
+
|
|
1529
|
+
Usage:
|
|
1530
|
+
mediaruntime stickers collections list [--include-archived] [--json]
|
|
1531
|
+
mediaruntime stickers collections create --name <name> [--description <text>] [--json]
|
|
1532
|
+
mediaruntime stickers collections get <collection_id> [--json]
|
|
1533
|
+
mediaruntime stickers collections update <collection_id>
|
|
1534
|
+
[--name <name>] [--description <text> | --clear-description]
|
|
1535
|
+
[--status active|archived] [--json]
|
|
1536
|
+
mediaruntime stickers collections archive <collection_id> [--json]
|
|
1537
|
+
mediaruntime stickers collections packs list <collection_id> [--json]
|
|
1538
|
+
mediaruntime stickers collections packs enable <collection_id>
|
|
1539
|
+
(--pack <pack_id> | --activation <activation_id>) [--json]
|
|
1540
|
+
mediaruntime stickers collections packs disable <collection_id> --pack <pack_id> [--json]
|
|
1541
|
+
mediaruntime stickers packs list --collection <collection_id> [--json]
|
|
1542
|
+
mediaruntime stickers search <query> --collection <collection_id>
|
|
1543
|
+
[--pack <pack_id>] [--category <category>] [--animated | --static]
|
|
1544
|
+
[--limit <1..50>] [--json]
|
|
1545
|
+
mediaruntime stickers typeahead <prefix> --collection <collection_id>
|
|
1546
|
+
[--pack <pack_id>] [--locale <locale>] [--limit <1..20>] [--json]
|
|
1547
|
+
mediaruntime stickers get <sticker_id> --collection <collection_id> [--json]
|
|
1548
|
+
mediaruntime stickers resolve <sticker_id> --variant <name>
|
|
1549
|
+
--collection <collection_id> [--json]
|
|
1550
|
+
mediaruntime stickers usage [--json]
|
|
1551
|
+
mediaruntime stickers token create --collection <collection_id>
|
|
1552
|
+
[--expires-in <60..3600>] [--scope <scope>]... [--json]
|
|
1553
|
+
|
|
1554
|
+
Authentication:
|
|
1555
|
+
API key or secure browser login is the default and is required for management,
|
|
1556
|
+
pack binding, usage, and token issuance. MEDIARUNTIME_STICKER_CLIENT_TOKEN is
|
|
1557
|
+
accepted only for packs/search/typeahead/get/resolve runtime reads and must match
|
|
1558
|
+
the explicit --collection. Secrets are never accepted as command-line options.
|
|
1559
|
+
`;
|
|
1560
|
+
function optionValue4(args, index, option) {
|
|
1561
|
+
const value = args[index + 1];
|
|
1562
|
+
if (value === void 0 || value.startsWith("--")) throw new UsageError(`${option} requires a value`);
|
|
1563
|
+
return value;
|
|
1564
|
+
}
|
|
1565
|
+
function collectionId(value) {
|
|
1566
|
+
if (!value || !COLLECTION_PATTERN.test(value)) {
|
|
1567
|
+
throw new UsageError("--collection must be a MediaRuntime sticker collection ID");
|
|
1568
|
+
}
|
|
1569
|
+
return value;
|
|
1570
|
+
}
|
|
1571
|
+
function boundedInteger(raw, option, minimum, maximum) {
|
|
1572
|
+
const value = Number(raw);
|
|
1573
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
1574
|
+
throw new UsageError(`${option} must be an integer between ${minimum} and ${maximum}`);
|
|
1575
|
+
}
|
|
1576
|
+
return value;
|
|
1577
|
+
}
|
|
1578
|
+
function output2(value, json, writeStdout, human) {
|
|
1579
|
+
writeStdout(json ? `${JSON.stringify(value)}
|
|
1580
|
+
` : human);
|
|
1581
|
+
}
|
|
1582
|
+
function table2(headings, rows) {
|
|
1583
|
+
const widths = headings.map(
|
|
1584
|
+
(heading, index) => Math.max(heading.length, ...rows.map((row) => row[index]?.length ?? 0))
|
|
1585
|
+
);
|
|
1586
|
+
const render = (row) => row.map((cell3, index) => cell3.padEnd(widths[index] ?? cell3.length)).join(" ").trimEnd();
|
|
1587
|
+
return `${[render(headings), render(widths.map((width) => "-".repeat(width))), ...rows.map(render)].join("\n")}
|
|
1588
|
+
`;
|
|
1589
|
+
}
|
|
1590
|
+
function collectionTable(page) {
|
|
1591
|
+
return table2(
|
|
1592
|
+
["ID", "NAME", "STATUS", "PACKS", "UPDATED"],
|
|
1593
|
+
page.items.map((item) => [
|
|
1594
|
+
item.collectionId,
|
|
1595
|
+
item.name,
|
|
1596
|
+
item.status,
|
|
1597
|
+
String(item.packs.length),
|
|
1598
|
+
item.updatedAt
|
|
1599
|
+
])
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
function bindingTable(page) {
|
|
1603
|
+
return table2(
|
|
1604
|
+
["PACK", "NAME", "VERSION", "STATUS", "HISTORICAL", "ACTIVATION"],
|
|
1605
|
+
page.items.map((item) => [
|
|
1606
|
+
item.packId,
|
|
1607
|
+
item.packName,
|
|
1608
|
+
item.packVersion,
|
|
1609
|
+
item.status,
|
|
1610
|
+
item.historicalAccess,
|
|
1611
|
+
item.activationId
|
|
1612
|
+
])
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
function runtimePackTable(items) {
|
|
1616
|
+
return table2(
|
|
1617
|
+
["PACK", "NAME", "VERSION", "STICKERS", "ANIMATED"],
|
|
1618
|
+
items.map((item) => [
|
|
1619
|
+
item.packId,
|
|
1620
|
+
item.name,
|
|
1621
|
+
item.version,
|
|
1622
|
+
String(item.assetCount),
|
|
1623
|
+
item.animated ? "yes" : "no"
|
|
1624
|
+
])
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
function stickerTable(items) {
|
|
1628
|
+
return table2(
|
|
1629
|
+
["STICKER", "LABEL", "EMOJI", "CATEGORY", "ANIMATED", "VARIANTS"],
|
|
1630
|
+
items.map((item) => [
|
|
1631
|
+
item.stickerId,
|
|
1632
|
+
item.label,
|
|
1633
|
+
item.emoji ?? "-",
|
|
1634
|
+
item.category ?? "-",
|
|
1635
|
+
item.animated ? "yes" : "no",
|
|
1636
|
+
item.variants.map((variant) => variant.name).join(",")
|
|
1637
|
+
])
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
function hostedClient(dependencies) {
|
|
1641
|
+
if (!dependencies.hosted) {
|
|
1642
|
+
throw new UsageError("This stickers command requires MEDIARUNTIME_API_KEY or mediaruntime login");
|
|
1643
|
+
}
|
|
1644
|
+
return dependencies.hosted;
|
|
1645
|
+
}
|
|
1646
|
+
function runtimeClient(selectedCollectionId, dependencies) {
|
|
1647
|
+
if (dependencies.runtime) return dependencies.runtime(selectedCollectionId);
|
|
1648
|
+
return hostedClient(dependencies).collection(selectedCollectionId);
|
|
1649
|
+
}
|
|
1650
|
+
async function collectionsCommand(args, dependencies) {
|
|
1651
|
+
const subcommand = args[0];
|
|
1652
|
+
const rest = args.slice(1);
|
|
1653
|
+
const client = hostedClient(dependencies);
|
|
1654
|
+
const json = rest.includes("--json");
|
|
1655
|
+
if (subcommand === "list") {
|
|
1656
|
+
let includeArchived = false;
|
|
1657
|
+
for (const argument of rest) {
|
|
1658
|
+
if (argument === "--include-archived") includeArchived = true;
|
|
1659
|
+
else if (argument !== "--json") throw new UsageError(`Unknown stickers collections list option: ${argument}`);
|
|
1660
|
+
}
|
|
1661
|
+
const page = await client.listCollections({ includeArchived });
|
|
1662
|
+
output2(page, json, dependencies.writeStdout, collectionTable(page));
|
|
1663
|
+
return 0;
|
|
1664
|
+
}
|
|
1665
|
+
if (subcommand === "create") {
|
|
1666
|
+
let name;
|
|
1667
|
+
let description;
|
|
1668
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1669
|
+
const argument = rest[index];
|
|
1670
|
+
if (argument === "--name") {
|
|
1671
|
+
name = optionValue4(rest, index, argument);
|
|
1672
|
+
index += 1;
|
|
1673
|
+
} else if (argument === "--description") {
|
|
1674
|
+
description = optionValue4(rest, index, argument);
|
|
1675
|
+
index += 1;
|
|
1676
|
+
} else if (argument !== "--json") throw new UsageError(`Unknown stickers collections create option: ${argument}`);
|
|
1677
|
+
}
|
|
1678
|
+
if (!name) throw new UsageError("Usage: mediaruntime stickers collections create --name <name> [--description <text>]");
|
|
1679
|
+
const created = await client.createCollection({ name, ...description === void 0 ? {} : { description } });
|
|
1680
|
+
output2(created, json, dependencies.writeStdout, `Created sticker collection ${created.collectionId} (${created.name})
|
|
1681
|
+
`);
|
|
1682
|
+
return 0;
|
|
1683
|
+
}
|
|
1684
|
+
if (subcommand === "get" || subcommand === "archive") {
|
|
1685
|
+
const positional = rest.filter((argument) => argument !== "--json");
|
|
1686
|
+
if (positional.length !== 1) {
|
|
1687
|
+
throw new UsageError(`Usage: mediaruntime stickers collections ${subcommand} <collection_id>`);
|
|
1688
|
+
}
|
|
1689
|
+
const selected = collectionId(positional[0]);
|
|
1690
|
+
const result = subcommand === "get" ? await client.getCollection(selected) : await client.archiveCollection(selected);
|
|
1691
|
+
const human = subcommand === "get" ? `${result.name} (${result.collectionId})
|
|
1692
|
+
Status: ${result.status}
|
|
1693
|
+
Description: ${result.description || "-"}
|
|
1694
|
+
Enabled packs: ${result.packs.length}
|
|
1695
|
+
` : `Archived sticker collection ${result.collectionId}
|
|
1696
|
+
`;
|
|
1697
|
+
output2(result, json, dependencies.writeStdout, human);
|
|
1698
|
+
return 0;
|
|
1699
|
+
}
|
|
1700
|
+
if (subcommand === "update") {
|
|
1701
|
+
let selected;
|
|
1702
|
+
let name;
|
|
1703
|
+
let description;
|
|
1704
|
+
let status;
|
|
1705
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1706
|
+
const argument = rest[index];
|
|
1707
|
+
if (argument === "--name") {
|
|
1708
|
+
name = optionValue4(rest, index, argument);
|
|
1709
|
+
index += 1;
|
|
1710
|
+
} else if (argument === "--description") {
|
|
1711
|
+
description = optionValue4(rest, index, argument);
|
|
1712
|
+
index += 1;
|
|
1713
|
+
} else if (argument === "--clear-description") {
|
|
1714
|
+
description = null;
|
|
1715
|
+
} else if (argument === "--status") {
|
|
1716
|
+
const value = optionValue4(rest, index, argument);
|
|
1717
|
+
if (value !== "active" && value !== "archived") throw new UsageError("--status must be active or archived");
|
|
1718
|
+
status = value;
|
|
1719
|
+
index += 1;
|
|
1720
|
+
} else if (argument !== "--json" && !selected) selected = argument;
|
|
1721
|
+
else if (argument !== "--json") throw new UsageError(`Unknown stickers collections update option: ${argument}`);
|
|
1722
|
+
}
|
|
1723
|
+
const id = collectionId(selected);
|
|
1724
|
+
if (name === void 0 && description === void 0 && status === void 0) {
|
|
1725
|
+
throw new UsageError("stickers collections update requires --name, --description, --clear-description, or --status");
|
|
1726
|
+
}
|
|
1727
|
+
const updated = await client.updateCollection(id, {
|
|
1728
|
+
...name === void 0 ? {} : { name },
|
|
1729
|
+
...description === void 0 ? {} : { description },
|
|
1730
|
+
...status === void 0 ? {} : { status }
|
|
1731
|
+
});
|
|
1732
|
+
output2(updated, json, dependencies.writeStdout, `Updated sticker collection ${updated.collectionId}
|
|
1733
|
+
`);
|
|
1734
|
+
return 0;
|
|
1735
|
+
}
|
|
1736
|
+
if (subcommand === "packs") return collectionPacksCommand(rest, dependencies);
|
|
1737
|
+
throw new UsageError("Usage: mediaruntime stickers collections <list|create|get|update|archive|packs>");
|
|
1738
|
+
}
|
|
1739
|
+
async function collectionPacksCommand(args, dependencies) {
|
|
1740
|
+
const action = args[0];
|
|
1741
|
+
const rest = args.slice(1);
|
|
1742
|
+
const json = rest.includes("--json");
|
|
1743
|
+
const client = hostedClient(dependencies);
|
|
1744
|
+
let selected;
|
|
1745
|
+
let packId;
|
|
1746
|
+
let activationId;
|
|
1747
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1748
|
+
const argument = rest[index];
|
|
1749
|
+
if (argument === "--pack") {
|
|
1750
|
+
packId = optionValue4(rest, index, argument);
|
|
1751
|
+
index += 1;
|
|
1752
|
+
} else if (argument === "--activation") {
|
|
1753
|
+
activationId = optionValue4(rest, index, argument);
|
|
1754
|
+
index += 1;
|
|
1755
|
+
} else if (argument !== "--json" && !selected) selected = argument;
|
|
1756
|
+
else if (argument !== "--json") throw new UsageError(`Unknown stickers collections packs option: ${argument}`);
|
|
1757
|
+
}
|
|
1758
|
+
const id = collectionId(selected);
|
|
1759
|
+
if (action === "list") {
|
|
1760
|
+
if (packId || activationId) throw new UsageError("stickers collections packs list does not accept a pack selector");
|
|
1761
|
+
const page = await client.listCollectionPacks(id);
|
|
1762
|
+
output2(page, json, dependencies.writeStdout, bindingTable(page));
|
|
1763
|
+
return 0;
|
|
1764
|
+
}
|
|
1765
|
+
if (action === "enable") {
|
|
1766
|
+
if ((packId ? 1 : 0) + (activationId ? 1 : 0) !== 1) {
|
|
1767
|
+
throw new UsageError("stickers collections packs enable requires exactly one of --pack or --activation");
|
|
1768
|
+
}
|
|
1769
|
+
const binding = activationId ? await client.addCollectionPack(id, { activationId }) : await client.enableCollectionPack(id, packId ?? "");
|
|
1770
|
+
output2(binding, json, dependencies.writeStdout, `Enabled pack ${binding.packId} in ${id}
|
|
1771
|
+
`);
|
|
1772
|
+
return 0;
|
|
1773
|
+
}
|
|
1774
|
+
if (action === "disable") {
|
|
1775
|
+
if (!packId || activationId) throw new UsageError("stickers collections packs disable requires --pack <pack_id>");
|
|
1776
|
+
const binding = await client.disableCollectionPack(id, packId);
|
|
1777
|
+
output2(binding, json, dependencies.writeStdout, `Disabled pack ${binding.packId} in ${id}; historical references remain ${binding.historicalAccess}
|
|
1778
|
+
`);
|
|
1779
|
+
return 0;
|
|
1780
|
+
}
|
|
1781
|
+
throw new UsageError("Usage: mediaruntime stickers collections packs <list|enable|disable> <collection_id>");
|
|
1782
|
+
}
|
|
1783
|
+
function parseRuntimeOptions(args, command) {
|
|
1784
|
+
const options = { json: false, positional: [] };
|
|
1785
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1786
|
+
const argument = args[index];
|
|
1787
|
+
if (argument === "--collection") {
|
|
1788
|
+
options.collection = optionValue4(args, index, argument);
|
|
1789
|
+
index += 1;
|
|
1790
|
+
} else if (argument === "--pack") {
|
|
1791
|
+
options.packId = optionValue4(args, index, argument);
|
|
1792
|
+
index += 1;
|
|
1793
|
+
} else if (argument === "--category") {
|
|
1794
|
+
options.category = optionValue4(args, index, argument);
|
|
1795
|
+
index += 1;
|
|
1796
|
+
} else if (argument === "--locale") {
|
|
1797
|
+
options.locale = optionValue4(args, index, argument);
|
|
1798
|
+
index += 1;
|
|
1799
|
+
} else if (argument === "--limit") {
|
|
1800
|
+
const maximum = command === "typeahead" ? 20 : 50;
|
|
1801
|
+
options.limit = boundedInteger(optionValue4(args, index, argument), argument, 1, maximum);
|
|
1802
|
+
index += 1;
|
|
1803
|
+
} else if (argument === "--animated" || argument === "--static") {
|
|
1804
|
+
if (options.animated !== void 0) throw new UsageError("Use only one of --animated or --static");
|
|
1805
|
+
options.animated = argument === "--animated";
|
|
1806
|
+
} else if (argument === "--variant") {
|
|
1807
|
+
options.variant = optionValue4(args, index, argument);
|
|
1808
|
+
index += 1;
|
|
1809
|
+
} else if (argument === "--json") options.json = true;
|
|
1810
|
+
else if (argument?.startsWith("--")) throw new UsageError(`Unknown stickers ${command} option: ${argument}`);
|
|
1811
|
+
else options.positional.push(argument ?? "");
|
|
1812
|
+
}
|
|
1813
|
+
return options;
|
|
1814
|
+
}
|
|
1815
|
+
async function runtimeCommand(command, args, dependencies) {
|
|
1816
|
+
const options = parseRuntimeOptions(args, command);
|
|
1817
|
+
const id = collectionId(options.collection);
|
|
1818
|
+
const client = runtimeClient(id, dependencies);
|
|
1819
|
+
if (command === "packs") {
|
|
1820
|
+
if (options.packId || options.category || options.locale || options.limit !== void 0 || options.animated !== void 0 || options.variant) {
|
|
1821
|
+
throw new UsageError("stickers packs list accepts only --collection and --json");
|
|
1822
|
+
}
|
|
1823
|
+
if (options.positional.length !== 1 || options.positional[0] !== "list") {
|
|
1824
|
+
throw new UsageError("Usage: mediaruntime stickers packs list --collection <collection_id>");
|
|
1825
|
+
}
|
|
1826
|
+
const items = await client.listPacks();
|
|
1827
|
+
output2({ items, total: items.length }, options.json, dependencies.writeStdout, runtimePackTable(items));
|
|
1828
|
+
return 0;
|
|
1829
|
+
}
|
|
1830
|
+
if (command === "search") {
|
|
1831
|
+
if (options.locale || options.variant) throw new UsageError("stickers search does not accept typeahead or asset options");
|
|
1832
|
+
if (options.positional.length !== 1) throw new UsageError("Usage: mediaruntime stickers search <query> --collection <collection_id>");
|
|
1833
|
+
const result = await client.search(options.positional[0] ?? "", {
|
|
1834
|
+
...options.packId === void 0 ? {} : { packId: options.packId },
|
|
1835
|
+
...options.category === void 0 ? {} : { category: options.category },
|
|
1836
|
+
...options.animated === void 0 ? {} : { animated: options.animated },
|
|
1837
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
1838
|
+
});
|
|
1839
|
+
output2(result, options.json, dependencies.writeStdout, stickerTable(result.items));
|
|
1840
|
+
return 0;
|
|
1841
|
+
}
|
|
1842
|
+
if (command === "typeahead") {
|
|
1843
|
+
if (options.category || options.animated !== void 0 || options.variant) {
|
|
1844
|
+
throw new UsageError("stickers typeahead accepts only --pack, --locale, and --limit filters");
|
|
1845
|
+
}
|
|
1846
|
+
if (options.positional.length !== 1) throw new UsageError("Usage: mediaruntime stickers typeahead <prefix> --collection <collection_id>");
|
|
1847
|
+
const result = await client.typeahead(options.positional[0] ?? "", {
|
|
1848
|
+
...options.packId === void 0 ? {} : { packId: options.packId },
|
|
1849
|
+
...options.locale === void 0 ? {} : { locale: options.locale },
|
|
1850
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
1851
|
+
});
|
|
1852
|
+
const human = table2(
|
|
1853
|
+
["SUGGESTION", "STICKERS"],
|
|
1854
|
+
result.suggestions.map((suggestion) => [suggestion.text, String(suggestion.assetCount)])
|
|
1855
|
+
);
|
|
1856
|
+
output2(result, options.json, dependencies.writeStdout, human);
|
|
1857
|
+
return 0;
|
|
1858
|
+
}
|
|
1859
|
+
if (command === "get") {
|
|
1860
|
+
if (options.packId || options.category || options.locale || options.limit !== void 0 || options.animated !== void 0 || options.variant) {
|
|
1861
|
+
throw new UsageError("stickers get accepts only --collection and --json");
|
|
1862
|
+
}
|
|
1863
|
+
if (options.positional.length !== 1) throw new UsageError("Usage: mediaruntime stickers get <sticker_id> --collection <collection_id>");
|
|
1864
|
+
const sticker = await client.retrieve(options.positional[0] ?? "");
|
|
1865
|
+
const human = `${sticker.label} (${sticker.stickerId})
|
|
1866
|
+
Pack: ${sticker.packId}
|
|
1867
|
+
Category: ${sticker.category ?? "-"}
|
|
1868
|
+
Variants: ${sticker.variants.map((item) => item.name).join(", ")}
|
|
1869
|
+
`;
|
|
1870
|
+
output2(sticker, options.json, dependencies.writeStdout, human);
|
|
1871
|
+
return 0;
|
|
1872
|
+
}
|
|
1873
|
+
if (command === "resolve") {
|
|
1874
|
+
if (options.packId || options.category || options.locale || options.limit !== void 0 || options.animated !== void 0) {
|
|
1875
|
+
throw new UsageError("stickers resolve accepts only --variant, --collection, and --json");
|
|
1876
|
+
}
|
|
1877
|
+
if (options.positional.length !== 1 || !options.variant) {
|
|
1878
|
+
throw new UsageError("Usage: mediaruntime stickers resolve <sticker_id> --variant <name> --collection <collection_id>");
|
|
1879
|
+
}
|
|
1880
|
+
if (!STICKER_VARIANTS.has(options.variant)) {
|
|
1881
|
+
throw new UsageError(`Unsupported sticker variant: ${options.variant}`);
|
|
1882
|
+
}
|
|
1883
|
+
const resolution = await client.resolve(options.positional[0] ?? "", options.variant);
|
|
1884
|
+
const human = `Sticker: ${String(resolution.stickerId ?? "-")}
|
|
1885
|
+
Variant: ${String(resolution.variant ?? options.variant)}
|
|
1886
|
+
Bytes: ${String(resolution.bytes ?? "-")}
|
|
1887
|
+
Expires: ${String(resolution.expiresAt ?? "-")}
|
|
1888
|
+
URL: ${String(resolution.url ?? "-")}
|
|
1889
|
+
`;
|
|
1890
|
+
output2(resolution, options.json, dependencies.writeStdout, human);
|
|
1891
|
+
return 0;
|
|
1892
|
+
}
|
|
1893
|
+
throw new UsageError("Usage: mediaruntime stickers <packs|search|typeahead|get|resolve>");
|
|
1894
|
+
}
|
|
1895
|
+
async function tokenCommand(args, dependencies) {
|
|
1896
|
+
if (args[0] !== "create") throw new UsageError("Usage: mediaruntime stickers token create --collection <collection_id>");
|
|
1897
|
+
let selected;
|
|
1898
|
+
let expiresInSeconds;
|
|
1899
|
+
const scopes = [];
|
|
1900
|
+
let json = false;
|
|
1901
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
1902
|
+
const argument = args[index];
|
|
1903
|
+
if (argument === "--collection") {
|
|
1904
|
+
selected = optionValue4(args, index, argument);
|
|
1905
|
+
index += 1;
|
|
1906
|
+
} else if (argument === "--expires-in") {
|
|
1907
|
+
expiresInSeconds = boundedInteger(optionValue4(args, index, argument), argument, 60, 3600);
|
|
1908
|
+
index += 1;
|
|
1909
|
+
} else if (argument === "--scope") {
|
|
1910
|
+
const scope = optionValue4(args, index, argument);
|
|
1911
|
+
if (!TOKEN_SCOPES.has(scope)) throw new UsageError(`Unsupported Sticker Runtime scope: ${scope}`);
|
|
1912
|
+
scopes.push(scope);
|
|
1913
|
+
index += 1;
|
|
1914
|
+
} else if (argument === "--json") json = true;
|
|
1915
|
+
else throw new UsageError(`Unknown stickers token create option: ${argument}`);
|
|
1916
|
+
}
|
|
1917
|
+
const result = await hostedClient(dependencies).createClientToken({
|
|
1918
|
+
collectionId: collectionId(selected),
|
|
1919
|
+
...expiresInSeconds === void 0 ? {} : { expiresInSeconds },
|
|
1920
|
+
...scopes.length === 0 ? {} : { scopes: [...new Set(scopes)] }
|
|
1921
|
+
});
|
|
1922
|
+
const human = `Access token: ${String(result.accessToken ?? "")}
|
|
1923
|
+
Expires: ${String(result.expiresAt ?? "-")}
|
|
1924
|
+
Collection: ${String(result.collectionId ?? "-")}
|
|
1925
|
+
Scopes: ${Array.isArray(result.scopes) ? result.scopes.join(", ") : "-"}
|
|
1926
|
+
`;
|
|
1927
|
+
output2(result, json, dependencies.writeStdout, human);
|
|
1928
|
+
return 0;
|
|
1929
|
+
}
|
|
1930
|
+
function isScopedStickerRuntimeCommand(args) {
|
|
1931
|
+
return ["packs", "search", "typeahead", "get", "resolve"].includes(args[0] ?? "");
|
|
1932
|
+
}
|
|
1933
|
+
function stickerCollectionOption(args) {
|
|
1934
|
+
const index = args.indexOf("--collection");
|
|
1935
|
+
return collectionId(index < 0 ? void 0 : args[index + 1]);
|
|
1936
|
+
}
|
|
1937
|
+
async function runStickersCommand(args, dependencies) {
|
|
1938
|
+
const command = args[0];
|
|
1939
|
+
if (!command || command === "--help" || command === "-h" || args.includes("--help") || args.includes("-h")) {
|
|
1940
|
+
dependencies.writeStdout(STICKERS_HELP);
|
|
1941
|
+
return 0;
|
|
1942
|
+
}
|
|
1943
|
+
if (command === "collections") return collectionsCommand(args.slice(1), dependencies);
|
|
1944
|
+
if (command === "token") return tokenCommand(args.slice(1), dependencies);
|
|
1945
|
+
if (command === "usage") {
|
|
1946
|
+
const rest = args.slice(1);
|
|
1947
|
+
if (rest.some((argument) => argument !== "--json")) throw new UsageError("Usage: mediaruntime stickers usage [--json]");
|
|
1948
|
+
const result = await hostedClient(dependencies).usage();
|
|
1949
|
+
const human = `Month: ${String(result.month ?? "-")}
|
|
1950
|
+
Status: ${String(result.status ?? "-")}
|
|
1951
|
+
Operations: ${String(result.operations ?? 0)} / ${String(result.includedOperations ?? 0)}
|
|
1952
|
+
Authorized delivery: ${String(result.authorizedDeliveryBytes ?? 0)} / ${String(result.includedDeliveryBytes ?? 0)} bytes
|
|
1953
|
+
Overage: ${String(result.overageChargedCents ?? 0)} ${String(result.currency ?? "USD")} cents
|
|
1954
|
+
`;
|
|
1955
|
+
output2(result, rest.includes("--json"), dependencies.writeStdout, human);
|
|
1956
|
+
return 0;
|
|
1957
|
+
}
|
|
1958
|
+
if (isScopedStickerRuntimeCommand(args)) return runtimeCommand(command ?? "", args.slice(1), dependencies);
|
|
1959
|
+
throw new UsageError("Usage: mediaruntime stickers <collections|packs|search|typeahead|get|resolve|usage|token>");
|
|
1960
|
+
}
|
|
1961
|
+
|
|
980
1962
|
// src/commands/trigger.ts
|
|
981
1963
|
import { randomBytes as randomBytes2, randomUUID } from "crypto";
|
|
982
1964
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -1050,6 +2032,7 @@ function createSyntheticTerminalEvent(type, options) {
|
|
|
1050
2032
|
size_bytes: 0,
|
|
1051
2033
|
sha256: "0".repeat(64),
|
|
1052
2034
|
download: {
|
|
2035
|
+
// This marker URL is descriptive only; trigger never serves a synthetic ZIP.
|
|
1053
2036
|
url: `http://127.0.0.1/__mediaruntime_synthetic__/${encodeURIComponent(options.jobId)}.zip`,
|
|
1054
2037
|
expiresAt
|
|
1055
2038
|
}
|
|
@@ -1211,7 +2194,7 @@ async function runTriggerCommand(args, dependencies = {}) {
|
|
|
1211
2194
|
writeStdout: dependencies.writeStdout ?? ((text) => process.stdout.write(text))
|
|
1212
2195
|
};
|
|
1213
2196
|
const secret = await resolveSecret(parsed, deps);
|
|
1214
|
-
const
|
|
2197
|
+
const timestamp2 = Math.floor(deps.now() / 1e3);
|
|
1215
2198
|
const unique = deps.randomUUID();
|
|
1216
2199
|
const eventId = `evt_cli_${unique}`;
|
|
1217
2200
|
const jobId = parsed.jobId ?? `job_cli_${unique}`;
|
|
@@ -1220,9 +2203,9 @@ async function runTriggerCommand(args, dependencies = {}) {
|
|
|
1220
2203
|
eventId,
|
|
1221
2204
|
jobId,
|
|
1222
2205
|
accountId,
|
|
1223
|
-
timestamp
|
|
2206
|
+
timestamp: timestamp2
|
|
1224
2207
|
});
|
|
1225
|
-
const signed = signSyntheticWebhook(payload, { eventId, timestamp, secret });
|
|
2208
|
+
const signed = signSyntheticWebhook(payload, { eventId, timestamp: timestamp2, secret });
|
|
1226
2209
|
const response = await deps.fetch(parsed.destination, {
|
|
1227
2210
|
method: "POST",
|
|
1228
2211
|
headers: signed.headers,
|
|
@@ -1296,16 +2279,55 @@ function createActivityIndicator(write, enabled) {
|
|
|
1296
2279
|
}
|
|
1297
2280
|
|
|
1298
2281
|
// src/cli.ts
|
|
1299
|
-
var VERSION = "1.
|
|
2282
|
+
var VERSION = "1.4.0";
|
|
1300
2283
|
var HELP = `MediaRuntime CLI
|
|
1301
2284
|
|
|
1302
2285
|
Usage:
|
|
1303
2286
|
mediaruntime run <source> (--recipe <name[@version]> | --output <alias> | --preset <name>) [...] [--wait]
|
|
2287
|
+
Animation presets: [--animation-width N] [--animation-fps N] [--animation-start SEC]
|
|
2288
|
+
[--animation-duration SEC] [--animation-loop N] [--animation-quality N]
|
|
2289
|
+
Placeholder preset: [--placeholder-max-dimension N] [--placeholder-time SEC]
|
|
2290
|
+
[--lqip-quality N] [--lqip-max-bytes N]
|
|
2291
|
+
Contact-sheet preset: [--contact-columns N] [--contact-rows N]
|
|
2292
|
+
[--contact-tile-width N] [--contact-tile-height N] [--contact-interval SEC]
|
|
2293
|
+
[--contact-start SEC] [--contact-duration SEC] [--contact-max-sheets N]
|
|
2294
|
+
[--contact-format jpg|png|webp] [--contact-quality N]
|
|
2295
|
+
[--image-width PX] [--image-height PX] [--image-mode fit|fill|cover|contain]
|
|
2296
|
+
[--image-format jpg|png|webp|avif] [--image-quality N]
|
|
2297
|
+
[--image-max-bytes N] [--image-min-quality N]
|
|
2298
|
+
Clip render: --preset video_clip_v1 --clip-start SEC --clip-duration SEC
|
|
2299
|
+
[--clip-layout original|vertical_blur] [--clip-transcript <plan.json|captions.srt|captions.vtt>] [--clip-captions]
|
|
2300
|
+
Clip suggestions: --preset clip_candidates_v1 [--clip-min-duration SEC]
|
|
2301
|
+
[--clip-max-duration SEC] [--clip-count 1..20] [--clip-keyword TEXT]
|
|
2302
|
+
[--clip-transcript <plan.json|captions.srt|captions.vtt>]
|
|
2303
|
+
Audiogram preset: --audiogram-artwork <source> [--audiogram-captions <source>]
|
|
2304
|
+
[--audiogram-layout square|portrait|landscape]
|
|
2305
|
+
[--audiogram-fit contain|cover|blurred_background]
|
|
2306
|
+
[--audiogram-background #RRGGBB] [--audiogram-waveform #RRGGBB]
|
|
2307
|
+
[--audiogram-waveform-gain 0.5..4]
|
|
2308
|
+
[--audiogram-caption-position top|bottom] [--audiogram-caption-scale 0.75..1.5]
|
|
2309
|
+
[--audiogram-normalize] [--audiogram-loudness-target -24..-12]
|
|
2310
|
+
[--audiogram-start SEC] [--audiogram-duration SEC] [--audiogram-fps 15..30]
|
|
2311
|
+
Privacy redaction Preview (still-image input + image output only):
|
|
2312
|
+
--privacy-detector face|license_plate|text
|
|
2313
|
+
[--privacy-style blur|pixelate|solid] [--privacy-failure-mode fail_closed|report_only]
|
|
2314
|
+
[--privacy-min-confidence 0.30..0.99] [--privacy-sample-interval 0.1..30]
|
|
2315
|
+
[--privacy-max-frames 1..18000] [--privacy-padding 0..0.5]
|
|
2316
|
+
[--privacy-solid-color #RRGGBB] [--privacy-pixel-block-size 4..128]
|
|
2317
|
+
[--privacy-strength standard|strong] [--privacy-debug-observations]
|
|
1304
2318
|
mediaruntime capabilities [--json]
|
|
1305
2319
|
mediaruntime presets list [--json]
|
|
1306
2320
|
mediaruntime jobs list [--status <status>] [--limit <n>] [--cursor <cursor>]
|
|
1307
2321
|
mediaruntime jobs get <job_id> [--download <bundle.zip>] [--force]
|
|
1308
2322
|
mediaruntime recipes <list|get|create|version|archive>
|
|
2323
|
+
mediaruntime stickers collections <list|create|get|update|archive|packs>
|
|
2324
|
+
mediaruntime stickers packs list --collection <collection_id>
|
|
2325
|
+
mediaruntime stickers search <query> --collection <collection_id>
|
|
2326
|
+
mediaruntime stickers typeahead <prefix> --collection <collection_id>
|
|
2327
|
+
mediaruntime stickers get <sticker_id> --collection <collection_id>
|
|
2328
|
+
mediaruntime stickers resolve <sticker_id> --variant <name> --collection <collection_id>
|
|
2329
|
+
mediaruntime stickers usage
|
|
2330
|
+
mediaruntime stickers token create --collection <collection_id>
|
|
1309
2331
|
mediaruntime trigger <job.completed|job.failed|job.rejected> --to <local-url>
|
|
1310
2332
|
mediaruntime login [--no-browser]
|
|
1311
2333
|
mediaruntime auth status
|
|
@@ -1320,6 +2342,9 @@ Run/jobs options:
|
|
|
1320
2342
|
--json Machine-readable, URL-redacted output
|
|
1321
2343
|
|
|
1322
2344
|
Authentication uses a secure login or MEDIARUNTIME_API_KEY.
|
|
2345
|
+
Sticker commands use that API key by default. MEDIARUNTIME_STICKER_CLIENT_TOKEN may
|
|
2346
|
+
replace it only for collection-bound packs/search/typeahead/get/resolve reads; it cannot
|
|
2347
|
+
manage collections, inspect workspace usage, or mint another token.
|
|
1323
2348
|
`;
|
|
1324
2349
|
function isFileExistsError(error) {
|
|
1325
2350
|
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
@@ -1499,6 +2524,28 @@ async function executeCli(argv, dependencies = {}) {
|
|
|
1499
2524
|
}
|
|
1500
2525
|
return await runPresetsCommand(global.args.slice(1), publicDependencies);
|
|
1501
2526
|
}
|
|
2527
|
+
const stickerArguments = global.args.slice(1);
|
|
2528
|
+
const stickerClientToken = process.env.MEDIARUNTIME_STICKER_CLIENT_TOKEN?.trim();
|
|
2529
|
+
if (command === "stickers" && (!stickerArguments[0] || stickerArguments.includes("--help") || stickerArguments.includes("-h"))) {
|
|
2530
|
+
return await runStickersCommand(stickerArguments, { writeStdout });
|
|
2531
|
+
}
|
|
2532
|
+
if (command === "stickers" && stickerClientToken && isScopedStickerRuntimeCommand(stickerArguments)) {
|
|
2533
|
+
const selectedCollectionId = stickerCollectionOption(stickerArguments);
|
|
2534
|
+
const options = {
|
|
2535
|
+
accessToken: stickerClientToken,
|
|
2536
|
+
collectionId: selectedCollectionId,
|
|
2537
|
+
...baseUrl === void 0 ? {} : { baseUrl }
|
|
2538
|
+
};
|
|
2539
|
+
const constructor = MediaRuntimeSdk.StickerRuntime;
|
|
2540
|
+
const runtime = dependencies.createStickerRuntime?.(options) ?? (constructor ? new constructor(options) : void 0);
|
|
2541
|
+
if (!runtime) {
|
|
2542
|
+
throw new UsageError("The installed @mediaruntime/node SDK does not support scoped Sticker Runtime tokens");
|
|
2543
|
+
}
|
|
2544
|
+
return await runStickersCommand(stickerArguments, {
|
|
2545
|
+
runtime: () => runtime,
|
|
2546
|
+
writeStdout
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
1502
2549
|
let apiKey = process.env.MEDIARUNTIME_API_KEY?.trim();
|
|
1503
2550
|
if (!apiKey && (!dependencies.createClient || dependencies.credentialStore)) {
|
|
1504
2551
|
apiKey = (await resolveCredential(authBaseUrl, authDependencies))?.apiKey;
|
|
@@ -1517,6 +2564,7 @@ async function executeCli(argv, dependencies = {}) {
|
|
|
1517
2564
|
const commandDependencies = {
|
|
1518
2565
|
jobs: client.jobs,
|
|
1519
2566
|
capabilities: client.capabilities,
|
|
2567
|
+
...client.uploads ? { uploads: client.uploads } : {},
|
|
1520
2568
|
writeStdout,
|
|
1521
2569
|
downloadBundle,
|
|
1522
2570
|
activity: createActivityIndicator(writeStderr, isStderrTTY && !json)
|
|
@@ -1532,6 +2580,14 @@ async function executeCli(argv, dependencies = {}) {
|
|
|
1532
2580
|
writeStdout
|
|
1533
2581
|
});
|
|
1534
2582
|
}
|
|
2583
|
+
if (command === "stickers") {
|
|
2584
|
+
const stickers = client.stickers;
|
|
2585
|
+
if (!stickers) throw new UsageError("This MediaRuntime client does not support Hosted Sticker Runtime");
|
|
2586
|
+
return await runStickersCommand(stickerArguments, {
|
|
2587
|
+
hosted: stickers,
|
|
2588
|
+
writeStdout
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
1535
2591
|
throw new UsageError(`Unknown command: ${command}`);
|
|
1536
2592
|
} catch (error) {
|
|
1537
2593
|
const exitCode = exitCodeFor(error);
|