diff --git a/README.md b/README.md index 8ef3c9b7..bb403960 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ improved on the original TagLib, hence why this project exists. * [APE](http://wiki.hydrogenaud.io/index.php?title=APE_key): `AAC`, `APE`, `M2A`, `MP1`, `MP2`, `MP3` * [ID3v1](https://id3.org/ID3v1): `AAC`, `M2A`, `MP1`, `MP2`, `MP3` * [ID3v2](https://id3.org/Developer%20Information): `M2A`, `MP1`, `MP2`, `MP3` +* ... More coming soon + +## Supported Codecs +* Advanced Audio Codec (AAC): `AAC` +* MPEG-1/2 Audio: `M2A`, `MP1`, `MP2`, `MP3` +* MPEG-1/2 Video: `M2V`, `MPE`, `MPEG`, `MPG`, `MPV2` +* Monkey's Audio: `APE` +* ... More coming soon ## Installation ``` @@ -59,4 +67,5 @@ myFile.tag.title = "Time Won't Let Me Go"; myFile.tag.album = "The Sun And The Moon"; myFile.tag.performers = ["The Bravery"]; myFile.save(); +myFile.dispose(); ``` diff --git a/docs/README.md b/docs/README.md index fe137582..d95a7303 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,14 @@ improved on the original TagLib, hence why this project exists. * [APE](http://wiki.hydrogenaud.io/index.php?title=APE_key): `AAC`, `APE`, `M2A`, `MP1`, `MP2`, `MP3` * [ID3v1](https://id3.org/ID3v1): `AAC`, `M2A`, `MP1`, `MP2`, `MP3` * [ID3v2](https://id3.org/Developer%20Information): `M2A`, `MP1`, `MP2`, `MP3` +* ... More coming soon + +## Supported Codecs +* Advanced Audio Codec (AAC): `AAC` +* MPEG-1/2 Audio: `M2A`, `MP1`, `MP2`, `MP3` +* MPEG-1/2 Video: `M2V`, `MPE`, `MPEG`, `MPG`, `MPV2` +* Monkey's Audio: `APE` +* ... More coming soon ## Installation ``` @@ -63,4 +71,5 @@ myFile.tag.title = "Time Won't Let Me Go"; myFile.tag.album = "The Sun And The Moon"; myFile.tag.performers = ["The Bravery"]; myFile.save(); +myFile.dispose(); ``` diff --git a/docs/classes/_src_aac_aacaudioheader_.aacaudioheader.md b/docs/classes/_src_aac_aacaudioheader_.aacaudioheader.md index 6eefe747..2a8534c3 100644 --- a/docs/classes/_src_aac_aacaudioheader_.aacaudioheader.md +++ b/docs/classes/_src_aac_aacaudioheader_.aacaudioheader.md @@ -73,7 +73,7 @@ An empty an unset header • get **audioBitrate**(): number -Bitrate of the audio in kilibits per second represented by the current instance. +Bitrate of the audio in kilobits per second represented by the current instance. **`inheritdoc`** diff --git a/docs/classes/_src_ape_apestreamheader_.apestreamheader.md b/docs/classes/_src_ape_apestreamheader_.apestreamheader.md index 0bf63320..c3f71346 100644 --- a/docs/classes/_src_ape_apestreamheader_.apestreamheader.md +++ b/docs/classes/_src_ape_apestreamheader_.apestreamheader.md @@ -78,7 +78,7 @@ Size of a Monkey Audio Header • get **audioBitrate**(): number -Bitrate of the audio in kilibits per second represented by the current instance. +Bitrate of the audio in kilobits per second represented by the current instance. **`inheritdoc`** diff --git a/docs/classes/_src_bytevector_.bytevector.md b/docs/classes/_src_bytevector_.bytevector.md index 271cd7c6..06e75b84 100644 --- a/docs/classes/_src_bytevector_.bytevector.md +++ b/docs/classes/_src_bytevector_.bytevector.md @@ -912,7 +912,7 @@ method reads from the current offset of the stream, not the beginning of the str Name | Type | Default value | Description | ------ | ------ | ------ | ------ | `stream` | [IStream](../interfaces/_src_stream_.istream.md) | - | TagLibSharp-node internal stream object | -`isReadOnly` | boolean | false | Whether or not the bytevector is readonly | +`isReadOnly` | boolean | false | Whether or not the byte vector is readonly | **Returns:** [ByteVector](_src_bytevector_.bytevector.md) diff --git a/docs/classes/_src_errors_.unsupportedformaterror.md b/docs/classes/_src_errors_.unsupportedformaterror.md new file mode 100644 index 00000000..c1cb3c17 --- /dev/null +++ b/docs/classes/_src_errors_.unsupportedformaterror.md @@ -0,0 +1,95 @@ +**[node-taglib-sharp](../README.md)** + +> [Globals](../globals.md) / ["src/errors"](../modules/_src_errors_.md) / UnsupportedFormatError + +# Class: UnsupportedFormatError + +## Hierarchy + +* [Error](_src_errors_.corruptfileerror.md#error) + + ↳ **UnsupportedFormatError** + +## Index + +### Constructors + +* [constructor](_src_errors_.unsupportedformaterror.md#constructor) + +### Properties + +* [isNotSupportedError](_src_errors_.unsupportedformaterror.md#isnotsupportederror) +* [message](_src_errors_.unsupportedformaterror.md#message) +* [name](_src_errors_.unsupportedformaterror.md#name) +* [stack](_src_errors_.unsupportedformaterror.md#stack) +* [Error](_src_errors_.unsupportedformaterror.md#error) + +### Methods + +* [errorIs](_src_errors_.unsupportedformaterror.md#erroris) + +## Constructors + +### constructor + +\+ **new UnsupportedFormatError**(`message?`: string): [UnsupportedFormatError](_src_errors_.unsupportedformaterror.md) + +#### Parameters: + +Name | Type | +------ | ------ | +`message?` | string | + +**Returns:** [UnsupportedFormatError](_src_errors_.unsupportedformaterror.md) + +## Properties + +### isNotSupportedError + +• `Readonly` **isNotSupportedError**: boolean = true + +___ + +### message + +• **message**: string + +*Inherited from [CorruptFileError](_src_errors_.corruptfileerror.md).[message](_src_errors_.corruptfileerror.md#message)* + +___ + +### name + +• **name**: string + +*Inherited from [CorruptFileError](_src_errors_.corruptfileerror.md).[name](_src_errors_.corruptfileerror.md#name)* + +___ + +### stack + +• `Optional` **stack**: string + +*Inherited from [CorruptFileError](_src_errors_.corruptfileerror.md).[stack](_src_errors_.corruptfileerror.md#stack)* + +*Overrides [CorruptFileError](_src_errors_.corruptfileerror.md).[stack](_src_errors_.corruptfileerror.md#stack)* + +___ + +### Error + +▪ `Static` **Error**: ErrorConstructor + +## Methods + +### errorIs + +▸ `Static`**errorIs**(`e`: [Error](_src_errors_.corruptfileerror.md#error)): boolean + +#### Parameters: + +Name | Type | +------ | ------ | +`e` | [Error](_src_errors_.corruptfileerror.md#error) | + +**Returns:** boolean diff --git a/docs/classes/_src_id3v2_frames_commentsframe_.commentsframe.md b/docs/classes/_src_id3v2_frames_commentsframe_.commentsframe.md index dd5a64a9..f64bbaa1 100644 --- a/docs/classes/_src_id3v2_frames_commentsframe_.commentsframe.md +++ b/docs/classes/_src_id3v2_frames_commentsframe_.commentsframe.md @@ -517,7 +517,7 @@ ___ ▸ `Static`**fromOffsetRawData**(`data`: [ByteVector](_src_bytevector_.bytevector.md), `offset`: number, `header`: [Id3v2FrameHeader](_src_id3v2_frames_frameheader_.id3v2frameheader.md), `version`: number): [CommentsFrame](_src_id3v2_frames_commentsframe_.commentsframe.md) Constructs and initializes a new CommentsFrame by reading its raw data in a specified ID3v2 -version. This method allows for offset reading from the data bytevector. +version. This method allows for offset reading from the data byte vector. #### Parameters: diff --git a/docs/classes/_src_id3v2_frames_eventtimecodeframe_.eventtimecodeframe.md b/docs/classes/_src_id3v2_frames_eventtimecodeframe_.eventtimecodeframe.md index ff435546..512c8a85 100644 --- a/docs/classes/_src_id3v2_frames_eventtimecodeframe_.eventtimecodeframe.md +++ b/docs/classes/_src_id3v2_frames_eventtimecodeframe_.eventtimecodeframe.md @@ -378,7 +378,7 @@ ___ ▸ `Static`**fromOffsetRawData**(`data`: [ByteVector](_src_bytevector_.bytevector.md), `offset`: number, `header`: [Id3v2FrameHeader](_src_id3v2_frames_frameheader_.id3v2frameheader.md), `version`: number): [EventTimeCodeFrame](_src_id3v2_frames_eventtimecodeframe_.eventtimecodeframe.md) Constructs and initializes a new instance by reading its raw data in a specified ID3v2 -version. This method allows for offset reading from the data bytevector. +version. This method allows for offset reading from the data byte vector. #### Parameters: diff --git a/docs/classes/_src_mpeg_mpegaudioheader_.mpegaudioheader.md b/docs/classes/_src_mpeg_mpegaudioheader_.mpegaudioheader.md index d394c606..4f256a41 100644 --- a/docs/classes/_src_mpeg_mpegaudioheader_.mpegaudioheader.md +++ b/docs/classes/_src_mpeg_mpegaudioheader_.mpegaudioheader.md @@ -59,7 +59,7 @@ header, see http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm • get **audioBitrate**(): number -Bitrate of the audio in kilibits per second represented by the current instance. +Bitrate of the audio in kilobits per second represented by the current instance. **`inheritdoc`** IAudioCodec.audioBitrate diff --git a/docs/classes/_src_mpeg_mpegfile_.mpegfile.md b/docs/classes/_src_mpeg_mpegfile_.mpegfile.md new file mode 100644 index 00000000..ef5ee37a --- /dev/null +++ b/docs/classes/_src_mpeg_mpegfile_.mpegfile.md @@ -0,0 +1,839 @@ +**[node-taglib-sharp](../README.md)** + +> [Globals](../globals.md) / ["src/mpeg/mpegFile"](../modules/_src_mpeg_mpegfile_.md) / MpegFile + +# Class: MpegFile + +This class extends [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md) to provide tagging and properties support for +MPEG-1, MPEG-2, and MPEG-2.5 video files. + +**`remarks`** A [Id3v1Tag](_src_id3v1_id3v1tag_.id3v1tag.md) and [Id3v2Tag](_src_id3v2_id3v2tag_.id3v2tag.md) will be added automatically to any file that + does not contain one. This change does not affect the file until it is saved and can be + reversed using the following method: + `file.removeTags(file.tagTypes & ~file.tagTypesOnDisk);` + +## Hierarchy + +* [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md) + + ↳ **MpegFile** + +## Index + +### Constructors + +* [constructor](_src_mpeg_mpegfile_.mpegfile.md#constructor) + +### Properties + +* [\_fileAbstraction](_src_mpeg_mpegfile_.mpegfile.md#_fileabstraction) +* [\_fileStream](_src_mpeg_mpegfile_.mpegfile.md#_filestream) +* [\_invariantEndPosition](_src_mpeg_mpegfile_.mpegfile.md#_invariantendposition) +* [\_invariantStartPosition](_src_mpeg_mpegfile_.mpegfile.md#_invariantstartposition) +* [\_tagTypesOnDisk](_src_mpeg_mpegfile_.mpegfile.md#_tagtypesondisk) + +### Accessors + +* [corruptionReasons](_src_mpeg_mpegfile_.mpegfile.md#corruptionreasons) +* [endTag](_src_mpeg_mpegfile_.mpegfile.md#endtag) +* [fileAbstraction](_src_mpeg_mpegfile_.mpegfile.md#fileabstraction) +* [invariantEndPosition](_src_mpeg_mpegfile_.mpegfile.md#invariantendposition) +* [invariantStartPosition](_src_mpeg_mpegfile_.mpegfile.md#invariantstartposition) +* [isPossiblyCorrupt](_src_mpeg_mpegfile_.mpegfile.md#ispossiblycorrupt) +* [isWritable](_src_mpeg_mpegfile_.mpegfile.md#iswritable) +* [length](_src_mpeg_mpegfile_.mpegfile.md#length) +* [mimeType](_src_mpeg_mpegfile_.mpegfile.md#mimetype) +* [mode](_src_mpeg_mpegfile_.mpegfile.md#mode) +* [name](_src_mpeg_mpegfile_.mpegfile.md#name) +* [position](_src_mpeg_mpegfile_.mpegfile.md#position) +* [properties](_src_mpeg_mpegfile_.mpegfile.md#properties) +* [startTag](_src_mpeg_mpegfile_.mpegfile.md#starttag) +* [tag](_src_mpeg_mpegfile_.mpegfile.md#tag) +* [tagTypes](_src_mpeg_mpegfile_.mpegfile.md#tagtypes) +* [tagTypesOnDisk](_src_mpeg_mpegfile_.mpegfile.md#tagtypesondisk) +* [bufferSize](_src_mpeg_mpegfile_.mpegfile.md#buffersize) + +### Methods + +* [dispose](_src_mpeg_mpegfile_.mpegfile.md#dispose) +* [find](_src_mpeg_mpegfile_.mpegfile.md#find) +* [getTag](_src_mpeg_mpegfile_.mpegfile.md#gettag) +* [insert](_src_mpeg_mpegfile_.mpegfile.md#insert) +* [markAsCorrupt](_src_mpeg_mpegfile_.mpegfile.md#markascorrupt) +* [preSave](_src_mpeg_mpegfile_.mpegfile.md#presave) +* [rFind](_src_mpeg_mpegfile_.mpegfile.md#rfind) +* [readBlock](_src_mpeg_mpegfile_.mpegfile.md#readblock) +* [readEnd](_src_mpeg_mpegfile_.mpegfile.md#readend) +* [readProperties](_src_mpeg_mpegfile_.mpegfile.md#readproperties) +* [readStart](_src_mpeg_mpegfile_.mpegfile.md#readstart) +* [removeBlock](_src_mpeg_mpegfile_.mpegfile.md#removeblock) +* [removeTags](_src_mpeg_mpegfile_.mpegfile.md#removetags) +* [save](_src_mpeg_mpegfile_.mpegfile.md#save) +* [seek](_src_mpeg_mpegfile_.mpegfile.md#seek) +* [truncate](_src_mpeg_mpegfile_.mpegfile.md#truncate) +* [writeBlock](_src_mpeg_mpegfile_.mpegfile.md#writeblock) +* [addFileType](_src_mpeg_mpegfile_.mpegfile.md#addfiletype) +* [addFileTypeResolver](_src_mpeg_mpegfile_.mpegfile.md#addfiletyperesolver) +* [createFromAbstraction](_src_mpeg_mpegfile_.mpegfile.md#createfromabstraction) +* [createFromPath](_src_mpeg_mpegfile_.mpegfile.md#createfrompath) +* [removeFileType](_src_mpeg_mpegfile_.mpegfile.md#removefiletype) +* [removeFileTypeResolver](_src_mpeg_mpegfile_.mpegfile.md#removefiletyperesolver) + +## Constructors + +### constructor + +\+ **new MpegFile**(`file`: [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) \| string, `propertiesStyle`: [ReadStyle](../enums/_src_file_.readstyle.md)): [MpegFile](_src_mpeg_mpegfile_.mpegfile.md) + +*Overrides [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[constructor](_src_noncontainer_noncontainerfile_.noncontainerfile.md#constructor)* + +#### Parameters: + +Name | Type | +------ | ------ | +`file` | [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) \| string | +`propertiesStyle` | [ReadStyle](../enums/_src_file_.readstyle.md) | + +**Returns:** [MpegFile](_src_mpeg_mpegfile_.mpegfile.md) + +## Properties + +### \_fileAbstraction + +• `Protected` **\_fileAbstraction**: [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) + +*Inherited from [File](_src_file_.file.md).[_fileAbstraction](_src_file_.file.md#_fileabstraction)* + +___ + +### \_fileStream + +• `Protected` **\_fileStream**: [IStream](../interfaces/_src_stream_.istream.md) + +*Inherited from [File](_src_file_.file.md).[_fileStream](_src_file_.file.md#_filestream)* + +___ + +### \_invariantEndPosition + +• `Protected` **\_invariantEndPosition**: number = -1 + +*Inherited from [File](_src_file_.file.md).[_invariantEndPosition](_src_file_.file.md#_invariantendposition)* + +___ + +### \_invariantStartPosition + +• `Protected` **\_invariantStartPosition**: number = -1 + +*Inherited from [File](_src_file_.file.md).[_invariantStartPosition](_src_file_.file.md#_invariantstartposition)* + +___ + +### \_tagTypesOnDisk + +• `Protected` **\_tagTypesOnDisk**: [TagTypes](../enums/_src_tag_.tagtypes.md) = TagTypes.None + +*Inherited from [File](_src_file_.file.md).[_tagTypesOnDisk](_src_file_.file.md#_tagtypesondisk)* + +## Accessors + +### corruptionReasons + +• get **corruptionReasons**(): string[] + +*Inherited from [File](_src_file_.file.md).[corruptionReasons](_src_file_.file.md#corruptionreasons)* + +Reasons for which this file is marked as corrupt. + +**Returns:** string[] + +___ + +### endTag + +• `Protected`get **endTag**(): [EndTag](_src_noncontainer_endtag_.endtag.md) + +*Inherited from [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[endTag](_src_noncontainer_noncontainerfile_.noncontainerfile.md#endtag)* + +Gets the collection of tags appearing at the end of the file. + +**Returns:** [EndTag](_src_noncontainer_endtag_.endtag.md) + +___ + +### fileAbstraction + +• get **fileAbstraction**(): [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) + +*Inherited from [File](_src_file_.file.md).[fileAbstraction](_src_file_.file.md#fileabstraction)* + +Gets the [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) representing the file. + +**Returns:** [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) + +___ + +### invariantEndPosition + +• get **invariantEndPosition**(): number + +*Inherited from [File](_src_file_.file.md).[invariantEndPosition](_src_file_.file.md#invariantendposition)* + +Gets the position at which the invariant (media) portion of the current instance ends. If +the value could not be determined, `-1` is returned; + +**Returns:** number + +___ + +### invariantStartPosition + +• get **invariantStartPosition**(): number + +*Inherited from [File](_src_file_.file.md).[invariantStartPosition](_src_file_.file.md#invariantstartposition)* + +Gets the position at which the invariant (media) portion of the current instance begins. If +the value could not be determined, `-1` is returned. + +**Returns:** number + +___ + +### isPossiblyCorrupt + +• get **isPossiblyCorrupt**(): boolean + +*Inherited from [File](_src_file_.file.md).[isPossiblyCorrupt](_src_file_.file.md#ispossiblycorrupt)* + +Indicates whether or not this file may be corrupt. Files with unknown corruptions should not +be written. + +**Returns:** boolean + +___ + +### isWritable + +• get **isWritable**(): boolean + +*Inherited from [File](_src_file_.file.md).[isWritable](_src_file_.file.md#iswritable)* + +Indicates whether or not tags can be written back to the current file. + +**Returns:** boolean + +___ + +### length + +• get **length**(): number + +*Inherited from [File](_src_file_.file.md).[length](_src_file_.file.md#length)* + +Gets the length of the file represented by the current instance. Value will be 0 if the file +is not open for reading; + +**Returns:** number + +___ + +### mimeType + +• get **mimeType**(): string + +*Inherited from [File](_src_file_.file.md).[mimeType](_src_file_.file.md#mimetype)* + +Gets the MimeType of the file as determined during creation of the instance. + +**Returns:** string + +___ + +### mode + +• get **mode**(): [FileAccessMode](../enums/_src_file_.fileaccessmode.md) + +*Inherited from [File](_src_file_.file.md).[mode](_src_file_.file.md#mode)* + +Gets the file access mode in use by the current instance. + +**Returns:** [FileAccessMode](../enums/_src_file_.fileaccessmode.md) + +• set **mode**(`val`: [FileAccessMode](../enums/_src_file_.fileaccessmode.md)): void + +*Inherited from [File](_src_file_.file.md).[mode](_src_file_.file.md#mode)* + +Sets the file access mode in use by the current instance. Changing the value will cause the +stream currently in use to be closed, except when a change is made from +[FileAccessMode.Write](../enums/_src_file_.fileaccessmode.md#write) to [FileAccessMode.Read](../enums/_src_file_.fileaccessmode.md#read) which has no effect. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`val` | [FileAccessMode](../enums/_src_file_.fileaccessmode.md) | File access mode to change to | + +**Returns:** void + +___ + +### name + +• get **name**(): string + +*Inherited from [File](_src_file_.file.md).[name](_src_file_.file.md#name)* + +Gets the name of the file as stored in its file abstraction. + +**Returns:** string + +___ + +### position + +• get **position**(): number + +*Inherited from [File](_src_file_.file.md).[position](_src_file_.file.md#position)* + +Gets the seek position in the internal stream used by the current instance. Value will be 0 +if the file is not open for reading + +**Returns:** number + +___ + +### properties + +• get **properties**(): [Properties](_src_properties_.properties.md) + +*Inherited from [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[properties](_src_noncontainer_noncontainerfile_.noncontainerfile.md#properties)* + +*Overrides [File](_src_file_.file.md).[properties](_src_file_.file.md#properties)* + +Gets the media properties of the file represented by the current instance. + +**Returns:** [Properties](_src_properties_.properties.md) + +___ + +### startTag + +• `Protected`get **startTag**(): [StartTag](_src_noncontainer_starttag_.starttag.md) + +*Inherited from [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[startTag](_src_noncontainer_noncontainerfile_.noncontainerfile.md#starttag)* + +Gets the collection of tags appearing at the start of the file. + +**Returns:** [StartTag](_src_noncontainer_starttag_.starttag.md) + +___ + +### tag + +• get **tag**(): [NonContainerTag](_src_noncontainer_noncontainertag_.noncontainertag.md) + +*Inherited from [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[tag](_src_noncontainer_noncontainerfile_.noncontainerfile.md#tag)* + +*Overrides [File](_src_file_.file.md).[tag](_src_file_.file.md#tag)* + +Gets an abstract representation of all tags stored in the current instance. + +**Returns:** [NonContainerTag](_src_noncontainer_noncontainertag_.noncontainertag.md) + +___ + +### tagTypes + +• get **tagTypes**(): [TagTypes](../enums/_src_tag_.tagtypes.md) + +*Inherited from [File](_src_file_.file.md).[tagTypes](_src_file_.file.md#tagtypes)* + +Gets the tag types contained in the current instance. + +**Returns:** [TagTypes](../enums/_src_tag_.tagtypes.md) + +___ + +### tagTypesOnDisk + +• get **tagTypesOnDisk**(): [TagTypes](../enums/_src_tag_.tagtypes.md) + +*Inherited from [File](_src_file_.file.md).[tagTypesOnDisk](_src_file_.file.md#tagtypesondisk)* + +Gets the tag types contained in the physical file represented by the current instance. + +**Returns:** [TagTypes](../enums/_src_tag_.tagtypes.md) + +___ + +### bufferSize + +• `Static`get **bufferSize**(): number + +*Inherited from [File](_src_file_.file.md).[bufferSize](_src_file_.file.md#buffersize)* + +Gets the buffer size to use when reading large blocks of data + +**Returns:** number + +## Methods + +### dispose + +▸ **dispose**(): void + +*Inherited from [File](_src_file_.file.md).[dispose](_src_file_.file.md#dispose)* + +Dispose the current instance. Equivalent to setting the mode to closed. + +**Returns:** void + +___ + +### find + +▸ **find**(`pattern`: [ByteVector](_src_bytevector_.bytevector.md), `startPosition?`: number, `before?`: [ByteVector](_src_bytevector_.bytevector.md)): number + +*Inherited from [File](_src_file_.file.md).[find](_src_file_.file.md#find)* + +Searches forward through a file for a specified pattern, starting at a specified offset. + +**`throws`** Error Thrown if `pattern` is not provided or `startPosition` is not a + positive, safe integer. + +#### Parameters: + +Name | Type | Default value | Description | +------ | ------ | ------ | ------ | +`pattern` | [ByteVector](_src_bytevector_.bytevector.md) | - | Pattern to search for in the current instance. Must be smaller than the | +`startPosition` | number | 0 | Seek position to start searching. Must be positive, safe integer. | +`before?` | [ByteVector](_src_bytevector_.bytevector.md) | - | Optional pattern that the searched for pattern must appear before. If this pattern is found first, `-1` is returned. | + +**Returns:** number + +Index at which the value was found. If not found, `-1` is returned. + +___ + +### getTag + +▸ **getTag**(`type`: [TagTypes](../enums/_src_tag_.tagtypes.md), `create`: boolean): [Tag](_src_tag_.tag.md) + +*Overrides [File](_src_file_.file.md).[getTag](_src_file_.file.md#gettag)* + +Gets a tag of a specified type from the current instance, optionally creating a new tag if +possible. + +**`remarks`** [Id3v2Tag](_src_id3v2_id3v2tag_.id3v2tag.md), [Id3v1Tag](_src_id3v1_id3v1tag_.id3v1tag.md), and [ApeTag](_src_ape_apetag_.apetag.md) will be added to the end of + the file. All other tag types will be ignored as they are unsupported by MPEG files. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`type` | [TagTypes](../enums/_src_tag_.tagtypes.md) | Type of tag to read | +`create` | boolean | Whether or not to try and create the tag if one is not found | + +**Returns:** [Tag](_src_tag_.tag.md) + +Tag Tag that was found in or added to the current instance. If no matching tag was + found and none was created, `undefined` is returned. + +___ + +### insert + +▸ **insert**(`data`: [ByteVector](_src_bytevector_.bytevector.md), `start`: number, `replace?`: number): void + +*Inherited from [File](_src_file_.file.md).[insert](_src_file_.file.md#insert)* + +Inserts a specified block of data into the file represented by the current instance, at a +specified location, replacing a specified number of bytes. + +**`throws`** Error Thrown when: 1) data is falsey, 2) start is not a safe, positive number, or 3) + replace is not a safe, positive number + +#### Parameters: + +Name | Type | Default value | Description | +------ | ------ | ------ | ------ | +`data` | [ByteVector](_src_bytevector_.bytevector.md) | - | Data to insert into the file. | +`start` | number | - | Index into the file at which to insert the data. Must be safe positive integer. | +`replace` | number | 0 | Number of bytes to replace. Typically this is the original size of the data block so that a new block will replace the old one. | + +**Returns:** void + +___ + +### markAsCorrupt + +▸ **markAsCorrupt**(`reason`: string): void + +*Inherited from [File](_src_file_.file.md).[markAsCorrupt](_src_file_.file.md#markascorrupt)* + +Mark the current instance as corrupt. NOTE: Not intended to be used outside of this library. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`reason` | string | Reason why this file is considered to be corrupt | + +**Returns:** void + +___ + +### preSave + +▸ `Protected`**preSave**(): void + +*Inherited from [File](_src_file_.file.md).[preSave](_src_file_.file.md#presave)* + +Prepares to save the file. This must be called at the beginning of every File.save() method. + +**Returns:** void + +___ + +### rFind + +▸ **rFind**(`pattern`: [ByteVector](_src_bytevector_.bytevector.md), `startPosition?`: number, `after?`: [ByteVector](_src_bytevector_.bytevector.md)): number + +*Inherited from [File](_src_file_.file.md).[rFind](_src_file_.file.md#rfind)* + +Searched backwards through a file for a specified pattern, starting at a specified offset. + +**`throws`** Error Thrown if `pattern` was not provided or if `startPosition` is + not a safe, positive integer. + +#### Parameters: + +Name | Type | Default value | Description | +------ | ------ | ------ | ------ | +`pattern` | [ByteVector](_src_bytevector_.bytevector.md) | - | Pattern to search for in the current instance. Must be shorter than the [bufferSize](_src_mpeg_mpegfile_.mpegfile.md#buffersize) | +`startPosition` | number | 0 | Seek position from which to start searching. | +`after?` | [ByteVector](_src_bytevector_.bytevector.md) | - | Pattern that the searched for pattern must appear after. If this pattern is found first, `-1` is returned. | + +**Returns:** number + +Index at which the value wa found. If not found, `-1` is returned. + +___ + +### readBlock + +▸ **readBlock**(`length`: number): [ByteVector](_src_bytevector_.bytevector.md) + +*Inherited from [File](_src_file_.file.md).[readBlock](_src_file_.file.md#readblock)* + +Reads a specified number of bytes at the current seek position from the current position. +This method reads the block of data at the current seek position. To change the seek +position, use [File.seek](_src_file_.file.md#seek). + +**`throws`** Error Thrown when `length` is not a positive, safe integer. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`length` | number | Number of bytes to read. | + +**Returns:** [ByteVector](_src_bytevector_.bytevector.md) + +ByteVector Object containing the data read from the current instance. + +___ + +### readEnd + +▸ `Protected`**readEnd**(`end`: number, `propertiesStyle`: [ReadStyle](../enums/_src_file_.readstyle.md)): void + +*Overrides [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[readEnd](_src_noncontainer_noncontainerfile_.noncontainerfile.md#readend)* + +**`inheritdoc`** + +#### Parameters: + +Name | Type | +------ | ------ | +`end` | number | +`propertiesStyle` | [ReadStyle](../enums/_src_file_.readstyle.md) | + +**Returns:** void + +___ + +### readProperties + +▸ `Protected`**readProperties**(`_start`: number, `_end`: number, `_propertiesStyle`: [ReadStyle](../enums/_src_file_.readstyle.md)): [Properties](_src_properties_.properties.md) + +*Overrides [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[readProperties](_src_noncontainer_noncontainerfile_.noncontainerfile.md#readproperties)* + +**`inheritdoc`** + +#### Parameters: + +Name | Type | +------ | ------ | +`_start` | number | +`_end` | number | +`_propertiesStyle` | [ReadStyle](../enums/_src_file_.readstyle.md) | + +**Returns:** [Properties](_src_properties_.properties.md) + +___ + +### readStart + +▸ `Protected`**readStart**(`start`: number, `propertiesStyle`: [ReadStyle](../enums/_src_file_.readstyle.md)): void + +*Overrides [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[readStart](_src_noncontainer_noncontainerfile_.noncontainerfile.md#readstart)* + +**`inheritdoc`** + +#### Parameters: + +Name | Type | +------ | ------ | +`start` | number | +`propertiesStyle` | [ReadStyle](../enums/_src_file_.readstyle.md) | + +**Returns:** void + +___ + +### removeBlock + +▸ **removeBlock**(`start`: number, `length`: number): void + +*Inherited from [File](_src_file_.file.md).[removeBlock](_src_file_.file.md#removeblock)* + +Removes a specified block of data from the file represented by the current instance. + +**`throws`** Error thrown if 1) start is not a safe, positive integer or 2) length must be a safe + integer. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`start` | number | Index into the file at which to remove data. Must be safe, positive integer. | +`length` | number | Number of bytes to remove. Must be a safe integer. | + +**Returns:** void + +___ + +### removeTags + +▸ **removeTags**(`types`: [TagTypes](../enums/_src_tag_.tagtypes.md)): void + +*Inherited from [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[removeTags](_src_noncontainer_noncontainerfile_.noncontainerfile.md#removetags)* + +*Overrides [File](_src_file_.file.md).[removeTags](_src_file_.file.md#removetags)* + +**`inheritdoc`** BaseFile.removeTags + +#### Parameters: + +Name | Type | +------ | ------ | +`types` | [TagTypes](../enums/_src_tag_.tagtypes.md) | + +**Returns:** void + +___ + +### save + +▸ **save**(): void + +*Inherited from [NonContainerFile](_src_noncontainer_noncontainerfile_.noncontainerfile.md).[save](_src_noncontainer_noncontainerfile_.noncontainerfile.md#save)* + +*Overrides [File](_src_file_.file.md).[save](_src_file_.file.md#save)* + +**`inheritdoc`** BaseFile.save + +**Returns:** void + +___ + +### seek + +▸ **seek**(`offset`: number, `origin?`: [SeekOrigin](../enums/_src_stream_.seekorigin.md)): void + +*Inherited from [File](_src_file_.file.md).[seek](_src_file_.file.md#seek)* + +Moves the read/write pointer to a specified offset in the current instance, relative to a +specified origin. + +#### Parameters: + +Name | Type | Default value | Description | +------ | ------ | ------ | ------ | +`offset` | number | - | Byte offset to seek to. Must be a safe, positive integer. | +`origin` | [SeekOrigin](../enums/_src_stream_.seekorigin.md) | SeekOrigin.Begin | Origin from which to seek | + +**Returns:** void + +___ + +### truncate + +▸ `Protected`**truncate**(`length`: number): void + +*Inherited from [File](_src_file_.file.md).[truncate](_src_file_.file.md#truncate)* + +Resizes the current instance to a specific number of bytes. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`length` | number | Number of bytes to resize the file to, must be a safe, positive integer. | + +**Returns:** void + +___ + +### writeBlock + +▸ **writeBlock**(`data`: [ByteVector](_src_bytevector_.bytevector.md)): void + +*Inherited from [File](_src_file_.file.md).[writeBlock](_src_file_.file.md#writeblock)* + +Writes a block of data to the file represented by the current instance at the current seek +position. This will overwrite any existing data at the seek position and append new data to +the file if writing past the current end. + +**`throws`** Error Thrown when `data` is not provided. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`data` | [ByteVector](_src_bytevector_.bytevector.md) | ByteVector containing data to the current instance. | + +**Returns:** void + +___ + +### addFileType + +▸ `Static`**addFileType**(`mimeType`: string, `constructor`: [FileTypeConstructor](../modules/_src_file_.md#filetypeconstructor), `override?`: boolean): void + +*Inherited from [File](_src_file_.file.md).[addFileType](_src_file_.file.md#addfiletype)* + +Registers the constructor for a subclass of [File](_src_file_.file.md) with the MimeType it is associated +with. Optionally, the MimeType can be forcefully overridden if it was already registered. + +#### Parameters: + +Name | Type | Default value | Description | +------ | ------ | ------ | ------ | +`mimeType` | string | - | MimeType to register this subclass constructor to. | +`constructor` | [FileTypeConstructor](../modules/_src_file_.md#filetypeconstructor) | - | Constructor for a subclass of [File](_src_file_.file.md) that will be called if a file with a MimeType of `mimeType` is created. | +`override` | boolean | false | If `true` and a subclass of [File](_src_file_.file.md) was already registered to `mimeType`, it will be forcefully overridden. If `false`, an [Error](_src_errors_.corruptfileerror.md#error) will be thrown if a subclass already registered to the MimeType.} | + +**Returns:** void + +___ + +### addFileTypeResolver + +▸ `Static`**addFileTypeResolver**(`resolver`: [FileTypeResolver](../modules/_src_file_.md#filetyperesolver)): void + +*Inherited from [File](_src_file_.file.md).[addFileTypeResolver](_src_file_.file.md#addfiletyperesolver)* + +Registers a [FileTypeResolver](../modules/_src_file_.md#filetyperesolver) to the front of the list of file type resolvers. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`resolver` | [FileTypeResolver](../modules/_src_file_.md#filetyperesolver) | Function to handle resolving a subclass of [File](_src_file_.file.md) from an [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) | + +**Returns:** void + +___ + +### createFromAbstraction + +▸ `Static`**createFromAbstraction**(`abstraction`: [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md), `mimeType?`: string, `propertiesStyle?`: [ReadStyle](../enums/_src_file_.readstyle.md)): [File](_src_file_.file.md) + +*Inherited from [File](_src_file_.file.md).[createFromAbstraction](_src_file_.file.md#createfromabstraction)* + +Creates a new instance of a [File](_src_file_.file.md) subclass for a specified file abstraction, MimeType, +and property read style. + +#### Parameters: + +Name | Type | Default value | Description | +------ | ------ | ------ | ------ | +`abstraction` | [IFileAbstraction](../interfaces/_src_fileabstraction_.ifileabstraction.md) | - | Object to use when reading/writing from the current instance. | +`mimeType?` | string | - | Optional, MimeType to use for determining the subclass of [File](_src_file_.file.md) to return. If omitted, the MimeType will be guessed based on the file's extension. | +`propertiesStyle` | [ReadStyle](../enums/_src_file_.readstyle.md) | ReadStyle.Average | Optional, level of detail to use when reading the media information from the new instance. If omitted, [ReadStyle.Average](../enums/_src_file_.readstyle.md#average) is used. | + +**Returns:** [File](_src_file_.file.md) + +New instance of [File](_src_file_.file.md) as read from the specified abstraction. + +___ + +### createFromPath + +▸ `Static`**createFromPath**(`filePath`: string, `mimeType?`: string, `propertiesStyle?`: [ReadStyle](../enums/_src_file_.readstyle.md)): [File](_src_file_.file.md) + +*Inherited from [File](_src_file_.file.md).[createFromPath](_src_file_.file.md#createfrompath)* + +Creates a new instance of [File](_src_file_.file.md) subclass for a specified file path, MimeType, and +property read style. + +#### Parameters: + +Name | Type | Default value | Description | +------ | ------ | ------ | ------ | +`filePath` | string | - | Path to the file to read/write. | +`mimeType?` | string | - | Optional, MimeType to use for determining the subclass of [File](_src_file_.file.md) to return. If omitted, the MimeType will be guessed based on the file's extension. | +`propertiesStyle` | [ReadStyle](../enums/_src_file_.readstyle.md) | ReadStyle.Average | Optional, level of detail to use when reading the media information from the new instance. If omitted [ReadStyle.Average](../enums/_src_file_.readstyle.md#average) is used. | + +**Returns:** [File](_src_file_.file.md) + +New instance of [File](_src_file_.file.md) as read from the specified path. + +___ + +### removeFileType + +▸ `Static`**removeFileType**(`mimeType`: string): void + +*Inherited from [File](_src_file_.file.md).[removeFileType](_src_file_.file.md#removefiletype)* + +Used for removing a file type constructor during unit testing + +#### Parameters: + +Name | Type | +------ | ------ | +`mimeType` | string | + +**Returns:** void + +___ + +### removeFileTypeResolver + +▸ `Static`**removeFileTypeResolver**(`resolver`: [FileTypeResolver](../modules/_src_file_.md#filetyperesolver)): void + +*Inherited from [File](_src_file_.file.md).[removeFileTypeResolver](_src_file_.file.md#removefiletyperesolver)* + +Used for removing a file type resolver during unit testing + +#### Parameters: + +Name | Type | +------ | ------ | +`resolver` | [FileTypeResolver](../modules/_src_file_.md#filetyperesolver) | + +**Returns:** void diff --git a/docs/classes/_src_mpeg_mpegvideoheader_.mpegvideoheader.md b/docs/classes/_src_mpeg_mpegvideoheader_.mpegvideoheader.md new file mode 100644 index 00000000..793f57c6 --- /dev/null +++ b/docs/classes/_src_mpeg_mpegvideoheader_.mpegvideoheader.md @@ -0,0 +1,132 @@ +**[node-taglib-sharp](../README.md)** + +> [Globals](../globals.md) / ["src/mpeg/mpegVideoHeader"](../modules/_src_mpeg_mpegvideoheader_.md) / MpegVideoHeader + +# Class: MpegVideoHeader + +Provides information about an MPEG video stream. + +## Hierarchy + +* **MpegVideoHeader** + +## Implements + +* [IVideoCodec](../interfaces/_src_icodec_.ivideocodec.md) + +## Index + +### Constructors + +* [constructor](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#constructor) + +### Accessors + +* [description](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#description) +* [durationMilliseconds](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#durationmilliseconds) +* [mediaTypes](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#mediatypes) +* [videoBitrate](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#videobitrate) +* [videoFrameRate](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#videoframerate) +* [videoHeight](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#videoheight) +* [videoWidth](_src_mpeg_mpegvideoheader_.mpegvideoheader.md#videowidth) + +## Constructors + +### constructor + +\+ **new MpegVideoHeader**(`file`: [File](_src_file_.file.md), `position`: number): [MpegVideoHeader](_src_mpeg_mpegvideoheader_.mpegvideoheader.md) + +Constructs and initializes a new instance of [MpegVideoHeader](_src_mpeg_mpegvideoheader_.mpegvideoheader.md) by reading it from a +specified location in a specified file. + +#### Parameters: + +Name | Type | Description | +------ | ------ | ------ | +`file` | [File](_src_file_.file.md) | File to read the header from | +`position` | number | Position in `file` at which the header begins | + +**Returns:** [MpegVideoHeader](_src_mpeg_mpegvideoheader_.mpegvideoheader.md) + +## Accessors + +### description + +• get **description**(): string + +Gets a text description of the media represented by the current instance. + +**`inheritdoc`** + +**Returns:** string + +___ + +### durationMilliseconds + +• get **durationMilliseconds**(): number + +Duration of the media in milliseconds represented by the current instance. + +**`inheritdoc`** +For MPEG, this is always 0 + +**`todo:`** Can we calculate the duration? + +**Returns:** number + +___ + +### mediaTypes + +• get **mediaTypes**(): [MediaTypes](../enums/_src_icodec_.mediatypes.md) + +Types of media represented by the current instance, bitwise combined. + +**`inheritdoc`** + +**Returns:** [MediaTypes](../enums/_src_icodec_.mediatypes.md) + +___ + +### videoBitrate + +• get **videoBitrate**(): number + +**`inheritdoc`** + +**Returns:** number + +___ + +### videoFrameRate + +• get **videoFrameRate**(): number + +**`inheritdoc`** + +**Returns:** number + +___ + +### videoHeight + +• get **videoHeight**(): number + +Height of the video in pixels represented by the current instance. + +**`inheritdoc`** + +**Returns:** number + +___ + +### videoWidth + +• get **videoWidth**(): number + +Width of the video in pixels represented by the current instance. + +**`inheritdoc`** + +**Returns:** number diff --git a/docs/classes/_src_noncontainer_noncontainerfile_.noncontainerfile.md b/docs/classes/_src_noncontainer_noncontainerfile_.noncontainerfile.md index e2369869..b9cf7902 100644 --- a/docs/classes/_src_noncontainer_noncontainerfile_.noncontainerfile.md +++ b/docs/classes/_src_noncontainer_noncontainerfile_.noncontainerfile.md @@ -32,6 +32,8 @@ The file is read upon construction in the following manner: ↳↳ [MpegAudioFile](_src_mpeg_mpegaudiofile_.mpegaudiofile.md) + ↳↳ [MpegFile](_src_mpeg_mpegfile_.mpegfile.md) + ## Index ### Constructors diff --git a/docs/classes/_src_tag_.tag.md b/docs/classes/_src_tag_.tag.md index f3c58390..f14ddfc3 100644 --- a/docs/classes/_src_tag_.tag.md +++ b/docs/classes/_src_tag_.tag.md @@ -763,8 +763,8 @@ Gets the genres of the media represented by the current instance. **`remarks`** This field represents genres that apply to the song, album, or video. This is often used for filtering media. - A list of common audio genres as popularized by ID3v1 is stored in [audioGenres](../modules/_src_genres_.md#audiogenres). - Additionally, [videoGenres](../modules/_src_genres_.md#videogenres) contains video genres as used by DivX. + A list of common audio genres as popularized by ID3v1 is stored in `genres.ts`. + Additionally, `genres.ts` contains video genres as used by DivX. **Returns:** string[] @@ -777,8 +777,8 @@ Sets the genres of the media represented by the current instance. **`remarks`** This field represents genres that apply to the song, album, or video. This is often used for filtering media. - A list of common audio genres as popularized by ID3v1 is stored in [audioGenres](../modules/_src_genres_.md#audiogenres). - Additionally, [videoGenres](../modules/_src_genres_.md#videogenres) contains video genres as used by DivX. + A list of common audio genres as popularized by ID3v1 is stored in `genres.ts. + Additionally, `genres.ts` contains video genres as used by DivX. #### Parameters: diff --git a/docs/enums/_src_mpeg_mpegfile_.mpegfilemarker.md b/docs/enums/_src_mpeg_mpegfile_.mpegfilemarker.md new file mode 100644 index 00000000..3bb14a55 --- /dev/null +++ b/docs/enums/_src_mpeg_mpegfile_.mpegfilemarker.md @@ -0,0 +1,93 @@ +**[node-taglib-sharp](../README.md)** + +> [Globals](../globals.md) / ["src/mpeg/mpegFile"](../modules/_src_mpeg_mpegfile_.md) / MpegFileMarker + +# Enumeration: MpegFileMarker + +Indicates the type of marker found in an MPEG file. + +## Index + +### Enumeration members + +* [AudioPacket](_src_mpeg_mpegfile_.mpegfilemarker.md#audiopacket) +* [Corrupt](_src_mpeg_mpegfile_.mpegfilemarker.md#corrupt) +* [EndOfStream](_src_mpeg_mpegfile_.mpegfilemarker.md#endofstream) +* [PaddingPacket](_src_mpeg_mpegfile_.mpegfilemarker.md#paddingpacket) +* [SystemPacket](_src_mpeg_mpegfile_.mpegfilemarker.md#systempacket) +* [SystemSyncPacket](_src_mpeg_mpegfile_.mpegfilemarker.md#systemsyncpacket) +* [VideoPacket](_src_mpeg_mpegfile_.mpegfilemarker.md#videopacket) +* [VideoSyncPacket](_src_mpeg_mpegfile_.mpegfilemarker.md#videosyncpacket) +* [Zero](_src_mpeg_mpegfile_.mpegfilemarker.md#zero) + +## Enumeration members + +### AudioPacket + +• **AudioPacket**: = 192 + +A marker indicating an audio packet. + +___ + +### Corrupt + +• **Corrupt**: = -1 + +An invalid marker. + +___ + +### EndOfStream + +• **EndOfStream**: = 185 + +A marker indicating the end of a stream. + +___ + +### PaddingPacket + +• **PaddingPacket**: = 190 + +A marker indicating a padding packet. + +___ + +### SystemPacket + +• **SystemPacket**: = 187 + +A marker indicating a system packet. + +___ + +### SystemSyncPacket + +• **SystemSyncPacket**: = 186 + +A marker indicating a system sync packet. + +___ + +### VideoPacket + +• **VideoPacket**: = 224 + +A marker indicating a video packet. + +___ + +### VideoSyncPacket + +• **VideoSyncPacket**: = 179 + +A marker indicating a video sync packet. + +___ + +### Zero + +• **Zero**: = 0 + +A zero value marker. diff --git a/docs/globals.md b/docs/globals.md index a3a6caf4..6744b1c2 100644 --- a/docs/globals.md +++ b/docs/globals.md @@ -54,6 +54,8 @@ * ["src/mpeg/mpegAudioFile"](modules/_src_mpeg_mpegaudiofile_.md) * ["src/mpeg/mpegAudioHeader"](modules/_src_mpeg_mpegaudioheader_.md) * ["src/mpeg/mpegEnums"](modules/_src_mpeg_mpegenums_.md) +* ["src/mpeg/mpegFile"](modules/_src_mpeg_mpegfile_.md) +* ["src/mpeg/mpegVideoHeader"](modules/_src_mpeg_mpegvideoheader_.md) * ["src/mpeg/vbriHeader"](modules/_src_mpeg_vbriheader_.md) * ["src/mpeg/xingHeader"](modules/_src_mpeg_xingheader_.md) * ["src/mpeg4/mpeg4AudioTypes"](modules/_src_mpeg4_mpeg4audiotypes_.md) diff --git a/docs/interfaces/_src_icodec_.iaudiocodec.md b/docs/interfaces/_src_icodec_.iaudiocodec.md index 338078be..dd8dbf4c 100644 --- a/docs/interfaces/_src_icodec_.iaudiocodec.md +++ b/docs/interfaces/_src_icodec_.iaudiocodec.md @@ -40,7 +40,7 @@ and can be recast without issue. • **audioBitrate**: number -Bitrate of the audio in kilibits per second represented by the current instance. +Bitrate of the audio in kilobits per second represented by the current instance. ___ diff --git a/docs/interfaces/_src_icodec_.ilosslessaudiocodec.md b/docs/interfaces/_src_icodec_.ilosslessaudiocodec.md index e4487cab..70e8bbc0 100644 --- a/docs/interfaces/_src_icodec_.ilosslessaudiocodec.md +++ b/docs/interfaces/_src_icodec_.ilosslessaudiocodec.md @@ -40,7 +40,7 @@ When dealing with an [ICodec](_src_icodec_.icodec.md), if [ICodec.mediaTypes](_s *Inherited from [IAudioCodec](_src_icodec_.iaudiocodec.md).[audioBitrate](_src_icodec_.iaudiocodec.md#audiobitrate)* -Bitrate of the audio in kilibits per second represented by the current instance. +Bitrate of the audio in kilobits per second represented by the current instance. ___ diff --git a/docs/interfaces/_src_icodec_.ivideocodec.md b/docs/interfaces/_src_icodec_.ivideocodec.md index 1cdaa05e..d1bec73b 100644 --- a/docs/interfaces/_src_icodec_.ivideocodec.md +++ b/docs/interfaces/_src_icodec_.ivideocodec.md @@ -17,6 +17,7 @@ and can be recast without issue. ## Implemented by +* [MpegVideoHeader](../classes/_src_mpeg_mpegvideoheader_.mpegvideoheader.md) * [Properties](../classes/_src_properties_.properties.md) ## Index diff --git a/docs/modules/_src_errors_.md b/docs/modules/_src_errors_.md index a7c540a8..4bc7b6c9 100644 --- a/docs/modules/_src_errors_.md +++ b/docs/modules/_src_errors_.md @@ -11,3 +11,4 @@ * [CorruptFileError](../classes/_src_errors_.corruptfileerror.md) * [NotImplementedError](../classes/_src_errors_.notimplementederror.md) * [NotSupportedError](../classes/_src_errors_.notsupportederror.md) +* [UnsupportedFormatError](../classes/_src_errors_.unsupportedformaterror.md) diff --git a/docs/modules/_src_genres_.md b/docs/modules/_src_genres_.md index d3c086ae..bc2dc971 100644 --- a/docs/modules/_src_genres_.md +++ b/docs/modules/_src_genres_.md @@ -19,13 +19,13 @@ ### audioGenres -• `Const` **audioGenres**: string[] = [ "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "Alternative Rock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fusion", "Bebop", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", // Because they really deserve their own genre "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A Cappella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop", "Synthpop"] +• `Const` **audioGenres**: string[] = [ "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "Alternative Rock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fusion", "Bebop", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", // Because they really deserve their own genre "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A Cappella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop", "Synthpop" ] ___ ### videoGenres -• `Const` **videoGenres**: string[] = [ "Action", "Action/Adventure", "Adult", "Adventure", "Catastrophe", "Child's", "Claymation", "Comedy", "Concert", "Documentary", "Drama", "Eastern", "Entertaining", "Erotic", "Extremal Sport", "Fantasy", "Fashion", "Historical", "Horror", "Horror/Mystic", "Humor", "Indian", "Informercial", "Melodrama", "Military & War", "Music Video", "Musical", "Mystery", "Nature", "Political Satire", "Popular Science", "Psychological Thriller", "Religion", "Science Fiction", "Scifi Action", "Slapstick", "Splatter", "Sports", "Thriller", "Western"] +• `Const` **videoGenres**: string[] = [ "Action", "Action/Adventure", "Adult", "Adventure", "Catastrophe", "Child's", "Claymation", "Comedy", "Concert", "Documentary", "Drama", "Eastern", "Entertaining", "Erotic", "Extremal Sport", "Fantasy", "Fashion", "Historical", "Horror", "Horror/Mystic", "Humor", "Indian", "Informercial", "Melodrama", "Military & War", "Music Video", "Musical", "Mystery", "Nature", "Political Satire", "Popular Science", "Psychological Thriller", "Religion", "Science Fiction", "Scifi Action", "Slapstick", "Splatter", "Sports", "Thriller", "Western" ] ## Functions diff --git a/docs/modules/_src_index_.md b/docs/modules/_src_index_.md index 83bba8a1..ddd7d7c9 100644 --- a/docs/modules/_src_index_.md +++ b/docs/modules/_src_index_.md @@ -68,8 +68,10 @@ * [MpegAudioChannelMode](_src_index_.md#mpegaudiochannelmode) * [MpegAudioFile](_src_index_.md#mpegaudiofile) * [MpegAudioHeader](_src_index_.md#mpegaudioheader) +* [MpegFile](_src_index_.md#mpegfile) * [MpegVbriHeader](_src_index_.md#mpegvbriheader) * [MpegVersion](_src_index_.md#mpegversion) +* [MpegVideoHeader](_src_index_.md#mpegvideoheader) * [MpegXingHeader](_src_index_.md#mpegxingheader) * [NotImplementedError](_src_index_.md#notimplementederror) * [Picture](_src_index_.md#picture) @@ -443,6 +445,12 @@ Re-exports: [MpegAudioHeader](../classes/_src_mpeg_mpegaudioheader_.mpegaudiohea ___ +### MpegFile + +Re-exports: [MpegFile](../classes/_src_mpeg_mpegfile_.mpegfile.md) + +___ + ### MpegVbriHeader Renames and re-exports: [VbriHeader](../classes/_src_mpeg_vbriheader_.vbriheader.md) @@ -455,6 +463,12 @@ Re-exports: [MpegVersion](../enums/_src_mpeg_mpegenums_.mpegversion.md) ___ +### MpegVideoHeader + +Re-exports: [MpegVideoHeader](../classes/_src_mpeg_mpegvideoheader_.mpegvideoheader.md) + +___ + ### MpegXingHeader Renames and re-exports: [XingHeader](../classes/_src_mpeg_xingheader_.xingheader.md) diff --git a/docs/modules/_src_mpeg_mpegfile_.md b/docs/modules/_src_mpeg_mpegfile_.md new file mode 100644 index 00000000..ecff3756 --- /dev/null +++ b/docs/modules/_src_mpeg_mpegfile_.md @@ -0,0 +1,15 @@ +**[node-taglib-sharp](../README.md)** + +> [Globals](../globals.md) / "src/mpeg/mpegFile" + +# Module: "src/mpeg/mpegFile" + +## Index + +### Enumerations + +* [MpegFileMarker](../enums/_src_mpeg_mpegfile_.mpegfilemarker.md) + +### Classes + +* [MpegFile](../classes/_src_mpeg_mpegfile_.mpegfile.md) diff --git a/docs/modules/_src_mpeg_mpegvideoheader_.md b/docs/modules/_src_mpeg_mpegvideoheader_.md new file mode 100644 index 00000000..cdee738f --- /dev/null +++ b/docs/modules/_src_mpeg_mpegvideoheader_.md @@ -0,0 +1,11 @@ +**[node-taglib-sharp](../README.md)** + +> [Globals](../globals.md) / "src/mpeg/mpegVideoHeader" + +# Module: "src/mpeg/mpegVideoHeader" + +## Index + +### Classes + +* [MpegVideoHeader](../classes/_src_mpeg_mpegvideoheader_.mpegvideoheader.md) diff --git a/package-lock.json b/package-lock.json index 63cc00d4..ccb69c76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1824,9 +1824,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, "interpret": { diff --git a/package.json b/package.json index 9a652965..0c5e82d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-taglib-sharp", "description": "Read and write audio/video/picture tags using a similar interface to TagLib#", - "version": "3.0.0", + "version": "3.1.0", "license": "LGPL-2.1-or-later", "author": "Ben Russell (https://github.com/benrr101)", "repository": "github:benrr101/node-taglib-sharp", diff --git a/src/errors.ts b/src/errors.ts index 87a0b889..de7105c4 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -33,3 +33,15 @@ export class NotSupportedError extends Error { return e.hasOwnProperty("isNotSupportedError"); } } + +export class UnsupportedFormatError extends Error { + public readonly isNotSupportedError: boolean = true; + + public constructor(message?: string) { + super(`Unsupported format${message ? `: ${message}` : ""}`); + } + + public static errorIs(e: Error): boolean { + return e.hasOwnProperty("isUnsupportedFormatError"); + } +} diff --git a/src/id3v2/syncData.ts b/src/id3v2/syncData.ts index 7472debb..bf556731 100644 --- a/src/id3v2/syncData.ts +++ b/src/id3v2/syncData.ts @@ -30,6 +30,10 @@ export default { /** * Resynchronizes a {@link ByteVector} object by removing the added bytes. + * @remarks In some cases (as determined by header flags), the metadata contains MPEG stream + * synchronization bytes that were "unsynchronized" by inserting empty bytes in between + * them. This method removes those bytes such that the original metadata bytes are + * returned. * @param data Object to resynchronize */ resyncByteVector: (data: ByteVector): void => { @@ -74,6 +78,9 @@ export default { /** * Unsynchronizes a {@link ByteVector} object by inserting empty bytes where necessary. + * @remarks This is necessary in some cases in order for the MPEG parser to ignore bytes used + * for MPEG stream synchronization that occur accidentally in metadata from being treated + * as synchronization bytes. * @param data Object to unsynchronize */ unsyncByteVector: (data: ByteVector): void => { diff --git a/src/index.ts b/src/index.ts index d47e57c8..c98b024d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,12 +79,14 @@ export { UserUrlLinkFrame as Id3v2UserUrlLinkFrame } from "./id3v2/frames/urlLinkFrame"; -// MPEG //////////////////////////////////////////////////////////////////// +// MPEG1/2 ///////////////////////////////////////////////////////////////// export {default as MpegAudioFile} from "./mpeg/mpegAudioFile"; export {default as MpegAudioHeader} from "./mpeg/mpegAudioHeader"; export { ChannelMode as MpegAudioChannelMode, MpegVersion as MpegVersion } from "./mpeg/mpegEnums"; +export {default as MpegFile} from "./mpeg/mpegFile"; export {default as MpegVbriHeader} from "./mpeg/vbriHeader"; +export {default as MpegVideoHeader} from "./mpeg/mpegVideoHeader"; export {default as MpegXingHeader} from "./mpeg/xingHeader"; diff --git a/src/mpeg/mpegFile.ts b/src/mpeg/mpegFile.ts new file mode 100644 index 00000000..0b9cc9a7 --- /dev/null +++ b/src/mpeg/mpegFile.ts @@ -0,0 +1,361 @@ +import MpegAudioHeader from "./mpegAudioHeader"; +import MpegVideoHeader from "./mpegVideoHeader"; +import NonContainerFile from "../nonContainer/nonContainerFile"; +import NonContainerTag from "../nonContainer/nonContainerTag"; +import Properties from "../properties"; +import {ByteVector} from "../byteVector"; +import {CorruptFileError, UnsupportedFormatError} from "../errors"; +import {File, ReadStyle} from "../file"; +import {IFileAbstraction} from "../fileAbstraction"; +import {MpegVersion} from "./mpegEnums"; +import {Tag, TagTypes} from "../tag"; + +/** + * Indicates the type of marker found in an MPEG file. + */ +enum MpegFileMarker { + /** + * An invalid marker. + */ + Corrupt = -1, + + /** + * A zero value marker. + */ + Zero = 0, + + /** + * A marker indicating a system sync packet. + */ + SystemSyncPacket = 0xBA, + + /** + * A marker indicating a video sync packet. + */ + VideoSyncPacket = 0xB3, + + /** + * A marker indicating a system packet. + */ + SystemPacket = 0xBB, + + /** + * A marker indicating a padding packet. + */ + PaddingPacket = 0xBE, + + /** + * A marker indicating an audio packet. + */ + AudioPacket = 0xC0, + + /** + * A marker indicating a video packet. + */ + VideoPacket = 0xE0, + + /** + * A marker indicating the end of a stream. + */ + EndOfStream = 0xB9 +} + +/** + * This class extends {@link NonContainerFile} to provide tagging and properties support for + * MPEG-1, MPEG-2, and MPEG-2.5 video files. + * @remarks A {@link Id3v1Tag} and {@link Id3v2Tag} will be added automatically to any file that + * does not contain one. This change does not affect the file until it is saved and can be + * reversed using the following method: + * `file.removeTags(file.tagTypes & ~file.tagTypesOnDisk);` + */ +export default class MpegFile extends NonContainerFile { + private static readonly _markerStart = ByteVector.fromByteArray(new Uint8Array([0, 0, 1])); + + private _audioFound = false; + private _audioHeader: MpegAudioHeader; + private _endTime: number; + private _startTime: number | undefined; + private _version: MpegVersion; + private _videoFound = false; + private _videoHeader: MpegVideoHeader; + + public constructor(file: IFileAbstraction|string, propertiesStyle: ReadStyle) { + super(file, propertiesStyle); + } + + /** + * Gets a tag of a specified type from the current instance, optionally creating a new tag if + * possible. + * @remarks {@link Id3v2Tag}, {@link Id3v1Tag}, and {@link ApeTag} will be added to the end of + * the file. All other tag types will be ignored as they are unsupported by MPEG files. + * @param type Type of tag to read + * @param create Whether or not to try and create the tag if one is not found + * @returns Tag Tag that was found in or added to the current instance. If no matching tag was + * found and none was created, `undefined` is returned. + */ + public getTag(type: TagTypes, create: boolean): Tag { + const tag = ( this.tag).getTag(type); + if (tag || !create) { + return tag; + } + + switch (type) { + case TagTypes.Id3v1: + return this.endTag.addTag(type, this.tag); + case TagTypes.Id3v2: + return this.endTag.addTag(type, this.tag); + case TagTypes.Ape: + return this.endTag.addTag(type, this.tag); + default: + return undefined; + } + } + + /** @inheritDoc */ + protected readEnd(end: number, propertiesStyle: ReadStyle) { + // Make sure we have ID3v1 and ID3v2 tags + this.getTag(TagTypes.Id3v1, true); + this.getTag(TagTypes.Id3v2, true); + + if ((propertiesStyle & ReadStyle.Average) === 0 || this._startTime === undefined) { + return; + } + + // Enable to search the marker in the entire file if non are found so far + if (end === this.length) { + end = 0; + } + + end = this.rFindMarkerPosition(end, MpegFileMarker.SystemSyncPacket); + this._endTime = this.readTimestamp(end + 4); + } + + /** @inheritDoc */ + protected readProperties(_start: number, _end: number, _propertiesStyle: ReadStyle): Properties { + const durationMilliseconds = this._startTime === undefined + ? 0 + : (this._endTime - this._startTime) * 1000; + return new Properties(durationMilliseconds, [this._videoHeader, this._audioHeader]); + } + + /** @inheritDoc */ + protected readStart(start: number, propertiesStyle: ReadStyle): void { + if ((propertiesStyle & ReadStyle.Average) === 0) { + return; + } + + start = this.findNextMarkerPosition(start, MpegFileMarker.SystemSyncPacket); + this.readSystemFile(start); + } + + // #region Private Methods + + private findFirstMarker(position: number): {marker: MpegFileMarker, position: number} { + position = this.find(MpegFile._markerStart, position); + if (position < 0) { + throw new CorruptFileError("Marker not found"); + } + + return { + marker: this.getMarker(position), + position: position + }; + } + + private findNextMarkerPosition(position: number, marker: MpegFileMarker): number { + const packet = ByteVector.concatenate( + MpegFile._markerStart, + marker + ); + position = this.find(packet, position); + + if (position < 0) { + throw new CorruptFileError("Marker not found"); + } + + return position; + } + + private getMarker(position: number): MpegFileMarker { + this.seek(position); + const identifier = this.readBlock(4); + + if (identifier.length === 4 && identifier.startsWith(MpegFile._markerStart)) { + return identifier.get(3); + } + + throw new CorruptFileError(`Invalid marker at position ${position}`); + } + + private readAudioPacket(position: number): number { + this.seek(position + 4); + const length = this.readBlock(2).toUShort(); + const returnValue = position + length; + + if (this._audioFound) { + return returnValue; + } + + // There is a maximum of 16 stuffing bytes, read to the PTS/DTS flags + const packetHeaderBytes = this.readBlock(19); + let i = 0; + while (i < packetHeaderBytes.length && packetHeaderBytes.get(i) === 0xFF) { + // Byte is a stuffing byte + i++; + } + + if ((packetHeaderBytes.get(i) & 0x40 ) !== 0) { + // STD buffer size is unexpected for audio packets, but whatever + i++; + } + + // Decode the PTS/DTS flags + const timestampFlags = packetHeaderBytes.get(i); + const dataOffset = 4 + 2 + i // Packet marker + packet length + stuffing bytes/STD buffer size + + ((timestampFlags & 0x20) > 0 ? 4 : 0) // Presentation timestamp + + ((timestampFlags & 0x10) > 0 ? 4 : 0); // Decode timestamp + + // Decode the MPEG audio header + const audioHeaderResult = MpegAudioHeader.find(this, position + dataOffset, length - 9); + this._audioFound = audioHeaderResult.success; + this._audioHeader = audioHeaderResult.header; + + return position + length; + } + + private readSystemFile(position: number): void { + const sanityLimit = 100; + + for ( + let i = 0; + i < sanityLimit && (this._startTime === undefined || !this._audioFound || !this._videoFound); + i++ + ) { + const markerResult = this.findFirstMarker(position); + position = markerResult.position; + + switch (markerResult.marker) { + case MpegFileMarker.SystemSyncPacket: + position = this.readSystemSyncPacket(position); + break; + case MpegFileMarker.SystemPacket: + case MpegFileMarker.PaddingPacket: + this.seek(position + 4); + position += this.readBlock(2).toUShort() + 6; + break; + case MpegFileMarker.VideoPacket: + position = this.readVideoPacket(position); + break; + case MpegFileMarker.AudioPacket: + position = this.readAudioPacket(position); + break; + case MpegFileMarker.EndOfStream: + return; + default: + position += 4; + break; + } + } + } + + private readSystemSyncPacket(position: number): number { + let packetSize = 0; + this.seek(position + 4); + + const versionInfo = this.readBlock(1).get(0); + if ((versionInfo & 0xF0) === 0x20) { + this._version = MpegVersion.Version1; + packetSize = 12; + } else if ((versionInfo & 0xC0) === 0x40) { + this._version = MpegVersion.Version2; + this.seek(position + 13); + packetSize = 14 + (this.readBlock(1).get(0) & 0x07); + } else { + throw new UnsupportedFormatError("Unknown MPEG version"); + } + + if (this._startTime === undefined) { + this._startTime = this.readTimestamp(position + 4); + } + + return position + packetSize; + } + + private readTimestamp(position: number): number { + let high: number; + let low: number; + + this.seek(position); + if (this._version === MpegVersion.Version1) { + const data = this.readBlock(5); + high = ((data.get(0) >>> 3) & 0x01) >>> 0; + low = (((data.get(0) >>> 1) & 0x03 << 30) + | (data.get(1) << 22) + | ((data.get(2) >>> 1) << 15) + | (data.get(3) << 7) + | (data.get(4) >>> 1)) >>> 0; + } else { + const data = this.readBlock(6); + high = ((data.get(0) & 0x20) >>> 5); + low = (((data.get(0) & 0x03) << 28) + | (data.get(1) << 20) + | ((data.get(2) & 0xF8) << 12) + | ((data.get(2) & 0x03) << 13) + | (data.get(3) << 5) + | (data.get(4) >>> 3)) >>> 0; + } + + return (high * 0x10000 * 0x10000 + low) / 90000; + } + + private readVideoPacket(position: number): number { + this.seek(position + 4); + const length = this.readBlock(2).toUShort(); + let offset = position + 6; + + while (!this._videoFound && offset < position + length) { + const markerResult = this.findFirstMarker(offset); + offset = markerResult.position; + if (markerResult.marker === MpegFileMarker.VideoSyncPacket) { + this._videoHeader = new MpegVideoHeader(this, offset + 4); + this._videoFound = true; + } else { + // Advance the offset by 6 bytes, so the next iteration of the loop won't find the + // same marker and get stuck. 6 bytes because findFirstMarker is a generic find + // that found get both PES packets and stream packets, the smallest possible PES + // packet with a size of 0 would be 6 bytes. + offset += 6; + } + } + + return position + length; + } + + private rFindMarkerPosition(position: number, marker: MpegFileMarker): number { + const packet = ByteVector.concatenate( + MpegFile._markerStart, + marker + ); + position = this.rFind(packet, position); + + if (position < 0) { + throw new CorruptFileError("Marker not found"); + } + + return position; + } + + // #endregion +} + +//////////////////////////////////////////////////////////////////////////// +// Register the file type +[ + "taglib/mpg", + "taglib/mpeg", + "taglib/mpe", + "taglib/mpv2", + "taglib/m2v", + "video/x-mpg", + "video/mpeg" +].forEach((mt) => File.addFileType(mt, MpegFile)); diff --git a/src/mpeg/mpegVideoHeader.ts b/src/mpeg/mpegVideoHeader.ts new file mode 100644 index 00000000..397e0acf --- /dev/null +++ b/src/mpeg/mpegVideoHeader.ts @@ -0,0 +1,72 @@ +import {CorruptFileError} from "../errors"; +import {File} from "../file"; +import {IVideoCodec, MediaTypes} from "../iCodec"; +import {Guards} from "../utils"; + +/** + * Provides information about an MPEG video stream. + */ +export default class MpegVideoHeader implements IVideoCodec { + private static readonly _frameRates = [ + 0, 24000 / 1001, 24, 25, 30000 / 1001, 30, 50, 60000 / 1001, 60 + ]; + + private readonly _frameRateIndex: number; + private readonly _videoBitrate: number; + private readonly _videoHeight: number; + private readonly _videoWidth: number; + + /** + * Constructs and initializes a new instance of {@link MpegVideoHeader} by reading it from a + * specified location in a specified file. + * @param file File to read the header from + * @param position Position in `file` at which the header begins + */ + public constructor(file: File, position: number) { + Guards.truthy(file, "file"); + Guards.uint(position, "position"); + + file.seek(position); + const data = file.readBlock(7); + + if (data.length < 7) { + throw new CorruptFileError("Insufficient data in header"); + } + + this._videoWidth = data.mid(0, 2).toUShort() >>> 4; + this._videoHeight = (data.mid(1, 2).toShort() & 0x0FFF) >>> 0; + this._frameRateIndex = (data.get(3) & 0x0F) >>> 0; + this._videoBitrate = ((data.mid(4, 3).toUInt() >>> 6) & 0x3FFFF) >>> 0; + } + + // #region + + /** @inheritDoc */ + public get description(): string { return "MPEG Video"; } + + /** + * @inheritDoc + * For MPEG, this is always 0 + * @TODO: Can we calculate the duration? + */ + public get durationMilliseconds(): number { return 0; } + + /** @inheritDoc */ + public get mediaTypes(): MediaTypes { return MediaTypes.Video; } + + /** @inheritDoc */ + public get videoBitrate(): number { return this._videoBitrate; } + + /** @inheritDoc */ + public get videoFrameRate(): number { + return this._frameRateIndex < 9 ? MpegVideoHeader._frameRates[this._frameRateIndex] : 0; + } + + /** @inheritDoc */ + public get videoHeight(): number { return this._videoHeight; } + + /** @inheritDoc */ + public get videoWidth(): number { return this._videoWidth; } + + // #endregion +} diff --git a/src/properties.ts b/src/properties.ts index e7571aa5..9fe335d4 100644 --- a/src/properties.ts +++ b/src/properties.ts @@ -135,6 +135,8 @@ export default class Properties implements ILosslessAudioCodec, IVideoCodec, IPh return this.findCodecProperty(MediaTypes.Video, (c) => c.videoWidth, 0); } + // @TODO: Add support for framerate + // #endregion // #region Private Helpers diff --git a/src/tag.ts b/src/tag.ts index 69bead0b..fafc6ea5 100644 --- a/src/tag.ts +++ b/src/tag.ts @@ -444,8 +444,8 @@ export abstract class Tag { * Gets the genres of the media represented by the current instance. * @remarks This field represents genres that apply to the song, album, or video. This is often * used for filtering media. - * A list of common audio genres as popularized by ID3v1 is stored in {@link audioGenres}. - * Additionally, {@link videoGenres} contains video genres as used by DivX. + * A list of common audio genres as popularized by ID3v1 is stored in `genres.ts`. + * Additionally, `genres.ts` contains video genres as used by DivX. * @returns Genres of the media represented by the current instance or an empty array if no * value is present. */ @@ -454,8 +454,8 @@ export abstract class Tag { * Sets the genres of the media represented by the current instance. * @remarks This field represents genres that apply to the song, album, or video. This is often * used for filtering media. - * A list of common audio genres as popularized by ID3v1 is stored in {@link audioGenres}. - * Additionally, {@link videoGenres} contains video genres as used by DivX. + * A list of common audio genres as popularized by ID3v1 is stored in `genres.ts. + * Additionally, `genres.ts` contains video genres as used by DivX. * @param value Genres of the media represented by the current instance or an empty array if no * value is present. */ diff --git a/test-integration/id3v24_fileTests.ts b/test-integration/id3v24_fileTests.ts index c498bf95..58906e78 100644 --- a/test-integration/id3v24_fileTests.ts +++ b/test-integration/id3v24_fileTests.ts @@ -4,7 +4,7 @@ import {suite, test} from "mocha-typescript"; import TestConstants from "./utilities/testConstants"; import {File, Id3v2FrameIdentifiers, Id3v2Tag, ReadStyle, TagTypes} from "../src"; -import {StandardFileTests} from "./utilities/standardFileTests"; +import {StandardFileTests, TestTagLevel} from "./utilities/standardFileTests"; // Setup chai Chai.use(ChaiAsPromised); @@ -119,7 +119,7 @@ const assert = Chai.assert; @test public writeStandardTags() { const tmpFilePath = TestConstants.getTempFilePath(Id3v24_FileTests.sampleTmpFileName); - StandardFileTests.writeStandardTags(Id3v24_FileTests.sampleFilePath, tmpFilePath); + StandardFileTests.writeStandardTags(Id3v24_FileTests.sampleFilePath, tmpFilePath, TestTagLevel.Medium); } @test diff --git a/test-integration/mpegFormatTest.ts b/test-integration/mpegFormatTest.ts new file mode 100644 index 00000000..6c92821d --- /dev/null +++ b/test-integration/mpegFormatTest.ts @@ -0,0 +1,76 @@ +import * as Chai from "chai"; +import {suite, test} from "mocha-typescript"; + +import TestConstants from "./utilities/testConstants"; +import {File, ReadStyle} from "../src"; +import {StandardFileTests, TestTagLevel} from "./utilities/standardFileTests"; + +const assert = Chai.assert; + +@suite class Mpeg_FileTests { + private static readonly corruptFilePath = TestConstants.getCorruptFilePath("corrupt.mpg"); + private static readonly sampleFilePath = TestConstants.getSampleFilePath("sample.mpg"); + private static readonly tmpFileName = "tmpwrite.mpg"; + + private static file: File; + + public static before() { + Mpeg_FileTests.file = File.createFromPath(Mpeg_FileTests.sampleFilePath); + } + + public static after() { + Mpeg_FileTests.file.dispose(); + } + + @test + public readAudioProperties() { + assert.strictEqual(Mpeg_FileTests.file.properties.audioBitrate, 128); + assert.strictEqual(Mpeg_FileTests.file.properties.audioChannels, 2); + assert.strictEqual(Mpeg_FileTests.file.properties.audioSampleRate, 44100); + assert.approximately(Mpeg_FileTests.file.properties.durationMilliseconds, 1391, 0.5); + assert.strictEqual(Mpeg_FileTests.file.properties.videoHeight, 480); + assert.strictEqual(Mpeg_FileTests.file.properties.videoWidth, 640); + } + + @test + public readTags() { + assert.isTrue(Mpeg_FileTests.file.tag.isEmpty); + } + + @test + public RemoveStandardTags() { + StandardFileTests.removeStandardTags(Mpeg_FileTests.sampleFilePath, Mpeg_FileTests.tmpFileName); + } + + @test + public TestCorruptionResistance() { + StandardFileTests.testCorruptionResistance(Mpeg_FileTests.corruptFilePath); + } + + @test + public WriteStandardPictures() { + StandardFileTests.writeStandardPictures( + Mpeg_FileTests.sampleFilePath, + Mpeg_FileTests.tmpFileName, + ReadStyle.None + ); + } + + // @test + // public WriteStandardPicturesLazy() { + // StandardFileTests.writeStandardPictures( + // Mpeg_FileTests.sampleFilePath, + // Mpeg_FileTests.tmpFileName, + // ReadStyle.PictureLazy + // ); + // } + + @test + public WriteStandardTags() { + StandardFileTests.writeStandardTags( + Mpeg_FileTests.sampleFilePath, + Mpeg_FileTests.tmpFileName, + TestTagLevel.Medium + ); + } +} diff --git a/test-integration/resources/corruptSamples/corrupt.mpg b/test-integration/resources/corruptSamples/corrupt.mpg new file mode 100644 index 00000000..e69de29b diff --git a/test-integration/resources/samples/sample.mpg b/test-integration/resources/samples/sample.mpg new file mode 100644 index 00000000..400b8036 Binary files /dev/null and b/test-integration/resources/samples/sample.mpg differ diff --git a/test-integration/utilities/standardFileTests.ts b/test-integration/utilities/standardFileTests.ts index e50a4a98..ceda3b14 100644 --- a/test-integration/utilities/standardFileTests.ts +++ b/test-integration/utilities/standardFileTests.ts @@ -238,12 +238,12 @@ export class StandardFileTests { assert.strictEqual(tag.titleSort, "title sort, TEST"); assert.strictEqual(tag.albumSort, "album sort, TEST"); assert.strictEqual(tag.joinedPerformersSort, "performer sort 1, TEST; performer sort 2, TEST"); - assert.strictEqual(tag.composersSort.join(";"), "composer sort 1, TEST; composer sort 2, TEST"); - assert.strictEqual(tag.albumArtistsSort.join(";"), "album artist sort 1, TEST; album artist sort 2, TEST"); + assert.strictEqual(tag.composersSort.join("; "), "composer sort 1, TEST; composer sort 2, TEST"); + assert.strictEqual(tag.albumArtistsSort.join("; "), "album artist sort 1, TEST; album artist sort 2, TEST"); assert.strictEqual(tag.beatsPerMinute, 120); - assert.strictEqual(tag.performersRole.join("\n"), "TEST role 1a; TEST role 1b\nTEST role 2"); + assert.strictEqual(tag.performersRole.join("\n"), "TEST role 1a;TEST role 1b\nTEST role 2"); - const dateTagged = (new Date(2017, 9, 12, 22, 47, 42)).getTime(); + const dateTagged = (new Date(2017, 8, 12, 22, 47, 42)).getTime(); assert.strictEqual(tag.dateTagged.getTime(), dateTagged); // @TODO: This doesn't correctly handle what happens if the field isn't supported on the version of the tag } diff --git a/test-unit/ape/apeTagFooterTests.ts b/test-unit/ape/apeTagFooterTests.ts index 5739004f..d6370978 100644 --- a/test-unit/ape/apeTagFooterTests.ts +++ b/test-unit/ape/apeTagFooterTests.ts @@ -140,7 +140,7 @@ const _sampleData = ByteVector.concatenate( // Act / Assert PropertyTests.propertyRoundTrip( - (v: number) => { footer.itemCount = v }, + (v: number) => { footer.itemCount = v; }, () => footer.itemCount, 8086 ); @@ -235,7 +235,7 @@ const _sampleData = ByteVector.concatenate( } @test - public renderHeader_headerPresentisNotHeader() { + public renderHeader_headerPresentIsNotHeader() { // Arrange const footer = ApeTagFooter.fromData(_sampleData); footer.flags = ApeTagFooterFlags.HeaderPresent; @@ -270,7 +270,7 @@ const _sampleData = ByteVector.concatenate( } @test - public renderHeader_headerPresentisHeader() { + public renderHeader_headerPresentIsHeader() { // Arrange const footer = ApeTagFooter.fromData(_sampleData); footer.flags = (ApeTagFooterFlags.HeaderPresent | ApeTagFooterFlags.IsHeader) >>> 0; diff --git a/test-unit/ape/apeTagTests.ts b/test-unit/ape/apeTagTests.ts index 50607134..5ad2cb31 100644 --- a/test-unit/ape/apeTagTests.ts +++ b/test-unit/ape/apeTagTests.ts @@ -461,7 +461,7 @@ function getTestTagFooter(flags: ApeTagFooterFlags, itemCount: number, itemPlusF assert.strictEqual(tag.items[0].key, "DateTagged"); assert.deepStrictEqual(tag.items[0].text, ["2020-04-25T12:34:56"]); - tag.items[0] = ApeTagItem.fromTextValues("DateTagged", "bunchagarbage"); + tag.items[0] = ApeTagItem.fromTextValues("DateTagged", "buncha_garbage"); assert.isUndefined(tag.dateTagged); PropertyTests.propertyRoundTrip(set, get, undefined); diff --git a/test-unit/byteVectorConstructorTests.ts b/test-unit/byteVectorConstructorTests.ts index a741d8c7..6112265c 100644 --- a/test-unit/byteVectorConstructorTests.ts +++ b/test-unit/byteVectorConstructorTests.ts @@ -1017,7 +1017,7 @@ const assert = Chai.assert; @test public fromSize_badFillValue() { // Arrange, Act, Assert - Testers.testByte((v: number) => { ByteVector.fromSize(1, v); }) + Testers.testByte((v: number) => { ByteVector.fromSize(1, v); }); } @test diff --git a/test-unit/byteVectorVoidMethodTests.ts b/test-unit/byteVectorVoidMethodTests.ts index ebd0b16c..973630ef 100644 --- a/test-unit/byteVectorVoidMethodTests.ts +++ b/test-unit/byteVectorVoidMethodTests.ts @@ -462,7 +462,7 @@ const assert = Chai.assert; } @test - public compareTo_inequalSizes() { + public compareTo_unequalSizes() { // Arrange const bv = ByteVector.concatenate(0x00, 0x05); const other = ByteVector.concatenate(0x00); @@ -1598,7 +1598,7 @@ const assert = Chai.assert; const bv = ByteVector.empty(); // Act / Assert - Testers.testTruthy((v: ByteVector) => { bv.startsWith(undefined); }); + Testers.testTruthy((v: ByteVector) => { bv.startsWith(v); }); } @test diff --git a/test-unit/fileTests.ts b/test-unit/fileTests.ts index 6c6ddd75..7da8dfbc 100644 --- a/test-unit/fileTests.ts +++ b/test-unit/fileTests.ts @@ -746,11 +746,11 @@ class TestFile extends File { public get stream(): IStream { return this._fileStream; } - public getTag(types: TagTypes, create: boolean): Tag { + public getTag(_types: TagTypes, _create: boolean): Tag { throw new Error("Not implemented"); } - public removeTags(types: TagTypes): Tag { + public removeTags(_types: TagTypes): Tag { throw new Error("Not implemented"); } diff --git a/test-unit/id3v2/attachmentsFrameTests.ts b/test-unit/id3v2/attachmentsFrameTests.ts index 69d8e584..183566fa 100644 --- a/test-unit/id3v2/attachmentsFrameTests.ts +++ b/test-unit/id3v2/attachmentsFrameTests.ts @@ -601,7 +601,7 @@ function getCustomTestFrame(data: ByteVector, desc: string, filename: string, mi public render_apicV2InvalidMimeType_correctsEncoding() { // Arrange const data = ByteVector.fromString("fuxbuxqux"); - const frame = getCustomTestFrame(data, "foo", "bar", "this/isnot/amimetype", PictureType.FrontCover); + const frame = getCustomTestFrame(data, "foo", "bar", "this/is_not/a_mimetype", PictureType.FrontCover); // Act const output = frame.render(2); diff --git a/test-unit/id3v2/frameFactoryTests.ts b/test-unit/id3v2/frameFactoryTests.ts index 463a239f..da531a62 100644 --- a/test-unit/id3v2/frameFactoryTests.ts +++ b/test-unit/id3v2/frameFactoryTests.ts @@ -1,28 +1,28 @@ import * as Chai from "chai"; import * as ChaiAsPromised from "chai-as-promised"; import * as TypeMoq from "typemoq"; -import {slow, suite, test, timeout} from "mocha-typescript"; +import TestFile from "../utilities/testFile"; +import {suite, test} from "mocha-typescript"; +import CommentsFrame from "../../src/id3v2/frames/commentsFrame"; import FrameFactory, {FrameCreator} from "../../src/id3v2/frames/frameFactory"; +import PlayCountFrame from "../../src/id3v2/frames/playCountFrame"; +import PopularimeterFrame from "../../src/id3v2/frames/popularimeterFrame"; +import PrivateFrame from "../../src/id3v2/frames/privateFrame"; +import TermsOfUseFrame from "../../src/id3v2/frames/termsOfUseFrame"; +import UniqueFileIdentifierFrame from "../../src/id3v2/frames/uniqueFileIdentifierFrame"; +import UnsynchronizedLyricsFrame from "../../src/id3v2/frames/unsynchronizedLyricsFrame"; import {ByteVector, StringType} from "../../src/byteVector"; -import TestFile from "../utilities/testFile"; +import {EventTimeCodeFrame} from "../../src/id3v2/frames/eventTimeCodeFrame"; import {Frame, FrameClassType} from "../../src/id3v2/frames/frame"; import {Id3v2FrameFlags, Id3v2FrameHeader} from "../../src/id3v2/frames/frameHeader"; import {FrameIdentifiers} from "../../src/id3v2/frameIdentifiers"; +import {PictureType} from "../../src/picture"; +import {RelativeVolumeFrame} from "../../src/id3v2/frames/relativeVolumeFrame"; import {TextInformationFrame, UserTextInformationFrame} from "../../src/id3v2/frames/textInformationFrame"; -import UniqueFileIdentifierFrame from "../../src/id3v2/frames/uniqueFileIdentifierFrame"; -import UnsynchronizedLyricsFrame from "../../src/id3v2/frames/unsynchronizedLyricsFrame"; import {SynchronizedLyricsFrame} from "../../src/id3v2/frames/synchronizedLyricsFrame"; -import {SynchronizedTextType, TimestampFormat} from "../../src/id3v2/utilTypes"; -import CommentsFrame from "../../src/id3v2/frames/commentsFrame"; -import {RelativeVolumeFrame} from "../../src/id3v2/frames/relativeVolumeFrame"; -import {PictureType} from "../../src/picture"; -import PlayCountFrame from "../../src/id3v2/frames/playCountFrame"; -import PopularimeterFrame from "../../src/id3v2/frames/popularimeterFrame"; -import TermsOfUseFrame from "../../src/id3v2/frames/termsOfUseFrame"; -import PrivateFrame from "../../src/id3v2/frames/privateFrame"; import {UrlLinkFrame, UserUrlLinkFrame} from "../../src/id3v2/frames/urlLinkFrame"; -import {EventTimeCodeFrame} from "../../src/id3v2/frames/eventTimeCodeFrame"; +import {SynchronizedTextType, TimestampFormat} from "../../src/id3v2/utilTypes"; // Setup chai Chai.use(ChaiAsPromised); diff --git a/test-unit/id3v2/frameIdentifiersTests.ts b/test-unit/id3v2/frameIdentifiersTests.ts index 2312308e..876c7a4c 100644 --- a/test-unit/id3v2/frameIdentifiersTests.ts +++ b/test-unit/id3v2/frameIdentifiersTests.ts @@ -1,6 +1,6 @@ import * as Chai from "chai"; import * as ChaiAsPromised from "chai-as-promised"; -import {slow, suite, test, timeout} from "mocha-typescript"; +import {suite, test} from "mocha-typescript"; import {ByteVector} from "../../src/byteVector"; import {FrameIdentifier, FrameIdentifiers} from "../../src/id3v2/frameIdentifiers"; diff --git a/test-unit/id3v2/frameTests.ts b/test-unit/id3v2/frameTests.ts index f67b30cf..a301f189 100644 --- a/test-unit/id3v2/frameTests.ts +++ b/test-unit/id3v2/frameTests.ts @@ -36,9 +36,9 @@ class TestFrame extends Frame { return undefined; } - protected parseFields(data: ByteVector, version: number): void { /* no-op */ } + protected parseFields(_data: ByteVector, _version: number): void { /* no-op */ } - protected renderFields(version: number): ByteVector { + protected renderFields(_version: number): ByteVector { return ByteVector.fromByteVector(TestFrame.renderFieldData); } } @@ -142,6 +142,7 @@ class TestFrame extends Frame { 0x88, TestFrame.renderFieldData ); + assert.isTrue(ByteVector.equal(output, expected)); } @test diff --git a/test-unit/id3v2/id3v2TagTests.ts b/test-unit/id3v2/id3v2TagTests.ts index 8446f342..18b81549 100644 --- a/test-unit/id3v2/id3v2TagTests.ts +++ b/test-unit/id3v2/id3v2TagTests.ts @@ -2,7 +2,7 @@ import * as BigInt from "big-integer"; import * as Chai from "chai"; import * as ChaiAsPromised from "chai-as-promised"; import * as TypeMoq from "typemoq"; -import {slow, suite, test, timeout} from "mocha-typescript"; +import {suite, test} from "mocha-typescript"; import Id3v2Tag from "../../src/id3v2/id3v2Tag"; import SyncData from "../../src/id3v2/syncData"; @@ -1138,7 +1138,7 @@ function getTestTagHeader(version: number, flags: Id3v2TagHeaderFlags, tagSize: assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.TDTG); assert.deepStrictEqual(( tag.frames[0]).text, ["2020-04-25T12:34:56"]); - ( tag.frames[0]).text = ["bunchagarbage"]; + ( tag.frames[0]).text = ["buncha_garbage"]; assert.isUndefined(tag.dateTagged); PropertyTests.propertyRoundTrip(set, get, undefined); diff --git a/test-unit/id3v2/relativeVolumeFrameTests.ts b/test-unit/id3v2/relativeVolumeFrameTests.ts index 7e80bad0..cd37744b 100644 --- a/test-unit/id3v2/relativeVolumeFrameTests.ts +++ b/test-unit/id3v2/relativeVolumeFrameTests.ts @@ -390,7 +390,7 @@ const assert = Chai.assert; @test public find_falsyFrames() { // Act / Assert - Testers.testTruthy((v: RelativeVolumeFrame[]) => { RelativeVolumeFrame.find(undefined, "foo"); }); + Testers.testTruthy((v: RelativeVolumeFrame[]) => { RelativeVolumeFrame.find(v, "foo"); }); } @test diff --git a/test-unit/id3v2/syncDataTests.ts b/test-unit/id3v2/syncDataTests.ts index f197d553..3d717f86 100644 --- a/test-unit/id3v2/syncDataTests.ts +++ b/test-unit/id3v2/syncDataTests.ts @@ -82,7 +82,7 @@ const assert = Chai.assert; @test public unsyncByteVector_falsyData() { // Act/Assert - Testers.testTruthy((v: ByteVector) => { SyncData.unsyncByteVector(null); }); + Testers.testTruthy((v: ByteVector) => { SyncData.unsyncByteVector(v); }); } // @TODO: unsyncByteVector_validData diff --git a/test-unit/id3v2/tagFooterTests.ts b/test-unit/id3v2/tagFooterTests.ts index dad0f551..bd1d276c 100644 --- a/test-unit/id3v2/tagFooterTests.ts +++ b/test-unit/id3v2/tagFooterTests.ts @@ -28,7 +28,7 @@ const getTestFooter = (majorVersion: number, minorVersion: number, flags: Id3v2T @test public fromData_falsyData() { // Act/Assert - Testers.testTruthy((v: ByteVector) => { const _ = Id3v2TagFooter.fromData(null); }); + Testers.testTruthy((v: ByteVector) => { const _ = Id3v2TagFooter.fromData(v); }); } @test @@ -171,7 +171,7 @@ const getTestFooter = (majorVersion: number, minorVersion: number, flags: Id3v2T const footer = getTestFooter(4, 0, Id3v2TagHeaderFlags.None); // Act/Assert - Testers.testByte((v: number) => { footer.revisionNumber = -1; }); + Testers.testByte((v: number) => { footer.revisionNumber = v; }); } @test diff --git a/test-unit/id3v2/tagHeaderTests.ts b/test-unit/id3v2/tagHeaderTests.ts index 903c06d9..9453adbf 100644 --- a/test-unit/id3v2/tagHeaderTests.ts +++ b/test-unit/id3v2/tagHeaderTests.ts @@ -27,7 +27,7 @@ const getTestHeader = (majorVersion: number, minorVersion: number, flags: Id3v2T @test public falsyData() { // Act/Assert - Testers.testTruthy((v: ByteVector) => { const _ = Id3v2TagHeader.fromData(null); }); + Testers.testTruthy((v: ByteVector) => { const _ = Id3v2TagHeader.fromData(v); }); } @test @@ -322,7 +322,7 @@ const getTestHeader = (majorVersion: number, minorVersion: number, flags: Id3v2T const header = getTestHeader(4, 0, Id3v2TagHeaderFlags.None); // Act/Assert - Testers.testUint((v: number) => { header.tagSize = -1; }); + Testers.testUint((v: number) => { header.tagSize = v; }); assert.throws(() => { header.tagSize = 0xF0000000; }); } diff --git a/test-unit/id3v2/unknownFrameTests.ts b/test-unit/id3v2/unknownFrameTests.ts index dae040e4..cebc4598 100644 --- a/test-unit/id3v2/unknownFrameTests.ts +++ b/test-unit/id3v2/unknownFrameTests.ts @@ -26,7 +26,7 @@ const assert = Chai.assert; @test public fromData_falsyType_throws() { // Act/Assert - Testers.testTruthy((v: FrameIdentifier) => { UnknownFrame.fromData(undefined, undefined); }); + Testers.testTruthy((v: FrameIdentifier) => { UnknownFrame.fromData(v, undefined); }); } @test diff --git a/test-unit/mpeg/audioHeaderTests.ts b/test-unit/mpeg/mpegAudioHeaderTests.ts similarity index 99% rename from test-unit/mpeg/audioHeaderTests.ts rename to test-unit/mpeg/mpegAudioHeaderTests.ts index ef2fc59c..a33c32ca 100644 --- a/test-unit/mpeg/audioHeaderTests.ts +++ b/test-unit/mpeg/mpegAudioHeaderTests.ts @@ -339,7 +339,7 @@ const assert = Chai.assert; } @test - public audioBitreateDuration_noVbrMpeg25Layer3_32() { + public audioBitrateDuration_noVbrMpeg25Layer3_32() { // Arrange const flags = 0x24000; // MPEG2.5, Layer3, 32kbps, 11025kHz const header = MpegAudioHeader.fromInfo(flags, 1024, XingHeader.unknown, VbriHeader.unknown); diff --git a/test-unit/mpeg/mpegVideoHeaderTests.ts b/test-unit/mpeg/mpegVideoHeaderTests.ts new file mode 100644 index 00000000..29d40d7b --- /dev/null +++ b/test-unit/mpeg/mpegVideoHeaderTests.ts @@ -0,0 +1,55 @@ +import * as Chai from "chai"; +import * as TypeMoq from "typemoq"; +import TestFile from "../utilities/testFile"; +import Testers from "../utilities/testers"; +import {suite, test} from "mocha-typescript"; + +import MpegVideoHeader from "../../src/mpeg/mpegVideoHeader"; +import {ByteVector} from "../../src/byteVector"; +import {File} from "../../src/file"; +import {MediaTypes} from "../../src/iCodec"; + +// Setup Chai +const assert = Chai.assert; + +@suite class MpegVideoHeader_ConstructorTests { + @test + public constructor_invalidArguments() { + // Act/Assert + Testers.testTruthy((f: File) => { const _ = new MpegVideoHeader(f, 0); }); + Testers.testUint((p: number) => { const _ = new MpegVideoHeader(TypeMoq.Mock.ofType().object, p); }); + } + + @test + public constructor_headerTooShort() { + // Arrange + const mockData = ByteVector.fromSize(10); + const mockFile = TestFile.getFile(mockData); + + // Act/Assert + assert.throws(() => { const _ = new MpegVideoHeader(mockFile, 5); }); + } + + @test + public constructor_validData() { + // Arrange + const mockData = ByteVector.concatenate( + 0x00, 0x00, 0x00, + 0x28, 0x31, 0xE3, 0x26, // 643x483, 4:3 aspect ratio, 50fps + 0xD8, 0x53, 0xBF // 221518bps + ); + const mockFile = TestFile.getFile(mockData); + + // Act + const header = new MpegVideoHeader(mockFile, 3); + + // Assert + assert.strictEqual(header.description, "MPEG Video"); + assert.strictEqual(header.durationMilliseconds, 0); + assert.strictEqual(header.mediaTypes, MediaTypes.Video); + assert.strictEqual(header.videoBitrate, 221518); + assert.strictEqual(header.videoFrameRate, 50); + assert.strictEqual(header.videoHeight, 483); + assert.strictEqual(header.videoWidth, 643); + } +} diff --git a/test-unit/mpeg/xingHeaderTests.ts b/test-unit/mpeg/xingHeaderTests.ts index c6e42937..1b466c3e 100644 --- a/test-unit/mpeg/xingHeaderTests.ts +++ b/test-unit/mpeg/xingHeaderTests.ts @@ -15,7 +15,7 @@ const assert = Chai.assert; @test public fromInfo_invalidParameters() { // Act / Assert - Testers.testUint((v: number) => { XingHeader.fromInfo(v, 123); }) + Testers.testUint((v: number) => { XingHeader.fromInfo(v, 123); }); Testers.testUint((v: number) => { XingHeader.fromInfo(123, v); }); }