UNPKG

newpipe-extractor-js

Version:
414 lines 14.4 kB
"use strict"; // Advanced Format Sorter for NewPipe Extractor JS // Based on yt-dlp's sophisticated format ranking system // Provides comprehensive quality analysis and format preference handling Object.defineProperty(exports, "__esModule", { value: true }); exports.FormatSorterPresets = exports.FormatSorter = void 0; const CodecUtils_1 = require("./CodecUtils"); /** * Advanced Format Sorter class based on yt-dlp methodology */ class FormatSorter { constructor(config = {}) { this.config = { preferHighQuality: true, preferHighFramerate: true, preferHDR: true, preferModernCodecs: true, preferUnencrypted: true, preferDash: false, targetPlatform: 'web', ...config }; } /** * Sort audio streams by quality and preferences */ sortAudioStreams(streams) { const extendedStreams = streams.map(stream => this.analyzeAudioStream(stream)); extendedStreams.sort((a, b) => b.totalScore - a.totalScore); return extendedStreams.map(ext => ext.stream); } /** * Sort video streams by quality and preferences */ sortVideoStreams(streams) { const extendedStreams = streams.map(stream => this.analyzeVideoStream(stream)); extendedStreams.sort((a, b) => b.totalScore - a.totalScore); return extendedStreams.map(ext => ext.stream); } /** * Get the best audio stream based on quality analysis */ getBestAudioStream(streams) { if (streams.length === 0) return null; const sorted = this.sortAudioStreams(streams); return sorted[0]; } /** * Get the best video stream based on quality analysis */ getBestVideoStream(streams) { if (streams.length === 0) return null; const sorted = this.sortVideoStreams(streams); return sorted[0]; } /** * Analyze audio stream and calculate comprehensive scores */ analyzeAudioStream(stream) { const codecInfo = this.getCodecInfo(stream); const qualityScore = this.calculateAudioQualityScore(stream); const codecScore = this.calculateCodecScore(codecInfo, 'audio'); const containerScore = this.calculateContainerScore(stream); const encryptionPenalty = this.calculateEncryptionPenalty(stream); const platformCompatibility = this.calculatePlatformCompatibility(codecInfo); const languageScore = this.calculateLanguageScore(stream); const totalScore = qualityScore * 0.35 + codecScore * 0.25 + containerScore * 0.15 + platformCompatibility * 0.15 + languageScore * 0.05 + encryptionPenalty * 0.05; return { stream, codecInfo, qualityScore, codecScore, containerScore, hdrScore: 0, // Not applicable for audio encryptionPenalty, platformCompatibility, languageScore, totalScore }; } /** * Analyze video stream and calculate comprehensive scores */ analyzeVideoStream(stream) { const codecInfo = this.getCodecInfo(stream); const qualityScore = this.calculateVideoQualityScore(stream); const codecScore = this.calculateCodecScore(codecInfo, 'video'); const containerScore = this.calculateContainerScore(stream); const hdrScore = this.calculateHDRScore(codecInfo); const encryptionPenalty = this.calculateEncryptionPenalty(stream); const platformCompatibility = this.calculatePlatformCompatibility(codecInfo); const languageScore = 0; // Not typically applicable for video const totalScore = qualityScore * 0.30 + codecScore * 0.25 + containerScore * 0.10 + hdrScore * 0.15 + platformCompatibility * 0.15 + encryptionPenalty * 0.05; return { stream, codecInfo, qualityScore, codecScore, containerScore, hdrScore, encryptionPenalty, platformCompatibility, languageScore, totalScore }; } /** * Extract codec information from stream */ getCodecInfo(stream) { if (stream.codec) { return (0, CodecUtils_1.parseCodecs)(stream.codec); } // Fallback to format-based detection const format = stream.format?.toString() || ''; if (format === 'mp4' || format === 'm4a') { return { acodec: 'aac', container: format }; } else if (format === 'webm') { return { acodec: 'opus', vcodec: 'vp9', container: format }; } return {}; } /** * Calculate audio quality score based on bitrate, sample rate, etc. */ calculateAudioQualityScore(stream) { let score = 0; // Bitrate scoring (0-60 points) if (stream.bitrate || stream.averageBitrate) { const bitrate = stream.bitrate || stream.averageBitrate || 0; score += Math.min(bitrate / 1000 / 8, 60); // Cap at 480kbps } // Sample rate scoring (0-25 points) if (stream.samplingRate) { score += Math.min(stream.samplingRate / 1000, 25); // Cap at 25kHz } // Audio track type bonus if (stream.audioTrackType === 'ORIGINAL') { score += 10; } return score; } /** * Calculate video quality score based on resolution, bitrate, fps, etc. */ calculateVideoQualityScore(stream) { let score = 0; // Resolution scoring (0-50 points) if (stream.width && stream.height) { const pixels = stream.width * stream.height; score += Math.min(Math.sqrt(pixels) / 100, 50); } else if (stream.resolution) { const resolutionNum = parseInt(stream.resolution); score += Math.min(resolutionNum / 40, 50); // 2000p = 50 points } // Bitrate scoring (0-30 points) if (stream.bitrate) { score += Math.min(stream.bitrate / 1000 / 200, 30); // 6Mbps = 30 points } // Frame rate scoring (0-20 points) if (stream.fps && this.config.preferHighFramerate) { score += Math.min(stream.fps / 3, 20); // 60fps = 20 points } return score; } /** * Calculate codec quality score */ calculateCodecScore(codecInfo, type) { const codecField = type === 'video' ? codecInfo.vcodec : codecInfo.acodec; if (!codecField) return 0; const rankings = FormatSorter.CODEC_RANKINGS[type]; // Find best matching codec ranking for (const [codecPattern, score] of Object.entries(rankings)) { if (codecField.toLowerCase().includes(codecPattern)) { return score; } } return 30; // Default score for unknown codecs } /** * Calculate container format score */ calculateContainerScore(stream) { const format = stream.format?.toString().toLowerCase() || ''; return FormatSorter.CONTAINER_RANKINGS[format] || 50; } /** * Calculate HDR bonus score */ calculateHDRScore(codecInfo) { if (!this.config.preferHDR) return 0; if ((0, CodecUtils_1.isHDRCodec)(codecInfo)) { if (codecInfo.hdr === 'DV') return 30; // Dolby Vision highest if (codecInfo.hdr === 'HDR10') return 25; return 20; // Other HDR types } return 0; } /** * Calculate encryption penalty */ calculateEncryptionPenalty(stream) { if (!this.config.preferUnencrypted) return 0; const isEncrypted = !!(stream.signatureCipher || stream.cipher); return isEncrypted ? -15 : 0; // Penalty for encrypted streams } /** * Calculate platform compatibility score */ calculatePlatformCompatibility(codecInfo) { const targetPlatform = this.config.targetPlatform || 'web'; const compatibility = { web: this.isWebCompatible(codecInfo), mobile: this.isMobileCompatible(codecInfo), desktop: true, // Desktop supports most formats smart_tv: this.isSmartTVCompatible(codecInfo) }; return compatibility[targetPlatform] ? 50 : 10; } /** * Calculate language preference score */ calculateLanguageScore(stream) { if (!this.config.preferredLanguages || !stream.audioTrackName) { return 50; // Neutral score } const trackLanguage = stream.audioTrackName.toLowerCase(); const preferredLanguages = this.config.preferredLanguages.map(lang => lang.toLowerCase()); for (let i = 0; i < preferredLanguages.length; i++) { if (trackLanguage.includes(preferredLanguages[i])) { return 100 - (i * 10); // Higher score for more preferred languages } } return 30; // Lower score for non-preferred languages } /** * Check web browser compatibility */ isWebCompatible(codecInfo) { const { vcodec, acodec } = codecInfo; const webVideoCodecs = ['h264', 'vp8', 'vp9', 'av01']; const webAudioCodecs = ['aac', 'opus', 'vorbis', 'mp3']; const videoOk = !vcodec || webVideoCodecs.some(codec => vcodec.includes(codec)); const audioOk = !acodec || webAudioCodecs.some(codec => acodec.includes(codec)); return videoOk && audioOk; } /** * Check mobile device compatibility */ isMobileCompatible(codecInfo) { const { vcodec, acodec } = codecInfo; const mobileVideoCodecs = ['h264', 'h265', 'hevc']; const mobileAudioCodecs = ['aac', 'mp3']; const videoOk = !vcodec || mobileVideoCodecs.some(codec => vcodec.includes(codec)); const audioOk = !acodec || mobileAudioCodecs.some(codec => acodec.includes(codec)); return videoOk && audioOk; } /** * Check smart TV compatibility */ isSmartTVCompatible(codecInfo) { const { vcodec, acodec } = codecInfo; const tvVideoCodecs = ['h264', 'h265', 'hevc']; const tvAudioCodecs = ['aac', 'ac-3', 'eac3']; const videoOk = !vcodec || tvVideoCodecs.some(codec => vcodec.includes(codec)); const audioOk = !acodec || tvAudioCodecs.some(codec => acodec.includes(codec)); return videoOk && audioOk; } /** * Get detailed format analysis for debugging */ getFormatAnalysis(stream) { if ('samplingRate' in stream) { return this.analyzeAudioStream(stream); } else { return this.analyzeVideoStream(stream); } } /** * Update sorting configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } /** * Get current configuration */ getConfig() { return { ...this.config }; } } exports.FormatSorter = FormatSorter; // Default sorting criteria based on yt-dlp preferences // private static readonly DEFAULT_SORT_FIELDS = [ // 'hidden', 'quality', 'res', 'fps', 'hdr', 'vcodec', 'channels', // 'acodec', 'size', 'br', 'asr', 'proto', 'ext', 'hasaud', 'source', 'id' // ]; // Codec preference rankings (higher = better) FormatSorter.CODEC_RANKINGS = { video: { 'av01': 100, 'av1': 100, 'vp9.2': 95, 'vp9': 90, 'h265': 85, 'hevc': 85, 'hev1': 85, 'hev2': 85, 'hvc1': 85, 'h264': 80, 'avc1': 80, 'avc2': 80, 'avc3': 80, 'avc4': 80, 'vp8': 70, 'mp4v': 60, 'h263': 50, 'theora': 40 }, audio: { 'flac': 100, 'alac': 95, 'wav': 90, 'pcm': 90, 'opus': 85, 'vorbis': 80, 'ogg': 80, 'aac-he-v2': 75, 'aac-he': 70, 'aac': 65, 'mp4a': 65, 'mp3': 60, 'ac-4': 55, 'ac-3': 50, 'eac3': 50, 'ec-3': 50, 'dts': 45 } }; // Container preference rankings FormatSorter.CONTAINER_RANKINGS = { 'mp4': 90, 'webm': 85, 'm4a': 80, 'ogg': 75, 'flac': 95, '3gp': 60, 'flv': 50 }; /** * Create a format sorter with predefined configurations for common use cases */ class FormatSorterPresets { /** * High quality preset - prefer best codecs and highest quality */ static createHighQualityPreset() { return new FormatSorter({ preferHighQuality: true, preferHighFramerate: true, preferHDR: true, preferModernCodecs: true, preferredVideoCodecs: ['av01', 'vp9', 'h265'], preferredAudioCodecs: ['flac', 'opus', 'aac'], preferUnencrypted: true, targetPlatform: 'desktop' }); } /** * Web compatibility preset - prefer formats that work well in browsers */ static createWebCompatibilityPreset() { return new FormatSorter({ preferHighQuality: true, preferModernCodecs: false, preferredVideoCodecs: ['h264', 'vp9', 'vp8'], preferredAudioCodecs: ['aac', 'opus', 'mp3'], preferredContainers: ['mp4', 'webm'], targetPlatform: 'web' }); } /** * Mobile preset - prefer formats that work well on mobile devices */ static createMobilePreset() { return new FormatSorter({ preferHighQuality: false, preferModernCodecs: false, preferredVideoCodecs: ['h264', 'h265'], preferredAudioCodecs: ['aac', 'mp3'], preferredContainers: ['mp4', 'm4a'], targetPlatform: 'mobile' }); } /** * Bandwidth optimized preset - prefer smaller file sizes */ static createBandwidthOptimizedPreset() { return new FormatSorter({ preferHighQuality: false, preferModernCodecs: true, preferredVideoCodecs: ['av01', 'vp9', 'h265'], preferredAudioCodecs: ['opus', 'aac'], preferDash: true, targetPlatform: 'web' }); } } exports.FormatSorterPresets = FormatSorterPresets; //# sourceMappingURL=FormatSorter.js.map