mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-13 01:22:11 +00:00
feat(StreamingInfo): Label Voice Boost audio streams (#1105)
* feat(StreamingInfo): Label Voice Boost audio streams * chore(StreamingInfoOptions): Clarify vb desc a bit * fix(StreamingInfoOptions): Fix `label_original` description * chore: lint
This commit is contained in:
@@ -9,7 +9,7 @@ export interface StreamingInfoOptions {
|
||||
*/
|
||||
captions_format?: 'vtt' | 'ttml';
|
||||
/**
|
||||
* The label to use for the non-DRC streams when a video has DRC and streams.
|
||||
* The label to use for the non-DRC/VB streams when a video has DRC and Voice Boost streams.
|
||||
*
|
||||
* Defaults to `"Original"`
|
||||
*/
|
||||
@@ -27,7 +27,19 @@ export interface StreamingInfoOptions {
|
||||
* Defaults to `(audio_track_display_name) => audio_track_display_name + " (Stable Volume)"`
|
||||
*/
|
||||
label_drc_multiple?: (audio_track_display_name: string) => string;
|
||||
|
||||
/**
|
||||
* The label to use for the VB (Voice Boost) streams when a video has VB streams.
|
||||
*
|
||||
* Defaults to `"Voice Boost"`
|
||||
*/
|
||||
label_vb?: string;
|
||||
/**
|
||||
* A function that generates the label to use for the Voice Boost streams when a video has multiple audio tracks and Voice Boost streams.
|
||||
* The non-Voice Boost streams use the unmodified audio track label provided by YouTube.
|
||||
*
|
||||
* Defaults to `(audio_track_display_name) => audio_track_display_name + " (Voice Boost)"`
|
||||
*/
|
||||
label_vb_multiple?: (audio_track_display_name: string) => string;
|
||||
/**
|
||||
* If `true`, the generated manifest will contain URLs that are suitable for use with the SABR protocol.
|
||||
*/
|
||||
|
||||
@@ -157,15 +157,22 @@ interface DrcLabels {
|
||||
label_drc_multiple: (audio_track_display_name: string) => string;
|
||||
}
|
||||
|
||||
interface VbLabels {
|
||||
label_original: string;
|
||||
label_vb: string;
|
||||
label_vb_multiple: (audio_track_display_name: string) => string;
|
||||
}
|
||||
|
||||
function getFormatGroupings(formats: Format[], is_post_live_dvr: boolean) {
|
||||
const group_info = new Map<string, Format[]>();
|
||||
|
||||
const has_multiple_audio_tracks = formats.some((fmt) => !!fmt.audio_track);
|
||||
|
||||
for (const format of formats) {
|
||||
if ((!format.index_range || !format.init_range) && !format.is_type_otf && !is_post_live_dvr) {
|
||||
if (((!format.index_range || !format.init_range) && !format.is_type_otf && !is_post_live_dvr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mime_type = format.mime_type.split(';')[0];
|
||||
|
||||
// Codec without any profile or level information
|
||||
@@ -177,8 +184,9 @@ function getFormatGroupings(formats: Format[], is_post_live_dvr: boolean) {
|
||||
const audio_track_id = format.audio_track?.id || '';
|
||||
|
||||
const drc = format.is_drc ? 'drc' : '';
|
||||
const vb = format.is_vb ? 'vb' : '';
|
||||
|
||||
const group_id = `${mime_type}-${just_codec}-${color_info}-${audio_track_id}-${drc}`;
|
||||
const group_id = `${mime_type}-${just_codec}-${color_info}-${audio_track_id}-${drc}-${vb}`;
|
||||
|
||||
if (!group_info.has(group_id)) {
|
||||
group_info.set(group_id, []);
|
||||
@@ -324,7 +332,7 @@ async function getSegmentInfo(
|
||||
is_sabr?: boolean
|
||||
) {
|
||||
let transformed_url = '';
|
||||
|
||||
|
||||
if (is_sabr) {
|
||||
const formatKey = `${format.itag || ''}:${format.xtags || ''}`;
|
||||
transformed_url = `sabr://${format.has_video ? 'video' : 'audio'}?key=${formatKey}`;
|
||||
@@ -333,7 +341,7 @@ async function getSegmentInfo(
|
||||
url.searchParams.set('cpn', cpn || '');
|
||||
transformed_url = url_transformer(url).toString();
|
||||
}
|
||||
|
||||
|
||||
if (format.is_type_otf) {
|
||||
if (!actions)
|
||||
throw new InnertubeError('Unable to get segment durations for this OTF stream without an Actions instance', { format });
|
||||
@@ -417,6 +425,10 @@ async function getAudioRepresentation(
|
||||
uid_parts.push('drc');
|
||||
}
|
||||
|
||||
if (format.is_vb) {
|
||||
uid_parts.push('vb');
|
||||
}
|
||||
|
||||
const rep: AudioRepresentation = {
|
||||
uid: uid_parts.join('-'),
|
||||
bitrate: format.bitrate,
|
||||
@@ -444,7 +456,7 @@ function getTrackRoles(format: Format, has_drc_streams: boolean) {
|
||||
if (format.is_descriptive)
|
||||
roles.push('description');
|
||||
|
||||
if (format.is_drc)
|
||||
if (format.is_drc || format.is_vb)
|
||||
roles.push('enhanced-audio-intelligibility');
|
||||
|
||||
return roles;
|
||||
@@ -458,6 +470,7 @@ async function getAudioSet(
|
||||
cpn?: string,
|
||||
shared_post_live_dvr_info?: SharedPostLiveDvrInfo,
|
||||
drc_labels?: DrcLabels,
|
||||
vb_labels?: VbLabels,
|
||||
is_sabr?: boolean
|
||||
) {
|
||||
const first_format = formats[0];
|
||||
@@ -465,17 +478,27 @@ async function getAudioSet(
|
||||
const hoisted: string[] = [];
|
||||
|
||||
const has_drc_streams = !!drc_labels;
|
||||
const has_vb_streams = !!vb_labels;
|
||||
|
||||
let track_name;
|
||||
|
||||
if (audio_track) {
|
||||
if (has_drc_streams && first_format.is_drc) {
|
||||
track_name = drc_labels.label_drc_multiple(audio_track.display_name);
|
||||
} else if (has_vb_streams && first_format.is_vb) {
|
||||
track_name = vb_labels.label_vb_multiple(audio_track.display_name);
|
||||
} else {
|
||||
track_name = audio_track.display_name;
|
||||
}
|
||||
} else if (has_drc_streams) {
|
||||
track_name = first_format.is_drc ? drc_labels.label_drc : drc_labels.label_original;
|
||||
} else if (has_drc_streams || has_vb_streams) {
|
||||
if (has_drc_streams && first_format.is_drc) {
|
||||
track_name = drc_labels.label_drc;
|
||||
} else if (has_vb_streams && first_format.is_vb) {
|
||||
track_name = vb_labels.label_vb;
|
||||
} else {
|
||||
// Both use the same param, so it doesn't matter which one is defined here.
|
||||
track_name = (drc_labels || vb_labels)?.label_original;
|
||||
}
|
||||
}
|
||||
|
||||
const set: AudioSet = {
|
||||
@@ -868,8 +891,23 @@ export async function getStreamingInfo(
|
||||
});
|
||||
|
||||
let drc_labels: DrcLabels | undefined;
|
||||
let vb_labels: VbLabels | undefined;
|
||||
|
||||
if (audio_groups.flat().some((format) => format.is_drc)) {
|
||||
let hasDrc = false;
|
||||
let hasVb = false;
|
||||
|
||||
for (const ag of audio_groups.flat()) {
|
||||
if (hasDrc === false && ag.is_drc) {
|
||||
hasDrc = true;
|
||||
}
|
||||
|
||||
if (hasVb === false && ag.is_vb) {
|
||||
hasVb = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Put these audio fields in a shared object to reduce dups.
|
||||
if (hasDrc) {
|
||||
drc_labels = {
|
||||
label_original: options?.label_original || 'Original',
|
||||
label_drc: options?.label_drc || 'Stable Volume',
|
||||
@@ -877,7 +915,15 @@ export async function getStreamingInfo(
|
||||
};
|
||||
}
|
||||
|
||||
const audio_sets = await Promise.all(audio_groups.map((formats) => getAudioSet(formats, url_transformer, actions, player, cpn, shared_post_live_dvr_info, drc_labels, options?.is_sabr)));
|
||||
if (hasVb) {
|
||||
vb_labels = {
|
||||
label_original: options?.label_original || 'Original',
|
||||
label_vb: options?.label_vb || 'Voice Boost',
|
||||
label_vb_multiple: options?.label_vb_multiple || ((display_name) => `${display_name} (Voice Boost)`)
|
||||
};
|
||||
}
|
||||
|
||||
const audio_sets = await Promise.all(audio_groups.map((formats) => getAudioSet(formats, url_transformer, actions, player, cpn, shared_post_live_dvr_info, drc_labels, vb_labels, options?.is_sabr)));
|
||||
|
||||
const video_sets = await Promise.all(video_groups.map((formats) => getVideoSet(formats, url_transformer, player, actions, cpn, shared_post_live_dvr_info, options?.is_sabr)));
|
||||
|
||||
@@ -908,7 +954,7 @@ export async function getStreamingInfo(
|
||||
text_sets = getTextSets(caption_tracks, options.captions_format, url_transformer);
|
||||
}
|
||||
|
||||
const info : StreamingInfo = {
|
||||
const info: StreamingInfo = {
|
||||
getDuration,
|
||||
audio_sets,
|
||||
video_sets,
|
||||
|
||||
Reference in New Issue
Block a user