const fs = require('fs'); const path = require('path'); const zlib = require('zlib'); const sharp = require('sharp'); const TAR_GZ_PATH = 'C:/Users/Carlos/Downloads/u858407019.almeiraproducciones-com.20260607074508.tar.gz'; const WP_DATA_PATH = path.join(__dirname, 'wp_extracted_data.json'); const OUT_DIR = 'C:/tmp/wp_images'; if (!fs.existsSync(OUT_DIR)) { fs.mkdirSync(OUT_DIR, { recursive: true }); } function safeFileKey(relPath) { return relPath.replace(/[^a-zA-Z0-9._-]/g, '_'); } async function run() { const wpData = JSON.parse(fs.readFileSync(WP_DATA_PATH, 'utf8')); const neededRelPaths = new Map(); // normalized lower relPath -> original relPath const neededBasenames = new Map(); // lower basename -> original relPath for (const post of wpData.posts) { if (post.imageRelPath) { const clean = post.imageRelPath.replace(/^\/+/, ''); neededRelPaths.set(clean.toLowerCase(), clean); neededBasenames.set(path.posix.basename(clean).toLowerCase(), clean); } } console.log(`Total imágenes destacadas únicas requeridas por los ${wpData.posts.length} posts: ${neededRelPaths.size}`); let extractedCount = 0; let scannedFiles = 0; let nextLongName = null; let nextPaxPath = null; // Streaming tar reader using a growable/ring buffer list let chunks = []; let totalBuffered = 0; function consumeBytes(n) { const out = Buffer.allocUnsafe(n); let offset = 0; while (offset < n) { const first = chunks[0]; const toCopy = Math.min(first.length, n - offset); first.copy(out, offset, 0, toCopy); offset += toCopy; if (toCopy === first.length) { chunks.shift(); } else { chunks[0] = first.subarray(toCopy); } } totalBuffered -= n; return out; } function skipBytes(n) { let remaining = n; while (remaining > 0) { const first = chunks[0]; if (first.length <= remaining) { remaining -= first.length; chunks.shift(); } else { chunks[0] = first.subarray(remaining); remaining = 0; } } totalBuffered -= n; } let state = 'HEADER'; // 'HEADER' | 'DATA' | 'SKIP' let currentEntrySize = 0; let currentEntryPadded = 0; let currentEntryType = '0'; let currentMatchedRel = null; const pendingSharpPromises = []; const gunzip = zlib.createGunzip({ chunkSize: 1024 * 1024 }); const readStream = fs.createReadStream(TAR_GZ_PATH, { highWaterMark: 1024 * 1024 }); const stream = readStream.pipe(gunzip); for await (const chunk of stream) { chunks.push(chunk); totalBuffered += chunk.length; while (true) { if (state === 'HEADER') { if (totalBuffered < 512) break; const header = consumeBytes(512); // Check empty block (end of archive) let allZero = true; for (let i = 0; i < 512; i++) { if (header[i] !== 0) { allZero = false; break; } } if (allZero) continue; const nameRaw = header.toString('utf8', 0, 100).replace(/\0.*$/, ''); const sizeOctal = header.toString('ascii', 124, 136).replace(/\0.*$/, '').trim(); const typeFlag = String.fromCharCode(header[156] || 48); const prefixRaw = header.toString('utf8', 345, 500).replace(/\0.*$/, ''); let fullPath = prefixRaw ? `${prefixRaw}/${nameRaw}` : nameRaw; if (nextLongName) { fullPath = nextLongName; nextLongName = null; } if (nextPaxPath) { fullPath = nextPaxPath; nextPaxPath = null; } currentEntrySize = parseInt(sizeOctal, 8) || 0; currentEntryPadded = Math.ceil(currentEntrySize / 512) * 512; currentEntryType = typeFlag; if (typeFlag === 'L' || typeFlag === 'x') { state = 'DATA'; continue; } if (typeFlag === '0' || typeFlag === '\0') { scannedFiles++; if (scannedFiles % 10000 === 0) { console.log(`... escaneados ${scannedFiles} archivos en tar.gz | extraídas: ${extractedCount}/${neededRelPaths.size}`); } const uploadsIdx = fullPath.indexOf('wp-content/uploads/'); if (uploadsIdx !== -1 && currentEntrySize > 0) { const relInUploads = fullPath.slice(uploadsIdx + 'wp-content/uploads/'.length); const lowerRel = relInUploads.toLowerCase(); const lowerBase = path.posix.basename(relInUploads).toLowerCase(); let matched = neededRelPaths.get(lowerRel) || neededBasenames.get(lowerBase) || null; if (matched) { const outFile = path.join(OUT_DIR, safeFileKey(matched) + '.jpg'); if (!fs.existsSync(outFile)) { currentMatchedRel = matched; state = 'DATA'; continue; } } } } state = 'SKIP'; } else if (state === 'SKIP') { if (totalBuffered < currentEntryPadded) break; if (currentEntryPadded > 0) skipBytes(currentEntryPadded); state = 'HEADER'; } else if (state === 'DATA') { if (totalBuffered < currentEntryPadded) break; const dataBlock = consumeBytes(currentEntryPadded); const fileData = dataBlock.subarray(0, currentEntrySize); if (currentEntryType === 'L') { nextLongName = fileData.toString('utf8').replace(/\0.*$/, ''); } else if (currentEntryType === 'x') { const paxStr = fileData.toString('utf8'); const m = paxStr.match(/\d+ path=([^\n]+)\n/); if (m) nextPaxPath = m[1]; } else if (currentMatchedRel) { const targetRel = currentMatchedRel; currentMatchedRel = null; const outFile = path.join(OUT_DIR, safeFileKey(targetRel) + '.jpg'); extractedCount++; const p = sharp(fileData) .resize(800, 500, { fit: 'inside', withoutEnlargement: true }) .jpeg({ quality: 80 }) .toFile(outFile) .catch(() => { // Fallback: write raw buffer if sharp fails on an unusual image try { fs.writeFileSync(outFile, fileData); } catch (_) {} }); pendingSharpPromises.push(p); if (pendingSharpPromises.length >= 50) { await Promise.all(pendingSharpPromises.splice(0, pendingSharpPromises.length)); } } state = 'HEADER'; } } } await Promise.all(pendingSharpPromises); console.log(`\n✅ Extracción de imágenes de WordPress finalizada: ${extractedCount} imágenes listas en ${OUT_DIR}`); } run().catch((e) => { console.error('Error:', e); process.exit(1); });