File size: 5,755 Bytes
ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f 8740315 ea5d04f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
import { VIDEO_EXTENSIONS } from './const.js';
export function isVideo(filename) {
if (!filename) return false;
return VIDEO_EXTENSIONS.some(ext =>
filename.toLowerCase().endsWith(ext)
);
}
export function extractInfoHash(magnetLink) {
if (!magnetLink) return null;
try {
const match = magnetLink.match(/xt=urn:btih:([^&]+)/i);
return match ? match[1].toLowerCase() : null;
} catch (error) {
console.error('Error extracting info hash:', error);
return null;
}
}
export async function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function base64Encode(str) {
if (!str) return '';
try {
return Buffer.from(str).toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
} catch (error) {
console.error('Error encoding to base64:', error);
return '';
}
}
export function base64Decode(str) {
if (!str) return '';
try {
str = str.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
return Buffer.from(str, 'base64').toString('ascii');
} catch (error) {
console.error('Error decoding from base64:', error);
return '';
}
}
export function findBestFile(files, metadata) {
if (!Array.isArray(files) || !files?.length) return null;
if (files.length === 1) return files[0];
// If no metadata provided, return largest file
if (!metadata?.meta?.name) {
const largestFile = files.sort((a, b) => (b.size || 0) - (a.size || 0))[0];
console.log('Selected largest file (no metadata available):', largestFile.name || largestFile.path);
return largestFile;
}
const title = metadata.meta.name.toLowerCase();
const year = new Date(metadata.meta.released).getFullYear();
const yearStr = year.toString();
// Get key parts of the title (for Harry Potter and the Prisoner of Azkaban -> ["harry", "potter", "prisoner", "azkaban"])
const titleParts = title
.replace(/[^a-z0-9\s]/g, '')
.split(/\s+/)
.filter(part => !['and', 'the', 'of'].includes(part));
console.log(`Looking for match with title parts:`, titleParts, `(${year})`);
// Score each video file
const scoredFiles = files.map(file => {
let score = 0;
const filename = (file.name?.toLowerCase() || file.path?.toLowerCase() || '');
// Skip if not a video file
if (!filename.match(/\.(mp4|mkv|avi|mov|wmv|m4v|ts)$/i)) {
return { file, score: -100, filename };
}
// Count matching title parts
const matchingParts = titleParts.filter(part => filename.includes(part));
score += (matchingParts.length / titleParts.length) * 100;
if (matchingParts.length > 0) {
console.log(`Found ${matchingParts.length}/${titleParts.length} title parts in "${filename}"`);
}
// Year matching
if (filename.includes(yearStr)) {
score += 50;
console.log(`Year match found in "${filename}"`);
}
// Quality bonus
if (filename.match(/2160p|4k|uhd/i)) score += 40;
else if (filename.match(/1080p/i)) score += 30;
else if (filename.match(/720p/i)) score += 20;
// Source quality bonus
if (filename.match(/blu-?ray|remux/i)) score += 25;
else if (filename.match(/web-?dl|webrip/i)) score += 15;
else if (filename.match(/hdtv/i)) score += 10;
// Encoding bonus
if (filename.match(/x265|hevc|h\.?265/i)) score += 15;
else if (filename.match(/x264|h\.?264/i)) score += 10;
// Penalize likely pack/collection files
if (filename.match(/\b(pack|collection|complete|season|episode|[es]\d{2,3})\b/i)) {
score -= 30;
console.log(`Pack/collection penalty applied to "${filename}"`);
}
// Penalize extras/bonus content
if (filename.match(/\b(extra|bonus|behind|feature|trailer|sample)\b/i)) {
score -= 40;
console.log(`Extras/bonus penalty applied to "${filename}"`);
}
return {
file,
score,
size: file.size || file.length || 0,
filename,
matchingParts
};
});
// Log all scores for debugging
console.log('\nFile scores:');
scoredFiles.forEach(({filename, score, size, matchingParts}) => {
console.log(`"${filename}": score=${score}, size=${(size / (1024 * 1024)).toFixed(2)}MB, matched=${matchingParts.join(', ')}`);
});
// Sort first by score, then by size
scoredFiles.sort((a, b) => {
// If scores are significantly different, use score
if (Math.abs(b.score - a.score) > 20) {
return b.score - a.score;
}
// If scores are close, use file size
return b.size - a.size;
});
const bestMatch = scoredFiles[0];
// If no good matches found (low score), fall back to largest file
if (bestMatch.score < 30) {
console.log('No confident match found, falling back to largest file');
const largestFile = files.sort((a, b) => (b.size || b.length || 0) - (a.size || a.length || 0))[0];
console.log(`Selected largest file: ${largestFile.name || largestFile.path}`);
return largestFile;
}
console.log(`\nSelected "${bestMatch.file.name || bestMatch.file.path}"`);
console.log(`Score: ${bestMatch.score}`);
console.log(`Size: ${(bestMatch.size / (1024 * 1024)).toFixed(2)}MB`);
console.log(`Matched parts: ${bestMatch.matchingParts.join(', ')}`);
return bestMatch.file;
}
|