ivan-94_obsidian-plugin-man.../scripts/fetch-visual-assets.mjs
2026-07-17 22:42:41 +08:00

68 lines
5.1 KiB
JavaScript

import { createHash } from "node:crypto";
import { createReadStream, createWriteStream } from "node:fs";
import { mkdir, rename, rm, stat } from "node:fs/promises";
import path from "node:path";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const ROOT = path.resolve(import.meta.dirname, "..");
const files = [
["assets-source/materials/oak_veneer_01/runtime/oak_veneer_01_diff_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/oak_veneer_01/oak_veneer_01_diff_1k.jpg", "39a99df4e328d62d74f3122722a64a95", 641756],
["assets-source/materials/oak_veneer_01/runtime/oak_veneer_01_nor_gl_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/oak_veneer_01/oak_veneer_01_nor_gl_1k.jpg", "d05406f5fdaf51f0d7b115b35d9af1b7", 484844],
["assets-source/materials/oak_veneer_01/runtime/oak_veneer_01_rough_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/oak_veneer_01/oak_veneer_01_rough_1k.jpg", "a19b44176a078b9ae93314133848c9aa", 865194],
["assets-source/materials/oak_veneer_01/runtime/oak_veneer_01_ao_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/oak_veneer_01/oak_veneer_01_ao_1k.jpg", "5b4c6d4315985713cc2270c8573da06c", 437549],
["assets-source/materials/book_pattern/runtime/book_pattern_col1_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/book_pattern/book_pattern_col1_1k.jpg", "4f9551b538201ee44ba2f4d5c9492110", 357132],
["assets-source/materials/book_pattern/runtime/book_pattern_col2_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/book_pattern/book_pattern_col2_1k.jpg", "a33c580aecf01d66322856ea420dfb6a", 354942],
["assets-source/materials/book_pattern/runtime/book_pattern_page_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/book_pattern/book_pattern_page_1k.jpg", "c64eef38d6aa652f69fa764679264680", 130916],
["assets-source/materials/book_pattern/runtime/book_pattern_nor_gl_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/book_pattern/book_pattern_nor_gl_1k.jpg", "4a7ebf8047690aa26fbd3f451a82fd81", 178809],
["assets-source/materials/book_pattern/runtime/book_pattern_rough_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/book_pattern/book_pattern_rough_1k.jpg", "e73a3decc3fc793db0dbe9671fa434f3", 554891],
["assets-source/materials/book_pattern/runtime/book_pattern_ao_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/book_pattern/book_pattern_ao_1k.jpg", "15ec5cbdd8aabc7607582c5d1a871b99", 380439],
["assets-source/materials/brown_leather/runtime/brown_leather_albedo_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/brown_leather/brown_leather_albedo_1k.jpg", "080b3bf1372766ff2c7ff121ed07b5e8", 503348],
["assets-source/materials/brown_leather/runtime/brown_leather_nor_gl_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/brown_leather/brown_leather_nor_gl_1k.jpg", "b63ae0121a415c23d74eed0b07fc6065", 863280],
["assets-source/materials/brown_leather/runtime/brown_leather_rough_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/brown_leather/brown_leather_rough_1k.jpg", "0f3a0148c8d2d25fbeca67e64e79bca0", 686142],
["assets-source/materials/brown_leather/runtime/brown_leather_ao_1k.jpg", "https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/brown_leather/brown_leather_ao_1k.jpg", "1852002bfccee92642945b43f713fe54", 421319],
["assets-source/environment/brown_photostudio_04/runtime/brown_photostudio_04_1k.hdr", "https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/brown_photostudio_04_1k.hdr", "792fd5d91f9df0ece2c6fa6fe5b9bbb8", 1637479],
].map(([filePath, url, md5, size]) => ({ filePath, url, md5, size }));
async function digest(filePath, algorithm) {
const hash = createHash(algorithm);
for await (const chunk of createReadStream(filePath)) hash.update(chunk);
return hash.digest("hex");
}
async function isValid(file) {
const destination = path.join(ROOT, file.filePath);
try {
const metadata = await stat(destination);
return metadata.size === file.size && (await digest(destination, "md5")) === file.md5;
} catch {
return false;
}
}
async function download(file) {
const destination = path.join(ROOT, file.filePath);
if (await isValid(file)) {
console.log(`verified ${file.filePath}`);
return;
}
await mkdir(path.dirname(destination), { recursive: true });
const partial = `${destination}.partial`;
await rm(partial, { force: true });
const response = await fetch(file.url, { redirect: "follow" });
if (!response.ok || !response.body) throw new Error(`Failed to download ${file.url}: ${response.status}`);
await pipeline(Readable.fromWeb(response.body), createWriteStream(partial));
const metadata = await stat(partial);
const checksum = await digest(partial, "md5");
if (metadata.size !== file.size || checksum !== file.md5) {
await rm(partial, { force: true });
throw new Error(`Integrity check failed for ${file.filePath}`);
}
await rename(partial, destination);
console.log(`downloaded ${file.filePath}`);
}
for (const file of files) await download(file);