newpipe-extractor-js
Version:
JavaScript/Node.js port of NewPipeExtractor
314 lines • 11 kB
JavaScript
;
// 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