diff --git a/README.md b/README.md index de072b5..e32c706 100644 --- a/README.md +++ b/README.md @@ -413,6 +413,16 @@ You want to add a format? [See FAQ](#faq) `Audio::class` convert some properties to be more readable. +- `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` | diff --git a/src/Id3/Id3Writer.php b/src/Id3/Id3Writer.php index 48b0271..5815a4b 100644 --- a/src/Id3/Id3Writer.php +++ b/src/Id3/Id3Writer.php @@ -13,6 +13,7 @@ class Id3Writer /** * @param array $options * @param array $new_tags + * @param string[] $custom_tags * @param string[] $warnings * @param string[] $errors * @param string[] $tag_formats @@ -21,15 +22,14 @@ protected function __construct( protected Audio $audio, protected getid3_writetags $writer, protected AudioCore $core, - // protected array $options = ['encoding' => 'UTF-8'], + protected bool $is_manual = false, protected array $new_tags = [], - // protected array $warnings = [], - // protected array $errors = [], - // protected bool $override_tags = true, + protected array $custom_tags = [], + protected array $warnings = [], + protected array $errors = [], protected bool $remove_old_tags = false, - // protected bool $fail_on_error = true, + protected bool $fail_on_error = false, protected array $tag_formats = [], - // protected ?string $path = null, protected bool $success = false, ) {} @@ -63,6 +63,13 @@ public function title(string $title): self return $this; } + public function artist(string $artist): self + { + $this->core->artist = $artist; + + return $this; + } + public function album(string $album): self { $this->core->album = $album; @@ -70,15 +77,243 @@ public function album(string $album): self return $this; } - public function save(): void + public function albumArtist(string $album_artist): self + { + $this->core->album_artist = $album_artist; + + return $this; + } + + public function year(string $year): self + { + $this->core->year = $year; + + return $this; + } + + public function genre(string $genre): self + { + $this->core->genre = $genre; + + return $this; + } + + public function trackNumber(string|int $track_number): self + { + if (is_int($track_number)) { + $track_number = (string) $track_number; + } + + $this->core->track_number = $track_number; + + return $this; + } + + public function discNumber(string|int $disc_number): self + { + 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; + } + + 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; + } + + public function encodingBy(string $encoding_by): self + { + $this->core->encoding_by = $encoding_by; + + return $this; + } + + public function encoding(string $encoding): self + { + $this->core->encoding = $encoding; + + return $this; + } + + public function description(string $description): self + { + $this->core->description = $description; + + return $this; + } + + public function synopsis(string $synopsis): self + { + $this->core->synopsis = $synopsis; + + return $this; + } + + public function language(string $language): self + { + $this->core->language = $language; + + return $this; + } + + /** + * Add custom tags without dedicated method. + * + * Example: + * + * ```php + * $writer->tag('TXXX:CustomTag', 'CustomValue'); + * ``` + */ + public function tag(string $key, string $value): self { - $this->assignTags(); + $this->custom_tags[$key] = $value; + + return $this; + } + + /** + * Set manually tags, to know which key used for which tag, you have to refer to documentation. + * + * @docs https://github.com/kiwilan/php-audio#convert-properties + * + * For example, album artist for `id3` encoded files, is `band` key. + * + * @param array $tags + */ + public function tags(array $tags): self + { + $this->new_tags = $this->convertTags($tags); + $this->is_manual = true; + + return $this; + } + + /** + * Fail on errors, by default it's `false`. + */ + public function failOnErrors(): self + { + $this->fail_on_error = true; + + return $this; + } + + public function save(): bool + { + $this->parseTagFormats(); + if (! $this->is_manual) { + $this->assignTags(); + } $this->writer->tagformats = $this->tag_formats; $this->writer->tag_data = $this->new_tags; $this->success = $this->writer->WriteTags(); + + $this->errors = $this->writer->errors; + $this->warnings = $this->writer->warnings; + + $this->handleErrors(); ray($this); + + return $this->success; + } + + private function handleErrors(): void + { + $this->errors = $this->writer->errors; + $this->warnings = $this->writer->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->fail_on_error) { + throw new \Exception($msg); + } + } + + if (! $supported && $this->fail_on_error) { + throw new \Exception("Format {$this->audio->getFormat()->value} is not supported."); + } + + if (! empty($this->warnings)) { + error_log($warnings); + } } /** @@ -86,8 +321,6 @@ public function save(): void */ private function assignTags(): self { - $this->parseTagFormats(); - $convert = match ($this->audio->getType()) { AudioTypeEnum::id3 => AudioCore::toId3v2($this->core), AudioTypeEnum::vorbiscomment => AudioCore::toVorbisComment($this->core), @@ -106,21 +339,9 @@ private function assignTags(): self ...$this->audio->getRawTags(), // old tags ...$convert->toArray(), // new tags ]; + ray($this->new_tags); $this->new_tags = $this->convertTags($this->new_tags); - // $tags = []; - // if ($convert) { - // $tags = $convert->toArray(); - // } - - // $tags = $this->convertTags($tags); - // $this->attachCover($tags); - - // $this->tags = [ - // ...$this->tags, - // ...$tags, - // ]; - return $this; } diff --git a/tests/AudiobookTest.php b/tests/AudiobookTest.php index f9b4e6a..998eeb9 100644 --- a/tests/AudiobookTest.php +++ b/tests/AudiobookTest.php @@ -39,6 +39,7 @@ expect($raw['comment'])->toBe('English'); expect($raw['asin'])->toBe('ASIN'); expect($raw['album_artist'])->toBe('Robin Hobb'); + ray($raw); expect($audiobook->isWritable())->toBeTrue(); expect($audiobook->isValid())->toBeTrue(); diff --git a/tests/Pest.php b/tests/Pest.php index ce1dc99..53b07b8 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,7 @@ 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(); +} diff --git a/tests/WriterTest.php b/tests/WriterTest.php index 6df77a2..d78eb9b 100644 --- a/tests/WriterTest.php +++ b/tests/WriterTest.php @@ -4,39 +4,82 @@ use Kiwilan\Audio\Enums\AudioFormatEnum; use Kiwilan\Audio\Models\AudioCore; -it('can update tags', function () { +beforeEach(function () { $audio = Audio::get(MP3_WRITER); - 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())->toBeTrue(); + $audio->update() + ->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() + ->save(); +}); + +it('can update tags', function () { + $audio = Audio::get(MP3_WRITER); + testMp3Writer($audio); $audio->update() - ->album('P1PDD') + ->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') + ->failOnErrors() ->save(); - expect($audio->getAlbum())->toBe('P1PDD'); + $audio = Audio::get(MP3_WRITER); + testMp3Writed($audio); +}); - // $audio->update() - // ->album('P1PDD') - // ->save(); +// it('can update tags manually', function () { +// $audio = Audio::get(MP3_WRITER); +// testMp3Writer($audio); - // $tag = $audio->update() - // ->tags([ - // 'title' => 'New Title', - // ]) - // ->save(); +// $audio->update() +// ->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', +// 'text' => 'New Text', +// ]) +// ->save(); - // expect($audio->getAlbum())->toBe('P1PDD'); -}); +// $audio = Audio::get(MP3_WRITER); +// testMp3Writed($audio); +// }); // it('can update file', function (string $path) { // $audio = Audio::get($path);