calculus / src /util.js
no1b4me's picture
Upload 12 files
8740315 verified
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;
}