mirror of
				https://github.com/musix-org/musix-oss
				synced 2025-11-04 09:49:32 +00:00 
			
		
		
		
	Updated
This commit is contained in:
		
							
								
								
									
										737
									
								
								node_modules/ytdl-core/lib/formats.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										737
									
								
								node_modules/ytdl-core/lib/formats.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,737 @@
 | 
			
		||||
/**
 | 
			
		||||
 * http://en.wikipedia.org/wiki/YouTube#Quality_and_formats
 | 
			
		||||
 */
 | 
			
		||||
module.exports = {
 | 
			
		||||
 | 
			
		||||
  '5': {
 | 
			
		||||
    container: 'flv',
 | 
			
		||||
    resolution: '240p',
 | 
			
		||||
    encoding: 'Sorenson H.283',
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: '0.25',
 | 
			
		||||
    audioEncoding: 'mp3',
 | 
			
		||||
    audioBitrate: 64,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '6': {
 | 
			
		||||
    container: 'flv',
 | 
			
		||||
    resolution: '270p',
 | 
			
		||||
    encoding: 'Sorenson H.263',
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: '0.8',
 | 
			
		||||
    audioEncoding: 'mp3',
 | 
			
		||||
    audioBitrate: 64,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '13': {
 | 
			
		||||
    container: '3gp',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: 'MPEG-4 Visual',
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: '0.5',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '17': {
 | 
			
		||||
    container: '3gp',
 | 
			
		||||
    resolution: '144p',
 | 
			
		||||
    encoding: 'MPEG-4 Visual',
 | 
			
		||||
    profile: 'simple',
 | 
			
		||||
    bitrate: '0.05',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 24,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '18': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'baseline',
 | 
			
		||||
    bitrate: '0.5',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 96,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '22': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '2-3',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '34': {
 | 
			
		||||
    container: 'flv',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.5',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '35': {
 | 
			
		||||
    container: 'flv',
 | 
			
		||||
    resolution: '480p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.8-1',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '36': {
 | 
			
		||||
    container: '3gp',
 | 
			
		||||
    resolution: '240p',
 | 
			
		||||
    encoding: 'MPEG-4 Visual',
 | 
			
		||||
    profile: 'simple',
 | 
			
		||||
    bitrate: '0.175',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 32,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '37': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '1080p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '3-5.9',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '38': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '3072p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '3.5-5',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '43': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'VP8',
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: '0.5-0.75',
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '44': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '480p',
 | 
			
		||||
    encoding: 'VP8',
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: '1',
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '45': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'VP8',
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: '2',
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '46': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '1080p',
 | 
			
		||||
    encoding: 'vp8',
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '82': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: '3d',
 | 
			
		||||
    bitrate: '0.5',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 96,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '83': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '240p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: '3d',
 | 
			
		||||
    bitrate: '0.5',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 96,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '84': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: '3d',
 | 
			
		||||
    bitrate: '2-3',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '85': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '1080p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: '3d',
 | 
			
		||||
    bitrate: '3-4',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '100': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'VP8',
 | 
			
		||||
    profile: '3d',
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '101': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'VP8',
 | 
			
		||||
    profile: '3d',
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '102': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'VP8',
 | 
			
		||||
    profile: '3d',
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // DASH (video only)
 | 
			
		||||
  '133': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '240p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.2-0.3',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '134': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.3-0.4',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '135': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '480p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.5-1',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '136': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '1-1.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '137': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '1080p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '2.5-3',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '138': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '4320p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '13.5-25',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '160': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '144p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.1',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '242': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '240p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '0.1-0.2',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '243': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '0.25',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '244': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '480p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '0.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '247': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '0.7-0.8',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '248': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '1080p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '1.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '264': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '1440p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '4-4.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '266': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '2160p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '12.5-16',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '271': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '1440p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '9',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '272': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '4320p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '20-25',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '278': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '144p 15fps',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '0.08',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '298': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '3-3.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '299': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: '1080p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '5.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '302': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '720p HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '2.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '303': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '1080p HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '308': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '1440p HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '10',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '313': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '2160p',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '13-15',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '315': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '2160p HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 0',
 | 
			
		||||
    bitrate: '20-25',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '330': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '144p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '0.08',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '331': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '240p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '0.1-0.15',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '332': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '360p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '0.25',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '333': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '240p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '0.5',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '334': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '720p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '1',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '335': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '1080p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '1.5-2',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '336': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '1440p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '5-7',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '337': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: '2160p HDR, HFR',
 | 
			
		||||
    encoding: 'VP9',
 | 
			
		||||
    profile: 'profile 2',
 | 
			
		||||
    bitrate: '12-14',
 | 
			
		||||
    audioEncoding: null,
 | 
			
		||||
    audioBitrate: null,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // DASH (audio only)
 | 
			
		||||
  '139': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 48,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '140': {
 | 
			
		||||
    container: 'm4a',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '141': {
 | 
			
		||||
    container: 'mp4',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 256,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '171': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '172': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'vorbis',
 | 
			
		||||
    audioBitrate: 192,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '249': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'opus',
 | 
			
		||||
    audioBitrate: 48,
 | 
			
		||||
  },
 | 
			
		||||
  '250': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'opus',
 | 
			
		||||
    audioBitrate: 64,
 | 
			
		||||
  },
 | 
			
		||||
  '251': {
 | 
			
		||||
    container: 'webm',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'opus',
 | 
			
		||||
    audioBitrate: 160,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // Live streaming
 | 
			
		||||
  '91': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '144p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.1',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 48,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '92': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '240p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.15-0.3',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 48,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '93': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '360p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.5-1',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '94': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '480p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '0.8-1.25',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '95': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'main',
 | 
			
		||||
    bitrate: '1.5-3',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 256,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '96': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '1080p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'high',
 | 
			
		||||
    bitrate: '2.5-6',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 256,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '120': {
 | 
			
		||||
    container: 'flv',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'Main@L3.1',
 | 
			
		||||
    bitrate: '2',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 128,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '127': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 96,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '128': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: null,
 | 
			
		||||
    encoding: null,
 | 
			
		||||
    profile: null,
 | 
			
		||||
    bitrate: null,
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 96,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '132': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '240p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'baseline',
 | 
			
		||||
    bitrate: '0.15-0.2',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 48,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  '151': {
 | 
			
		||||
    container: 'ts',
 | 
			
		||||
    resolution: '720p',
 | 
			
		||||
    encoding: 'H.264',
 | 
			
		||||
    profile: 'baseline',
 | 
			
		||||
    bitrate: '0.05',
 | 
			
		||||
    audioEncoding: 'aac',
 | 
			
		||||
    audioBitrate: 24,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										156
									
								
								node_modules/ytdl-core/lib/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								node_modules/ytdl-core/lib/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
const PassThrough = require('stream').PassThrough;
 | 
			
		||||
const getInfo     = require('./info');
 | 
			
		||||
const util        = require('./util');
 | 
			
		||||
const sig         = require('./sig');
 | 
			
		||||
const request     = require('miniget');
 | 
			
		||||
const m3u8stream  = require('m3u8stream');
 | 
			
		||||
const parseTime   = require('m3u8stream/lib/parse-time');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {string} link
 | 
			
		||||
 * @param {!Object} options
 | 
			
		||||
 * @return {ReadableStream}
 | 
			
		||||
 */
 | 
			
		||||
const ytdl = (link, options) => {
 | 
			
		||||
  const stream = createStream(options);
 | 
			
		||||
  ytdl.getInfo(link, options, (err, info) => {
 | 
			
		||||
    if (err) {
 | 
			
		||||
      stream.emit('error', err);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    downloadFromInfoCallback(stream, info, options);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return stream;
 | 
			
		||||
};
 | 
			
		||||
module.exports = ytdl;
 | 
			
		||||
 | 
			
		||||
ytdl.getBasicInfo = getInfo.getBasicInfo;
 | 
			
		||||
ytdl.getInfo = getInfo.getFullInfo;
 | 
			
		||||
ytdl.chooseFormat = util.chooseFormat;
 | 
			
		||||
ytdl.filterFormats = util.filterFormats;
 | 
			
		||||
ytdl.validateID = util.validateID;
 | 
			
		||||
ytdl.validateURL = util.validateURL;
 | 
			
		||||
ytdl.getURLVideoID = util.getURLVideoID;
 | 
			
		||||
ytdl.getVideoID = util.getVideoID;
 | 
			
		||||
ytdl.cache = {
 | 
			
		||||
  sig: sig.cache,
 | 
			
		||||
  info: getInfo.cache,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const createStream = (options) => {
 | 
			
		||||
  const stream = new PassThrough({
 | 
			
		||||
    highWaterMark: options && options.highWaterMark || null,
 | 
			
		||||
  });
 | 
			
		||||
  stream.destroy = () => { stream._isDestroyed = true; };
 | 
			
		||||
  return stream;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Chooses a format to download.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {stream.Readable} stream
 | 
			
		||||
 * @param {Object} info
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 */
 | 
			
		||||
const downloadFromInfoCallback = (stream, info, options) => {
 | 
			
		||||
  options = options || {};
 | 
			
		||||
  const format = util.chooseFormat(info.formats, options);
 | 
			
		||||
  if (format instanceof Error) {
 | 
			
		||||
    // The caller expects this function to be async.
 | 
			
		||||
    setImmediate(() => {
 | 
			
		||||
      stream.emit('error', format);
 | 
			
		||||
    });
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  stream.emit('info', info, format);
 | 
			
		||||
  if (stream._isDestroyed) { return; }
 | 
			
		||||
 | 
			
		||||
  let contentLength, downloaded = 0;
 | 
			
		||||
  const ondata = (chunk) => {
 | 
			
		||||
    downloaded += chunk.length;
 | 
			
		||||
    stream.emit('progress', chunk.length, downloaded, contentLength);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let req;
 | 
			
		||||
  if (format.isHLS || format.isDashMPD) {
 | 
			
		||||
    req = m3u8stream(format.url, {
 | 
			
		||||
      chunkReadahead: +info.live_chunk_readahead,
 | 
			
		||||
      begin: options.begin || format.live && Date.now(),
 | 
			
		||||
      liveBuffer: options.liveBuffer,
 | 
			
		||||
      requestOptions: options.requestOptions,
 | 
			
		||||
      parser: format.isDashMPD ? 'dash-mpd' : 'm3u8',
 | 
			
		||||
      id: format.itag,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    req.on('progress', (segment, totalSegments) => {
 | 
			
		||||
      stream.emit('progress', segment.size, segment.num, totalSegments);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  } else {
 | 
			
		||||
    if (options.begin) {
 | 
			
		||||
      format.url += '&begin=' + parseTime.humanStr(options.begin);
 | 
			
		||||
    }
 | 
			
		||||
    let requestOptions = Object.assign({}, options.requestOptions, {
 | 
			
		||||
      maxReconnects: 5
 | 
			
		||||
    });
 | 
			
		||||
    if (options.range && (options.range.start || options.range.end)) {
 | 
			
		||||
      requestOptions.headers = Object.assign({}, requestOptions.headers, {
 | 
			
		||||
        Range: `bytes=${options.range.start || '0'}-${options.range.end || ''}`
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    req = request(format.url, requestOptions);
 | 
			
		||||
 | 
			
		||||
    req.on('response', (res) => {
 | 
			
		||||
      if (stream._isDestroyed) { return; }
 | 
			
		||||
      if (!contentLength) {
 | 
			
		||||
        contentLength = parseInt(res.headers['content-length'], 10);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    req.on('data', ondata);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  stream.destroy = () => {
 | 
			
		||||
    stream._isDestroyed = true;
 | 
			
		||||
    if (req.abort) req.abort();
 | 
			
		||||
    req.end();
 | 
			
		||||
    req.removeListener('data', ondata);
 | 
			
		||||
    req.unpipe();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Forward events from the request to the stream.
 | 
			
		||||
  [
 | 
			
		||||
    'abort', 'request', 'response', 'error', 'retry', 'reconnect'
 | 
			
		||||
  ].forEach((event) => {
 | 
			
		||||
    req.prependListener(event, (arg) => {
 | 
			
		||||
      stream.emit(event, arg); });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  req.pipe(stream);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Can be used to download video after its `info` is gotten through
 | 
			
		||||
 * `ytdl.getInfo()`. In case the user might want to look at the
 | 
			
		||||
 * `info` object before deciding to download.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} info
 | 
			
		||||
 * @param {!Object} options
 | 
			
		||||
 */
 | 
			
		||||
ytdl.downloadFromInfo = (info, options) => {
 | 
			
		||||
  const stream = createStream(options);
 | 
			
		||||
  if (!info.full) {
 | 
			
		||||
    throw Error('Cannot use `ytdl.downloadFromInfo()` when called ' +
 | 
			
		||||
      'with info from `ytdl.getBasicInfo()`');
 | 
			
		||||
  }
 | 
			
		||||
  setImmediate(() => {
 | 
			
		||||
    downloadFromInfoCallback(stream, info, options);
 | 
			
		||||
  });
 | 
			
		||||
  return stream;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										171
									
								
								node_modules/ytdl-core/lib/info-extras.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								node_modules/ytdl-core/lib/info-extras.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,171 @@
 | 
			
		||||
const qs          = require('querystring');
 | 
			
		||||
const url         = require('url');
 | 
			
		||||
const Entities    = require('html-entities').AllHtmlEntities;
 | 
			
		||||
const util        = require('./util');
 | 
			
		||||
const parseTime   = require('m3u8stream/lib/parse-time');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const VIDEO_URL = 'https://www.youtube.com/watch?v=';
 | 
			
		||||
const getMetaItem = (body, name) => {
 | 
			
		||||
  return util.between(body, `<meta itemprop="${name}" content="`, '">');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get video description from html
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} html
 | 
			
		||||
 * @return {string}
 | 
			
		||||
 */
 | 
			
		||||
exports.getVideoDescription = (html) => {
 | 
			
		||||
  const regex = /<p.*?id="eow-description".*?>(.+?)<\/p>[\n\r\s]*?<\/div>/im;
 | 
			
		||||
  const description = html.match(regex);
 | 
			
		||||
  return description ?
 | 
			
		||||
    Entities.decode(util.stripHTML(description[1])) : '';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get video media (extra information) from html
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} body
 | 
			
		||||
 * @return {Object}
 | 
			
		||||
 */
 | 
			
		||||
exports.getVideoMedia = (body) => {
 | 
			
		||||
  let mediainfo = util.between(body,
 | 
			
		||||
    '<div id="watch-description-extras">',
 | 
			
		||||
    '<div id="watch-discussion" class="branded-page-box yt-card">');
 | 
			
		||||
  if (mediainfo === '') {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const regexp = /<h4 class="title">([\s\S]*?)<\/h4>[\s\S]*?<ul .*?class=".*?watch-info-tag-list">[\s\S]*?<li>([\s\S]*?)<\/li>(?:\s*?<li>([\s\S]*?)<\/li>)?/g;
 | 
			
		||||
  const contentRegexp = /(?: - (\d{4}) \()?<a .*?(?:href="([^"]+)")?.*?>(.*?)<\/a>/;
 | 
			
		||||
  const imgRegexp = /<img src="([^"]+)".*?>/;
 | 
			
		||||
  const media = {};
 | 
			
		||||
 | 
			
		||||
  const image = imgRegexp.exec(mediainfo);
 | 
			
		||||
  if (image) {
 | 
			
		||||
    media.image = url.resolve(VIDEO_URL, image[1]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let match;
 | 
			
		||||
  while ((match = regexp.exec(mediainfo)) != null) {
 | 
			
		||||
    let [, key, value, detail] = match;
 | 
			
		||||
    key = Entities.decode(key).trim().replace(/\s/g, '_').toLowerCase();
 | 
			
		||||
    const content = contentRegexp.exec(value);
 | 
			
		||||
    if (content) {
 | 
			
		||||
      let [, year, mediaUrl, value2] = content;
 | 
			
		||||
      if (year) {
 | 
			
		||||
        media.year = parseInt(year);
 | 
			
		||||
      } else if (detail) {
 | 
			
		||||
        media.year = parseInt(detail);
 | 
			
		||||
      }
 | 
			
		||||
      value = value.slice(0, content.index);
 | 
			
		||||
      if (key !== 'game' || value2 !== 'YouTube Gaming') {
 | 
			
		||||
        value += value2;
 | 
			
		||||
      }
 | 
			
		||||
      media[key + '_url'] = url.resolve(VIDEO_URL, mediaUrl);
 | 
			
		||||
    }
 | 
			
		||||
    media[key] = Entities.decode(value);
 | 
			
		||||
  }
 | 
			
		||||
  return media;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get video Owner from html.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} body
 | 
			
		||||
 * @return {Object}
 | 
			
		||||
 */
 | 
			
		||||
const userRegexp = /<a href="\/user\/([^"]+)/;
 | 
			
		||||
const verifiedRegexp = /<span .*?(aria-label="Verified")(.*?(?=<\/span>))/;
 | 
			
		||||
exports.getAuthor = (body) => {
 | 
			
		||||
  let ownerinfo = util.between(body,
 | 
			
		||||
    '<div id="watch7-user-header" class=" spf-link ">',
 | 
			
		||||
    '<div id="watch8-action-buttons" class="watch-action-buttons clearfix">');
 | 
			
		||||
  if (ownerinfo === '') {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
  const channelName = Entities.decode(util.between(util.between(
 | 
			
		||||
    ownerinfo, '<div class="yt-user-info">', '</div>'), '>', '</a>'));
 | 
			
		||||
  const userMatch = ownerinfo.match(userRegexp);
 | 
			
		||||
  const verifiedMatch = ownerinfo.match(verifiedRegexp);
 | 
			
		||||
  const channelID = getMetaItem(body, 'channelId');
 | 
			
		||||
  const username = userMatch ? userMatch[1] : util.between(
 | 
			
		||||
    util.between(body, '<span itemprop="author"', '</span>'), '/user/', '">');
 | 
			
		||||
  return {
 | 
			
		||||
    id: channelID,
 | 
			
		||||
    name: channelName,
 | 
			
		||||
    avatar: url.resolve(VIDEO_URL, util.between(ownerinfo,
 | 
			
		||||
      'data-thumb="', '"')),
 | 
			
		||||
    verified: !!verifiedMatch,
 | 
			
		||||
    user: username,
 | 
			
		||||
    channel_url: 'https://www.youtube.com/channel/' + channelID,
 | 
			
		||||
    user_url: 'https://www.youtube.com/user/' + username,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get video published at from html.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} body
 | 
			
		||||
 * @return {string}
 | 
			
		||||
 */
 | 
			
		||||
exports.getPublished = (body) => {
 | 
			
		||||
  return Date.parse(getMetaItem(body, 'datePublished'));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get video published at from html.
 | 
			
		||||
 * Credits to https://github.com/paixaop.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} body
 | 
			
		||||
 * @return {Array.<Object>}
 | 
			
		||||
 */
 | 
			
		||||
exports.getRelatedVideos = (body) => {
 | 
			
		||||
  let jsonStr = util.between(body, '\'RELATED_PLAYER_ARGS\': ', ',\n');
 | 
			
		||||
  let watchNextJson, rvsParams, secondaryResults;
 | 
			
		||||
  try {
 | 
			
		||||
    jsonStr = JSON.parse(jsonStr);
 | 
			
		||||
    watchNextJson = JSON.parse(jsonStr.watch_next_response);
 | 
			
		||||
    rvsParams = jsonStr.rvs.split(',').map((e) => qs.parse(e));
 | 
			
		||||
    secondaryResults = watchNextJson.contents.twoColumnWatchNextResults.secondaryResults.secondaryResults.results;
 | 
			
		||||
  }
 | 
			
		||||
  catch (err) {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
  let videos = [];
 | 
			
		||||
  for (let result of secondaryResults) {
 | 
			
		||||
    let details = result.compactVideoRenderer;
 | 
			
		||||
    if (details) {
 | 
			
		||||
      try {
 | 
			
		||||
        let viewCount = details.viewCountText.simpleText;
 | 
			
		||||
        let shortViewCount = details.shortViewCountText.simpleText;
 | 
			
		||||
        let shortViewCountTest = /^\d/.test(shortViewCount);
 | 
			
		||||
        if (!shortViewCountTest) {
 | 
			
		||||
          let shortViewCountFromRVS = rvsParams.find((elem) => elem.id === details.videoId);
 | 
			
		||||
          if (shortViewCountFromRVS) {
 | 
			
		||||
            shortViewCount = shortViewCountFromRVS;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        viewCount = (/^\d/.test(viewCount) ? viewCount : shortViewCount).split(' ')[0];
 | 
			
		||||
        videos.push({
 | 
			
		||||
          id: details.videoId,
 | 
			
		||||
          title: details.title.simpleText,
 | 
			
		||||
          author: details.shortBylineText.runs[0].text,
 | 
			
		||||
          ucid: details.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId,
 | 
			
		||||
          author_thumbnail: details.channelThumbnail.thumbnails[0].url,
 | 
			
		||||
          short_view_count_text: shortViewCount.split(' ')[0],
 | 
			
		||||
          view_count: viewCount.replace(',', ''),
 | 
			
		||||
          length_seconds: Math.floor(parseTime.humanStr(details.lengthText.simpleText) / 1000)
 | 
			
		||||
        });
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return videos;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										384
									
								
								node_modules/ytdl-core/lib/info.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								node_modules/ytdl-core/lib/info.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,384 @@
 | 
			
		||||
const urllib      = require('url');
 | 
			
		||||
const querystring = require('querystring');
 | 
			
		||||
const sax         = require('sax');
 | 
			
		||||
const request     = require('miniget');
 | 
			
		||||
const util        = require('./util');
 | 
			
		||||
const extras      = require('./info-extras');
 | 
			
		||||
const sig         = require('./sig');
 | 
			
		||||
const FORMATS     = require('./formats');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const VIDEO_URL = 'https://www.youtube.com/watch?v=';
 | 
			
		||||
const EMBED_URL = 'https://www.youtube.com/embed/';
 | 
			
		||||
const VIDEO_EURL = 'https://youtube.googleapis.com/v/';
 | 
			
		||||
const INFO_HOST = 'www.youtube.com';
 | 
			
		||||
const INFO_PATH = '/get_video_info';
 | 
			
		||||
const KEYS_TO_SPLIT = [
 | 
			
		||||
  'fmt_list',
 | 
			
		||||
  'fexp',
 | 
			
		||||
  'watermark'
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets info from a video without getting additional formats.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} id
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @param {Function(Error, Object)} callback
 | 
			
		||||
 */
 | 
			
		||||
exports.getBasicInfo = (id, options, callback) => {
 | 
			
		||||
  // Try getting config from the video page first.
 | 
			
		||||
  const params = 'hl=' + (options.lang || 'en');
 | 
			
		||||
  let url = VIDEO_URL + id + '&' + params +
 | 
			
		||||
    '&bpctr=' + Math.ceil(Date.now() / 1000);
 | 
			
		||||
 | 
			
		||||
  // Remove header from watch page request.
 | 
			
		||||
  // Otherwise, it'll use a different framework for rendering content.
 | 
			
		||||
  const reqOptions = Object.assign({}, options.requestOptions);
 | 
			
		||||
  reqOptions.headers = Object.assign({}, reqOptions.headers, {
 | 
			
		||||
    'User-Agent': ''
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  request(url, reqOptions, (err, res, body) => {
 | 
			
		||||
    if (err) return callback(err);
 | 
			
		||||
 | 
			
		||||
    // Check if there are any errors with this video page.
 | 
			
		||||
    const unavailableMsg = util.between(body, '<div id="player-unavailable"', '>');
 | 
			
		||||
    if (unavailableMsg &&
 | 
			
		||||
        !/\bhid\b/.test(util.between(unavailableMsg, 'class="', '"'))) {
 | 
			
		||||
      // Ignore error about age restriction.
 | 
			
		||||
      if (!body.includes('<div id="watch7-player-age-gate-content"')) {
 | 
			
		||||
        return callback(Error(util.between(body,
 | 
			
		||||
          '<h1 id="unavailable-message" class="message">', '</h1>').trim()));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Parse out additional metadata from this page.
 | 
			
		||||
    const additional = {
 | 
			
		||||
      // Get the author/uploader.
 | 
			
		||||
      author: extras.getAuthor(body),
 | 
			
		||||
 | 
			
		||||
      // Get the day the vid was published.
 | 
			
		||||
      published: extras.getPublished(body),
 | 
			
		||||
 | 
			
		||||
      // Get description.
 | 
			
		||||
      description: extras.getVideoDescription(body),
 | 
			
		||||
 | 
			
		||||
      // Get media info.
 | 
			
		||||
      media: extras.getVideoMedia(body),
 | 
			
		||||
 | 
			
		||||
      // Get related videos.
 | 
			
		||||
      related_videos: extras.getRelatedVideos(body),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const jsonStr = util.between(body, 'ytplayer.config = ', '</script>');
 | 
			
		||||
    let config;
 | 
			
		||||
    if (jsonStr) {
 | 
			
		||||
      config = jsonStr.slice(0, jsonStr.lastIndexOf(';ytplayer.load'));
 | 
			
		||||
      gotConfig(id, options, additional, config, false, callback);
 | 
			
		||||
 | 
			
		||||
    } else {
 | 
			
		||||
      // If the video page doesn't work, maybe because it has mature content.
 | 
			
		||||
      // and requires an account logged in to view, try the embed page.
 | 
			
		||||
      url = EMBED_URL + id + '?' + params;
 | 
			
		||||
      request(url, options.requestOptions, (err, res, body) => {
 | 
			
		||||
        if (err) return callback(err);
 | 
			
		||||
        config = util.between(body, 't.setConfig({\'PLAYER_CONFIG\': ', /\}(,'|\}\);)/);
 | 
			
		||||
        gotConfig(id, options, additional, config, true, callback);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Object} info
 | 
			
		||||
 * @return {Array.<Object>}
 | 
			
		||||
 */
 | 
			
		||||
const parseFormats = (info) => {
 | 
			
		||||
  let formats = [];
 | 
			
		||||
  if (info.url_encoded_fmt_stream_map) {
 | 
			
		||||
    formats = formats.concat(info.url_encoded_fmt_stream_map.split(','));
 | 
			
		||||
  }
 | 
			
		||||
  if (info.adaptive_fmts) {
 | 
			
		||||
    formats = formats.concat(info.adaptive_fmts.split(','));
 | 
			
		||||
  }
 | 
			
		||||
  formats = formats.map((format) => querystring.parse(format));
 | 
			
		||||
  delete info.url_encoded_fmt_stream_map;
 | 
			
		||||
  delete info.adaptive_fmts;
 | 
			
		||||
  return formats;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Object} id
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @param {Object} additional
 | 
			
		||||
 * @param {Object} config
 | 
			
		||||
 * @param {boolean} fromEmbed
 | 
			
		||||
 * @param {Function(Error, Object)} callback
 | 
			
		||||
 */
 | 
			
		||||
const gotConfig = (id, options, additional, config, fromEmbed, callback) => {
 | 
			
		||||
  if (!config) {
 | 
			
		||||
    return callback(Error('Could not find player config'));
 | 
			
		||||
  }
 | 
			
		||||
  try {
 | 
			
		||||
    config = JSON.parse(config + (fromEmbed ? '}' : ''));
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    return callback(Error('Error parsing config: ' + err.message));
 | 
			
		||||
  }
 | 
			
		||||
  const url = urllib.format({
 | 
			
		||||
    protocol: 'https',
 | 
			
		||||
    host: INFO_HOST,
 | 
			
		||||
    pathname: INFO_PATH,
 | 
			
		||||
    query: {
 | 
			
		||||
      video_id: id,
 | 
			
		||||
      eurl: VIDEO_EURL + id,
 | 
			
		||||
      ps: 'default',
 | 
			
		||||
      gl: 'US',
 | 
			
		||||
      hl: (options.lang || 'en'),
 | 
			
		||||
      sts: config.sts,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  request(url, options.requestOptions, (err, res, body) => {
 | 
			
		||||
    if (err) return callback(err);
 | 
			
		||||
    let info = querystring.parse(body);
 | 
			
		||||
 | 
			
		||||
    if (!info.url_encoded_fmt_stream_map && !info.adaptive_fmts &&
 | 
			
		||||
      !info.config && (config.args.fmt_list ||
 | 
			
		||||
      config.args.url_encoded_fmt_stream_map || config.args.adaptive_fmts)) {
 | 
			
		||||
      info = config.args;
 | 
			
		||||
      info.no_embed_allowed = true;
 | 
			
		||||
    } else if (info.status === 'fail') {
 | 
			
		||||
      return callback(
 | 
			
		||||
        Error(`Code ${info.errorcode}: ${util.stripHTML(info.reason)}`));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const player_response = config.args.player_response || info.player_response;
 | 
			
		||||
    try {
 | 
			
		||||
      info.player_response = JSON.parse(player_response);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return callback(
 | 
			
		||||
        Error('Error parsing `player_response`: ' + err.message));
 | 
			
		||||
    }
 | 
			
		||||
    let playability = info.player_response.playabilityStatus;
 | 
			
		||||
    if (playability && playability.status === 'UNPLAYABLE') {
 | 
			
		||||
      return callback(Error(playability.reason));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Split some keys by commas.
 | 
			
		||||
    KEYS_TO_SPLIT.forEach((key) => {
 | 
			
		||||
      if (!info[key]) return;
 | 
			
		||||
      info[key] = info[key]
 | 
			
		||||
        .split(',')
 | 
			
		||||
        .filter((v) => v !== '');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    info.fmt_list = info.fmt_list ?
 | 
			
		||||
      info.fmt_list.map((format) => format.split('/')) : [];
 | 
			
		||||
 | 
			
		||||
    info.formats = parseFormats(info);
 | 
			
		||||
    // Add additional properties to info.
 | 
			
		||||
    Object.assign(info, additional, {
 | 
			
		||||
      video_id: id,
 | 
			
		||||
 | 
			
		||||
      // Give the standard link to the video.
 | 
			
		||||
      video_url: VIDEO_URL + id,
 | 
			
		||||
 | 
			
		||||
      // Copy over a few props from `player_response.videoDetails`
 | 
			
		||||
      // for backwards compatibility.
 | 
			
		||||
      title: info.player_response.videoDetails && info.player_response.videoDetails.title,
 | 
			
		||||
      length_seconds: info.player_response.videoDetails && info.player_response.videoDetails.lengthSeconds,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    info.age_restricted = fromEmbed;
 | 
			
		||||
    info.html5player = config.assets.js;
 | 
			
		||||
 | 
			
		||||
    callback(null, info);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets info from a video additional formats and deciphered URLs.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} id
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @param {Function(Error, Object)} callback
 | 
			
		||||
 */
 | 
			
		||||
exports.getFullInfo = (id, options, callback) => {
 | 
			
		||||
  return exports.getBasicInfo(id, options, (err, info) => {
 | 
			
		||||
    if (err) return callback(err);
 | 
			
		||||
    const hasManifest =
 | 
			
		||||
      info.player_response && info.player_response.streamingData && (
 | 
			
		||||
        info.player_response.streamingData.dashManifestUrl ||
 | 
			
		||||
        info.player_response.streamingData.hlsManifestUrl
 | 
			
		||||
      );
 | 
			
		||||
    if (info.formats.length || hasManifest) {
 | 
			
		||||
      const html5playerfile = urllib.resolve(VIDEO_URL, info.html5player);
 | 
			
		||||
      sig.getTokens(html5playerfile, options, (err, tokens) => {
 | 
			
		||||
        if (err) return callback(err);
 | 
			
		||||
 | 
			
		||||
        sig.decipherFormats(info.formats, tokens, options.debug);
 | 
			
		||||
        let funcs = [];
 | 
			
		||||
        if (hasManifest && info.player_response.streamingData.dashManifestUrl) {
 | 
			
		||||
          let url = info.player_response.streamingData.dashManifestUrl;
 | 
			
		||||
          funcs.push(getDashManifest.bind(null, url, options));
 | 
			
		||||
        }
 | 
			
		||||
        if (hasManifest && info.player_response.streamingData.hlsManifestUrl) {
 | 
			
		||||
          let url = info.player_response.streamingData.hlsManifestUrl;
 | 
			
		||||
          funcs.push(getM3U8.bind(null, url, options));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        util.parallel(funcs, (err, results) => {
 | 
			
		||||
          if (err) return callback(err);
 | 
			
		||||
          if (results[0]) { mergeFormats(info, results[0]); }
 | 
			
		||||
          if (results[1]) { mergeFormats(info, results[1]); }
 | 
			
		||||
 | 
			
		||||
          if (options.debug) {
 | 
			
		||||
            info.formats.forEach((format) => {
 | 
			
		||||
              const itag = format.itag;
 | 
			
		||||
              if (!FORMATS[itag]) {
 | 
			
		||||
                console.warn(`No format metadata for itag ${itag} found`);
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          info.formats.forEach(util.addFormatMeta);
 | 
			
		||||
          info.formats.sort(util.sortFormats);
 | 
			
		||||
          info.full = true;
 | 
			
		||||
          callback(null, info);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      callback(Error('This video is unavailable'));
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Merges formats from DASH or M3U8 with formats from video info page.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} info
 | 
			
		||||
 * @param {Object} formatsMap
 | 
			
		||||
 */
 | 
			
		||||
const mergeFormats = (info, formatsMap) => {
 | 
			
		||||
  info.formats.forEach((f) => {
 | 
			
		||||
    if (!formatsMap[f.itag]) {
 | 
			
		||||
      formatsMap[f.itag] = f;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  info.formats = [];
 | 
			
		||||
  for (let itag in formatsMap) { info.formats.push(formatsMap[itag]); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets additional DASH formats.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} url
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @param {Function(!Error, Array.<Object>)} callback
 | 
			
		||||
 */
 | 
			
		||||
const getDashManifest = (url, options, callback) => {
 | 
			
		||||
  let formats = {};
 | 
			
		||||
 | 
			
		||||
  const parser = sax.parser(false);
 | 
			
		||||
  parser.onerror = callback;
 | 
			
		||||
  parser.onopentag = (node) => {
 | 
			
		||||
    if (node.name === 'REPRESENTATION') {
 | 
			
		||||
      const itag = node.attributes.ID;
 | 
			
		||||
      formats[itag] = { itag, url };
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  parser.onend = () => { callback(null, formats); };
 | 
			
		||||
 | 
			
		||||
  const req = request(urllib.resolve(VIDEO_URL, url), options.requestOptions);
 | 
			
		||||
  req.setEncoding('utf8');
 | 
			
		||||
  req.on('error', callback);
 | 
			
		||||
  req.on('data', (chunk) => { parser.write(chunk); });
 | 
			
		||||
  req.on('end', parser.close.bind(parser));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets additional formats.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} url
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @param {Function(!Error, Array.<Object>)} callback
 | 
			
		||||
 */
 | 
			
		||||
const getM3U8 = (url, options, callback) => {
 | 
			
		||||
  url = urllib.resolve(VIDEO_URL, url);
 | 
			
		||||
  request(url, options.requestOptions, (err, res, body) => {
 | 
			
		||||
    if (err) return callback(err);
 | 
			
		||||
 | 
			
		||||
    let formats = {};
 | 
			
		||||
    body
 | 
			
		||||
      .split('\n')
 | 
			
		||||
      .filter((line) => /https?:\/\//.test(line))
 | 
			
		||||
      .forEach((line) => {
 | 
			
		||||
        const itag = line.match(/\/itag\/(\d+)\//)[1];
 | 
			
		||||
        formats[itag] = { itag: itag, url: line };
 | 
			
		||||
      });
 | 
			
		||||
    callback(null, formats);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Cached for getting basic/full info.
 | 
			
		||||
exports.cache = new Map();
 | 
			
		||||
exports.cache.timeout = 1000;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Cache get info functions.
 | 
			
		||||
// In case a user wants to get a video's info before downloading.
 | 
			
		||||
for (let fnName of ['getBasicInfo', 'getFullInfo']) {
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {string} link
 | 
			
		||||
   * @param {Object} options
 | 
			
		||||
   * @param {Function(Error, Object)} callback
 | 
			
		||||
   */
 | 
			
		||||
  const fn = exports[fnName];
 | 
			
		||||
  exports[fnName] = (link, options, callback) => {
 | 
			
		||||
    if (typeof options === 'function') {
 | 
			
		||||
      callback = options;
 | 
			
		||||
      options = {};
 | 
			
		||||
    } else if (!options) {
 | 
			
		||||
      options = {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!callback) {
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        exports[fnName](link, options, (err, info) => {
 | 
			
		||||
          if (err) return reject(err);
 | 
			
		||||
          resolve(info);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const id = util.getVideoID(link);
 | 
			
		||||
    if (id instanceof Error) return callback(id);
 | 
			
		||||
 | 
			
		||||
    const key = [fnName, id, options.lang].join('-');
 | 
			
		||||
    if (exports.cache.has(key)) {
 | 
			
		||||
      callback(null, exports.cache.get(key));
 | 
			
		||||
    } else {
 | 
			
		||||
      fn(id, options, (err, info) => {
 | 
			
		||||
        if (err) return callback(err);
 | 
			
		||||
        exports.cache.set(key, info);
 | 
			
		||||
        setTimeout(() => { exports.cache.delete(key); }, exports.cache.timeout);
 | 
			
		||||
        callback(null, info);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Export a few helpers.
 | 
			
		||||
exports.validateID = util.validateID;
 | 
			
		||||
exports.validateURL = util.validateURL;
 | 
			
		||||
exports.getURLVideoID = util.getURLVideoID;
 | 
			
		||||
exports.getVideoID = util.getVideoID;
 | 
			
		||||
							
								
								
									
										271
									
								
								node_modules/ytdl-core/lib/sig.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								node_modules/ytdl-core/lib/sig.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,271 @@
 | 
			
		||||
const url     = require('url');
 | 
			
		||||
const request = require('miniget');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// A shared cache to keep track of html5player.js tokens.
 | 
			
		||||
exports.cache = new Map();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extract signature deciphering tokens from html5player file.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} html5playerfile
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @param {Function(!Error, Array.<string>)} callback
 | 
			
		||||
 */
 | 
			
		||||
exports.getTokens = (html5playerfile, options, callback) => {
 | 
			
		||||
  let key, cachedTokens;
 | 
			
		||||
  const rs = /(?:html5)?player[-_]([a-zA-Z0-9\-_]+)(?:\.js|\/)/
 | 
			
		||||
    .exec(html5playerfile);
 | 
			
		||||
  if (rs) {
 | 
			
		||||
    key = rs[1];
 | 
			
		||||
    cachedTokens = exports.cache.get(key);
 | 
			
		||||
  } else {
 | 
			
		||||
    console.warn('Could not extract html5player key:', html5playerfile);
 | 
			
		||||
  }
 | 
			
		||||
  if (cachedTokens) {
 | 
			
		||||
    callback(null, cachedTokens);
 | 
			
		||||
  } else {
 | 
			
		||||
    request(html5playerfile, options.requestOptions, (err, res, body) => {
 | 
			
		||||
      if (err) return callback(err);
 | 
			
		||||
 | 
			
		||||
      const tokens = exports.extractActions(body);
 | 
			
		||||
      if (key && (!tokens || !tokens.length)) {
 | 
			
		||||
        callback(Error('Could not extract signature deciphering actions'));
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      exports.cache.set(key, tokens);
 | 
			
		||||
      callback(null, tokens);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Decipher a signature based on action tokens.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Array.<string>} tokens
 | 
			
		||||
 * @param {string} sig
 | 
			
		||||
 * @return {string}
 | 
			
		||||
 */
 | 
			
		||||
exports.decipher = (tokens, sig) => {
 | 
			
		||||
  sig = sig.split('');
 | 
			
		||||
  for (let i = 0, len = tokens.length; i < len; i++) {
 | 
			
		||||
    let token = tokens[i], pos;
 | 
			
		||||
    switch (token[0]) {
 | 
			
		||||
      case 'r':
 | 
			
		||||
        sig = sig.reverse();
 | 
			
		||||
        break;
 | 
			
		||||
      case 'w':
 | 
			
		||||
        pos = ~~token.slice(1);
 | 
			
		||||
        sig = swapHeadAndPosition(sig, pos);
 | 
			
		||||
        break;
 | 
			
		||||
      case 's':
 | 
			
		||||
        pos = ~~token.slice(1);
 | 
			
		||||
        sig = sig.slice(pos);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'p':
 | 
			
		||||
        pos = ~~token.slice(1);
 | 
			
		||||
        sig.splice(0, pos);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return sig.join('');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Swaps the first element of an array with one of given position.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Array.<Object>} arr
 | 
			
		||||
 * @param {number} position
 | 
			
		||||
 * @return {Array.<Object>}
 | 
			
		||||
 */
 | 
			
		||||
const swapHeadAndPosition = (arr, position) => {
 | 
			
		||||
  const first = arr[0];
 | 
			
		||||
  arr[0] = arr[position % arr.length];
 | 
			
		||||
  arr[position] = first;
 | 
			
		||||
  return arr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*';
 | 
			
		||||
const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`;
 | 
			
		||||
const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`;
 | 
			
		||||
const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`;
 | 
			
		||||
const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`;
 | 
			
		||||
const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`;
 | 
			
		||||
const jsEmptyStr = `(?:''|"")`;
 | 
			
		||||
const reverseStr = ':function\\(a\\)\\{' +
 | 
			
		||||
  '(?:return )?a\\.reverse\\(\\)' +
 | 
			
		||||
'\\}';
 | 
			
		||||
const sliceStr = ':function\\(a,b\\)\\{' +
 | 
			
		||||
  'return a\\.slice\\(b\\)' +
 | 
			
		||||
'\\}';
 | 
			
		||||
const spliceStr = ':function\\(a,b\\)\\{' +
 | 
			
		||||
  'a\\.splice\\(0,b\\)' +
 | 
			
		||||
'\\}';
 | 
			
		||||
const swapStr = ':function\\(a,b\\)\\{' +
 | 
			
		||||
  'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' +
 | 
			
		||||
'\\}';
 | 
			
		||||
const actionsObjRegexp = new RegExp(
 | 
			
		||||
  `var (${jsVarStr})=\\{((?:(?:` +
 | 
			
		||||
    jsKeyStr + reverseStr + '|' +
 | 
			
		||||
    jsKeyStr + sliceStr   + '|' +
 | 
			
		||||
    jsKeyStr + spliceStr  + '|' +
 | 
			
		||||
    jsKeyStr + swapStr +
 | 
			
		||||
  '),?\\r?\\n?)+)\\};'
 | 
			
		||||
);
 | 
			
		||||
const actionsFuncRegexp = new RegExp(`function(?: ${jsVarStr})?\\(a\\)\\{` +
 | 
			
		||||
    `a=a\\.split\\(${jsEmptyStr}\\);\\s*` +
 | 
			
		||||
    `((?:(?:a=)?${jsVarStr}` +
 | 
			
		||||
    jsPropStr +
 | 
			
		||||
    '\\(a,\\d+\\);)+)' +
 | 
			
		||||
    `return a\\.join\\(${jsEmptyStr}\\)` +
 | 
			
		||||
  '\\}'
 | 
			
		||||
);
 | 
			
		||||
const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm');
 | 
			
		||||
const sliceRegexp   = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm');
 | 
			
		||||
const spliceRegexp  = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm');
 | 
			
		||||
const swapRegexp    = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extracts the actions that should be taken to decipher a signature.
 | 
			
		||||
 *
 | 
			
		||||
 * This searches for a function that performs string manipulations on
 | 
			
		||||
 * the signature. We already know what the 3 possible changes to a signature
 | 
			
		||||
 * are in order to decipher it. There is
 | 
			
		||||
 *
 | 
			
		||||
 * * Reversing the string.
 | 
			
		||||
 * * Removing a number of characters from the beginning.
 | 
			
		||||
 * * Swapping the first character with another position.
 | 
			
		||||
 *
 | 
			
		||||
 * Note, `Array#slice()` used to be used instead of `Array#splice()`,
 | 
			
		||||
 * it's kept in case we encounter any older html5player files.
 | 
			
		||||
 *
 | 
			
		||||
 * After retrieving the function that does this, we can see what actions
 | 
			
		||||
 * it takes on a signature.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} body
 | 
			
		||||
 * @return {Array.<string>}
 | 
			
		||||
 */
 | 
			
		||||
exports.extractActions = (body) => {
 | 
			
		||||
  const objResult = actionsObjRegexp.exec(body);
 | 
			
		||||
  const funcResult = actionsFuncRegexp.exec(body);
 | 
			
		||||
  if (!objResult || !funcResult) { return null; }
 | 
			
		||||
 | 
			
		||||
  const obj      = objResult[1].replace(/\$/g, '\\$');
 | 
			
		||||
  const objBody  = objResult[2].replace(/\$/g, '\\$');
 | 
			
		||||
  const funcBody = funcResult[1].replace(/\$/g, '\\$');
 | 
			
		||||
 | 
			
		||||
  let result = reverseRegexp.exec(objBody);
 | 
			
		||||
  const reverseKey = result && result[1]
 | 
			
		||||
    .replace(/\$/g, '\\$')
 | 
			
		||||
    .replace(/\$|^'|^"|'$|"$/g, '');
 | 
			
		||||
  result = sliceRegexp.exec(objBody);
 | 
			
		||||
  const sliceKey = result && result[1]
 | 
			
		||||
    .replace(/\$/g, '\\$')
 | 
			
		||||
    .replace(/\$|^'|^"|'$|"$/g, '');
 | 
			
		||||
  result = spliceRegexp.exec(objBody);
 | 
			
		||||
  const spliceKey = result && result[1]
 | 
			
		||||
    .replace(/\$/g, '\\$')
 | 
			
		||||
    .replace(/\$|^'|^"|'$|"$/g, '');
 | 
			
		||||
  result = swapRegexp.exec(objBody);
 | 
			
		||||
  const swapKey = result && result[1]
 | 
			
		||||
    .replace(/\$/g, '\\$')
 | 
			
		||||
    .replace(/\$|^'|^"|'$|"$/g, '');
 | 
			
		||||
 | 
			
		||||
  const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`;
 | 
			
		||||
  const myreg = '(?:a=)?' + obj +
 | 
			
		||||
    `(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` +
 | 
			
		||||
    '\\(a,(\\d+)\\)';
 | 
			
		||||
  const tokenizeRegexp = new RegExp(myreg, 'g');
 | 
			
		||||
  const tokens = [];
 | 
			
		||||
  while ((result = tokenizeRegexp.exec(funcBody)) !== null) {
 | 
			
		||||
    let key = result[1] || result[2] || result[3];
 | 
			
		||||
    switch (key) {
 | 
			
		||||
      case swapKey:
 | 
			
		||||
        tokens.push('w' + result[4]);
 | 
			
		||||
        break;
 | 
			
		||||
      case reverseKey:
 | 
			
		||||
        tokens.push('r');
 | 
			
		||||
        break;
 | 
			
		||||
      case sliceKey:
 | 
			
		||||
        tokens.push('s' + result[4]);
 | 
			
		||||
        break;
 | 
			
		||||
      case spliceKey:
 | 
			
		||||
        tokens.push('p' + result[4]);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return tokens;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Object} format
 | 
			
		||||
 * @param {string} sig
 | 
			
		||||
 * @param {boolean} debug
 | 
			
		||||
 */
 | 
			
		||||
exports.setDownloadURL = (format, sig, debug) => {
 | 
			
		||||
  let decodedUrl;
 | 
			
		||||
  if (format.url) {
 | 
			
		||||
    decodedUrl = format.url;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (debug) {
 | 
			
		||||
      console.warn('Download url not found for itag ' + format.itag);
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    decodedUrl = decodeURIComponent(decodedUrl);
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    if (debug) {
 | 
			
		||||
      console.warn('Could not decode url: ' + err.message);
 | 
			
		||||
    }
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Make some adjustments to the final url.
 | 
			
		||||
  const parsedUrl = url.parse(decodedUrl, true);
 | 
			
		||||
 | 
			
		||||
  // Deleting the `search` part is necessary otherwise changes to
 | 
			
		||||
  // `query` won't reflect when running `url.format()`
 | 
			
		||||
  delete parsedUrl.search;
 | 
			
		||||
 | 
			
		||||
  let query = parsedUrl.query;
 | 
			
		||||
 | 
			
		||||
  // This is needed for a speedier download.
 | 
			
		||||
  // See https://github.com/fent/node-ytdl-core/issues/127
 | 
			
		||||
  query.ratebypass = 'yes';
 | 
			
		||||
  if (sig) {
 | 
			
		||||
    // When YouTube provides a `sp` parameter the signature `sig` must go
 | 
			
		||||
    // into the parameter it specifies.
 | 
			
		||||
    // See https://github.com/fent/node-ytdl-core/issues/417
 | 
			
		||||
    if (format.sp) {
 | 
			
		||||
      query[format.sp] = sig;
 | 
			
		||||
    } else {
 | 
			
		||||
      query.signature = sig;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  format.url = url.format(parsedUrl);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Applies `sig.decipher()` to all format URL's.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Array.<Object>} formats
 | 
			
		||||
 * @param {Array.<string>} tokens
 | 
			
		||||
 * @param {boolean} debug
 | 
			
		||||
 */
 | 
			
		||||
exports.decipherFormats = (formats, tokens, debug) => {
 | 
			
		||||
  formats.forEach((format) => {
 | 
			
		||||
    const sig = tokens && format.s ? exports.decipher(tokens, format.s) : null;
 | 
			
		||||
    exports.setDownloadURL(format, sig, debug);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										391
									
								
								node_modules/ytdl-core/lib/util.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										391
									
								
								node_modules/ytdl-core/lib/util.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,391 @@
 | 
			
		||||
const url      = require('url');
 | 
			
		||||
const FORMATS  = require('./formats');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Use these to help sort formats, higher is better.
 | 
			
		||||
const audioEncodingRanks = {
 | 
			
		||||
  mp3: 1,
 | 
			
		||||
  vorbis: 2,
 | 
			
		||||
  aac: 3,
 | 
			
		||||
  opus: 4,
 | 
			
		||||
  flac: 5,
 | 
			
		||||
};
 | 
			
		||||
const videoEncodingRanks = {
 | 
			
		||||
  'Sorenson H.283': 1,
 | 
			
		||||
  'MPEG-4 Visual': 2,
 | 
			
		||||
  'VP8': 3,
 | 
			
		||||
  'VP9': 4,
 | 
			
		||||
  'H.264': 5,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sort formats from highest quality to lowest.
 | 
			
		||||
 * By resolution, then video bitrate, then audio bitrate.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} a
 | 
			
		||||
 * @param {Object} b
 | 
			
		||||
 */
 | 
			
		||||
exports.sortFormats = (a, b) => {
 | 
			
		||||
  const ares = a.resolution ? parseInt(a.resolution.slice(0, -1), 10) : 0;
 | 
			
		||||
  const bres = b.resolution ? parseInt(b.resolution.slice(0, -1), 10) : 0;
 | 
			
		||||
  const afeats = ~~!!ares * 2 + ~~!!a.audioBitrate;
 | 
			
		||||
  const bfeats = ~~!!bres * 2 + ~~!!b.audioBitrate;
 | 
			
		||||
 | 
			
		||||
  const getBitrate = (c) => {
 | 
			
		||||
    if (c.bitrate) {
 | 
			
		||||
      let s = c.bitrate.split('-');
 | 
			
		||||
      return parseFloat(s[s.length - 1], 10);
 | 
			
		||||
    } else {
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const audioScore = (c) => {
 | 
			
		||||
    const abitrate = c.audioBitrate || 0;
 | 
			
		||||
    const aenc = audioEncodingRanks[c.audioEncoding] || 0;
 | 
			
		||||
    return abitrate + aenc / 10;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (afeats === bfeats) {
 | 
			
		||||
    if (ares === bres) {
 | 
			
		||||
      let avbitrate = getBitrate(a);
 | 
			
		||||
      let bvbitrate = getBitrate(b);
 | 
			
		||||
      if (avbitrate === bvbitrate) {
 | 
			
		||||
        let aascore = audioScore(a);
 | 
			
		||||
        let bascore = audioScore(b);
 | 
			
		||||
        if (aascore === bascore) {
 | 
			
		||||
          let avenc = videoEncodingRanks[a.encoding] || 0;
 | 
			
		||||
          let bvenc = videoEncodingRanks[b.encoding] || 0;
 | 
			
		||||
          return bvenc - avenc;
 | 
			
		||||
        } else {
 | 
			
		||||
          return bascore - aascore;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        return bvbitrate - avbitrate;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      return bres - ares;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    return bfeats - afeats;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Choose a format depending on the given options.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Array.<Object>} formats
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @return {Object|Error}
 | 
			
		||||
 */
 | 
			
		||||
exports.chooseFormat = (formats, options) => {
 | 
			
		||||
  if (typeof options.format === 'object') {
 | 
			
		||||
    return options.format;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (options.filter) {
 | 
			
		||||
    formats = exports.filterFormats(formats, options.filter);
 | 
			
		||||
    if (formats.length === 0) {
 | 
			
		||||
      return Error('No formats found with custom filter');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let format;
 | 
			
		||||
  const quality = options.quality || 'highest';
 | 
			
		||||
  const getBitrate = (f) => {
 | 
			
		||||
    let s = f.bitrate.split('-');
 | 
			
		||||
    return parseFloat(s[s.length - 1], 10);
 | 
			
		||||
  };
 | 
			
		||||
  switch (quality) {
 | 
			
		||||
    case 'highest':
 | 
			
		||||
      format = formats[0];
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'lowest':
 | 
			
		||||
      format = formats[formats.length - 1];
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'highestaudio':
 | 
			
		||||
      formats = exports.filterFormats(formats, 'audio');
 | 
			
		||||
      format = null;
 | 
			
		||||
      for (let f of formats) {
 | 
			
		||||
        if (!format
 | 
			
		||||
          || f.audioBitrate > format.audioBitrate
 | 
			
		||||
          || (f.audioBitrate === format.audioBitrate && format.encoding && !f.encoding))
 | 
			
		||||
          format = f;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'lowestaudio':
 | 
			
		||||
      formats = exports.filterFormats(formats, 'audio')
 | 
			
		||||
      format = null;
 | 
			
		||||
      for (let f of formats) {
 | 
			
		||||
        if (!format
 | 
			
		||||
          || f.audioBitrate < format.audioBitrate
 | 
			
		||||
          || (f.audioBitrate === format.audioBitrate && format.encoding && !f.encoding))
 | 
			
		||||
          format = f;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'highestvideo':
 | 
			
		||||
      formats = exports.filterFormats(formats, 'video');
 | 
			
		||||
      format = null;
 | 
			
		||||
      for (let f of formats) {
 | 
			
		||||
        if (!format
 | 
			
		||||
          || getBitrate(f) > getBitrate(format)
 | 
			
		||||
          || (getBitrate(f) === getBitrate(format) && format.audioEncoding && !f.audioEncoding))
 | 
			
		||||
          format = f;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'lowestvideo':
 | 
			
		||||
      formats = exports.filterFormats(formats, 'video')
 | 
			
		||||
      format = null;
 | 
			
		||||
      for (let f of formats) {
 | 
			
		||||
        if (!format
 | 
			
		||||
          || getBitrate(f) < getBitrate(format)
 | 
			
		||||
          || (getBitrate(f) === getBitrate(format) && format.audioEncoding && !f.audioEncoding))
 | 
			
		||||
          format = f;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    default: {
 | 
			
		||||
      let getFormat = (itag) => {
 | 
			
		||||
        return formats.find((format) => format.itag === '' + itag);
 | 
			
		||||
      };
 | 
			
		||||
      if (Array.isArray(quality)) {
 | 
			
		||||
        quality.find((q) => format = getFormat(q));
 | 
			
		||||
      } else {
 | 
			
		||||
        format = getFormat(quality);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!format) {
 | 
			
		||||
    return Error('No such format found: ' + quality);
 | 
			
		||||
  }
 | 
			
		||||
  return format;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Array.<Object>} formats
 | 
			
		||||
 * @param {Function} filter
 | 
			
		||||
 * @return {Array.<Object>}
 | 
			
		||||
 */
 | 
			
		||||
exports.filterFormats = (formats, filter) => {
 | 
			
		||||
  let fn;
 | 
			
		||||
  switch (filter) {
 | 
			
		||||
    case 'audioandvideo':
 | 
			
		||||
      fn = (format) => format.bitrate && format.audioBitrate;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'video':
 | 
			
		||||
      fn = (format) => format.bitrate;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'videoonly':
 | 
			
		||||
      fn = (format) => format.bitrate && !format.audioBitrate;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'audio':
 | 
			
		||||
      fn = (format) => format.audioBitrate;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 'audioonly':
 | 
			
		||||
      fn = (format) => !format.bitrate && format.audioBitrate;
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      if (typeof filter === 'function') {
 | 
			
		||||
        fn = filter;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw TypeError(`Given filter (${filter}) is not supported`);
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
  return formats.filter(fn);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * String#indexOf() that supports regex too.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} haystack
 | 
			
		||||
 * @param {string|RegExp} needle
 | 
			
		||||
 * @return {number}
 | 
			
		||||
 */
 | 
			
		||||
const indexOf = (haystack, needle) => {
 | 
			
		||||
  return needle instanceof RegExp ?
 | 
			
		||||
    haystack.search(needle) : haystack.indexOf(needle);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Extract string inbetween another.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} haystack
 | 
			
		||||
 * @param {string} left
 | 
			
		||||
 * @param {string} right
 | 
			
		||||
 * @return {string}
 | 
			
		||||
 */
 | 
			
		||||
exports.between = (haystack, left, right) => {
 | 
			
		||||
  let pos = indexOf(haystack, left);
 | 
			
		||||
  if (pos === -1) { return ''; }
 | 
			
		||||
  haystack = haystack.slice(pos + left.length);
 | 
			
		||||
  pos = indexOf(haystack, right);
 | 
			
		||||
  if (pos === -1) { return ''; }
 | 
			
		||||
  haystack = haystack.slice(0, pos);
 | 
			
		||||
  return haystack;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get video ID.
 | 
			
		||||
 *
 | 
			
		||||
 * There are a few type of video URL formats.
 | 
			
		||||
 *  - https://www.youtube.com/watch?v=VIDEO_ID
 | 
			
		||||
 *  - https://m.youtube.com/watch?v=VIDEO_ID
 | 
			
		||||
 *  - https://youtu.be/VIDEO_ID
 | 
			
		||||
 *  - https://www.youtube.com/v/VIDEO_ID
 | 
			
		||||
 *  - https://www.youtube.com/embed/VIDEO_ID
 | 
			
		||||
 *  - https://music.youtube.com/watch?v=VIDEO_ID
 | 
			
		||||
 *  - https://gaming.youtube.com/watch?v=VIDEO_ID
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} link
 | 
			
		||||
 * @return {string|Error}
 | 
			
		||||
 */
 | 
			
		||||
const validQueryDomains = new Set([
 | 
			
		||||
  'youtube.com',
 | 
			
		||||
  'www.youtube.com',
 | 
			
		||||
  'm.youtube.com',
 | 
			
		||||
  'music.youtube.com',
 | 
			
		||||
  'gaming.youtube.com',
 | 
			
		||||
]);
 | 
			
		||||
const validPathDomains = new Set([
 | 
			
		||||
  'youtu.be',
 | 
			
		||||
  'youtube.com',
 | 
			
		||||
  'www.youtube.com',
 | 
			
		||||
]);
 | 
			
		||||
exports.getURLVideoID = (link) => {
 | 
			
		||||
  const parsed = url.parse(link, true);
 | 
			
		||||
  let id = parsed.query.v;
 | 
			
		||||
  if (validPathDomains.has(parsed.hostname) && !id) {
 | 
			
		||||
    const paths = parsed.pathname.split('/');
 | 
			
		||||
    id = paths[paths.length - 1];
 | 
			
		||||
  } else if (parsed.hostname && !validQueryDomains.has(parsed.hostname)) {
 | 
			
		||||
    return Error('Not a YouTube domain');
 | 
			
		||||
  }
 | 
			
		||||
  if (!id) {
 | 
			
		||||
    return Error('No video id found: ' + link);
 | 
			
		||||
  }
 | 
			
		||||
  id = id.substring(0, 11);
 | 
			
		||||
  if (!exports.validateID(id)) {
 | 
			
		||||
    return TypeError(`Video id (${id}) does not match expected ` +
 | 
			
		||||
      `format (${idRegex.toString()})`);
 | 
			
		||||
  }
 | 
			
		||||
  return id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets video ID either from a url or by checking if the given string
 | 
			
		||||
 * matches the video ID format.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} str
 | 
			
		||||
 * @return {string|Error}
 | 
			
		||||
 */
 | 
			
		||||
exports.getVideoID = (str) => {
 | 
			
		||||
  if (exports.validateID(str)) {
 | 
			
		||||
    return str;
 | 
			
		||||
  } else {
 | 
			
		||||
    return exports.getURLVideoID(str);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns true if given id satifies YouTube's id format.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} id
 | 
			
		||||
 * @return {boolean}
 | 
			
		||||
 */
 | 
			
		||||
const idRegex = /^[a-zA-Z0-9-_]{11}$/;
 | 
			
		||||
exports.validateID = (id) => {
 | 
			
		||||
  return idRegex.test(id);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks wether the input string includes a valid id.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} string
 | 
			
		||||
 * @return {boolean}
 | 
			
		||||
 */
 | 
			
		||||
exports.validateURL = (string) => {
 | 
			
		||||
  return !(exports.getURLVideoID(string) instanceof Error);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Object} format
 | 
			
		||||
 */
 | 
			
		||||
exports.addFormatMeta = (format) => {
 | 
			
		||||
  const meta = FORMATS[format.itag];
 | 
			
		||||
  for (let key in meta) {
 | 
			
		||||
    format[key] = meta[key];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  format.live = /\/source\/yt_live_broadcast\//.test(format.url);
 | 
			
		||||
  format.isHLS = /\/manifest\/hls_(variant|playlist)\//.test(format.url);
 | 
			
		||||
  format.isDashMPD = /\/manifest\/dash\//.test(format.url);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get only the string from an HTML string.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} html
 | 
			
		||||
 * @return {string}
 | 
			
		||||
 */
 | 
			
		||||
exports.stripHTML = (html) => {
 | 
			
		||||
  return html
 | 
			
		||||
    .replace(/\n/g, ' ')
 | 
			
		||||
    .replace(/\s*<\s*br\s*\/?\s*>\s*/gi, '\n')
 | 
			
		||||
    .replace(/<\s*\/\s*p\s*>\s*<\s*p[^>]*>/gi, '\n')
 | 
			
		||||
    .replace(/<.*?>/gi, '')
 | 
			
		||||
    .trim();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Array.<Function>} funcs
 | 
			
		||||
 * @param {Function(!Error, Array.<Object>)} callback
 | 
			
		||||
 */
 | 
			
		||||
exports.parallel = (funcs, callback) => {
 | 
			
		||||
  let funcsDone = 0;
 | 
			
		||||
  let errGiven = false;
 | 
			
		||||
  let results = [];
 | 
			
		||||
  const len = funcs.length;
 | 
			
		||||
 | 
			
		||||
  const checkDone = (index, err, result) => {
 | 
			
		||||
    if (errGiven) { return; }
 | 
			
		||||
    if (err) {
 | 
			
		||||
      errGiven = true;
 | 
			
		||||
      callback(err);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    results[index] = result;
 | 
			
		||||
    if (++funcsDone === len) {
 | 
			
		||||
      callback(null, results);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (len > 0) {
 | 
			
		||||
    funcs.forEach((f, i) => { f(checkDone.bind(null, i)); });
 | 
			
		||||
  } else {
 | 
			
		||||
    callback(null, results);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user