Skip to content

Instantly share code, notes, and snippets.

@garywill
Last active June 25, 2025 08:51
Show Gist options
  • Save garywill/6f65915db7a890a1d95b4aecf5a3cb24 to your computer and use it in GitHub Desktop.
Save garywill/6f65915db7a890a1d95b4aecf5a3cb24 to your computer and use it in GitHub Desktop.

Revisions

  1. garywill revised this gist Jun 25, 2025. 1 changed file with 38 additions and 19 deletions.
    57 changes: 38 additions & 19 deletions ytb-formats-bookmarklet.js
    Original file line number Diff line number Diff line change
    @@ -44,6 +44,21 @@ function extractAdaptiveFormatsInfo(in_formats) {
    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);
    @@ -56,12 +71,14 @@ function extractAdaptiveFormatsInfo(in_formats) {
    quality = cleanQualityStr(quality);
    const size = prettySize(f.contentLength || f.approximateContentLength);

    // 用于排序
    let resSort = 0;
    if (f.width && f.height) {
    resSort = f.width * f.height;
    }
    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+)/);
    @@ -75,19 +92,16 @@ function extractAdaptiveFormatsInfo(in_formats) {
    if (m) qualitySort = parseInt(m[1]);
    }

    // itag后加-drc
    let itagStr = f.itag;
    if (f.isDrc) {
    itagStr = itagStr + '-drc';
    }

    return {
    itag: itagStr,
    format,
    type,
    bitrate,
    res,
    bitrate,
    videoBitrate,
    audioBitrate,
    audioSampleRate,
    fps,
    format,
    codec,
    quality,
    size,
    @@ -96,7 +110,6 @@ function extractAdaptiveFormatsInfo(in_formats) {
    };
    });

    // Sort by resolution (descending); if no res, by qualitySort (descending)
    rows.sort((a, b) => {
    if (a.resSort && b.resSort) return b.resSort - a.resSort;
    if (a.resSort) return -1;
    @@ -106,24 +119,30 @@ function extractAdaptiveFormatsInfo(in_formats) {

    let out = [
    pad('itag', 10) +
    pad('format', 8) +
    pad('type', 12) +
    pad('bitrate', 12) +
    pad('resolution', 13) +
    pad('total br', 12) +
    pad('video br', 12) +
    pad('audio br', 12) +
    pad('audio rate', 12) +
    pad('fps', 5) +
    pad('format', 8) +
    pad('codec', 18) +
    pad('quality', 12) +
    pad('size', 12)
    ];
    out.push('-'.repeat(102));
    out.push('-'.repeat(138));
    rows.forEach(r => {
    out.push(
    pad(r.itag, 10) +
    pad(r.format, 8) +
    pad(r.type, 12) +
    pad(r.bitrate, 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.format, 8) +
    pad(r.codec, 18) +
    pad(r.quality, 12) +
    pad(r.size, 12)
  2. garywill revised this gist Jun 25, 2025. 1 changed file with 78 additions and 30 deletions.
    108 changes: 78 additions & 30 deletions ytb-formats-bookmarklet.js
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,12 @@
    function extractAdaptiveFormatsInfo(in_formats) {
    // 判断类型:区分“仅视频”“视频+音频”“仅音频”
    function getStreamType(f) {
    const mimeType = f.mimeType || '';
    if (mimeType.startsWith('audio/')) return '仅音频';
    if (mimeType.startsWith('audio/')) return 'audio';
    if (mimeType.startsWith('video/')) {
    // audioChannels > 0 视为含音频
    if (f.audioChannels && f.audioChannels > 0) {
    return '视频+音频';
    return 'video+audio';
    }
    return '仅视频';
    return 'video';
    }
    return '-';
    }
    @@ -46,46 +44,96 @@ function extractAdaptiveFormatsInfo(in_formats) {
    str = String(str ?? '-');
    return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length);
    }
    let out = [
    pad('itag', 6) +
    pad('类型', 10) +
    pad('码率', 12) +
    pad('分辨率', 12) +
    pad('帧率', 6) +
    pad('格式', 8) +
    pad('编码', 18) +
    pad('质量', 16) +
    pad('文件大小', 12)
    ];
    out.push('-'.repeat(100));
    in_formats.forEach(f => {

    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);
    // 只去掉AUDIO_QUALITY_前缀
    let quality = f.qualityLabel || f.audioQuality || f.quality || '-';
    quality = cleanQualityStr(quality);
    const size = prettySize(f.contentLength || f.approximateContentLength);

    // 用于排序
    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]);
    }

    // itag后加-drc
    let itagStr = f.itag;
    if (f.isDrc) {
    itagStr = itagStr + '-drc';
    }

    return {
    itag: itagStr,
    type,
    bitrate,
    res,
    fps,
    format,
    codec,
    quality,
    size,
    resSort,
    qualitySort
    };
    });

    // Sort by resolution (descending); if no res, by qualitySort (descending)
    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('type', 12) +
    pad('bitrate', 12) +
    pad('resolution', 13) +
    pad('fps', 5) +
    pad('format', 8) +
    pad('codec', 18) +
    pad('quality', 12) +
    pad('size', 12)
    ];
    out.push('-'.repeat(102));
    rows.forEach(r => {
    out.push(
    pad(f.itag, 6) +
    pad(type, 10) +
    pad(bitrate, 12) +
    pad(res, 12) +
    pad(fps, 6) +
    pad(format, 8) +
    pad(codec, 18) +
    pad(quality, 16) +
    pad(size, 12)
    pad(r.itag, 10) +
    pad(r.type, 12) +
    pad(r.bitrate, 12) +
    pad(r.res, 13) +
    pad(r.fps, 5) +
    pad(r.format, 8) +
    pad(r.codec, 18) +
    pad(r.quality, 12) +
    pad(r.size, 12)
    );
    });
    return out.join('\n');
    }


    var plyRes = document.getElementsByTagName("ytd-app")[0].data.playerResponse ;
    // Usage example:
    var plyRes = document.getElementsByTagName("ytd-app")[0].data.playerResponse;
    var streamingData = plyRes.streamingData;
    var arr_formats = (streamingData.adaptiveFormats || [])
    .concat(streamingData.formats || []);
  3. garywill created this gist Jun 25, 2025.
    92 changes: 92 additions & 0 deletions ytb-formats-bookmarklet.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,92 @@
    function extractAdaptiveFormatsInfo(in_formats) {
    // 判断类型:区分“仅视频”“视频+音频”“仅音频”
    function getStreamType(f) {
    const mimeType = f.mimeType || '';
    if (mimeType.startsWith('audio/')) return '仅音频';
    if (mimeType.startsWith('video/')) {
    // audioChannels > 0 视为含音频
    if (f.audioChannels && f.audioChannels > 0) {
    return '视频+音频';
    }
    return '仅视频';
    }
    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);
    }
    let out = [
    pad('itag', 6) +
    pad('类型', 10) +
    pad('码率', 12) +
    pad('分辨率', 12) +
    pad('帧率', 6) +
    pad('格式', 8) +
    pad('编码', 18) +
    pad('质量', 16) +
    pad('文件大小', 12)
    ];
    out.push('-'.repeat(100));
    in_formats.forEach(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);
    // 只去掉AUDIO_QUALITY_前缀
    let quality = f.qualityLabel || f.audioQuality || f.quality || '-';
    quality = cleanQualityStr(quality);
    const size = prettySize(f.contentLength || f.approximateContentLength);
    out.push(
    pad(f.itag, 6) +
    pad(type, 10) +
    pad(bitrate, 12) +
    pad(res, 12) +
    pad(fps, 6) +
    pad(format, 8) +
    pad(codec, 18) +
    pad(quality, 16) +
    pad(size, 12)
    );
    });
    return out.join('\n');
    }


    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));