diff --git a/.github/workflows/phpstan-php-code.yml b/.github/workflows/phpstan-php-code.yml
new file mode 100644
index 0000000..a222dfc
--- /dev/null
+++ b/.github/workflows/phpstan-php-code.yml
@@ -0,0 +1,14 @@
+name: PHPStan PHP code issues
+
+on: [push]
+
+jobs:
+ build-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: php-actions/composer@v6
+ - uses: php-actions/phpstan@v3
+ with:
+ path: src/
diff --git a/.gitignore b/.gitignore
index 7c784c0..9ee9c98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,5 @@ psalm.xml
vendor
.php-cs-fixer.cache
.DS_Store
+.phpunit.cache
+version-draft.md
diff --git a/README.md b/README.md
index 2951778..0db74de 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Audio files can use different formats, this package aims to provide a simple way
## Requirements
-- PHP >= 8.1
+- PHP `8.1` minimum
- Optional for update
- `FLAC`: `flac` (with `apt`, `brew` or `scoop`)
- `OGG`: `vorbis-tools` (with `apt` or `brew`) / `extras/icecast` (with `scoop`)
@@ -32,7 +32,7 @@ Audio files can use different formats, this package aims to provide a simple way
## Installation
-You can install the package via composer:
+You can install the package via [composer](https://getcomposer.org/):
```bash
composer require kiwilan/php-audio
@@ -45,7 +45,7 @@ Core metadata:
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getTitle(); // `?string` to get title
$audio->getArtist(); // `?string` to get artist
@@ -58,15 +58,15 @@ $audio->getAlbumArtist(); // `?string` to get album artist
$audio->getComposer(); // `?string` to get composer
$audio->getDiscNumber(); // `?string` to get disc number
$audio->isCompilation(); // `bool` to know if is compilation
-$audio->getCreationDate(); // `?string` to get creation date (audiobook)
-$audio->getCopyright(); // `?string` to get copyright (audiobook)
+$audio->getCreationDate(); // `?string` to get creation date
+$audio->getCopyright(); // `?string` to get copyright
$audio->getEncoding(); // `?string` to get encoding
-$audio->getDescription(); // `?string` to get description (audiobook)
-$audio->getPodcastDescription(); // `?string` to get podcast description (audiobook)
+$audio->getDescription(); // `?string` to get description
+$audio->getSynopsis(); // `?string` to get synopsis
$audio->getLanguage(); // `?string` to get language
-$audio->getLyrics(); // `?string` (audiobook)
-$audio->getStik(); // `?string` (audiobook)
+$audio->getLyrics(); // `?string`
$audio->getDuration(); // `?float` to get duration in seconds
+$audio->getDurationHuman(); // `?string` to get duration in human readable format
```
Raw tags:
@@ -74,15 +74,14 @@ Raw tags:
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
-$audio->getTags(); // `array` with all tags
-$title = $audio->getTag('title'); // `?string` to get title same as `$audio->getTitle()`
+$raw_all = $audio->getRawAll(); // `array` with all tags
+$raw = $audio->getRaw(); // `array` with main tag
+$title = $audio->getRawKey('title'); // `?string` to get title same as `$audio->getTitle()`
-$formats = $audio->getAudioFormats(); // `array` with all audio formats
-
-$format = $audio->getTags('id3v2'); // `?array` with all tags with format `id3v2`
-$title = $audio->getTag('title', 'id3v2'); // `?string` to get title with format `id3v2`
+$format = $audio->getRaw('id3v2'); // `?array` with all tags with format `id3v2`
+$title = $audio->getRawKey('title', 'id3v2'); // `?string` to get title with format `id3v2`
```
Additional metadata:
@@ -90,26 +89,23 @@ Additional metadata:
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getPath(); // `string` to get path
+$audio->getExtension(); // `string` to get extension
$audio->hasCover(); // `bool` to know if has cover
$audio->isValid(); // `bool` to know if file is valid audio file
+$audio->isWritable(); // `bool` to know if file is writable
$audio->getFormat(); // `AudioFormatEnum` to get format (mp3, m4a, ...)
$audio->getType(); // `?AudioTypeEnum` ID3 type (id3, riff, asf, quicktime, matroska, ape, vorbiscomment)
-$audio->getExtras(); // `array` with raw metadata (could contains some metadata not parsed)
```
-Raw audio:
-
-> [!NOTE]
->
-> Cover is removed from `toArray()` method, you can use `getCover()` method to get cover metadata.
+You can use `toArray()` method to get raw info:
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->toArray(); // `array` with all metadata
```
@@ -119,12 +115,10 @@ Advanced properties:
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
-$audio->getReader(); // `?Id3Reader` reader based on `getID3`
-$audio->getWriter(); // `?Id3Writer` writer based on `getid3_writetags`
-$audio->getStat(); // `AudioStat` (from `stat` function)
-$audio->getAudio(); // `?AudioMetadata` with audio metadata
+$audio->getId3Reader(); // `?Id3Reader` reader based on `getID3`
+$audio->getMetadata(); // `?AudioMetadata` with audio metadata
$audio->getCover(); // `?AudioCover` with cover metadata
```
@@ -139,10 +133,10 @@ You can update audio files metadata with `Audio::class`, but not all formats are
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getTitle(); // `Title`
-$tag = $audio->update()
+$tag = $audio->write()
->title('New Title')
->artist('New Artist')
->album('New Album')
@@ -154,16 +148,16 @@ $tag = $audio->update()
->composer('New Composer')
->creationDate('2021-01-01')
->description('New Description')
+ ->synopsis('New Synopsis')
->discNumber('2/2')
->encodingBy('New Encoding By')
->encoding('New Encoding')
->isCompilation()
->lyrics('New Lyrics')
- ->stik('New Stik')
->cover('path/to/cover.jpg') // you can use file content `file_get_contents('path/to/cover.jpg')`
->save();
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getTitle(); // `New Title`
$audio->getCreationDate(); // `null` because `creationDate` is not supported by `MP3`
```
@@ -172,29 +166,31 @@ Some properties are not supported by all formats, for example `MP3` can't handle
#### Set tags manually
-You can set tags manually with `tags` method, but you need to know the format of the tag, you could use `tagFormats` to set formats of tags (if you don't know the format, it will be automatically detected).
+You can set tags manually with `tag()` or `tags()` methods, but you need to know the format of the tag, you could use `tagFormats` to set formats of tags (if you don't know the format, it will be automatically detected).
> [!WARNING]
>
-> If you use `tags` method, you have to use key used by metadata container. For example, if you want to set album artist in `id3v2`, you have to use `band` key. If you want to know which key to use check `src/Models/AudioCore.php` file.
+> If you use `tags` method, you have to use key used by metadata container. For example, if you want to set album artist in `id3v2`, you have to use `band` key. If you want to know which key to use check [`src/Core/AudioCore.php`](https://github.com/kiwilan/php-audio/blob/main/src/Core/AudioCore.php) file.
>
-> If your key is not supported, `save` method will throw an exception, unless you use `preventFailOnErrors`.
+> If your key is not supported, `save` method will throw an exception, unless you use `skipErrors`.
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getAlbumArtist(); // `Band`
-$tag = $audio->update()
- ->tags([
- 'title' => 'New Title',
- 'band' => 'New Band', // `band` is used by `id3v2` to set album artist, method is `albumArtist` but `albumArtist` key will throw an exception with `id3v2`
- ])
- ->tagFormats(['id3v1', 'id3v2.4']) // optional
- ->save();
+$tag = $audio->write()
+ ->tag('composer', 'New Composer')
+ ->tag('genre', 'New Genre') // can be chained
+ ->tags([
+ 'title' => 'New Title',
+ 'band' => 'New Band', // `band` is used by `id3v2` to set album artist, method is `albumArtist` but `albumArtist` key will throw an exception with `id3v2`
+ ])
+ ->tagFormats(['id3v1', 'id3v2.4']) // optional
+ ->save();
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getAlbumArtist(); // `New Band`
```
@@ -203,139 +199,119 @@ $audio->getAlbumArtist(); // `New Band`
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getAlbumArtist(); // `Band`
-$tag = $audio->update()
+$tag = $audio->write()
->title('New Title')
->albumArtist('New Band') // `albumArtist` will set `band` for `id3v2`, exception safe
->save();
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
$audio->getAlbumArtist(); // `New Band`
```
-#### Prevent fail on errors
+#### Skip errors
-You can use `preventFailOnError` to prevent exception if you use unsupported format.
+You can use `skipErrors` to prevent exception if you use unsupported format.
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
-$tag = $audio->update()
+$tag = $audio->write()
->tags([
'title' => 'New Title',
'title2' => 'New title', // not supported by `id3v2`, will throw an exception
])
- ->preventFailOnError() // will prevent exception
+ ->skipErrors() // will prevent exception
->save();
```
-Arrow functions are exception safe for properties but not for unsupported formats.
-
-```php
-use Kiwilan\Audio\Audio;
-
-$audio = Audio::get('path/to/audio.mp3');
+> [!NOTE]
+>
+> Arrow functions are exception safe for properties but not for unsupported formats.
-$tag = $audio->update()
- ->encoding('New encoding') // not supported by `id3v2`, BUT will not throw an exception
- ->preventFailOnError() // if you have some errors with unsupported format for example, you can prevent exception
- ->save();
-```
+### Raw tags
-#### Tags and cover
+Audio files format metadata with different methods, `JamesHeinrich/getID3` offer to check these metadatas by different methods. In `extras` property of `Audio::class`, you will find raw metadata from `JamesHeinrich/getID3` package, like `id3v2`, `id3v1`, `riff`, `asf`, `quicktime`, `matroska`, `ape`, `vorbiscomment`...
-Of course you can add cover with `tags` method.
+If you want to extract specific field which can be skipped by `Audio::class`, you can use `extras` property.
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
-$cover = 'path/to/cover.jpg';
-
-$image = getimagesize($cover);
-$coverData = file_get_contents($cover);
-$coverPicturetypeid = $image[2];
-$coverDescription = 'cover';
-$coverMime = $image['mime'];
-
-$tag = $audio->update()
- ->tags([
- 'title' => 'New Title',
- 'band' => 'New Band',
- 'attached_picture' => [
- [
- 'data' => $coverData,
- 'picturetypeid' => $coverPicturetypeid,
- 'description' => $coverDescription,
- 'mime' => $coverMime,
- ],
- ],
- ])
- ->save();
+$audio = Audio::read('path/to/audio.mp3');
+$raw_all = $audio->getRawAll(); // all formats
+$raw = $audio->getRaw(); // main format
```
-#### Merge tags
-
-Merge `tags` with arrow functions.
+### AudioMetadata
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get($path);
-
-$tag = $audio->update()
- ->title('New Title') // will be merged with `tags` and override `title` key
- ->tags([
- 'title' => 'New Title tag',
- 'band' => 'New Band',
- ]);
-
-$tag->save();
-
-$audio = Audio::get($path);
-expect($audio->getTitle())->toBe('New Title');
-expect($audio->getAlbumArtist())->toBe('New Band');
+$audio = Audio::read('path/to/audio.mp3');
+$metadata = $audio->getMetadata();
+
+$metadata->getFileSize(); // `?int` in bytes
+$metadata->getSizeHuman(); // `?string` (1.2 MB, 1.2 GB, ...)
+$metadata->getExtension(); // `?string` (mp3, m4a, ...)
+$metadata->getEncoding(); // `?string` (UTF-8...)
+$metadata->getMimeType(); // `?string` (audio/mpeg, audio/mp4, ...)
+$metadata->getDurationSeconds(); // `?float` in seconds
+$metadata->getDurationReadable(); // `?string` (00:00:00)
+$metadata->getBitrate(); // `?int` in kbps
+$metadata->getBitrateMode(); // `?string` (cbr, vbr, ...)
+$metadata->getSampleRate(); // `?int` in Hz
+$metadata->getChannels(); // `?int` (1, 2, ...)
+$metadata->getChannelMode(); // `?string` (mono, stereo, ...)
+$metadata->isLossless(); // `bool` to know if is lossless
+$metadata->getCompressionRatio(); // `?float`
+$metadata->getFilesize(); // `?int` in bytes
+$metadata->getSizeHuman(); // `?string` (1.2 MB, 1.2 GB, ...)
+$metadata->getDataFormat(); // `?string` (mp3, m4a, ...)
+$metadata->getWarning(); // `?array`
+$metadata->getQuicktime(); // `?Id3AudioQuicktime
+$metadata->getCodec(); // `?string` (mp3, aac, ...)
+$metadata->getEncoderOptions(); // `?string`
+$metadata->getVersion(); // `?string`
+$metadata->getAvDataOffset(); // `?int` in bytes
+$metadata->getAvDataEnd(); // `?int` in bytes
+$metadata->getFilePath(); // `?string`
+$metadata->getFilename(); // `?string`
+$metadata->getLastAccessAt(); // `?DateTime`
+$metadata->getCreatedAt(); // `?DateTime`
+$metadata->getModifiedAt(); // `?DateTime`
+$metadata->toArray();
```
-### Extras
+### Quicktime
-Audio files format metadata with different methods, `JamesHeinrich/getID3` offer to check these metadatas by different methods. In `extras` property of `Audio::class`, you will find raw metadata from `JamesHeinrich/getID3` package, like `id3v2`, `id3v1`, `riff`, `asf`, `quicktime`, `matroska`, `ape`, `vorbiscomment`...
-
-If you want to extract specific field which can be skipped by `Audio::class`, you can use `extras` property.
-
-```php
-use Kiwilan\Audio\Audio;
-
-$audio = Audio::get('path/to/audio.mp3');
-$extras = $audio->getExtras();
-
-$id3v2 = $extras['id3v2'] ?? [];
-```
-
-### AudioMetadata
+For `quicktime` type, like for M4B audiobook, you can use `Id3TagQuicktime` to get more informations.
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
-
-$audio->getAudio()->getFilesize(); // `?int` in bytes
-$audio->getAudio()->getExtension(); // `?string` (mp3, m4a, ...)
-$audio->getAudio()->getEncoding(); // `?string` (UTF-8...)
-$audio->getAudio()->getMimeType(); // `?string` (audio/mpeg, audio/mp4, ...)
-$audio->getAudio()->getDurationSeconds(); // `?float` in seconds
-$audio->getAudio()->getDurationReadable(); // `?string` (00:00:00)
-$audio->getAudio()->getBitrate(); // `?int` in kbps
-$audio->getAudio()->getBitrateMode(); // `?string` (cbr, vbr, ...)
-$audio->getAudio()->getSampleRate(); // `?int` in Hz
-$audio->getAudio()->getChannels(); // `?int` (1, 2, ...)
-$audio->getAudio()->getChannelMode(); // `?string` (mono, stereo, ...)
-$audio->getAudio()->getLossless(); // `bool` to know if is lossless
-$audio->getAudio()->getCompressionRatio(); // `?float`
+$audio = Audio::read('path/to/audio.m4b');
+$quicktime = $audio->getMetadata()->getQuicktime();
+
+$quicktime->getHinting();
+$quicktime->getController();
+$quicktime->getFtyp();
+$quicktime->getTimestampsUnix();
+$quicktime->getTimeScale();
+$quicktime->getDisplayScale();
+$quicktime->getVideo();
+$quicktime->getAudio();
+$quicktime->getSttsFramecount();
+$quicktime->getComments();
+$quicktime->getFree();
+$quicktime->getWide();
+$quicktime->getMdat();
+$quicktime->getEncoding();
+$quicktime->getChapters(); // ?Id3AudioQuicktimeChapter[]
```
### AudioCover
@@ -343,12 +319,13 @@ $audio->getAudio()->getCompressionRatio(); // `?float`
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
+$cover = $audio->getCover();
-$audio->getCover()->getContents(); // `?string` raw file
-$audio->getCover()->getMimeType(); // `?string` (image/jpeg, image/png, ...)
-$audio->getCover()->getWidth(); // `?int` in pixels
-$audio->getCover()->getHeight(); // `?int` in pixels
+$cover->getContents(); // `?string` raw file
+$cover->getMimeType(); // `?string` (image/jpeg, image/png, ...)
+$cover->getWidth(); // `?int` in pixels
+$cover->getHeight(); // `?int` in pixels
```
## Supported formats
@@ -413,41 +390,43 @@ You want to add a format? [See FAQ](#faq)
`Audio::class` convert some properties to be more readable.
-| ID3 type | Original | New property |
-| :-------------: | :---------------------: | :------------------: |
-| `id3v2` | `band` | `albumArtist` |
-| `id3v2` | `track_number` | `trackNumber` |
-| `id3v2` | `part_of_a_set` | `discNumber` |
-| `id3v2` | `part_of_a_compilation` | `isCompilation` |
-| `quicktime` | `track_number` | `trackNumber` |
-| `quicktime` | `disc_number` | `discNumber` |
-| `quicktime` | `compilation` | `isCompilation` |
-| `quicktime` | `creation_date` | `creationDate` |
-| `quicktime` | `album_artist` | `albumArtist` |
-| `quicktime` | `encoded_by` | `encodingBy` |
-| `quicktime` | `encoding_tool` | `encoding` |
-| `quicktime` | `description_long` | `podcastDescription` |
-| `asf` | `albumartist` | `albumArtist` |
-| `asf` | `partofset` | `discNumber` |
-| `asf` | `track_number` | `trackNumber` |
-| `asf` | `encodingsettings` | `encoding` |
-| `vorbiscomment` | `encoder` | `encoding` |
-| `vorbiscomment` | `albumartist` | `albumArtist` |
-| `vorbiscomment` | `discnumber` | `discNumber` |
-| `vorbiscomment` | `compilation` | `isCompilation` |
-| `vorbiscomment` | `tracknumber` | `trackNumber` |
-| `matroska` | `album_artist` | `albumArtist` |
-| `matroska` | `disc` | `discNumber` |
-| `matroska` | `part_number` | `trackNumber` |
-| `matroska` | `date` | `year` |
-| `matroska` | `compilation` | `isCompilation` |
-| `matroska` | `encoder` | `encoding` |
-| `ape` | `album_artist` | `albumArtist` |
-| `ape` | `disc` | `discNumber` |
-| `ape` | `compilation` | `isCompilation` |
-| `ape` | `track` | `trackNumber` |
-| `ape` | `date` | `year` |
-| `ape` | `encoder` | `encoding` |
+- `ape` format: [`Id3TagApe`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagApe.php)
+- `asf` format: [`Id3TagAsf`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagAsf.php)
+- `id3v1` format: [`Id3TagAudioV1`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagAudioV1.php)
+- `id3v2` format: [`Id3TagAudioV2`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagAudioV2.php)
+- `matroska` format: [`Id3TagMatroska`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagMatroska.php)
+- `quicktime` format: [`Id3TagQuicktime`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagQuicktime.php)
+- `vorbiscomment` format: [`Id3TagVorbisComment`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagVorbisComment.php)
+- `riff` format: [`Id3TagRiff`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagRiff.php)
+- `unknown` format: [`Id3TagVorbisComment`](https://github.com/kiwilan/php-audio/blob/main/src/Id3/Tag/Id3TagVorbisComment.php)
+
+| ID3 type | Original | New property |
+| :-------------: | :---------------------: | :--------------: |
+| `id3v2` | `band` | `album_artist` |
+| `id3v2` | `part_of_a_set` | `disc_number` |
+| `id3v2` | `part_of_a_compilation` | `is_compilation` |
+| `quicktime` | `compilation` | `is_compilation` |
+| `quicktime` | `encoded_by` | `encoding_by` |
+| `quicktime` | `encoding_tool` | `encoding` |
+| `quicktime` | `description_long` | `synopsis` |
+| `asf` | `albumartist` | `album_artist` |
+| `asf` | `partofset` | `disc_number` |
+| `asf` | `encodingsettings` | `encoding` |
+| `vorbiscomment` | `encoder` | `encoding` |
+| `vorbiscomment` | `albumartist` | `album_artist` |
+| `vorbiscomment` | `discnumber` | `disc_number` |
+| `vorbiscomment` | `compilation` | `is_compilation` |
+| `vorbiscomment` | `tracknumber` | `track_number` |
+| `matroska` | `disc` | `disc_number` |
+| `matroska` | `part_number` | `track_number` |
+| `matroska` | `date` | `year` |
+| `matroska` | `compilation` | `is_compilation` |
+| `matroska` | `encoder` | `encoding` |
+| `ape` | `disc` | `disc_number` |
+| `ape` | `compilation` | `is_compilation` |
+| `ape` | `track` | `track_number` |
+| `ape` | `date` | `year` |
+| `ape` | `encoder` | `encoding` |
## Testing
@@ -472,11 +451,11 @@ In `Audio::class`, you have a property `extras` which contains all raw metadata,
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
-$extras = $audio->getExtras();
+$audio = Audio::read('path/to/audio.mp3');
+$raw_all = $audio->getRawAll());
$custom = null;
-$id3v2 = $extras['id3v2'] ?? [];
+$id3v2 = $raw_all['id3v2'] ?? [];
if ($id3v2) {
$custom = $id3v2['custom'] ?? null;
@@ -492,9 +471,9 @@ You can check `extras` property to know if some metadata are available.
```php
use Kiwilan\Audio\Audio;
-$audio = Audio::get('path/to/audio.mp3');
+$audio = Audio::read('path/to/audio.mp3');
-$extras = $audio->getExtras();
+$extras = $audio->getRawAll();
var_dump($extras);
```
@@ -520,10 +499,9 @@ Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed re
## Credits
-- [Ewilan Rivière](https://github.com/ewilan-riviere): package author
-- [JamesHeinrich/getID3](https://github.com/JamesHeinrich/getID3): parser used to read audio files
-- [spatie/package-skeleton-php](https://github.com/spatie/package-skeleton-php): package skeleton used to create this package
-- Tests files from [p1pdd.com](https://p1pdd.com/) (episode 00)
+- [`ewilan-riviere`](https://github.com/ewilan-riviere): package author
+- [`JamesHeinrich/getID3`](https://github.com/JamesHeinrich/getID3): parser used to read audio files
+- [`spatie/package-skeleton-php`](https://github.com/spatie/package-skeleton-php): package skeleton used to create this package
## License
diff --git a/composer.json b/composer.json
index 5033e57..346ddc6 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "kiwilan/php-audio",
"description": "PHP package to parse and update audio files metadata, with `JamesHeinrich/getID3`.",
- "version": "3.0.08",
+ "version": "4.0.0",
"keywords": [
"audio",
"php",
@@ -28,7 +28,8 @@
"wma",
"wv",
"wav",
- "webm"
+ "webm",
+ "music"
],
"homepage": "https://github.com/kiwilan/php-audio",
"license": "MIT",
@@ -41,10 +42,11 @@
],
"require": {
"php": "^8.1",
- "james-heinrich/getid3": "^1.9"
+ "james-heinrich/getid3": "^v1.9.22"
},
"require-dev": {
- "pestphp/pest": "^1.20",
+ "pestphp/pest": "^2.0",
+ "phpstan/phpstan": "^1.12",
"laravel/pint": "^1.2",
"spatie/ray": "^1.28"
},
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..e5b7e20
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,8 @@
+parameters:
+ tmpDir: public/build/.phpstan
+
+ paths:
+ - src
+
+ # The level 9 is the highest level
+ level: 5
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index a7e9c48..966c25d 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,14 +1,10 @@
@@ -24,16 +21,18 @@
-
- ./src
-
-
-
-
+
+
+
-
+
-
+
+
+ ./src
+
+
+
\ No newline at end of file
diff --git a/src/Audio.php b/src/Audio.php
index 4bf8e65..b3525fe 100755
--- a/src/Audio.php
+++ b/src/Audio.php
@@ -2,81 +2,55 @@
namespace Kiwilan\Audio;
+use Kiwilan\Audio\Core\AudioCore;
use Kiwilan\Audio\Enums\AudioFormatEnum;
use Kiwilan\Audio\Enums\AudioTypeEnum;
-use Kiwilan\Audio\Models\AudioCore;
+use Kiwilan\Audio\Id3\Id3Reader;
+use Kiwilan\Audio\Id3\Id3Writer;
use Kiwilan\Audio\Models\AudioCover;
use Kiwilan\Audio\Models\AudioMetadata;
-use Kiwilan\Audio\Models\AudioStat;
-use Kiwilan\Audio\Models\Id3Reader;
-use Kiwilan\Audio\Models\Id3Writer;
class Audio
{
- protected ?string $title = null;
-
- protected ?string $artist = null;
-
- protected ?string $album = null;
-
- protected ?string $genre = null;
-
- protected ?int $year = null;
-
- protected ?string $trackNumber = null;
-
- protected ?string $comment = null;
-
- protected ?string $albumArtist = null;
-
- protected ?string $composer = null;
-
- protected ?string $discNumber = null;
-
- protected bool $isCompilation = false;
-
- protected ?string $creationDate = null;
-
- protected ?string $copyright = null;
-
- protected ?string $encodingBy = null;
-
- protected ?string $encoding = null;
-
- protected ?string $description = null;
-
- protected ?string $podcastDescription = null;
-
- protected ?string $language = null;
-
- protected ?string $lyrics = null;
-
- protected ?string $stik = null;
-
- protected ?float $duration = null;
-
- protected array $extras = [];
-
- protected ?AudioMetadata $audio = null;
-
- protected bool $hasCover = false;
-
- protected ?AudioCover $cover = null;
-
- protected bool $isValid = false;
-
- protected ?AudioTypeEnum $type = null;
-
+ /**
+ * @param array $raw_tags_all
+ */
protected function __construct(
protected string $path,
protected string $extension,
protected AudioFormatEnum $format,
- protected AudioStat $stat,
- protected Id3Reader $reader,
- protected ?Id3Writer $writer = null,
+ protected ?AudioTypeEnum $type = null,
+ protected ?AudioMetadata $metadata = null,
+ protected ?AudioCover $cover = null,
+ protected ?float $duration = null,
+ protected bool $is_writable = false,
+ protected bool $is_valid = false,
+ protected bool $has_cover = false,
+ //
+ protected ?string $title = null,
+ protected ?string $artist = null,
+ protected ?string $album = null,
+ protected ?string $genre = null,
+ protected ?int $year = null,
+ protected ?string $track_number = null,
+ protected ?string $comment = null,
+ protected ?string $album_artist = null,
+ protected ?string $composer = null,
+ protected ?string $disc_number = null,
+ protected bool $is_compilation = false,
+ protected ?string $creation_date = null,
+ protected ?string $copyright = null,
+ protected ?string $encoding_by = null,
+ protected ?string $encoding = null,
+ protected ?string $description = null,
+ protected ?string $synopsis = null,
+ protected ?string $language = null,
+ protected ?string $lyrics = null,
+
+ protected array $raw_tags_all = [],
) {}
- public static function get(string $path): self
+ public static function read(string $path): self
{
$fileExists = file_exists($path);
if (! $fileExists) {
@@ -91,303 +65,432 @@ public static function get(string $path): self
path: $path,
extension: $extension,
format: $format ? $format : AudioFormatEnum::unknown,
- stat: AudioStat::make($path),
- reader: Id3Reader::make($path),
);
- if ($self->reader->isWritable()) {
- $self->writer = Id3Writer::make($self);
+
+ try {
+ $id3_reader = Id3Reader::make($path);
+
+ $self->metadata = AudioMetadata::make($self, $id3_reader);
+ $self->duration = (float) number_format((float) $self->metadata->getDurationSeconds(), 2, '.', '');
+ $self->is_writable = $id3_reader->isWritable();
+
+ $self->parseTags($id3_reader);
+ } catch (\Throwable $th) {
+ error_log($th->getMessage());
}
- $self->audio = AudioMetadata::make($self);
- $self->parse();
return $self;
}
/**
- * Update metadata of audio file.
+ * @deprecated Use `read()` method instead.
+ *
+ * Get audio file from path.
*/
- public function update(): Id3Writer
+ public static function get(string $path): self
{
- return $this->writer;
+ return self::read($path);
}
/**
- * Get the value of `stat` method.
+ * Get audio file path, like `/path/to/audio.mp3`.
*/
- public function getStat(): ?AudioStat
+ public function getPath(): string
{
- return $this->stat;
+ return $this->path;
}
/**
- * `Id3Reader` with metadata.
+ * Get audio file extension, like `mp3`.
*/
- public function getReader(): Id3Reader
+ public function getExtension(): string
{
- return $this->reader;
+ return $this->extension;
}
/**
- * `Id3Writer` to update metadata.
+ * Get audio format if recognized, like `AudioFormatEnum::mp3`.
*/
- public function getWriter(): ?Id3Writer
+ public function getFormat(): AudioFormatEnum
{
- return $this->writer;
+ return $this->format;
}
/**
- * Get `title` metadata.
+ * Get audio type if recognized, like `AudioTypeEnum::id3`.
*/
- public function getTitle(): ?string
+ public function getType(): ?AudioTypeEnum
{
- return $this->title;
+ return $this->type;
}
/**
- * Get `artist` metadata.
+ * Get audio metadata.
*/
- public function getArtist(): ?string
+ public function getMetadata(): ?AudioMetadata
{
- return $this->artist;
+ return $this->metadata;
}
/**
- * Get `album` metadata.
+ * Get audio cover.
*/
- public function getAlbum(): ?string
+ public function getCover(): ?AudioCover
{
- return $this->album;
+ return $this->cover;
+ }
+
+ public function getId3Reader(): ?Id3Reader
+ {
+ return Id3Reader::make($this->path);
+ }
+
+ public function write(): Id3Writer
+ {
+ return Id3Writer::make($this);
}
/**
- * Get `genre` metadata.
+ * @deprecated Use `write()` method instead.
+ *
+ * Update audio file.
*/
- public function getGenre(): ?string
+ public function update(): Id3Writer
{
- return $this->genre;
+ return $this->write();
}
/**
- * Get `year` metadata.
+ * Get duration of the audio file in seconds, limited to 2 decimals, like `180.66`
+ *
+ * To get exact duration, use `getMetadata()->getDurationSeconds()` instead.
*/
- public function getYear(): ?int
+ public function getDuration(): ?float
{
- return $this->year;
+ return $this->duration;
}
/**
- * Get `trackNumber` metadata.
+ * Get duration of the audio file in human readable format, like `00:03:00`
*/
- public function getTrackNumber(): ?string
+ public function getDurationHuman(): ?string
{
- return $this->trackNumber;
+ return gmdate('H:i:s', intval($this->duration));
}
/**
- * Get `comment` metadata.
+ * To know if the audio file is writable.
*/
- public function getComment(): ?string
+ public function isWritable(): bool
{
- return $this->comment;
+ return $this->is_writable;
}
/**
- * Get `albumArtist` metadata.
+ * To know if the audio file is valid.
*/
- public function getAlbumArtist(): ?string
+ public function isValid(): bool
{
- return $this->albumArtist;
+ return $this->is_valid;
}
/**
- * Get `composer` metadata.
+ * To know if the audio file has cover.
*/
- public function getComposer(): ?string
+ public function hasCover(): bool
{
- return $this->composer;
+ return $this->has_cover;
}
/**
- * Get `discNumber` metadata.
+ * Get `title` tag, like `Another Brick In The Wall`.
*/
- public function getDiscNumber(): ?string
+ public function getTitle(): ?string
{
- return $this->discNumber;
+ return $this->title;
}
/**
- * Know if audio file is a compilation.
+ * Get `artist` tag, like `Pink Floyd`.
*/
- public function isCompilation(): bool
+ public function getArtist(): ?string
{
- return $this->isCompilation;
+ return $this->artist;
}
/**
- * Get `creationDate` metadata for audiobook.
+ * Get `album` tag, like `The Wall`.
*/
- public function getCreationDate(): ?string
+ public function getAlbum(): ?string
{
- return $this->creationDate;
+ return $this->album;
}
/**
- * Get `encodingBy` metadata for audiobook.
+ * Get `genre` tag, like `Rock`.
*/
- public function getEncodingBy(): ?string
+ public function getGenre(): ?string
{
- return $this->encodingBy;
+ return $this->genre;
}
/**
- * Get `encoding` metadata for audiobook.
+ * Get `year` tag, like `1979`.
+ *
+ * - For `matroska` format: `date` tag.
+ * - For `ape` format: `date` tag.
*/
- public function getEncoding(): ?string
+ public function getYear(): ?int
{
- return $this->encoding;
+ return $this->year;
}
/**
- * Get `encoding` metadata for audiobook.
+ * Get `track_number` tag, like `1`.
+ *
+ * - For `vorbiscomment` format: `track_number` tag.
+ * - For `matroska` format: `part_number` tag.
+ * - For `ape` format: `track` tag.
*/
- public function getCopyright(): ?string
+ public function getTrackNumber(): ?string
{
- return $this->copyright;
+ return $this->track_number;
}
/**
- * Get `description` metadata for audiobook.
+ * Get `track_number` tag as integer, like `1`.
*/
- public function getDescription(): ?string
+ public function getTrackNumberInt(): ?int
{
- return $this->description;
+ return $this->track_number ? intval($this->track_number) : null;
+ }
+
+ /**
+ * Get `comment` tag, like `Recorded at Abbey Road Studios`.
+ */
+ public function getComment(): ?string
+ {
+ return $this->comment;
}
/**
- * Get `podcastDescription` metadata for audiobook.
+ * Get `album_artist` tag, like `Pink Floyd`.
+ *
+ * - For `id3v2` format: `band` tag.
+ * - For `asf` format: `albumartist` tag.
+ * - For `vorbiscomment` format: `albumartist` tag.
*/
- public function getPodcastDescription(): ?string
+ public function getAlbumArtist(): ?string
{
- return $this->podcastDescription;
+ return $this->album_artist;
}
/**
- * Get `language` metadata for audiobook.
+ * Get `composer` tag, like `Roger Waters`.
*/
- public function getLanguage(): ?string
+ public function getComposer(): ?string
{
- return $this->language;
+ return $this->composer;
}
/**
- * Get `lyrics` metadata for audiobook.
+ * Get `disc_number` tag, like `1`.
+ *
+ * - For `id3v2` format: `part_of_a_set` tag.
+ * - For `asf` format: `partofset` tag.
+ * - For `vorbiscomment` format: `discnumber` tag.
+ * - For `matroska` format: `disc` tag.
+ * - For `ape` format: `disc` tag.
*/
- public function getLyrics(): ?string
+ public function getDiscNumber(): ?string
{
- return $this->lyrics;
+ return $this->disc_number;
}
/**
- * Get `stik` metadata for audiobook.
+ * Get `disc_number` tag as integer, like `1`.
*/
- public function getStik(): ?string
+ public function getDiscNumberInt(): ?int
{
- return $this->stik;
+ if (str_contains($this->disc_number, '/')) {
+ $disc_number = explode('/', $this->disc_number);
+
+ return intval($disc_number[0]);
+ }
+
+ return $this->disc_number ? intval($this->disc_number) : null;
}
/**
- * Get `duration` in seconds.
+ * To know if the audio file is a compilation.
+ *
+ * - For `id3v2` format: `part_of_a_compilation` tag.
+ * - For `quicktime` format: `compilation` tag.
+ * - For `vorbiscomment` format: `compilation` tag.
+ * - For `matroska` format: `compilation` tag.
+ * - For `ape` format: `compilation` tag.
*/
- public function getDuration(): ?float
+ public function isCompilation(): bool
{
- return $this->duration;
+ return $this->is_compilation;
}
/**
- * Get `duration` in human readable format: `00:00:00`.
+ * Get `creation_date` tag, like `1979-11-30`.
+ *
+ * - For `matroska` format: `date` tag.
+ * - For `ape` format: `date` tag.
*/
- public function getDurationHumanReadable(): ?string
+ public function getCreationDate(): ?string
{
- return gmdate('H:i:s', intval($this->duration));
+ return $this->creation_date;
}
/**
- * Know if audio file is valid.
+ * Get `encoding_by` tag, like `EAC`.
*/
- public function isValid(): bool
+ public function getEncodingBy(): ?string
{
- return $this->isValid;
+ return $this->encoding_by;
}
/**
- * Get `extras` with raw metadata.
+ * Get `encoding` tag, like `LAME`.
*/
- public function getExtras(): array
+ public function getEncoding(): ?string
{
- return $this->extras;
+ return $this->encoding;
}
/**
- * Get `audio` metadata with some audio information.
+ * Get `copyright` tag, like `© 1979 Pink Floyd`.
*/
- public function getAudio(): ?AudioMetadata
+ public function getCopyright(): ?string
{
- return $this->audio;
+ return $this->copyright;
}
/**
- * Know if audio file has cover.
+ * Get `description` tag, like `The Wall is the eleventh studio album by the English rock band Pink Floyd`.
*/
- public function hasCover(): bool
+ public function getDescription(): ?string
{
- return $this->hasCover;
+ return $this->description;
}
/**
- * Get `cover` metadata with some cover information.
+ * Get `synopsis` tag, like `The Wall is the eleventh studio album by the English rock band Pink Floyd`.
+ *
+ * `description` and `synopsis` are not the same tag, but for many formats, they are the same.
*/
- public function getCover(): ?AudioCover
+ public function getSynopsis(): ?string
{
- return $this->cover;
+ return $this->synopsis;
}
/**
- * Get `path` of audio file.
+ * Get `language` tag, like `en`.
*/
- public function getPath(): string
+ public function getLanguage(): ?string
{
- return $this->path;
+ return $this->language;
}
/**
- * Get `extension` of audio file.
+ * Get `lyrics` tag, like `We don't need no education`.
*/
- public function getgetExtension(): string
+ public function getLyrics(): ?string
{
- return $this->extension;
+ return $this->lyrics;
}
/**
- * Get `format` of audio file.
+ * Get raw tags as array with all formats.
+ *
+ * For example, for `mp3` format: `['id3v1' => [...], 'id3v2' => [...]]`.
*/
- public function getFormat(): AudioFormatEnum
+ public function getRawAll(): array
{
- return $this->format;
+ return $this->raw_tags_all;
}
/**
- * Get `type` of audio file.
+ * Get raw tags as array with main format.
+ *
+ * For example, for `mp3` format, `id3v2` entry will be returned.
+ *
+ * @param string|null $format If not provided, main format will be returned.
+ * @return string[]
*/
- public function getType(): ?AudioTypeEnum
+ public function getRaw(?string $format = null): ?array
{
- return $this->type;
+ if ($format) {
+ return $this->raw_tags_all[$format] ?? null;
+ }
+
+ $tags = match ($this->type) {
+ AudioTypeEnum::id3 => $this->raw_tags_all['id3v2'] ?? [],
+ AudioTypeEnum::vorbiscomment => $this->raw_tags_all['vorbiscomment'] ?? [],
+ AudioTypeEnum::quicktime => $this->raw_tags_all['quicktime'] ?? [],
+ AudioTypeEnum::matroska => $this->raw_tags_all['matroska'] ?? [],
+ AudioTypeEnum::ape => $this->raw_tags_all['ape'] ?? [],
+ AudioTypeEnum::asf => $this->raw_tags_all['asf'] ?? [],
+ default => [],
+ };
+
+ return $tags;
}
- private function parse(): self
+ /**
+ * Get raw tags key from main format.
+ *
+ * @param string $key Key name.
+ * @param string|null $format If not provided, main format will be used.
+ */
+ public function getRawKey(string $key, ?string $format = null): string|int|bool|null
{
- $raw = $this->getReader()->getRaw();
- $reader = $this->getReader();
+ $tags = $this->getRaw($format);
+ return $tags[$key] ?? null;
+ }
+
+ public function toArray(): array
+ {
+ return [
+ 'path' => $this->path,
+ 'extension' => $this->extension,
+ 'format' => $this->format,
+ 'type' => $this->type,
+ 'metadata' => $this->metadata?->toArray(),
+ 'cover' => $this->cover?->toArray(),
+ 'duration' => $this->duration,
+ 'is_writable' => $this->is_writable,
+ 'is_valid' => $this->is_valid,
+ 'has_cover' => $this->has_cover,
+ 'title' => $this->title,
+ 'artist' => $this->artist,
+ 'album' => $this->album,
+ 'genre' => $this->genre,
+ 'year' => $this->year,
+ 'track_number' => $this->track_number,
+ 'comment' => $this->comment,
+ 'album_artist' => $this->album_artist,
+ 'composer' => $this->composer,
+ 'disc_number' => $this->disc_number,
+ 'is_compilation' => $this->is_compilation,
+ 'creation_date' => $this->creation_date,
+ 'encoding_by' => $this->encoding_by,
+ 'encoding' => $this->encoding,
+ 'description' => $this->description,
+ 'synopsis' => $this->synopsis,
+ 'language' => $this->language,
+ 'lyrics' => $this->lyrics,
+ 'raw_tags_all' => $this->raw_tags_all,
+ ];
+ }
+
+ private function parseTags(?\Kiwilan\Audio\Id3\Id3Reader $id3_reader): self
+ {
$this->type = match ($this->format) {
AudioFormatEnum::aac => null,
AudioFormatEnum::aif => AudioTypeEnum::id3,
@@ -412,128 +515,67 @@ private function parse(): self
default => null,
};
- $tags = $reader->getTags();
- if (! $tags) {
+ $tags = $id3_reader->getTags();
+ if (! $tags || $tags->is_empty) {
return $this;
}
- $core = null;
- if ($this->type === AudioTypeEnum::id3) {
- $core = AudioCore::fromId3($tags->id3v1(), $tags->id3v2());
- $this->isValid = true;
- }
-
- if ($this->type === AudioTypeEnum::quicktime) {
- $core = AudioCore::fromQuicktime($tags->quicktime());
- $this->isValid = true;
+ $raw_tags = $id3_reader->getRaw()['tags'] ?? [];
+ foreach ($raw_tags as $name => $raw_tag) {
+ $this->raw_tags_all[$name] = Id3Reader::cleanTags($raw_tag);
}
- if ($this->type === AudioTypeEnum::vorbiscomment) {
- $core = AudioCore::fromVorbisComment($tags->vorbiscomment());
- $this->isValid = true;
- }
-
- if ($this->type === AudioTypeEnum::asf) {
- $core = AudioCore::fromAsf($tags->asf());
- $this->isValid = true;
- }
-
- if ($this->type === AudioTypeEnum::matroska) {
- $core = AudioCore::fromMatroska($tags->matroska());
- $this->isValid = true;
- }
+ $core = match ($this->type) {
+ AudioTypeEnum::id3 => AudioCore::fromId3($tags->id3v1, $tags->id3v2),
+ AudioTypeEnum::vorbiscomment => AudioCore::fromVorbisComment($tags->vorbiscomment),
+ AudioTypeEnum::quicktime => AudioCore::fromQuicktime($tags->quicktime),
+ AudioTypeEnum::matroska => AudioCore::fromMatroska($tags->matroska),
+ AudioTypeEnum::ape => AudioCore::fromApe($tags->ape),
+ AudioTypeEnum::asf => AudioCore::fromAsf($tags->asf),
+ default => null,
+ };
- if ($this->type === AudioTypeEnum::ape) {
- $core = AudioCore::fromApe($tags->ape());
- $this->isValid = true;
+ if (! $core) {
+ return $this;
}
- $this->coreToProperties($core);
- $this->extras = $raw['tags'] ?? [];
-
- $this->audio = AudioMetadata::make($this);
- $this->cover = AudioCover::make($reader->getComments());
+ $this->convertCore($core);
+ $this->is_valid = true;
+ $this->cover = AudioCover::make($id3_reader->getComments());
if ($this->cover?->getContents()) {
- $this->hasCover = true;
+ $this->has_cover = true;
}
- $this->duration = number_format((float) $this->audio->getDurationSeconds(), 2, '.', '');
-
return $this;
}
- private function coreToProperties(?AudioCore $core): self
+ private function convertCore(?AudioCore $core): self
{
if (! $core) {
return $this;
}
- $this->title = $core->getTitle();
- $this->artist = $core->getArtist();
- $this->album = $core->getAlbum();
- $this->genre = $core->getGenre();
- $this->year = $core->getYear();
- $this->trackNumber = $core->getTrackNumber();
- $this->comment = $core->getComment();
- $this->albumArtist = $core->getAlbumArtist();
- $this->composer = $core->getComposer();
- $this->discNumber = $core->getDiscNumber();
- $this->isCompilation = $core->isCompilation();
- $this->creationDate = $core->getCreationDate();
- $this->encodingBy = $core->getEncodingBy();
- $this->encoding = $core->getEncoding();
- $this->copyright = $core->getCopyright();
- $this->description = $core->getDescription();
- $this->podcastDescription = $core->getPodcastDescription();
- $this->language = $core->getLanguage();
- $this->lyrics = $core->getLyrics();
- $this->stik = $core->getStik();
+ $this->title = $core->title;
+ $this->artist = $core->artist;
+ $this->album = $core->album;
+ $this->genre = $core->genre;
+ $this->year = $core->year;
+ $this->track_number = $core->track_number;
+ $this->comment = $core->comment;
+ $this->album_artist = $core->album_artist;
+ $this->composer = $core->composer;
+ $this->disc_number = $core->disc_number;
+ $this->is_compilation = $core->is_compilation;
+ $this->creation_date = $core->creation_date;
+ $this->encoding_by = $core->encoding_by;
+ $this->encoding = $core->encoding;
+ $this->copyright = $core->copyright;
+ $this->description = $core->description;
+ $this->synopsis = $core->synopsis;
+ $this->language = $core->language;
+ $this->lyrics = $core->lyrics;
return $this;
}
-
- /**
- * Get a specific tag.
- *
- * @param string $tag Tag name.
- * @param string|null $audioFormat Get a specific format, default format is format with maximum tags.
- */
- public function getTag(string $tag, ?string $audioFormat = null): ?string
- {
- $tags = $this->reader->toTags($audioFormat);
-
- return $tags[$tag] ?? null;
- }
-
- /**
- * Get all tags as array.
- *
- * @param string|null $tag Get a specific format, default format is format with maximum tags.
- * @return array
- */
- public function getTags(?string $audioFormat = null): array
- {
- return $this->reader->toTags($audioFormat);
- }
-
- /**
- * Get all audio formats as array, with tags.
- *
- * @return array>
- */
- public function getAudioFormats(): array
- {
- return $this->reader->toAudioFormats();
- }
-
- /**
- * Get all raw metadata as array.
- *
- * @return array
- */
- public function toArray(): array
- {
- return $this->reader->toArray();
- }
}
diff --git a/src/Core/AudioCore.php b/src/Core/AudioCore.php
new file mode 100644
index 0000000..51858e1
--- /dev/null
+++ b/src/Core/AudioCore.php
@@ -0,0 +1,366 @@
+ $value !== null);
+ $properties = array_filter($properties, fn ($value) => $value !== '');
+
+ return $properties;
+ }
+
+ private function parseCompilation(AudioCore $core): ?string
+ {
+ if ($core->is_compilation === null) {
+ return null;
+ }
+
+ return $core->is_compilation ? '1' : '0';
+ }
+
+ public static function toId3v2(AudioCore $core): Tag\Id3TagAudioV2
+ {
+ return new Tag\Id3TagAudioV2(
+ album: $core->album,
+ artist: $core->artist,
+ band: $core->album_artist,
+ comment: $core->comment,
+ composer: $core->composer,
+ part_of_a_set: $core->disc_number,
+ genre: $core->genre,
+ part_of_a_compilation: $core->parseCompilation($core),
+ title: $core->title,
+ track_number: $core->track_number,
+ year: (string) $core->year,
+ copyright: $core->copyright,
+ unsynchronised_lyric: $core->lyrics,
+ language: $core->language,
+ );
+ }
+
+ public static function toId3v1(AudioCore $core): Tag\Id3TagAudioV1
+ {
+ return new Tag\Id3TagAudioV1(
+ album: $core->album,
+ artist: $core->artist,
+ comment: $core->comment,
+ genre: $core->genre,
+ title: $core->title,
+ track_number: $core->track_number,
+ year: (string) $core->year,
+ );
+ }
+
+ public static function toVorbisComment(AudioCore $core): Tag\Id3TagVorbisComment
+ {
+ return new Tag\Id3TagVorbisComment(
+ album: $core->album,
+ artist: $core->artist,
+ albumartist: $core->album_artist,
+ comment: $core->comment,
+ composer: $core->composer,
+ compilation: $core->parseCompilation($core),
+ discnumber: $core->disc_number,
+ genre: $core->genre,
+ title: $core->title,
+ tracknumber: $core->track_number,
+ date: (string) $core->year,
+ encoder: $core->encoding,
+ description: $core->description,
+ );
+ }
+
+ public static function toQuicktime(AudioCore $core): Tag\Id3TagQuicktime
+ {
+ return new Tag\Id3TagQuicktime(
+ title: $core->title,
+ track_number: $core->track_number,
+ disc_number: $core->disc_number,
+ compilation: $core->parseCompilation($core),
+ album: $core->album,
+ genre: $core->genre,
+ composer: $core->composer,
+ creation_date: $core->creation_date,
+ copyright: $core->copyright,
+ artist: $core->artist,
+ album_artist: $core->album_artist,
+ encoded_by: $core->encoding,
+ encoding_tool: $core->encoding,
+ description: $core->description,
+ description_long: $core->synopsis,
+ lyrics: $core->lyrics,
+ comment: $core->comment,
+ );
+ }
+
+ public static function toMatroska(AudioCore $core): Tag\Id3TagMatroska
+ {
+ return new Tag\Id3TagMatroska(
+ title: $core->title,
+ album: $core->album,
+ artist: $core->artist,
+ album_artist: $core->album_artist,
+ comment: $core->comment,
+ composer: $core->composer,
+ disc: $core->disc_number,
+ compilation: $core->parseCompilation($core),
+ genre: $core->genre,
+ part_number: $core->track_number,
+ date: (string) $core->year,
+ encoder: $core->encoding,
+ );
+ }
+
+ public static function toApe(AudioCore $core): Tag\Id3TagApe
+ {
+ return new Tag\Id3TagApe(
+ album: $core->album,
+ artist: $core->artist,
+ album_artist: $core->album_artist,
+ comment: $core->comment,
+ composer: $core->composer,
+ disc: $core->disc_number,
+ compilation: $core->parseCompilation($core),
+ genre: $core->genre,
+ title: $core->title,
+ track: $core->track_number,
+ date: (string) $core->year,
+ encoder: $core->encoding,
+ );
+ }
+
+ public static function toAsf(AudioCore $core): Tag\Id3TagAsf
+ {
+ return new Tag\Id3TagAsf(
+ album: $core->album,
+ artist: $core->artist,
+ albumartist: $core->album_artist,
+ composer: $core->composer,
+ partofset: $core->disc_number,
+ genre: $core->genre,
+ track_number: $core->track_number,
+ year: (string) $core->year,
+ encodingsettings: $core->encoding,
+ );
+ }
+
+ public static function fromId3(?Tag\Id3TagAudioV1 $v1, ?Tag\Id3TagAudioV2 $v2): AudioCore
+ {
+ if (! $v1) {
+ $v1 = new Tag\Id3TagAudioV1;
+ }
+
+ if (! $v2) {
+ $v2 = new Tag\Id3TagAudioV2;
+ }
+
+ return new AudioCore(
+ album: $v2->album ?? $v1->album,
+ artist: $v2->artist ?? $v1->artist,
+ album_artist: $v2->band ?? null,
+ comment: $v2->comment ?? $v1->comment,
+ composer: $v2->composer ?? null,
+ disc_number: $v2->part_of_a_set ?? null,
+ genre: $v2->genre ?? $v1->genre,
+ is_compilation: $v2->part_of_a_compilation === '1',
+ title: $v2->title ?? $v1->title,
+ track_number: $v2->track_number ?? $v1->track_number,
+ year: (int) ($v2->year ?? $v1->year),
+ copyright: $v2->copyright ?? null,
+ description: $v2->text ?? null,
+ lyrics: $v2->unsynchronised_lyric ?? null,
+ language: $v2->language ?? null,
+ );
+ }
+
+ public static function fromId3v2(Tag\Id3TagAudioV2 $tag): AudioCore
+ {
+ return new AudioCore(
+ album: $tag->album,
+ artist: $tag->artist,
+ album_artist: $tag->band,
+ comment: $tag->comment,
+ composer: $tag->composer,
+ disc_number: $tag->part_of_a_set,
+ genre: $tag->genre,
+ is_compilation: $tag->part_of_a_compilation === '1',
+ title: $tag->title,
+ track_number: $tag->track_number,
+ year: (int) $tag->year,
+ );
+ }
+
+ public static function fromId3v1(Tag\Id3TagAudioV1 $tag): AudioCore
+ {
+ return new AudioCore(
+ album: $tag->album,
+ artist: $tag->artist,
+ comment: $tag->comment,
+ genre: $tag->genre,
+ title: $tag->title,
+ track_number: $tag->track_number,
+ year: (int) $tag->year,
+ );
+ }
+
+ public static function fromQuicktime(Tag\Id3TagQuicktime $tag): AudioCore
+ {
+ $date = $tag->creation_date;
+ $description = $tag->description;
+ $description_long = $tag->description_long;
+
+ $creation_date = null;
+ $year = null;
+
+ if ($date) {
+ if (strlen($date) === 4) {
+ $year = (int) $date;
+ } else {
+ try {
+ $parsedCreationDate = new \DateTimeImmutable($date);
+ } catch (\Exception $e) {
+ // ignore the issue so the rest of the data will be available
+ }
+
+ if (! empty($parsedCreationDate)) {
+ $creation_date = $parsedCreationDate->format('Y-m-d\TH:i:s\Z');
+ $year = (int) $parsedCreationDate->format('Y');
+ }
+ }
+ }
+
+ $core = new AudioCore(
+ title: $tag->title,
+ artist: $tag->artist,
+ album: $tag->album,
+ genre: $tag->genre,
+ track_number: $tag->track_number,
+ disc_number: $tag->disc_number,
+ composer: $tag->composer,
+ is_compilation: $tag->compilation === '1',
+ comment: $tag->comment,
+ album_artist: $tag->album_artist,
+ encoding_by: $tag->encoded_by,
+ encoding: $tag->encoding_tool,
+ language: $tag->language,
+ copyright: $tag->copyright,
+ description: $description,
+ synopsis: $description_long,
+ lyrics: $tag->lyrics,
+ creation_date: $creation_date,
+ year: $year,
+ );
+
+ return $core;
+ }
+
+ public static function fromVorbisComment(Tag\Id3TagVorbisComment $tag): AudioCore
+ {
+ return new AudioCore(
+ title: $tag->title,
+ artist: $tag->artist,
+ album: $tag->album,
+ genre: $tag->genre,
+ track_number: $tag->tracknumber,
+ comment: $tag->comment,
+ album_artist: $tag->albumartist,
+ composer: $tag->composer,
+ disc_number: $tag->discnumber,
+ is_compilation: $tag->compilation === '1',
+ year: (int) $tag->date,
+ encoding: $tag->encoder,
+ description: $tag->description,
+ );
+ }
+
+ public static function fromAsf(Tag\Id3TagAsf $tag): AudioCore
+ {
+ return new AudioCore(
+ title: $tag->title,
+ artist: $tag->artist,
+ album: $tag->album,
+ album_artist: $tag->albumartist,
+ composer: $tag->composer,
+ disc_number: $tag->partofset,
+ genre: $tag->genre,
+ track_number: $tag->track_number,
+ year: (int) $tag->year,
+ encoding: $tag->encodingsettings,
+ );
+ }
+
+ public static function fromMatroska(Tag\Id3TagMatroska $tag): AudioCore
+ {
+ return new AudioCore(
+ title: $tag->title,
+ album: $tag->album,
+ artist: $tag->artist,
+ album_artist: $tag->album_artist,
+ comment: $tag->comment,
+ composer: $tag->composer,
+ disc_number: $tag->disc,
+ genre: $tag->genre,
+ is_compilation: $tag->compilation === 'true',
+ track_number: $tag->part_number,
+ year: (int) $tag->date,
+ encoding: $tag->encoder,
+ );
+ }
+
+ public static function fromApe(Tag\Id3TagApe $tag): AudioCore
+ {
+ return new AudioCore(
+ album: $tag->album,
+ artist: $tag->artist,
+ album_artist: $tag->album_artist,
+ comment: $tag->comment,
+ composer: $tag->composer,
+ disc_number: $tag->disc,
+ genre: $tag->genre,
+ is_compilation: $tag->compilation === '1',
+ title: $tag->title,
+ track_number: $tag->track,
+ creation_date: $tag->date,
+ year: $tag->year ?? (int) $tag->date,
+ encoding: $tag->encoder,
+ description: $tag->description,
+ copyright: $tag->copyright,
+ lyrics: $tag->lyrics,
+ synopsis: $tag->podcastdesc,
+ language: $tag->language,
+ );
+ }
+}
diff --git a/src/Core/AudioCoreCover.php b/src/Core/AudioCoreCover.php
new file mode 100644
index 0000000..68799cc
--- /dev/null
+++ b/src/Core/AudioCoreCover.php
@@ -0,0 +1,30 @@
+data = file_exists($pathOrData)
+ ? base64_encode(file_get_contents($pathOrData))
+ : base64_encode($pathOrData);
+ $self->picture_type_id = $image[2];
+ $self->description = 'cover';
+ $self->mime = $image['mime'];
+
+ return $self;
+ }
+}
diff --git a/src/Id3/Id3Reader.php b/src/Id3/Id3Reader.php
new file mode 100644
index 0000000..09e104e
--- /dev/null
+++ b/src/Id3/Id3Reader.php
@@ -0,0 +1,261 @@
+raw = $self->instance->analyze($path);
+ $self->is_writable = $self->instance->is_writable($path);
+ $metadata = $self->raw;
+
+ $audio = Id3Audio::make($metadata['audio'] ?? null);
+ $video = Id3Video::make($metadata['video'] ?? null);
+ $tags = Id3AudioTag::make($metadata['tags'] ?? null);
+ $comments = Id3Comments::make($metadata['comments'] ?? null);
+ $quicktime = Id3AudioQuicktime::make($self->raw['quicktime'] ?? null);
+ $warning = $metadata['warning'] ?? null;
+
+ $bitrate = $metadata['bitrate'] ?? null;
+ if ($bitrate) {
+ $bitrate = intval($bitrate);
+ }
+
+ $self->version = $metadata['GETID3_VERSION'] ?? null;
+ $self->file_size = $metadata['filesize'] ?? null;
+ $self->file_path = $metadata['filepath'] ?? null;
+ $self->filename = $metadata['filename'] ?? null;
+ $self->filename_path = $metadata['filenamepath'] ?? null;
+ $self->av_data_offset = $metadata['avdataoffset'] ?? null;
+ $self->av_data_end = $metadata['avdataend'] ?? null;
+ $self->file_format = $metadata['fileformat'] ?? null;
+ $self->audio = $audio;
+ $self->video = $video;
+ $self->tags = $tags;
+ $self->quicktime = $quicktime;
+ $self->comments = $comments;
+ $self->warning = $warning;
+ $self->encoding = $metadata['encoding'] ?? null;
+ $self->mime_type = $metadata['mime_type'] ?? null;
+ $self->mpeg = $metadata['mpeg'] ?? null;
+ $self->playtime_seconds = $metadata['playtime_seconds'] ?? null;
+ $self->bitrate = $bitrate;
+ $self->playtime_string = $metadata['playtime_string'] ?? null;
+
+ return $self;
+ }
+
+ public function getInstance(): getID3
+ {
+ return $this->instance;
+ }
+
+ public function getVersion(): ?string
+ {
+ return $this->version;
+ }
+
+ public function getFileSize(): ?int
+ {
+ return $this->file_size;
+ }
+
+ public function getFilePath(): ?string
+ {
+ return $this->file_path;
+ }
+
+ public function getFilename(): ?string
+ {
+ return $this->filename;
+ }
+
+ public function getFilenamePath(): ?string
+ {
+ return $this->filename_path;
+ }
+
+ public function getAvDataOffset(): ?int
+ {
+ return $this->av_data_offset;
+ }
+
+ public function getAvDataEnd(): ?int
+ {
+ return $this->av_data_end;
+ }
+
+ public function getFileFormat(): ?string
+ {
+ return $this->file_format;
+ }
+
+ public function getAudio(): ?Id3Audio
+ {
+ return $this->audio;
+ }
+
+ public function getTags(): ?Id3AudioTag
+ {
+ return $this->tags;
+ }
+
+ public function getComments(): ?Id3Comments
+ {
+ return $this->comments;
+ }
+
+ public function getVideo(): ?Id3Video
+ {
+ return $this->video;
+ }
+
+ public function getQuicktime(): ?Id3AudioQuicktime
+ {
+ return $this->quicktime;
+ }
+
+ public function getWarning(): ?array
+ {
+ return $this->warning;
+ }
+
+ public function getEncoding(): ?string
+ {
+ return $this->encoding;
+ }
+
+ public function getMimeType(): ?string
+ {
+ return $this->mime_type;
+ }
+
+ public function getMpeg(): mixed
+ {
+ return $this->mpeg;
+ }
+
+ public function getPlaytimeSeconds(): ?float
+ {
+ return $this->playtime_seconds;
+ }
+
+ public function getBitrate(): ?float
+ {
+ return $this->bitrate;
+ }
+
+ public function getPlaytimeString(): ?string
+ {
+ return $this->playtime_string;
+ }
+
+ public function isWritable(): bool
+ {
+ return $this->is_writable;
+ }
+
+ public function getRaw(): array
+ {
+ return $this->raw;
+ }
+
+ public function toTags(?string $audioFormat = null): array
+ {
+ $rawTags = $this->raw['tags_html'] ?? [];
+
+ if (count($rawTags) === 0) {
+ return [];
+ }
+
+ $tagsItems = [];
+ if ($audioFormat) {
+ $tagsItems = $rawTags[$audioFormat] ?? [];
+ } else {
+ if (count($rawTags) > 1) {
+ $entries = [];
+ foreach ($rawTags as $key => $keyTags) {
+ $entries[$key] = count($keyTags);
+ }
+ $maxKey = array_search(max($entries), $entries);
+ $tagsItems = $rawTags[$maxKey] ?? [];
+ } else {
+ $tagsItems = reset($rawTags);
+ }
+ }
+
+ return Id3Reader::cleanTags($tagsItems);
+ }
+
+ public static function cleanTags(?array $tagsItems): array
+ {
+ if (! $tagsItems) {
+ return [];
+ }
+
+ $temp = [];
+ foreach ($tagsItems as $k => $v) {
+ $temp[$k] = $v[0] ?? null;
+ }
+
+ $items = [];
+ foreach ($temp as $k => $v) {
+ $k = strtolower($k);
+ $k = str_replace(' ', '_', $k);
+ $items[$k] = $v;
+ }
+
+ return $items;
+ }
+
+ public function toAudioFormats(): array
+ {
+ return $this->raw['tags_html'] ?? [];
+ }
+
+ public function toArray(): array
+ {
+ $raw = $this->raw;
+ $raw['id3v2']['APIC'] = null;
+ $raw['ape']['items']['cover art (front)'] = null;
+ $raw['comments'] = null;
+
+ return $raw;
+ }
+}
diff --git a/src/Id3/Id3Writer.php b/src/Id3/Id3Writer.php
new file mode 100644
index 0000000..15d3d81
--- /dev/null
+++ b/src/Id3/Id3Writer.php
@@ -0,0 +1,642 @@
+ $tags Array for `Id3Writer` format.
+ * @param string[] $tags_core Tags from dedicated methods.
+ * @param string[] $tags_custom Tags from `tag()` method.
+ * @param string[] $tags_custom_bulk Tags from `tags()` method.
+ * @param string[] $warnings
+ * @param string[] $errors
+ * @param string[] $formats Formats to write tags.
+ */
+ protected function __construct(
+ protected Audio $audio,
+ protected getid3_writetags $writer,
+ protected AudioCore $core,
+ protected array $tags = [],
+ protected array $tags_core = [],
+ protected array $tags_current = [],
+ protected array $tags_custom = [],
+ protected array $tags_custom_bulk = [],
+ protected array $warnings = [],
+ protected array $errors = [],
+ protected bool $cover_deleted = false,
+ protected bool $skip_errors = false,
+ protected array $formats = [],
+ protected bool $success = false,
+ ) {}
+
+ public static function make(Audio $audio): self
+ {
+ $self = new self(
+ audio: $audio,
+ writer: new getid3_writetags,
+ core: new AudioCore,
+ );
+
+ $self->writer->filename = $audio->getPath();
+
+ return $self;
+ }
+
+ public function getCore(): AudioCore
+ {
+ return $this->core;
+ }
+
+ /**
+ * Allow to remove other tags when writing tags.
+ */
+ public function removeOtherTags(): self
+ {
+ $this->writer->remove_other_tags = true;
+
+ return $this;
+ }
+
+ public function title(?string $title): self
+ {
+ $this->core->title = $title;
+
+ return $this;
+ }
+
+ public function artist(?string $artist): self
+ {
+ $this->core->artist = $artist;
+
+ return $this;
+ }
+
+ public function album(?string $album): self
+ {
+ $this->core->album = $album;
+
+ return $this;
+ }
+
+ public function year(string|int|null $year): self
+ {
+ if (! $year) {
+ $this->core->year = null;
+
+ return $this;
+ }
+
+ $this->core->year = intval($year);
+
+ return $this;
+ }
+
+ public function genre(?string $genre): self
+ {
+ $this->core->genre = $genre;
+
+ return $this;
+ }
+
+ public function trackNumber(string|int|null $track_number): self
+ {
+ if (! $track_number) {
+ $this->core->track_number = null;
+
+ return $this;
+ }
+
+ if (is_int($track_number)) {
+ $track_number = (string) $track_number;
+ }
+
+ $this->core->track_number = $track_number;
+
+ return $this;
+ }
+
+ public function discNumber(string|int|null $disc_number): self
+ {
+ if (! $disc_number) {
+ $this->core->disc_number = null;
+
+ return $this;
+ }
+
+ if (is_int($disc_number)) {
+ $disc_number = (string) $disc_number;
+ }
+
+ $this->core->disc_number = $disc_number;
+
+ return $this;
+ }
+
+ public function composer(?string $composer): self
+ {
+ $this->core->composer = $composer;
+
+ return $this;
+ }
+
+ public function comment(?string $comment): self
+ {
+ $this->core->comment = $comment;
+
+ return $this;
+ }
+
+ public function lyrics(?string $lyrics): self
+ {
+ $this->core->lyrics = $lyrics;
+
+ return $this;
+ }
+
+ public function isCompilation(): self
+ {
+ $this->core->is_compilation = true;
+
+ return $this;
+ }
+
+ public function isNotCompilation(): self
+ {
+ $this->core->is_compilation = false;
+
+ return $this;
+ }
+
+ /**
+ * Not supported by `id3`.
+ */
+ public function creationDate(?string $creation_date): self
+ {
+ $this->core->creation_date = $creation_date;
+
+ return $this;
+ }
+
+ public function copyright(?string $copyright): self
+ {
+ $this->core->copyright = $copyright;
+
+ return $this;
+ }
+
+ /**
+ * Not supported by `id3`.
+ */
+ public function encodingBy(?string $encoding_by): self
+ {
+ $this->core->encoding_by = $encoding_by;
+
+ return $this;
+ }
+
+ /**
+ * Not supported by `id3`.
+ */
+ public function encoding(?string $encoding): self
+ {
+ $this->core->encoding = $encoding;
+
+ return $this;
+ }
+
+ /**
+ * Not supported by `id3`.
+ */
+ public function description(?string $description): self
+ {
+ $this->core->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Not supported by `id3`.
+ */
+ public function synopsis(?string $synopsis): self
+ {
+ $this->core->synopsis = $synopsis;
+
+ return $this;
+ }
+
+ public function language(?string $language): self
+ {
+ $this->core->language = $language;
+
+ return $this;
+ }
+
+ /**
+ * Set new album artist.
+ */
+ public function albumArtist(?string $album_artist): self
+ {
+ $this->core->album_artist = $album_artist;
+
+ return $this;
+ }
+
+ /**
+ * To create a copy of the audio file with new tags.
+ */
+ public function path(string $path): self
+ {
+ if (file_exists($path)) {
+ unlink($path);
+ }
+ copy($this->audio->getPath(), $path);
+
+ $this->writer->filename = $path;
+
+ return $this;
+ }
+
+ /**
+ * Advanced usage only to set tags formats.
+ *
+ * @param string[] $tag_formats
+ */
+ public function tagFormats(array $tag_formats): self
+ {
+ $this->formats = $tag_formats;
+
+ return $this;
+ }
+
+ /**
+ * Remove cover from tags.
+ */
+ public function removeCover(): self
+ {
+ $this->cover_deleted = true;
+
+ return $this;
+ }
+
+ /**
+ * Update cover is only supported by `id3` format.
+ *
+ * @param string $pathOrData Path to cover image or binary data
+ */
+ public function cover(string $pathOrData): self
+ {
+ $this->core->cover = AudioCoreCover::make($pathOrData);
+ $this->core->has_cover = true;
+
+ return $this;
+ }
+
+ /**
+ * Add custom tags without dedicated method (can be use multiple times).
+ *
+ * To know which key use for each format, see documentation.
+ * For example, album artist for `id3` encoded files, is `band` key.
+ *
+ * @docs https://github.com/kiwilan/php-audio#convert-properties
+ *
+ * Example:
+ *
+ * ```php
+ * $audio->write()
+ * ->tag('series-part', '1')
+ * ->tag('series', 'The Lord of the Rings');
+ * ```
+ */
+ public function tag(string $key, string|int|bool|null $value): self
+ {
+ $this->tags_custom[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Alternative to `tag()` method, with a full array of tags.
+ *
+ * To know which key use for each format, see documentation.
+ * For example, album artist for `id3` encoded files, is `band` key.
+ *
+ * @docs https://github.com/kiwilan/php-audio#convert-properties
+ *
+ * @param array $tags
+ *
+ * Example:
+ *
+ * ```php
+ * $audio->write()
+ * ->tags([
+ * 'series-part' => '1',
+ * 'series' => 'The Lord of the Rings',
+ * ]);
+ * ```
+ */
+ public function tags(array $tags): self
+ {
+ $this->tags_custom_bulk = $tags;
+
+ return $this;
+ }
+
+ /**
+ * Skip errors when writing tags.
+ */
+ public function skipErrors(): self
+ {
+ $this->skip_errors = true;
+
+ return $this;
+ }
+
+ /**
+ * Write new tags on file.
+ */
+ public function save(): bool
+ {
+ $this->assignFormats();
+ $this->assignTagsCurrent();
+ $this->assignCoverCurrent();
+ $this->assignTagsCore();
+ $this->assignTagsCustom();
+
+ $this->convertToWriter();
+ $this->convertCoverToWriter();
+
+ $this->writer->tagformats = $this->formats;
+ $this->writer->tag_data = $this->tags;
+
+ $this->success = $this->writer->WriteTags();
+ $this->errors = $this->writer->errors;
+ $this->warnings = $this->writer->warnings;
+
+ $this->handleErrors();
+
+ return $this->success;
+ }
+
+ private function handleErrors(): void
+ {
+ $errors = implode(', ', $this->errors);
+ $warnings = implode(', ', $this->warnings);
+ $errors = strip_tags($errors);
+ $warnings = strip_tags($warnings);
+
+ $supported = match ($this->audio->getFormat()) {
+ AudioFormatEnum::flac => true,
+ AudioFormatEnum::mp3 => true,
+ AudioFormatEnum::ogg => true,
+ AudioFormatEnum::m4b => true,
+ default => false
+ };
+
+ if (! $supported && ! $this->skip_errors) {
+ throw new \Exception("php-audio: format {$this->audio->getFormat()->value} is not supported.");
+ }
+
+ if (! empty($this->errors)) {
+ error_log("php-audio: {$errors}");
+ }
+
+ if (! empty($this->warnings)) {
+ error_log("php-audio: {$warnings}");
+ }
+
+ if (empty($this->errors) && empty($this->warnings)) {
+ return;
+ }
+
+ $msg = 'php-audio: Save tags failed.';
+ if ($errors) {
+ $msg .= " Errors: {$errors}.";
+ }
+ if ($warnings) {
+ $msg .= " Warnings: {$warnings}.";
+ }
+ $isSuccess = $this->success ? 'true' : 'false';
+ $msg .= " Success: {$isSuccess}.";
+ error_log($msg);
+
+ if (! $this->skip_errors) {
+ throw new \Exception($msg);
+ }
+ }
+
+ /**
+ * Parse all tags to convert it to writer format.
+ */
+ private function convertToWriter(): self
+ {
+ $tags = [];
+
+ // set current tags
+ foreach ($this->tags_current as $key => $value) {
+ $tags[$key] = $value;
+ }
+
+ // set custom tags
+ foreach ($this->tags_custom as $key => $value) {
+ $tags[$key] = $value;
+ }
+
+ // set custom bulk tags
+ foreach ($this->tags_custom_bulk as $key => $value) {
+ $tags[$key] = $value;
+ }
+
+ // set core tags
+ foreach ($this->tags_core as $key => $value) {
+ $tags[$key] = $value;
+ }
+
+ $this->tags = $this->formatTags($tags);
+
+ $forbiddenKeys = ['totaltracks'];
+ foreach ($forbiddenKeys as $key) {
+ if (isset($this->tags[$key])) {
+ unset($this->tags[$key]);
+ }
+ }
+
+ return $this;
+ }
+
+ private function assignTagsCustom(): self
+ {
+ if (empty($this->tags_custom) || empty($this->tags_custom_bulk)) {
+ return $this;
+ }
+
+ foreach ($this->tags_custom as $key => $value) {
+ $this->tags_current[$key] = $value;
+ }
+
+ foreach ($this->tags_custom_bulk as $key => $value) {
+ $this->tags_current[$key] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Assign current cover.
+ */
+ private function assignCoverCurrent(): self
+ {
+ // cover deleted
+ if ($this->cover_deleted) {
+ $this->core->cover = null;
+
+ return $this;
+ }
+
+ // skip if no current cover
+ if (! $this->audio->hasCover()) {
+ return $this;
+ }
+
+ // skip if new cover already assigned
+ if ($this->core->cover !== null) {
+ return $this;
+ }
+
+ // get current cover
+ $this->core->cover = new AudioCoreCover(
+ data: $this->audio->getCover()->getContents(base64: true),
+ mime: $this->audio->getCover()->getMimeType(),
+ );
+
+ return $this;
+ }
+
+ /**
+ * Add cover to writer.
+ */
+ private function convertCoverToWriter(): self
+ {
+ if (! in_array($this->audio->getType(), self::ALLOWED_COVER_TYPE)) {
+ return $this;
+ }
+
+ // skip if cover not exists
+ if (! $this->core->cover) {
+ return $this;
+ }
+
+ if (! $this->core->cover->data) {
+ return $this;
+ }
+
+ // 'CTOC' => $old_tags['id3v2']['CTOC'],
+ // 'CHAP' => $old_tags['id3v2']['CHAP'],
+ // 'chapters' => $old_tags['id3v2']['chapters'],
+ $this->tags['attached_picture'] = [
+ [
+ 'data' => base64_decode($this->core->cover->data),
+ 'picturetypeid' => $this->core->cover->picture_type_id ?? 1,
+ 'description' => $this->core->cover->description ?? 'cover',
+ 'mime' => $this->core->cover->mime,
+ ],
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Assign current tags.
+ */
+ private function assignTagsCurrent(): self
+ {
+ $currentTags = [];
+ if (! $this->writer->remove_other_tags) {
+ $currentTags = $this->audio->getRaw();
+ }
+
+ $this->tags_current = $currentTags;
+
+ return $this;
+ }
+
+ /**
+ * Assign new tags from core to array.
+ */
+ private function assignTagsCore(): self
+ {
+ $tagFormat = match ($this->audio->getType()) {
+ AudioTypeEnum::id3 => AudioCore::toId3v2($this->core),
+ AudioTypeEnum::vorbiscomment => AudioCore::toVorbisComment($this->core),
+ AudioTypeEnum::quicktime => AudioCore::toQuicktime($this->core),
+ AudioTypeEnum::matroska => AudioCore::toMatroska($this->core),
+ AudioTypeEnum::ape => AudioCore::toApe($this->core),
+ AudioTypeEnum::asf => AudioCore::toAsf($this->core),
+ default => null,
+ };
+
+ if (! $tagFormat) {
+ return $this;
+ }
+
+ $this->tags_core = $tagFormat->toArray();
+
+ return $this;
+ }
+
+ /**
+ * Assign formats to know how to write tags.
+ *
+ * - ID3v1 (v1 & v1.1)
+ * - ID3v2 (v2.3, v2.4)
+ * - APE (v2)
+ * - Ogg Vorbis comments (need `vorbis-tools`)
+ * - FLAC comments
+ *
+ * Options: `id3v1`, `id3v2.2`, `id2v2.3`, `id3v2.4`, `ape`, `vorbiscomment`, `metaflac`, `real`
+ */
+ private function assignFormats(): self
+ {
+ if (! empty($this->formats)) {
+ return $this;
+ }
+
+ $this->formats = match ($this->audio->getFormat()) {
+ AudioFormatEnum::flac => ['metaflac'],
+ AudioFormatEnum::mp3 => ['id3v1', 'id3v2.4'],
+ AudioFormatEnum::ogg => ['vorbiscomment'],
+ default => [],
+ };
+
+ return $this;
+ }
+
+ /**
+ * Format tags to writer format.
+ *
+ * @param array $tags
+ * @return array
+ */
+ private function formatTags(array $tags): array
+ {
+ $items = [];
+ if (! empty($tags)) {
+ foreach ($tags as $key => $tag) {
+ if (gettype($tag) === 'string') {
+ $items[$key] = [$tag];
+ }
+ }
+ }
+
+ return $items;
+ }
+}
diff --git a/src/Id3/Reader/Id3Audio.php b/src/Id3/Reader/Id3Audio.php
new file mode 100644
index 0000000..c476d95
--- /dev/null
+++ b/src/Id3/Reader/Id3Audio.php
@@ -0,0 +1,60 @@
+streams[0] ?? null;
+ }
+}
diff --git a/src/Id3/Reader/Id3AudioQuicktime.php b/src/Id3/Reader/Id3AudioQuicktime.php
new file mode 100644
index 0000000..87ea32e
--- /dev/null
+++ b/src/Id3/Reader/Id3AudioQuicktime.php
@@ -0,0 +1,174 @@
+|null $timestamps_unix
+ * @param array|null $comments
+ * @param array|null $video
+ * @param array|null $audio
+ * @param Id3AudioQuicktimeChapter[] $chapters
+ */
+ protected function __construct(
+ protected bool $hinting = false,
+ protected ?string $controller = null,
+ protected ?Id3AudioQuicktimeItem $ftyp = null,
+ protected ?array $timestamps_unix = null,
+ protected ?int $time_scale = null,
+ protected ?int $display_scale = null,
+ protected ?array $video = null,
+ protected ?array $audio = null,
+ protected ?array $stts_framecount = null,
+ protected ?array $comments = [],
+ protected array $chapters = [],
+ protected ?Id3AudioQuicktimeItem $free = null,
+ protected ?Id3AudioQuicktimeItem $wide = null,
+ protected ?Id3AudioQuicktimeItem $mdat = null,
+ protected ?string $encoding = null,
+ ) {}
+
+ public static function make(?array $metadata): ?self
+ {
+ if (! $metadata) {
+ return null;
+ }
+
+ $hinting = $metadata['hinting'] ?? false;
+ $controller = $metadata['controller'] ?? null;
+ $ftyp = Id3AudioQuicktimeItem::make($metadata['ftyp'] ?? null);
+ $timestamps_unix = $metadata['timestamps_unix'] ?? null;
+ $time_scale = $metadata['time_scale'] ?? null;
+ $display_scale = $metadata['display_scale'] ?? null;
+ $video = $metadata['video'] ?? null;
+ $audio = $metadata['audio'] ?? null;
+ $stts_framecount = $metadata['stts_framecount'] ?? null;
+ $comments = $metadata['comments'] ?? [];
+
+ $chapters = [];
+ $chaps = $metadata['chapters'] ?? [];
+ foreach ($chaps as $chapter) {
+ $chapters[] = Id3AudioQuicktimeChapter::make($chapter);
+ }
+
+ $free = Id3AudioQuicktimeItem::make($metadata['free'] ?? null);
+ $wide = Id3AudioQuicktimeItem::make($metadata['wide'] ?? null);
+ $mdat = Id3AudioQuicktimeItem::make($metadata['mdat'] ?? null);
+ $encoding = $metadata['encoding'] ?? null;
+
+ $self = new self(
+ hinting: $hinting,
+ controller: $controller,
+ ftyp: $ftyp,
+ timestamps_unix: $timestamps_unix,
+ time_scale: $time_scale,
+ display_scale: $display_scale,
+ video: $video,
+ audio: $audio,
+ stts_framecount: $stts_framecount,
+ comments: $comments,
+ chapters: $chapters,
+ free: $free,
+ wide: $wide,
+ mdat: $mdat,
+ encoding: $encoding,
+ );
+
+ return $self;
+ }
+
+ /**
+ * @return Id3AudioQuicktimeChapter[]
+ */
+ public function getChapters(): array
+ {
+ return $this->chapters;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getComments(): ?array
+ {
+ return $this->comments;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getTimestampsUnix(): ?array
+ {
+ return $this->timestamps_unix;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getVideo(): ?array
+ {
+ return $this->video;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getAudio(): ?array
+ {
+ return $this->audio;
+ }
+
+ public function getEncoding(): ?string
+ {
+ return $this->encoding;
+ }
+
+ public function getHinting(): bool
+ {
+ return $this->hinting;
+ }
+
+ public function getController(): ?string
+ {
+ return $this->controller;
+ }
+
+ public function getFtyp(): ?Id3AudioQuicktimeItem
+ {
+ return $this->ftyp;
+ }
+
+ public function getTimeScale(): ?int
+ {
+ return $this->time_scale;
+ }
+
+ public function getDisplayScale(): ?int
+ {
+ return $this->display_scale;
+ }
+
+ /**
+ * @return int[]|null
+ */
+ public function getSttsFramecount(): ?array
+ {
+ return $this->stts_framecount;
+ }
+
+ public function getFree(): ?Id3AudioQuicktimeItem
+ {
+ return $this->free;
+ }
+
+ public function getWide(): ?Id3AudioQuicktimeItem
+ {
+ return $this->wide;
+ }
+
+ public function getMdat(): ?Id3AudioQuicktimeItem
+ {
+ return $this->mdat;
+ }
+}
diff --git a/src/Id3/Reader/Id3AudioQuicktimeChapter.php b/src/Id3/Reader/Id3AudioQuicktimeChapter.php
new file mode 100644
index 0000000..be6c790
--- /dev/null
+++ b/src/Id3/Reader/Id3AudioQuicktimeChapter.php
@@ -0,0 +1,38 @@
+timestamp;
+ }
+
+ public function getTitle(): ?string
+ {
+ return $this->title;
+ }
+}
diff --git a/src/Id3/Reader/Id3AudioQuicktimeItem.php b/src/Id3/Reader/Id3AudioQuicktimeItem.php
new file mode 100644
index 0000000..4980e98
--- /dev/null
+++ b/src/Id3/Reader/Id3AudioQuicktimeItem.php
@@ -0,0 +1,78 @@
+hierarchy;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->size;
+ }
+
+ public function getOffset(): ?int
+ {
+ return $this->offset;
+ }
+
+ public function getSignature(): ?string
+ {
+ return $this->signature;
+ }
+
+ public function getUnknown1(): ?int
+ {
+ return $this->unknown_1;
+ }
+
+ public function getFourcc(): ?string
+ {
+ return $this->fourcc;
+ }
+}
diff --git a/src/Id3/Reader/Id3AudioTag.php b/src/Id3/Reader/Id3AudioTag.php
new file mode 100644
index 0000000..7b508e1
--- /dev/null
+++ b/src/Id3/Reader/Id3AudioTag.php
@@ -0,0 +1,56 @@
+ $value !== null);
+ $properties = array_filter($properties, fn ($value) => $value !== '');
+
+ return $properties;
+ }
+}
diff --git a/src/Id3/Tag/Id3TagApe.php b/src/Id3/Tag/Id3TagApe.php
new file mode 100644
index 0000000..2c3eb46
--- /dev/null
+++ b/src/Id3/Tag/Id3TagApe.php
@@ -0,0 +1,60 @@
+title;
- }
-
- public function getArtist(): ?string
- {
- return $this->artist;
- }
-
- public function getAlbum(): ?string
- {
- return $this->album;
- }
-
- public function getGenre(): ?string
- {
- return $this->genre;
- }
-
- public function getYear(): ?int
- {
- return $this->year;
- }
-
- public function getTrackNumber(): ?string
- {
- return $this->trackNumber;
- }
-
- public function getComment(): ?string
- {
- return $this->comment;
- }
-
- public function getAlbumArtist(): ?string
- {
- return $this->albumArtist;
- }
-
- public function getComposer(): ?string
- {
- return $this->composer;
- }
-
- public function getDiscNumber(): ?string
- {
- return $this->discNumber;
- }
-
- public function isCompilation(): bool
- {
- if ($this->isCompilation === null) {
- return false;
- }
-
- return $this->isCompilation;
- }
-
- public function getCreationDate(): ?string
- {
- return $this->creationDate;
- }
-
- public function getCopyright(): ?string
- {
- return $this->copyright;
- }
-
- public function getEncodingBy(): ?string
- {
- return $this->encodingBy;
- }
-
- public function getEncoding(): ?string
- {
- return $this->encoding;
- }
-
- public function getDescription(): ?string
- {
- return $this->description;
- }
-
- public function getPodcastDescription(): ?string
- {
- return $this->podcastDescription;
- }
-
- public function getLanguage(): ?string
- {
- return $this->language;
- }
-
- public function getLyrics(): ?string
- {
- return $this->lyrics;
- }
-
- public function getStik(): ?string
- {
- return $this->stik;
- }
-
- public function hasCover(): bool
- {
- return $this->hasCover;
- }
-
- public function getCover(): ?AudioCoreCover
- {
- return $this->cover;
- }
-
- public function setTitle(?string $title): self
- {
- $this->title = $title;
-
- return $this;
- }
-
- public function setArtist(?string $artist): self
- {
- $this->artist = $artist;
-
- return $this;
- }
-
- public function setAlbum(?string $album): self
- {
- $this->album = $album;
-
- return $this;
- }
-
- public function setGenre(?string $genre): self
- {
- $this->genre = $genre;
-
- return $this;
- }
-
- public function setYear(int $year): self
- {
- $this->year = $year;
-
- return $this;
- }
-
- public function setTrackNumber(?string $trackNumber): self
- {
- $this->trackNumber = $trackNumber;
-
- return $this;
- }
-
- public function setComment(?string $comment): self
- {
- $this->comment = $comment;
-
- return $this;
- }
-
- public function setAlbumArtist(?string $albumArtist): self
- {
- $this->albumArtist = $albumArtist;
-
- return $this;
- }
-
- public function setComposer(?string $composer): self
- {
- $this->composer = $composer;
-
- return $this;
- }
-
- public function setDiscNumber(?string $discNumber): self
- {
- $this->discNumber = $discNumber;
-
- return $this;
- }
-
- public function setIsCompilation(bool $isCompilation): self
- {
- $this->isCompilation = $isCompilation;
-
- return $this;
- }
-
- public function setCreationDate(?string $creationDate): self
- {
- $this->creationDate = $creationDate;
-
- return $this;
- }
-
- public function setCopyright(?string $copyright): self
- {
- $this->copyright = $copyright;
-
- return $this;
- }
-
- public function setEncodingBy(?string $encodingBy): self
- {
- $this->encodingBy = $encodingBy;
-
- return $this;
- }
-
- public function setEncoding(?string $encoding): self
- {
- $this->encoding = $encoding;
-
- return $this;
- }
-
- public function setDescription(?string $description): self
- {
- $this->description = $description;
-
- return $this;
- }
-
- public function setPodcastDescription(?string $podcastDescription): self
- {
- $this->podcastDescription = $podcastDescription;
-
- return $this;
- }
-
- public function setLanguage(?string $language): self
- {
- $this->language = $language;
-
- return $this;
- }
-
- public function setLyrics(?string $lyrics): self
- {
- $this->lyrics = $lyrics;
-
- return $this;
- }
-
- public function setStik(?string $stik): self
- {
- $this->stik = $stik;
-
- return $this;
- }
-
- public function setHasCover(bool $hasCover): self
- {
- $this->hasCover = $hasCover;
-
- return $this;
- }
-
- public function setCover(string $pathOrData): self
- {
- $this->cover = AudioCoreCover::make($pathOrData);
-
- return $this;
- }
-
- public function toArray(): array
- {
- return [
- 'title' => $this->title,
- 'artist' => $this->artist,
- 'album' => $this->album,
- 'genre' => $this->genre,
- 'year' => $this->year,
- 'trackNumber' => $this->trackNumber,
- 'comment' => $this->comment,
- 'albumArtist' => $this->albumArtist,
- 'composer' => $this->composer,
- 'discNumber' => $this->discNumber,
- 'isCompilation' => $this->isCompilation,
- 'creationDate' => $this->creationDate,
- 'encodingBy' => $this->encodingBy,
- 'encoding' => $this->encoding,
- 'description' => $this->description,
- 'podcastDescription' => $this->podcastDescription,
- 'language' => $this->language,
- 'lyrics' => $this->lyrics,
- 'stik' => $this->stik,
- 'hasCover' => $this->hasCover,
- 'cover' => $this->cover?->toArray(),
- ];
- }
-
- public static function toId3v2(AudioCore $core): Id3AudioTagV2
- {
- return new Id3AudioTagV2(
- album: $core->getAlbum(),
- artist: $core->getArtist(),
- band: $core->getAlbumArtist(),
- comment: $core->getComment(),
- composer: $core->getComposer(),
- part_of_a_set: $core->getDiscNumber(),
- genre: $core->getGenre(),
- part_of_a_compilation: $core->isCompilation() ? '1' : '0',
- title: $core->getTitle(),
- track_number: $core->getTrackNumber(),
- year: $core->getYear(),
- copyright: $core->getCopyright(),
- text: $core->getPodcastDescription(),
- unsynchronised_lyric: $core->getLyrics(),
- language: $core->getLanguage(),
- );
- }
-
- public static function toId3v1(AudioCore $core): Id3AudioTagV1
- {
- return new Id3AudioTagV1(
- album: $core->getAlbum(),
- artist: $core->getArtist(),
- comment: $core->getComment(),
- genre: $core->getGenre(),
- title: $core->getTitle(),
- track_number: $core->getTrackNumber(),
- year: $core->getYear(),
- );
- }
-
- public static function toVorbisComment(AudioCore $core): Id3TagVorbisComment
- {
- return new Id3TagVorbisComment(
- album: $core->getAlbum(),
- artist: $core->getArtist(),
- albumartist: $core->getAlbumArtist(),
- comment: $core->getComment(),
- composer: $core->getComposer(),
- compilation: $core->isCompilation() ? '1' : '0',
- discnumber: $core->getDiscNumber(),
- genre: $core->getGenre(),
- title: $core->getTitle(),
- tracknumber: $core->getTrackNumber(),
- date: $core->getYear(),
- encoder: $core->getEncoding(),
- description: $core->getDescription(),
- );
- }
-
- public static function toQuicktime(AudioCore $core): Id3TagQuicktime
- {
- return new Id3TagQuicktime(
- title: $core->getTitle(),
- track_number: $core->getTrackNumber(),
- disc_number: $core->getDiscNumber(),
- compilation: $core->isCompilation() ? '1' : '0',
- album: $core->getAlbum(),
- genre: $core->getGenre(),
- composer: $core->getComposer(),
- creation_date: $core->getCreationDate(),
- copyright: $core->getCopyright(),
- artist: $core->getArtist(),
- album_artist: $core->getAlbumArtist(),
- encoded_by: $core->getEncoding(),
- encoding_tool: $core->getEncoding(),
- description: $core->getDescription(),
- description_long: $core->getPodcastDescription(),
- lyrics: $core->getLyrics(),
- comment: $core->getComment(),
- stik: $core->getStik(),
- );
- }
-
- public static function toMatroska(AudioCore $core): Id3TagMatroska
- {
- return new Id3TagMatroska(
- title: $core->getTitle(),
- album: $core->getAlbum(),
- artist: $core->getArtist(),
- album_artist: $core->getAlbumArtist(),
- comment: $core->getComment(),
- composer: $core->getComposer(),
- disc: $core->getDiscNumber(),
- compilation: $core->isCompilation() ? '1' : '0',
- genre: $core->getGenre(),
- part_number: $core->getTrackNumber(),
- date: $core->getYear(),
- encoder: $core->getEncoding(),
- );
- }
-
- public static function toApe(AudioCore $core): Id3TagApe
- {
- return new Id3TagApe(
- album: $core->getAlbum(),
- artist: $core->getArtist(),
- album_artist: $core->getAlbumArtist(),
- comment: $core->getComment(),
- composer: $core->getComposer(),
- disc: $core->getDiscNumber(),
- compilation: $core->isCompilation() ? '1' : '0',
- genre: $core->getGenre(),
- title: $core->getTitle(),
- track: $core->getTrackNumber(),
- date: $core->getYear(),
- encoder: $core->getEncoding(),
- );
- }
-
- public static function toAsf(AudioCore $core): Id3TagAsf
- {
- return new Id3TagAsf(
- album: $core->getAlbum(),
- artist: $core->getArtist(),
- albumartist: $core->getAlbumArtist(),
- composer: $core->getComposer(),
- partofset: $core->getDiscNumber(),
- genre: $core->getGenre(),
- track_number: $core->getTrackNumber(),
- year: $core->getYear(),
- encodingsettings: $core->getEncoding(),
- );
- }
-
- public static function fromId3(?Id3AudioTagV1 $v1, ?Id3AudioTagV2 $v2): AudioCore
- {
- if (! $v1) {
- $v1 = new Id3AudioTagV1;
- }
-
- if (! $v2) {
- $v2 = new Id3AudioTagV2;
- }
-
- return new AudioCore(
- album: $v2->album() ?? $v1->album(),
- artist: $v2->artist() ?? $v1->artist(),
- albumArtist: $v2->band() ?? null,
- comment: $v2->comment() ?? $v1->comment(),
- composer: $v2->composer() ?? null,
- discNumber: $v2->part_of_a_set() ?? null,
- genre: $v2->genre() ?? $v1->genre(),
- isCompilation: $v2->part_of_a_compilation() === '1',
- title: $v2->title() ?? $v1->title(),
- trackNumber: $v2->track_number() ?? $v1->track_number(),
- year: $v2->year() ?? $v1->year(),
- copyright: $v2->copyright() ?? null,
- description: $v2->text() ?? null,
- lyrics: $v2->unsynchronised_lyric() ?? null,
- language: $v2->language() ?? null,
- );
- }
-
- public static function fromId3v2(Id3AudioTagV2 $tag): AudioCore
- {
- return new AudioCore(
- album: $tag->album(),
- artist: $tag->artist(),
- albumArtist: $tag->band(),
- comment: $tag->comment(),
- composer: $tag->composer(),
- discNumber: $tag->part_of_a_set(),
- genre: $tag->genre(),
- isCompilation: $tag->part_of_a_compilation() === '1',
- title: $tag->title(),
- trackNumber: $tag->track_number(),
- year: $tag->year(),
- );
- }
-
- public static function fromId3v1(Id3AudioTagV1 $tag): AudioCore
- {
- return new AudioCore(
- album: $tag->album(),
- artist: $tag->artist(),
- comment: $tag->comment(),
- genre: $tag->genre(),
- title: $tag->title(),
- trackNumber: $tag->track_number(),
- year: $tag->year(),
- );
- }
-
- public static function fromQuicktime(Id3TagQuicktime $tag): AudioCore
- {
-
- $creation_date = $tag->creation_date();
- $description = $tag->description();
- $description_long = $tag->description_long();
-
- $core = new AudioCore(
- title: $tag->title(),
- artist: $tag->artist(),
- album: $tag->album(),
- genre: $tag->genre(),
- trackNumber: $tag->track_number(),
- discNumber: $tag->disc_number(),
- composer: $tag->composer(),
- isCompilation: $tag->compilation() === '1',
- comment: $tag->comment(),
- albumArtist: $tag->album_artist(),
- encodingBy: $tag->encoded_by(),
- encoding: $tag->encoding_tool(),
- language: $tag->language(),
- );
-
- if ($creation_date) {
- if (strlen($creation_date) === 4) {
- $core->setYear((int) $creation_date);
- } else {
- try {
- $parsedCreationDate = new \DateTimeImmutable($creation_date);
- } catch (\Exception $e) {
- // ignore the issue so the rest of the data will be available
- }
-
- if (! empty($parsedCreationDate)) {
- $core->setCreationDate($parsedCreationDate->format('Y-m-d\TH:i:s\Z'));
- $core->setYear((int) $parsedCreationDate->format('Y'));
- }
- }
- }
-
- $core->setCopyright($tag->copyright());
- $core->setDescription($description);
- $core->setPodcastDescription($description_long);
- $core->setLyrics($tag->lyrics());
- $core->setStik($tag->stik());
-
- return $core;
- }
-
- public static function fromVorbisComment(Id3TagVorbisComment $tag): AudioCore
- {
- return new AudioCore(
- title: $tag->title(),
- artist: $tag->artist(),
- album: $tag->album(),
- genre: $tag->genre(),
- trackNumber: $tag->trackNumber(),
- comment: $tag->comment(),
- albumArtist: $tag->albumartist(),
- composer: $tag->composer(),
- discNumber: $tag->discNumber(),
- isCompilation: $tag->compilation() === '1',
- year: (int) $tag->date(),
- encoding: $tag->encoder(),
- description: $tag->description(),
- );
- }
-
- public static function fromAsf(Id3TagAsf $tag): AudioCore
- {
- return new AudioCore(
- title: $tag->title(),
- artist: $tag->artist(),
- album: $tag->album(),
- albumArtist: $tag->albumartist(),
- composer: $tag->composer(),
- discNumber: $tag->partofset(),
- genre: $tag->genre(),
- trackNumber: $tag->track_number(),
- year: (int) $tag->year(),
- encoding: $tag->encodingsettings(),
- );
- }
-
- public static function fromMatroska(Id3TagMatroska $tag): AudioCore
- {
- return new AudioCore(
- title: $tag->title(),
- album: $tag->album(),
- artist: $tag->artist(),
- albumArtist: $tag->album_artist(),
- comment: $tag->comment(),
- composer: $tag->composer(),
- discNumber: $tag->disc(),
- genre: $tag->genre(),
- isCompilation: $tag->compilation(),
- trackNumber: $tag->part_number(),
- year: (int) $tag->date(),
- encoding: $tag->encoder(),
- );
- }
-
- public static function fromApe(Id3TagApe $tag): AudioCore
- {
- return new AudioCore(
- album: $tag->album(),
- artist: $tag->artist(),
- albumArtist: $tag->album_artist(),
- comment: $tag->comment(),
- composer: $tag->composer(),
- discNumber: $tag->disc(),
- genre: $tag->genre(),
- isCompilation: $tag->compilation() === '1',
- title: $tag->title(),
- trackNumber: $tag->track(),
- creationDate: $tag->date(),
- year: $tag->year() ?? (int) $tag->date(),
- encoding: $tag->encoder(),
- description: $tag->description(),
- copyright: $tag->copyright(),
- lyrics: $tag->lyrics(),
- podcastDescription: $tag->podcastdesc(),
- language: $tag->language(),
- );
- }
-}
-
-class AudioCoreCover
-{
- public function __construct(
- protected ?string $data = null,
- protected ?string $picturetypeid = null,
- protected ?string $description = null,
- protected ?string $mime = null,
- ) {}
-
- public static function make(string $pathOrData): self
- {
- $self = new self;
-
- if (file_exists($pathOrData)) {
- $image = getimagesize($pathOrData);
- $self->data = base64_encode(file_get_contents($pathOrData));
- $self->picturetypeid = $image[2];
- $self->description = 'cover';
- $self->mime = $image['mime'];
-
- return $self;
- }
-
- $image = getimagesizefromstring($pathOrData);
- $self->data = base64_encode($pathOrData);
- $self->picturetypeid = $image[2];
- $self->mime = $image['mime'];
- $self->description = 'cover';
-
- return $self;
- }
-
- public function data(): ?string
- {
- return $this->data;
- }
-
- public function picturetypeid(): ?string
- {
- return $this->picturetypeid;
- }
-
- public function description(): ?string
- {
- return $this->description;
- }
-
- public function mime(): ?string
- {
- return $this->mime;
- }
-
- public function toArray(): array
- {
- return [
- 'data' => $this->data,
- 'picturetypeid' => $this->picturetypeid,
- 'description' => $this->description,
- 'mime' => $this->mime,
- ];
- }
-}
diff --git a/src/Models/AudioCover.php b/src/Models/AudioCover.php
index 201c119..fabe1b7 100644
--- a/src/Models/AudioCover.php
+++ b/src/Models/AudioCover.php
@@ -2,57 +2,86 @@
namespace Kiwilan\Audio\Models;
+use Kiwilan\Audio\Id3\Reader\Id3Comments;
+
class AudioCover
{
- protected ?string $contents = null;
-
- protected ?string $mimeType = null;
-
- protected ?int $width = null;
-
- protected ?int $height = null;
+ protected function __construct(
+ protected ?string $contents = null,
+ protected ?string $mime_type = null,
+ protected ?int $width = null,
+ protected ?int $height = null,
+ ) {}
public static function make(?Id3Comments $comments): ?self
{
- if (! $comments) {
+ if (! $comments || ! $comments->picture) {
return null;
}
$self = new self;
- $self->contents = $comments->picture()->data();
- $self->mimeType = $comments->picture()->image_mime();
- $self->width = $comments->picture()->image_width();
- $self->height = $comments->picture()->image_height();
+ $self->contents = base64_encode($comments->picture->data);
+ $self->mime_type = $comments->picture->image_mime;
+ $self->width = $comments->picture->image_width;
+ $self->height = $comments->picture->image_height;
return $self;
}
/**
- * @deprecated Use `getContents()` instead.
+ * Get the contents of the cover
+ *
+ * By default, the contents are decoded from base64, but you can get the raw contents by passing `true` as the first argument.
*/
- public function getContent(): ?string
+ public function getContents(bool $base64 = false): ?string
{
- return $this->contents;
- }
+ if (! $this->contents) {
+ return null;
+ }
- public function getContents(): ?string
- {
- return $this->contents;
+ return $base64 ? $this->contents : base64_decode($this->contents);
}
+ /**
+ * Get the MIME type of the cover
+ */
public function getMimeType(): ?string
{
- return $this->mimeType;
+ return $this->mime_type;
}
+ /**
+ * Get the width of the cover
+ */
public function getWidth(): ?int
{
return $this->width;
}
+ /**
+ * Get the height of the cover
+ */
public function getHeight(): ?int
{
return $this->height;
}
+
+ /**
+ * Extract the cover to a file.
+ */
+ public function extractCover(string $path): void
+ {
+ file_put_contents($path, $this->getContents());
+ }
+
+ public function toArray(): array
+ {
+ return [
+ 'contents' => $this->contents,
+ 'mime_type' => $this->mime_type,
+ 'width' => $this->width,
+ 'height' => $this->height,
+ ];
+ }
}
diff --git a/src/Models/AudioMetadata.php b/src/Models/AudioMetadata.php
index 0e04738..6900d6a 100644
--- a/src/Models/AudioMetadata.php
+++ b/src/Models/AudioMetadata.php
@@ -2,125 +2,315 @@
namespace Kiwilan\Audio\Models;
+use DateTime;
use Kiwilan\Audio\Audio;
+use Kiwilan\Audio\Id3\Id3Reader;
+use Kiwilan\Audio\Id3\Reader\Id3AudioQuicktime;
class AudioMetadata
{
protected function __construct(
- protected ?string $path = null,
- protected ?int $filesize = null,
- protected ?string $extension = null,
- protected ?string $dataformat = null,
+ protected ?int $file_size = null,
+ protected ?string $data_format = null,
+ protected ?array $warning = [],
protected ?string $encoding = null,
- protected ?string $mimeType = null,
- protected ?float $durationSeconds = null,
- protected ?string $durationReadable = null,
+ protected ?string $mime_type = null,
+ protected ?Id3AudioQuicktime $quicktime = null,
+ protected ?float $duration_seconds = null,
protected ?int $bitrate = null,
- protected ?string $bitrateMode = null,
- protected ?int $sampleRate = null,
+ protected ?string $bitrate_mode = null,
+ protected ?int $sample_rate = null,
protected ?int $channels = null,
- protected ?string $channelMode = null,
- protected bool $lossless = false,
- protected ?float $compressionRatio = null,
+ protected ?string $channel_mode = null,
+ protected bool $is_lossless = false,
+ protected ?float $compression_ratio = null,
+ protected ?string $codec = null,
+ protected ?string $encoder_options = null,
+ protected ?string $version = null,
+ protected ?int $av_data_offset = null,
+ protected ?int $av_data_end = null,
+ protected ?string $file_path = null,
+ protected ?string $filename = null,
+ protected ?DateTime $last_access_at = null,
+ protected ?DateTime $created_at = null,
+ protected ?DateTime $modified_at = null,
) {}
- public static function make(Audio $audio): self
+ public static function make(Audio $audio, Id3Reader $id3_reader): self
{
$path = $audio->getPath();
- $reader = $audio->getReader();
- $audio = $reader->getAudio();
+ $audio = $id3_reader->getAudio();
+ $stat = stat($path);
return new self(
- path: $path,
- filesize: $reader->getFilesize(),
- extension: pathinfo($path, PATHINFO_EXTENSION),
- dataformat: $audio?->dataformat(),
- encoding: $reader->getEncoding(),
- mimeType: $reader->getMimeType(),
- durationSeconds: $reader->getPlaytimeSeconds(),
- durationReadable: $reader->getPlaytimeString(),
- bitrate: $reader->getBitrate(),
- bitrateMode: $audio?->bitrate_mode(),
- sampleRate: $audio?->sample_rate(),
- channels: $audio?->channels(),
- channelMode: $audio?->channelmode(),
- lossless: $audio?->lossless() ?? false,
- compressionRatio: $audio?->compression_ratio(),
+ file_size: $id3_reader->getFileSize(),
+ data_format: $audio?->data_format,
+ warning: $id3_reader->getWarning(),
+ encoding: $id3_reader->getEncoding(),
+ mime_type: $id3_reader->getMimeType(),
+ quicktime: $id3_reader->getQuicktime(),
+ duration_seconds: $id3_reader->getPlaytimeSeconds(),
+ bitrate: intval($id3_reader->getBitrate()),
+ bitrate_mode: $audio?->bitrate_mode,
+ sample_rate: $audio?->sample_rate,
+ channels: $audio?->channels,
+ channel_mode: $audio?->channel_mode,
+ is_lossless: $audio?->lossless ?? false,
+ compression_ratio: $audio?->compression_ratio,
+ codec: $audio?->codec,
+ encoder_options: $audio?->encoder_options,
+ version: $id3_reader->getVersion(),
+ av_data_offset: $id3_reader->getAvDataOffset(),
+ av_data_end: $id3_reader->getAvDataEnd(),
+ file_path: $id3_reader->getFilePath(),
+ filename: $id3_reader->getFilename(),
+ last_access_at: $stat['atime'] ? new DateTime('@'.$stat['atime']) : null,
+ created_at: $stat['ctime'] ? new DateTime('@'.$stat['ctime']) : null,
+ modified_at: $stat['mtime'] ? new DateTime('@'.$stat['mtime']) : null,
);
}
- public function getPath(): ?string
+ /**
+ * Get size of the audio file in bytes, like `180664`
+ */
+ public function getFileSize(): ?int
{
- return $this->path;
+ return $this->file_size;
}
- public function getFilesize(): ?int
+ /**
+ * Get size of the audio file in human readable format, like `175.99 KB`
+ */
+ public function getSizeHuman(int $decimals = 2): ?string
{
- return $this->filesize;
+ $file_size = (string) $this->file_size;
+ $size = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ $factor = floor((strlen($file_size) - 1) / 3);
+
+ return sprintf("%.{$decimals}f", $file_size / pow(1024, $factor)).' '.$size[$factor];
}
- public function getExtension(): ?string
+ /**
+ * Get data format of the audio file, like `mp3`, `wav`, `etc`
+ */
+ public function getDataFormat(): ?string
{
- return $this->extension;
+ return $this->data_format;
}
- public function getDataformat(): ?string
+ /**
+ * Get warning of the audio file
+ *
+ * @return string[]
+ */
+ public function getWarning(): array
{
- return $this->dataformat;
+ return $this->warning;
}
+ /**
+ * Get encoding of the audio file, like `UTF-8`, `ISO-8859-1`, `etc`
+ */
public function getEncoding(): ?string
{
return $this->encoding;
}
+ /**
+ * Get mime type of the audio file, like `audio/x-matroska`, `audio/mpeg`, `etc`
+ */
public function getMimeType(): ?string
{
- return $this->mimeType;
+ return $this->mime_type;
}
- public function getDurationSeconds(): ?float
+ /**
+ * Get quicktime data of the audio file, if available
+ */
+ public function getQuicktime(): ?Id3AudioQuicktime
{
- return $this->durationSeconds;
+ return $this->quicktime;
}
- public function getDurationReadable(): ?string
+ /**
+ * Get duration of the audio file in seconds, like `11.05`
+ */
+ public function getDurationSeconds(?int $decimals = null): ?float
{
- return $this->durationReadable;
+ if ($decimals !== null) {
+ return round($this->duration_seconds, $decimals);
+ }
+
+ return $this->duration_seconds;
}
+ /**
+ * Get bitrate of the audio file in bits per second, like `128000`
+ */
public function getBitrate(): ?int
{
return $this->bitrate;
}
+ /**
+ * Get bitrate mode of the audio file, like `cbr`, `vbr`, `etc`
+ */
public function getBitrateMode(): ?string
{
- return $this->bitrateMode;
+ return $this->bitrate_mode;
}
+ /**
+ * Get sample rate of the audio file in hertz, like `44100`
+ */
public function getSampleRate(): ?int
{
- return $this->sampleRate;
+ return $this->sample_rate;
}
+ /**
+ * Get channels of the audio file, like `2`
+ */
public function getChannels(): ?int
{
return $this->channels;
}
+ /**
+ * Get channel mode of the audio file, like `joint stereo`, `stereo`, `etc`
+ */
public function getChannelMode(): ?string
{
- return $this->channelMode;
+ return $this->channel_mode;
+ }
+
+ /**
+ * Get lossless status of the audio file, like `false`
+ */
+ public function isLossless(): bool
+ {
+ return $this->is_lossless;
+ }
+
+ /**
+ * Get compression ratio of the audio file, like `0.1`
+ */
+ public function getCompressionRatio(?int $decimals = null): ?float
+ {
+ if ($decimals !== null) {
+ return round($this->compression_ratio, $decimals);
+ }
+
+ return $this->compression_ratio;
+ }
+
+ /**
+ * Get codec of the audio file, like `LAME`
+ */
+ public function getCodec(): ?string
+ {
+ return $this->codec;
+ }
+
+ /**
+ * Get encoder options of the audio file, like `CBR`, `VBR`, `etc`
+ */
+ public function getEncoderOptions(): ?string
+ {
+ return $this->encoder_options;
+ }
+
+ /**
+ * Get version of `JamesHeinrich/getID3`, like `1.9.23-202310190849`
+ *
+ * @docs https://github.com/JamesHeinrich/getID3
+ */
+ public function getVersion(): ?string
+ {
+ return $this->version;
+ }
+
+ /**
+ * Get audio/video data offset of the audio file, like `25808`
+ */
+ public function getAvDataOffset(): ?int
+ {
+ return $this->av_data_offset;
+ }
+
+ /**
+ * Get audio/video data end of the audio file, like `1214046`
+ */
+ public function getAvDataEnd(): ?int
+ {
+ return $this->av_data_end;
+ }
+
+ /**
+ * Get path of audio file directory, like `/path/to`
+ */
+ public function getFilePath(): ?string
+ {
+ return $this->file_path;
+ }
+
+ /**
+ * Get filename of the audio file, like `audio.mp3`
+ */
+ public function getFilename(): ?string
+ {
+ return $this->filename;
+ }
+
+ /**
+ * Get last access time of the audio file, like `2021-09-01 00:00:00`
+ */
+ public function getLastAccessAt(): ?DateTime
+ {
+ return $this->last_access_at;
+ }
+
+ /**
+ * Get created time of the audio file, like `2021-09-01 00:00:00`
+ */
+ public function getCreatedAt(): ?DateTime
+ {
+ return $this->created_at;
}
- public function getLossless(): bool
+ /**
+ * Get modified time of the audio file, like `2021-09-01 00:00:00`
+ */
+ public function getModifiedAt(): ?DateTime
{
- return $this->lossless;
+ return $this->modified_at;
}
- public function getCompressionRatio(): ?float
+ public function toArray(): array
{
- return $this->compressionRatio;
+ return [
+ 'file_size' => $this->file_size,
+ 'data_format' => $this->data_format,
+ 'encoding' => $this->encoding,
+ 'mime_type' => $this->mime_type,
+ 'duration_seconds' => $this->duration_seconds,
+ 'bitrate' => $this->bitrate,
+ 'bitrate_mode' => $this->bitrate_mode,
+ 'sample_rate' => $this->sample_rate,
+ 'channels' => $this->channels,
+ 'channel_mode' => $this->channel_mode,
+ 'is_lossless' => $this->is_lossless,
+ 'compression_ratio' => $this->compression_ratio,
+ 'codec' => $this->codec,
+ 'encoder_options' => $this->encoder_options,
+ 'version' => $this->version,
+ 'av_data_offset' => $this->av_data_offset,
+ 'av_data_end' => $this->av_data_end,
+ 'file_path' => $this->file_path,
+ 'filename' => $this->filename,
+ 'last_access_at' => $this->last_access_at?->format('Y-m-d H:i:s'),
+ 'created_at' => $this->created_at?->format('Y-m-d H:i:s'),
+ 'modified_at' => $this->modified_at?->format('Y-m-d H:i:s'),
+ ];
}
}
diff --git a/src/Models/AudioStat.php b/src/Models/AudioStat.php
deleted file mode 100644
index 3e70105..0000000
--- a/src/Models/AudioStat.php
+++ /dev/null
@@ -1,147 +0,0 @@
-deviceNumber = $stat['dev'] ?? null;
- $self->inodeNumber = $stat['ino'] ?? null;
- $self->inodeProtectionMode = $stat['mode'] ?? null;
- $self->numberOfLinks = $stat['nlink'] ?? null;
- $self->userId = $stat['uid'] ?? null;
- $self->groupId = $stat['gid'] ?? null;
- $self->deviceType = $stat['rdev'] ?? null;
- $self->size = $stat['size'] ?? null;
- $self->lastAccessAt = $stat['atime'] ? new DateTime('@'.$stat['atime']) : null;
- $self->createdAt = $stat['ctime'] ? new DateTime('@'.$stat['ctime']) : null;
- $self->modifiedAt = $stat['mtime'] ? new DateTime('@'.$stat['mtime']) : null;
- $self->blockSize = $stat['blksize'] ?? null;
- $self->numberOfBlocks = $stat['blocks'] ?? null;
-
- return $self;
- }
-
- public function getPath(): string
- {
- return $this->path;
- }
-
- public function getDeviceNumber(): ?int
- {
- return $this->deviceNumber;
- }
-
- public function getInodeNumber(): ?int
- {
- return $this->inodeNumber;
- }
-
- public function getInodeProtectionMode(): ?int
- {
- return $this->inodeProtectionMode;
- }
-
- public function getNumberOfLinks(): ?int
- {
- return $this->numberOfLinks;
- }
-
- public function getUserId(): ?int
- {
- return $this->userId;
- }
-
- public function getGroupId(): ?int
- {
- return $this->groupId;
- }
-
- public function getDeviceType(): ?int
- {
- return $this->deviceType;
- }
-
- public function getSize(): ?int
- {
- return $this->size;
- }
-
- public function getLastAccessAt(): ?DateTime
- {
- return $this->lastAccessAt;
- }
-
- public function getCreatedAt(): ?DateTime
- {
- return $this->createdAt;
- }
-
- public function getModifiedAt(): ?DateTime
- {
- return $this->modifiedAt;
- }
-
- public function getBlockSize(): ?int
- {
- return $this->blockSize;
- }
-
- public function getNumberOfBlocks(): ?int
- {
- return $this->numberOfBlocks;
- }
-
- public function toArray(): array
- {
- return [
- 'path' => $this->path,
- 'deviceNumber' => $this->deviceNumber,
- 'inodeNumber' => $this->inodeNumber,
- 'inodeProtectionMode' => $this->inodeProtectionMode,
- 'numberOfLinks' => $this->numberOfLinks,
- 'userId' => $this->userId,
- 'groupId' => $this->groupId,
- 'deviceType' => $this->deviceType,
- 'size' => $this->size,
- 'lastAccessAt' => $this->lastAccessAt?->format('Y-m-d H:i:s'),
- 'createdAt' => $this->createdAt?->format('Y-m-d H:i:s'),
- 'modifiedAt' => $this->modifiedAt?->format('Y-m-d H:i:s'),
- 'blockSize' => $this->blockSize,
- 'numberOfBlocks' => $this->numberOfBlocks,
- ];
- }
-
- public function toJson(): string
- {
- return json_encode($this->toArray());
- }
-
- public function __toString(): string
- {
- return $this->toJson();
- }
-}
diff --git a/src/Models/Id3Reader.php b/src/Models/Id3Reader.php
deleted file mode 100644
index ed23da6..0000000
--- a/src/Models/Id3Reader.php
+++ /dev/null
@@ -1,1792 +0,0 @@
-raw = $self->instance->analyze($path);
- $self->is_writable = $self->instance->is_writable($path);
- $metadata = $self->raw;
-
- $audio = Id3Audio::make($metadata['audio'] ?? null);
- $video = Id3Video::make($metadata['video'] ?? null);
- $tags = Id3AudioTag::make($metadata['tags'] ?? null);
- $comments = Id3Comments::make($metadata['comments'] ?? null);
- $tags_html = Id3TagsHtml::make($metadata['tags_html'] ?? null);
- $bitrate = $metadata['bitrate'] ?? null;
- if ($bitrate) {
- $bitrate = intval($bitrate);
- }
-
- $self->version = $metadata['GETID3_VERSION'] ?? null;
- $self->filesize = $metadata['filesize'] ?? null;
- $self->filepath = $metadata['filepath'] ?? null;
- $self->filename = $metadata['filename'] ?? null;
- $self->filenamepath = $metadata['filenamepath'] ?? null;
- $self->avdataoffset = $metadata['avdataoffset'] ?? null;
- $self->avdataend = $metadata['avdataend'] ?? null;
- $self->fileformat = $metadata['fileformat'] ?? null;
- $self->audio = $audio;
- $self->video = $video;
- $self->tags = $tags;
- $self->comments = $comments;
- $self->encoding = $metadata['encoding'] ?? null;
- $self->mime_type = $metadata['mime_type'] ?? null;
- $self->mpeg = $metadata['mpeg'] ?? null;
- $self->playtime_seconds = $metadata['playtime_seconds'] ?? null;
- $self->tags_html = $tags_html;
- $self->bitrate = $bitrate;
- $self->playtime_string = $metadata['playtime_string'] ?? null;
-
- return $self;
- }
-
- public function getInstance(): getID3
- {
- return $this->instance;
- }
-
- public function getVersion(): ?string
- {
- return $this->version;
- }
-
- public function getFilesize(): ?int
- {
- return $this->filesize;
- }
-
- public function getFilepath(): ?string
- {
- return $this->filepath;
- }
-
- public function getFilename(): ?string
- {
- return $this->filename;
- }
-
- public function getFilenamepath(): ?string
- {
- return $this->filenamepath;
- }
-
- public function getAvdataoffset(): ?int
- {
- return $this->avdataoffset;
- }
-
- public function getAvdataend(): ?int
- {
- return $this->avdataend;
- }
-
- public function getFileformat(): ?string
- {
- return $this->fileformat;
- }
-
- public function getAudio(): ?Id3Audio
- {
- return $this->audio;
- }
-
- public function getTags(): ?Id3AudioTag
- {
- return $this->tags;
- }
-
- public function getComments(): ?Id3Comments
- {
- return $this->comments;
- }
-
- public function getEncoding(): ?string
- {
- return $this->encoding;
- }
-
- public function getMimeType(): ?string
- {
- return $this->mime_type;
- }
-
- public function getMpeg(): mixed
- {
- return $this->mpeg;
- }
-
- public function getPlaytimeSeconds(): ?float
- {
- return $this->playtime_seconds;
- }
-
- public function getTagsHtml(): ?Id3TagsHtml
- {
- return $this->tags_html;
- }
-
- public function getBitrate(): ?float
- {
- return $this->bitrate;
- }
-
- public function getPlaytimeString(): ?string
- {
- return $this->playtime_string;
- }
-
- public function isWritable(): bool
- {
- return $this->is_writable;
- }
-
- public function getRaw(): array
- {
- return $this->raw;
- }
-
- public function toTags(?string $audioFormat = null): array
- {
- $rawTags = $this->raw['tags_html'] ?? [];
-
- if (count($rawTags) === 0) {
- return [];
- }
-
- $tagsItems = [];
- if ($audioFormat) {
- $tagsItems = $rawTags[$audioFormat] ?? [];
- } else {
- if (count($rawTags) > 1) {
- $entries = [];
- foreach ($rawTags as $key => $keyTags) {
- $entries[$key] = count($keyTags);
- }
- $maxKey = array_search(max($entries), $entries);
- $tagsItems = $rawTags[$maxKey] ?? [];
- } else {
- $tagsItems = reset($rawTags);
- }
- }
-
- return Id3Reader::cleanTags($tagsItems);
- }
-
- public static function cleanTags(?array $tagsItems): array
- {
- if (! $tagsItems) {
- return [];
- }
-
- $temp = [];
- foreach ($tagsItems as $k => $v) {
- $temp[$k] = $v[0] ?? null;
- }
-
- $items = [];
- foreach ($temp as $k => $v) {
- $k = strtolower($k);
- $k = str_replace(' ', '_', $k);
- $items[$k] = $v;
- }
-
- return $items;
- }
-
- public function toAudioFormats(): array
- {
- return $this->raw['tags_html'] ?? [];
- }
-
- public function toArray(): array
- {
- $raw = $this->raw;
- $raw['id3v2']['APIC'] = null;
- $raw['ape']['items']['cover art (front)'] = null;
- $raw['comments'] = null;
-
- return $raw;
- }
-}
-
-class Id3Audio
-{
- /** @var Id3Stream[] */
- protected array $streams = [];
-
- protected function __construct(
- protected ?string $dataformat = null,
- protected ?int $channels = null,
- protected ?int $sample_rate = null,
- protected ?float $bitrate = null,
- protected ?string $channelmode = null,
- protected ?string $bitrate_mode = null,
- protected ?string $codec = null,
- protected ?string $encoder = null,
- protected bool $lossless = false,
- protected ?string $encoder_options = null,
- protected ?float $compression_ratio = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $streams = [];
- if (array_key_exists('streams', $metadata)) {
- foreach ($metadata['streams'] as $stream) {
- $streams[] = Id3Stream::make($stream);
- }
- }
-
- $self = new self(
- dataformat: $metadata['dataformat'] ?? null,
- channels: $metadata['channels'] ?? null,
- sample_rate: $metadata['sample_rate'] ?? null,
- bitrate: $metadata['bitrate'] ?? null,
- channelmode: $metadata['channelmode'] ?? null,
- bitrate_mode: $metadata['bitrate_mode'] ?? null,
- codec: $metadata['codec'] ?? null,
- encoder: $metadata['encoder'] ?? null,
- lossless: $metadata['lossless'] ?? false,
- encoder_options: $metadata['encoder_options'] ?? null,
- compression_ratio: $metadata['compression_ratio'] ?? null,
- );
- $self->streams = $streams;
-
- return $self;
- }
-
- /** @return Id3Stream[] */
- public function streams(): array
- {
- return $this->streams;
- }
-
- public function dataformat(): ?string
- {
- return $this->dataformat;
- }
-
- public function channels(): ?int
- {
- return $this->channels;
- }
-
- public function sample_rate(): ?int
- {
- return $this->sample_rate;
- }
-
- public function bitrate(): ?float
- {
- return $this->bitrate;
- }
-
- public function channelmode(): ?string
- {
- return $this->channelmode;
- }
-
- public function bitrate_mode(): ?string
- {
- return $this->bitrate_mode;
- }
-
- public function codec(): ?string
- {
- return $this->codec;
- }
-
- public function encoder(): ?string
- {
- return $this->encoder;
- }
-
- public function lossless(): bool
- {
- return $this->lossless;
- }
-
- public function encoder_options(): ?string
- {
- return $this->encoder_options;
- }
-
- public function compression_ratio(): ?float
- {
- return $this->compression_ratio;
- }
-
- public function stream(): ?Id3Stream
- {
- return $this->streams[0] ?? null;
- }
-}
-
-class Id3Video
-{
- protected function __construct(
- protected ?string $dataformat = null,
- protected ?int $rotate = null,
- protected ?float $resolution_x = null,
- protected ?float $resolution_y = null,
- protected ?float $frame_rate = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- dataformat: $metadata['dataformat'] ?? null,
- rotate: $metadata['rotate'] ?? null,
- resolution_x: $metadata['resolution_x'] ?? null,
- resolution_y: $metadata['resolution_y'] ?? null,
- frame_rate: $metadata['frame_rate'] ?? null,
- );
-
- return $self;
- }
-
- public function dataformat(): ?string
- {
- return $this->dataformat;
- }
-
- public function rotate(): ?int
- {
- return $this->rotate;
- }
-
- public function resolution_x(): ?float
- {
- return $this->resolution_x;
- }
-
- public function resolution_y(): ?float
- {
- return $this->resolution_y;
- }
-
- public function frame_rate(): ?float
- {
- return $this->frame_rate;
- }
-}
-
-class Id3Stream
-{
- protected function __construct(
- protected ?string $dataformat = null,
- protected ?int $channels = null,
- protected ?int $sample_rate = null,
- protected ?float $bitrate = null,
- protected ?string $channelmode = null,
- protected ?string $bitrate_mode = null,
- protected ?string $codec = null,
- protected ?string $encoder = null,
- protected bool $lossless = false,
- protected ?string $encoder_options = null,
- protected ?float $compression_ratio = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- dataformat: $metadata['dataformat'] ?? null,
- channels: $metadata['channels'] ?? null,
- sample_rate: $metadata['sample_rate'] ?? null,
- bitrate: $metadata['bitrate'] ?? null,
- channelmode: $metadata['channelmode'] ?? null,
- bitrate_mode: $metadata['bitrate_mode'] ?? null,
- codec: $metadata['codec'] ?? null,
- encoder: $metadata['encoder'] ?? null,
- lossless: $metadata['lossless'] ?? false,
- encoder_options: $metadata['encoder_options'] ?? null,
- compression_ratio: $metadata['compression_ratio'] ?? null,
- );
-
- return $self;
- }
-
- public function dataformat(): ?string
- {
- return $this->dataformat;
- }
-
- public function channels(): ?int
- {
- return $this->channels;
- }
-
- public function sample_rate(): ?int
- {
- return $this->sample_rate;
- }
-
- public function bitrate(): ?float
- {
- return $this->bitrate;
- }
-
- public function channelmode(): ?string
- {
- return $this->channelmode;
- }
-
- public function bitrate_mode(): ?string
- {
- return $this->bitrate_mode;
- }
-
- public function codec(): ?string
- {
- return $this->codec;
- }
-
- public function encoder(): ?string
- {
- return $this->encoder;
- }
-
- public function lossless(): bool
- {
- return $this->lossless;
- }
-
- public function encoder_options(): ?string
- {
- return $this->encoder_options;
- }
-
- public function compression_ratio(): ?float
- {
- return $this->compression_ratio;
- }
-}
-
-class Id3AudioTag
-{
- protected function __construct(
- protected ?Id3AudioTagV1 $id3v1 = null,
- protected ?Id3AudioTagV2 $id3v2 = null,
- protected ?Id3TagQuicktime $quicktime = null,
- protected ?Id3TagAsf $asf = null,
- protected ?Id3TagVorbisComment $vorbiscomment = null,
- protected ?Id3TagRiff $riff = null,
- protected ?Id3TagMatroska $matroska = null,
- protected ?Id3TagApe $ape = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $id3v1 = Id3Reader::cleanTags($metadata['id3v1'] ?? null);
- $id3v2 = Id3Reader::cleanTags($metadata['id3v2'] ?? null);
- $quicktime = Id3Reader::cleanTags($metadata['quicktime'] ?? null);
- $asf = Id3Reader::cleanTags($metadata['asf'] ?? null);
- $vorbiscomment = Id3Reader::cleanTags($metadata['vorbiscomment'] ?? null);
- $riff = Id3Reader::cleanTags($metadata['riff'] ?? null);
- $matroska = Id3Reader::cleanTags($metadata['matroska'] ?? null);
- $ape = Id3Reader::cleanTags($metadata['ape'] ?? null);
-
- $self = new self(
- id3v1: Id3AudioTagV1::make($id3v1),
- id3v2: Id3AudioTagV2::make($id3v2),
- quicktime: Id3TagQuicktime::make($quicktime),
- asf: Id3TagAsf::make($asf),
- vorbiscomment: Id3TagVorbisComment::make($vorbiscomment),
- riff: Id3TagRiff::make($riff),
- matroska: Id3TagMatroska::make($matroska),
- ape: Id3TagApe::make($ape),
- );
-
- return $self;
- }
-
- public function id3v1(): ?Id3AudioTagV1
- {
- return $this->id3v1;
- }
-
- public function id3v2(): ?Id3AudioTagV2
- {
- return $this->id3v2;
- }
-
- public function quicktime(): ?Id3TagQuicktime
- {
- return $this->quicktime;
- }
-
- public function asf(): ?Id3TagAsf
- {
- return $this->asf;
- }
-
- public function vorbiscomment(): ?Id3TagVorbisComment
- {
- return $this->vorbiscomment;
- }
-
- public function riff(): ?Id3TagRiff
- {
- return $this->riff;
- }
-
- public function matroska(): ?Id3TagMatroska
- {
- return $this->matroska;
- }
-
- public function ape(): ?Id3TagApe
- {
- return $this->ape;
- }
-}
-
-class Id3AudioTagV1
-{
- public function __construct(
- protected ?string $title = null,
- protected ?string $artist = null,
- protected ?string $album = null,
- protected ?string $year = null,
- protected ?string $genre = null,
- protected ?string $comment = null,
- protected ?string $track_number = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- title: $metadata['title'] ?? null,
- artist: $metadata['artist'] ?? null,
- album: $metadata['album'] ?? null,
- year: $metadata['year'] ?? null,
- genre: $metadata['genre'] ?? null,
- comment: $metadata['comment'] ?? null,
- track_number: $metadata['track_number'] ?? null,
- );
-
- return $self;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function album(): ?string
- {
- return $this->album;
- }
-
- public function year(): ?string
- {
- return $this->year;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function comment(): ?string
- {
- return $this->comment;
- }
-
- public function track_number(): ?string
- {
- return $this->track_number;
- }
-
- public function toArray(): array
- {
- return [
- 'title' => $this->title,
- 'artist' => $this->artist,
- 'album' => $this->album,
- 'year' => $this->year,
- 'genre' => $this->genre,
- 'comment' => $this->comment,
- 'track_number' => $this->track_number,
- ];
- }
-}
-
-class Id3AudioTagV2
-{
- public function __construct(
- protected ?string $album = null,
- protected ?string $artist = null,
- protected ?string $band = null,
- protected ?string $comment = null,
- protected ?string $composer = null,
- protected ?string $part_of_a_set = null,
- protected ?string $genre = null,
- protected ?string $part_of_a_compilation = null,
- protected ?string $title = null,
- protected ?string $track_number = null,
- protected ?string $year = null,
- protected ?string $copyright = null,
- protected ?string $text = null,
- protected ?string $unsynchronised_lyric = null,
- protected ?string $language = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- album: $metadata['album'] ?? null,
- artist: $metadata['artist'] ?? null,
- band: $metadata['band'] ?? null,
- comment: $metadata['comment'] ?? null,
- composer: $metadata['composer'] ?? null,
- part_of_a_set: $metadata['part_of_a_set'] ?? null,
- genre: $metadata['genre'] ?? null,
- part_of_a_compilation: $metadata['part_of_a_compilation'] ?? null,
- title: $metadata['title'] ?? null,
- track_number: $metadata['track_number'] ?? null,
- year: $metadata['year'] ?? null,
- copyright: $metadata['copyright_message'] ?? null,
- text: $metadata['text'] ?? null,
- unsynchronised_lyric: $metadata['unsynchronised_lyric'] ?? null,
- language: $metadata['language'] ?? null,
- );
-
- return $self;
- }
-
- public function album(): ?string
- {
- return $this->album;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function band(): ?string
- {
- return $this->band;
- }
-
- public function comment(): ?string
- {
- return $this->comment;
- }
-
- public function composer(): ?string
- {
- return $this->composer;
- }
-
- public function part_of_a_set(): ?string
- {
- return $this->part_of_a_set;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function part_of_a_compilation(): ?string
- {
- return $this->part_of_a_compilation;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function track_number(): ?string
- {
- return $this->track_number;
- }
-
- public function year(): ?string
- {
- return $this->year;
- }
-
- public function copyright(): ?string
- {
- return $this->copyright;
- }
-
- public function text(): ?string
- {
- return $this->text;
- }
-
- public function unsynchronised_lyric(): ?string
- {
- return $this->unsynchronised_lyric;
- }
-
- public function language(): ?string
- {
- return $this->language;
- }
-
- public function toArray(): array
- {
- return [
- 'album' => $this->album,
- 'artist' => $this->artist,
- 'band' => $this->band,
- 'comment' => $this->comment,
- 'composer' => $this->composer,
- 'part_of_a_set' => $this->part_of_a_set,
- 'genre' => $this->genre,
- 'part_of_a_compilation' => $this->part_of_a_compilation,
- 'title' => $this->title,
- 'track_number' => $this->track_number,
- 'year' => $this->year,
- 'copyright' => $this->copyright,
- 'text' => $this->text,
- 'unsynchronised_lyric' => $this->unsynchronised_lyric,
- 'language' => $this->language,
- ];
- }
-}
-
-class Id3Comments
-{
- protected function __construct(
- protected ?string $language = null,
- protected ?Id3CommentsPicture $picture = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $language = $metadata['language'][0] ?? null;
- $picture = Id3CommentsPicture::make($metadata['picture'][0] ?? null);
-
- $self = new self(
- language: $language,
- picture: $picture,
- );
-
- return $self;
- }
-
- public function picture(): ?Id3CommentsPicture
- {
- return $this->picture;
- }
-}
-
-class Id3CommentsPicture
-{
- protected function __construct(
- protected ?string $data = null,
- protected ?string $image_mime = null,
- protected ?int $image_width = null,
- protected ?int $image_height = null,
- protected ?string $picturetype = null,
- protected ?string $description = null,
- protected ?int $datalength = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- data: $metadata['data'] ?? null,
- image_mime: $metadata['image_mime'] ?? null,
- image_width: $metadata['image_width'] ?? null,
- image_height: $metadata['image_height'] ?? null,
- picturetype: $metadata['picturetype'] ?? null,
- description: $metadata['description'] ?? null,
- datalength: $metadata['datalength'] ?? null,
- );
-
- return $self;
- }
-
- public function data(): ?string
- {
- return $this->data;
- }
-
- public function image_mime(): ?string
- {
- return $this->image_mime;
- }
-
- public function image_width(): ?int
- {
- return $this->image_width;
- }
-
- public function image_height(): ?int
- {
- return $this->image_height;
- }
-
- public function picturetype(): ?string
- {
- return $this->picturetype;
- }
-
- public function description(): ?string
- {
- return $this->description;
- }
-
- public function datalength(): ?int
- {
- return $this->datalength;
- }
-}
-
-class Id3TagQuicktime
-{
- public function __construct(
- protected ?string $title = null,
- protected ?string $track_number = null,
- protected ?string $disc_number = null,
- protected ?string $compilation = null,
- protected ?string $album = null,
- protected ?string $genre = null,
- protected ?string $composer = null,
- protected ?string $creation_date = null,
- protected ?string $copyright = null,
- protected ?string $artist = null,
- protected ?string $album_artist = null,
- protected ?string $encoded_by = null,
- protected ?string $encoding_tool = null,
- protected ?string $description = null,
- protected ?string $description_long = null,
- protected ?string $language = null,
- protected ?string $lyrics = null,
- protected ?string $comment = null,
- protected ?string $stik = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- title: $metadata['title'] ?? null,
- track_number: $metadata['track_number'] ?? null,
- disc_number: $metadata['disc_number'] ?? null,
- compilation: $metadata['compilation'] ?? null,
- album: $metadata['album'] ?? null,
- genre: $metadata['genre'] ?? null,
- composer: $metadata['composer'] ?? null,
- creation_date: $metadata['creation_date'] ?? null,
- copyright: $metadata['copyright'] ?? null,
- artist: $metadata['artist'] ?? null,
- album_artist: $metadata['album_artist'] ?? null,
- encoded_by: $metadata['encoded_by'] ?? null,
- encoding_tool: $metadata['encoding_tool'] ?? null,
- description: $metadata['description'] ?? null,
- description_long: $metadata['description_long'] ?? null,
- language: $metadata['language'] ?? null,
- lyrics: $metadata['lyrics'] ?? null,
- comment: $metadata['comment'] ?? null,
- stik: $metadata['stik'] ?? null,
- );
-
- return $self;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function track_number(): ?string
- {
- return $this->track_number;
- }
-
- public function disc_number(): ?string
- {
- return $this->disc_number;
- }
-
- public function compilation(): ?string
- {
- return $this->compilation;
- }
-
- public function album(): ?string
- {
- return $this->album;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function composer(): ?string
- {
- return $this->composer;
- }
-
- public function creation_date(): ?string
- {
- return $this->creation_date;
- }
-
- public function copyright(): ?string
- {
- return $this->copyright;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function album_artist(): ?string
- {
- return $this->album_artist;
- }
-
- public function encoded_by(): ?string
- {
- return $this->encoded_by;
- }
-
- public function encoding_tool(): ?string
- {
- return $this->encoding_tool;
- }
-
- public function description(): ?string
- {
- return $this->description;
- }
-
- public function description_long(): ?string
- {
- return $this->description_long;
- }
-
- public function language(): ?string
- {
- return $this->language;
- }
-
- public function lyrics(): ?string
- {
- return $this->lyrics;
- }
-
- public function comment(): ?string
- {
- return $this->comment;
- }
-
- public function stik(): ?string
- {
- return $this->stik;
- }
-
- public function toArray(): array
- {
- return [
- 'title' => $this->title,
- 'track_number' => $this->track_number,
- 'disc_number' => $this->disc_number,
- 'compilation' => $this->compilation,
- 'album' => $this->album,
- 'genre' => $this->genre,
- 'composer' => $this->composer,
- 'creation_date' => $this->creation_date,
- 'copyright' => $this->copyright,
- 'artist' => $this->artist,
- 'album_artist' => $this->album_artist,
- 'encoded_by' => $this->encoded_by,
- 'encoding_tool' => $this->encoding_tool,
- 'description' => $this->description,
- 'description_long' => $this->description_long,
- 'language' => $this->language,
- 'lyrics' => $this->lyrics,
- 'comment' => $this->comment,
- 'stik' => $this->stik,
- ];
- }
-}
-
-class Id3TagAsf
-{
- public function __construct(
- protected ?string $title = null,
- protected ?string $artist = null,
- protected ?string $album = null,
- protected ?string $albumartist = null,
- protected ?string $composer = null,
- protected ?string $partofset = null,
- protected ?string $genre = null,
- protected ?string $track_number = null,
- protected ?string $year = null,
- protected ?string $encodingsettings = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
- $self = new self(
- title: $metadata['title'] ?? null,
- artist: $metadata['artist'] ?? null,
- album: $metadata['album'] ?? null,
- albumartist: $metadata['albumartist'] ?? null,
- composer: $metadata['composer'] ?? null,
- partofset: $metadata['partofset'] ?? null,
- genre: $metadata['genre'] ?? null,
- track_number: $metadata['track_number'] ?? null,
- year: $metadata['year'] ?? null,
- encodingsettings: $metadata['encodingsettings'] ?? null,
- );
-
- return $self;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function album(): ?string
- {
- return $this->album;
- }
-
- public function albumartist(): ?string
- {
- return $this->albumartist;
- }
-
- public function composer(): ?string
- {
- return $this->composer;
- }
-
- public function partofset(): ?string
- {
- return $this->partofset;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function track_number(): ?string
- {
- return $this->track_number;
- }
-
- public function year(): ?string
- {
- return $this->year;
- }
-
- public function encodingsettings(): ?string
- {
- return $this->encodingsettings;
- }
-
- public function toArray(): array
- {
- return [
- 'title' => $this->title,
- 'artist' => $this->artist,
- 'album' => $this->album,
- 'albumartist' => $this->albumartist,
- 'composer' => $this->composer,
- 'partofset' => $this->partofset,
- 'genre' => $this->genre,
- 'track_number' => $this->track_number,
- 'year' => $this->year,
- 'encodingsettings' => $this->encodingsettings,
- ];
- }
-}
-
-class Id3TagVorbisComment
-{
- public function __construct(
- protected ?string $description = null,
- protected ?string $encoder = null,
- protected ?string $title = null,
- protected ?string $artist = null,
- protected ?string $album = null,
- protected ?string $genre = null,
- protected ?string $comment = null,
- protected ?string $albumartist = null,
- protected ?string $composer = null,
- protected ?string $discnumber = null,
- protected ?string $compilation = null,
- protected ?string $date = null,
- protected ?string $tracknumber = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
- $self = new self(
- description: $metadata['description'] ?? null,
- encoder: $metadata['encoder'] ?? null,
- title: $metadata['title'] ?? null,
- artist: $metadata['artist'] ?? null,
- album: $metadata['album'] ?? null,
- genre: $metadata['genre'] ?? null,
- comment: $metadata['comment'] ?? null,
- albumartist: $metadata['albumartist'] ?? null,
- composer: $metadata['composer'] ?? null,
- discnumber: $metadata['discnumber'] ?? null,
- compilation: $metadata['compilation'] ?? null,
- date: $metadata['date'] ?? null,
- tracknumber: $metadata['tracknumber'] ?? null,
- );
-
- return $self;
- }
-
- public function description(): ?string
- {
- return $this->description;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function album(): ?string
- {
- return $this->album;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function comment(): ?string
- {
- return $this->comment;
- }
-
- public function albumartist(): ?string
- {
- return $this->albumartist;
- }
-
- public function composer(): ?string
- {
- return $this->composer;
- }
-
- public function discnumber(): ?string
- {
- return $this->discnumber;
- }
-
- public function compilation(): ?string
- {
- return $this->compilation;
- }
-
- public function date(): ?string
- {
- return $this->date;
- }
-
- public function tracknumber(): ?string
- {
- return $this->tracknumber;
- }
-
- public function encoder(): ?string
- {
- return $this->encoder;
- }
-
- public function toArray(): array
- {
- return [
- 'description' => $this->description,
- 'encoder' => $this->encoder,
- 'title' => $this->title,
- 'artist' => $this->artist,
- 'album' => $this->album,
- 'genre' => $this->genre,
- 'comment' => $this->comment,
- 'albumartist' => $this->albumartist,
- 'composer' => $this->composer,
- 'discnumber' => $this->discnumber,
- 'compilation' => $this->compilation,
- 'date' => $this->date,
- 'tracknumber' => $this->tracknumber,
- ];
- }
-}
-
-class Id3TagRiff
-{
- public function __construct(
- protected ?string $artist = null,
- protected ?string $comment = null,
- protected ?string $creationdate = null,
- protected ?string $genre = null,
- protected ?string $title = null,
- protected ?string $product = null,
- protected ?string $software = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
- $self = new self(
- artist: $metadata['artist'] ?? null,
- comment: $metadata['comment'] ?? null,
- creationdate: $metadata['creationdate'] ?? null,
- genre: $metadata['genre'] ?? null,
- title: $metadata['title'] ?? null,
- product: $metadata['product'] ?? null,
- software: $metadata['software'] ?? null,
- );
-
- return $self;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function comment(): ?string
- {
- return $this->comment;
- }
-
- public function creationdate(): ?string
- {
- return $this->creationdate;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function product(): ?string
- {
- return $this->product;
- }
-
- public function software(): ?string
- {
- return $this->software;
- }
-
- public function toArray(): array
- {
- return [
- 'artist' => $this->artist,
- 'comment' => $this->comment,
- 'creationdate' => $this->creationdate,
- 'genre' => $this->genre,
- 'title' => $this->title,
- 'product' => $this->product,
- 'software' => $this->software,
- ];
- }
-}
-
-class Id3TagMatroska
-{
- public function __construct(
- protected ?string $title = null,
- protected ?string $muxingapp = null,
- protected ?string $writingapp = null,
- protected ?string $album = null,
- protected ?string $artist = null,
- protected ?string $album_artist = null,
- protected ?string $comment = null,
- protected ?string $composer = null,
- protected ?string $disc = null,
- protected ?string $genre = null,
- protected ?string $compilation = null,
- protected ?string $part_number = null,
- protected ?string $date = null,
- protected ?string $encoder = null,
- protected ?string $duration = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- title: $metadata['title'] ?? null,
- muxingapp: $metadata['muxingapp'] ?? null,
- writingapp: $metadata['writingapp'] ?? null,
- album: $metadata['album'] ?? null,
- artist: $metadata['artist'] ?? null,
- album_artist: $metadata['album_artist'] ?? null,
- comment: $metadata['comment'] ?? null,
- composer: $metadata['composer'] ?? null,
- disc: $metadata['disc'] ?? null,
- genre: $metadata['genre'] ?? null,
- compilation: $metadata['compilation'] ?? null,
- part_number: $metadata['part_number'] ?? null,
- date: $metadata['date'] ?? null,
- encoder: $metadata['encoder'] ?? null,
- duration: $metadata['duration'] ?? null,
- );
-
- return $self;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function muxingapp(): ?string
- {
- return $this->muxingapp;
- }
-
- public function writingapp(): ?string
- {
- return $this->writingapp;
- }
-
- public function album(): ?string
- {
- return $this->album;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function album_artist(): ?string
- {
- return $this->album_artist;
- }
-
- public function comment(): ?string
- {
- return $this->comment;
- }
-
- public function composer(): ?string
- {
- return $this->composer;
- }
-
- public function disc(): ?string
- {
- return $this->disc;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function compilation(): ?string
- {
- return $this->compilation;
- }
-
- public function part_number(): ?string
- {
- return $this->part_number;
- }
-
- public function date(): ?string
- {
- return $this->date;
- }
-
- public function encoder(): ?string
- {
- return $this->encoder;
- }
-
- public function duration(): ?string
- {
- return $this->duration;
- }
-
- public function toArray(): array
- {
- return [
- 'title' => $this->title,
- 'muxingapp' => $this->muxingapp,
- 'writingapp' => $this->writingapp,
- 'album' => $this->album,
- 'artist' => $this->artist,
- 'album_artist' => $this->album_artist,
- 'comment' => $this->comment,
- 'composer' => $this->composer,
- 'disc' => $this->disc,
- 'genre' => $this->genre,
- 'compilation' => $this->compilation,
- 'part_number' => $this->part_number,
- 'date' => $this->date,
- 'encoder' => $this->encoder,
- 'duration' => $this->duration,
- ];
- }
-}
-
-class Id3TagApe
-{
- public function __construct(
- protected ?string $title = null,
- protected ?string $artist = null,
- protected ?string $album = null,
- protected ?string $album_artist = null,
- protected ?string $composer = null,
- protected ?string $comment = null,
- protected ?string $genre = null,
- protected ?string $disc = null,
- protected ?string $compilation = null,
- protected ?string $track = null,
- protected ?string $date = null,
- protected ?string $encoder = null,
- protected ?string $description = null,
- protected ?string $copyright = null,
- protected ?string $lyrics = null,
- protected ?string $podcastdesc = null,
- protected ?string $language = null,
- protected ?string $year = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $self = new self(
- title: $metadata['title'] ?? null,
- artist: $metadata['artist'] ?? null,
- album: $metadata['album'] ?? null,
- album_artist: $metadata['album_artist'] ?? $metadata['albumartist'] ?? null,
- composer: $metadata['composer'] ?? null,
- comment: $metadata['comment'] ?? null,
- genre: $metadata['genre'] ?? null,
- disc: $metadata['disc'] ?? $metadata['discnumber'] ?? null,
- compilation: $metadata['compilation'] ?? null,
- track: $metadata['track'] ?? null,
- date: $metadata['date'] ?? null,
- encoder: $metadata['encoder'] ?? null,
- description: $metadata['description'] ?? null,
- copyright: $metadata['copyright'] ?? null,
- lyrics: $metadata['unsyncedlyrics'] ?? null,
- podcastdesc: $metadata['podcastdesc'] ?? null,
- language: $metadata['language'] ?? null,
- year: $metadata['year'] ?? null,
- );
-
- return $self;
- }
-
- public function title(): ?string
- {
- return $this->title;
- }
-
- public function artist(): ?string
- {
- return $this->artist;
- }
-
- public function album(): ?string
- {
- return $this->album;
- }
-
- public function album_artist(): ?string
- {
- return $this->album_artist;
- }
-
- public function composer(): ?string
- {
- return $this->composer;
- }
-
- public function comment(): ?string
- {
- return $this->comment;
- }
-
- public function genre(): ?string
- {
- return $this->genre;
- }
-
- public function disc(): ?string
- {
- return $this->disc;
- }
-
- public function compilation(): ?string
- {
- return $this->compilation;
- }
-
- public function track(): ?string
- {
- return $this->track;
- }
-
- public function date(): ?string
- {
- return $this->date;
- }
-
- public function encoder(): ?string
- {
- return $this->encoder;
- }
-
- public function description(): ?string
- {
- return $this->description;
- }
-
- public function copyright(): ?string
- {
- return $this->copyright;
- }
-
- public function lyrics(): ?string
- {
- return $this->lyrics;
- }
-
- public function podcastdesc(): ?string
- {
- return $this->podcastdesc;
- }
-
- public function language(): ?string
- {
- return $this->language;
- }
-
- public function year(): ?string
- {
- return $this->year;
- }
-
- public function toArray(): array
- {
- return [
- 'title' => $this->title,
- 'artist' => $this->artist,
- 'album' => $this->album,
- 'album_artist' => $this->album_artist,
- 'composer' => $this->composer,
- 'comment' => $this->comment,
- 'genre' => $this->genre,
- 'disc' => $this->disc,
- 'compilation' => $this->compilation,
- 'track' => $this->track,
- 'date' => $this->date,
- 'encoder' => $this->encoder,
- 'description' => $this->description,
- 'copyright' => $this->copyright,
- 'lyrics' => $this->lyrics,
- 'podcastdesc' => $this->podcastdesc,
- 'language' => $this->language,
- 'year' => $this->year,
- ];
- }
-}
-
-class Id3TagsHtml
-{
- protected function __construct(
- protected ?Id3AudioTagV1 $id3v1 = null,
- protected ?Id3AudioTagV2 $id3v2 = null,
- protected ?Id3TagQuicktime $quicktime = null,
- protected ?Id3TagAsf $asf = null,
- protected ?Id3TagVorbisComment $vorbiscomment = null,
- protected ?Id3TagRiff $riff = null,
- protected ?Id3TagMatroska $matroska = null,
- protected ?Id3TagApe $ape = null,
- ) {}
-
- public static function make(?array $metadata): ?self
- {
- if (! $metadata) {
- return null;
- }
-
- $id3v1 = Id3Reader::cleanTags($metadata['id3v1'] ?? null);
- $id3v2 = Id3Reader::cleanTags($metadata['id3v2'] ?? null);
- $quicktime = Id3Reader::cleanTags($metadata['quicktime'] ?? null);
- $asf = Id3Reader::cleanTags($metadata['asf'] ?? null);
- $vorbiscomment = Id3Reader::cleanTags($metadata['vorbiscomment'] ?? null);
- $riff = Id3Reader::cleanTags($metadata['riff'] ?? null);
- $matroska = Id3Reader::cleanTags($metadata['matroska'] ?? null);
- $ape = Id3Reader::cleanTags($metadata['ape'] ?? null);
-
- $self = new self(
- id3v1: Id3AudioTagV1::make($id3v1),
- id3v2: Id3AudioTagV2::make($id3v2),
- quicktime: Id3TagQuicktime::make($quicktime),
- asf: Id3TagAsf::make($asf),
- vorbiscomment: Id3TagVorbisComment::make($vorbiscomment),
- riff: Id3TagRiff::make($riff),
- matroska: Id3TagMatroska::make($matroska),
- ape: Id3TagApe::make($ape),
- );
-
- return $self;
- }
-
- public function id3v1(): ?Id3AudioTagV1
- {
- return $this->id3v1;
- }
-
- public function id3v2(): ?Id3AudioTagV2
- {
- return $this->id3v2;
- }
-
- public function quicktime(): ?Id3TagQuicktime
- {
- return $this->quicktime;
- }
-
- public function asf(): ?Id3TagAsf
- {
- return $this->asf;
- }
-
- public function vorbiscomment(): ?Id3TagVorbisComment
- {
- return $this->vorbiscomment;
- }
-
- public function riff(): ?Id3TagRiff
- {
- return $this->riff;
- }
-
- public function matroska(): ?Id3TagMatroska
- {
- return $this->matroska;
- }
-
- public function ape(): ?Id3TagApe
- {
- return $this->ape;
- }
-}
diff --git a/src/Models/Id3Writer.php b/src/Models/Id3Writer.php
deleted file mode 100644
index d403cdb..0000000
--- a/src/Models/Id3Writer.php
+++ /dev/null
@@ -1,505 +0,0 @@
-
- */
- protected array $options = [
- 'encoding' => 'UTF-8',
- ];
-
- /**
- * @var array
- */
- protected array $tags = [];
-
- /**
- * @var string[]
- */
- protected array $warnings = [];
-
- /**
- * @var string[]
- */
- protected array $errors = [];
-
- protected bool $overrideTags = true;
-
- protected bool $removeOldTags = false;
-
- protected bool $failOnError = true;
-
- /**
- * @var string[]
- */
- protected array $tagFormats = [];
-
- protected ?string $path = null;
-
- protected bool $success = false;
-
- protected function __construct(
- protected Audio $audio,
- protected getid3_writetags $instance,
- protected AudioCore $core,
- ) {}
-
- public static function make(Audio $audio): self
- {
- $self = new self(
- audio: $audio,
- instance: new getid3_writetags,
- core: new AudioCore
- );
-
- return $self;
- }
-
- public function getCore(): AudioCore
- {
- return $this->core;
- }
-
- public function title(?string $title): self
- {
- $this->core->setTitle($title);
-
- return $this;
- }
-
- public function artist(?string $artist): self
- {
- $this->core->setArtist($artist);
-
- return $this;
- }
-
- public function album(?string $album): self
- {
- $this->core->setAlbum($album);
-
- return $this;
- }
-
- public function genre(?string $genre): self
- {
- $this->core->setGenre($genre);
-
- return $this;
- }
-
- public function year(int $year): self
- {
- $this->core->setYear($year);
-
- return $this;
- }
-
- public function trackNumber(string|int|null $trackNumber): self
- {
- if (is_int($trackNumber)) {
- $trackNumber = (string) $trackNumber;
- }
-
- $this->core->setTrackNumber($trackNumber);
-
- return $this;
- }
-
- public function comment(?string $comment): self
- {
- $this->core->setComment($comment);
-
- return $this;
- }
-
- public function albumArtist(?string $albumArtist): self
- {
- $this->core->setAlbumArtist($albumArtist);
-
- return $this;
- }
-
- public function composer(?string $composer): self
- {
- $this->core->setComposer($composer);
-
- return $this;
- }
-
- public function discNumber(string|int|null $discNumber): self
- {
- if (is_int($discNumber)) {
- $discNumber = (string) $discNumber;
- }
-
- $this->core->setDiscNumber($discNumber);
-
- return $this;
- }
-
- public function isCompilation(): self
- {
- $this->core->setIsCompilation(true);
-
- return $this;
- }
-
- public function isNotCompilation(): self
- {
- $this->core->setIsCompilation(false);
-
- return $this;
- }
-
- public function creationDate(string|DateTime|null $creationDate): self
- {
- if ($creationDate instanceof DateTime) {
- $creationDate = $creationDate->format('Y-m-d');
- }
-
- $this->core->setCreationDate($creationDate);
-
- return $this;
- }
-
- public function copyright(?string $copyright): self
- {
- $this->core->setCopyright($copyright);
-
- return $this;
- }
-
- public function encodingBy(?string $encodingBy): self
- {
- $this->core->setEncodingBy($encodingBy);
-
- return $this;
- }
-
- public function encoding(?string $encoding): self
- {
- $this->core->setEncoding($encoding);
-
- return $this;
- }
-
- public function description(?string $description): self
- {
- $this->core->setDescription($description);
-
- return $this;
- }
-
- public function podcastDescription(?string $podcastDescription): self
- {
- $this->core->setPodcastDescription($podcastDescription);
-
- return $this;
- }
-
- public function language(?string $language): self
- {
- $this->core->setLanguage($language);
-
- return $this;
- }
-
- public function lyrics(?string $lyrics): self
- {
- $this->core->setLyrics($lyrics);
-
- return $this;
- }
-
- public function stik(?string $stik): self
- {
- $this->core->setStik($stik);
-
- return $this;
- }
-
- /**
- * @param string $pathOrData Path to cover image or binary data
- */
- public function cover(string $pathOrData): self
- {
- $this->core->setCover($pathOrData);
-
- return $this;
- }
-
- public function options(array $options = ['encoding' => 'UTF-8']): self
- {
- $this->options = $options;
-
- return $this;
- }
-
- public function path(string $path): self
- {
- $this->path = $path;
-
- if (file_exists($this->audio->getPath())) {
- copy($this->audio->getPath(), $this->path);
- }
-
- return $this;
- }
-
- /**
- * Prevent fail on error.
- */
- public function preventFailOnError(): self
- {
- $this->failOnError = false;
-
- return $this;
- }
-
- /**
- * Override existing tags, default is true.
- */
- // public function notOverrideTags(): self
- // {
- // $this->overrideTags = false;
-
- // return $this;
- // }
-
- /**
- * Remove other tags, default is false.
- */
- public function removeOldTags(): self
- {
- $this->removeOldTags = true;
-
- return $this;
- }
-
- /**
- * Set manually tags.
- *
- * @param array $tags
- */
- public function tags(array $tags): self
- {
- $this->tags = $this->convertTags($tags);
-
- return $this;
- }
-
- /**
- * Set tag format.
- *
- * @param string[] $tags Options are `id3v1`, `id3v2.2`, `id2v2.3`, `id3v2.4`, `ape`, `vorbiscomment`, `metaflac`, `real`
- */
- public function tagFormats(array $tags): self
- {
- $this->tagFormats = $tags;
-
- return $this;
- }
-
- /**
- * Save tags.
- *
- * @throws \Exception
- */
- public function save(): bool
- {
- if (! $this->path) {
- $this->path = $this->audio->getPath();
- }
-
- $this->instance->filename = $this->path;
-
- $this->convertTagFormats();
- $this->automaticConvert();
-
- $this->instance->overwrite_tags = $this->overrideTags;
- $this->instance->remove_other_tags = $this->removeOldTags;
- $this->instance->tagformats = $this->tagFormats;
- $this->instance->tag_data = $this->tags;
-
- $this->success = $this->instance->WriteTags();
-
- $this->errors = $this->instance->errors;
- $this->warnings = $this->instance->warnings;
-
- $errors = implode(', ', $this->errors);
- $warnings = implode(', ', $this->warnings);
- $supported = match ($this->audio->getFormat()) {
- AudioFormatEnum::flac => true,
- AudioFormatEnum::mp3 => true,
- AudioFormatEnum::ogg => true,
- default => false
- };
-
- if (! empty($this->errors)) {
- $msg = 'Save tags failed.';
-
- $errors = strip_tags($errors);
- $errors = "Errors: {$errors}.";
- if (! empty($this->errors)) {
- $msg .= " {$errors}";
- }
-
- $warnings = "Warnings: {$warnings}.";
- if (! empty($this->warnings)) {
- $msg .= " {$warnings}";
- }
-
- $isSuccess = $this->success ? 'true' : 'false';
- $success = "Success: {$isSuccess}";
- $msg .= " {$success}";
-
- error_log($msg);
-
- if ($this->failOnError) {
- throw new \Exception($msg);
- }
- }
-
- if (! $supported && $this->failOnError) {
- throw new \Exception("Format {$this->audio->getFormat()?->value} is not supported.");
- }
-
- if (! empty($this->warnings)) {
- error_log($warnings);
- }
-
- return $this->success;
- }
-
- private function automaticConvert(): self
- {
- $this->convertTagFormats();
-
- $convert = match ($this->audio->getType()) {
- AudioTypeEnum::id3 => AudioCore::toId3v2($this->core),
- AudioTypeEnum::vorbiscomment => AudioCore::toVorbisComment($this->core),
- AudioTypeEnum::quicktime => AudioCore::toQuicktime($this->core),
- AudioTypeEnum::matroska => AudioCore::toMatroska($this->core),
- AudioTypeEnum::ape => AudioCore::toApe($this->core),
- AudioTypeEnum::asf => AudioCore::toAsf($this->core),
- default => null,
- };
-
- $tags = [];
- if ($convert) {
- $tags = $convert->toArray();
- }
-
- $tags = $this->convertTags($tags);
- $this->attachCover($tags);
-
- $this->tags = [
- ...$this->tags,
- ...$tags,
- ];
-
- return $this;
- }
-
- /**
- * @param array $tags
- * @return array
- */
- private function convertTags(array $tags): array
- {
- $attached = $tags['attached_picture'] ?? null;
- $items = [];
- if (! empty($tags)) {
- foreach ($tags as $key => $tag) {
- if ($tag && gettype($tag) === 'string') {
- $items[$key] = [$tag];
- }
- }
- }
-
- if ($attached) {
- $items['attached_picture'] = $attached;
- }
-
- return $items;
- }
-
- /**
- * - ID3v1 (v1 & v1.1)
- * - ID3v2 (v2.3, v2.4)
- * - APE (v2)
- * - Ogg Vorbis comments (need `vorbis-tools`)
- * - FLAC comments
- *
- * Options: `id3v1`, `id3v2.2`, `id2v2.3`, `id3v2.4`, `ape`, `vorbiscomment`, `metaflac`, `real`
- */
- private function convertTagFormats(): self
- {
- if (! empty($this->tagFormats)) {
- return $this;
- }
-
- $formats = match ($this->audio->getFormat()) {
- AudioFormatEnum::aac => [],
- AudioFormatEnum::aif => [],
- AudioFormatEnum::aifc => [],
- AudioFormatEnum::aiff => [],
- AudioFormatEnum::dsf => [],
- AudioFormatEnum::flac => ['metaflac'],
- AudioFormatEnum::m4a => [],
- AudioFormatEnum::m4b => [],
- AudioFormatEnum::m4v => [],
- AudioFormatEnum::mpc => [],
- AudioFormatEnum::mka => [],
- AudioFormatEnum::mkv => [],
- AudioFormatEnum::ape => [],
- AudioFormatEnum::mp3 => ['id3v1', 'id3v2.4'],
- AudioFormatEnum::mp4 => [],
- AudioFormatEnum::ogg => ['vorbiscomment'],
- AudioFormatEnum::opus => [],
- AudioFormatEnum::ofr => [],
- AudioFormatEnum::ofs => [],
- AudioFormatEnum::spx => [],
- AudioFormatEnum::tak => [],
- AudioFormatEnum::tta => [],
- AudioFormatEnum::wav => [],
- AudioFormatEnum::webm => [],
- AudioFormatEnum::wma => [],
- AudioFormatEnum::wv => [],
- default => null,
- };
- $this->tagFormats = $formats;
-
- return $this;
- }
-
- private function attachCover(array &$tags): void
- {
- $coverFormatsAllowed = [AudioFormatEnum::mp3];
- if ($this->core->getCover() && in_array($this->audio->getFormat(), $coverFormatsAllowed)) {
- // $tags = [
- // ...$tags,
- // 'CTOC' => $old_tags['id3v2']['CTOC'],
- // 'CHAP' => $old_tags['id3v2']['CHAP'],
- // 'chapters' => $old_tags['id3v2']['chapters'],
- // ];
- $tags['attached_picture'][0] = [
- 'data' => base64_decode($this->core->getCover()->data()),
- 'picturetypeid' => $this->core->getCover()->picturetypeid(),
- 'description' => $this->core->getCover()->description(),
- 'mime' => $this->core->getCover()->mime(),
- ];
- $this->core->setHasCover(true);
- }
- }
-}
diff --git a/test.php b/test.php
deleted file mode 100644
index a645ff2..0000000
--- a/test.php
+++ /dev/null
@@ -1 +0,0 @@
-1.9.23-202310190849366480/Users/ewilan/Workspace/php-audio/tests/mediaaudiobook.mp3/Users/ewilan/Workspace/php-audio/tests/media/audiobook.mp395396272609mp3ArrayArrayArrayUTF-8ArrayArrayArrayaudio/mpegArray11.0496875Array1280000:11
\ No newline at end of file
diff --git a/tests/ArchTest.php b/tests/ArchTest.php
new file mode 100644
index 0000000..fd6a1e4
--- /dev/null
+++ b/tests/ArchTest.php
@@ -0,0 +1,5 @@
+expect(['dd', 'dump', 'ray'])
+ ->not->toBeUsed();
diff --git a/tests/AudioCoreTest.php b/tests/AudioCoreTest.php
index f146f9e..9fab03a 100644
--- a/tests/AudioCoreTest.php
+++ b/tests/AudioCoreTest.php
@@ -1,43 +1,42 @@
getTitle(),
artist: $audio->getArtist(),
album: $audio->getAlbum(),
genre: $audio->getGenre(),
year: $audio->getYear(),
- trackNumber: $audio->getTrackNumber(),
+ track_number: $audio->getTrackNumber(),
comment: $audio->getComment(),
- albumArtist: $audio->getAlbumArtist(),
+ album_artist: $audio->getAlbumArtist(),
composer: $audio->getComposer(),
- discNumber: $audio->getDiscNumber(),
- isCompilation: $audio->isCompilation(),
- creationDate: $audio->getCreationDate(),
+ disc_number: $audio->getDiscNumber(),
+ is_compilation: $audio->isCompilation(),
+ creation_date: $audio->getCreationDate(),
copyright: $audio->getCopyright(),
- encodingBy: $audio->getEncodingBy(),
+ encoding_by: $audio->getEncodingBy(),
encoding: $audio->getEncoding(),
description: $audio->getDescription(),
lyrics: $audio->getLyrics(),
- stik: $audio->getStik(),
);
- expect($core->getTitle())->toBe('Introduction');
- expect($core->getArtist())->toBe('Mr Piouf');
- expect($core->getAlbum())->toBe('P1PDD Le conclave de Troie');
- expect($core->getGenre())->toBe('Roleplaying game');
- expect($core->getYear())->toBe(2016);
- expect($core->getTrackNumber())->toBe('1');
- expect($core->getComment())->toBe('http://www.p1pdd.com');
- expect($core->getAlbumArtist())->toBe('P1PDD & Mr Piouf');
- expect($core->getComposer())->toBe('P1PDD & Piouf');
- expect($core->getDiscNumber())->toBe('1');
- expect($core->isCompilation())->toBe(true);
+ expect($core->title)->toBe('Introduction');
+ expect($core->artist)->toBe('Mr Piouf');
+ expect($core->album)->toBe('P1PDD Le conclave de Troie');
+ expect($core->genre)->toBe('Roleplaying game');
+ expect($core->year)->toBe(2016);
+ expect($core->track_number)->toBe('1');
+ expect($core->comment)->toBe('http://www.p1pdd.com');
+ expect($core->album_artist)->toBe('P1PDD & Mr Piouf');
+ expect($core->composer)->toBe('P1PDD & Piouf');
+ expect($core->disc_number)->toBe('1');
+ expect($core->is_compilation)->toBe(true);
$id3v1 = AudioCore::toId3v1($core);
$id3v2 = AudioCore::toId3v2($core);
@@ -52,5 +51,9 @@
$cover = AudioCoreCover::make(FOLDER);
expect($core->toArray())->toBeArray();
- expect($cover->toArray())->toBeArray();
+
+ expect($cover->data)->toBeString();
+ expect($cover->picture_type_id)->toBeInt();
+ expect($cover->description)->toBeString();
+ expect($cover->mime)->toBeString();
});
diff --git a/tests/AudioCoverTest.php b/tests/AudioCoverTest.php
new file mode 100644
index 0000000..3d7e375
--- /dev/null
+++ b/tests/AudioCoverTest.php
@@ -0,0 +1,45 @@
+getCover();
+
+ if ($audio->hasCover()) {
+ expect($cover)->toBeInstanceOf(AudioCover::class);
+ expect($cover->getContents())->toBeString();
+ expect($cover->getContents())->toBeString();
+ expect($cover->getMimeType())->toBeString();
+ if ($cover->getWidth()) {
+ expect($cover->getWidth())->toBeInt();
+ }
+ if ($cover->getHeight()) {
+ expect($cover->getHeight())->toBeInt();
+ }
+
+ $path = "tests/output/cover-{$ext}.jpg";
+ file_put_contents($path, $cover->getContents());
+ expect(file_exists($path))->toBeTrue();
+ expect($path)->toBeReadableFile();
+ } else {
+ expect($cover)->toBeNull();
+ }
+})->with([...AUDIO]);
+
+it('can read as array', function (string $path) {
+ $audio = Audio::read($path);
+ $cover = $audio->getCover();
+
+ if ($cover) {
+ expect($cover->toArray())->toBeArray();
+ } else {
+ expect($cover)->toBeNull();
+ }
+})->with([...AUDIO]);
diff --git a/tests/AudioMetadataTest.php b/tests/AudioMetadataTest.php
new file mode 100644
index 0000000..005c930
--- /dev/null
+++ b/tests/AudioMetadataTest.php
@@ -0,0 +1,142 @@
+getMetadata();
+ expect($metadata->getFileSize())->toBe(272737);
+ expect($metadata->getDataFormat())->toBe('mp3');
+ expect($metadata->getEncoding())->toBe('UTF-8');
+ expect($metadata->getMimeType())->toBe('audio/mpeg');
+ expect($metadata->getDurationSeconds())->toBe(11.0496875);
+ expect($metadata->getBitrate())->toBe(128000);
+ expect($metadata->getBitrateMode())->toBe('cbr');
+ expect($metadata->getSampleRate())->toBe(44100);
+ expect($metadata->getChannels())->toBe(2);
+ expect($metadata->getChannelMode())->toBe('joint stereo');
+ expect($metadata->isLossless())->toBeFalse();
+ expect($metadata->getCompressionRatio())->toBe(0.09070294784580499);
+ expect($metadata->getCodec())->toBe('LAME');
+ expect($metadata->getEncoderOptions())->toBe('CBR128');
+ expect($metadata->getVersion())->toContain('1.9');
+ expect($metadata->getAvDataOffset())->toBe(95396);
+ expect($metadata->getAvDataEnd())->toBe(272609);
+ expect($metadata->getFilePath())->toContain('tests/media');
+ expect($metadata->getFilename())->toBe('test.mp3');
+ expect($metadata->getLastAccessAt())->toBeInstanceOf(DateTime::class);
+ expect($metadata->getCreatedAt())->toBeInstanceOf(DateTime::class);
+ expect($metadata->getModifiedAt())->toBeInstanceOf(DateTime::class);
+});
+
+it('can read basic info', function (string $path) {
+ $audio = Audio::read($path);
+ $metadata = $audio->getMetadata();
+
+ expect($metadata)->toBeInstanceOf(AudioMetadata::class);
+ expect($metadata->getFileSize())->toBeInt();
+ expect($metadata->getSizeHuman())->toBeString();
+ expect($metadata->getMimeType())->toBeString();
+ expect($metadata->isLossless())->toBeBool();
+ expect($metadata->getLastAccessAt())->toBeInstanceOf(DateTime::class);
+ expect($metadata->getCreatedAt())->toBeInstanceOf(DateTime::class);
+ expect($metadata->getModifiedAt())->toBeInstanceOf(DateTime::class);
+ expect($metadata->getVersion())->toContain('1.9');
+ expect($metadata->getAvDataOffset())->toBeInt();
+ expect($metadata->getAvDataEnd())->toBeInt();
+ expect($metadata->getFilePath())->toBeString();
+ expect($metadata->getFilename())->toBeString();
+
+ if ($metadata->getChannels()) {
+ expect($metadata->getChannels())->toBeInt();
+ }
+ if ($metadata->getBitrate()) {
+ expect($metadata->getBitrate())->toBeInt();
+ }
+ if ($metadata->getChannelMode()) {
+ expect($metadata->getChannelMode())->toBeString();
+ }
+ if ($metadata->getDataFormat()) {
+ expect($metadata->getDataFormat())->toBeString();
+ }
+ if ($metadata->getEncoding()) {
+ expect($metadata->getEncoding())->toBeString();
+ }
+ if ($metadata->getDurationSeconds()) {
+ expect($metadata->getDurationSeconds())->toBeFloat();
+ expect($metadata->getDurationSeconds(2))->toBeFloat();
+ }
+ if ($metadata->getSampleRate()) {
+ expect($metadata->getSampleRate())->toBeInt();
+ }
+ if ($metadata->getBitrateMode()) {
+ expect($metadata->getBitrateMode())->toBeString();
+ }
+ if ($metadata->getCompressionRatio()) {
+ expect($metadata->getCompressionRatio())->toBeFloat();
+ expect($metadata->getCompressionRatio(2))->toBeFloat();
+ }
+ if ($metadata->getCodec()) {
+ expect($metadata->getCodec())->toBeString();
+ }
+ if ($metadata->getEncoderOptions()) {
+ expect($metadata->getEncoderOptions())->toBeString();
+ }
+})->with([...AUDIO]);
+
+it('can read as array', function (string $path) {
+ $audio = Audio::read($path);
+ $metadata = $audio->getMetadata();
+
+ expect($metadata->toArray())->toBeArray();
+})->with([...AUDIO]);
+
+it('can read warning', function () {
+ $audio = Audio::read(AUDIOBOOK_RH_NOCOVER);
+ $metadata = $audio->getMetadata();
+
+ expect($metadata->getWarning())->toBeArray();
+});
+
+it('can read quicktime', function () {
+ $audio = Audio::read(AUDIOBOOK_RH_NOCOVER);
+ $quicktime = $audio->getMetadata()->getQuicktime();
+
+ expect($quicktime)->toBeInstanceOf(Id3AudioQuicktime::class);
+ expect($quicktime->getHinting())->toBeBool();
+ expect($quicktime->getController())->toBeString();
+
+ expect($quicktime->getFtyp())->toBeInstanceOf(Id3AudioQuicktimeItem::class);
+ expect($quicktime->getFtyp()->getFourcc())->toBeString();
+ expect($quicktime->getFtyp()->getHierarchy())->toBeString();
+ expect($quicktime->getFtyp()->getName())->toBeString();
+ expect($quicktime->getFtyp()->getOffset())->toBeInt();
+ expect($quicktime->getFtyp()->getSignature())->toBeString();
+ expect($quicktime->getFtyp()->getSize())->toBeInt();
+ expect($quicktime->getFtyp()->getUnknown1())->toBeInt();
+
+ expect($quicktime->getTimestampsUnix())->toBeArray();
+ expect($quicktime->getTimeScale())->toBeInt();
+ expect($quicktime->getDisplayScale())->toBeInt();
+ expect($quicktime->getVideo())->toBeArray();
+ expect($quicktime->getAudio())->toBeArray();
+ expect($quicktime->getSttsFramecount())->toBeArray();
+
+ expect($quicktime->getSttsFramecount())->toBeArray();
+ expect($quicktime->getSttsFramecount())->each(fn (Pest\Expectation $i) => expect($i->value)->toBeInt());
+
+ expect($quicktime->getComments())->toBeArray();
+
+ expect($quicktime->getChapters())->toBeArray();
+ expect($quicktime->getChapters())->each(fn (Pest\Expectation $i) => expect($i->value)->toBeInstanceOf(Id3AudioQuicktimeChapter::class));
+
+ expect($quicktime->getFree())->toBeInstanceOf(Id3AudioQuicktimeItem::class);
+ expect($quicktime->getWide())->toBeInstanceOf(Id3AudioQuicktimeItem::class);
+ expect($quicktime->getMdat())->toBeInstanceOf(Id3AudioQuicktimeItem::class);
+ expect($quicktime->getEncoding())->toBeString();
+});
diff --git a/tests/AudioMp3Test.php b/tests/AudioMp3Test.php
new file mode 100644
index 0000000..36d8bb5
--- /dev/null
+++ b/tests/AudioMp3Test.php
@@ -0,0 +1,116 @@
+toBeInstanceOf(Audio::class);
+ expect($audio->getPath())->toBe(MP3);
+ expect($audio->getExtension())->toBe('mp3');
+ expect($audio->getFormat())->toBe(AudioFormatEnum::mp3);
+ expect($audio->getType())->toBe(AudioTypeEnum::id3);
+ expect($audio->getMetadata())->toBeInstanceOf(AudioMetadata::class);
+ expect($audio->getId3Reader())->toBeInstanceOf(Id3Reader::class);
+ expect($audio->getDuration())->toBe(11.05);
+ expect($audio->getDurationHuman())->toBe('00:00:11');
+
+ expect($audio->isWritable())->toBeTrue();
+ expect($audio->isValid())->toBeTrue();
+ expect($audio->hasCover())->toBeTrue();
+
+ expect($audio->getTitle())->toBe('Introduction');
+ expect($audio->getArtist())->toBe('Mr Piouf');
+ expect($audio->getAlbum())->toBe('P1PDD Le conclave de Troie');
+ expect($audio->getGenre())->toBe('Roleplaying game');
+ expect($audio->getYear())->toBe(2016);
+ expect($audio->getTrackNumber())->toBe('1');
+ expect($audio->getTrackNumberInt())->toBe(1);
+ expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf');
+ expect($audio->getComposer())->toBe('P1PDD & Piouf');
+ expect($audio->getDiscNumber())->toBe('1');
+ expect($audio->getDiscNumberInt())->toBe(1);
+ expect($audio->isCompilation())->toBeTrue();
+ expect($audio->getCreationDate())->toBeNull();
+ expect($audio->getEncodingBy())->toBeNull();
+ expect($audio->getEncoding())->toBeNull();
+ expect($audio->getCopyright())->toBeNull();
+ expect($audio->getDescription())->toBeNull();
+ expect($audio->getSynopsis())->toBeNull();
+ expect($audio->getLanguage())->toBeNull();
+ expect($audio->getLyrics())->toBeNull();
+ expect($audio->getComment())->toBe('http://www.p1pdd.com');
+
+ expect($audio->getRawAll())->toBeArray();
+ expect($audio->getRawAll()['id3v1'])->toBeArray();
+ expect($audio->getRawAll()['id3v1'])->toHaveCount(6);
+ expect($audio->getRawAll()['id3v2'])->toBeArray();
+ expect($audio->getRawAll()['id3v2'])->toHaveCount(11);
+ expect($audio->getRaw())->toHaveCount(11);
+ expect($audio->getRaw('id3v2'))->toHaveCount(11);
+ expect($audio->getRawKey('title'))->toBe('Introduction');
+
+ $cover = $audio->getCover();
+ expect($cover)->toBeInstanceOf(AudioCover::class);
+ expect($cover->getContents())->toBeString();
+ expect($cover->getContents(base64: true))->toBeString();
+ expect($cover->getMimeType())->toBe('image/jpeg');
+ expect($cover->getWidth())->toBe(640);
+ expect($cover->getHeight())->toBe(640);
+
+ $metadata = $audio->getMetadata();
+ expect($metadata->getFileSize())->toBe(272737);
+ expect($metadata->getEncoding())->toBe('UTF-8');
+ expect($metadata->getMimeType())->toBe('audio/mpeg');
+ expect($metadata->getDurationSeconds())->toBe(11.0496875);
+ expect($metadata->getBitrate())->toBe(128000);
+ expect($metadata->getBitrateMode())->toBe('cbr');
+ expect($metadata->getSampleRate())->toBe(44100);
+ expect($metadata->getChannels())->toBe(2);
+ expect($metadata->getChannelMode())->toBe('joint stereo');
+ expect($metadata->isLossless())->toBeFalse();
+ expect($metadata->getCompressionRatio())->toBe(0.09070294784580499);
+});
+
+it('can extract cover mp3', function () {
+ $audio = Audio::read(MP3);
+ $cover = $audio->getCover();
+
+ expect($cover)->toBeInstanceOf(AudioCover::class);
+ expect($cover->getContents())->toBeString();
+ expect($cover->getMimeType())->toBe('image/jpeg');
+ expect($cover->getWidth())->toBe(640);
+ expect($cover->getHeight())->toBe(640);
+
+ $path = 'tests/output/cover.jpg';
+ file_put_contents($path, $cover->getContents());
+ expect(file_exists($path))->toBeTrue();
+ expect($path)->toBeReadableFile();
+});
+
+it('can read file mp3 no meta', function () {
+ $audio = Audio::read(MP3_NO_META);
+
+ expect($audio)->toBeInstanceOf(Audio::class);
+ expect($audio->getTitle())->toBeNull();
+ expect($audio->getArtist())->toBeNull();
+ expect($audio->getAlbum())->toBeNull();
+ expect($audio->getGenre())->toBeNull();
+ expect($audio->getYear())->toBeNull();
+ expect($audio->getTrackNumber())->toBeNull();
+ expect($audio->getComment())->toBeNull();
+ expect($audio->getAlbumArtist())->toBeNull();
+ expect($audio->getComposer())->toBeNull();
+ expect($audio->getDiscNumber())->toBeNull();
+ expect($audio->isCompilation())->toBeFalse();
+ expect($audio->getPath())->toBe(MP3_NO_META);
+});
+
+it("can fail if file didn't exists", function () {
+ expect(fn () => Audio::read('tests/media/unknown.mp3'))->toThrow(Exception::class);
+});
diff --git a/tests/AudioTest.php b/tests/AudioTest.php
index 2bd29d2..414fde7 100644
--- a/tests/AudioTest.php
+++ b/tests/AudioTest.php
@@ -2,144 +2,70 @@
use Kiwilan\Audio\Audio;
use Kiwilan\Audio\Enums\AudioFormatEnum;
-use Kiwilan\Audio\Models\AudioCover;
+use Kiwilan\Audio\Id3\Id3Reader;
+use Kiwilan\Audio\Models\AudioMetadata;
-it('can read file', function (string $path) {
- $audio = Audio::get($path);
+it('can use get method', function () {
+ $audio = Audio::get(MP3);
+ expect($audio)->toBeInstanceOf(Audio::class);
+});
+
+it('can read basic info', function (string $path) {
+ $audio = Audio::read($path);
$extension = pathinfo($path, PATHINFO_EXTENSION);
$format = AudioFormatEnum::tryFrom($extension);
expect($audio)->toBeInstanceOf(Audio::class);
+ expect($audio->getPath())->toBe($path);
+ expect($audio->getExtension())->toBe($extension);
+ expect($audio->getFormat())->toBe($format);
+
+ expect($audio->getMetadata())->toBeInstanceOf(AudioMetadata::class);
+ expect($audio->getId3Reader())->toBeInstanceOf(Id3Reader::class);
+ expect($audio->getDuration())->toBeFloat();
+ expect($audio->getDurationHuman())->toBeString();
+
+ expect($audio->isWritable())->toBeBool();
+ expect($audio->isValid())->toBeBool();
+ expect($audio->hasCover())->toBeBool();
+
expect($audio->getTitle())->toBe('Introduction');
expect($audio->getArtist())->toBe('Mr Piouf');
expect($audio->getAlbum())->toBe('P1PDD Le conclave de Troie');
expect($audio->getGenre())->toBe('Roleplaying game');
expect($audio->getYear())->toBe(2016);
expect($audio->getTrackNumber())->toBe('1');
- if ($audio->getComment()) {
- expect($audio->getComment())->toBe('http://www.p1pdd.com');
- }
- expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf');
- expect($audio->getComposer())->toBe('P1PDD & Piouf');
- expect($audio->getDiscNumber())->toBeString();
- expect($audio->isCompilation())->toBeBool();
- expect($audio->getPath())->toBe($path);
- expect($audio->getgetExtension())->toBe($extension);
+ expect($audio->getTrackNumberInt())->toBe(1);
expect($audio->getFormat())->toBe($format);
- expect($audio->getDuration())->toBeFloat();
- expect($audio->getExtras())->toBeArray();
- expect($audio->getTags())->toBeArray();
- expect($audio->toArray())->toBeArray();
-
- $metadata = $audio->getAudio();
- expect($metadata->getPath())->toBeString();
- expect($metadata->getFilesize())->toBeInt();
- expect($metadata->getExtension())->toBeString();
- expect($metadata->getEncoding())->toBeString();
- expect($metadata->getMimeType())->toBeString();
- if ($metadata->getDurationSeconds()) {
- expect($metadata->getDurationSeconds())->toBeFloat();
- }
- if ($metadata->getDurationReadable()) {
- expect($metadata->getDurationReadable())->toBeString();
- }
- if ($metadata->getBitrate()) {
- expect($metadata->getBitrate())->toBeInt();
- }
- if ($metadata->getBitrateMode()) {
- expect($metadata->getBitrateMode())->toBeString();
- }
- if ($metadata->getSampleRate()) {
- expect($metadata->getSampleRate())->toBeInt();
- }
- if ($metadata->getChannels()) {
- expect($metadata->getChannels())->toBeInt();
- }
- if ($metadata->getChannelMode()) {
- expect($metadata->getChannelMode())->toBeString();
- }
- expect($metadata->getLossless())->toBeBool();
- if ($metadata->getCompressionRatio()) {
- expect($metadata->getCompressionRatio())->toBeFloat();
- }
- expect($audio->isValid())->toBeTrue();
})->with([...AUDIO]);
-it('can extract cover', function (string $path) {
- $audio = Audio::get($path);
- $ext = pathinfo($path, PATHINFO_EXTENSION);
- $cover = $audio->getCover();
-
- if ($audio->hasCover()) {
- expect($cover)->toBeInstanceOf(AudioCover::class);
- expect($cover->getContents())->toBeString();
- expect($cover->getContents())->toBeString();
- expect($cover->getMimeType())->toBeString();
- if ($cover->getWidth()) {
- expect($cover->getWidth())->toBeInt();
- }
- if ($cover->getHeight()) {
- expect($cover->getHeight())->toBeInt();
- }
-
- $path = "tests/output/cover-{$ext}.jpg";
- file_put_contents($path, $cover->getContents());
- expect(file_exists($path))->toBeTrue();
- expect($path)->toBeReadableFile();
- } else {
- expect($cover)->toBeNull();
- }
-})->with([...AUDIO]);
+it('can read disc number', function () {
+ $audio = Audio::read(M4A);
-it('can use stat data', function (string $path) {
- $audio = Audio::get($path);
- $stat = $audio->getStat();
-
- expect($stat->getPath())->toBe($path);
- expect($stat->getDeviceNumber())->toBeInt();
- expect($stat->getInodeNumber())->toBeInt();
- expect($stat->getInodeProtectionMode())->toBeInt();
- expect($stat->getNumberOfLinks())->toBeInt();
- expect($stat->getUserId())->toBeInt();
- expect($stat->getGroupId())->toBeInt();
- expect($stat->getDeviceType())->toBeInt();
- expect($stat->getLastAccessAt())->toBeInstanceOf(DateTime::class);
- expect($stat->getCreatedAt())->toBeInstanceOf(DateTime::class);
- expect($stat->getModifiedAt())->toBeInstanceOf(DateTime::class);
- expect($stat->getBlockSize())->toBeInt();
- expect($stat->getNumberOfBlocks())->toBeInt();
- expect($stat->toArray())->toBeArray();
- expect($stat->toJson())->toBeString();
- expect($stat->__toString())->toBeString();
-})->with([...AUDIO]);
+ expect($audio->getDiscNumber())->toBe('1/2');
+ expect($audio->getDiscNumberInt())->toBe(1);
+});
-it('can read mp3 stream', function () {
- $audio = Audio::get(MP3);
- $streams = $audio->getReader()->getAudio()->streams();
-
- expect($streams)->toBeArray();
- expect($streams)->toHaveCount(1);
- expect($streams[0]->dataformat())->toBe('mp3');
- expect($streams[0]->channels())->toBe(2);
- expect($streams[0]->sample_rate())->toBe(44100);
- expect($streams[0]->bitrate())->toBe(128000.0);
- expect($streams[0]->channelmode())->toBe('joint stereo');
- expect($streams[0]->bitrate_mode())->toBe('cbr');
- expect($streams[0]->codec())->toBe('LAME');
- expect($streams[0]->encoder())->toBe('LAME3.100');
- expect($streams[0]->lossless())->toBeFalse();
- expect($streams[0]->encoder_options())->toBe('CBR128');
- expect($streams[0]->compression_ratio())->toBe(0.09070294784580499);
+it('can read encoding', function () {
+ $audio = Audio::read(M4V);
+
+ expect($audio->getEncoding())->toBe('Lavf60.3.100');
});
-it('can read wrong audio file', function () {
- $audio = Audio::get(MD);
+it('can read description', function () {
+ $audio = Audio::read(FLAC);
- expect($audio->isValid())->toBeFalse();
+ expect($audio->getDescription())->toBe('http://www.p1pdd.com');
+});
+
+it('can read creation date', function () {
+ $audio = Audio::read(WV);
+
+ expect($audio->getCreationDate())->toBe('2016');
});
it('can read file id3v1', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
$extension = pathinfo($path, PATHINFO_EXTENSION);
$format = AudioFormatEnum::tryFrom($extension);
@@ -150,11 +76,22 @@
expect($audio->getAlbumArtist())->toBeString();
expect($audio->getComposer())->toBeNull();
- expect($audio->getgetExtension())->toBe($extension);
+ expect($audio->getExtension())->toBe($extension);
expect($audio->getFormat())->toBe($format);
expect($audio->getDuration())->toBeFloat();
- expect($audio->getDurationHumanReadable())->toBe('00:00:11');
- expect($audio->getExtras())->toBeArray();
+ expect($audio->getDurationHuman())->toBe('00:00:11');
expect($audio)->toBeInstanceOf(Audio::class);
})->with([...AUDIO_ID3_V1]);
+
+it('can read wrong audio file', function () {
+ $audio = Audio::read(MD);
+
+ expect($audio->isValid())->toBeFalse();
+});
+
+it('can read as array', function (string $path) {
+ $audio = Audio::read($path);
+
+ expect($audio->toArray())->toBeArray();
+})->with([...AUDIO]);
diff --git a/tests/AudiobookTest.php b/tests/AudiobookTest.php
index 3bcc230..b91796a 100644
--- a/tests/AudiobookTest.php
+++ b/tests/AudiobookTest.php
@@ -2,9 +2,104 @@
use Kiwilan\Audio\Audio;
use Kiwilan\Audio\Enums\AudioFormatEnum;
+use Kiwilan\Audio\Enums\AudioTypeEnum;
+use Kiwilan\Audio\Id3\Reader\Id3AudioQuicktimeChapter;
+use Kiwilan\Audio\Models\AudioMetadata;
+
+it('can read audiobook', function () {
+ $audiobook = Audio::read(AUDIOBOOK_RH);
+
+ expect($audiobook->getPath())->toContain('tests/media/audiobook_rh.m4b');
+ expect($audiobook->getExtension())->toBe('m4b');
+ expect($audiobook->getFormat())->toBe(AudioFormatEnum::m4b);
+ expect($audiobook->getType())->toBe(AudioTypeEnum::quicktime);
+ expect($audiobook->getMetadata())->toBeInstanceOf(AudioMetadata::class);
+ expect($audiobook->isWritable())->toBeTrue();
+ expect($audiobook->isValid())->toBeTrue();
+ expect($audiobook->hasCover())->toBeTrue();
+
+ expect($audiobook->getTitle())->toBe('Assassin’s Apprentice');
+ expect($audiobook->getArtist())->toBe('Robin Hobb');
+ expect($audiobook->getAlbum())->toBe('Assassin’s Apprentice');
+ expect($audiobook->getGenre())->toBe('Animals/Political/Epic/Military');
+ expect($audiobook->getYear())->toBe(2024);
+ expect($audiobook->getTrackNumber())->toBe('1/1');
+ expect($audiobook->getTrackNumberInt())->toBe(1);
+ expect($audiobook->getComment())->toBe('English');
+ expect($audiobook->getAlbumArtist())->toBe('Robin Hobb');
+ expect($audiobook->getComposer())->toBe('Paul Boehmer');
+ expect($audiobook->getDiscNumber())->toBe('1');
+ expect($audiobook->getDiscNumberInt())->toBe(1);
+ expect($audiobook->isCompilation())->toBeTrue();
+ expect($audiobook->getCreationDate())->toBe('2024-09-30T12:00:00Z');
+ expect($audiobook->getCopyright())->toBe('HarperCollins');
+ expect($audiobook->getEncodingBy())->toBe('©2012 Robin Hobb (P)2012 HarperCollins Publishers Limited');
+ expect($audiobook->getEncoding())->toBe('Audiobook Builder 2.2.9 (www.splasm.com), macOS 15.0');
+ expect($audiobook->getDescription())->toBeString();
+ expect($audiobook->getSynopsis())->toBeString();
+ expect($audiobook->getLanguage())->toBe('English');
+ expect($audiobook->getLyrics())->toBe('The Farseer #01');
+});
+
+it('can read audiobook raw', function () {
+ $audiobook = Audio::read(AUDIOBOOK_RH);
+
+ $raw = $audiobook->getRaw();
+ expect($raw['title'])->toBe('Assassin’s Apprentice');
+ expect($raw['artist'])->toBe('Robin Hobb');
+ expect($raw['album'])->toBe('Assassin’s Apprentice');
+ expect($raw['genre'])->toBe('Animals/Political/Epic/Military');
+ expect($raw['origyear'])->toBe('2024/09/30');
+ expect($raw['track_number'])->toBe('1/1');
+ expect($raw['disc_number'])->toBe('1');
+ expect($raw['compilation'])->toBe(1);
+ expect($raw['creation_date'])->toBe('2024-9-30T12:00:00Z');
+ expect($raw['encoding_tool'])->toBe('Audiobook Builder 2.2.9 (www.splasm.com), macOS 15.0');
+ expect($raw['subtitle'])->toBe('Subtitle');
+ expect($raw['description_long'])->toBeString();
+ expect($raw['language'])->toBe('English');
+ expect($raw['lyrics'])->toBe('The Farseer #01');
+ expect($raw['stik'])->toBe('Audiobook');
+ expect($raw['encoded_by'])->toBe('©2012 Robin Hobb (P)2012 HarperCollins Publishers Limited');
+ expect($raw['description'])->toBeString();
+ expect($raw['copyright'])->toBe('HarperCollins');
+ expect($raw['isbn'])->toBe('ISBN');
+ expect($raw['composer'])->toBe('Paul Boehmer');
+ expect($raw['comment'])->toBe('English');
+ expect($raw['asin'])->toBe('ASIN');
+ expect($raw['album_artist'])->toBe('Robin Hobb');
+ expect($raw['series-part'])->toBe('1');
+ expect($raw['series'])->toBe('The Farseer');
+
+ expect($audiobook->getRawKey('title'))->toBe('Assassin’s Apprentice');
+ expect($audiobook->getRawKey('artist'))->toBe('Robin Hobb');
+ expect($audiobook->getRawKey('album'))->toBe('Assassin’s Apprentice');
+ expect($audiobook->getRawKey('genre'))->toBe('Animals/Political/Epic/Military');
+ expect($audiobook->getRawKey('origyear'))->toBe('2024/09/30');
+ expect($audiobook->getRawKey('track_number'))->toBe('1/1');
+ expect($audiobook->getRawKey('disc_number'))->toBe('1');
+ expect($audiobook->getRawKey('compilation'))->toBe(1);
+ expect($audiobook->getRawKey('creation_date'))->toBe('2024-9-30T12:00:00Z');
+ expect($audiobook->getRawKey('encoding_tool'))->toBe('Audiobook Builder 2.2.9 (www.splasm.com), macOS 15.0');
+ expect($audiobook->getRawKey('subtitle'))->toBe('Subtitle');
+ expect($audiobook->getRawKey('description_long'))->toBeString();
+ expect($audiobook->getRawKey('language'))->toBe('English');
+ expect($audiobook->getRawKey('lyrics'))->toBe('The Farseer #01');
+ expect($audiobook->getRawKey('stik'))->toBe('Audiobook');
+ expect($audiobook->getRawKey('encoded_by'))->toBe('©2012 Robin Hobb (P)2012 HarperCollins Publishers Limited');
+ expect($audiobook->getRawKey('description'))->toBeString();
+ expect($audiobook->getRawKey('copyright'))->toBe('HarperCollins');
+ expect($audiobook->getRawKey('isbn'))->toBe('ISBN');
+ expect($audiobook->getRawKey('composer'))->toBe('Paul Boehmer');
+ expect($audiobook->getRawKey('comment'))->toBe('English');
+ expect($audiobook->getRawKey('asin'))->toBe('ASIN');
+ expect($audiobook->getRawKey('album_artist'))->toBe('Robin Hobb');
+ expect($audiobook->getRawKey('series-part'))->toBe('1');
+ expect($audiobook->getRawKey('series'))->toBe('The Farseer');
+});
it('can read audiobook file m4b', function (string $file) {
- $audio = Audio::get($file);
+ $audio = Audio::read($file);
expect($audio->getTitle())->toBe('P1PDD Saison 1');
expect($audio->getArtist())->toBe('Mr Piouf');
@@ -24,31 +119,36 @@
expect($audio->getEncoding())->toBe('Audiobook Builder 2.2.6 (www.splasm.com), macOS 13.4');
expect($audio->getCopyright())->toBe('Copyright');
expect($audio->getDescription())->toBe('Description');
- expect($audio->getPodcastDescription())->toBe('Synopsis');
+ expect($audio->getSynopsis())->toBe('Synopsis');
expect($audio->getLanguage())->toBe('Language');
expect($audio->getLyrics())->toBe('Lyrics');
- expect($audio->getStik())->toBe('Audiobook');
expect($audio->getDuration())->toBe(11.00);
- expect($audio->getDurationHumanReadable())->toBe('00:00:11');
- expect($audio->getExtras())->toBeArray();
- expect($audio->toArray())->toBeArray();
-
- expect($audio->getTags())->toBeArray();
- expect($audio->getTag('title'))->toBe('P1PDD Saison 1');
- expect($audio->getTag('artist'))->toBe('Mr Piouf');
- expect($audio->getTag('album'))->toBe('P1PDD Saison 1');
- expect($audio->getTag('genre'))->toBe('Audiobooks');
- expect($audio->getTag('track_number'))->toBe('1/1');
- expect($audio->getTag('comment'))->toBe('P1PDD team');
+ expect($audio->getDurationHuman())->toBe('00:00:11');
+
+ expect($audio->getRaw())->toBeArray();
+ expect($audio->getRawKey('title'))->toBe('P1PDD Saison 1');
+ expect($audio->getRawKey('artist'))->toBe('Mr Piouf');
+ expect($audio->getRawKey('album'))->toBe('P1PDD Saison 1');
+ expect($audio->getRawKey('genre'))->toBe('Audiobooks');
+ expect($audio->getRawKey('track_number'))->toBe('1/1');
+ expect($audio->getRawKey('comment'))->toBe('P1PDD team');
})->with([AUDIOBOOK]);
it('can read audiobook file mp3', function (string $file) {
- $audio = Audio::get($file);
-
- expect(count($audio->getTags()))->toBe(16);
- expect(count($audio->getTags('id3v2')))->toBe(15);
+ $audio = Audio::read($file);
- expect(count($audio->getAudioFormats()))->toBe(3);
- expect($audio->getAudioFormats())->toBeArray();
- expect($audio->toArray())->toBeArray();
+ expect(count($audio->getRaw()))->toBe(15);
+ expect(count($audio->getRaw('id3v2')))->toBe(15);
})->with([AUDIOBOOK_MP3]);
+
+it('can read chapters', function () {
+ $audio = Audio::read(AUDIOBOOK_RH_NOCOVER);
+ $quicktime = $audio->getMetadata()->getQuicktime();
+
+ expect($quicktime->getChapters())->toBeArray();
+ expect($quicktime->getChapters())->each(fn (Pest\Expectation $chapter) => expect($chapter->value)->toBeInstanceOf(Id3AudioQuicktimeChapter::class));
+
+ $first = $quicktime->getChapters()[0];
+ expect($first->getTimestamp())->toBe(0);
+ expect($first->getTitle())->toBe('Chapter 01');
+});
diff --git a/tests/Mp3Test.php b/tests/Mp3Test.php
deleted file mode 100644
index 7598f3c..0000000
--- a/tests/Mp3Test.php
+++ /dev/null
@@ -1,80 +0,0 @@
-toBeInstanceOf(Audio::class);
- expect($audio->getTitle())->toBe('Introduction');
- expect($audio->getArtist())->toBe('Mr Piouf');
- expect($audio->getAlbum())->toBe('P1PDD Le conclave de Troie');
- expect($audio->getGenre())->toBe('Roleplaying game');
- expect($audio->getYear())->toBe(2016);
- expect($audio->getTrackNumber())->toBe('1');
- expect($audio->getComment())->toBe('http://www.p1pdd.com');
- expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf');
- expect($audio->getComposer())->toBe('P1PDD & Piouf');
- expect($audio->getDiscNumber())->toBe('1');
- expect($audio->isCompilation())->toBe(true);
- expect($audio->getPath())->toBe(MP3);
- expect($audio->getFormat())->toBe(AudioFormatEnum::mp3);
- expect($audio->getDuration())->toBe(11.05);
- expect($audio->getDurationHumanReadable())->toBe('00:00:11');
- expect($audio->getExtras())->toBeArray();
-
- $audio = $audio->getAudio();
- expect($audio->getFilesize())->toBe(272737);
- expect($audio->getExtension())->toBe('mp3');
- expect($audio->getEncoding())->toBe('UTF-8');
- expect($audio->getMimeType())->toBe('audio/mpeg');
- expect($audio->getDurationSeconds())->toBe(11.0496875);
- expect($audio->getDurationReadable())->toBe('0:11');
- expect($audio->getBitrate())->toBe(128000);
- expect($audio->getBitrateMode())->toBe('cbr');
- expect($audio->getSampleRate())->toBe(44100);
- expect($audio->getChannels())->toBe(2);
- expect($audio->getChannelMode())->toBe('joint stereo');
- expect($audio->getLossless())->toBe(false);
- expect($audio->getCompressionRatio())->toBe(0.09070294784580499);
-});
-
-it('can extract cover mp3', function () {
- $audio = Audio::get(MP3);
- $cover = $audio->getCover();
-
- expect($cover)->toBeInstanceOf(AudioCover::class);
- expect($cover->getContents())->toBeString();
- expect($cover->getMimeType())->toBe('image/jpeg');
- expect($cover->getWidth())->toBe(640);
- expect($cover->getHeight())->toBe(640);
-
- $path = 'tests/output/cover.jpg';
- file_put_contents($path, $cover->getContents());
- expect(file_exists($path))->toBeTrue();
- expect($path)->toBeReadableFile();
-});
-
-it('can read file mp3 no meta', function () {
- $audio = Audio::get(MP3_NO_META);
-
- expect($audio)->toBeInstanceOf(Audio::class);
- expect($audio->getTitle())->toBeNull();
- expect($audio->getArtist())->toBeNull();
- expect($audio->getAlbum())->toBeNull();
- expect($audio->getGenre())->toBeNull();
- expect($audio->getYear())->toBeNull();
- expect($audio->getTrackNumber())->toBeNull();
- expect($audio->getComment())->toBeNull();
- expect($audio->getAlbumArtist())->toBeNull();
- expect($audio->getComposer())->toBeNull();
- expect($audio->getDiscNumber())->toBeNull();
- expect($audio->isCompilation())->toBeFalse();
- expect($audio->getPath())->toBe(MP3_NO_META);
-});
-
-it("can fail if file didn't exists", function () {
- expect(fn () => Audio::get('tests/media/unknown.mp3'))->toThrow(Exception::class);
-});
diff --git a/tests/Pest.php b/tests/Pest.php
index a265cc3..4bd0d54 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -1,9 +1,12 @@
getTitle())->toBe('Introduction');
+ expect($audio->getArtist())->toBe('Mr Piouf');
+ expect($audio->getAlbum())->toBe('P1PDD Le conclave de Troie');
+ expect($audio->getGenre())->toBe('Roleplaying game');
+ expect($audio->getYear())->toBe(2016);
+ expect($audio->getTrackNumber())->toBe('1');
+ expect($audio->getComment())->toBe('http://www.p1pdd.com');
+ expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf');
+ expect($audio->getComposer())->toBe('P1PDD & Piouf');
+ expect($audio->getDiscNumber())->toBe('1');
+ expect($audio->isCompilation())->toBeTrue();
+}
+
+function testMp3Writed(Audio $audio)
+{
+ expect($audio->getTitle())->toBe('New Title');
+ expect($audio->getArtist())->toBe('New Artist');
+ expect($audio->getAlbum())->toBe('New Album');
+ expect($audio->getGenre())->toBe('New Genre');
+ expect($audio->getYear())->toBe(2022);
+ expect($audio->getAlbumArtist())->toBe('New Album Artist');
+ expect($audio->getComment())->toBe('New Comment');
+ expect($audio->getComposer())->toBe('New Composer');
+ expect($audio->getDiscNumber())->toBe('2/2');
+ expect($audio->isCompilation())->toBeFalse();
+}
+
+function pathTo(string $filename, string $subDirectory = 'output'): string
+{
+ return __DIR__.'/'.$subDirectory.'/'.$filename;
+}
+
+function resetMp3Writer()
+{
+ $audio = Audio::read(MP3_WRITER);
+
+ $audio->write()
+ ->title('Introduction')
+ ->artist('Mr Piouf')
+ ->album('P1PDD Le conclave de Troie')
+ ->genre('Roleplaying game')
+ ->year(2016)
+ ->trackNumber('1')
+ ->comment('http://www.p1pdd.com')
+ ->albumArtist('P1PDD & Mr Piouf')
+ ->composer('P1PDD & Piouf')
+ ->discNumber('1')
+ ->isCompilation()
+ ->cover(DEFAULT_FOLDER)
+ ->save();
+}
diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php
index dda2a98..edd926d 100644
--- a/tests/ReaderTest.php
+++ b/tests/ReaderTest.php
@@ -1,16 +1,34 @@
getId3Reader()->getAudio()->streams;
+
+ expect($streams)->toBeArray();
+ expect($streams)->toHaveCount(1);
+ expect($streams[0]->data_format)->toBe('mp3');
+ expect($streams[0]->channels)->toBe(2);
+ expect($streams[0]->sample_rate)->toBe(44100);
+ expect($streams[0]->bitrate)->toBe(128000.0);
+ expect($streams[0]->channel_mode)->toBe('joint stereo');
+ expect($streams[0]->bitrate_mode)->toBe('cbr');
+ expect($streams[0]->codec)->toBe('LAME');
+ expect($streams[0]->encoder)->toBe('LAME3.100');
+ expect($streams[0]->lossless)->toBeFalse();
+ expect($streams[0]->encoder_options)->toBe('CBR128');
+ expect($streams[0]->compression_ratio)->toBe(0.09070294784580499);
+});
it('can parse ID3 reader', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
- $reader = $audio->getReader();
+ $reader = $audio->getId3Reader();
$raw = $reader->getRaw();
expect($reader->getInstance())->toBeInstanceOf(getID3::class);
@@ -41,9 +59,6 @@
expect($reader->getPlaytimeSeconds())->toBeFloat();
}
- if ($reader->getTagsHtml()) {
- expect($reader->getTagsHtml())->toBeInstanceOf(Id3TagsHtml::class);
- }
if ($reader->getBitrate()) {
expect($reader->getBitrate())->toBeFloat();
}
@@ -51,16 +66,3 @@
expect($reader->getPlaytimeString())->toBeString();
}
})->with([...AUDIO]);
-
-it('can parse with ID3 methods', function (string $path) {
- $audio = Audio::get($path);
- $type = $audio->getType()->value;
- $tags = $audio->getReader()->getTags();
-
- if ($type === 'id3') {
- $type = 'id3v2';
- }
-
- $metadata = $tags->{$type}();
- expect($metadata->toArray())->toBeArray();
-})->with([...AUDIO]);
diff --git a/tests/WriterCoverTest.php b/tests/WriterCoverTest.php
new file mode 100644
index 0000000..97cc1e1
--- /dev/null
+++ b/tests/WriterCoverTest.php
@@ -0,0 +1,109 @@
+write()
+ ->cover(FOLDER)
+ ->save();
+
+ $audio = Audio::read($path);
+ expect($audio->getTitle())->toBe('Introduction');
+
+ $content = base64_encode(file_get_contents(FOLDER));
+ expect($audio->getCover()->getContents(true))->toBe($content);
+})->with([MP3_WRITER]);
+
+it('can read use file content as cover', function (string $path) {
+ $audio = Audio::read($path);
+
+ $tag = $audio->write()
+ ->cover(file_get_contents(FOLDER));
+
+ $tag->save();
+
+ $audio = Audio::read($path);
+
+ $content = file_get_contents(FOLDER);
+ expect($audio->getCover()->getContents(true))->toBe(base64_encode($content));
+})->with([MP3_WRITER]);
+
+it('can read use tags', function (string $path) {
+ $audio = Audio::read($path);
+
+ $random = (string) rand(1, 1000);
+ $coverData = file_get_contents(FOLDER);
+ $tag = $audio->write()
+ ->tags([
+ 'title' => $random,
+ ])
+ ->cover($coverData);
+
+ $tag->save();
+
+ $audio = Audio::read($path);
+ expect($audio->getTitle())->toBe($random);
+
+ $content = file_get_contents(FOLDER);
+ expect($audio->getCover()->getContents())->toBe($content);
+})->with([MP3_WRITER]);
+
+it('can use tags with cover', function (string $path) {
+ $audio = Audio::read($path);
+
+ $tag = $audio->write()
+ ->tags([
+ 'title' => 'New Title',
+ ])
+ ->cover(FOLDER);
+
+ $tag->save();
+
+ $audio = Audio::read($path);
+
+ $content = file_get_contents(FOLDER);
+ expect($audio->getTitle())->toBe('New Title');
+ expect($tag->getCore()->cover->data)->toBe(base64_encode($content));
+})->with([MP3_WRITER]);
+
+it('can update cover with path', function () {
+ $audio = Audio::read(MP3_WRITER);
+
+ $path = pathTo('cover.jpg', 'media');
+
+ if (file_exists($path)) {
+ unlink($path);
+ }
+ $audio->getCover()->extractCover($path);
+ expect(file_exists($path))->toBeTrue();
+
+ $audio->write()
+ ->cover(FOLDER)
+ ->save();
+
+ $audio = Audio::read(MP3_WRITER);
+ unlink($path);
+ $audio->getCover()->extractCover($path);
+ expect(file_exists($path))->toBeTrue();
+
+ $content = file_get_contents(FOLDER);
+ expect($audio->getCover()->getContents(true))->toBe(base64_encode($content));
+});
+
+it('can remove cover', function () {
+ $audio = Audio::read(MP3_WRITER);
+
+ $audio->write()
+ ->removeCover()
+ ->save();
+
+ $audio = Audio::read(MP3_WRITER);
+
+ expect($audio->hasCover())->toBeFalse();
+ expect($audio->getCover())->toBeNull();
+});
diff --git a/tests/WriterMp3Test.php b/tests/WriterMp3Test.php
new file mode 100644
index 0000000..9a04363
--- /dev/null
+++ b/tests/WriterMp3Test.php
@@ -0,0 +1,118 @@
+write()
+ ->title('New Title')
+ ->artist('New Artist')
+ ->album('New Album')
+ ->genre('New Genre')
+ ->year(2022)
+ ->trackNumber('2/10')
+ ->albumArtist('New Album Artist')
+ ->comment('New Comment')
+ ->composer('New Composer')
+ ->discNumber('2/2')
+ ->isNotCompilation()
+ ->lyrics('New Lyrics')
+ ->creationDate('2021-01-01')
+ ->copyright('New Copyright')
+ ->encodingBy('New Encoding By')
+ ->encoding('New Encoding')
+ ->description('New Description')
+ ->synopsis('New Synopsis')
+ ->language('en')
+ ->copyright('New Copyright')
+ ->save();
+
+ $audio = Audio::read(MP3_WRITER);
+ testMp3Writed($audio);
+ expect($audio->getLanguage())->toBe('en');
+ expect($audio->getCopyright())->toBe('New Copyright');
+});
+
+it('can update only one tag', function () {
+ $audio = Audio::read(MP3_WRITER);
+ testMp3Writer($audio);
+
+ $audio->write()
+ ->title('New Title')
+ ->save();
+
+ $audio = Audio::read(MP3_WRITER);
+ expect($audio->getTitle())->toBe('New Title');
+ expect($audio->getArtist())->toBe('Mr Piouf');
+ expect($audio->getAlbum())->toBe('P1PDD Le conclave de Troie');
+ expect($audio->getGenre())->toBe('Roleplaying game');
+ expect($audio->getYear())->toBe(2016);
+ expect($audio->getTrackNumber())->toBe('1');
+ expect($audio->getComment())->toBe('http://www.p1pdd.com');
+ expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf');
+ expect($audio->getComposer())->toBe('P1PDD & Piouf');
+ expect($audio->getDiscNumber())->toBe('1');
+ expect($audio->isCompilation())->toBeTrue();
+});
+
+it('can update tags manually', function () {
+ $audio = Audio::read(MP3_WRITER);
+ testMp3Writer($audio);
+
+ $audio->write()
+ ->tags([
+ 'title' => 'New Title',
+ 'artist' => 'New Artist',
+ 'album' => 'New Album',
+ 'genre' => 'New Genre',
+ 'year' => '2022',
+ 'track_number' => '2/10',
+ 'band' => 'New Album Artist',
+ 'comment' => 'New Comment',
+ 'composer' => 'New Composer',
+ 'part_of_a_set' => '2/2',
+ 'part_of_a_compilation' => false,
+ 'unsynchronised_lyric' => 'New Lyrics',
+ 'language' => 'en',
+ 'copyright' => 'New Copyright',
+ ])
+ ->save();
+
+ $audio = Audio::read(MP3_WRITER);
+ testMp3Writed($audio);
+ expect($audio->getLanguage())->toBe('en');
+ expect($audio->getCopyright())->toBe('New Copyright');
+});
+
+it('can update tag', function () {
+ $audio = Audio::read(MP3_WRITER);
+ testMp3Writer($audio);
+
+ $audio->write()
+ ->tag('title', 'New Title')
+ ->tag('artist', 'New Artist')
+ ->tags([
+ 'album' => 'New Album',
+ 'part_of_a_compilation' => false,
+ ])
+ ->albumArtist('New Album Artist')
+ ->cover(FOLDER)
+ ->save();
+
+ $audio = Audio::read(MP3_WRITER);
+ expect($audio->getTitle())->toBe('New Title');
+ expect($audio->getArtist())->toBe('New Artist');
+ expect($audio->getAlbum())->toBe('New Album');
+ expect($audio->getAlbumArtist())->toBe('New Album Artist');
+ expect($audio->isCompilation())->toBeFalse();
+ expect($audio->getGenre())->toBe('Roleplaying game');
+
+ $content = base64_encode(file_get_contents(FOLDER));
+ expect($audio->getCover()->getContents(true))->toBe($content);
+});
diff --git a/tests/WriterTest.php b/tests/WriterTest.php
index a1c3c60..0ea3720 100644
--- a/tests/WriterTest.php
+++ b/tests/WriterTest.php
@@ -1,13 +1,17 @@
update()
+ $audio->write()
->title($random)
->artist('New Artist')
->album('New Album')
@@ -24,13 +28,9 @@
->encoding('New Encoding')
->isNotCompilation()
->lyrics('New Lyrics')
- ->stik('New Stik')
- ->cover(FOLDER);
-
- $core = $tag->getCore();
- $tag->save();
+ ->save();
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
expect($audio->getTitle())->toBe($random);
expect($audio->getArtist())->toBe('New Artist');
@@ -52,70 +52,19 @@
if ($audio->getLyrics()) {
expect($audio->getLyrics())->toBe('New Lyrics');
}
- expect($audio->getStik())->toBeNull();
if ($audio->getFormat() !== AudioFormatEnum::mp3) {
expect($audio->getTrackNumber())->toBe('2/10');
} else {
expect($audio->getTrackNumber())->toBe('2');
}
-
- if ($tag->getCore()->hasCover()) {
- $content = file_get_contents(FOLDER);
- expect($tag->getCore()->getCover()->data())->toBe(base64_encode($content));
- }
})->with(AUDIO_WRITER);
-it('can read use file content as cover', function (string $path) {
- $audio = Audio::get($path);
-
- $tag = $audio->update()
- ->cover(file_get_contents(FOLDER));
-
- $tag->save();
-
- $audio = Audio::get($path);
-
- $content = file_get_contents(FOLDER);
- expect($tag->getCore()->getCover()->data())->toBe(base64_encode($content));
-})->with([MP3_WRITER]);
-
-it('can read use tags', function (string $path) {
- $audio = Audio::get($path);
-
- $random = (string) rand(1, 1000);
- $image = getimagesize(FOLDER);
- $coverData = file_get_contents(FOLDER);
- $coverPicturetypeid = $image[2];
- $coverDescription = 'cover';
- $coverMime = $image['mime'];
- $tag = $audio->update()
- ->tags([
- 'title' => $random,
- 'attached_picture' => [
- [
- 'data' => $coverData,
- 'picturetypeid' => $coverPicturetypeid,
- 'description' => $coverDescription,
- 'mime' => $coverMime,
- ],
- ],
- ]);
-
- $tag->save();
-
- $audio = Audio::get($path);
- expect($audio->getTitle())->toBe($random);
-
- $content = file_get_contents(FOLDER);
- expect($audio->getCover()->getContents())->toBe($content);
-})->with([MP3_WRITER]);
-
it('can update use tags with tag formats', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
$random = (string) rand(1, 1000);
- $tag = $audio->update()
+ $tag = $audio->write()
->tags([
'title' => $random,
])
@@ -123,14 +72,14 @@
$tag->save();
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
expect($audio->getTitle())->toBe($random);
})->with([MP3_WRITER]);
it('can update with tags and handle native metadata', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
- $tag = $audio->update()
+ $tag = $audio->write()
->isCompilation()
->tags([
'title' => 'New Title',
@@ -140,160 +89,117 @@
$tag->save();
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
expect($audio->getTitle())->toBe('New Title');
expect($audio->getAlbumArtist())->toBe('New Band');
expect($audio->isCompilation())->toBeTrue();
})->with([MP3_WRITER]);
-it('can update with new path', function (string $path) {
- $audio = Audio::get($path);
- $newPath = 'tests/output/new.mp3';
-
- $tag = $audio->update()
- ->title('New Title')
- ->path($newPath);
-
- $tag->save();
-
- $audio = Audio::get($newPath);
- expect($audio->getTitle())->toBe('New Title');
-})->with([MP3_WRITER]);
-
-it('can update with merged tags and core methods', function (string $path) {
- $audio = Audio::get($path);
-
- $tag = $audio->update()
- ->title('New Title')
- ->tags([
- 'title' => 'New Title tag',
- 'band' => 'New Band',
- ]);
-
- $tag->save();
-
- $audio = Audio::get($path);
- expect($audio->getTitle())->toBe('New Title');
- expect($audio->getAlbumArtist())->toBe('New Band');
-})->with([MP3_WRITER]);
-
it('can use arrow function safe with unsupported tags', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
- $tag = $audio->update()
+ $tag = $audio->write()
->title('New Title')
->encoding('New encoding');
expect(fn () => $tag->save())->not()->toThrow(Exception::class);
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
expect($audio->getTitle())->toBe('New Title');
})->with([MP3_WRITER]);
-it('can use arrow function safe with unsupported formats', function (string $path) {
- $audio = Audio::get($path);
-
- $tag = $audio->update()
- ->title('New Title Alac');
-
- expect(fn () => $tag->save())->toThrow(Exception::class);
-})->with([ALAC_WRITER]);
-
it('can get core before save', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
- $tag = $audio->update()
+ $writer = $audio->write()
->title('New Title')
->tags([
'title' => 'New Title tag',
'band' => 'New Band',
]);
- expect($tag->getCore())->toBeInstanceOf(AudioCore::class);
+ expect($writer->getCore())->toBeInstanceOf(AudioCore::class);
})->with([MP3_WRITER]);
it('can handle exceptions', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
- $tag = $audio->update()
+ $tag = $audio->write()
->tags([
'title' => 'New Title',
'albumArtist' => 'New Album Artist',
- ])
- ->options(['encoding' => 'UTF-8']);
+ ]);
expect(fn () => $tag->save())->toThrow(Exception::class);
})->with([MP3_WRITER]);
it('can skip exceptions', function (string $path) {
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
- $tag = $audio->update()
+ $tag = $audio->write()
->tags([
'title' => 'New Title',
'albumArtist' => 'New Album Artist',
])
- ->preventFailOnError();
+ ->skipErrors();
$tag->save();
- $audio = Audio::get($path);
+ $audio = Audio::read($path);
expect($audio->getTitle())->toBe('New Title');
- expect($audio->getAlbumArtist())->toBeNull();
+ expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf');
})->with([MP3_WRITER]);
-it('can remove old tags', function (string $path) {
- $audio = Audio::get($path);
+it('can update with new path', function (string $path) {
+ $audio = Audio::read($path);
+ $newPath = 'tests/output/new.mp3';
- $tag = $audio->update()
+ $tag = $audio->write()
->title('New Title')
- ->removeOldTags()
- ->path('tests/output/new.mp3');
+ ->path($newPath);
$tag->save();
- $audio = Audio::get('tests/output/new.mp3');
+ $audio = Audio::read($newPath);
expect($audio->getTitle())->toBe('New Title');
- expect($audio->getAlbumArtist())->toBeNull();
-})->with([MP3]);
+})->with([MP3_WRITER]);
-it('can use tags with cover', function (string $path) {
- $audio = Audio::get($path);
+it('can update with merged tags and core methods', function (string $path) {
+ $audio = Audio::read($path);
- $tag = $audio->update()
+ $tag = $audio->write()
->tags([
- 'title' => 'New Title',
- ])
- ->cover(FOLDER);
-
+ 'title' => 'New Title tag',
+ 'band' => 'New Band',
+ ]);
$tag->save();
- $audio = Audio::get($path);
-
- $content = file_get_contents(FOLDER);
- expect($audio->getTitle())->toBe('New Title');
- expect($tag->getCore()->getCover()->data())->toBe(base64_encode($content));
+ $audio = Audio::read($path);
+ expect($audio->getTitle())->toBe('New Title tag');
+ expect($audio->getAlbumArtist())->toBe('New Band');
})->with([MP3_WRITER]);
-// it('can change podcast description and language', function () {
-// $audio = Audio::get(AUDIOBOOK);
-// $tag = $audio->update()
-// ->title('New Title')
-// ->podcastDescription('New Podcast Description')
-// ->language('New Language')
-// ->save();
-// });
+it('can use arrow function safe with unsupported formats', function (string $path) {
+ $audio = Audio::read($path);
+
+ $tag = $audio->write()
+ ->title('New Title Alac');
+
+ expect(fn () => $tag->save())->toThrow(Exception::class);
+})->with([ALAC_WRITER]);
-// it('can not override tags', function (string $path) {
-// $audio = Audio::get($path);
+it('can remove old tags', function (string $path) {
+ $audio = Audio::read($path);
+ $newPath = 'tests/output/new.mp3';
-// $tag = $audio->update()
-// ->getTitle('New Title')
-// ->notOverrideTags()
-// ->path('tests/output/new.mp3');
+ $tag = $audio->write()
+ ->title('New Title')
+ ->removeOtherTags()
+ ->path($newPath);
-// $tag->save();
+ $tag->save();
-// $audio = Audio::get('tests/output/new.mp3');
-// expect($audio->getTitle())->toBe('Introduction');
-// })->with([MP3]);
+ $audio = Audio::read($newPath);
+ expect($audio->getTitle())->toBe('New Title');
+ expect($audio->getAlbumArtist())->toBeNull();
+})->with([MP3]);
diff --git a/tests/media/audiobook_rh-nocover.m4b b/tests/media/audiobook_rh-nocover.m4b
new file mode 100644
index 0000000..4017a1b
Binary files /dev/null and b/tests/media/audiobook_rh-nocover.m4b differ
diff --git a/tests/media/audiobook_rh.m4b b/tests/media/audiobook_rh.m4b
new file mode 100644
index 0000000..f5e4e88
Binary files /dev/null and b/tests/media/audiobook_rh.m4b differ
diff --git a/tests/media/cover.jpg b/tests/media/cover.jpg
new file mode 100644
index 0000000..4eb8fa2
Binary files /dev/null and b/tests/media/cover.jpg differ
diff --git a/tests/media/default-folder.jpg b/tests/media/default-folder.jpg
new file mode 100644
index 0000000..2a2de69
Binary files /dev/null and b/tests/media/default-folder.jpg differ