UNPKG

newpipe-extractor-js

Version:
314 lines 11 kB
"use strict"; // Enhanced Stream Processing Utilities for NewPipe Extractor JS // Provides comprehensive stream analysis, format detection, and quality scoring Object.defineProperty(exports, "__esModule", { value: true }); exports.extractVideoIdFromUrl = extractVideoIdFromUrl; exports.validateYouTubeUrl = validateYouTubeUrl; exports.getStreamQualityScore = getStreamQualityScore; exports.findBestAudioFormat = findBestAudioFormat; exports.findBestVideoFormat = findBestVideoFormat; exports.isEncryptedFormat = isEncryptedFormat; exports.getFormatCodecInfo = getFormatCodecInfo; /** * Extract YouTube video ID from various URL formats */ function extractVideoIdFromUrl(url) { const patterns = [ /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/, /^[a-zA-Z0-9_-]{11}$/ // Direct video ID ]; for (const pattern of patterns) { const match = url.match(pattern); if (match && match[1]) { return match[1]; } if (pattern.test(url)) { return url; // Direct ID } } return null; } /** * Validate if a URL is a supported YouTube URL */ function validateYouTubeUrl(url) { const validPatterns = [ /^https?:\/\/(www\.)?(youtube\.com|youtu\.be)\//, /^https?:\/\/(www\.)?m\.youtube\.com\//, /^https?:\/\/(www\.)?gaming\.youtube\.com\//, /^https?:\/\/(www\.)?music\.youtube\.com\// ]; return validPatterns.some(pattern => pattern.test(url)); } /** * Calculate stream quality score based on multiple factors * Higher score = better quality */ function getStreamQualityScore(stream) { let score = 0; // Bitrate scoring (40% weight) if (stream.bitrate) { score += Math.min(stream.bitrate / 1000, 500) * 0.4; // Cap at 500kbps for scaling } // Format/Container scoring (20% weight) const formatScore = getFormatScore(stream.format?.toString() || ''); score += formatScore * 0.2; // Codec scoring (25% weight) const codecScore = getCodecScore(stream); score += codecScore * 0.25; // Audio-specific scoring if ('samplingRate' in stream) { const audioStream = stream; // Sample rate (15% weight for audio streams) if (audioStream.samplingRate) { score += Math.min(audioStream.samplingRate / 1000, 48) * 0.15; // Cap at 48kHz } } // Video-specific scoring if ('width' in stream && 'height' in stream) { const videoStream = stream; // Resolution scoring (10% weight for video) if (videoStream.width && videoStream.height) { const pixels = videoStream.width * videoStream.height; score += Math.min(pixels / 1000000, 8) * 0.1; // 4K = 8.3M pixels } // Frame rate (5% weight for video) if (videoStream.fps) { score += Math.min(videoStream.fps, 60) * 0.05; } } return Math.round(score * 10) / 10; // Round to 1 decimal place } /** * Find the best audio format from available streams */ function findBestAudioFormat(audioStreams) { if (!audioStreams || audioStreams.length === 0) { return null; } // Filter valid streams and calculate scores const validStreams = audioStreams .filter(stream => stream.url && !isStreamExpired(stream)) .map(stream => ({ stream, score: getStreamQualityScore(stream), isEncrypted: isEncryptedFormat(stream), codecRank: getCodecRank(stream) })); if (validStreams.length === 0) { return null; } // Sort by: codec rank (prefer modern codecs), quality score, prefer unencrypted validStreams.sort((a, b) => { // First priority: codec rank (modern codecs) if (a.codecRank !== b.codecRank) { return b.codecRank - a.codecRank; } // Second priority: quality score if (Math.abs(a.score - b.score) > 0.1) { return b.score - a.score; } // Third priority: prefer unencrypted streams if (a.isEncrypted !== b.isEncrypted) { return a.isEncrypted ? 1 : -1; } return 0; }); return validStreams[0].stream; } /** * Find the best video format from available streams */ function findBestVideoFormat(videoStreams) { if (!videoStreams || videoStreams.length === 0) { return null; } // Filter valid streams and calculate scores const validStreams = videoStreams .filter(stream => stream.url && !isStreamExpired(stream)) .map(stream => ({ stream, score: getStreamQualityScore(stream), isEncrypted: isEncryptedFormat(stream), codecRank: getCodecRank(stream) })); if (validStreams.length === 0) { return null; } // Sort similar to audio but prioritize resolution for video validStreams.sort((a, b) => { // First priority: codec rank if (a.codecRank !== b.codecRank) { return b.codecRank - a.codecRank; } // Second priority: quality score (includes resolution) if (Math.abs(a.score - b.score) > 0.1) { return b.score - a.score; } // Third priority: prefer unencrypted if (a.isEncrypted !== b.isEncrypted) { return a.isEncrypted ? 1 : -1; } return 0; }); return validStreams[0].stream; } /** * Check if a stream format is encrypted (requires signature decryption) */ function isEncryptedFormat(stream) { if (!stream.url) { return false; } const url = stream.url; // Check for signature cipher parameters const hasSignatureCipher = url.includes('signatureCipher=') || url.includes('cipher='); // Check for encrypted signature parameter const hasEncryptedSig = url.includes('&s=') || url.includes('?s='); // Check for signature parameter (usually means it's processed) const hasSigParam = url.includes('&sig=') || url.includes('?sig='); return hasSignatureCipher || (hasEncryptedSig && !hasSigParam); } /** * Extract codec information from stream */ function getFormatCodecInfo(stream) { const mimeType = ''; // mimeType is not available in the current type definitions const formatName = stream.format?.toString() || ''; // Extract container let container = 'unknown'; if (mimeType.includes('mp4') || formatName.includes('mp4')) { container = 'mp4'; } else if (mimeType.includes('webm') || formatName.includes('webm')) { container = 'webm'; } else if (mimeType.includes('3gpp') || formatName.includes('3gp')) { container = '3gp'; } // Parse codecs from mimeType const codecMatch = mimeType.match(/codecs="([^"]+)"/); let videoCodec; let audioCodec; let hdr; if (codecMatch && codecMatch[1]) { const codecs = codecMatch[1].split(',').map((c) => c.trim().toLowerCase()); for (const codec of codecs) { const parts = codec.split('.'); const baseCodec = parts[0]; // Video codecs if (['avc1', 'avc2', 'avc3', 'avc4', 'h264'].includes(baseCodec)) { videoCodec = codec; } else if (['vp9', 'vp8'].includes(baseCodec)) { videoCodec = codec; if (parts[1] === '2') hdr = 'HDR10'; } else if (['hev1', 'hev2', 'hvc1', 'hevc'].includes(baseCodec)) { videoCodec = codec; } else if (baseCodec === 'av01' || baseCodec === 'av1') { videoCodec = codec; if (parts[2] === '10') hdr = 'HDR10'; } else if (['dvh1', 'dvhe'].includes(baseCodec)) { videoCodec = codec; hdr = 'DV'; } // Audio codecs else if (['mp4a', 'aac'].includes(baseCodec)) { audioCodec = codec; } else if (['opus', 'vorbis'].includes(baseCodec)) { audioCodec = codec; } else if (['mp3', 'flac'].includes(baseCodec)) { audioCodec = codec; } } } return { container, videoCodec, audioCodec, hdr }; } // Helper functions function getFormatScore(format) { const formatLower = format.toLowerCase(); // Modern formats get higher scores if (formatLower.includes('mp4')) return 90; if (formatLower.includes('webm')) return 85; if (formatLower.includes('3gp')) return 60; if (formatLower.includes('flv')) return 40; return 50; // Default } function getCodecScore(stream) { const codecInfo = getFormatCodecInfo(stream); let score = 0; // Video codec scoring if (codecInfo.videoCodec) { const vcodec = codecInfo.videoCodec.toLowerCase(); if (vcodec.includes('av01') || vcodec.includes('av1')) score += 100; else if (vcodec.includes('vp9')) score += 90; else if (vcodec.includes('hevc') || vcodec.includes('hev')) score += 85; else if (vcodec.includes('avc') || vcodec.includes('h264')) score += 75; else if (vcodec.includes('vp8')) score += 60; else score += 40; } // Audio codec scoring if (codecInfo.audioCodec) { const acodec = codecInfo.audioCodec.toLowerCase(); if (acodec.includes('flac')) score += 100; else if (acodec.includes('opus')) score += 90; else if (acodec.includes('aac') || acodec.includes('mp4a')) score += 80; else if (acodec.includes('vorbis')) score += 70; else if (acodec.includes('mp3')) score += 60; else score += 40; } // HDR bonus if (codecInfo.hdr) { score += 20; } return score; } function getCodecRank(stream) { const codecInfo = getFormatCodecInfo(stream); // Modern codec preference ranking if (codecInfo.videoCodec?.includes('av01') || codecInfo.audioCodec?.includes('opus')) return 100; if (codecInfo.videoCodec?.includes('vp9') || codecInfo.audioCodec?.includes('flac')) return 90; if (codecInfo.videoCodec?.includes('hevc') || codecInfo.audioCodec?.includes('aac')) return 80; if (codecInfo.videoCodec?.includes('avc') || codecInfo.audioCodec?.includes('vorbis')) return 70; return 50; // Default rank } function isStreamExpired(stream) { if (!stream.url) return true; // Check for expire parameter in URL const expireMatch = stream.url.match(/[?&]expire=(\d+)/); if (expireMatch && expireMatch[1]) { const expireTime = parseInt(expireMatch[1]) * 1000; // Convert to milliseconds const now = Date.now(); return now >= expireTime; } return false; // If no expire info, assume not expired } //# sourceMappingURL=StreamUtils.js.map