Skip to content

Commit

Permalink
feat: Add originalLanguage to the Track structure (shaka-project#5409)
Browse files Browse the repository at this point in the history
Shaka in most of places normalizes tracks' language code to be compliant with ISO 639-1 when possible. However, it does not do that all the time (i.e. normalization is missing in MSS parser) and there is no way to get value that has been explicitly set in a manifest. Moreover, documentation is misleading, as it claims that value is taken directly from a manifest.
Normalization should take place, specifically to easify PeriodCombiner algorithm and also to not break existing applications.
However, original value can be desired for some implementations.
This PR introduces new field to get original language value from the manifest.
  • Loading branch information
tykus160 committed Jul 18, 2023
1 parent 72e13c4 commit f53349f
Show file tree
Hide file tree
Showing 23 changed files with 139 additions and 41 deletions.
4 changes: 4 additions & 0 deletions externs/shaka/manifest.js
Expand Up @@ -344,6 +344,7 @@ shaka.extern.FetchCryptoKeysFunction;
* drmInfos: !Array.<shaka.extern.DrmInfo>,
* keyIds: !Set.<string>,
* language: string,
* originalLanguage: ?string,
* label: ?string,
* type: string,
* primary: boolean,
Expand Down Expand Up @@ -429,6 +430,9 @@ shaka.extern.FetchCryptoKeysFunction;
* The Stream's language, specified as a language code. <br>
* Audio stream's language must be identical to the language of the containing
* Variant.
* @property {?string} originalLanguage
* <i>Optional.</i> <br>
* The original language, if any, that appeared in the manifest.
* @property {?string} label
* The Stream's label, unique text that should describe the audio/text track.
* @property {string} type
Expand Down
3 changes: 3 additions & 0 deletions externs/shaka/offline.js
Expand Up @@ -123,6 +123,7 @@ shaka.extern.ManifestDB;
* hdr: (string|undefined),
* kind: (string|undefined),
* language: string,
* originalLanguage: (?string|undefined),
* label: ?string,
* width: ?number,
* height: ?number,
Expand Down Expand Up @@ -162,6 +163,8 @@ shaka.extern.ManifestDB;
* The kind of text stream; undefined for audio/video.
* @property {string} language
* The language of the stream; '' for video.
* @property {(?string|undefined)} originalLanguage
* The original language, if any, that appeared in the manifest.
* @property {?string} label
* The label of the stream; '' for video.
* @property {?number} width
Expand Down
13 changes: 10 additions & 3 deletions externs/shaka/player.js
Expand Up @@ -238,7 +238,8 @@ shaka.extern.BufferedInfo;
* originalVideoId: ?string,
* originalAudioId: ?string,
* originalTextId: ?string,
* originalImageId: ?string
* originalImageId: ?string,
* originalLanguage: ?string
* }}
*
* @description
Expand All @@ -260,8 +261,10 @@ shaka.extern.BufferedInfo;
* The bandwidth required to play the track, in bits/sec.
*
* @property {string} language
* The language of the track, or <code>'und'</code> if not given. This is the
* exact value provided in the manifest; it may need to be normalized.
* The language of the track, or <code>'und'</code> if not given. This value
* is normalized as follows - language part is always lowercase and translated
* to ISO-639-1 when possible, locale part is always uppercase,
* i.e. <code>'en-US'</code>.
* @property {?string} label
* The track label, which is unique text that should describe the track.
* @property {?string} kind
Expand Down Expand Up @@ -340,6 +343,10 @@ shaka.extern.BufferedInfo;
* @property {?string} originalImageId
* (image tracks only) The original ID of the image track, if any, as it
* appeared in the original manifest.
* @property {?string} originalLanguage
* The original language of the track, if any, as it appeared in the original
* manifest. This is the exact value provided in the manifest; for normalized
* value use <code>language</code> property.
* @exportDoc
*/
shaka.extern.Track;
Expand Down
9 changes: 7 additions & 2 deletions lib/dash/dash_parser.js
Expand Up @@ -1108,8 +1108,8 @@ shaka.dash.DashParser = class {
this.config_.dash.ignoreDrmInfo,
this.config_.dash.keySystemsByURI);

const language =
shaka.util.LanguageUtils.normalize(elem.getAttribute('lang') || 'und');
const language = shaka.util.LanguageUtils.normalize(
context.adaptationSet.language || 'und');

// This attribute is currently non-standard, but it is supported by Kaltura.
let label = elem.getAttribute('label');
Expand Down Expand Up @@ -1380,6 +1380,7 @@ shaka.dash.DashParser = class {
drmInfos: contentProtection.drmInfos,
keyIds,
language,
originalLanguage: context.adaptationSet.language,
label,
type: context.adaptationSet.contentType,
primary: isPrimary,
Expand Down Expand Up @@ -1569,6 +1570,7 @@ shaka.dash.DashParser = class {
pixelAspectRatio: pixelAspectRatio,
emsgSchemeIdUris: emsgSchemeIdUris,
id: elem.getAttribute('id'),
language: elem.getAttribute('lang'),
numChannels: numChannels,
audioSamplingRate: audioSamplingRate,
availabilityTimeOffset: availabilityTimeOffset,
Expand Down Expand Up @@ -1979,6 +1981,7 @@ shaka.dash.DashParser.RequestSegmentCallback;
* pixelAspectRatio: (string|undefined),
* emsgSchemeIdUris: !Array.<string>,
* id: ?string,
* language: ?string,
* numChannels: ?number,
* audioSamplingRate: ?number,
* availabilityTimeOffset: number
Expand Down Expand Up @@ -2014,6 +2017,8 @@ shaka.dash.DashParser.RequestSegmentCallback;
* emsg registered schemeIdUris.
* @property {?string} id
* The ID of the element.
* @property {?string} language
* The original language of the element.
* @property {?number} numChannels
* The number of audio channels, or null if unknown.
* @property {?number} audioSamplingRate
Expand Down
53 changes: 27 additions & 26 deletions lib/hls/hls_parser.js
Expand Up @@ -666,7 +666,7 @@ shaka.hls.HlsParser = class {
const type = basicInfo.type;
const mimeType = basicInfo.mimeType;
const codecs = basicInfo.codecs;
const language = basicInfo.language || 'und';
const languageValue = basicInfo.language;
const height = basicInfo.height;
const width = basicInfo.width;
const channelsCount = basicInfo.channelCount;
Expand All @@ -685,7 +685,7 @@ shaka.hls.HlsParser = class {

// Make the stream info, with those values.
const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
playlist, uri, uri, codecs, type, language, primary, name,
playlist, uri, uri, codecs, type, languageValue, primary, name,
channelsCount, closedCaptions, characteristics, forced, sampleRate,
spatialAudio, mimeType);
this.uriToStreamInfosMap_.set(uri, streamInfo);
Expand All @@ -698,7 +698,7 @@ shaka.hls.HlsParser = class {
// Wrap the stream from that stream info with a variant.
variants.push({
id: 0,
language: language,
language: this.getLanguage_(languageValue),
disabledUntilTime: 0,
primary: true,
audio: type == 'audio' ? streamInfo.stream : null,
Expand Down Expand Up @@ -1609,16 +1609,15 @@ shaka.hls.HlsParser = class {
}

/**
* Get the language value.
* Get the normalized language value.
*
* @param {!shaka.hls.Tag} tag
* @param {?string} languageValue
* @return {string}
* @private
*/
getLanguage_(tag) {
getLanguage_(languageValue) {
const LanguageUtils = shaka.util.LanguageUtils;
const languageValue = tag.getAttributeValue('LANGUAGE') || 'und';
return LanguageUtils.normalize(languageValue);
return LanguageUtils.normalize(languageValue || 'und');
}

/**
Expand Down Expand Up @@ -1751,7 +1750,8 @@ shaka.hls.HlsParser = class {
for (const tag of closedCaptionsTags) {
goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
'Should only be called on media tags!');
const language = this.getLanguage_(tag);
const languageValue = tag.getAttributeValue('LANGUAGE');
const language = this.getLanguage_(languageValue);

// The GROUP-ID value is a quoted-string that specifies the group to which
// the Rendition belongs.
Expand Down Expand Up @@ -1797,7 +1797,7 @@ shaka.hls.HlsParser = class {
return this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
}

const language = this.getLanguage_(tag);
const language = tag.getAttributeValue('LANGUAGE');
const name = tag.getAttributeValue('NAME');

// NOTE: According to the HLS spec, "DEFAULT=YES" requires "AUTOSELECT=YES".
Expand Down Expand Up @@ -1860,7 +1860,7 @@ shaka.hls.HlsParser = class {
return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
}

const language = this.getLanguage_(tag);
const language = tag.getAttributeValue('LANGUAGE');
const name = tag.getAttributeValue('NAME');

const characteristics = tag.getAttributeValue('CHARACTERISTICS');
Expand Down Expand Up @@ -1930,7 +1930,7 @@ shaka.hls.HlsParser = class {
const closedCaptions = this.getClosedCaptions_(tag, type);
const codecs = shaka.util.ManifestParserUtils.guessCodecs(type, allCodecs);
const streamInfo = this.createStreamInfo_(verbatimMediaPlaylistUri,
codecs, type, /* language= */ 'und', /* primary= */ false,
codecs, type, /* language= */ null, /* primary= */ false,
/* name= */ null, /* channelcount= */ null, closedCaptions,
/* characteristics= */ null, /* forced= */ false,
/* sampleRate= */ null, /* spatialAudio= */ false);
Expand All @@ -1949,7 +1949,7 @@ shaka.hls.HlsParser = class {
* @param {string} verbatimMediaPlaylistUri
* @param {string} codecs
* @param {string} type
* @param {string} language
* @param {?string} languageValue
* @param {boolean} primary
* @param {?string} name
* @param {?number} channelsCount
Expand All @@ -1961,7 +1961,7 @@ shaka.hls.HlsParser = class {
* @return {!shaka.hls.HlsParser.StreamInfo}
* @private
*/
createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, languageValue,
primary, name, channelsCount, closedCaptions, characteristics, forced,
sampleRate, spatialAudio) {
// TODO: Refactor, too many parameters
Expand All @@ -1971,9 +1971,9 @@ shaka.hls.HlsParser = class {
// This stream is lazy-loaded inside the createSegmentIndex function.
// So we start out with a stream object that does not contain the actual
// segment index, then download when createSegmentIndex is called.
const stream = this.makeStreamObject_(codecs, type, language, primary, name,
channelsCount, closedCaptions, characteristics, forced, sampleRate,
spatialAudio);
const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
name, channelsCount, closedCaptions, characteristics, forced,
sampleRate, spatialAudio);
const streamInfo = {
stream,
type,
Expand Down Expand Up @@ -2013,7 +2013,7 @@ shaka.hls.HlsParser = class {
const wasLive = this.isLive_();
const realStreamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
playlist, verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs,
type, language, primary, name, channelsCount, closedCaptions,
type, languageValue, primary, name, channelsCount, closedCaptions,
characteristics, forced, sampleRate, spatialAudio);
if (abortSignal.aborted) {
return;
Expand Down Expand Up @@ -2250,7 +2250,7 @@ shaka.hls.HlsParser = class {
* @param {string} absoluteMediaPlaylistUri
* @param {string} codecs
* @param {string} type
* @param {string} language
* @param {?string} languageValue
* @param {boolean} primary
* @param {?string} name
* @param {?number} channelsCount
Expand All @@ -2264,7 +2264,7 @@ shaka.hls.HlsParser = class {
* @private
*/
async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
absoluteMediaPlaylistUri, codecs, type, language, primary, name,
absoluteMediaPlaylistUri, codecs, type, languageValue, primary, name,
channelsCount, closedCaptions, characteristics, forced, sampleRate,
spatialAudio, mimeType = undefined) {
if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
Expand Down Expand Up @@ -2335,9 +2335,9 @@ shaka.hls.HlsParser = class {
const {nextMediaSequence, nextPart} =
this.getNextMediaSequenceAndPart_(mediaSequenceNumber, segments);

const stream = this.makeStreamObject_(codecs, type, language, primary, name,
channelsCount, closedCaptions, characteristics, forced, sampleRate,
spatialAudio);
const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
name, channelsCount, closedCaptions, characteristics, forced,
sampleRate, spatialAudio);
stream.segmentIndex = segmentIndex;
stream.encrypted = encrypted;
stream.drmInfos = drmInfos;
Expand Down Expand Up @@ -2414,7 +2414,7 @@ shaka.hls.HlsParser = class {
* manually on the object after creation.
* @param {string} codecs
* @param {string} type
* @param {string} language
* @param {?string} languageValue
* @param {boolean} primary
* @param {?string} name
* @param {?number} channelsCount
Expand All @@ -2426,7 +2426,7 @@ shaka.hls.HlsParser = class {
* @return {!shaka.extern.Stream}
* @private
*/
makeStreamObject_(codecs, type, language, primary, name, channelsCount,
makeStreamObject_(codecs, type, languageValue, primary, name, channelsCount,
closedCaptions, characteristics, forced, sampleRate, spatialAudio) {
// Fill out a "best-guess" mimeType, for now. It will be replaced once the
// stream is lazy-loaded.
Expand All @@ -2445,7 +2445,8 @@ shaka.hls.HlsParser = class {
encrypted: false,
drmInfos: [],
keyIds: new Set(),
language,
language: this.getLanguage_(languageValue),
originalLanguage: languageValue,
label: name, // For historical reasons, since before "originalId".
type,
primary,
Expand Down
4 changes: 3 additions & 1 deletion lib/mss/mss_parser.js
Expand Up @@ -17,6 +17,7 @@ goog.require('shaka.media.SegmentReference');
goog.require('shaka.mss.ContentProtection');
goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.util.Error');
goog.require('shaka.util.LanguageUtils');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.Mp4Generator');
goog.require('shaka.util.OperationManager');
Expand Down Expand Up @@ -513,7 +514,8 @@ shaka.mss.MssParser = class {
encrypted: drmInfos.length > 0,
drmInfos: drmInfos,
keyIds: new Set(),
language: lang || 'und',
language: shaka.util.LanguageUtils.normalize(lang || 'und'),
originalLanguage: lang,
label: '',
type: '',
primary: false,
Expand Down
1 change: 1 addition & 0 deletions lib/offline/indexeddb/v1_storage_cell.js
Expand Up @@ -159,6 +159,7 @@ shaka.offline.indexeddb.V1StorageCell = class
hdr: undefined,
kind: old.kind,
language: old.language,
originalLanguage: old.language || null,
label: old.label,
width: old.width,
height: old.height,
Expand Down
1 change: 1 addition & 0 deletions lib/offline/indexeddb/v2_storage_cell.js
Expand Up @@ -108,6 +108,7 @@ shaka.offline.indexeddb.V2StorageCell = class
hdr: undefined,
kind: old.kind,
language: old.language,
originalLanguage: old.language || null,
label: old.label,
width: old.width,
height: old.height,
Expand Down
1 change: 1 addition & 0 deletions lib/offline/manifest_converter.js
Expand Up @@ -194,6 +194,7 @@ shaka.offline.ManifestConverter = class {
drmInfos: [],
keyIds: streamDB.keyIds,
language: streamDB.language,
originalLanguage: streamDB.originalLanguage || null,
label: streamDB.label,
type: streamDB.type,
primary: streamDB.primary,
Expand Down
1 change: 1 addition & 0 deletions lib/offline/storage.js
Expand Up @@ -1308,6 +1308,7 @@ shaka.offline.Storage = class {
hdr: stream.hdr,
kind: stream.kind,
language: stream.language,
originalLanguage: stream.originalLanguage,
label: stream.label,
width: stream.width || null,
height: stream.height || null,
Expand Down
7 changes: 6 additions & 1 deletion lib/player.js
Expand Up @@ -2382,6 +2382,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
drmInfos: [], // Filled in by DrmEngine config.
keyIds: new Set(),
language: 'und',
originalLanguage: null,
label: null,
type: ContentType.VIDEO,
primary: false,
Expand Down Expand Up @@ -4873,6 +4874,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
drmInfos: [],
keyIds: new Set(),
language: language,
originalLanguage: language,
label: label || null,
type: ContentType.TEXT,
primary: false,
Expand Down Expand Up @@ -5024,6 +5026,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
drmInfos: [],
keyIds: new Set(),
language: 'und',
originalLanguage: null,
label: null,
type: ContentType.IMAGE,
primary: false,
Expand Down Expand Up @@ -5433,6 +5436,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
// Add an empty segmentIndex, for the benefit of the period combiner
// in our builtin DASH parser.
const segmentIndex = new shaka.media.MetaSegmentIndex();
const language = video.closedCaptions.get(id);
const textStream = {
id: this.nextExternalStreamId_++, // A globally unique ID.
originalId: id, // The CC ID string, like 'CC1', 'CC3', etc.
Expand All @@ -5444,7 +5448,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
encrypted: false,
drmInfos: [],
keyIds: new Set(),
language: video.closedCaptions.get(id),
language,
originalLanguage: language,
label: null,
type: ContentType.TEXT,
primary: false,
Expand Down

0 comments on commit f53349f

Please sign in to comment.