Skip to content

Commit

Permalink
Merge branch 'main' of github.com:qualabs/common-media-library into f…
Browse files Browse the repository at this point in the history
…eature/cea-608-708

Signed-off-by: hernan <[email protected]>
  • Loading branch information
hernanr99 committed Mar 1, 2024
2 parents 7b87581 + 2c2206b commit 9cf6931
Show file tree
Hide file tree
Showing 14 changed files with 382 additions and 51 deletions.
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Unit tests for id3 feature helper functions [#74](https://github.com/streaming-video-technology-alliance/common-media-library/pull/74)
- CEA608/708 parser [#75](https://github.com/streaming-video-technology-alliance/common-media-library/pull/75)


## [0.6.3] - 2024-03-01

### Added
- Unit tests for ID3 feature helper functions [#74](https://github.com/streaming-video-technology-alliance/common-media-library/pull/74)
- ID3 APIC frame decode functionality, and it's respective unit tests [#77](https://github.com/streaming-video-technology-alliance/common-media-library/issues/77)


## [0.6.2] - 2023-01-18

### Fixed
Expand Down Expand Up @@ -173,7 +179,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bootstrap project [#2](https://github.com/streaming-video-technology-alliance/common-media-library/issues/2)


[Unreleased]\: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.6.2...HEAD
[Unreleased]\: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.6.3...HEAD
[0.6.3]\: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.6.2...v0.6.3
[0.6.2]\: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.6.1...v0.6.2
[0.6.1]\: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.6.0...v0.6.1
[0.6.0]\: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.5.1...v0.6.0
Expand Down
4 changes: 2 additions & 2 deletions dev/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@svta/common-media-library-dev",
"private": true,
"version": "0.6.2",
"version": "0.6.3",
"license": "Apache-2.0",
"type": "module",
"homepage": "https://github.com/streaming-video-technology-alliance/common-media-library",
Expand All @@ -17,6 +17,6 @@
"start": "wds --node-resolve --open --watch"
},
"devDependencies": {
"@web/dev-server": "0.4.2"
"@web/dev-server": "0.4.3"
}
}
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@svta/common-media-library-docs",
"private": true,
"version": "0.6.2",
"version": "0.6.3",
"license": "Apache-2.0",
"type": "module",
"homepage": "https://github.com/streaming-video-technology-alliance/common-media-library",
Expand Down
2 changes: 1 addition & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@svta/common-media-library",
"version": "0.6.2",
"version": "0.6.3",
"license": "Apache-2.0",
"type": "module",
"homepage": "https://github.com/streaming-video-technology-alliance/common-media-library",
Expand Down
7 changes: 6 additions & 1 deletion lib/src/id3/getId3Frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export function getId3Frames(id3Data: Uint8Array): Id3Frame[] {

while (isId3Header(id3Data, offset)) {
const size = readId3Size(id3Data, offset + 6);

if ((id3Data[offset + 5] >> 6) & 1) {
// skip extended header
offset += HEADER_FOOTER_SIZE;
}
// skip past ID3 header
offset += HEADER_FOOTER_SIZE;
const end = offset + size;
Expand All @@ -38,7 +43,7 @@ export function getId3Frames(id3Data: Uint8Array): Id3Frame[] {
}

// skip frame header and frame data
offset += frameData.size + 10;
offset += frameData.size + HEADER_FOOTER_SIZE;
}

if (isId3Footer(id3Data, offset)) {
Expand Down
5 changes: 5 additions & 0 deletions lib/src/id3/util/decodeId3Frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RawId3Frame } from './RawFrame.js';
import { decodeId3PrivFrame } from './decodeId3PrivFrame.js';
import { decodeId3TextFrame } from './decodeId3TextFrame.js';
import { decodeId3UrlFrame } from './decodeId3UrlFrame.js';
import { decodeId3ImageFrame } from './decodeId3ImageFrame.js';

/**
* Decode an ID3 frame.
Expand All @@ -23,5 +24,9 @@ export function decodeId3Frame(frame: RawId3Frame): Id3Frame | undefined {
return decodeId3UrlFrame(frame);
}

else if (frame.type === 'APIC') {
return decodeId3ImageFrame(frame);
}

return decodeId3TextFrame(frame);
}
69 changes: 69 additions & 0 deletions lib/src/id3/util/decodeId3ImageFrame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { DecodedId3Frame } from '../DecodedId3Frame.js';
import { RawId3Frame } from './RawFrame.js';
import { toUint8 } from './utf8.js';
import { toArrayBuffer } from './toArrayBuffer.js';
import { utf8ArrayToStr } from '../../utils.js';

interface MetadataFrame {
key: string;
description: string;
data: string | ArrayBuffer;
mimeType: string | null;
pictureType: number | null;
}

export function decodeId3ImageFrame(
frame: RawId3Frame
): DecodedId3Frame<string | ArrayBuffer> | undefined {
const metadataFrame: MetadataFrame = {
key: frame.type,
description: '',
data: '',
mimeType: null,
pictureType: null,
};

const utf8Encoding = 0x03;

if (frame.size < 2) {
return undefined;
}
if (frame.data[0] !== utf8Encoding) {
console.log('Ignore frame with unrecognized character ' + 'encoding');
return undefined;
}

const mimeTypeEndIndex = frame.data.subarray(1).indexOf(0);
if (mimeTypeEndIndex === -1) {
return undefined;
}
const mimeType = utf8ArrayToStr(toUint8(frame.data, 1, mimeTypeEndIndex));
const pictureType = frame.data[2 + mimeTypeEndIndex];
const descriptionEndIndex = frame.data
.subarray(3 + mimeTypeEndIndex)
.indexOf(0);
if (descriptionEndIndex === -1) {
return undefined;
}
const description = utf8ArrayToStr(
toUint8(frame.data, 3 + mimeTypeEndIndex, descriptionEndIndex)
);

let data;
if (mimeType === '-->') {
data = utf8ArrayToStr(
toUint8(frame.data, 4 + mimeTypeEndIndex + descriptionEndIndex)
);
}
else {
data = toArrayBuffer(
frame.data.subarray(4 + mimeTypeEndIndex + descriptionEndIndex)
);
}

metadataFrame.mimeType = mimeType;
metadataFrame.pictureType = pictureType;
metadataFrame.description = description;
metadataFrame.data = data;
return metadataFrame;
}
1 change: 0 additions & 1 deletion lib/src/id3/util/isId3Header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,5 @@ export function isId3Header(data: Uint8Array, offset: number): boolean {
}
}
}

return false;
}
26 changes: 26 additions & 0 deletions lib/src/id3/util/toArrayBuffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type TypedArray =
| Int8Array
| Uint8Array
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| Uint8ClampedArray;

export function toArrayBuffer(view: ArrayBuffer | TypedArray): ArrayBuffer {
if (view instanceof ArrayBuffer) {
return view;
}
else {
if (view.byteOffset == 0 && view.byteLength == view.buffer.byteLength) {
// This is a TypedArray over the whole buffer.
return view.buffer;
}
// This is a 'view' on the buffer. Create a new buffer that only contains
// the data. Note that since this isn't an ArrayBuffer, the 'new' call
// will allocate a new buffer to hold the copy.
return new Uint8Array(view).buffer;
}
}
40 changes: 40 additions & 0 deletions lib/src/id3/util/utf8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { isArrayBufferView } from 'util/types';

export function toUint8(
data: BufferSource,
offset: number = 0,
length: number = Infinity
) {
return view(data, offset, length, Uint8Array);
}

function view<T extends ArrayBufferView>(
data: BufferSource,
offset: number,
length: number,
Type: { new (buffer: ArrayBuffer, byteOffset: number, length: number): T }
): T {
const buffer = unsafeGetArrayBuffer(data);
let bytesPerElement: any = 1;
if ('BYTES_PER_ELEMENT' in Type) {
bytesPerElement = Type.BYTES_PER_ELEMENT;
}
// Absolute end of the |data| view within |buffer|.
const dataOffset = isArrayBufferView(data) ? data.byteOffset : 0;
const dataEnd = ((dataOffset) + data.byteLength) / bytesPerElement;
// Absolute start of the result within |buffer|.
const rawStart = ((dataOffset) + offset) / bytesPerElement;
const start = Math.floor(Math.max(0, Math.min(rawStart, dataEnd)));
// Absolute end of the result within |buffer|.
const end = Math.floor(Math.min(start + Math.max(length, 0), dataEnd));
return new Type(buffer, start, end - start);
}

function unsafeGetArrayBuffer(view: BufferSource) {
if (view instanceof ArrayBuffer) {
return view;
}
else {
return view.buffer;
}
}
124 changes: 95 additions & 29 deletions lib/test/id3/getId3Frames.test.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,119 @@
import { getId3Frames } from '@svta/common-media-library';
import { deepEqual } from 'node:assert';
import { deepEqual, deepStrictEqual } from 'node:assert';
import { describe, it } from 'node:test';
import { DATA, DATA_BYTES, DATA_UINT8 } from './data/DATA.js';
import { INFO, INFO_BYTES } from './data/INFO.js';
import { createId3 } from './data/createId3.js';
import {
generateId3,
generateId3Frame,
} from '../utils/id3/util/id3Generator.js';
import { toArrayBuffer } from '../../src/id3/util/toArrayBuffer.js';

describe('getId3Frames', () => {
it('no valid data produces empty output', () => {
deepStrictEqual(getId3Frames(new Uint8Array([])), []);
});

it('parse an APIC frame with image data', () => {
const apicValue = new Uint8Array([
3, 105, 109, 97, 103, 101, 47, 106, 112, 101, 103, 0, 3, 83, 104, 97, 107,
97, 0, 1, 2, 3,
]);
const apicFrame = generateId3Frame('APIC', apicValue);
const apicID3 = generateId3(apicFrame);
const expectedID3 = [
{
key: 'APIC',
mimeType: 'image/jpeg',
pictureType: 3,
description: 'Shaka',
data: toArrayBuffer(new Uint8Array([1, 2, 3])),
},
];
deepStrictEqual(getId3Frames(apicID3), expectedID3);
});

it('parse an APIC frame with image URL', () => {
const apicValue = new Uint8Array([
3, 45, 45, 62, 0, 3, 83, 104, 97, 107, 97, 0, 103, 111, 111, 103, 108,
101, 46, 99, 111, 109,
]);
const apicFrame = generateId3Frame('APIC', apicValue);
const apicID3 = generateId3(apicFrame);
const expectedID3 = [
{
key: 'APIC',
mimeType: '-->',
pictureType: 3,
description: 'Shaka',
data: 'google.com',
},
];
deepStrictEqual(getId3Frames(apicID3), expectedID3);
});

it('parses PRIV frames', () => {
const id3 = createId3('PRIV', new Uint8Array([...INFO_BYTES, 0x00, ...DATA_BYTES]));
deepEqual(getId3Frames(id3), [{
key: 'PRIV',
info: INFO,
data: DATA_UINT8.buffer,
}]);
const id3 = createId3(
'PRIV',
new Uint8Array([...INFO_BYTES, 0x00, ...DATA_BYTES])
);
deepEqual(getId3Frames(id3), [
{
key: 'PRIV',
info: INFO,
data: DATA_UINT8.buffer,
},
]);
});

it('parses TXXX frames', () => {
const id3 = createId3('TXXX', new Uint8Array([0x03, ...INFO_BYTES, 0x00, ...DATA_BYTES]));
deepEqual(getId3Frames(id3), [{
key: 'TXXX',
info: INFO,
data: DATA,
}]);
const id3 = createId3(
'TXXX',
new Uint8Array([0x03, ...INFO_BYTES, 0x00, ...DATA_BYTES])
);
deepEqual(getId3Frames(id3), [
{
key: 'TXXX',
info: INFO,
data: DATA,
},
]);
});

it('parses WXXX frames', () => {
const id3 = createId3('WXXX', new Uint8Array([0x03, ...INFO_BYTES, 0x00, ...DATA_BYTES]));
deepEqual(getId3Frames(id3), [{
key: 'WXXX',
info: INFO,
data: DATA,
}]);
const id3 = createId3(
'WXXX',
new Uint8Array([0x03, ...INFO_BYTES, 0x00, ...DATA_BYTES])
);
deepEqual(getId3Frames(id3), [
{
key: 'WXXX',
info: INFO,
data: DATA,
},
]);
});

it('parses TCOP frames', () => {
const id3 = createId3('TCOP', new Uint8Array([0x03, ...INFO_BYTES]));
deepEqual(getId3Frames(id3), [{
key: 'TCOP',
info: '',
data: INFO,
}]);
deepEqual(getId3Frames(id3), [
{
key: 'TCOP',
info: '',
data: INFO,
},
]);
});

it('parses WCOP frames', () => {
const id3 = createId3('WCOP', new Uint8Array([...INFO_BYTES]));
deepEqual(getId3Frames(id3), [{
key: 'WCOP',
info: '',
data: INFO,
}]);
deepEqual(getId3Frames(id3), [
{
key: 'WCOP',
info: '',
data: INFO,
},
]);
});
});
Loading

0 comments on commit 9cf6931

Please sign in to comment.