almeira-web/scripts/extract_wp_images_from_tar.js

205 lines
6.6 KiB
JavaScript

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);
});