@vosjs/cli 0.35.0 → 0.37.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/README.md +9 -1
- package/dist/{chunk-VQXOM2NU.js → chunk-DD6OXVFJ.js} +790 -486
- package/dist/chunk-DD6OXVFJ.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/{run-4DNLUCJH.js → run-6T4NK5P2.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-VQXOM2NU.js.map +0 -1
- /package/dist/{run-4DNLUCJH.js.map → run-6T4NK5P2.js.map} +0 -0
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-NHKHTIDR.js";
|
|
7
7
|
|
|
8
8
|
// src/plugin/run.ts
|
|
9
|
-
import { mkdir as
|
|
9
|
+
import { mkdir as mkdir10, readFile as readFile15, rm as rm5 } from "fs/promises";
|
|
10
10
|
import { existsSync as existsSync15 } from "fs";
|
|
11
11
|
import { join as join18, resolve as resolve9 } from "path";
|
|
12
12
|
import {
|
|
@@ -1681,6 +1681,378 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
|
|
|
1681
1681
|
}
|
|
1682
1682
|
}
|
|
1683
1683
|
|
|
1684
|
+
// src/plugin/container.ts
|
|
1685
|
+
var OCTET_STREAM = "application/octet-stream";
|
|
1686
|
+
var TYPES_BY_EXTENSION = {
|
|
1687
|
+
svg: "image/svg+xml",
|
|
1688
|
+
png: "image/png",
|
|
1689
|
+
jpg: "image/jpeg",
|
|
1690
|
+
jpeg: "image/jpeg",
|
|
1691
|
+
webp: "image/webp",
|
|
1692
|
+
gif: "image/gif",
|
|
1693
|
+
avif: "image/avif",
|
|
1694
|
+
webm: "video/webm",
|
|
1695
|
+
mp4: "video/mp4",
|
|
1696
|
+
mov: "video/quicktime",
|
|
1697
|
+
mp3: "audio/mpeg",
|
|
1698
|
+
wav: "audio/wav",
|
|
1699
|
+
ogg: "audio/ogg",
|
|
1700
|
+
m4a: "audio/mp4"
|
|
1701
|
+
};
|
|
1702
|
+
var EXTENSION_BY_TYPE = {
|
|
1703
|
+
"image/jpeg": ".jpg",
|
|
1704
|
+
"video/mp4": ".mp4",
|
|
1705
|
+
"audio/mp4": ".m4a"
|
|
1706
|
+
};
|
|
1707
|
+
var TAKE_MEDIA_EXTENSIONS = [
|
|
1708
|
+
".webm",
|
|
1709
|
+
".mp4",
|
|
1710
|
+
".mov",
|
|
1711
|
+
".m4a",
|
|
1712
|
+
".mp3",
|
|
1713
|
+
".ogg",
|
|
1714
|
+
".wav"
|
|
1715
|
+
];
|
|
1716
|
+
var MEDIA_HEAD_BYTES = 16;
|
|
1717
|
+
var ISO_BRANDS = {
|
|
1718
|
+
"qt ": "video/quicktime",
|
|
1719
|
+
"M4A ": "audio/mp4",
|
|
1720
|
+
"M4B ": "audio/mp4",
|
|
1721
|
+
avif: "image/avif",
|
|
1722
|
+
avis: "image/avif"
|
|
1723
|
+
};
|
|
1724
|
+
var RIFF_FORMS = {
|
|
1725
|
+
WEBP: "image/webp",
|
|
1726
|
+
WAVE: "audio/wav",
|
|
1727
|
+
"AVI ": "video/x-msvideo"
|
|
1728
|
+
};
|
|
1729
|
+
function sniffMediaType(head) {
|
|
1730
|
+
const ascii = (at2, len) => head.length >= at2 + len ? String.fromCharCode(...head.subarray(at2, at2 + len)) : "";
|
|
1731
|
+
const startsWith = (...bytes) => bytes.every((b, i) => head[i] === b);
|
|
1732
|
+
if (startsWith(26, 69, 223, 163)) return "video/webm";
|
|
1733
|
+
if (ascii(4, 4) === "ftyp") return ISO_BRANDS[ascii(8, 4)] ?? "video/mp4";
|
|
1734
|
+
if (ascii(0, 4) === "RIFF") return RIFF_FORMS[ascii(8, 4)] ?? null;
|
|
1735
|
+
if (startsWith(137, 80, 78, 71, 13, 10, 26, 10))
|
|
1736
|
+
return "image/png";
|
|
1737
|
+
if (startsWith(255, 216, 255)) return "image/jpeg";
|
|
1738
|
+
if (ascii(0, 4) === "GIF8") return "image/gif";
|
|
1739
|
+
if (ascii(0, 4) === "OggS") return "audio/ogg";
|
|
1740
|
+
if (ascii(0, 3) === "ID3") return "audio/mpeg";
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
function bareType(value) {
|
|
1744
|
+
const type = (value ?? "").split(";")[0].trim().toLowerCase();
|
|
1745
|
+
return !type || type === OCTET_STREAM || type === "binary/octet-stream" ? "" : type;
|
|
1746
|
+
}
|
|
1747
|
+
function mediaContentType(file) {
|
|
1748
|
+
const ext = (/\.([a-z0-9]+)$/i.exec(file)?.[1] ?? "").toLowerCase();
|
|
1749
|
+
return TYPES_BY_EXTENSION[ext] ?? OCTET_STREAM;
|
|
1750
|
+
}
|
|
1751
|
+
function extensionFor(contentType) {
|
|
1752
|
+
const type = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
1753
|
+
if (EXTENSION_BY_TYPE[type]) return EXTENSION_BY_TYPE[type];
|
|
1754
|
+
const ext = Object.keys(TYPES_BY_EXTENSION).find(
|
|
1755
|
+
(e) => TYPES_BY_EXTENSION[e] === type
|
|
1756
|
+
);
|
|
1757
|
+
return ext ? `.${ext}` : "";
|
|
1758
|
+
}
|
|
1759
|
+
function resolveMediaType(claims) {
|
|
1760
|
+
const sniffed = claims.head?.length ? sniffMediaType(claims.head) : null;
|
|
1761
|
+
if (sniffed) return { type: sniffed, from: "bytes" };
|
|
1762
|
+
const declared = bareType(claims.declared);
|
|
1763
|
+
if (declared) return { type: declared, from: "declared" };
|
|
1764
|
+
const named = mediaContentType(claims.filename ?? "");
|
|
1765
|
+
if (named !== OCTET_STREAM) return { type: named, from: "filename" };
|
|
1766
|
+
return { type: OCTET_STREAM, from: "unknown" };
|
|
1767
|
+
}
|
|
1768
|
+
function nameForType(filename, type) {
|
|
1769
|
+
const ext = extensionFor(type);
|
|
1770
|
+
if (!ext) return filename;
|
|
1771
|
+
const current = /\.[a-z0-9]+$/i.exec(filename)?.[0] ?? "";
|
|
1772
|
+
if (current && mediaContentType(`x${current}`) === type) return filename;
|
|
1773
|
+
return `${filename.slice(0, filename.length - current.length)}${ext}`;
|
|
1774
|
+
}
|
|
1775
|
+
function containerMismatch(filename, head) {
|
|
1776
|
+
const actual = sniffMediaType(head);
|
|
1777
|
+
if (!actual) return null;
|
|
1778
|
+
const claimed = mediaContentType(filename);
|
|
1779
|
+
if (claimed === actual) return null;
|
|
1780
|
+
return `${filename} is ${actual}, not the ${claimed} its name claims \u2014 rename it to ${nameForType(filename, actual)}`;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// src/plugin/media.ts
|
|
1784
|
+
import { isKeyedOverlay } from "@vosjs/studio-core";
|
|
1785
|
+
import { createWriteStream, existsSync as existsSync2 } from "fs";
|
|
1786
|
+
import {
|
|
1787
|
+
mkdir as mkdir2,
|
|
1788
|
+
open,
|
|
1789
|
+
readdir,
|
|
1790
|
+
rename as rename2,
|
|
1791
|
+
rm as rm2,
|
|
1792
|
+
stat,
|
|
1793
|
+
writeFile as writeFile2
|
|
1794
|
+
} from "fs/promises";
|
|
1795
|
+
import { basename as basename2, dirname, join as join2 } from "path";
|
|
1796
|
+
import { Readable } from "stream";
|
|
1797
|
+
import { pipeline } from "stream/promises";
|
|
1798
|
+
|
|
1799
|
+
// src/plugin/take.ts
|
|
1800
|
+
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
1801
|
+
import { existsSync } from "fs";
|
|
1802
|
+
import { basename, join } from "path";
|
|
1803
|
+
var RECORDING_NAME = "recording.webm";
|
|
1804
|
+
function takeMediaFiles(dir, stem) {
|
|
1805
|
+
return TAKE_MEDIA_EXTENSIONS.map((ext) => join(dir, `${stem}${ext}`)).filter(
|
|
1806
|
+
(file) => existsSync(file)
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
function takeMediaFile(dir, stem, fallback) {
|
|
1810
|
+
return takeMediaFiles(dir, stem)[0] ?? join(dir, fallback);
|
|
1811
|
+
}
|
|
1812
|
+
var PREV_DOC_NAME = "doc.prev.json";
|
|
1813
|
+
function takePaths(dir) {
|
|
1814
|
+
const recording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
1815
|
+
return {
|
|
1816
|
+
dir,
|
|
1817
|
+
recording,
|
|
1818
|
+
recordingName: basename(recording),
|
|
1819
|
+
framesDir: join(dir, "frames"),
|
|
1820
|
+
framesIndex: join(dir, "frames.json"),
|
|
1821
|
+
cursor: join(dir, "cursor.json"),
|
|
1822
|
+
meta: join(dir, "meta.json"),
|
|
1823
|
+
actions: join(dir, "actions.json"),
|
|
1824
|
+
doc: join(dir, "doc.json")
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
async function ensureTakeDir(dir) {
|
|
1828
|
+
const p = takePaths(dir);
|
|
1829
|
+
await mkdir(p.framesDir, { recursive: true });
|
|
1830
|
+
return p;
|
|
1831
|
+
}
|
|
1832
|
+
async function prepareReRecord(dir) {
|
|
1833
|
+
const p = takePaths(dir);
|
|
1834
|
+
const kept = [];
|
|
1835
|
+
let prevDoc = false;
|
|
1836
|
+
if (existsSync(p.doc)) {
|
|
1837
|
+
await rename(p.doc, join(dir, PREV_DOC_NAME));
|
|
1838
|
+
prevDoc = true;
|
|
1839
|
+
kept.push(PREV_DOC_NAME);
|
|
1840
|
+
}
|
|
1841
|
+
if (existsSync(p.actions)) kept.push("actions.json");
|
|
1842
|
+
if (existsSync(join(dir, "vos.json"))) kept.push("vos.json");
|
|
1843
|
+
for (const stale of [
|
|
1844
|
+
// Every container, not just the one `record` writes: a pulled take's
|
|
1845
|
+
// footage may be mp4, and a survivor would outrank the new recording.
|
|
1846
|
+
...takeMediaFiles(dir, "recording"),
|
|
1847
|
+
...takeMediaFiles(dir, "mic"),
|
|
1848
|
+
...takeMediaFiles(dir, "cam"),
|
|
1849
|
+
p.cursor,
|
|
1850
|
+
p.meta,
|
|
1851
|
+
p.framesIndex,
|
|
1852
|
+
p.framesDir,
|
|
1853
|
+
join(dir, "stills"),
|
|
1854
|
+
join(dir, "digest")
|
|
1855
|
+
]) {
|
|
1856
|
+
await rm(stale, { recursive: true, force: true });
|
|
1857
|
+
}
|
|
1858
|
+
return { prevDoc, kept };
|
|
1859
|
+
}
|
|
1860
|
+
async function readJson(file) {
|
|
1861
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
1862
|
+
}
|
|
1863
|
+
async function writeJson(file, value, pretty = false) {
|
|
1864
|
+
await writeFile(
|
|
1865
|
+
file,
|
|
1866
|
+
pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
async function loadTake(dir) {
|
|
1870
|
+
const paths = takePaths(dir);
|
|
1871
|
+
if (!existsSync(paths.meta)) {
|
|
1872
|
+
throw new Error(
|
|
1873
|
+
`${dir} is not a take directory (missing meta.json) \u2014 run record first`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
const meta = await readJson(paths.meta);
|
|
1877
|
+
const cursor = existsSync(paths.cursor) ? await readJson(paths.cursor) : [];
|
|
1878
|
+
const actions = existsSync(paths.actions) ? await readJson(paths.actions) : null;
|
|
1879
|
+
const doc = existsSync(paths.doc) ? await readJson(paths.doc) : null;
|
|
1880
|
+
return { paths, cursor, meta, actions, doc };
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/plugin/media.ts
|
|
1884
|
+
var SOURCE_MEDIA = [
|
|
1885
|
+
{ key: "videoKey", stem: "recording", fallbackExt: ".webm" },
|
|
1886
|
+
{ key: "micKey", stem: "mic", fallbackExt: ".webm" },
|
|
1887
|
+
{ key: "camKey", stem: "cam", fallbackExt: ".webm" }
|
|
1888
|
+
];
|
|
1889
|
+
function isTakeRelativeKey(key) {
|
|
1890
|
+
return !!key && !/^(https?:|blob:|data:|media:|\/\/)/.test(key) && !key.startsWith("/api/");
|
|
1891
|
+
}
|
|
1892
|
+
var takeRelativeFile = (key) => key.replace(/^\/+/, "");
|
|
1893
|
+
function docMediaRefs(doc, keep = isTakeRelativeKey) {
|
|
1894
|
+
const out = [];
|
|
1895
|
+
for (const m of doc.media ?? []) {
|
|
1896
|
+
if (keep(m.videoKey))
|
|
1897
|
+
out.push({
|
|
1898
|
+
where: `media ${m.id}`,
|
|
1899
|
+
key: m.videoKey,
|
|
1900
|
+
set: (next) => {
|
|
1901
|
+
m.videoKey = next;
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
const mic = m.micKey;
|
|
1905
|
+
if (mic && keep(mic))
|
|
1906
|
+
out.push({
|
|
1907
|
+
where: `media ${m.id} mic`,
|
|
1908
|
+
key: mic,
|
|
1909
|
+
set: (next) => {
|
|
1910
|
+
m.micKey = next;
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1913
|
+
const cam = m.camKey;
|
|
1914
|
+
if (cam && keep(cam))
|
|
1915
|
+
out.push({
|
|
1916
|
+
where: `media ${m.id} cam`,
|
|
1917
|
+
key: cam,
|
|
1918
|
+
set: (next) => {
|
|
1919
|
+
m.camKey = next;
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
for (const clip of doc.overlays ?? []) {
|
|
1924
|
+
if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
|
|
1925
|
+
out.push({
|
|
1926
|
+
where: `overlay ${clip.id}`,
|
|
1927
|
+
key: clip.key,
|
|
1928
|
+
set: (next) => {
|
|
1929
|
+
clip.key = next;
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
const mark = doc.endCard?.mark;
|
|
1934
|
+
if (mark && keep(mark.key))
|
|
1935
|
+
out.push({
|
|
1936
|
+
where: "end card mark",
|
|
1937
|
+
key: mark.key,
|
|
1938
|
+
set: (next) => {
|
|
1939
|
+
mark.key = next;
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
const bg = doc.frame.backgroundMedia;
|
|
1943
|
+
if (bg && keep(bg.key))
|
|
1944
|
+
out.push({
|
|
1945
|
+
where: "background",
|
|
1946
|
+
key: bg.key,
|
|
1947
|
+
set: (next) => {
|
|
1948
|
+
bg.key = next;
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
return out;
|
|
1952
|
+
}
|
|
1953
|
+
function assetIdOf(url) {
|
|
1954
|
+
if (!url) return null;
|
|
1955
|
+
const m = /\/api\/assets\/([A-Za-z0-9_-]+)\/file/.exec(url);
|
|
1956
|
+
return m ? m[1] : null;
|
|
1957
|
+
}
|
|
1958
|
+
async function readMediaHead(file) {
|
|
1959
|
+
const handle = await open(file, "r");
|
|
1960
|
+
try {
|
|
1961
|
+
const buf = Buffer.alloc(MEDIA_HEAD_BYTES);
|
|
1962
|
+
const { bytesRead } = await handle.read(buf, 0, MEDIA_HEAD_BYTES, 0);
|
|
1963
|
+
return new Uint8Array(buf.subarray(0, bytesRead));
|
|
1964
|
+
} finally {
|
|
1965
|
+
await handle.close();
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
async function downloadMedia(ctx, url, target) {
|
|
1969
|
+
const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
|
|
1970
|
+
const res = await fetch(abs, {
|
|
1971
|
+
headers: ctx.key ? { authorization: `Bearer ${ctx.key}` } : {}
|
|
1972
|
+
});
|
|
1973
|
+
if (!res.ok || !res.body) {
|
|
1974
|
+
throw new Error(
|
|
1975
|
+
`download of ${target.describe} failed (${res.status})${res.status === 404 && target.notFoundHint ? ` \u2014 ${target.notFoundHint}` : ""}`
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
const into = target.subdir ? join2(target.dir, target.subdir) : target.dir;
|
|
1979
|
+
const partial = join2(into, `${target.stem}.part`);
|
|
1980
|
+
await mkdir2(dirname(partial), { recursive: true });
|
|
1981
|
+
try {
|
|
1982
|
+
await pipeline(
|
|
1983
|
+
Readable.fromWeb(res.body),
|
|
1984
|
+
createWriteStream(partial)
|
|
1985
|
+
);
|
|
1986
|
+
const { type } = resolveMediaType({
|
|
1987
|
+
head: await readMediaHead(partial),
|
|
1988
|
+
declared: res.headers.get("content-type"),
|
|
1989
|
+
filename: `${target.stem}${target.fallbackExt}`
|
|
1990
|
+
});
|
|
1991
|
+
const name = `${target.stem}${extensionFor(type) || target.fallbackExt}`;
|
|
1992
|
+
await rename2(partial, join2(into, name));
|
|
1993
|
+
const file = target.subdir ? `${target.subdir}/${name}` : name;
|
|
1994
|
+
return { file, bytes: (await stat(join2(target.dir, file))).size };
|
|
1995
|
+
} finally {
|
|
1996
|
+
await rm2(partial, { force: true });
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
async function pullMedia(ctx, dir, doc, log) {
|
|
2000
|
+
const result = { downloaded: [], kept: [] };
|
|
2001
|
+
for (const { key, stem, fallbackExt } of SOURCE_MEDIA) {
|
|
2002
|
+
const url = doc.source[key];
|
|
2003
|
+
const assetId = assetIdOf(url);
|
|
2004
|
+
if (!assetId) continue;
|
|
2005
|
+
const present = takeMediaFiles(dir, stem)[0];
|
|
2006
|
+
if (present) {
|
|
2007
|
+
result.kept.push(basename2(present));
|
|
2008
|
+
doc.source[key] = basename2(present);
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
const got = await downloadMedia(ctx, url, {
|
|
2012
|
+
dir,
|
|
2013
|
+
stem,
|
|
2014
|
+
fallbackExt,
|
|
2015
|
+
describe: stem,
|
|
2016
|
+
notFoundHint: "a private recording needs a content key of its owner"
|
|
2017
|
+
});
|
|
2018
|
+
result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
|
|
2019
|
+
log(` ${got.file} \u2190 asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`);
|
|
2020
|
+
doc.source[key] = got.file;
|
|
2021
|
+
}
|
|
2022
|
+
for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {
|
|
2023
|
+
const assetId = assetIdOf(ref.key);
|
|
2024
|
+
const existing = existsSync2(join2(dir, "media")) ? (await readdir(join2(dir, "media"))).find(
|
|
2025
|
+
(f) => f === assetId || f.startsWith(`${assetId}.`)
|
|
2026
|
+
) : void 0;
|
|
2027
|
+
if (existing) {
|
|
2028
|
+
const file = `media/${existing}`;
|
|
2029
|
+
result.kept.push(file);
|
|
2030
|
+
ref.set(file);
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
const got = await downloadMedia(ctx, ref.key, {
|
|
2034
|
+
dir,
|
|
2035
|
+
subdir: "media",
|
|
2036
|
+
stem: assetId,
|
|
2037
|
+
// A type with no extension here (an svg served as such) keeps the bare
|
|
2038
|
+
// asset id, exactly as before.
|
|
2039
|
+
fallbackExt: "",
|
|
2040
|
+
describe: `${ref.where} (${ref.key})`
|
|
2041
|
+
});
|
|
2042
|
+
result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
|
|
2043
|
+
log(
|
|
2044
|
+
` ${got.file} \u2190 ${ref.where}, asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`
|
|
2045
|
+
);
|
|
2046
|
+
ref.set(got.file);
|
|
2047
|
+
}
|
|
2048
|
+
const metaPath = join2(dir, "meta.json");
|
|
2049
|
+
if (!existsSync2(metaPath)) await writeJson(metaPath, doc.source.meta, true);
|
|
2050
|
+
const cursorPath = join2(dir, "cursor.json");
|
|
2051
|
+
if (!existsSync2(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
|
|
2052
|
+
await writeFile2(join2(dir, "doc.json"), JSON.stringify(doc, null, 2));
|
|
2053
|
+
return result;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1684
2056
|
// src/plugin/mediaProbe.ts
|
|
1685
2057
|
var MEDIA_EXT = /\.(png|jpe?g|webp|gif|webm|mp4|mov|mp3|wav|m4a|ogg|glb|gltf|hdr|exr|woff2?)($|\?)/i;
|
|
1686
2058
|
var isRemote = (v) => typeof v === "string" && /^https?:\/\//i.test(v);
|
|
@@ -1766,9 +2138,9 @@ function mediaProbeLints(results) {
|
|
|
1766
2138
|
}
|
|
1767
2139
|
|
|
1768
2140
|
// src/plugin/framesTake.ts
|
|
1769
|
-
import { mkdir as
|
|
1770
|
-
import { existsSync as
|
|
1771
|
-
import { join as
|
|
2141
|
+
import { mkdir as mkdir4, readFile as readFile4, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
2142
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2143
|
+
import { join as join5, resolve as resolve2 } from "path";
|
|
1772
2144
|
import { compileVosConfig } from "@vosjs/core";
|
|
1773
2145
|
import {
|
|
1774
2146
|
docStillTime,
|
|
@@ -1780,83 +2152,11 @@ import {
|
|
|
1780
2152
|
spanOutputExtent as spanOutputExtent2
|
|
1781
2153
|
} from "@vosjs/studio-core";
|
|
1782
2154
|
|
|
1783
|
-
// src/plugin/take.ts
|
|
1784
|
-
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
1785
|
-
import { existsSync } from "fs";
|
|
1786
|
-
import { join } from "path";
|
|
1787
|
-
var RECORDING_NAME = "recording.webm";
|
|
1788
|
-
var PREV_DOC_NAME = "doc.prev.json";
|
|
1789
|
-
function takePaths(dir) {
|
|
1790
|
-
return {
|
|
1791
|
-
dir,
|
|
1792
|
-
recording: join(dir, RECORDING_NAME),
|
|
1793
|
-
framesDir: join(dir, "frames"),
|
|
1794
|
-
framesIndex: join(dir, "frames.json"),
|
|
1795
|
-
cursor: join(dir, "cursor.json"),
|
|
1796
|
-
meta: join(dir, "meta.json"),
|
|
1797
|
-
actions: join(dir, "actions.json"),
|
|
1798
|
-
doc: join(dir, "doc.json")
|
|
1799
|
-
};
|
|
1800
|
-
}
|
|
1801
|
-
async function ensureTakeDir(dir) {
|
|
1802
|
-
const p = takePaths(dir);
|
|
1803
|
-
await mkdir(p.framesDir, { recursive: true });
|
|
1804
|
-
return p;
|
|
1805
|
-
}
|
|
1806
|
-
async function prepareReRecord(dir) {
|
|
1807
|
-
const p = takePaths(dir);
|
|
1808
|
-
const kept = [];
|
|
1809
|
-
let prevDoc = false;
|
|
1810
|
-
if (existsSync(p.doc)) {
|
|
1811
|
-
await rename(p.doc, join(dir, PREV_DOC_NAME));
|
|
1812
|
-
prevDoc = true;
|
|
1813
|
-
kept.push(PREV_DOC_NAME);
|
|
1814
|
-
}
|
|
1815
|
-
if (existsSync(p.actions)) kept.push("actions.json");
|
|
1816
|
-
if (existsSync(join(dir, "vos.json"))) kept.push("vos.json");
|
|
1817
|
-
for (const stale of [
|
|
1818
|
-
p.recording,
|
|
1819
|
-
p.cursor,
|
|
1820
|
-
p.meta,
|
|
1821
|
-
p.framesIndex,
|
|
1822
|
-
p.framesDir,
|
|
1823
|
-
join(dir, "stills"),
|
|
1824
|
-
join(dir, "digest"),
|
|
1825
|
-
join(dir, "mic.webm"),
|
|
1826
|
-
join(dir, "cam.webm")
|
|
1827
|
-
]) {
|
|
1828
|
-
await rm(stale, { recursive: true, force: true });
|
|
1829
|
-
}
|
|
1830
|
-
return { prevDoc, kept };
|
|
1831
|
-
}
|
|
1832
|
-
async function readJson(file) {
|
|
1833
|
-
return JSON.parse(await readFile(file, "utf8"));
|
|
1834
|
-
}
|
|
1835
|
-
async function writeJson(file, value, pretty = false) {
|
|
1836
|
-
await writeFile(
|
|
1837
|
-
file,
|
|
1838
|
-
pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)
|
|
1839
|
-
);
|
|
1840
|
-
}
|
|
1841
|
-
async function loadTake(dir) {
|
|
1842
|
-
const paths = takePaths(dir);
|
|
1843
|
-
if (!existsSync(paths.meta)) {
|
|
1844
|
-
throw new Error(
|
|
1845
|
-
`${dir} is not a take directory (missing meta.json) \u2014 run record first`
|
|
1846
|
-
);
|
|
1847
|
-
}
|
|
1848
|
-
const meta = await readJson(paths.meta);
|
|
1849
|
-
const cursor = existsSync(paths.cursor) ? await readJson(paths.cursor) : [];
|
|
1850
|
-
const actions = existsSync(paths.actions) ? await readJson(paths.actions) : null;
|
|
1851
|
-
const doc = existsSync(paths.doc) ? await readJson(paths.doc) : null;
|
|
1852
|
-
return { paths, cursor, meta, actions, doc };
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
2155
|
// src/plugin/server.ts
|
|
1856
2156
|
import { createServer } from "http";
|
|
1857
|
-
import { existsSync as
|
|
1858
|
-
import { writeFile as
|
|
1859
|
-
import { extname, join as
|
|
2157
|
+
import { existsSync as existsSync3, readFileSync, statSync } from "fs";
|
|
2158
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
2159
|
+
import { extname, join as join3 } from "path";
|
|
1860
2160
|
var MIME = {
|
|
1861
2161
|
".html": "text/html",
|
|
1862
2162
|
".js": "text/javascript",
|
|
@@ -1890,7 +2190,7 @@ function startTakeServer(rootDir, pages) {
|
|
|
1890
2190
|
const chunks = [];
|
|
1891
2191
|
req.on("data", (c) => chunks.push(c));
|
|
1892
2192
|
req.on("end", () => {
|
|
1893
|
-
void
|
|
2193
|
+
void writeFile3(join3(rootDir, name), Buffer.concat(chunks)).then(
|
|
1894
2194
|
() => res.writeHead(200).end("ok"),
|
|
1895
2195
|
(e) => res.writeHead(500).end(String(e))
|
|
1896
2196
|
);
|
|
@@ -1902,11 +2202,11 @@ function startTakeServer(rootDir, pages) {
|
|
|
1902
2202
|
res.end(pages[url.pathname]);
|
|
1903
2203
|
return;
|
|
1904
2204
|
}
|
|
1905
|
-
const file =
|
|
1906
|
-
if (
|
|
1907
|
-
const type = MIME[extname(file)] ?? "application/octet-stream";
|
|
2205
|
+
const file = join3(rootDir, decodeURIComponent(url.pathname));
|
|
2206
|
+
if (existsSync3(file) && statSync(file).isFile()) {
|
|
1908
2207
|
const size = statSync(file).size;
|
|
1909
2208
|
const buf = readFileSync(file);
|
|
2209
|
+
const type = sniffMediaType(buf.subarray(0, MEDIA_HEAD_BYTES)) ?? MIME[extname(file)] ?? "application/octet-stream";
|
|
1910
2210
|
const range = req.headers.range;
|
|
1911
2211
|
const m = range && /^bytes=(\d*)-(\d*)$/.exec(range);
|
|
1912
2212
|
if (m) {
|
|
@@ -1969,7 +2269,7 @@ async function waitForPageDone(page, label, onProgress, timeoutMs) {
|
|
|
1969
2269
|
}
|
|
1970
2270
|
|
|
1971
2271
|
// src/plugin/renderAnimation.ts
|
|
1972
|
-
import { readFile as readFile2, rm as
|
|
2272
|
+
import { readFile as readFile2, rm as rm3 } from "fs/promises";
|
|
1973
2273
|
import { tweenRuntimeCode } from "@vosjs/tween/bundle";
|
|
1974
2274
|
import { elementsBundleCode } from "@vosjs/elements/bundle";
|
|
1975
2275
|
import { concatEncodedVideo, planChunks } from "@vosjs/render-core";
|
|
@@ -2267,7 +2567,7 @@ async function renderAnimation(browser, opts) {
|
|
|
2267
2567
|
outNames.map((name) => readFile2(`${opts.workDir}/${name}`))
|
|
2268
2568
|
);
|
|
2269
2569
|
await Promise.all(
|
|
2270
|
-
outNames.map((name) =>
|
|
2570
|
+
outNames.map((name) => rm3(`${opts.workDir}/${name}`, { force: true }))
|
|
2271
2571
|
);
|
|
2272
2572
|
if (chunks.length === 1) {
|
|
2273
2573
|
return { bytes: new Uint8Array(files[0]), totalFrames, chunks: 1 };
|
|
@@ -2522,9 +2822,9 @@ function applyAndValidate(doc, o) {
|
|
|
2522
2822
|
}
|
|
2523
2823
|
|
|
2524
2824
|
// src/plugin/digestTake.ts
|
|
2525
|
-
import { mkdir as
|
|
2526
|
-
import { existsSync as
|
|
2527
|
-
import { join as
|
|
2825
|
+
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile4 } from "fs/promises";
|
|
2826
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2827
|
+
import { join as join4, resolve } from "path";
|
|
2528
2828
|
import {
|
|
2529
2829
|
DIGEST_CROP_MAX,
|
|
2530
2830
|
DIGEST_FULL_MAX,
|
|
@@ -2746,8 +3046,8 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
2746
3046
|
const meta = doc.source.meta;
|
|
2747
3047
|
let plan = planForDigest(doc);
|
|
2748
3048
|
const transcript = opts.transcript ?? await discoverTranscript(dir);
|
|
2749
|
-
const outDir = resolve(opts.outDir ??
|
|
2750
|
-
await
|
|
3049
|
+
const outDir = resolve(opts.outDir ?? join4(dir, DIGEST_DIR));
|
|
3050
|
+
await mkdir3(outDir, { recursive: true });
|
|
2751
3051
|
const fullMax = opts.full ?? DIGEST_FULL_MAX;
|
|
2752
3052
|
const cropMax = opts.crop ?? DIGEST_CROP_MAX;
|
|
2753
3053
|
const wantFrames = opts.frames !== false && browser !== null;
|
|
@@ -2759,8 +3059,11 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
2759
3059
|
let sheet = null;
|
|
2760
3060
|
let moments;
|
|
2761
3061
|
const boxes = /* @__PURE__ */ new Map();
|
|
2762
|
-
let source = {
|
|
2763
|
-
|
|
3062
|
+
let source = {
|
|
3063
|
+
kind: "video",
|
|
3064
|
+
url: `/${take.paths.recordingName}`
|
|
3065
|
+
};
|
|
3066
|
+
if (existsSync4(take.paths.framesIndex) && existsSync4(take.paths.framesDir)) {
|
|
2764
3067
|
const index = await readJson(
|
|
2765
3068
|
take.paths.framesIndex
|
|
2766
3069
|
);
|
|
@@ -2775,9 +3078,9 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
2775
3078
|
}
|
|
2776
3079
|
}
|
|
2777
3080
|
if (wantFrames) {
|
|
2778
|
-
if (source.kind === "video" && !
|
|
3081
|
+
if (source.kind === "video" && !existsSync4(take.paths.recording))
|
|
2779
3082
|
throw new Error(
|
|
2780
|
-
`${dir} has no
|
|
3083
|
+
`${dir} has no recording \u2014 pull it with \`vos pull --media\` first`
|
|
2781
3084
|
);
|
|
2782
3085
|
const server = await startTakeServer(dir, {
|
|
2783
3086
|
"/digest.html": digestPageHtml()
|
|
@@ -2848,9 +3151,9 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
2848
3151
|
);
|
|
2849
3152
|
sizes = f.sizes;
|
|
2850
3153
|
for (const name of Object.keys(sizes)) {
|
|
2851
|
-
const from =
|
|
2852
|
-
const to =
|
|
2853
|
-
if (from !== to &&
|
|
3154
|
+
const from = join4(dir, name);
|
|
3155
|
+
const to = join4(outDir, name);
|
|
3156
|
+
if (from !== to && existsSync4(from)) await rename3(from, to);
|
|
2854
3157
|
}
|
|
2855
3158
|
if (sizes["sheet.png"]) sheet = "sheet.png";
|
|
2856
3159
|
} finally {
|
|
@@ -2876,12 +3179,12 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
2876
3179
|
if (!p) continue;
|
|
2877
3180
|
for (const f of [p.full, p.crop]) {
|
|
2878
3181
|
if (!f) continue;
|
|
2879
|
-
const size = await pngSize(
|
|
3182
|
+
const size = await pngSize(join4(outDir, f));
|
|
2880
3183
|
if (size) sizes[f] = size;
|
|
2881
3184
|
}
|
|
2882
3185
|
if (p.box) boxes.set(m.id, p.box);
|
|
2883
3186
|
}
|
|
2884
|
-
if (prior.images.sheet &&
|
|
3187
|
+
if (prior.images.sheet && existsSync4(join4(outDir, prior.images.sheet)))
|
|
2885
3188
|
sheet = prior.images.sheet;
|
|
2886
3189
|
}
|
|
2887
3190
|
}
|
|
@@ -2910,14 +3213,14 @@ async function digestTake(browser, dir, opts = {}) {
|
|
|
2910
3213
|
style: opts.style ?? null,
|
|
2911
3214
|
transcript
|
|
2912
3215
|
});
|
|
2913
|
-
const file =
|
|
3216
|
+
const file = join4(outDir, "digest.json");
|
|
2914
3217
|
const body = JSON.stringify(digest, null, 2);
|
|
2915
|
-
await
|
|
3218
|
+
await writeFile4(file, body);
|
|
2916
3219
|
return { file, outDir, digest, bytes: Buffer.byteLength(body) };
|
|
2917
3220
|
}
|
|
2918
3221
|
async function discoverTranscript(dir) {
|
|
2919
|
-
const file =
|
|
2920
|
-
if (!
|
|
3222
|
+
const file = join4(dir, "transcript.json");
|
|
3223
|
+
if (!existsSync4(file)) return null;
|
|
2921
3224
|
try {
|
|
2922
3225
|
return parseTranscript(JSON.parse(await readFile3(file, "utf8")));
|
|
2923
3226
|
} catch {
|
|
@@ -2927,7 +3230,7 @@ async function discoverTranscript(dir) {
|
|
|
2927
3230
|
async function readPriorDigest(outDir) {
|
|
2928
3231
|
try {
|
|
2929
3232
|
const raw = JSON.parse(
|
|
2930
|
-
await readFile3(
|
|
3233
|
+
await readFile3(join4(outDir, "digest.json"), "utf8")
|
|
2931
3234
|
);
|
|
2932
3235
|
return raw.digestVersion === DIGEST_VERSION && Array.isArray(raw.moments) ? raw : null;
|
|
2933
3236
|
} catch {
|
|
@@ -3111,8 +3414,8 @@ async function framesTake(browser, dir, opts) {
|
|
|
3111
3414
|
throw new Error(
|
|
3112
3415
|
"no frame times \u2014 pass --times/--frame, --at-zooms, --at-moments or --at-still"
|
|
3113
3416
|
);
|
|
3114
|
-
const outDir = resolve2(opts.outDir ??
|
|
3115
|
-
await
|
|
3417
|
+
const outDir = resolve2(opts.outDir ?? join5(dir, "stills"));
|
|
3418
|
+
await mkdir4(outDir, { recursive: true });
|
|
3116
3419
|
const named = shots.map((s, i) => ({
|
|
3117
3420
|
...s,
|
|
3118
3421
|
name: s.kind === "moment" ? `moment-${s.momentId}-${s.time.toFixed(2)}s.png` : `${s.kind === "zoom" ? "zoom" : "frame"}-${String(i).padStart(2, "0")}-${s.time.toFixed(2)}s.png`
|
|
@@ -3140,7 +3443,7 @@ async function framesTake(browser, dir, opts) {
|
|
|
3140
3443
|
void page.evaluate(stillsInPage, {
|
|
3141
3444
|
animationCode,
|
|
3142
3445
|
token: VIDEO_TOKEN,
|
|
3143
|
-
videoUrl: `/${
|
|
3446
|
+
videoUrl: `/${take.paths.recordingName}`,
|
|
3144
3447
|
W: width,
|
|
3145
3448
|
H: height,
|
|
3146
3449
|
ss,
|
|
@@ -3156,9 +3459,9 @@ async function framesTake(browser, dir, opts) {
|
|
|
3156
3459
|
);
|
|
3157
3460
|
const frames = [];
|
|
3158
3461
|
for (const n of named) {
|
|
3159
|
-
const from =
|
|
3160
|
-
const to =
|
|
3161
|
-
if (from !== to) await
|
|
3462
|
+
const from = join5(dir, n.name);
|
|
3463
|
+
const to = join5(outDir, n.name);
|
|
3464
|
+
if (from !== to) await rename4(from, to);
|
|
3162
3465
|
frames.push({
|
|
3163
3466
|
file: to,
|
|
3164
3467
|
time: n.time,
|
|
@@ -3173,8 +3476,8 @@ async function framesTake(browser, dir, opts) {
|
|
|
3173
3476
|
}
|
|
3174
3477
|
}
|
|
3175
3478
|
async function momentsFor(dir, doc) {
|
|
3176
|
-
const file =
|
|
3177
|
-
if (
|
|
3479
|
+
const file = join5(dir, DIGEST_DIR, "digest.json");
|
|
3480
|
+
if (existsSync5(file)) {
|
|
3178
3481
|
try {
|
|
3179
3482
|
const d = JSON.parse(await readFile4(file, "utf8"));
|
|
3180
3483
|
if (Array.isArray(d.moments)) return d.moments;
|
|
@@ -3196,8 +3499,8 @@ function parseTimes(spec, duration) {
|
|
|
3196
3499
|
});
|
|
3197
3500
|
}
|
|
3198
3501
|
async function writeIndexJson(result) {
|
|
3199
|
-
const index =
|
|
3200
|
-
await
|
|
3502
|
+
const index = join5(result.outDir, "stills.json");
|
|
3503
|
+
await writeFile5(
|
|
3201
3504
|
index,
|
|
3202
3505
|
JSON.stringify(
|
|
3203
3506
|
{
|
|
@@ -3214,10 +3517,10 @@ async function writeIndexJson(result) {
|
|
|
3214
3517
|
}
|
|
3215
3518
|
|
|
3216
3519
|
// src/plugin/deliver.ts
|
|
3217
|
-
import { mkdir as
|
|
3520
|
+
import { mkdir as mkdir5, mkdtemp, rename as rename5, rm as rm4, stat as stat2, writeFile as writeFile7 } from "fs/promises";
|
|
3218
3521
|
import { tmpdir } from "os";
|
|
3219
|
-
import { join as
|
|
3220
|
-
import { existsSync as
|
|
3522
|
+
import { join as join8, relative, resolve as resolve4 } from "path";
|
|
3523
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3221
3524
|
import { readFile as readFile6 } from "fs/promises";
|
|
3222
3525
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
3223
3526
|
import {
|
|
@@ -3232,7 +3535,7 @@ import {
|
|
|
3232
3535
|
} from "@vosjs/studio-core";
|
|
3233
3536
|
|
|
3234
3537
|
// src/plugin/renderTake.ts
|
|
3235
|
-
import { writeFile as
|
|
3538
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
3236
3539
|
import { compileVosConfig as compileVosConfig2 } from "@vosjs/core";
|
|
3237
3540
|
import {
|
|
3238
3541
|
audioProducerCode,
|
|
@@ -3304,7 +3607,7 @@ async function renderTake(browser, dir, outFile, opts) {
|
|
|
3304
3607
|
const result = await renderAnimation(browser, {
|
|
3305
3608
|
animationCode,
|
|
3306
3609
|
workDir: dir,
|
|
3307
|
-
videoFile:
|
|
3610
|
+
videoFile: take.paths.recordingName,
|
|
3308
3611
|
videoToken: VIDEO_TOKEN2,
|
|
3309
3612
|
width,
|
|
3310
3613
|
height,
|
|
@@ -3321,7 +3624,7 @@ async function renderTake(browser, dir, outFile, opts) {
|
|
|
3321
3624
|
} : void 0,
|
|
3322
3625
|
onProgress: opts.onProgress
|
|
3323
3626
|
});
|
|
3324
|
-
await
|
|
3627
|
+
await writeFile6(outFile, result.bytes);
|
|
3325
3628
|
return {
|
|
3326
3629
|
out: outFile,
|
|
3327
3630
|
bytes: result.bytes.length,
|
|
@@ -3876,9 +4179,9 @@ function proposeTransitions(input, kind) {
|
|
|
3876
4179
|
for (const step of steps) {
|
|
3877
4180
|
if (!step.navigated || step.skipped) continue;
|
|
3878
4181
|
const name = step.id ?? String(step.step);
|
|
3879
|
-
const
|
|
3880
|
-
const outEnd = round3(
|
|
3881
|
-
const inStart = round3(
|
|
4182
|
+
const wait2 = step.do === "wait";
|
|
4183
|
+
const outEnd = round3(wait2 ? step.tStart : step.tEnd);
|
|
4184
|
+
const inStart = round3(wait2 ? step.tEnd : step.tEnd + STEP_SETTLE_SECONDS);
|
|
3882
4185
|
const segs = doc.segments;
|
|
3883
4186
|
if (segs.some(
|
|
3884
4187
|
(s) => !s.media && Math.abs(s.out - outEnd) < 0.05 && s !== segs.at(-1)
|
|
@@ -4197,9 +4500,9 @@ function isLightHexGround(hex2) {
|
|
|
4197
4500
|
}
|
|
4198
4501
|
|
|
4199
4502
|
// src/plugin/posterDoc.ts
|
|
4200
|
-
import { existsSync as
|
|
4503
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4201
4504
|
import { readFile as readFile5 } from "fs/promises";
|
|
4202
|
-
import { isAbsolute, join as
|
|
4505
|
+
import { isAbsolute, join as join7, resolve as resolve3 } from "path";
|
|
4203
4506
|
import {
|
|
4204
4507
|
computeCardLayout,
|
|
4205
4508
|
docStillTime as docStillTime2,
|
|
@@ -4211,7 +4514,7 @@ import {
|
|
|
4211
4514
|
// src/plugin/platform.ts
|
|
4212
4515
|
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
4213
4516
|
import { homedir } from "os";
|
|
4214
|
-
import { join as
|
|
4517
|
+
import { join as join6 } from "path";
|
|
4215
4518
|
function platformOrigin(flags = {}) {
|
|
4216
4519
|
const legacyEnv = process.env.VOS_API_BASE?.trim();
|
|
4217
4520
|
const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
|
|
@@ -4244,7 +4547,7 @@ function parseVosId(input) {
|
|
|
4244
4547
|
function deriveSlug(title) {
|
|
4245
4548
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
|
|
4246
4549
|
}
|
|
4247
|
-
var CREDENTIALS_PATH =
|
|
4550
|
+
var CREDENTIALS_PATH = join6(homedir(), ".config", "vos", "credentials");
|
|
4248
4551
|
function resolveCredential(explicit) {
|
|
4249
4552
|
const flag = explicit?.trim();
|
|
4250
4553
|
if (flag) return flag;
|
|
@@ -4267,7 +4570,7 @@ function requireCredential(explicit) {
|
|
|
4267
4570
|
return key;
|
|
4268
4571
|
}
|
|
4269
4572
|
function writeCredential(key) {
|
|
4270
|
-
mkdirSync(
|
|
4573
|
+
mkdirSync(join6(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
|
|
4271
4574
|
writeFileSync(CREDENTIALS_PATH, `${key.trim()}
|
|
4272
4575
|
`, { mode: 384 });
|
|
4273
4576
|
return CREDENTIALS_PATH;
|
|
@@ -4311,7 +4614,7 @@ function readJsonFile(file) {
|
|
|
4311
4614
|
}
|
|
4312
4615
|
}
|
|
4313
4616
|
function readSyncState(dir) {
|
|
4314
|
-
const own = readJsonFile(
|
|
4617
|
+
const own = readJsonFile(join6(dir, SYNC_STATE_NAME));
|
|
4315
4618
|
if (own && typeof own.vosId === "string") {
|
|
4316
4619
|
return {
|
|
4317
4620
|
vosId: own.vosId,
|
|
@@ -4322,7 +4625,7 @@ function readSyncState(dir) {
|
|
|
4322
4625
|
...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
|
|
4323
4626
|
};
|
|
4324
4627
|
}
|
|
4325
|
-
const push = readJsonFile(
|
|
4628
|
+
const push = readJsonFile(join6(dir, "push.json"));
|
|
4326
4629
|
if (push && typeof push.vosId === "string") {
|
|
4327
4630
|
return {
|
|
4328
4631
|
vosId: push.vosId,
|
|
@@ -4330,7 +4633,7 @@ function readSyncState(dir) {
|
|
|
4330
4633
|
...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
|
|
4331
4634
|
};
|
|
4332
4635
|
}
|
|
4333
|
-
const meta = readJsonFile(
|
|
4636
|
+
const meta = readJsonFile(join6(dir, "meta.json"));
|
|
4334
4637
|
if (meta && typeof meta.id === "string") {
|
|
4335
4638
|
return {
|
|
4336
4639
|
vosId: meta.id,
|
|
@@ -4348,7 +4651,7 @@ function writeSyncState(dir, patch) {
|
|
|
4348
4651
|
...patch
|
|
4349
4652
|
};
|
|
4350
4653
|
writeFileSync(
|
|
4351
|
-
|
|
4654
|
+
join6(dir, SYNC_STATE_NAME),
|
|
4352
4655
|
`${JSON.stringify(merged, null, 2)}
|
|
4353
4656
|
`
|
|
4354
4657
|
);
|
|
@@ -4387,9 +4690,9 @@ var roleFor = (cls) => `poster-${cls}`;
|
|
|
4387
4690
|
async function readPosterAt(cls, path, takeDir, from) {
|
|
4388
4691
|
let file = path;
|
|
4389
4692
|
let docDir = path;
|
|
4390
|
-
if (
|
|
4391
|
-
file =
|
|
4392
|
-
} else if (!/\.json$/i.test(path) || !
|
|
4693
|
+
if (existsSync6(join7(path, "doc.json"))) {
|
|
4694
|
+
file = join7(path, "doc.json");
|
|
4695
|
+
} else if (!/\.json$/i.test(path) || !existsSync6(path)) {
|
|
4393
4696
|
return null;
|
|
4394
4697
|
} else {
|
|
4395
4698
|
docDir = resolve3(path, "..");
|
|
@@ -4406,7 +4709,7 @@ async function readPosterAt(cls, path, takeDir, from) {
|
|
|
4406
4709
|
const doc = migrateHostedDoc(
|
|
4407
4710
|
raw
|
|
4408
4711
|
);
|
|
4409
|
-
const ownFootage =
|
|
4712
|
+
const ownFootage = existsSync6(join7(docDir, "meta.json")) && existsSync6(join7(docDir, source.videoKey));
|
|
4410
4713
|
const sync = readSyncState(docDir);
|
|
4411
4714
|
return {
|
|
4412
4715
|
cls,
|
|
@@ -4429,10 +4732,10 @@ async function findPosterDocs(takeDir, launchRoles) {
|
|
|
4429
4732
|
candidates.push({ path: at2(named.trim()), from: `LAUNCH.md ${role}` });
|
|
4430
4733
|
}
|
|
4431
4734
|
candidates.push({
|
|
4432
|
-
path:
|
|
4735
|
+
path: join7(takeDir, "poster", cls),
|
|
4433
4736
|
from: `poster/${cls}`
|
|
4434
4737
|
});
|
|
4435
|
-
candidates.push({ path:
|
|
4738
|
+
candidates.push({ path: join7(takeDir, "poster"), from: "poster" });
|
|
4436
4739
|
for (const c of candidates) {
|
|
4437
4740
|
const ref = await readPosterAt(cls, c.path, takeDir, c.from);
|
|
4438
4741
|
if (ref) {
|
|
@@ -4528,18 +4831,18 @@ function resolveChannels(raw) {
|
|
|
4528
4831
|
return out;
|
|
4529
4832
|
}
|
|
4530
4833
|
async function readBrandBesideTake(dir, explicit) {
|
|
4531
|
-
const candidates = explicit ? [explicit] : [
|
|
4834
|
+
const candidates = explicit ? [explicit] : [join8(dir, "BRAND.md"), join8(dir, "..", "BRAND.md")];
|
|
4532
4835
|
for (const file of candidates) {
|
|
4533
|
-
if (!
|
|
4836
|
+
if (!existsSync7(file)) continue;
|
|
4534
4837
|
const roles = parseFrontmatter(await readFile6(file, "utf8"));
|
|
4535
4838
|
return { file, roles };
|
|
4536
4839
|
}
|
|
4537
4840
|
return null;
|
|
4538
4841
|
}
|
|
4539
4842
|
async function readLaunchBesideTake(dir, explicit) {
|
|
4540
|
-
const candidates = explicit ? [explicit] : [
|
|
4843
|
+
const candidates = explicit ? [explicit] : [join8(dir, "LAUNCH.md"), join8(dir, "..", "LAUNCH.md")];
|
|
4541
4844
|
for (const file of candidates) {
|
|
4542
|
-
if (!
|
|
4845
|
+
if (!existsSync7(file)) continue;
|
|
4543
4846
|
return { file, roles: parseFrontmatter(await readFile6(file, "utf8")) };
|
|
4544
4847
|
}
|
|
4545
4848
|
return null;
|
|
@@ -4604,7 +4907,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4604
4907
|
const meta = doc.source.meta;
|
|
4605
4908
|
const vw = meta.captureWidth ?? meta.width;
|
|
4606
4909
|
const vh = meta.captureHeight ?? meta.height;
|
|
4607
|
-
const probeDir = await mkdtemp(
|
|
4910
|
+
const probeDir = await mkdtemp(join8(tmpdir(), "vos-moments-"));
|
|
4608
4911
|
try {
|
|
4609
4912
|
const probe = await framesTake(browser, dir, {
|
|
4610
4913
|
times: candidates.map((c) => c.time),
|
|
@@ -4643,7 +4946,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4643
4946
|
notes: [...notes, ...pick.dropped.map((d) => `moment: ${d}`)]
|
|
4644
4947
|
};
|
|
4645
4948
|
} finally {
|
|
4646
|
-
await
|
|
4949
|
+
await rm4(probeDir, { recursive: true, force: true });
|
|
4647
4950
|
}
|
|
4648
4951
|
}
|
|
4649
4952
|
var overCeiling = (bytes, maxBytes) => `${Math.ceil(bytes / 1024)} KB exceeds the ${Math.floor(maxBytes / 1024)} KB ceiling`;
|
|
@@ -4691,8 +4994,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4691
4994
|
const doc = take.doc;
|
|
4692
4995
|
const duration = docOutputDuration2(doc);
|
|
4693
4996
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
4694
|
-
const outDir = resolve4(opts.outDir ??
|
|
4695
|
-
await
|
|
4997
|
+
const outDir = resolve4(opts.outDir ?? join8(dir, "kit"));
|
|
4998
|
+
await mkdir5(outDir, { recursive: true });
|
|
4696
4999
|
const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
|
|
4697
5000
|
const assets = [];
|
|
4698
5001
|
const skipped = [];
|
|
@@ -4752,7 +5055,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4752
5055
|
opts.onPhase?.(
|
|
4753
5056
|
`${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
|
|
4754
5057
|
);
|
|
4755
|
-
const shotDir = await mkdtemp(
|
|
5058
|
+
const shotDir = await mkdtemp(join8(tmpdir(), "vos-poster-"));
|
|
4756
5059
|
try {
|
|
4757
5060
|
const captured = await framesTake(browser, ref.takeDir, {
|
|
4758
5061
|
times: [time],
|
|
@@ -4762,9 +5065,9 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4762
5065
|
doc: ref.doc,
|
|
4763
5066
|
overrides: opts.overrides
|
|
4764
5067
|
});
|
|
4765
|
-
const to =
|
|
4766
|
-
await
|
|
4767
|
-
const bytes = (await
|
|
5068
|
+
const to = join8(outDir, `${d.id}.png`);
|
|
5069
|
+
await rename5(captured.frames[0].file, to);
|
|
5070
|
+
const bytes = (await stat2(to)).size;
|
|
4768
5071
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
4769
5072
|
skipped.push(
|
|
4770
5073
|
`${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
|
|
@@ -4793,7 +5096,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4793
5096
|
...shot.x < 0 && shot.x + shot.w > 1 ? { crop: true } : {}
|
|
4794
5097
|
});
|
|
4795
5098
|
} finally {
|
|
4796
|
-
await
|
|
5099
|
+
await rm4(shotDir, { recursive: true, force: true });
|
|
4797
5100
|
}
|
|
4798
5101
|
}
|
|
4799
5102
|
if (missing.size) {
|
|
@@ -4834,7 +5137,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4834
5137
|
}
|
|
4835
5138
|
}
|
|
4836
5139
|
opts.onPhase?.(`${label} (${specWords(d)})`);
|
|
4837
|
-
const outFile =
|
|
5140
|
+
const outFile = join8(outDir, `${d.id}.${d.format}`);
|
|
4838
5141
|
const bitrate = d.maxBytes !== void 0 ? Math.min(
|
|
4839
5142
|
1e7,
|
|
4840
5143
|
Math.floor(d.maxBytes * 8 / seconds * 0.85)
|
|
@@ -4881,9 +5184,9 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4881
5184
|
for (let i = 0; i < captured.frames.length; i++) {
|
|
4882
5185
|
const frame = captured.frames[i];
|
|
4883
5186
|
const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
|
|
4884
|
-
const to =
|
|
4885
|
-
await
|
|
4886
|
-
const bytes = (await
|
|
5187
|
+
const to = join8(outDir, name);
|
|
5188
|
+
await rename5(frame.file, to);
|
|
5189
|
+
const bytes = (await stat2(to)).size;
|
|
4887
5190
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
4888
5191
|
skipped.push(
|
|
4889
5192
|
`${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
|
|
@@ -4919,15 +5222,15 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4919
5222
|
skipped,
|
|
4920
5223
|
assets
|
|
4921
5224
|
};
|
|
4922
|
-
const kitFile =
|
|
4923
|
-
await
|
|
5225
|
+
const kitFile = join8(outDir, "kit.json");
|
|
5226
|
+
await writeFile7(kitFile, JSON.stringify(kit, null, 2));
|
|
4924
5227
|
return { kit, kitFile, outDir };
|
|
4925
5228
|
}
|
|
4926
5229
|
|
|
4927
5230
|
// src/plugin/markAsset.ts
|
|
4928
|
-
import { mkdir as
|
|
4929
|
-
import { existsSync as
|
|
4930
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5231
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
5232
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5233
|
+
import { isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
|
|
4931
5234
|
function markExtension(url) {
|
|
4932
5235
|
const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
|
|
4933
5236
|
if (!m) return null;
|
|
@@ -5009,7 +5312,7 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5009
5312
|
bytes = new Uint8Array(await res.arrayBuffer());
|
|
5010
5313
|
} else {
|
|
5011
5314
|
const file = isAbsolute2(src) ? src : resolve5(takeDir, src);
|
|
5012
|
-
if (!
|
|
5315
|
+
if (!existsSync8(file)) throw new Error("no such file");
|
|
5013
5316
|
bytes = new Uint8Array(await readFile7(file));
|
|
5014
5317
|
}
|
|
5015
5318
|
} catch (e) {
|
|
@@ -5025,8 +5328,8 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5025
5328
|
);
|
|
5026
5329
|
return null;
|
|
5027
5330
|
}
|
|
5028
|
-
await
|
|
5029
|
-
await
|
|
5331
|
+
await mkdir6(join9(takeDir, "brand"), { recursive: true });
|
|
5332
|
+
await writeFile8(join9(takeDir, "brand", `${name}.${ext}`), bytes);
|
|
5030
5333
|
return { key: `/brand/${name}.${ext}`, aspect };
|
|
5031
5334
|
};
|
|
5032
5335
|
out.light = await one("logoUrl", "mark");
|
|
@@ -5062,9 +5365,9 @@ async function fetchMusicCatalog(origin) {
|
|
|
5062
5365
|
}
|
|
5063
5366
|
|
|
5064
5367
|
// src/plugin/judge.ts
|
|
5065
|
-
import { existsSync as
|
|
5066
|
-
import { mkdir as
|
|
5067
|
-
import { dirname, isAbsolute as isAbsolute3, join as
|
|
5368
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5369
|
+
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
5370
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, join as join10, resolve as resolve6 } from "path";
|
|
5068
5371
|
import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
|
|
5069
5372
|
|
|
5070
5373
|
// src/plugin/shotBake.ts
|
|
@@ -5147,7 +5450,8 @@ function rolesFor(asset) {
|
|
|
5147
5450
|
const portrait = spec.px.w < spec.px.h;
|
|
5148
5451
|
return portrait ? ["feature-clip", "site-walkthrough"] : ["feature-clip", "site-walkthrough", "feature-clip-dark"];
|
|
5149
5452
|
}
|
|
5150
|
-
if (spec?.genre === "card")
|
|
5453
|
+
if (spec?.genre === "card")
|
|
5454
|
+
return ["window-in-scene", "card-on-gradient", "framed-screenshot"];
|
|
5151
5455
|
if (spec?.genre === "screenshot") return ["framed-screenshot", "app-session"];
|
|
5152
5456
|
return [];
|
|
5153
5457
|
}
|
|
@@ -5207,7 +5511,8 @@ function composeSheet(left, right, height = 540, gutter = 32) {
|
|
|
5207
5511
|
const si = (y * src.w + x) * 4;
|
|
5208
5512
|
const a = src.data[si + 3] / 255;
|
|
5209
5513
|
const o = ((y0 + y) * w + x0 + x) * 4;
|
|
5210
|
-
for (let c = 0; c < 3; c++)
|
|
5514
|
+
for (let c = 0; c < 3; c++)
|
|
5515
|
+
out[o + c] = Math.round(src.data[si + c] * a + out[o + c] * (1 - a));
|
|
5211
5516
|
}
|
|
5212
5517
|
}
|
|
5213
5518
|
};
|
|
@@ -5223,51 +5528,70 @@ function composeSheet(left, right, height = 540, gutter = 32) {
|
|
|
5223
5528
|
}
|
|
5224
5529
|
};
|
|
5225
5530
|
fill(margin, margin + lw, margin + height + 6, margin + height + 6 + band, 40);
|
|
5226
|
-
fill(
|
|
5531
|
+
fill(
|
|
5532
|
+
margin + lw + gutter,
|
|
5533
|
+
margin + lw + gutter + rw,
|
|
5534
|
+
margin + height + 6,
|
|
5535
|
+
margin + height + 6 + band,
|
|
5536
|
+
200
|
|
5537
|
+
);
|
|
5227
5538
|
return { w, h, data: out };
|
|
5228
5539
|
}
|
|
5229
5540
|
async function judgeKit(kitPath, manifestPath, outDir) {
|
|
5230
5541
|
const kit = JSON.parse(await readFile8(kitPath, "utf8"));
|
|
5231
|
-
const manifest = JSON.parse(
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
const
|
|
5235
|
-
|
|
5542
|
+
const manifest = JSON.parse(
|
|
5543
|
+
await readFile8(manifestPath, "utf8")
|
|
5544
|
+
);
|
|
5545
|
+
const manifestDir = dirname2(resolve6(manifestPath));
|
|
5546
|
+
const kitDir = dirname2(resolve6(kitPath));
|
|
5547
|
+
const out = resolve6(outDir ?? join10(kitDir, "judge"));
|
|
5548
|
+
await mkdir7(out, { recursive: true });
|
|
5236
5549
|
const sheets = [];
|
|
5237
5550
|
const skipped = [];
|
|
5238
5551
|
const refCache = /* @__PURE__ */ new Map();
|
|
5239
5552
|
const loadRef = async (file) => {
|
|
5240
5553
|
if (refCache.has(file)) return refCache.get(file) ?? null;
|
|
5241
|
-
const p =
|
|
5242
|
-
const img =
|
|
5554
|
+
const p = join10(manifestDir, file);
|
|
5555
|
+
const img = existsSync9(p) ? decodePng(new Uint8Array(await readFile8(p))) : null;
|
|
5243
5556
|
refCache.set(file, img);
|
|
5244
5557
|
return img;
|
|
5245
5558
|
};
|
|
5246
5559
|
for (const a of kit.assets) {
|
|
5247
5560
|
if (!/\.png$/i.test(a.path)) continue;
|
|
5248
|
-
const file = isAbsolute3(a.path) &&
|
|
5249
|
-
const img =
|
|
5561
|
+
const file = isAbsolute3(a.path) && existsSync9(a.path) ? a.path : join10(kitDir, a.path.split("/").pop() ?? a.path);
|
|
5562
|
+
const img = existsSync9(file) ? decodePng(new Uint8Array(await readFile8(file))) : null;
|
|
5250
5563
|
if (!img) {
|
|
5251
5564
|
skipped.push(`${a.destination ?? a.path}: unreadable`);
|
|
5252
5565
|
continue;
|
|
5253
5566
|
}
|
|
5254
|
-
const roles = rolesFor({
|
|
5255
|
-
|
|
5567
|
+
const roles = rolesFor({
|
|
5568
|
+
destination: a.destination,
|
|
5569
|
+
source: a.source,
|
|
5570
|
+
template: a.template,
|
|
5571
|
+
path: a.path
|
|
5572
|
+
});
|
|
5573
|
+
const ref = manifest.assets.find(
|
|
5574
|
+
(r) => roles.includes(r.role) && r.genre !== "context"
|
|
5575
|
+
);
|
|
5256
5576
|
if (!ref) {
|
|
5257
|
-
skipped.push(
|
|
5577
|
+
skipped.push(
|
|
5578
|
+
`${a.destination ?? a.path}: no reference of role ${roles.join("|") || "(none)"}`
|
|
5579
|
+
);
|
|
5258
5580
|
continue;
|
|
5259
5581
|
}
|
|
5260
5582
|
const refImg = await loadRef(ref.file);
|
|
5261
5583
|
if (!refImg) {
|
|
5262
|
-
skipped.push(
|
|
5584
|
+
skipped.push(
|
|
5585
|
+
`${a.destination ?? a.path}: reference ${ref.file} unreadable`
|
|
5586
|
+
);
|
|
5263
5587
|
continue;
|
|
5264
5588
|
}
|
|
5265
5589
|
const stem = (a.path.split("/").pop() ?? a.path).replace(/\.png$/i, "");
|
|
5266
5590
|
const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
|
|
5267
5591
|
const nameA = `${id}--A.png`;
|
|
5268
5592
|
const nameB = `${id}--B.png`;
|
|
5269
|
-
await
|
|
5270
|
-
await
|
|
5593
|
+
await writeFile9(join10(out, nameA), encodePng(composeSheet(img, refImg)));
|
|
5594
|
+
await writeFile9(join10(out, nameB), encodePng(composeSheet(refImg, img)));
|
|
5271
5595
|
const rubric = [
|
|
5272
5596
|
`# ${id} against ${ref.id} (${ref.file})`,
|
|
5273
5597
|
"",
|
|
@@ -5287,18 +5611,31 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5287
5611
|
"In judge.json: `win` true when the kit asset is at least as good as the reference on the rules that apply to its genre, false when it is worse, null for a tie; `reasons` names the rules by number.",
|
|
5288
5612
|
""
|
|
5289
5613
|
].join("\n");
|
|
5290
|
-
await
|
|
5291
|
-
sheets.push({
|
|
5614
|
+
await writeFile9(join10(out, `${id}.md`), rubric);
|
|
5615
|
+
sheets.push({
|
|
5616
|
+
asset: id,
|
|
5617
|
+
reference: ref.id,
|
|
5618
|
+
sheetA: nameA,
|
|
5619
|
+
sheetB: nameB,
|
|
5620
|
+
rubric: `${id}.md`
|
|
5621
|
+
});
|
|
5292
5622
|
}
|
|
5293
|
-
const verdictFile =
|
|
5294
|
-
if (!
|
|
5295
|
-
await
|
|
5623
|
+
const verdictFile = join10(out, "judge.json");
|
|
5624
|
+
if (!existsSync9(verdictFile)) {
|
|
5625
|
+
await writeFile9(
|
|
5296
5626
|
verdictFile,
|
|
5297
5627
|
JSON.stringify(
|
|
5298
5628
|
{
|
|
5299
5629
|
kit: kitPath,
|
|
5300
5630
|
manifest: manifestPath,
|
|
5301
|
-
verdicts: sheets.map((s) => ({
|
|
5631
|
+
verdicts: sheets.map((s) => ({
|
|
5632
|
+
asset: s.asset,
|
|
5633
|
+
reference: s.reference,
|
|
5634
|
+
A: null,
|
|
5635
|
+
B: null,
|
|
5636
|
+
win: null,
|
|
5637
|
+
reasons: []
|
|
5638
|
+
}))
|
|
5302
5639
|
},
|
|
5303
5640
|
null,
|
|
5304
5641
|
2
|
|
@@ -5308,17 +5645,24 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5308
5645
|
return { sheets, skipped, outDir: out, verdictFile };
|
|
5309
5646
|
}
|
|
5310
5647
|
function winRate(verdicts) {
|
|
5311
|
-
const judgedList = verdicts.filter(
|
|
5648
|
+
const judgedList = verdicts.filter(
|
|
5649
|
+
(v) => v.win !== null || (v.reasons?.length ?? 0) > 0
|
|
5650
|
+
);
|
|
5312
5651
|
const wins = judgedList.filter((v) => v.win === true).length;
|
|
5313
5652
|
const ties = judgedList.filter((v) => v.win === null).length;
|
|
5314
5653
|
const judged = judgedList.length;
|
|
5315
|
-
return {
|
|
5654
|
+
return {
|
|
5655
|
+
wins,
|
|
5656
|
+
ties,
|
|
5657
|
+
judged,
|
|
5658
|
+
rate: judged ? (wins + ties / 2) / judged : null
|
|
5659
|
+
};
|
|
5316
5660
|
}
|
|
5317
5661
|
|
|
5318
5662
|
// src/plugin/callout.ts
|
|
5319
|
-
import { existsSync as
|
|
5663
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5320
5664
|
import { readFile as readFile9 } from "fs/promises";
|
|
5321
|
-
import { join as
|
|
5665
|
+
import { join as join11 } from "path";
|
|
5322
5666
|
import { lerpArray, sample } from "@vosjs/timeline";
|
|
5323
5667
|
import {
|
|
5324
5668
|
CALLOUT_DEFAULT_BODY_PX,
|
|
@@ -5501,10 +5845,10 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
|
5501
5845
|
const out = /* @__PURE__ */ new Map();
|
|
5502
5846
|
if (!times.length) return out;
|
|
5503
5847
|
const bare = { ...doc, overlays: [] };
|
|
5504
|
-
const outDir =
|
|
5848
|
+
const outDir = join11(dir, ".ground-check");
|
|
5505
5849
|
const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
|
|
5506
5850
|
for (const f of res.frames) {
|
|
5507
|
-
if (!
|
|
5851
|
+
if (!existsSync10(f.file)) continue;
|
|
5508
5852
|
out.set(
|
|
5509
5853
|
+f.time.toFixed(3),
|
|
5510
5854
|
decodePng(new Uint8Array(await readFile9(f.file)))
|
|
@@ -5885,9 +6229,9 @@ function formatFinding(f) {
|
|
|
5885
6229
|
}
|
|
5886
6230
|
|
|
5887
6231
|
// src/plugin/validateKit.ts
|
|
5888
|
-
import { existsSync as
|
|
5889
|
-
import { readFile as readFile11, stat as
|
|
5890
|
-
import { dirname as
|
|
6232
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6233
|
+
import { readFile as readFile11, stat as stat3 } from "fs/promises";
|
|
6234
|
+
import { dirname as dirname3, isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
5891
6235
|
import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
|
|
5892
6236
|
var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
5893
6237
|
function pngDimensions(bytes) {
|
|
@@ -5944,10 +6288,10 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5944
6288
|
measured
|
|
5945
6289
|
};
|
|
5946
6290
|
}
|
|
5947
|
-
const base =
|
|
6291
|
+
const base = dirname3(kitPath);
|
|
5948
6292
|
const resolvePath = (p) => {
|
|
5949
|
-
if (isAbsolute4(p) &&
|
|
5950
|
-
const beside =
|
|
6293
|
+
if (isAbsolute4(p) && existsSync11(p)) return p;
|
|
6294
|
+
const beside = join12(base, p.split("/").pop() ?? p);
|
|
5951
6295
|
return beside;
|
|
5952
6296
|
};
|
|
5953
6297
|
const perDestination = /* @__PURE__ */ new Map();
|
|
@@ -5958,7 +6302,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5958
6302
|
const file = resolvePath(a.path);
|
|
5959
6303
|
let bytes;
|
|
5960
6304
|
try {
|
|
5961
|
-
bytes = (await
|
|
6305
|
+
bytes = (await stat3(file)).size;
|
|
5962
6306
|
} catch {
|
|
5963
6307
|
problems.push(`${label}: ${a.path} is missing`);
|
|
5964
6308
|
continue;
|
|
@@ -6083,7 +6427,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6083
6427
|
|
|
6084
6428
|
// src/plugin/recorder.ts
|
|
6085
6429
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
6086
|
-
import { join as
|
|
6430
|
+
import { join as join13 } from "path";
|
|
6087
6431
|
|
|
6088
6432
|
// src/plugin/recordingCap.ts
|
|
6089
6433
|
var HOSTED_RECORDING_CAP_SECONDS = 30 * 60;
|
|
@@ -6165,8 +6509,8 @@ async function clockMotion(dur, at2, clock) {
|
|
|
6165
6509
|
await at2(easeInOutCubic(Math.min(1, elapsed / dur)));
|
|
6166
6510
|
samples++;
|
|
6167
6511
|
const next = start + Math.ceil((clock.now() - start) / MOTION_TICK_MS) * MOTION_TICK_MS;
|
|
6168
|
-
const
|
|
6169
|
-
if (
|
|
6512
|
+
const wait2 = Math.min(next, start + dur) - clock.now();
|
|
6513
|
+
if (wait2 > 0) await clock.sleep(wait2);
|
|
6170
6514
|
}
|
|
6171
6515
|
await at2(1);
|
|
6172
6516
|
return samples + 1;
|
|
@@ -6176,8 +6520,8 @@ async function clockTyping(chars, delay, type, clock) {
|
|
|
6176
6520
|
for (let i = 0; i < chars.length; i++) {
|
|
6177
6521
|
await type(chars[i], i);
|
|
6178
6522
|
const due = start + (i + 1) * delay;
|
|
6179
|
-
const
|
|
6180
|
-
if (
|
|
6523
|
+
const wait2 = due - clock.now();
|
|
6524
|
+
if (wait2 > 0 && i < chars.length - 1) await clock.sleep(wait2);
|
|
6181
6525
|
}
|
|
6182
6526
|
}
|
|
6183
6527
|
function askedMs(step) {
|
|
@@ -6261,7 +6605,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6261
6605
|
cdp.on("Page.screencastFrame", (ev) => {
|
|
6262
6606
|
const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
|
|
6263
6607
|
const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
|
|
6264
|
-
writeFileSync2(
|
|
6608
|
+
writeFileSync2(join13(paths.framesDir, file), Buffer.from(ev.data, "base64"));
|
|
6265
6609
|
frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
|
|
6266
6610
|
cdp.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => {
|
|
6267
6611
|
});
|
|
@@ -6523,7 +6867,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6523
6867
|
await sleep(200);
|
|
6524
6868
|
const pageTitle = await page.title().catch(() => "");
|
|
6525
6869
|
await context.close();
|
|
6526
|
-
const firstFrame = frames[0] ? jpegDims(readFileSync3(
|
|
6870
|
+
const firstFrame = frames[0] ? jpegDims(readFileSync3(join13(paths.framesDir, frames[0].file))) : null;
|
|
6527
6871
|
const meta = {
|
|
6528
6872
|
dpr: 1,
|
|
6529
6873
|
zoom: 1,
|
|
@@ -6634,9 +6978,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
6634
6978
|
}
|
|
6635
6979
|
|
|
6636
6980
|
// src/plugin/plan.ts
|
|
6637
|
-
import { existsSync as
|
|
6981
|
+
import { existsSync as existsSync12 } from "fs";
|
|
6638
6982
|
import { readFile as readFile12 } from "fs/promises";
|
|
6639
|
-
import { join as
|
|
6983
|
+
import { join as join14 } from "path";
|
|
6640
6984
|
import {
|
|
6641
6985
|
DEFAULT_FRAME_STYLE,
|
|
6642
6986
|
STYLE_FIELDS,
|
|
@@ -6858,8 +7202,8 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
6858
7202
|
// src/plugin/plan.ts
|
|
6859
7203
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
6860
7204
|
async function readDigestActivity(dir) {
|
|
6861
|
-
const file =
|
|
6862
|
-
if (!
|
|
7205
|
+
const file = join14(dir, "digest", "digest.json");
|
|
7206
|
+
if (!existsSync12(file)) return null;
|
|
6863
7207
|
try {
|
|
6864
7208
|
const d = JSON.parse(await readFile12(file, "utf8"));
|
|
6865
7209
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
@@ -6928,11 +7272,11 @@ async function planTake(dir, opts = {}) {
|
|
|
6928
7272
|
if (opts.reuse) {
|
|
6929
7273
|
const prev = migrateMotion2(opts.reuse.doc);
|
|
6930
7274
|
const artifact = {
|
|
6931
|
-
videoKey:
|
|
7275
|
+
videoKey: take.paths.recordingName,
|
|
6932
7276
|
cursor,
|
|
6933
7277
|
meta
|
|
6934
7278
|
};
|
|
6935
|
-
doc = projectFromArtifact(artifact,
|
|
7279
|
+
doc = projectFromArtifact(artifact, take.paths.recordingName, ingest).doc;
|
|
6936
7280
|
doc = copyStyle(prev, doc);
|
|
6937
7281
|
const rt = retimeCut(prev, meta.steps ?? [], meta.durationMs);
|
|
6938
7282
|
doc.segments = rt.segments;
|
|
@@ -7020,11 +7364,11 @@ async function planTake(dir, opts = {}) {
|
|
|
7020
7364
|
}
|
|
7021
7365
|
} else {
|
|
7022
7366
|
const artifact = {
|
|
7023
|
-
videoKey:
|
|
7367
|
+
videoKey: take.paths.recordingName,
|
|
7024
7368
|
cursor,
|
|
7025
7369
|
meta
|
|
7026
7370
|
};
|
|
7027
|
-
doc = projectFromArtifact(artifact,
|
|
7371
|
+
doc = projectFromArtifact(artifact, take.paths.recordingName, ingest).doc;
|
|
7028
7372
|
if (opts.style) {
|
|
7029
7373
|
const applied = applyStyle(opts.style.doc, doc, opts);
|
|
7030
7374
|
doc = applied.doc;
|
|
@@ -7074,207 +7418,11 @@ import { createHash } from "crypto";
|
|
|
7074
7418
|
import { existsSync as existsSync13 } from "fs";
|
|
7075
7419
|
import { readFile as readFile13 } from "fs/promises";
|
|
7076
7420
|
import { createInterface } from "readline/promises";
|
|
7077
|
-
import { basename, join as join16 } from "path";
|
|
7421
|
+
import { basename as basename3, join as join16 } from "path";
|
|
7078
7422
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
7079
7423
|
|
|
7080
|
-
// src/plugin/media.ts
|
|
7081
|
-
import { isKeyedOverlay } from "@vosjs/studio-core";
|
|
7082
|
-
import { createWriteStream, existsSync as existsSync12 } from "fs";
|
|
7083
|
-
import { writeFile as writeFile9 } from "fs/promises";
|
|
7084
|
-
import { join as join14 } from "path";
|
|
7085
|
-
import { Readable } from "stream";
|
|
7086
|
-
import { pipeline } from "stream/promises";
|
|
7087
|
-
var MIC_NAME = "mic.webm";
|
|
7088
|
-
var CAM_NAME = "cam.webm";
|
|
7089
|
-
var KEYS = [
|
|
7090
|
-
{ key: "videoKey", file: RECORDING_NAME },
|
|
7091
|
-
{ key: "micKey", file: MIC_NAME },
|
|
7092
|
-
{ key: "camKey", file: CAM_NAME }
|
|
7093
|
-
];
|
|
7094
|
-
function isTakeRelativeKey(key) {
|
|
7095
|
-
return !!key && !/^(https?:|blob:|data:|media:|\/\/)/.test(key) && !key.startsWith("/api/");
|
|
7096
|
-
}
|
|
7097
|
-
var takeRelativeFile = (key) => key.replace(/^\/+/, "");
|
|
7098
|
-
function docMediaRefs(doc, keep = isTakeRelativeKey) {
|
|
7099
|
-
const out = [];
|
|
7100
|
-
for (const m of doc.media ?? []) {
|
|
7101
|
-
if (keep(m.videoKey))
|
|
7102
|
-
out.push({
|
|
7103
|
-
where: `media ${m.id}`,
|
|
7104
|
-
key: m.videoKey,
|
|
7105
|
-
set: (next) => {
|
|
7106
|
-
m.videoKey = next;
|
|
7107
|
-
}
|
|
7108
|
-
});
|
|
7109
|
-
const mic = m.micKey;
|
|
7110
|
-
if (mic && keep(mic))
|
|
7111
|
-
out.push({
|
|
7112
|
-
where: `media ${m.id} mic`,
|
|
7113
|
-
key: mic,
|
|
7114
|
-
set: (next) => {
|
|
7115
|
-
m.micKey = next;
|
|
7116
|
-
}
|
|
7117
|
-
});
|
|
7118
|
-
const cam = m.camKey;
|
|
7119
|
-
if (cam && keep(cam))
|
|
7120
|
-
out.push({
|
|
7121
|
-
where: `media ${m.id} cam`,
|
|
7122
|
-
key: cam,
|
|
7123
|
-
set: (next) => {
|
|
7124
|
-
m.camKey = next;
|
|
7125
|
-
}
|
|
7126
|
-
});
|
|
7127
|
-
}
|
|
7128
|
-
for (const clip of doc.overlays ?? []) {
|
|
7129
|
-
if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
|
|
7130
|
-
out.push({
|
|
7131
|
-
where: `overlay ${clip.id}`,
|
|
7132
|
-
key: clip.key,
|
|
7133
|
-
set: (next) => {
|
|
7134
|
-
clip.key = next;
|
|
7135
|
-
}
|
|
7136
|
-
});
|
|
7137
|
-
}
|
|
7138
|
-
const mark = doc.endCard?.mark;
|
|
7139
|
-
if (mark && keep(mark.key))
|
|
7140
|
-
out.push({
|
|
7141
|
-
where: "end card mark",
|
|
7142
|
-
key: mark.key,
|
|
7143
|
-
set: (next) => {
|
|
7144
|
-
mark.key = next;
|
|
7145
|
-
}
|
|
7146
|
-
});
|
|
7147
|
-
const bg = doc.frame.backgroundMedia;
|
|
7148
|
-
if (bg && keep(bg.key))
|
|
7149
|
-
out.push({
|
|
7150
|
-
where: "background",
|
|
7151
|
-
key: bg.key,
|
|
7152
|
-
set: (next) => {
|
|
7153
|
-
bg.key = next;
|
|
7154
|
-
}
|
|
7155
|
-
});
|
|
7156
|
-
return out;
|
|
7157
|
-
}
|
|
7158
|
-
function mediaContentType(file) {
|
|
7159
|
-
const ext = (/\.([a-z0-9]+)$/i.exec(file)?.[1] ?? "").toLowerCase();
|
|
7160
|
-
const types = {
|
|
7161
|
-
svg: "image/svg+xml",
|
|
7162
|
-
png: "image/png",
|
|
7163
|
-
jpg: "image/jpeg",
|
|
7164
|
-
jpeg: "image/jpeg",
|
|
7165
|
-
webp: "image/webp",
|
|
7166
|
-
gif: "image/gif",
|
|
7167
|
-
avif: "image/avif",
|
|
7168
|
-
webm: "video/webm",
|
|
7169
|
-
mp4: "video/mp4",
|
|
7170
|
-
mov: "video/quicktime",
|
|
7171
|
-
mp3: "audio/mpeg",
|
|
7172
|
-
wav: "audio/wav",
|
|
7173
|
-
ogg: "audio/ogg",
|
|
7174
|
-
m4a: "audio/mp4"
|
|
7175
|
-
};
|
|
7176
|
-
return types[ext] ?? "application/octet-stream";
|
|
7177
|
-
}
|
|
7178
|
-
function extensionFor(contentType) {
|
|
7179
|
-
const t = contentType.split(";")[0].trim().toLowerCase();
|
|
7180
|
-
const exts = {
|
|
7181
|
-
"image/svg+xml": ".svg",
|
|
7182
|
-
"image/png": ".png",
|
|
7183
|
-
"image/jpeg": ".jpg",
|
|
7184
|
-
"image/webp": ".webp",
|
|
7185
|
-
"image/gif": ".gif",
|
|
7186
|
-
"image/avif": ".avif",
|
|
7187
|
-
"video/webm": ".webm",
|
|
7188
|
-
"video/mp4": ".mp4",
|
|
7189
|
-
"video/quicktime": ".mov",
|
|
7190
|
-
"audio/mpeg": ".mp3",
|
|
7191
|
-
"audio/wav": ".wav",
|
|
7192
|
-
"audio/ogg": ".ogg",
|
|
7193
|
-
"audio/mp4": ".m4a"
|
|
7194
|
-
};
|
|
7195
|
-
return exts[t] ?? "";
|
|
7196
|
-
}
|
|
7197
|
-
function assetIdOf(url) {
|
|
7198
|
-
if (!url) return null;
|
|
7199
|
-
const m = /\/api\/assets\/([A-Za-z0-9_-]+)\/file/.exec(url);
|
|
7200
|
-
return m ? m[1] : null;
|
|
7201
|
-
}
|
|
7202
|
-
async function pullMedia(ctx, dir, doc, log) {
|
|
7203
|
-
const result = { downloaded: [], kept: [] };
|
|
7204
|
-
for (const { key, file } of KEYS) {
|
|
7205
|
-
const url = doc.source[key];
|
|
7206
|
-
const assetId = assetIdOf(url);
|
|
7207
|
-
if (!assetId) continue;
|
|
7208
|
-
const target = join14(dir, file);
|
|
7209
|
-
if (existsSync12(target)) {
|
|
7210
|
-
result.kept.push(file);
|
|
7211
|
-
} else {
|
|
7212
|
-
const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
|
|
7213
|
-
const res = await fetch(abs, {
|
|
7214
|
-
headers: ctx.key ? { authorization: `Bearer ${ctx.key}` } : {}
|
|
7215
|
-
});
|
|
7216
|
-
if (!res.ok || !res.body) {
|
|
7217
|
-
throw new Error(
|
|
7218
|
-
`download of ${file} failed (${res.status})${res.status === 404 ? " \u2014 a private recording needs a content key of its owner" : ""}`
|
|
7219
|
-
);
|
|
7220
|
-
}
|
|
7221
|
-
await pipeline(
|
|
7222
|
-
Readable.fromWeb(res.body),
|
|
7223
|
-
createWriteStream(target)
|
|
7224
|
-
);
|
|
7225
|
-
const bytes = (await import("fs/promises").then((fs) => fs.stat(target))).size;
|
|
7226
|
-
result.downloaded.push({ file, assetId, bytes });
|
|
7227
|
-
log(` ${file} \u2190 asset ${assetId} (${Math.round(bytes / 1024)} kB)`);
|
|
7228
|
-
}
|
|
7229
|
-
doc.source[key] = file;
|
|
7230
|
-
}
|
|
7231
|
-
for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {
|
|
7232
|
-
const assetId = assetIdOf(ref.key);
|
|
7233
|
-
const abs = /^https?:/.test(ref.key) ? ref.key : `${ctx.origin}${ref.key}`;
|
|
7234
|
-
const existing = existsSync12(join14(dir, "media")) ? (await import("fs/promises").then(
|
|
7235
|
-
(fs) => fs.readdir(join14(dir, "media"))
|
|
7236
|
-
)).find((f) => f.startsWith(`${assetId}.`)) : void 0;
|
|
7237
|
-
let file;
|
|
7238
|
-
if (existing) {
|
|
7239
|
-
file = `media/${existing}`;
|
|
7240
|
-
result.kept.push(file);
|
|
7241
|
-
} else {
|
|
7242
|
-
const res = await fetch(abs, {
|
|
7243
|
-
headers: ctx.key ? { authorization: `Bearer ${ctx.key}` } : {}
|
|
7244
|
-
});
|
|
7245
|
-
if (!res.ok || !res.body) {
|
|
7246
|
-
throw new Error(
|
|
7247
|
-
`download of ${ref.where} (${ref.key}) failed (${res.status})`
|
|
7248
|
-
);
|
|
7249
|
-
}
|
|
7250
|
-
const ext = extensionFor(res.headers.get("content-type") ?? "");
|
|
7251
|
-
file = `media/${assetId}${ext}`;
|
|
7252
|
-
const target = join14(dir, file);
|
|
7253
|
-
await import("fs/promises").then(
|
|
7254
|
-
(fs) => fs.mkdir(join14(dir, "media"), { recursive: true })
|
|
7255
|
-
);
|
|
7256
|
-
await pipeline(
|
|
7257
|
-
Readable.fromWeb(res.body),
|
|
7258
|
-
createWriteStream(target)
|
|
7259
|
-
);
|
|
7260
|
-
const bytes = (await import("fs/promises").then((fs) => fs.stat(target))).size;
|
|
7261
|
-
result.downloaded.push({ file, assetId, bytes });
|
|
7262
|
-
log(
|
|
7263
|
-
` ${file} \u2190 ${ref.where}, asset ${assetId} (${Math.round(bytes / 1024)} kB)`
|
|
7264
|
-
);
|
|
7265
|
-
}
|
|
7266
|
-
ref.set(file);
|
|
7267
|
-
}
|
|
7268
|
-
const metaPath = join14(dir, "meta.json");
|
|
7269
|
-
if (!existsSync12(metaPath)) await writeJson(metaPath, doc.source.meta, true);
|
|
7270
|
-
const cursorPath = join14(dir, "cursor.json");
|
|
7271
|
-
if (!existsSync12(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
|
|
7272
|
-
await writeFile9(join14(dir, "doc.json"), JSON.stringify(doc, null, 2));
|
|
7273
|
-
return result;
|
|
7274
|
-
}
|
|
7275
|
-
|
|
7276
7424
|
// src/plugin/folder.ts
|
|
7277
|
-
import { mkdir as
|
|
7425
|
+
import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
|
|
7278
7426
|
import { join as join15 } from "path";
|
|
7279
7427
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
7280
7428
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
@@ -7385,7 +7533,7 @@ async function cmdMove(argv) {
|
|
|
7385
7533
|
});
|
|
7386
7534
|
const key = requireCredential(strFlag(flags, "key"));
|
|
7387
7535
|
const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
|
|
7388
|
-
let
|
|
7536
|
+
let failed2 = 0;
|
|
7389
7537
|
const moved = [];
|
|
7390
7538
|
for (const raw of positionals) {
|
|
7391
7539
|
const id = parseVosId(raw);
|
|
@@ -7400,28 +7548,28 @@ async function cmdMove(argv) {
|
|
|
7400
7548
|
continue;
|
|
7401
7549
|
}
|
|
7402
7550
|
if (asVos.status === 404) {
|
|
7403
|
-
const
|
|
7551
|
+
const asAsset2 = await apiJson(origin, `/api/assets/${id}`, {
|
|
7404
7552
|
method: "PATCH",
|
|
7405
7553
|
key,
|
|
7406
7554
|
body: { folderId }
|
|
7407
7555
|
});
|
|
7408
|
-
if (
|
|
7556
|
+
if (asAsset2.status === 200) {
|
|
7409
7557
|
moved.push({ id, kind: "asset" });
|
|
7410
7558
|
r.log(`moved asset ${id}`);
|
|
7411
7559
|
continue;
|
|
7412
7560
|
}
|
|
7413
|
-
|
|
7414
|
-
r.log(apiError(`move ${id}`,
|
|
7561
|
+
failed2++;
|
|
7562
|
+
r.log(apiError(`move ${id}`, asAsset2));
|
|
7415
7563
|
continue;
|
|
7416
7564
|
}
|
|
7417
|
-
|
|
7565
|
+
failed2++;
|
|
7418
7566
|
r.log(apiError(`move ${id}`, asVos));
|
|
7419
7567
|
}
|
|
7420
7568
|
r.done(
|
|
7421
|
-
{ moved, failed, folderId },
|
|
7422
|
-
`moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${
|
|
7569
|
+
{ moved, failed: failed2, folderId },
|
|
7570
|
+
`moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${failed2 ? ` \u2014 ${failed2} failed` : ""}`
|
|
7423
7571
|
);
|
|
7424
|
-
return
|
|
7572
|
+
return failed2 ? EXIT_ERROR : EXIT_OK;
|
|
7425
7573
|
}
|
|
7426
7574
|
function recipeLine(filename, body) {
|
|
7427
7575
|
const h = recipeHints(body);
|
|
@@ -7451,7 +7599,7 @@ async function cmdPull(argv) {
|
|
|
7451
7599
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
7452
7600
|
const payload = res.body;
|
|
7453
7601
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
7454
|
-
await
|
|
7602
|
+
await mkdir8(join15(out, "recipes"), { recursive: true });
|
|
7455
7603
|
const lines = [];
|
|
7456
7604
|
const recipes = [];
|
|
7457
7605
|
for (const rec of payload.recipes) {
|
|
@@ -7461,7 +7609,7 @@ async function cmdPull(argv) {
|
|
|
7461
7609
|
}
|
|
7462
7610
|
for (const rec of payload.inheritedRecipes) {
|
|
7463
7611
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
7464
|
-
await
|
|
7612
|
+
await mkdir8(join15(out, "recipes", "_inherited", from), { recursive: true });
|
|
7465
7613
|
const path = join15(
|
|
7466
7614
|
out,
|
|
7467
7615
|
"recipes",
|
|
@@ -7478,7 +7626,7 @@ async function cmdPull(argv) {
|
|
|
7478
7626
|
const members = [];
|
|
7479
7627
|
for (const v of payload.voses) {
|
|
7480
7628
|
const dir = join15(out, "members", safeName(v.slug || v.title || v.id));
|
|
7481
|
-
await
|
|
7629
|
+
await mkdir8(dir, { recursive: true });
|
|
7482
7630
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
7483
7631
|
if (cfg.status !== 200) {
|
|
7484
7632
|
lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
|
|
@@ -7567,6 +7715,144 @@ async function cmdFolder(argv) {
|
|
|
7567
7715
|
}
|
|
7568
7716
|
}
|
|
7569
7717
|
|
|
7718
|
+
// src/plugin/uploadAsset.ts
|
|
7719
|
+
var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
|
|
7720
|
+
var PART_ATTEMPTS = 3;
|
|
7721
|
+
function planParts(totalBytes, partBytes) {
|
|
7722
|
+
if (totalBytes <= 0 || partBytes <= 0) return [];
|
|
7723
|
+
const parts = [];
|
|
7724
|
+
for (let start = 0; start < totalBytes; start += partBytes) {
|
|
7725
|
+
parts.push({
|
|
7726
|
+
partNumber: parts.length + 1,
|
|
7727
|
+
start,
|
|
7728
|
+
end: Math.min(start + partBytes, totalBytes)
|
|
7729
|
+
});
|
|
7730
|
+
}
|
|
7731
|
+
return parts;
|
|
7732
|
+
}
|
|
7733
|
+
function failed(what, r) {
|
|
7734
|
+
const detail = typeof r.body.error === "string" ? r.body.error : "";
|
|
7735
|
+
return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
|
|
7736
|
+
}
|
|
7737
|
+
function asAsset(body) {
|
|
7738
|
+
return {
|
|
7739
|
+
id: String(body.id),
|
|
7740
|
+
url: String(body.url),
|
|
7741
|
+
size: typeof body.size === "number" ? body.size : 0,
|
|
7742
|
+
reused: body.reused === true
|
|
7743
|
+
};
|
|
7744
|
+
}
|
|
7745
|
+
function transient(status) {
|
|
7746
|
+
return status >= 500 || status === 429;
|
|
7747
|
+
}
|
|
7748
|
+
var wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7749
|
+
async function uploadAsset(target, bytes, opts) {
|
|
7750
|
+
return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
|
|
7751
|
+
}
|
|
7752
|
+
async function uploadWhole(target, bytes, opts) {
|
|
7753
|
+
const res = await apiJson(target.origin, "/api/assets/recording", {
|
|
7754
|
+
method: "POST",
|
|
7755
|
+
key: target.key,
|
|
7756
|
+
headers: {
|
|
7757
|
+
"Content-Type": opts.contentType,
|
|
7758
|
+
"Content-Length": String(bytes.length),
|
|
7759
|
+
"X-Filename": opts.filename,
|
|
7760
|
+
"X-Content-Hash": opts.contentHash,
|
|
7761
|
+
...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {}
|
|
7762
|
+
},
|
|
7763
|
+
raw: bytes
|
|
7764
|
+
});
|
|
7765
|
+
if (res.status !== 201 && res.status !== 200)
|
|
7766
|
+
throw failed("upload failed", res);
|
|
7767
|
+
return asAsset(res.body);
|
|
7768
|
+
}
|
|
7769
|
+
async function uploadInParts(target, bytes, opts) {
|
|
7770
|
+
const begun = await apiJson(
|
|
7771
|
+
target.origin,
|
|
7772
|
+
"/api/assets/recording/multipart",
|
|
7773
|
+
{
|
|
7774
|
+
method: "POST",
|
|
7775
|
+
key: target.key,
|
|
7776
|
+
body: {
|
|
7777
|
+
filename: opts.filename,
|
|
7778
|
+
contentType: opts.contentType,
|
|
7779
|
+
size: bytes.length,
|
|
7780
|
+
contentHash: opts.contentHash,
|
|
7781
|
+
...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
);
|
|
7785
|
+
if (begun.status !== 201 && begun.status !== 200) {
|
|
7786
|
+
throw failed("upload failed", begun);
|
|
7787
|
+
}
|
|
7788
|
+
if (begun.body.reused === true || typeof begun.body.url === "string") {
|
|
7789
|
+
return asAsset(begun.body);
|
|
7790
|
+
}
|
|
7791
|
+
const uploadId = String(begun.body.id);
|
|
7792
|
+
const partBytes = Number(begun.body.partBytes);
|
|
7793
|
+
if (!Number.isFinite(partBytes) || partBytes <= 0) {
|
|
7794
|
+
throw new Error("upload failed: the platform returned no part size");
|
|
7795
|
+
}
|
|
7796
|
+
const parts = planParts(bytes.length, partBytes);
|
|
7797
|
+
const etags = [];
|
|
7798
|
+
try {
|
|
7799
|
+
for (const part of parts) {
|
|
7800
|
+
const chunk2 = bytes.subarray(part.start, part.end);
|
|
7801
|
+
let sent = null;
|
|
7802
|
+
for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
|
|
7803
|
+
try {
|
|
7804
|
+
sent = await apiJson(
|
|
7805
|
+
target.origin,
|
|
7806
|
+
`/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
|
|
7807
|
+
{
|
|
7808
|
+
method: "PUT",
|
|
7809
|
+
key: target.key,
|
|
7810
|
+
headers: {
|
|
7811
|
+
"Content-Type": "application/octet-stream",
|
|
7812
|
+
"Content-Length": String(chunk2.length)
|
|
7813
|
+
},
|
|
7814
|
+
raw: chunk2
|
|
7815
|
+
}
|
|
7816
|
+
);
|
|
7817
|
+
} catch (networkError) {
|
|
7818
|
+
if (attempt === PART_ATTEMPTS) throw networkError;
|
|
7819
|
+
await wait(500 * 2 ** (attempt - 1));
|
|
7820
|
+
continue;
|
|
7821
|
+
}
|
|
7822
|
+
if (sent.status === 200) break;
|
|
7823
|
+
if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
|
|
7824
|
+
throw failed(`part ${part.partNumber} failed`, sent);
|
|
7825
|
+
}
|
|
7826
|
+
await wait(500 * 2 ** (attempt - 1));
|
|
7827
|
+
}
|
|
7828
|
+
if (!sent || sent.status !== 200) {
|
|
7829
|
+
throw new Error(`part ${part.partNumber} failed`);
|
|
7830
|
+
}
|
|
7831
|
+
etags.push({
|
|
7832
|
+
partNumber: Number(sent.body.partNumber),
|
|
7833
|
+
etag: String(sent.body.etag)
|
|
7834
|
+
});
|
|
7835
|
+
opts.onPart?.(etags.length, parts.length);
|
|
7836
|
+
}
|
|
7837
|
+
const sealed = await apiJson(
|
|
7838
|
+
target.origin,
|
|
7839
|
+
`/api/assets/recording/multipart/${uploadId}/complete`,
|
|
7840
|
+
{ method: "POST", key: target.key, body: { parts: etags } }
|
|
7841
|
+
);
|
|
7842
|
+
if (sealed.status !== 201 && sealed.status !== 200) {
|
|
7843
|
+
throw failed("upload failed", sealed);
|
|
7844
|
+
}
|
|
7845
|
+
return asAsset(sealed.body);
|
|
7846
|
+
} catch (err) {
|
|
7847
|
+
await apiJson(
|
|
7848
|
+
target.origin,
|
|
7849
|
+
`/api/assets/recording/multipart/${uploadId}`,
|
|
7850
|
+
{ method: "DELETE", key: target.key }
|
|
7851
|
+
).catch(() => void 0);
|
|
7852
|
+
throw err;
|
|
7853
|
+
}
|
|
7854
|
+
}
|
|
7855
|
+
|
|
7570
7856
|
// src/plugin/sync.ts
|
|
7571
7857
|
function apiContext(flags) {
|
|
7572
7858
|
return {
|
|
@@ -7615,7 +7901,9 @@ async function pushTake(dir, flags, r) {
|
|
|
7615
7901
|
${lint.problems.join("\n ")}`);
|
|
7616
7902
|
}
|
|
7617
7903
|
if (!existsSync13(take.paths.recording)) {
|
|
7618
|
-
throw new Error(
|
|
7904
|
+
throw new Error(
|
|
7905
|
+
"no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
|
|
7906
|
+
);
|
|
7619
7907
|
}
|
|
7620
7908
|
const ctx = apiContext(flags);
|
|
7621
7909
|
const state = readSyncState(dir);
|
|
@@ -7648,32 +7936,36 @@ async function pushTake(dir, flags, r) {
|
|
|
7648
7936
|
}
|
|
7649
7937
|
const bytes = await readFile13(take.paths.recording);
|
|
7650
7938
|
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
7939
|
+
const { type: contentType } = resolveMediaType({
|
|
7940
|
+
head: bytes.subarray(0, MEDIA_HEAD_BYTES),
|
|
7941
|
+
filename: take.paths.recordingName
|
|
7942
|
+
});
|
|
7943
|
+
const filename = nameForType(take.paths.recordingName, contentType);
|
|
7944
|
+
if (filename !== take.paths.recordingName) {
|
|
7945
|
+
r.log(
|
|
7946
|
+
` ${take.paths.recordingName} holds ${contentType} \u2014 uploading it as ${filename}`
|
|
7947
|
+
);
|
|
7948
|
+
}
|
|
7651
7949
|
r.log(`uploading recording (${Math.round(bytes.length / 1024)} kB)\u2026`);
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
"X-Content-Hash": hash,
|
|
7950
|
+
let upload;
|
|
7951
|
+
try {
|
|
7952
|
+
upload = await uploadAsset(ctx, new Uint8Array(bytes), {
|
|
7953
|
+
filename,
|
|
7954
|
+
contentType,
|
|
7955
|
+
contentHash: hash,
|
|
7659
7956
|
// The take's length so the server holds it to the plan's cap.
|
|
7660
|
-
...take.meta.durationMs > 0 ? {
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
})
|
|
7664
|
-
if (upload.status !== 201 && upload.status !== 200) {
|
|
7957
|
+
...take.meta.durationMs > 0 ? { durationSeconds: take.meta.durationMs / 1e3 } : {},
|
|
7958
|
+
onPart: (done, total) => r.log(` part ${done}/${total}`)
|
|
7959
|
+
});
|
|
7960
|
+
} catch (e) {
|
|
7665
7961
|
throw new Error(
|
|
7666
|
-
`recording upload failed
|
|
7962
|
+
`recording upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
7667
7963
|
);
|
|
7668
7964
|
}
|
|
7669
|
-
const assetId =
|
|
7670
|
-
const assetUrl =
|
|
7671
|
-
r.event({
|
|
7672
|
-
|
|
7673
|
-
assetId,
|
|
7674
|
-
reused: upload.json.reused === true
|
|
7675
|
-
});
|
|
7676
|
-
if (upload.json.reused === true) r.log(" recording already hosted \u2014 reused");
|
|
7965
|
+
const assetId = upload.id;
|
|
7966
|
+
const assetUrl = upload.url;
|
|
7967
|
+
r.event({ event: "recording", assetId, reused: upload.reused });
|
|
7968
|
+
if (upload.reused) r.log(" recording already hosted \u2014 reused");
|
|
7677
7969
|
const docForPush = structuredClone(take.doc);
|
|
7678
7970
|
docForPush.source.videoKey = assetUrl;
|
|
7679
7971
|
if (docForPush.source.camKey && !/^(https?:)?\//.test(docForPush.source.camKey)) {
|
|
@@ -7692,31 +7984,35 @@ async function pushTake(dir, flags, r) {
|
|
|
7692
7984
|
}
|
|
7693
7985
|
const media = await readFile13(file);
|
|
7694
7986
|
const mediaHash = createHash("sha256").update(media).digest("hex");
|
|
7695
|
-
const type =
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
headers: {
|
|
7699
|
-
"Content-Type": type,
|
|
7700
|
-
"Content-Length": String(media.length),
|
|
7701
|
-
"X-Filename": basename(file),
|
|
7702
|
-
"X-Content-Hash": mediaHash
|
|
7703
|
-
},
|
|
7704
|
-
raw: new Uint8Array(media)
|
|
7987
|
+
const { type } = resolveMediaType({
|
|
7988
|
+
head: media.subarray(0, MEDIA_HEAD_BYTES),
|
|
7989
|
+
filename: file
|
|
7705
7990
|
});
|
|
7706
|
-
|
|
7991
|
+
const name = nameForType(basename3(file), type);
|
|
7992
|
+
if (name !== basename3(file)) {
|
|
7993
|
+
r.log(` ${ref.where}: ${basename3(file)} holds ${type} \u2014 sent as ${name}`);
|
|
7994
|
+
}
|
|
7995
|
+
let put;
|
|
7996
|
+
try {
|
|
7997
|
+
put = await uploadAsset(ctx, new Uint8Array(media), {
|
|
7998
|
+
filename: name,
|
|
7999
|
+
contentType: type,
|
|
8000
|
+
contentHash: mediaHash
|
|
8001
|
+
});
|
|
8002
|
+
} catch (e) {
|
|
7707
8003
|
throw new Error(
|
|
7708
|
-
`${ref.where} upload failed
|
|
8004
|
+
`${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
7709
8005
|
);
|
|
7710
8006
|
}
|
|
7711
|
-
ref.set(
|
|
8007
|
+
ref.set(put.url);
|
|
7712
8008
|
r.log(
|
|
7713
|
-
` ${ref.where}: ${ref.key} \u2192 asset ${
|
|
8009
|
+
` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
|
|
7714
8010
|
);
|
|
7715
8011
|
}
|
|
7716
8012
|
const lowered = lowerToComposition3(docForPush);
|
|
7717
8013
|
const config = { ...lowered.config, data: lowered.data };
|
|
7718
8014
|
if (!state) {
|
|
7719
|
-
const title = flags.title ?? `Take ${
|
|
8015
|
+
const title = flags.title ?? `Take ${basename3(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
7720
8016
|
const slug = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "take"}-${Date.now().toString(36)}`;
|
|
7721
8017
|
const created = await api(ctx, "/vos", {
|
|
7722
8018
|
method: "POST",
|
|
@@ -7888,8 +8184,9 @@ async function pullTake(dir, flags, r) {
|
|
|
7888
8184
|
}
|
|
7889
8185
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
7890
8186
|
const doc = hostedDoc;
|
|
7891
|
-
|
|
7892
|
-
|
|
8187
|
+
const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
8188
|
+
if (existsSync13(localRecording)) {
|
|
8189
|
+
doc.source.videoKey = basename3(localRecording);
|
|
7893
8190
|
}
|
|
7894
8191
|
await writeJson(join16(dir, "doc.json"), doc, true);
|
|
7895
8192
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
@@ -7911,9 +8208,9 @@ async function pullTake(dir, flags, r) {
|
|
|
7911
8208
|
|
|
7912
8209
|
// src/plugin/program.ts
|
|
7913
8210
|
import { existsSync as existsSync14 } from "fs";
|
|
7914
|
-
import { mkdir as
|
|
8211
|
+
import { mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
7915
8212
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7916
|
-
import { basename as
|
|
8213
|
+
import { basename as basename4, dirname as dirname4, join as join17 } from "path";
|
|
7917
8214
|
import {
|
|
7918
8215
|
CURRENT_CONFIG_VERSION,
|
|
7919
8216
|
migrateConfig,
|
|
@@ -8110,7 +8407,7 @@ async function cmdFetch(argv) {
|
|
|
8110
8407
|
throw new Error(apiError(`fetch config for ${vosId}`, cfg));
|
|
8111
8408
|
const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
|
|
8112
8409
|
const out = strFlag(flags, "out") ?? slug;
|
|
8113
|
-
await
|
|
8410
|
+
await mkdir9(out, { recursive: true });
|
|
8114
8411
|
await writeFile11(
|
|
8115
8412
|
join17(out, "config.json"),
|
|
8116
8413
|
JSON.stringify(cfg.body.config, null, 2)
|
|
@@ -8254,7 +8551,7 @@ async function cmdPushProgram(argv) {
|
|
|
8254
8551
|
origin: strFlag(flags, "origin"),
|
|
8255
8552
|
api: strFlag(flags, "api")
|
|
8256
8553
|
});
|
|
8257
|
-
const dir =
|
|
8554
|
+
const dir = dirname4(source);
|
|
8258
8555
|
const parsed = JSON.parse(await readFile14(source, "utf8"));
|
|
8259
8556
|
const pre = preflightConfig(parsed);
|
|
8260
8557
|
if (!pre.ok || !pre.config) {
|
|
@@ -8282,7 +8579,7 @@ async function cmdPushProgram(argv) {
|
|
|
8282
8579
|
"--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
|
|
8283
8580
|
);
|
|
8284
8581
|
}
|
|
8285
|
-
const fallback = state?.title ? `${state.title} remix` :
|
|
8582
|
+
const fallback = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos program";
|
|
8286
8583
|
const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
|
|
8287
8584
|
const body = { title: title2, config };
|
|
8288
8585
|
const slug = strFlag(flags, "slug");
|
|
@@ -8380,7 +8677,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
|
|
|
8380
8677
|
return EXIT_OK;
|
|
8381
8678
|
}
|
|
8382
8679
|
const remixOfId = strFlag(flags, "remix-of") ?? state?.vosId;
|
|
8383
|
-
const fallbackTitle = state?.title ? `${state.title} remix` :
|
|
8680
|
+
const fallbackTitle = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos remix";
|
|
8384
8681
|
const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
|
|
8385
8682
|
const folderId = folderRef ? resolveFolder(await listFolders(origin, key), folderRef).id : void 0;
|
|
8386
8683
|
const created = await createProgramVos({
|
|
@@ -8432,7 +8729,7 @@ Iterate with: vos push ${source} --vos ${created.id}`
|
|
|
8432
8729
|
async function cmdPullProgram(argv) {
|
|
8433
8730
|
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS2);
|
|
8434
8731
|
const target = positionals[0] ?? ".";
|
|
8435
|
-
const dir = target.endsWith(".json") ?
|
|
8732
|
+
const dir = target.endsWith(".json") ? dirname4(target) : target;
|
|
8436
8733
|
const r = createReporter(flags.json === true);
|
|
8437
8734
|
const origin = platformOrigin({
|
|
8438
8735
|
origin: strFlag(flags, "origin"),
|
|
@@ -8615,7 +8912,7 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
8615
8912
|
|
|
8616
8913
|
// src/plugin/asset.ts
|
|
8617
8914
|
import { readFileSync as readFileSync4 } from "fs";
|
|
8618
|
-
import { basename as
|
|
8915
|
+
import { basename as basename5, extname as extname2 } from "path";
|
|
8619
8916
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
8620
8917
|
var MIME_BY_EXT = {
|
|
8621
8918
|
".png": "image/png",
|
|
@@ -8666,7 +8963,7 @@ async function cmdPush(argv) {
|
|
|
8666
8963
|
const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
|
|
8667
8964
|
const uploaded = [];
|
|
8668
8965
|
for (const file of positionals) {
|
|
8669
|
-
const name =
|
|
8966
|
+
const name = basename5(file);
|
|
8670
8967
|
const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
|
|
8671
8968
|
const body = readFileSync4(file);
|
|
8672
8969
|
const form = new FormData();
|
|
@@ -8723,11 +9020,11 @@ async function cmdAsset(argv) {
|
|
|
8723
9020
|
|
|
8724
9021
|
// src/plugin/recipe.ts
|
|
8725
9022
|
import { readFileSync as readFileSync5 } from "fs";
|
|
8726
|
-
import { basename as
|
|
9023
|
+
import { basename as basename6 } from "path";
|
|
8727
9024
|
var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
8728
9025
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
8729
9026
|
function recipeFilename(file) {
|
|
8730
|
-
const name =
|
|
9027
|
+
const name = basename6(file);
|
|
8731
9028
|
if (!/\.md$/i.test(name)) {
|
|
8732
9029
|
throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
|
|
8733
9030
|
}
|
|
@@ -10033,7 +10330,7 @@ async function cmdRecord(argv) {
|
|
|
10033
10330
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
10034
10331
|
);
|
|
10035
10332
|
}
|
|
10036
|
-
await
|
|
10333
|
+
await mkdir10(outDir, { recursive: true });
|
|
10037
10334
|
const paths = await ensureTakeDir(outDir);
|
|
10038
10335
|
const browser = await launchBrowser();
|
|
10039
10336
|
try {
|
|
@@ -10117,7 +10414,7 @@ async function cmdCreate2(argv) {
|
|
|
10117
10414
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
10118
10415
|
);
|
|
10119
10416
|
}
|
|
10120
|
-
await
|
|
10417
|
+
await mkdir10(outDir, { recursive: true });
|
|
10121
10418
|
const paths = await ensureTakeDir(outDir);
|
|
10122
10419
|
const browser = await launchBrowser();
|
|
10123
10420
|
try {
|
|
@@ -10202,7 +10499,7 @@ async function cmdPlan(argv) {
|
|
|
10202
10499
|
);
|
|
10203
10500
|
const r = createReporter(flags.json === true);
|
|
10204
10501
|
if (flags.fresh === true) {
|
|
10205
|
-
await
|
|
10502
|
+
await rm5(join18(dir, "doc.json"), { force: true });
|
|
10206
10503
|
r.log("note: --fresh discarded the existing doc.json");
|
|
10207
10504
|
}
|
|
10208
10505
|
let reuse;
|
|
@@ -10766,7 +11063,7 @@ async function cmdOpen(argv) {
|
|
|
10766
11063
|
const r = createReporter(flags.json === true);
|
|
10767
11064
|
const take = await loadTake(dir);
|
|
10768
11065
|
if (!existsSync15(take.paths.recording)) {
|
|
10769
|
-
throw new UsageError(`${dir} has no recording
|
|
11066
|
+
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
10770
11067
|
}
|
|
10771
11068
|
const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
|
|
10772
11069
|
/\/+$/,
|
|
@@ -11003,8 +11300,15 @@ async function cmdValidate(argv) {
|
|
|
11003
11300
|
const take = await loadTake(target);
|
|
11004
11301
|
const problems = [];
|
|
11005
11302
|
const warnings = [];
|
|
11006
|
-
if (!existsSync15(take.paths.recording))
|
|
11007
|
-
problems.push("missing recording
|
|
11303
|
+
if (!existsSync15(take.paths.recording)) {
|
|
11304
|
+
problems.push("missing recording (re-run record)");
|
|
11305
|
+
} else {
|
|
11306
|
+
const mismatch = containerMismatch(
|
|
11307
|
+
take.paths.recordingName,
|
|
11308
|
+
await readMediaHead(take.paths.recording)
|
|
11309
|
+
);
|
|
11310
|
+
if (mismatch) warnings.push(mismatch);
|
|
11311
|
+
}
|
|
11008
11312
|
if (!take.doc) problems.push("missing doc.json (run plan)");
|
|
11009
11313
|
if (take.doc) {
|
|
11010
11314
|
const lint = lintDoc(take.doc);
|
|
@@ -11224,6 +11528,7 @@ export {
|
|
|
11224
11528
|
RECORDING_NAME,
|
|
11225
11529
|
takePaths,
|
|
11226
11530
|
loadTake,
|
|
11531
|
+
pullMedia,
|
|
11227
11532
|
startTakeServer,
|
|
11228
11533
|
waitForPageDone,
|
|
11229
11534
|
renderAnimation,
|
|
@@ -11240,10 +11545,9 @@ export {
|
|
|
11240
11545
|
recordTake,
|
|
11241
11546
|
encodeRecording,
|
|
11242
11547
|
planTake,
|
|
11243
|
-
pullMedia,
|
|
11244
11548
|
parseAgentBrowserLog,
|
|
11245
11549
|
splitCommand,
|
|
11246
11550
|
convertAgentBrowser,
|
|
11247
11551
|
run
|
|
11248
11552
|
};
|
|
11249
|
-
//# sourceMappingURL=chunk-
|
|
11553
|
+
//# sourceMappingURL=chunk-DD6OXVFJ.js.map
|