149 lines
4.3 KiB
TypeScript
149 lines
4.3 KiB
TypeScript
// 2025-11-07-import-thumbnails.js
|
|
|
|
import PocketBase from 'pocketbase';
|
|
import { readFileSync } from 'node:fs';
|
|
import { basename, join } from 'node:path';
|
|
import spawn from 'nano-spawn';
|
|
import { tmpdir } from 'node:os';
|
|
import mime from 'mime';
|
|
|
|
const pb = new PocketBase(process.env.POCKETBASE_URL || 'http://127.0.0.1:8090');
|
|
if (!process.env.POCKETBASE_USERNAME) throw new Error('POCKETBASE_USERNAME missing');
|
|
if (!process.env.POCKETBASE_PASSWORD) throw new Error('POCKETBASE_PASSWORD missing');
|
|
if (!process.env.B2_BUCKET_FROM) throw new Error('B2_BUCKET_FROM missing');
|
|
if (!process.env.V1_DATA_FILE) throw new Error('V1_DATA_FILE missing');
|
|
|
|
|
|
interface ManifestItem {
|
|
thumbnail_url: string;
|
|
thumbnail_key: string;
|
|
tmp_file: string;
|
|
vod_id: string;
|
|
}
|
|
|
|
type Manifest = ManifestItem[];
|
|
|
|
interface Vod {
|
|
id: string;
|
|
thumbnail: string;
|
|
streamDate: string;
|
|
}
|
|
|
|
const vodsCollectionName = 'pbc_144770472';
|
|
// pbc_144770472 is the vods collection
|
|
// cv6m31vj98gmtsx is a sample vod id
|
|
|
|
|
|
async function main() {
|
|
console.log('Authenticating with PocketBase...');
|
|
await pb
|
|
.collection("_superusers")
|
|
.authWithPassword(process.env.POCKETBASE_USERNAME!, process.env.POCKETBASE_PASSWORD!);
|
|
|
|
|
|
// Use vod.streamDate to find the correct entry
|
|
// load v1 datafile
|
|
const v1VodDataRaw = readFileSync(process.env.V1_DATA_FILE!, { encoding: 'utf-8' });
|
|
const v1VodData = JSON.parse(v1VodDataRaw).map((vod: any) => vod.data);
|
|
console.log('v1VodData sample', v1VodData[0])
|
|
|
|
|
|
|
|
// # BUILD A MANIFEST
|
|
let manifest: Manifest = [];
|
|
const vods = await pb.collection('vods').getFullList();
|
|
for (const vod of vods) {
|
|
// console.log('v2VodData sample', vod)
|
|
// console.log(`for this vod`, vod)
|
|
const v1Vod = v1VodData.find((vod1: Vod) => new Date(vod1.streamDate).getTime() === new Date(vod.streamDate).getTime());
|
|
console.log(`v1vod`, v1Vod);
|
|
if (!v1Vod) {
|
|
console.warn(`failed to find matching v1 data vod for vod ${vod.id} ${vod.streamDate}`);
|
|
continue;
|
|
}
|
|
|
|
// skip if there is no thumbnail in the v1 vod
|
|
if (!v1Vod.thumbnail) continue;
|
|
|
|
// get a temporary file path to which we will DL
|
|
const tmpFile = join(tmpdir(), basename(v1Vod.thumbnail));
|
|
|
|
// we will take the thumbnail url, download it, then upload it to the specified vod record.
|
|
const entry: ManifestItem = {
|
|
thumbnail_url: v1Vod.thumbnail,
|
|
thumbnail_key: basename(v1Vod.thumbnail),
|
|
tmp_file: tmpFile,
|
|
vod_id: vod.id,
|
|
};
|
|
manifest.push(entry);
|
|
}
|
|
|
|
|
|
// sanity check
|
|
const invalidVods = manifest.filter((m) => m.thumbnail_url.includes('mp4'))
|
|
if (invalidVods.length > 0) {
|
|
console.warn('invalid thumbnails found', invalidVods)
|
|
// throw new Error('invalid. mp4s found in thumbnails');
|
|
}
|
|
|
|
const validManifest = manifest.filter((m) => !m.thumbnail_url.includes('mp4'));
|
|
|
|
|
|
// console.log('manifest', manifest);
|
|
|
|
// # ACTUAL WORK
|
|
// Download the thumbnail to tmp file
|
|
// upload the tmp file to pocketbase
|
|
|
|
function sleep(ms: number) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function retry(fn: any, retries = 6, delayMs = 500) {
|
|
let lastError;
|
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
try {
|
|
return await fn();
|
|
} catch (err) {
|
|
lastError = err;
|
|
console.warn(`Attempt ${attempt} failed: ${err}. Retrying in ${delayMs}ms...`);
|
|
if (attempt < retries) await sleep(delayMs);
|
|
}
|
|
}
|
|
throw lastError;
|
|
}
|
|
|
|
|
|
for (const [i, m] of validManifest.entries()) {
|
|
|
|
|
|
var from = "b2://" + process.env.B2_BUCKET_FROM + "/" + m.thumbnail_key;
|
|
var to = m.tmp_file;
|
|
var vodId = m.vod_id;
|
|
|
|
console.log("processing thumbnail " + i + ". " + from + " -> " + to + " (vod_id=" + m.vod_id + ")");
|
|
|
|
// Retry the download
|
|
await retry(function () {
|
|
return spawn('b2', ['file', 'download', from, to]);
|
|
});
|
|
|
|
// Retry the PocketBase upload
|
|
await retry(async function () {
|
|
var fileData = readFileSync(m.tmp_file);
|
|
var mimetype = mime.getType(m.tmp_file) || undefined;
|
|
var file = new File([fileData], basename(m.tmp_file), { type: mimetype });
|
|
var form = new FormData();
|
|
form.set("thumbnail", file);
|
|
return pb.collection('vods').update(vodId, form);
|
|
});
|
|
|
|
}
|
|
|
|
console.log("All done.");
|
|
|
|
|
|
|
|
}
|
|
|
|
main() |