mirror of
https://github.com/block/buzz.git
synced 2026-11-03 16:01:35 +00:00
176 lines
4.4 KiB
JavaScript
176 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
import { spawn } from "node:child_process";
|
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import process from "node:process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const ARTIFACTORY_BASE =
|
|
"https://global.block-artifacts.com/artifactory/goose-internal/avatars";
|
|
const LATEST_URL = `${ARTIFACTORY_BASE}/latest.json`;
|
|
|
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
const desktopRoot = dirname(scriptDir);
|
|
const outputRoot = join(desktopRoot, "src/shared/assets/goose-avatars");
|
|
const catalogPath = join(outputRoot, "catalog.json");
|
|
|
|
const FORMATS = ["webm", "hevc"];
|
|
|
|
function variantOutputPath(asset, format) {
|
|
const extension = format === "hevc" ? "mp4" : "webm";
|
|
return join(
|
|
outputRoot,
|
|
format,
|
|
asset.collectionId,
|
|
`${asset.id}.${extension}`,
|
|
);
|
|
}
|
|
|
|
function posterOutputPath(asset) {
|
|
return join(outputRoot, "posters", asset.collectionId, `${asset.id}.png`);
|
|
}
|
|
|
|
async function fetchJson(url) {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch ${url}: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
async function fileExistsWithSize(path, byteSize) {
|
|
try {
|
|
const info = await stat(path);
|
|
return info.size === byteSize;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function downloadFile(url, path, byteSize) {
|
|
if (await fileExistsWithSize(path, byteSize)) {
|
|
return "skipped";
|
|
}
|
|
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to download ${url}: ${response.status}`);
|
|
}
|
|
|
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
if (bytes.byteLength !== byteSize) {
|
|
throw new Error(
|
|
`Downloaded ${url} with ${bytes.byteLength} bytes, expected ${byteSize}.`,
|
|
);
|
|
}
|
|
|
|
await mkdir(dirname(path), { recursive: true });
|
|
await writeFile(path, bytes);
|
|
return "downloaded";
|
|
}
|
|
|
|
async function runFfmpeg(args) {
|
|
await new Promise((resolve, reject) => {
|
|
const child = spawn("ffmpeg", args, {
|
|
stdio: ["ignore", "ignore", "pipe"],
|
|
});
|
|
let stderr = "";
|
|
child.stderr.on("data", (chunk) => {
|
|
stderr += chunk.toString();
|
|
});
|
|
child.on("error", reject);
|
|
child.on("close", (code) => {
|
|
if (code === 0) {
|
|
resolve();
|
|
} else {
|
|
reject(
|
|
new Error(
|
|
`ffmpeg exited with ${code}: ${stderr.trim() || "no stderr"}`,
|
|
),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async function ensurePoster(asset) {
|
|
const posterPath = posterOutputPath(asset);
|
|
try {
|
|
await stat(posterPath);
|
|
return "skipped";
|
|
} catch {
|
|
// Generate it below.
|
|
}
|
|
|
|
await mkdir(dirname(posterPath), { recursive: true });
|
|
await runFfmpeg([
|
|
"-hide_banner",
|
|
"-loglevel",
|
|
"error",
|
|
"-y",
|
|
"-i",
|
|
variantOutputPath(asset, "webm"),
|
|
"-frames:v",
|
|
"1",
|
|
posterPath,
|
|
]);
|
|
return "generated";
|
|
}
|
|
|
|
async function main() {
|
|
const latest = await fetchJson(LATEST_URL);
|
|
const manifest = await fetchJson(
|
|
`${ARTIFACTORY_BASE}/${latest.manifestPath}`,
|
|
);
|
|
const versionRoot = `${ARTIFACTORY_BASE}/${manifest.catalogVersion}`;
|
|
|
|
await mkdir(outputRoot, { recursive: true });
|
|
await writeFile(catalogPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
|
|
const totals = {
|
|
downloaded: 0,
|
|
skipped: 0,
|
|
postersGenerated: 0,
|
|
postersSkipped: 0,
|
|
};
|
|
|
|
for (const [index, asset] of manifest.assets.entries()) {
|
|
for (const format of FORMATS) {
|
|
const variant = asset.variants[format];
|
|
const sourceUrl = `${versionRoot}/${variant.path}`;
|
|
const result = await downloadFile(
|
|
sourceUrl,
|
|
variantOutputPath(asset, format),
|
|
variant.byteSize,
|
|
);
|
|
totals[result] += 1;
|
|
}
|
|
|
|
const posterResult = await ensurePoster(asset);
|
|
if (posterResult === "generated") {
|
|
totals.postersGenerated += 1;
|
|
} else {
|
|
totals.postersSkipped += 1;
|
|
}
|
|
|
|
const completed = index + 1;
|
|
if (completed % 5 === 0 || completed === manifest.assets.length) {
|
|
console.log(
|
|
`Synced ${completed}/${manifest.assets.length} Goose avatars...`,
|
|
);
|
|
}
|
|
}
|
|
|
|
const catalogBytes = await readFile(catalogPath, "utf8");
|
|
JSON.parse(catalogBytes);
|
|
|
|
console.log(
|
|
`Done. Downloaded ${totals.downloaded}, skipped ${totals.skipped}, generated ${totals.postersGenerated} posters, skipped ${totals.postersSkipped} posters.`,
|
|
);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|