622 lines
20 KiB
TypeScript
622 lines
20 KiB
TypeScript
// ASF Objects
|
|
|
|
import * as Token from 'token-types';
|
|
import type { IGetToken, ITokenizer } from 'strtok3';
|
|
|
|
import * as util from '../common/Util.js';
|
|
import type { AnyTagValue, IPicture, ITag } from '../type.js';
|
|
import AsfGuid from './AsfGuid.js';
|
|
import { getParserForAttr, parseUnicodeAttr } from './AsfUtil.js';
|
|
import { AttachedPictureType } from '../id3v2/ID3v2Token.js';
|
|
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
|
|
export class AsfContentParseError extends makeUnexpectedFileContentError('ASF'){
|
|
}
|
|
|
|
/**
|
|
* Data Type: Specifies the type of information being stored. The following values are recognized.
|
|
*/
|
|
export const DataType = {
|
|
/**
|
|
* Unicode string. The data consists of a sequence of Unicode characters.
|
|
*/
|
|
UnicodeString: 0,
|
|
/**
|
|
* BYTE array. The type of data is implementation-specific.
|
|
*/
|
|
ByteArray: 1,
|
|
/**
|
|
* BOOL. The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values.
|
|
*/
|
|
Bool: 2,
|
|
/**
|
|
* DWORD. The data is 4 bytes long and should be interpreted as a 32-bit unsigned integer.
|
|
*/
|
|
DWord: 3,
|
|
/**
|
|
* QWORD. The data is 8 bytes long and should be interpreted as a 64-bit unsigned integer.
|
|
*/
|
|
QWord: 4,
|
|
/**
|
|
* WORD. The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer.
|
|
*/
|
|
Word: 5
|
|
}
|
|
export type DataType = typeof DataType[keyof typeof DataType]
|
|
|
|
/**
|
|
* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ee663575
|
|
*/
|
|
export interface IAsfObjectHeader {
|
|
|
|
/**
|
|
* A GUID that identifies the object. 128 bits
|
|
*/
|
|
objectId: AsfGuid,
|
|
|
|
/**
|
|
* The size of the object (64-bits)
|
|
*/
|
|
objectSize: number,
|
|
}
|
|
|
|
/**
|
|
* Interface for: 3. ASF top-level Header Object
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3
|
|
*/
|
|
export interface IAsfTopLevelObjectHeader extends IAsfObjectHeader {
|
|
numberOfHeaderObjects: number
|
|
}
|
|
|
|
/**
|
|
* Token for: 3. ASF top-level Header Object
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3
|
|
*/
|
|
export const TopLevelHeaderObjectToken: IGetToken<IAsfTopLevelObjectHeader, Uint8Array> = {
|
|
|
|
len: 30,
|
|
|
|
get: (buf, off): IAsfTopLevelObjectHeader => {
|
|
return {
|
|
objectId: AsfGuid.fromBin(buf, off),
|
|
objectSize: Number(Token.UINT64_LE.get(buf, off + 16)),
|
|
numberOfHeaderObjects: Token.UINT32_LE.get(buf, off + 24)
|
|
// Reserved: 2 bytes
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Token for: 3.1 Header Object (mandatory, one only)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_1
|
|
*/
|
|
export const HeaderObjectToken: IGetToken<IAsfObjectHeader, Uint8Array> = {
|
|
|
|
len: 24,
|
|
|
|
get: (buf, off): IAsfObjectHeader => {
|
|
return {
|
|
objectId: AsfGuid.fromBin(buf, off),
|
|
objectSize: Number(Token.UINT64_LE.get(buf, off + 16))
|
|
};
|
|
}
|
|
};
|
|
|
|
export abstract class State<T> implements IGetToken<T> {
|
|
|
|
public len: number;
|
|
|
|
constructor(header: IAsfObjectHeader) {
|
|
this.len = Number(header.objectSize) - HeaderObjectToken.len;
|
|
}
|
|
|
|
public abstract get(buf: Uint8Array, off: number): T;
|
|
|
|
protected postProcessTag(tags: ITag[], name: string, valueType: number, data: AnyTagValue) {
|
|
if (name === 'WM/Picture') {
|
|
tags.push({id: name, value: WmPictureToken.fromBuffer(data as Uint8Array)});
|
|
} else {
|
|
const parseAttr = getParserForAttr(valueType);
|
|
if (!parseAttr) {
|
|
throw new AsfContentParseError(`unexpected value headerType: ${valueType}`);
|
|
}
|
|
tags.push({id: name, value: parseAttr(data as Uint8Array)});
|
|
}
|
|
}
|
|
}
|
|
|
|
// ToDo: use ignore type
|
|
export class IgnoreObjectState extends State<unknown> {
|
|
|
|
public get(_buf: Uint8Array, _off: number): null {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interface for: 3.2: File Properties Object (mandatory, one only)
|
|
*
|
|
* The File Properties Object defines the global characteristics of the combined digital media streams found within the Data Object.
|
|
*/
|
|
export interface IFilePropertiesObject {
|
|
|
|
/**
|
|
* Specifies the unique identifier for this file.
|
|
* The value of this field shall be regenerated every time the file is modified in any way.
|
|
* The value of this field shall be identical to the value of the File ID field of the Data Object.
|
|
*/
|
|
fileId: AsfGuid,
|
|
|
|
/**
|
|
* Specifies the size, in bytes, of the entire file.
|
|
* The value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
|
|
*/
|
|
fileSize: bigint,
|
|
/**
|
|
* Specifies the date and time of the initial creation of the file. The value is given as the number of 100-nanosecond
|
|
* intervals since January 1, 1601, according to Coordinated Universal Time (Greenwich Mean Time). The value of this
|
|
* field may be invalid if the Broadcast Flag bit in the Flags field is set to 1.
|
|
*/
|
|
creationDate: bigint,
|
|
/**
|
|
* Specifies the number of Data Packet entries that exist within the Data Object. The value of this field is invalid
|
|
* if the Broadcast Flag bit in the Flags field is set to 1.
|
|
*/
|
|
dataPacketsCount: bigint,
|
|
/**
|
|
* Specifies the time needed to play the file in 100-nanosecond units.
|
|
* This value should include the duration (estimated, if an exact value is unavailable) of the the last media object
|
|
* in the presentation. The value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
|
|
*/
|
|
playDuration: bigint,
|
|
/**
|
|
* Specifies the time needed to send the file in 100-nanosecond units.
|
|
* This value should include the duration of the last packet in the content.
|
|
* The value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
|
|
* Players can ignore this value.
|
|
*/
|
|
sendDuration: bigint,
|
|
/**
|
|
* Specifies the amount of time to buffer data before starting to play the file, in millisecond units.
|
|
* If this value is nonzero, the Play Duration field and all of the payload Presentation Time fields have been offset
|
|
* by this amount. Therefore, player software must subtract the value in the preroll field from the play duration and
|
|
* presentation times to calculate their actual values. It follows that all payload Presentation Time fields need to
|
|
* be at least this value.
|
|
*/
|
|
preroll: bigint,
|
|
/**
|
|
* The flags
|
|
*/
|
|
flags: {
|
|
/**
|
|
* Specifies, if set, that a file is in the process of being created (for example, for recording applications),
|
|
* and thus that various values stored in the header objects are invalid. It is highly recommended that
|
|
* post-processing be performed to remove this condition at the earliest opportunity.
|
|
*/
|
|
broadcast: boolean,
|
|
/**
|
|
* Specifies, if set, that a file is seekable.
|
|
* Note that for files containing a single audio stream and a Minimum Data Packet Size field equal to the Maximum
|
|
* Data Packet Size field, this flag shall always be set to 1.
|
|
* For files containing a single audio stream and a video stream or mutually exclusive video streams,
|
|
* this flag is only set to 1 if the file contains a matching Simple Index Object for each regular video stream
|
|
* (that is, video streams that are not hidden according to the method described in section 8.2.2).
|
|
*/
|
|
seekable: boolean
|
|
},
|
|
/**
|
|
* Specifies the minimum Data Packet size in bytes. In general, the value of this field is invalid if the Broadcast
|
|
* Flag bit in the Flags field is set to 1.
|
|
* However, for the purposes of this specification, the values for the Minimum Data Packet Size and Maximum Data
|
|
* Packet Size fields shall be set to the same value, and this value should be set to the packet size, even when the
|
|
* Broadcast Flag in the Flags field is set to 1.
|
|
*/
|
|
minimumDataPacketSize: number,
|
|
/**
|
|
* Specifies the maximum Data Packet size in bytes.
|
|
* In general, the value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
|
|
* However, for the purposes of this specification, the values of the Minimum Data Packet Size and Maximum Data Packet
|
|
* Size fields shall be set to the same value,
|
|
* and this value should be set to the packet size, even when the Broadcast Flag field is set to 1.
|
|
*/
|
|
maximumDataPacketSize: number,
|
|
/**
|
|
* Specifies the maximum instantaneous bit rate in bits per second for the entire file.
|
|
* This shall equal the sum of the bit rates of the individual digital media streams.
|
|
* It shall be noted that the digital media stream includes ASF data packetization overhead as well as digital media
|
|
* data in payloads.
|
|
* Only those streams that have a free-standing Stream Properties Object in the header shall have their bit rates
|
|
* included in the sum;
|
|
* streams whose Stream Properties Object exists as part of an Extended Stream Properties Object in the Header
|
|
* Extension Object shall not have their bit rates included in this sum, except when this value would otherwise be 0.
|
|
*/
|
|
maximumBitrate: number,
|
|
}
|
|
|
|
/**
|
|
* Token for: 3.2: File Properties Object (mandatory, one only)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_2
|
|
*/
|
|
export class FilePropertiesObject extends State<IFilePropertiesObject> {
|
|
|
|
public static guid = AsfGuid.FilePropertiesObject;
|
|
|
|
public get(buf: Uint8Array, off: number): IFilePropertiesObject {
|
|
|
|
return {
|
|
fileId: AsfGuid.fromBin(buf, off),
|
|
fileSize: Token.UINT64_LE.get(buf, off + 16),
|
|
creationDate: Token.UINT64_LE.get(buf, off + 24),
|
|
dataPacketsCount: Token.UINT64_LE.get(buf, off + 32),
|
|
playDuration: Token.UINT64_LE.get(buf, off + 40),
|
|
sendDuration: Token.UINT64_LE.get(buf, off + 48),
|
|
preroll: Token.UINT64_LE.get(buf, off + 56),
|
|
flags: {
|
|
broadcast: util.getBit(buf, off + 64, 24),
|
|
seekable: util.getBit(buf, off + 64, 25)
|
|
},
|
|
// flagsNumeric: Token.UINT32_LE.get(buf, off + 64),
|
|
minimumDataPacketSize: Token.UINT32_LE.get(buf, off + 68),
|
|
maximumDataPacketSize: Token.UINT32_LE.get(buf, off + 72),
|
|
maximumBitrate: Token.UINT32_LE.get(buf, off + 76)
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interface for: 3.3 Stream Properties Object (mandatory, one per stream)
|
|
*/
|
|
export interface IStreamPropertiesObject {
|
|
|
|
/**
|
|
* Stream Type
|
|
*/
|
|
streamType: string | undefined,
|
|
|
|
/**
|
|
* Error Correction Type
|
|
*/
|
|
errorCorrectionType: AsfGuid,
|
|
|
|
}
|
|
|
|
/**
|
|
* Token for: 3.3 Stream Properties Object (mandatory, one per stream)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_3
|
|
*/
|
|
export class StreamPropertiesObject extends State<IStreamPropertiesObject> {
|
|
|
|
public static guid = AsfGuid.StreamPropertiesObject;
|
|
|
|
public get(buf: Uint8Array, off: number): IStreamPropertiesObject {
|
|
|
|
return {
|
|
streamType: AsfGuid.decodeMediaType(AsfGuid.fromBin(buf, off)),
|
|
errorCorrectionType: AsfGuid.fromBin(buf, off + 8)
|
|
// ToDo
|
|
};
|
|
}
|
|
}
|
|
|
|
export interface IHeaderExtensionObject {
|
|
reserved1: AsfGuid,
|
|
reserved2: number,
|
|
extensionDataSize: number
|
|
}
|
|
|
|
/**
|
|
* 3.4: Header Extension Object (mandatory, one only)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_4
|
|
*/
|
|
export class HeaderExtensionObject implements IGetToken<IHeaderExtensionObject> {
|
|
|
|
public static guid = AsfGuid.HeaderExtensionObject;
|
|
|
|
public len: number;
|
|
|
|
public constructor() {
|
|
this.len = 22;
|
|
}
|
|
|
|
public get(buf: Uint8Array, off: number): IHeaderExtensionObject {
|
|
const view = new DataView(buf.buffer, off);
|
|
return {
|
|
reserved1: AsfGuid.fromBin(buf, off),
|
|
reserved2: view.getUint16(16, true),
|
|
extensionDataSize: view.getUint16(18, true)
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 3.5: The Codec-List-Object interface.
|
|
*/
|
|
interface ICodecListObjectHeader {
|
|
entryCount: number
|
|
}
|
|
|
|
/**
|
|
* 3.5: The Codec List Object provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
|
|
*/
|
|
const CodecListObjectHeader: IGetToken<ICodecListObjectHeader> = {
|
|
len: 20,
|
|
get: (buf: Uint8Array, off: number): ICodecListObjectHeader => {
|
|
const view = new DataView(buf.buffer, off);
|
|
return {
|
|
entryCount: view.getUint16(16, true)
|
|
};
|
|
}
|
|
};
|
|
|
|
export interface ICodecEntry {
|
|
type: {
|
|
videoCodec: boolean,
|
|
audioCodec: boolean
|
|
},
|
|
codecName: string,
|
|
description: string,
|
|
information: Uint8Array
|
|
}
|
|
|
|
async function readString(tokenizer: ITokenizer): Promise<string> {
|
|
const length = await tokenizer.readNumber(Token.UINT16_LE);
|
|
return (await tokenizer.readToken(new Token.StringType(length * 2, 'utf-16le'))).replace('\0', '');
|
|
}
|
|
|
|
/**
|
|
* 3.5: Read the Codec-List-Object, which provides user-friendly information about the codecs and formats used to encode the content found in the ASF file.
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_5
|
|
*/
|
|
export async function readCodecEntries(tokenizer: ITokenizer): Promise<ICodecEntry[]> {
|
|
const codecHeader = await tokenizer.readToken(CodecListObjectHeader);
|
|
const entries: ICodecEntry[] = [];
|
|
for (let i = 0; i < codecHeader.entryCount; ++i) {
|
|
entries.push(await readCodecEntry(tokenizer));
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
async function readInformation(tokenizer: ITokenizer): Promise<Uint8Array> {
|
|
const length = await tokenizer.readNumber(Token.UINT16_LE);
|
|
const buf = new Uint8Array(length);
|
|
await tokenizer.readBuffer(buf);
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* Read Codec-Entries
|
|
* @param tokenizer
|
|
*/
|
|
async function readCodecEntry(tokenizer: ITokenizer): Promise<ICodecEntry> {
|
|
const type = await tokenizer.readNumber(Token.UINT16_LE);
|
|
return {
|
|
type: {
|
|
videoCodec: (type & 0x0001) === 0x0001,
|
|
audioCodec: (type & 0x0002) === 0x0002
|
|
},
|
|
codecName: await readString(tokenizer),
|
|
description: await readString(tokenizer),
|
|
information: await readInformation(tokenizer)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 3.10 Content Description Object (optional, one only)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_10
|
|
*/
|
|
export class ContentDescriptionObjectState extends State<ITag[]> {
|
|
|
|
public static guid = AsfGuid.ContentDescriptionObject;
|
|
|
|
private static contentDescTags = ['Title', 'Author', 'Copyright', 'Description', 'Rating'];
|
|
|
|
public get(buf: Uint8Array, off: number): ITag[] {
|
|
const tags: ITag[] = [];
|
|
|
|
const view = new DataView(buf.buffer, off);
|
|
let pos = 10;
|
|
for (let i = 0; i < ContentDescriptionObjectState.contentDescTags.length; ++i) {
|
|
const length = view.getUint16(i * 2, true);
|
|
if (length > 0) {
|
|
const tagName = ContentDescriptionObjectState.contentDescTags[i];
|
|
const end = pos + length;
|
|
tags.push({id: tagName, value: parseUnicodeAttr(buf.subarray(off + pos, off + end))});
|
|
pos = end;
|
|
}
|
|
}
|
|
return tags;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 3.11 Extended Content Description Object (optional, one only)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/03_asf_top_level_header_object.html#3_11
|
|
*/
|
|
export class ExtendedContentDescriptionObjectState extends State<ITag[]> {
|
|
|
|
public static guid = AsfGuid.ExtendedContentDescriptionObject;
|
|
|
|
public get(buf: Uint8Array, off: number): ITag[] {
|
|
const tags: ITag[] = [];
|
|
const view = new DataView(buf.buffer, off);
|
|
const attrCount = view.getUint16(0, true);
|
|
let pos = 2;
|
|
for (let i = 0; i < attrCount; i += 1) {
|
|
const nameLen = view.getUint16(pos, true);
|
|
pos += 2;
|
|
const name = parseUnicodeAttr(buf.subarray(off + pos, off + pos + nameLen));
|
|
pos += nameLen;
|
|
const valueType = view.getUint16(pos, true);
|
|
pos += 2;
|
|
const valueLen = view.getUint16(pos, true);
|
|
pos += 2;
|
|
const value = buf.subarray(off + pos, off + pos + valueLen);
|
|
pos += valueLen;
|
|
this.postProcessTag(tags, name, valueType, value);
|
|
}
|
|
return tags;
|
|
}
|
|
}
|
|
|
|
export interface IStreamName {
|
|
streamLanguageId: number,
|
|
streamName: string
|
|
}
|
|
|
|
/**
|
|
* 4.1 Extended Stream Properties Object (optional, 1 per media stream)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_1
|
|
*/
|
|
export interface IExtendedStreamPropertiesObject {
|
|
startTime: bigint,
|
|
endTime: bigint,
|
|
dataBitrate: number,
|
|
bufferSize: number,
|
|
initialBufferFullness: number,
|
|
alternateDataBitrate: number,
|
|
alternateBufferSize: number,
|
|
alternateInitialBufferFullness: number,
|
|
maximumObjectSize: number,
|
|
flags: {
|
|
reliableFlag: boolean,
|
|
seekableFlag: boolean,
|
|
resendLiveCleanpointsFlag: boolean
|
|
},
|
|
// flagsNumeric: Token.UINT32_LE.get(buf, off + 64),
|
|
streamNumber: number,
|
|
streamLanguageId: number,
|
|
averageTimePerFrame: number,
|
|
streamNameCount: number,
|
|
payloadExtensionSystems: number,
|
|
streamNames: IStreamName[],
|
|
streamPropertiesObject: number | null
|
|
}
|
|
|
|
/**
|
|
* 4.1 Extended Stream Properties Object (optional, 1 per media stream)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_1
|
|
*/
|
|
export class ExtendedStreamPropertiesObjectState extends State<IExtendedStreamPropertiesObject> {
|
|
|
|
public static guid = AsfGuid.ExtendedStreamPropertiesObject;
|
|
|
|
public get(buf: Uint8Array, off: number): IExtendedStreamPropertiesObject {
|
|
const view = new DataView(buf.buffer, off);
|
|
return {
|
|
startTime: Token.UINT64_LE.get(buf, off),
|
|
endTime: Token.UINT64_LE.get(buf, off + 8),
|
|
dataBitrate: view.getInt32(12, true),
|
|
bufferSize: view.getInt32(16, true),
|
|
initialBufferFullness: view.getInt32(20, true),
|
|
alternateDataBitrate: view.getInt32(24, true),
|
|
alternateBufferSize: view.getInt32(28, true),
|
|
alternateInitialBufferFullness: view.getInt32(32, true),
|
|
maximumObjectSize: view.getInt32(36, true),
|
|
flags: { // ToDo, check flag positions
|
|
reliableFlag: util.getBit(buf, off + 40, 0),
|
|
seekableFlag: util.getBit(buf, off + 40, 1),
|
|
resendLiveCleanpointsFlag: util.getBit(buf, off + 40, 2)
|
|
},
|
|
// flagsNumeric: Token.UINT32_LE.get(buf, off + 64),
|
|
streamNumber: view.getInt16(42, true),
|
|
streamLanguageId: view.getInt16(44, true),
|
|
averageTimePerFrame: view.getInt32(52, true),
|
|
streamNameCount: view.getInt32(54, true),
|
|
payloadExtensionSystems: view.getInt32(56, true),
|
|
streamNames: [], // ToDo
|
|
streamPropertiesObject: null
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 4.7 Metadata Object (optional, 0 or 1)
|
|
* Ref: http://drang.s4.xrea.com/program/tips/id3tag/wmp/04_objects_in_the_asf_header_extension_object.html#4_7
|
|
*/
|
|
export class MetadataObjectState extends State<ITag[]> {
|
|
|
|
public static guid = AsfGuid.MetadataObject;
|
|
|
|
public get(uint8Array: Uint8Array, off: number): ITag[] {
|
|
const tags: ITag[] = [];
|
|
|
|
const view = new DataView(uint8Array.buffer, off);
|
|
const descriptionRecordsCount = view.getUint16(0, true);
|
|
let pos =2;
|
|
for (let i = 0; i < descriptionRecordsCount; i += 1) {
|
|
pos += 4;
|
|
const nameLen = view.getUint16(pos, true);
|
|
pos += 2;
|
|
const dataType = view.getUint16(pos, true);
|
|
pos += 2;
|
|
const dataLen = view.getUint32(pos, true);
|
|
pos += 4;
|
|
const name = parseUnicodeAttr(uint8Array.subarray(off + pos, off + pos + nameLen));
|
|
pos += nameLen;
|
|
const data = uint8Array.subarray(off + pos, off + pos + dataLen);
|
|
pos += dataLen;
|
|
this.postProcessTag(tags, name, dataType, data);
|
|
}
|
|
return tags;
|
|
}
|
|
}
|
|
|
|
// 4.8 Metadata Library Object (optional, 0 or 1)
|
|
|
|
export class MetadataLibraryObjectState extends MetadataObjectState {
|
|
|
|
public static guid = AsfGuid.MetadataLibraryObject;
|
|
}
|
|
|
|
export interface IWmPicture extends IPicture {
|
|
type: string,
|
|
format: string,
|
|
description: string,
|
|
size: number
|
|
data: Uint8Array;
|
|
}
|
|
|
|
/**
|
|
* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd757977(v=vs.85).aspx
|
|
*/
|
|
export class WmPictureToken implements IGetToken<IWmPicture> {
|
|
|
|
public static fromBuffer(buffer: Uint8Array): IWmPicture {
|
|
const pic = new WmPictureToken(buffer.length);
|
|
return pic.get(buffer, 0);
|
|
}
|
|
|
|
public len: number;
|
|
|
|
constructor(len: number) {
|
|
this.len = len;
|
|
}
|
|
|
|
public get(buffer: Uint8Array, offset: number): IWmPicture {
|
|
const view = new DataView(buffer.buffer, offset);
|
|
|
|
const typeId = view.getUint8(0);
|
|
const size = view.getInt32(1, true);
|
|
let index = 5;
|
|
|
|
while (view.getUint16(index) !== 0) {
|
|
index += 2;
|
|
}
|
|
const format = new Token.StringType(index - 5, 'utf-16le').get(buffer, 5);
|
|
|
|
while (view.getUint16(index) !== 0) {
|
|
index += 2;
|
|
}
|
|
const description = new Token.StringType(index - 5, 'utf-16le').get(buffer, 5);
|
|
|
|
return {
|
|
type: AttachedPictureType[typeId as keyof typeof AttachedPictureType],
|
|
format,
|
|
description,
|
|
size,
|
|
data: buffer.slice(index + 4)
|
|
};
|
|
}
|
|
}
|