-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alvaro Velad
committed
Aug 16, 2022
1 parent
122f223
commit 153b727
Showing
4 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,300 @@ | ||
/*! @license | ||
* Shaka Player | ||
* Copyright 2022 Google LLC | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
goog.provide('shaka.util.Id3Utils'); | ||
|
||
goog.require('shaka.util.BufferUtils'); | ||
goog.require('shaka.util.StringUtils'); | ||
|
||
|
||
/** | ||
* @summary A set of Id3Utils utility functions. | ||
* @export | ||
*/ | ||
shaka.util.Id3Utils = class { | ||
/** | ||
* @param {Uint8Array} data | ||
* @param {number} offset | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
static isHeader_(data, offset) { | ||
/* | ||
* http://id3.org/id3v2.3.0 | ||
* [0] = 'I' | ||
* [1] = 'D' | ||
* [2] = '3' | ||
* [3,4] = {Version} | ||
* [5] = {Flags} | ||
* [6-9] = {ID3 Size} | ||
* | ||
* An ID3v2 tag can be detected with the following pattern: | ||
* $49 44 33 yy yy xx zz zz zz zz | ||
* Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80 | ||
*/ | ||
if (offset + 10 <= data.length) { | ||
// look for 'ID3' identifier | ||
if (data[offset] === 0x49 && | ||
data[offset + 1] === 0x44 && | ||
data[offset + 2] === 0x33) { | ||
// check version is within range | ||
if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { | ||
// check size is within range | ||
if (data[offset + 6] < 0x80 && | ||
data[offset + 7] < 0x80 && | ||
data[offset + 8] < 0x80 && | ||
data[offset + 9] < 0x80) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param {Uint8Array} data | ||
* @param {number} offset | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
static isFooter_(data, offset) { | ||
/* | ||
* The footer is a copy of the header, but with a different identifier | ||
*/ | ||
if (offset + 10 <= data.length) { | ||
// look for '3DI' identifier | ||
if (data[offset] === 0x33 && | ||
data[offset + 1] === 0x44 && | ||
data[offset + 2] === 0x49) { | ||
// check version is within range | ||
if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { | ||
// check size is within range | ||
if (data[offset + 6] < 0x80 && | ||
data[offset + 7] < 0x80 && | ||
data[offset + 8] < 0x80 && | ||
data[offset + 9] < 0x80) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* @param {Uint8Array} data | ||
* @param {number} offset | ||
* @return {number} | ||
* @private | ||
*/ | ||
static readSize_(data, offset) { | ||
let size = 0; | ||
size = (data[offset] & 0x7f) << 21; | ||
size |= (data[offset + 1] & 0x7f) << 14; | ||
size |= (data[offset + 2] & 0x7f) << 7; | ||
size |= data[offset + 3] & 0x7f; | ||
return size; | ||
} | ||
|
||
/** | ||
* @param {Uint8Array} data | ||
* @return {shaka.extern.MetadataRawFrame} | ||
* @private | ||
*/ | ||
static getFrameData_(data) { | ||
/* | ||
* Frame ID $xx xx xx xx (four characters) | ||
* Size $xx xx xx xx | ||
* Flags $xx xx | ||
*/ | ||
const type = String.fromCharCode(data[0], data[1], data[2], data[3]); | ||
const size = shaka.util.Id3Utils.readSize_(data, 4); | ||
|
||
// skip frame id, size, and flags | ||
const offset = 10; | ||
|
||
return { | ||
type, | ||
size, | ||
data: data.subarray(offset, offset + size), | ||
}; | ||
} | ||
|
||
/** | ||
* @param {shaka.extern.MetadataRawFrame} frame | ||
* @return {?shaka.extern.MetadataFrame} | ||
* @private | ||
*/ | ||
static decodeFrame_(frame) { | ||
const Id3Utils = shaka.util.Id3Utils; | ||
if (frame.type === 'PRIV') { | ||
return Id3Utils.decodePrivFrame_(frame); | ||
} else if (frame.type[0] === 'W') { | ||
return Id3Utils.decodeURLFrame_(frame); | ||
} | ||
|
||
return Id3Utils.decodeTextFrame_(frame); | ||
} | ||
|
||
/** | ||
* @param {shaka.extern.MetadataRawFrame} frame | ||
* @return {?shaka.extern.MetadataFrame} | ||
* @private | ||
*/ | ||
static decodePrivFrame_(frame) { | ||
/* | ||
* Format: <text string>\0<binary data> | ||
*/ | ||
if (frame.size < 2) { | ||
return null; | ||
} | ||
const StringUtils = shaka.util.StringUtils; | ||
const BufferUtils = shaka.util.BufferUtils; | ||
|
||
const owner = StringUtils.fromUTF8(frame.data); | ||
const data = BufferUtils.toArrayBuffer( | ||
frame.data.subarray(owner.length + 1)); | ||
|
||
return { | ||
id: frame.type, | ||
key: frame.type, | ||
description: owner, | ||
data: data, | ||
value: data, | ||
}; | ||
} | ||
|
||
/** | ||
* @param {shaka.extern.MetadataRawFrame} frame | ||
* @return {?shaka.extern.MetadataFrame} | ||
* @private | ||
*/ | ||
static decodeTextFrame_(frame) { | ||
const StringUtils = shaka.util.StringUtils; | ||
if (frame.size < 2) { | ||
return null; | ||
} | ||
|
||
if (frame.type === 'TXXX') { | ||
/* | ||
* Format: | ||
* [0] = {Text Encoding} | ||
* [1-?] = {Description}\0{Value} | ||
*/ | ||
let index = 1; | ||
const description = StringUtils.fromUTF8(frame.data.subarray(index)); | ||
|
||
index += description.length + 1; | ||
const value = StringUtils.fromUTF8(frame.data.subarray(index)); | ||
|
||
return { | ||
id: frame.type, | ||
key: frame.type, | ||
description: description, | ||
data: value, | ||
value: value, | ||
}; | ||
} | ||
/* | ||
* Format: | ||
* [0] = {Text Encoding} | ||
* [1-?] = {Value} | ||
*/ | ||
const text = StringUtils.fromUTF8(frame.data.subarray(1)); | ||
return { | ||
id: frame.type, | ||
key: frame.type, | ||
description: '', | ||
data: text, | ||
value: text, | ||
}; | ||
} | ||
|
||
/** | ||
* @param {shaka.extern.MetadataRawFrame} frame | ||
* @return {?shaka.extern.MetadataFrame} | ||
* @private | ||
*/ | ||
static decodeURLFrame_(frame) { | ||
const StringUtils = shaka.util.StringUtils; | ||
if (frame.type === 'WXXX') { | ||
/* | ||
* Format: | ||
* [0] = {Text Encoding} | ||
* [1-?] = {Description}\0{URL} | ||
*/ | ||
if (frame.size < 2) { | ||
return null; | ||
} | ||
|
||
let index = 1; | ||
const description = StringUtils.fromUTF8(frame.data.subarray(index)); | ||
|
||
index += description.length + 1; | ||
const value = StringUtils.fromUTF8(frame.data.subarray(index)); | ||
|
||
return { | ||
id: frame.type, | ||
key: frame.type, | ||
description: description, | ||
data: value, | ||
value: value, | ||
}; | ||
} | ||
/* | ||
* Format: | ||
* [0-?] = {URL} | ||
*/ | ||
const url = StringUtils.fromUTF8(frame.data); | ||
return { | ||
id: frame.type, | ||
key: frame.type, | ||
description: '', | ||
data: url, | ||
value: url, | ||
}; | ||
} | ||
|
||
/** | ||
* Returns an array of ID3 frames found in all the ID3 tags in the id3Data | ||
* @param {Uint8Array} id3Data - The ID3 data containing one or more ID3 tags | ||
* @return {!Array.<shaka.extern.MetadataFrame>} | ||
*/ | ||
static getID3Frames(id3Data) { | ||
const Id3Utils = shaka.util.Id3Utils; | ||
let offset = 0; | ||
const frames = []; | ||
|
||
while (offset < (id3Data.length - 1000)) { | ||
while (Id3Utils.isHeader_(id3Data, offset)) { | ||
const size = Id3Utils.readSize_(id3Data, offset + 6); | ||
// skip past ID3 header | ||
offset += 10; | ||
const end = offset + size; | ||
// loop through frames in the ID3 tag | ||
while (offset + 8 < end) { | ||
const frameData = Id3Utils.getFrameData_(id3Data.subarray(offset)); | ||
const frame = Id3Utils.decodeFrame_(frameData); | ||
if (frame) { | ||
frames.push(frame); | ||
} | ||
|
||
// skip frame header and frame data | ||
offset += frameData.size + 10; | ||
} | ||
|
||
if (Id3Utils.isFooter_(id3Data, offset)) { | ||
offset += 10; | ||
} | ||
} | ||
offset++; | ||
} | ||
return frames; | ||
} | ||
}; |