newpipe-extractor-js
Version:
JavaScript/Node.js port of NewPipeExtractor
414 lines • 14.4 kB
JavaScript
"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