diff --git a/prproj-remapper.js b/prproj-remapper.js index f737c08..2834201 100644 --- a/prproj-remapper.js +++ b/prproj-remapper.js @@ -323,6 +323,12 @@ function performSwaps(xml, mediaBlocks, proxyLinks, options = {}) { // Collect all unique .gves UIDs that have a mapped high-res block const gvesUids = Object.keys(proxyLinks); + // Also collect any .gves blocks that aren't in proxyLinks (for unlinked proxies) + const allGvesBlocks = Object.entries(mediaBlocks) + .filter(([uid, block]) => block.isGves) + .map(([uid, block]) => ({ uid, block })); + + // Process linked proxy pairs first for (const gvesUid of gvesUids) { const hiresUid = proxyLinks[gvesUid]; const gvesBlock = mediaBlocks[gvesUid]; @@ -400,6 +406,59 @@ function performSwaps(xml, mediaBlocks, proxyLinks, options = {}) { ); } + // Handle unlinked high-res proxy blocks (have IsProxy: true, FilePath: null, no proxyLinks entry) + // These occur when FramelightX created proxy blocks but didn't link them explicitly + if (options.hiresMediaFolder && allGvesBlocks.length > 0) { + // Get the first .gves block as reference (for path lookup) + const gvesRef = allGvesBlocks[0].block; + + for (const [uid, hiresBlock] of Object.entries(mediaBlocks)) { + // Skip if already processed, not a proxy, or already has a path + if (gvesUids.includes(uid) || !hiresBlock.isProxy || hiresBlock.filePath) continue; + + // Try to find a high-res file for this unlinked proxy using its Title + let hiresPath = findHighResFileForGves( + gvesRef.filePath, // Use first gves path for reference + hiresBlock.title, + options.hiresMediaFolder, + fs + ); + + if (!hiresPath) continue; + + const hiresTitle = hiresBlock.title || pathToTitle(hiresPath); + + // Perform replacements for this unlinked proxy + // We'll replace the Title in the XML with the found file + if (hiresBlock.title) { + const titleEscaped = escapeRegex(hiresBlock.title); + remappedXml = remappedXml.replace( + new RegExp(`${titleEscaped}`, 'g'), + `${hiresTitle}` + ); + } + + // Add a FilePath to the high-res block's Media section + // Look for the Media block with this Title and add FilePath if missing + const mediaBlockRegex = new RegExp( + `]*>([^<]*${escapeRegex(hiresBlock.title)}[^<]*)`, + 'g' + ); + remappedXml = remappedXml.replace(mediaBlockRegex, (match) => { + if (match.includes('')) return match; // Already has path + return match.replace(``, `${hiresPath}`); + }); + + swaps.push({ + gvesUid: 'unlinked', + hiresUid: uid, + oldPath: 'null', + newPath: hiresPath, + newTitle: hiresTitle + }); + } + } + // Remove IsProxy and OfflineReason from high-res blocks so they're treated as online remappedXml = remappedXml.replace(/\s*true<\/IsProxy>/g, ''); remappedXml = remappedXml.replace(/\s*\d+<\/OfflineReason>/g, '');