From c5b9561c0cef9e82b9058d43e027d86e59a57b42 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 11 Jan 2026 00:25:36 +0100 Subject: [PATCH] fix plugin poster --- src/plugin.ts | 128 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index a947500..a57433d 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -30,7 +30,8 @@ */ import axios from 'axios'; -import { createWriteStream, existsSync, mkdirSync } from 'fs'; +import { createWriteStream, existsSync, mkdirSync, statSync, openSync, readSync, closeSync } from 'fs'; +import { createHash } from 'crypto'; import * as path from 'path'; import { pipeline } from 'stream/promises'; import type { PluginManifest, ProcessRequest, CallbackPayload } from './types.js'; @@ -52,6 +53,79 @@ const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/original'; const FILES_PATH = (globalThis as any).process?.env?.FILES_PATH || '/files'; const PLUGIN_OUTPUT_PATH = '/output'; +/** + * Compute midhash256 CID for a file (matches meta-hash algorithm) + * + * Algorithm: + * - For files <= 1MB: Hashes entire file content + 8-byte size prefix + * - For files > 1MB: Hashes middle 1MB + 8-byte size prefix + * - Returns CIDv1 with custom codec 0x1000 + */ +function computeMidHash256(filePath: string): string { + const SAMPLE_SIZE = 1024 * 1024; // 1MB + // varint encoding of 0x1000 (4096) for both codec and hash function code + const MIDHASH_VARINT = Buffer.from([0x80, 0x20]); + + // Get file size + const stats = statSync(filePath); + const fileSize = stats.size; + + // Create size buffer (64-bit big-endian) + const sizeBuffer = Buffer.allocUnsafe(8); + sizeBuffer.writeBigUInt64BE(BigInt(fileSize), 0); + + // Extract sample data + let sampleData: Buffer; + if (fileSize <= SAMPLE_SIZE) { + // Small file: read entire content + const fd = openSync(filePath, 'r'); + sampleData = Buffer.allocUnsafe(fileSize); + readSync(fd, sampleData, 0, fileSize, 0); + closeSync(fd); + } else { + // Large file: read middle 1MB + const middleOffset = Math.floor((fileSize - SAMPLE_SIZE) / 2); + const fd = openSync(filePath, 'r'); + sampleData = Buffer.allocUnsafe(SAMPLE_SIZE); + readSync(fd, sampleData, 0, SAMPLE_SIZE, middleOffset); + closeSync(fd); + } + + // Compute SHA-256 hash of [size + sample] + const hashInput = Buffer.concat([sizeBuffer, sampleData]); + const hashBuffer = createHash('sha256').update(hashInput).digest(); + + // Build CIDv1: version (0x01) + codec (varint) + multihash + // Codec 0x1000 = varint [0x80, 0x20] + // Multihash: function-code (varint 0x1000) + length (0x20) + hash + const cidBytes = Buffer.concat([ + Buffer.from([0x01]), // CIDv1 + MIDHASH_VARINT, // codec 0x1000 as varint + MIDHASH_VARINT, // hash function code 0x1000 as varint + Buffer.from([0x20]), // 32 bytes digest length + hashBuffer + ]); + + // Encode as base32lower with 'b' prefix (multibase) + const base32Chars = 'abcdefghijklmnopqrstuvwxyz234567'; + let cid = 'b'; + let bits = 0; + let value = 0; + for (const byte of cidBytes) { + value = (value << 8) | byte; + bits += 8; + while (bits >= 5) { + bits -= 5; + cid += base32Chars[(value >> bits) & 0x1f]; + } + } + if (bits > 0) { + cid += base32Chars[(value << (5 - bits)) & 0x1f]; + } + + return cid; +} + export const manifest: PluginManifest = { id: 'tmdb', name: 'TMDB Metadata', @@ -217,22 +291,21 @@ async function downloadImage(imageUrl: string, localPath: string): Promise} + * @returns {Promise} The midhash256 CID of the downloaded image */ async function downloadAndHashImage( imagePath: string, imageType: string, title: string, year: string | undefined, - tmdbId: string, - metaCore: MetaCoreClient -): Promise<{ cid: string; path: string } | null> { + tmdbId: string +): Promise { if (!imagePath) { return null; } @@ -250,10 +323,6 @@ async function downloadAndHashImage( // This respects the mount architecture: /files is READ-ONLY const localPath = path.join(PLUGIN_OUTPUT_PATH, filename); - // Relative path for storage (relative to /files for meta-core compatibility) - // Path format: plugin/tmdb/ - const relativePath = `plugin/tmdb/${filename}`; - // Check if file already exists (skip download) if (existsSync(localPath)) { console.log(`[tmdb] Image already exists: ${localPath}`); @@ -271,15 +340,11 @@ async function downloadAndHashImage( } } - // Compute CID using meta-core API + // Compute midhash256 CID (same algorithm as meta-sort file entries) try { - const cid = await metaCore.computeFileCID(relativePath); - if (cid) { - console.log(`[tmdb] Image ${imageType} CID: ${cid}`); - return { cid, path: relativePath }; - } else { - console.warn(`[tmdb] Failed to compute CID for ${localPath}`); - } + const cid = computeMidHash256(localPath); + console.log(`[tmdb] Image ${imageType} CID: ${cid}`); + return cid; } catch (e) { console.error(`[tmdb] Failed to compute CID for ${localPath}: ${e instanceof Error ? e.message : String(e)}`); } @@ -479,35 +544,32 @@ async function applyTmdbData( await metaCore.addToSet(cid, 'tags', 'tmdb-verified'); // Download poster and backdrop images to plugin output folder + // The files will be picked up by meta-sort's file watcher and processed like any other file if (data.poster_path && tmdbId) { - const posterResult = await downloadAndHashImage( + const posterCid = await downloadAndHashImage( data.poster_path, 'poster', displayTitle, year, - tmdbId, - metaCore as any + tmdbId ); - if (posterResult) { - await metaCore.setProperty(cid, 'poster', posterResult.cid); - await metaCore.setProperty(cid, 'posterPath', posterResult.path); - console.log(`[tmdb] Set poster CID: ${posterResult.cid}`); + if (posterCid) { + await metaCore.setProperty(cid, 'poster', posterCid); + console.log(`[tmdb] Set poster CID: ${posterCid}`); } } if (data.backdrop_path && tmdbId) { - const backdropResult = await downloadAndHashImage( + const backdropCid = await downloadAndHashImage( data.backdrop_path, 'backdrop', displayTitle, year, - tmdbId, - metaCore as any + tmdbId ); - if (backdropResult) { - await metaCore.setProperty(cid, 'backdrop', backdropResult.cid); - await metaCore.setProperty(cid, 'backdropPath', backdropResult.path); - console.log(`[tmdb] Set backdrop CID: ${backdropResult.cid}`); + if (backdropCid) { + await metaCore.setProperty(cid, 'backdrop', backdropCid); + console.log(`[tmdb] Set backdrop CID: ${backdropCid}`); } } }