Last active
June 25, 2025 08:51
-
-
Save garywill/6f65915db7a890a1d95b4aecf5a3cb24 to your computer and use it in GitHub Desktop.
Browser bookmarklet: get formats from youtube video page
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function extractAdaptiveFormatsInfo(in_formats) { | |
| function getStreamType(f) { | |
| const mimeType = f.mimeType || ''; | |
| if (mimeType.startsWith('audio/')) return 'audio'; | |
| if (mimeType.startsWith('video/')) { | |
| if (f.audioChannels && f.audioChannels > 0) { | |
| return 'video+audio'; | |
| } | |
| return 'video'; | |
| } | |
| return '-'; | |
| } | |
| function getFormat(mimeType) { | |
| if (!mimeType) return '-'; | |
| return mimeType.split(';')[0].split('/')[1] || '-'; | |
| } | |
| function getCodec(mimeType) { | |
| if (!mimeType) return '-'; | |
| const match = mimeType.match(/codecs="(.+?)"/); | |
| return match ? match[1] : '-'; | |
| } | |
| function prettyBitrate(bps) { | |
| if (bps == null || bps === '-') return '-'; | |
| if (bps >= 1_000_000) return (bps / 1_000_000).toFixed(2) + ' Mb'; | |
| if (bps >= 1_000) return (bps / 1_000).toFixed(1) + ' kb'; | |
| return bps + ' bps'; | |
| } | |
| function prettySize(size) { | |
| if (!size || isNaN(size)) return '-'; | |
| size = Number(size); | |
| if (size >= 1024 * 1024 * 1024) | |
| return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB'; | |
| if (size >= 1024 * 1024) | |
| return (size / (1024 * 1024)).toFixed(2) + ' MB'; | |
| if (size >= 1024) | |
| return (size / 1024).toFixed(1) + ' KB'; | |
| return size + ' B'; | |
| } | |
| function cleanQualityStr(q) { | |
| if (!q) return '-'; | |
| return String(q).replace(/^AUDIO_QUALITY_/, ''); | |
| } | |
| function pad(str, len) { | |
| str = String(str ?? '-'); | |
| return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length); | |
| } | |
| function getVideoAudioBitrate(f) { | |
| let videoBitrate = '-'; | |
| let audioBitrate = '-'; | |
| if (f.approximateAudioBitrate || f.audioBitrate) { | |
| audioBitrate = prettyBitrate(f.approximateAudioBitrate || f.audioBitrate); | |
| if (f.bitrate) { | |
| videoBitrate = prettyBitrate(f.bitrate - (f.approximateAudioBitrate || f.audioBitrate)); | |
| } | |
| } else if (getStreamType(f).startsWith('audio')) { | |
| audioBitrate = prettyBitrate(f.bitrate); | |
| } else if (getStreamType(f).startsWith('video')) { | |
| videoBitrate = prettyBitrate(f.bitrate); | |
| } | |
| return { videoBitrate, audioBitrate }; | |
| } | |
| let rows = in_formats.map(f => { | |
| const type = getStreamType(f); | |
| const bitrate = prettyBitrate(f.bitrate); | |
| const res = (f.width && f.height) ? `${f.width}x${f.height}` : '-'; | |
| const fps = f.fps ?? '-'; | |
| const format = getFormat(f.mimeType); | |
| const codec = getCodec(f.mimeType); | |
| let quality = f.qualityLabel || f.audioQuality || f.quality || '-'; | |
| quality = cleanQualityStr(quality); | |
| const size = prettySize(f.contentLength || f.approximateContentLength); | |
| const { videoBitrate, audioBitrate } = getVideoAudioBitrate(f); | |
| let audioSampleRate = f.audioSampleRate ? f.audioSampleRate + ' Hz' : '-'; | |
| let itagStr = f.itag; | |
| if (f.isDrc) itagStr = itagStr + '-drc'; | |
| let resSort = 0; | |
| if (f.width && f.height) resSort = f.width * f.height; | |
| let qualitySort = 0; | |
| if (f.qualityLabel) { | |
| const m = f.qualityLabel.match(/^(\d+)/); | |
| if (m) qualitySort = parseInt(m[1]); | |
| } else if (f.audioQuality) { | |
| if (/LOW/i.test(f.audioQuality)) qualitySort = 1; | |
| else if (/MEDIUM/i.test(f.audioQuality)) qualitySort = 2; | |
| else if (/HIGH/i.test(f.audioQuality)) qualitySort = 3; | |
| } else if (f.quality) { | |
| const m = String(f.quality).match(/^(\d+)/); | |
| if (m) qualitySort = parseInt(m[1]); | |
| } | |
| return { | |
| itag: itagStr, | |
| format, | |
| type, | |
| res, | |
| bitrate, | |
| videoBitrate, | |
| audioBitrate, | |
| audioSampleRate, | |
| fps, | |
| codec, | |
| quality, | |
| size, | |
| resSort, | |
| qualitySort | |
| }; | |
| }); | |
| rows.sort((a, b) => { | |
| if (a.resSort && b.resSort) return b.resSort - a.resSort; | |
| if (a.resSort) return -1; | |
| if (b.resSort) return 1; | |
| return b.qualitySort - a.qualitySort; | |
| }); | |
| let out = [ | |
| pad('itag', 10) + | |
| pad('format', 8) + | |
| pad('type', 12) + | |
| pad('resolution', 13) + | |
| pad('total br', 12) + | |
| pad('video br', 12) + | |
| pad('audio br', 12) + | |
| pad('audio rate', 12) + | |
| pad('fps', 5) + | |
| pad('codec', 18) + | |
| pad('quality', 12) + | |
| pad('size', 12) | |
| ]; | |
| out.push('-'.repeat(138)); | |
| rows.forEach(r => { | |
| out.push( | |
| pad(r.itag, 10) + | |
| pad(r.format, 8) + | |
| pad(r.type, 12) + | |
| pad(r.res, 13) + | |
| pad(r.bitrate, 12) + | |
| pad(r.videoBitrate, 12) + | |
| pad(r.audioBitrate, 12) + | |
| pad(r.audioSampleRate, 12) + | |
| pad(r.fps, 5) + | |
| pad(r.codec, 18) + | |
| pad(r.quality, 12) + | |
| pad(r.size, 12) | |
| ); | |
| }); | |
| return out.join('\n'); | |
| } | |
| // Usage example: | |
| var plyRes = document.getElementsByTagName("ytd-app")[0].data.playerResponse; | |
| var streamingData = plyRes.streamingData; | |
| var arr_formats = (streamingData.adaptiveFormats || []) | |
| .concat(streamingData.formats || []); | |
| console.log(extractAdaptiveFormatsInfo(arr_formats)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment