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..de072b5 100644 --- a/README.md +++ b/README.md @@ -413,41 +413,33 @@ 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` | +| 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 diff --git a/composer.json b/composer.json index 5033e57..2c74a7b 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", @@ -41,10 +41,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..1c7a7b6 100755 --- a/src/Audio.php +++ b/src/Audio.php @@ -2,78 +2,51 @@ 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\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 @@ -91,303 +64,382 @@ 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. + * Get audio file path, like `/path/to/audio.mp3`. */ - public function update(): Id3Writer + public function getPath(): string { - return $this->writer; + return $this->path; } /** - * Get the value of `stat` method. + * Get audio file extension, like `mp3`. */ - public function getStat(): ?AudioStat + public function getExtension(): string { - return $this->stat; + return $this->extension; } /** - * `Id3Reader` with metadata. + * Get audio format if recognized, like `AudioFormatEnum::mp3`. */ - public function getReader(): Id3Reader + public function getFormat(): AudioFormatEnum { - return $this->reader; + return $this->format; } /** - * `Id3Writer` to update metadata. + * Get audio type if recognized, like `AudioTypeEnum::id3`. */ - public function getWriter(): ?Id3Writer + public function getType(): ?AudioTypeEnum { - return $this->writer; + return $this->type; } /** - * Get `title` metadata. + * Get audio metadata. */ - public function getTitle(): ?string + public function getMetadata(): ?AudioMetadata { - return $this->title; + return $this->metadata; } /** - * Get `artist` metadata. + * Get audio cover. */ - public function getArtist(): ?string + public function getCover(): ?AudioCover { - return $this->artist; + return $this->cover; + } + + public function getId3Reader(): ?Id3Reader + { + return Id3Reader::make($this->path); } /** - * Get `album` 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 getAlbum(): ?string + public function getDuration(): ?float { - return $this->album; + return $this->duration; } /** - * Get `genre` metadata. + * Get duration of the audio file in human readable format, like `00:03:00` */ - public function getGenre(): ?string + public function getDurationHuman(): ?string { - return $this->genre; + return gmdate('H:i:s', intval($this->duration)); } /** - * Get `year` metadata. + * To know if the audio file is writable. */ - public function getYear(): ?int + public function isWritable(): bool { - return $this->year; + return $this->is_writable; } /** - * Get `trackNumber` metadata. + * To know if the audio file is valid. */ - public function getTrackNumber(): ?string + public function isValid(): bool { - return $this->trackNumber; + return $this->is_valid; } /** - * Get `comment` metadata. + * To know if the audio file has cover. */ - public function getComment(): ?string + public function hasCover(): bool { - return $this->comment; + return $this->has_cover; } /** - * Get `albumArtist` metadata. + * Get `title` tag, like `Another Brick In The Wall`. */ - public function getAlbumArtist(): ?string + public function getTitle(): ?string { - return $this->albumArtist; + return $this->title; } /** - * Get `composer` metadata. + * Get `artist` tag, like `Pink Floyd`. */ - public function getComposer(): ?string + public function getArtist(): ?string { - return $this->composer; + return $this->artist; } /** - * Get `discNumber` metadata. + * Get `album` tag, like `The Wall`. */ - public function getDiscNumber(): ?string + public function getAlbum(): ?string { - return $this->discNumber; + return $this->album; } /** - * Know if audio file is a compilation. + * Get `genre` tag, like `Rock`. */ - public function isCompilation(): bool + public function getGenre(): ?string { - return $this->isCompilation; + return $this->genre; } /** - * Get `creationDate` metadata for audiobook. + * Get `year` tag, like `1979`. + * + * - For `matroska` format: `date` tag. + * - For `ape` format: `date` tag. */ - public function getCreationDate(): ?string + public function getYear(): ?int { - return $this->creationDate; + return $this->year; } /** - * Get `encodingBy` 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 getEncodingBy(): ?string + public function getTrackNumber(): ?string { - return $this->encodingBy; + return $this->track_number; } /** - * Get `encoding` metadata for audiobook. + * Get `track_number` tag as integer, like `1`. */ - public function getEncoding(): ?string + public function getTrackNumberInt(): ?int { - return $this->encoding; + return $this->track_number ? intval($this->track_number) : null; } /** - * Get `encoding` metadata for audiobook. + * Get `comment` tag, like `Recorded at Abbey Road Studios`. */ - public function getCopyright(): ?string + public function getComment(): ?string { - return $this->copyright; + return $this->comment; } /** - * Get `description` 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 getDescription(): ?string + public function getAlbumArtist(): ?string { - return $this->description; + return $this->album_artist; } /** - * Get `podcastDescription` metadata for audiobook. + * Get `composer` tag, like `Roger Waters`. */ - public function getPodcastDescription(): ?string + public function getComposer(): ?string { - return $this->podcastDescription; + return $this->composer; } /** - * Get `language` 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 getLanguage(): ?string + public function getDiscNumber(): ?string { - return $this->language; + return $this->disc_number; } /** - * Get `lyrics` metadata for audiobook. + * Get `disc_number` tag as integer, like `1`. */ - public function getLyrics(): ?string + public function getDiscNumberInt(): ?int { - return $this->lyrics; + 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 `stik` metadata for audiobook. + * 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 getStik(): ?string + public function isCompilation(): bool { - return $this->stik; + return $this->is_compilation; } /** - * Get `duration` in seconds. + * Get `creation_date` tag, like `1979-11-30`. + * + * - For `matroska` format: `date` tag. + * - For `ape` format: `date` tag. */ - public function getDuration(): ?float + public function getCreationDate(): ?string { - return $this->duration; + return $this->creation_date; } /** - * Get `duration` in human readable format: `00:00:00`. + * Get `encoding_by` tag, like `EAC`. */ - public function getDurationHumanReadable(): ?string + public function getEncodingBy(): ?string { - return gmdate('H:i:s', intval($this->duration)); + return $this->encoding_by; } /** - * Know if audio file is valid. + * Get `encoding` tag, like `LAME`. */ - public function isValid(): bool + public function getEncoding(): ?string { - return $this->isValid; + return $this->encoding; } /** - * Get `extras` with raw metadata. + * Get `copyright` tag, like `© 1979 Pink Floyd`. */ - public function getExtras(): array + public function getCopyright(): ?string { - return $this->extras; + return $this->copyright; } /** - * Get `audio` metadata with some audio information. + * Get `description` tag, like `The Wall is the eleventh studio album by the English rock band Pink Floyd`. */ - public function getAudio(): ?AudioMetadata + public function getDescription(): ?string { - return $this->audio; + return $this->description; } /** - * Know if audio file has cover. + * 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 hasCover(): bool + public function getSynopsis(): ?string { - return $this->hasCover; + return $this->synopsis; } /** - * Get `cover` metadata with some cover information. + * Get `language` tag, like `en`. */ - public function getCover(): ?AudioCover + public function getLanguage(): ?string { - return $this->cover; + return $this->language; } /** - * Get `path` of audio file. + * Get `lyrics` tag, like `We don't need no education`. */ - public function getPath(): string + public function getLyrics(): ?string { - return $this->path; + return $this->lyrics; } /** - * Get `extension` of audio file. + * Get raw tags as array with all formats. + * + * For example, for `mp3` format: `['id3v1' => [...], 'id3v2' => [...]]`. */ - public function getgetExtension(): string + public function getRawTagsAll(): array { - return $this->extension; + return $this->raw_tags_all; } /** - * Get `format` 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 getFormat(): AudioFormatEnum + public function getRawTags(?string $format = null): ?array { - return $this->format; + 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; } /** - * Get `type` of audio file. + * 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 getType(): ?AudioTypeEnum + public function getRawTagsKey(string $key, ?string $format = null): ?string { - return $this->type; + $tags = $this->getRawTags($format); + + return $tags[$key] ?? null; } - private function parse(): self + /** + * Get raw tags as array with main format, same as `getRawTags()`. + * + * @return string[] + */ + public function getExtras(): array { - $raw = $this->getReader()->getRaw(); - $reader = $this->getReader(); + return $this->getRawTags(); + } + private function parseTags(?\Kiwilan\Audio\Id3\Id3Reader $id3_reader): self + { $this->type = match ($this->format) { AudioFormatEnum::aac => null, AudioFormatEnum::aif => AudioTypeEnum::id3, @@ -412,58 +464,42 @@ private function parse(): self default => null, }; - $tags = $reader->getTags(); - if (! $tags) { + $tags = $id3_reader->getTags(); + if (! $tags || $tags->isEmpty()) { 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; - } - - 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; + $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::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; @@ -474,66 +510,21 @@ private function coreToProperties(?AudioCore $core): self $this->album = $core->getAlbum(); $this->genre = $core->getGenre(); $this->year = $core->getYear(); - $this->trackNumber = $core->getTrackNumber(); + $this->track_number = $core->getTrackNumber(); $this->comment = $core->getComment(); - $this->albumArtist = $core->getAlbumArtist(); + $this->album_artist = $core->getAlbumArtist(); $this->composer = $core->getComposer(); - $this->discNumber = $core->getDiscNumber(); - $this->isCompilation = $core->isCompilation(); - $this->creationDate = $core->getCreationDate(); - $this->encodingBy = $core->getEncodingBy(); + $this->disc_number = $core->getDiscNumber(); + $this->is_compilation = $core->isCompilation(); + $this->creation_date = $core->getCreationDate(); + $this->encoding_by = $core->getEncodingBy(); $this->encoding = $core->getEncoding(); $this->copyright = $core->getCopyright(); $this->description = $core->getDescription(); - $this->podcastDescription = $core->getPodcastDescription(); + $this->synopsis = $core->getSynopsis(); $this->language = $core->getLanguage(); $this->lyrics = $core->getLyrics(); - $this->stik = $core->getStik(); 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..79c7657 --- /dev/null +++ b/src/Core/AudioCore.php @@ -0,0 +1,624 @@ +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->track_number; + } + + public function getComment(): ?string + { + return $this->comment; + } + + public function getAlbumArtist(): ?string + { + return $this->album_artist; + } + + public function getComposer(): ?string + { + return $this->composer; + } + + public function getDiscNumber(): ?string + { + return $this->disc_number; + } + + public function isCompilation(): bool + { + if ($this->is_compilation === null) { + return false; + } + + return $this->is_compilation; + } + + public function getCreationDate(): ?string + { + return $this->creation_date; + } + + public function getCopyright(): ?string + { + return $this->copyright; + } + + public function getEncodingBy(): ?string + { + return $this->encoding_by; + } + + public function getEncoding(): ?string + { + return $this->encoding; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function getSynopsis(): ?string + { + return $this->synopsis; + } + + public function getLanguage(): ?string + { + return $this->language; + } + + public function getLyrics(): ?string + { + return $this->lyrics; + } + + public function hasCover(): bool + { + return $this->has_cover; + } + + 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 $track_number): self + { + $this->track_number = $track_number; + + return $this; + } + + public function setComment(?string $comment): self + { + $this->comment = $comment; + + return $this; + } + + public function setAlbumArtist(?string $album_artist): self + { + $this->album_artist = $album_artist; + + return $this; + } + + public function setComposer(?string $composer): self + { + $this->composer = $composer; + + return $this; + } + + public function setDiscNumber(?string $disc_number): self + { + $this->disc_number = $disc_number; + + return $this; + } + + public function setIsCompilation(bool $is_compilation): self + { + $this->is_compilation = $is_compilation; + + return $this; + } + + public function setCreationDate(?string $creation_date): self + { + $this->creation_date = $creation_date; + + return $this; + } + + public function setCopyright(?string $copyright): self + { + $this->copyright = $copyright; + + return $this; + } + + public function setEncodingBy(?string $encoding_by): self + { + $this->encoding_by = $encoding_by; + + 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 $synopsis): self + { + $this->synopsis = $synopsis; + + 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 setHasCover(bool $has_cover): self + { + $this->has_cover = $has_cover; + + 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, + '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, + 'has_cover' => $this->has_cover, + 'cover' => $this->cover?->toArray(), + ]; + } + + public static function toId3v2(AudioCore $core): Tag\Id3TagAudioV2 + { + return new Tag\Id3TagAudioV2( + 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: (string) $core->getYear(), + copyright: $core->getCopyright(), + text: $core->getSynopsis(), + unsynchronised_lyric: $core->getLyrics(), + language: $core->getLanguage(), + ); + } + + public static function toId3v1(AudioCore $core): Tag\Id3TagAudioV1 + { + return new Tag\Id3TagAudioV1( + album: $core->getAlbum(), + artist: $core->getArtist(), + comment: $core->getComment(), + genre: $core->getGenre(), + title: $core->getTitle(), + track_number: $core->getTrackNumber(), + year: (string) $core->getYear(), + ); + } + + public static function toVorbisComment(AudioCore $core): Tag\Id3TagVorbisComment + { + return new Tag\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: (string) $core->getYear(), + encoder: $core->getEncoding(), + description: $core->getDescription(), + ); + } + + public static function toQuicktime(AudioCore $core): Tag\Id3TagQuicktime + { + return new Tag\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->getSynopsis(), + lyrics: $core->getLyrics(), + comment: $core->getComment(), + ); + } + + public static function toMatroska(AudioCore $core): Tag\Id3TagMatroska + { + return new Tag\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: (string) $core->getYear(), + encoder: $core->getEncoding(), + ); + } + + public static function toApe(AudioCore $core): Tag\Id3TagApe + { + return new Tag\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: (string) $core->getYear(), + encoder: $core->getEncoding(), + ); + } + + public static function toAsf(AudioCore $core): Tag\Id3TagAsf + { + return new Tag\Id3TagAsf( + album: $core->getAlbum(), + artist: $core->getArtist(), + albumartist: $core->getAlbumArtist(), + composer: $core->getComposer(), + partofset: $core->getDiscNumber(), + genre: $core->getGenre(), + track_number: $core->getTrackNumber(), + year: (string) $core->getYear(), + encodingsettings: $core->getEncoding(), + ); + } + + 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 + { + $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, + 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, + ); + + 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); + + 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..574b188 --- /dev/null +++ b/src/Core/AudioCoreCover.php @@ -0,0 +1,66 @@ +data = base64_encode(file_get_contents($pathOrData)); + $self->picture_type_id = $image[2]; + $self->description = 'cover'; + $self->mime = $image['mime']; + + return $self; + } + + $image = getimagesizefromstring($pathOrData); + $self->data = base64_encode($pathOrData); + $self->picture_type_id = $image[2]; + $self->mime = $image['mime']; + $self->description = 'cover'; + + return $self; + } + + public function data(): ?string + { + return $this->data; + } + + public function pictureTypeId(): ?int + { + return $this->picture_type_id; + } + + public function description(): ?string + { + return $this->description; + } + + public function mime(): ?string + { + return $this->mime; + } + + public function toArray(): array + { + return [ + 'data' => $this->data, + 'picture_type_id' => $this->picture_type_id, + 'description' => $this->description, + 'mime' => $this->mime, + ]; + } +} diff --git a/src/Id3/Id3Reader.php b/src/Id3/Id3Reader.php new file mode 100644 index 0000000..eae4a5e --- /dev/null +++ b/src/Id3/Id3Reader.php @@ -0,0 +1,240 @@ +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); + + $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->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->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 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/Reader/Id3Audio.php b/src/Id3/Reader/Id3Audio.php new file mode 100644 index 0000000..348bd27 --- /dev/null +++ b/src/Id3/Reader/Id3Audio.php @@ -0,0 +1,94 @@ +streams = $streams; + + return $self; + } + + /** @return Id3Stream[] */ + public function streams(): array + { + return $this->streams; + } + + public function dataFormat(): ?string + { + return $this->data_format; + } + + public function channels(): ?int + { + return $this->channels; + } + + public function sampleRate(): ?int + { + return $this->sample_rate; + } + + public function bitrate(): ?float + { + return $this->bitrate; + } + + public function channelMode(): ?string + { + return $this->channel_mode; + } + + public function bitrateMode(): ?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 encoderOptions(): ?string + { + return $this->encoder_options; + } + + public function compressionRatio(): ?float + { + return $this->compression_ratio; + } + + public function stream(): ?Id3Stream + { + return $this->streams[0] ?? null; + } +} diff --git a/src/Id3/Reader/Id3AudioBase.php b/src/Id3/Reader/Id3AudioBase.php new file mode 100644 index 0000000..0a97dfb --- /dev/null +++ b/src/Id3/Reader/Id3AudioBase.php @@ -0,0 +1,102 @@ +data_format = $metadata['dataformat'] ?? null; + $this->channels = $metadata['channels'] ?? null; + $this->sample_rate = $metadata['sample_rate'] ?? null; + $this->bitrate = $metadata['bitrate'] ?? null; + $this->channel_mode = $metadata['channelmode'] ?? null; + $this->bitrate_mode = $metadata['bitrate_mode'] ?? null; + $this->codec = $metadata['codec'] ?? null; + $this->encoder = $metadata['encoder'] ?? null; + $this->lossless = $metadata['lossless'] ?? false; + $this->encoder_options = $metadata['encoder_options'] ?? null; + $this->compression_ratio = $metadata['compression_ratio'] ?? null; + } + + public function dataFormat(): ?string + { + return $this->data_format; + } + + public function channels(): ?int + { + return $this->channels; + } + + public function sampleRate(): ?int + { + return $this->sample_rate; + } + + public function bitrate(): ?float + { + return $this->bitrate; + } + + public function channelMode(): ?string + { + return $this->channel_mode; + } + + public function bitrateMode(): ?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 encoderOptions(): ?string + { + return $this->encoder_options; + } + + public function compressionRatio(): ?float + { + return $this->compression_ratio; + } +} diff --git a/src/Id3/Reader/Id3AudioTag.php b/src/Id3/Reader/Id3AudioTag.php new file mode 100644 index 0000000..53ee36d --- /dev/null +++ b/src/Id3/Reader/Id3AudioTag.php @@ -0,0 +1,99 @@ +id3v1 && ! $self->id3v2 && ! $self->quicktime && ! $self->asf && ! $self->vorbiscomment && ! $self->riff && ! $self->matroska && ! $self->ape) { + $self->is_empty = true; + } + + return $self; + } + + public function id3v1(): ?Tag\Id3TagAudioV1 + { + return $this->id3v1; + } + + public function id3v2(): ?Tag\Id3TagAudioV2 + { + return $this->id3v2; + } + + public function quicktime(): ?Tag\Id3TagQuicktime + { + return $this->quicktime; + } + + public function asf(): ?Tag\Id3TagAsf + { + return $this->asf; + } + + public function vorbiscomment(): ?Tag\Id3TagVorbisComment + { + return $this->vorbiscomment; + } + + public function riff(): ?Tag\Id3TagRiff + { + return $this->riff; + } + + public function matroska(): ?Tag\Id3TagMatroska + { + return $this->matroska; + } + + public function ape(): ?Tag\Id3TagApe + { + return $this->ape; + } + + public function isEmpty(): bool + { + return $this->is_empty; + } +} diff --git a/src/Id3/Reader/Id3Comments.php b/src/Id3/Reader/Id3Comments.php new file mode 100644 index 0000000..c0156f9 --- /dev/null +++ b/src/Id3/Reader/Id3Comments.php @@ -0,0 +1,28 @@ +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..24ff8df 100644 --- a/src/Models/AudioCover.php +++ b/src/Models/AudioCover.php @@ -2,55 +2,66 @@ 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; diff --git a/src/Models/AudioMetadata.php b/src/Models/AudioMetadata.php index 0e04738..909bec8 100644 --- a/src/Models/AudioMetadata.php +++ b/src/Models/AudioMetadata.php @@ -2,125 +2,264 @@ namespace Kiwilan\Audio\Models; +use DateTime; use Kiwilan\Audio\Audio; +use Kiwilan\Audio\Id3\Id3Reader; 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 ?string $encoding = null, - protected ?string $mimeType = null, - protected ?float $durationSeconds = null, - protected ?string $durationReadable = null, + protected ?string $mime_type = 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(), + file_size: $id3_reader->getFileSize(), + data_format: $audio?->dataFormat(), + encoding: $id3_reader->getEncoding(), + mime_type: $id3_reader->getMimeType(), + duration_seconds: $id3_reader->getPlaytimeSeconds(), + bitrate: intval($id3_reader->getBitrate()), + bitrate_mode: $audio?->bitrateMode(), + sample_rate: $audio?->sampleRate(), channels: $audio?->channels(), - channelMode: $audio?->channelmode(), - lossless: $audio?->lossless() ?? false, - compressionRatio: $audio?->compression_ratio(), + channel_mode: $audio?->channelMode(), + is_lossless: $audio?->lossless() ?? false, + compression_ratio: $audio?->compressionRatio(), + codec: $audio?->codec(), + encoder_options: $audio?->encoderOptions(), + 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); - public function getExtension(): ?string - { - return $this->extension; + return sprintf("%.{$decimals}f", $file_size / pow(1024, $factor)).' '.$size[$factor]; } - public function getDataformat(): ?string + /** + * Get data format of the audio file, like `mp3`, `wav`, `etc` + */ + public function getDataFormat(): ?string { - return $this->dataformat; + return $this->data_format; } + /** + * 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 duration of the audio file in seconds, like `11.05` + */ + public function getDurationSeconds(?int $decimals = null): ?float { - return $this->durationSeconds; - } + if ($decimals !== null) { + return round($this->duration_seconds, $decimals); + } - public function getDurationReadable(): ?string - { - return $this->durationReadable; + 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; } - public function getLossless(): bool + /** + * Get created time of the audio file, like `2021-09-01 00:00:00` + */ + public function getCreatedAt(): ?DateTime { - return $this->lossless; + return $this->created_at; } - public function getCompressionRatio(): ?float + /** + * Get modified time of the audio file, like `2021-09-01 00:00:00` + */ + public function getModifiedAt(): ?DateTime { - return $this->compressionRatio; + return $this->modified_at; } } 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 index d403cdb..e4c7c8f 100644 --- a/src/Models/Id3Writer.php +++ b/src/Models/Id3Writer.php @@ -5,6 +5,7 @@ use DateTime; use getid3_writetags; use Kiwilan\Audio\Audio; +use Kiwilan\Audio\Core\AudioCore; use Kiwilan\Audio\Enums\AudioFormatEnum; use Kiwilan\Audio\Enums\AudioTypeEnum; @@ -221,13 +222,6 @@ public function lyrics(?string $lyrics): self return $this; } - public function stik(?string $stik): self - { - $this->core->setStik($stik); - - return $this; - } - /** * @param string $pathOrData Path to cover image or binary data */ @@ -331,6 +325,13 @@ public function save(): bool $this->instance->tagformats = $this->tagFormats; $this->instance->tag_data = $this->tags; + // $this->instance->overwrite_tags = false; + // $this->instance->remove_other_tags = false; + // ray($this->instance); + + // $this->core->setTitle('test'); + // ray($this->core); + // ray($this->instance); $this->success = $this->instance->WriteTags(); $this->errors = $this->instance->errors; @@ -371,7 +372,7 @@ public function save(): bool } if (! $supported && $this->failOnError) { - throw new \Exception("Format {$this->audio->getFormat()?->value} is not supported."); + throw new \Exception("Format {$this->audio->getFormat()->value} is not supported."); } if (! empty($this->warnings)) { @@ -381,6 +382,11 @@ public function save(): bool return $this->success; } + private function assignCurrentTags(): self + { + // + } + private function automaticConvert(): self { $this->convertTagFormats(); 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..02f3d0a 100644 --- a/tests/AudioCoreTest.php +++ b/tests/AudioCoreTest.php @@ -1,8 +1,8 @@ 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'); diff --git a/tests/AudioCoverTest.php b/tests/AudioCoverTest.php new file mode 100644 index 0000000..cd68e29 --- /dev/null +++ b/tests/AudioCoverTest.php @@ -0,0 +1,34 @@ +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]); diff --git a/tests/AudioMetadataTest.php b/tests/AudioMetadataTest.php new file mode 100644 index 0000000..62c4661 --- /dev/null +++ b/tests/AudioMetadataTest.php @@ -0,0 +1,87 @@ +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::get($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]); diff --git a/tests/AudioMp3Test.php b/tests/AudioMp3Test.php new file mode 100644 index 0000000..53e27c6 --- /dev/null +++ b/tests/AudioMp3Test.php @@ -0,0 +1,117 @@ +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->getRawTagsAll())->toBeArray(); + expect($audio->getRawTagsAll()['id3v1'])->toBeArray(); + expect($audio->getRawTagsAll()['id3v1'])->toHaveCount(6); + expect($audio->getRawTagsAll()['id3v2'])->toBeArray(); + expect($audio->getRawTagsAll()['id3v2'])->toHaveCount(11); + expect($audio->getRawTags())->toHaveCount(11); + expect($audio->getRawTags('id3v2'))->toHaveCount(11); + expect($audio->getRawTagsKey('title'))->toBe('Introduction'); + expect($audio->getExtras())->toBeArray(); + + $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::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/AudioTest.php b/tests/AudioTest.php index 2bd29d2..d743271 100644 --- a/tests/AudioTest.php +++ b/tests/AudioTest.php @@ -2,140 +2,61 @@ 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) { +it('can read basic info', function (string $path) { $audio = Audio::get($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::get(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::get(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::get(FLAC); - expect($audio->isValid())->toBeFalse(); + expect($audio->getDescription())->toBe('http://www.p1pdd.com'); +}); + +it('can read creation date', function () { + $audio = Audio::get(WV); + + expect($audio->getCreationDate())->toBe('2016'); }); it('can read file id3v1', function (string $path) { @@ -150,11 +71,17 @@ 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->getDurationHuman())->toBe('00:00:11'); expect($audio->getExtras())->toBeArray(); expect($audio)->toBeInstanceOf(Audio::class); })->with([...AUDIO_ID3_V1]); + +it('can read wrong audio file', function () { + $audio = Audio::get(MD); + + expect($audio->isValid())->toBeFalse(); +}); diff --git a/tests/AudiobookTest.php b/tests/AudiobookTest.php index 3bcc230..f9b4e6a 100644 --- a/tests/AudiobookTest.php +++ b/tests/AudiobookTest.php @@ -2,6 +2,70 @@ use Kiwilan\Audio\Audio; use Kiwilan\Audio\Enums\AudioFormatEnum; +use Kiwilan\Audio\Enums\AudioTypeEnum; +use Kiwilan\Audio\Models\AudioMetadata; + +it('can read audiobook', function () { + $audiobook = Audio::get(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); + + $raw = $audiobook->getRawTags(); + 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($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 file m4b', function (string $file) { $audio = Audio::get($file); @@ -24,31 +88,25 @@ 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->getDurationHuman())->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->getRawTags())->toBeArray(); + expect($audio->getRawTagsKey('title'))->toBe('P1PDD Saison 1'); + expect($audio->getRawTagsKey('artist'))->toBe('Mr Piouf'); + expect($audio->getRawTagsKey('album'))->toBe('P1PDD Saison 1'); + expect($audio->getRawTagsKey('genre'))->toBe('Audiobooks'); + expect($audio->getRawTagsKey('track_number'))->toBe('1/1'); + expect($audio->getRawTagsKey('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); - - expect(count($audio->getAudioFormats()))->toBe(3); - expect($audio->getAudioFormats())->toBeArray(); - expect($audio->toArray())->toBeArray(); + expect(count($audio->getRawTags()))->toBe(15); + expect(count($audio->getRawTags('id3v2')))->toBe(15); })->with([AUDIOBOOK_MP3]); 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..ce1dc99 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -76,6 +76,9 @@ function addWriterFilesForTests() define('WMA', __DIR__.'/media/test.wma'); define('WV', __DIR__.'/media/test.wv'); +define('AUDIOBOOK_RH', __DIR__.'/media/audiobook_rh.m4b'); +define('AUDIOBOOK_RH_NOCOVER', __DIR__.'/media/audiobook_rh-nocover.m4b'); + define('AUDIOBOOKS', [ AUDIOBOOK, AUDIOBOOK_MP3, @@ -131,3 +134,16 @@ function addWriterFilesForTests() // WMA_WRITER, // WV_WRITER, ]); + +function clearOutput() +{ + $files = glob('./tests/output/*'); + foreach ($files as $file) { + if (is_file($file)) { + if ($file === './tests/output/.gitignore') { + continue; + } + unlink($file); + } + } +} diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index dda2a98..c7821db 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]->dataFormat())->toBe('mp3'); + expect($streams[0]->channels())->toBe(2); + expect($streams[0]->sampleRate())->toBe(44100); + expect($streams[0]->bitrate())->toBe(128000.0); + expect($streams[0]->channelmode())->toBe('joint stereo'); + expect($streams[0]->bitrateMode())->toBe('cbr'); + expect($streams[0]->codec())->toBe('LAME'); + expect($streams[0]->encoder())->toBe('LAME3.100'); + expect($streams[0]->lossless())->toBeFalse(); + expect($streams[0]->encoderOptions())->toBe('CBR128'); + expect($streams[0]->compressionRatio())->toBe(0.09070294784580499); +}); it('can parse ID3 reader', function (string $path) { $audio = Audio::get($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/WriterTest.php b/tests/WriterTest.php index a1c3c60..a4d2d84 100644 --- a/tests/WriterTest.php +++ b/tests/WriterTest.php @@ -4,276 +4,276 @@ use Kiwilan\Audio\Enums\AudioFormatEnum; use Kiwilan\Audio\Models\AudioCore; -it('can update file', function (string $path) { - $audio = Audio::get($path); - $random = (string) rand(1, 1000); - $tag = $audio->update() - ->title($random) - ->artist('New Artist') - ->album('New Album') - ->genre('New Genre') - ->year('2022') - ->trackNumber('2/10') - ->albumArtist('New Album Artist') - ->comment('New Comment') - ->composer('New Composer') - ->creationDate('2021-01-01') - ->description('New Description') - ->discNumber('2/2') - ->encodingBy('New Encoding By') - ->encoding('New Encoding') - ->isNotCompilation() - ->lyrics('New Lyrics') - ->stik('New Stik') - ->cover(FOLDER); - - $core = $tag->getCore(); - $tag->save(); - - $audio = Audio::get($path); - - expect($audio->getTitle())->toBe($random); - 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(); - - expect($audio->getCreationDate())->toBeNull(); - if ($audio->getFormat() === AudioFormatEnum::mp3) { - expect($audio->getDescription())->toBeNull(); - expect($audio->getEncoding())->toBeNull(); - } - expect($audio->getEncodingBy())->toBeNull(); - 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); - - $random = (string) rand(1, 1000); - $tag = $audio->update() - ->tags([ - 'title' => $random, - ]) - ->tagFormats(['id3v1', 'id3v2.4']); - - $tag->save(); - - $audio = Audio::get($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); - - $tag = $audio->update() - ->isCompilation() - ->tags([ - 'title' => 'New Title', - 'band' => 'New Band', - ]) - ->tagFormats(['id3v1', 'id3v2.4']); - - $tag->save(); - - $audio = Audio::get($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 file', function (string $path) { +// $audio = Audio::get($path); +// $random = (string) rand(1, 1000); +// $tag = $audio->update() +// ->title($random) +// ->artist('New Artist') +// ->album('New Album') +// ->genre('New Genre') +// ->year('2022') +// ->trackNumber('2/10') +// ->albumArtist('New Album Artist') +// ->comment('New Comment') +// ->composer('New Composer') +// ->creationDate('2021-01-01') +// ->description('New Description') +// ->discNumber('2/2') +// ->encodingBy('New Encoding By') +// ->encoding('New Encoding') +// ->isNotCompilation() +// ->lyrics('New Lyrics') +// ->stik('New Stik') +// ->cover(FOLDER); + +// $core = $tag->getCore(); +// $tag->save(); + +// $audio = Audio::get($path); + +// expect($audio->getTitle())->toBe($random); +// 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(); + +// expect($audio->getCreationDate())->toBeNull(); +// if ($audio->getFormat() === AudioFormatEnum::mp3) { +// expect($audio->getDescription())->toBeNull(); +// expect($audio->getEncoding())->toBeNull(); +// } +// expect($audio->getEncodingBy())->toBeNull(); +// 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); + +// $random = (string) rand(1, 1000); +// $tag = $audio->update() +// ->tags([ +// 'title' => $random, +// ]) +// ->tagFormats(['id3v1', 'id3v2.4']); + +// $tag->save(); + +// $audio = Audio::get($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); + +// $tag = $audio->update() +// ->isCompilation() +// ->tags([ +// 'title' => 'New Title', +// 'band' => 'New Band', +// ]) +// ->tagFormats(['id3v1', 'id3v2.4']); + +// $tag->save(); + +// $audio = Audio::get($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(); -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', - ]); +// $audio = Audio::get($newPath); +// expect($audio->getTitle())->toBe('New Title'); +// })->with([MP3_WRITER]); - $tag->save(); +// it('can update with merged tags and core methods', function (string $path) { +// $audio = Audio::get($path); - $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); - - $tag = $audio->update() - ->title('New Title') - ->encoding('New encoding'); - - expect(fn () => $tag->save())->not()->toThrow(Exception::class); - - $audio = Audio::get($path); - expect($audio->getTitle())->toBe('New Title'); -})->with([MP3_WRITER]); +// $tag = $audio->update() +// ->title('New Title') +// ->tags([ +// 'title' => 'New Title tag', +// 'band' => 'New Band', +// ]); -it('can use arrow function safe with unsupported formats', function (string $path) { - $audio = Audio::get($path); +// $tag->save(); - $tag = $audio->update() - ->title('New Title Alac'); +// $audio = Audio::get($path); +// expect($audio->getTitle())->toBe('New Title'); +// expect($audio->getAlbumArtist())->toBe('New Band'); +// })->with([MP3_WRITER]); - expect(fn () => $tag->save())->toThrow(Exception::class); -})->with([ALAC_WRITER]); +// it('can use arrow function safe with unsupported tags', function (string $path) { +// $audio = Audio::get($path); -it('can get core before save', function (string $path) { - $audio = Audio::get($path); +// $tag = $audio->update() +// ->title('New Title') +// ->encoding('New encoding'); - $tag = $audio->update() - ->title('New Title') - ->tags([ - 'title' => 'New Title tag', - 'band' => 'New Band', - ]); +// expect(fn () => $tag->save())->not()->toThrow(Exception::class); - expect($tag->getCore())->toBeInstanceOf(AudioCore::class); -})->with([MP3_WRITER]); +// $audio = Audio::get($path); +// expect($audio->getTitle())->toBe('New Title'); +// })->with([MP3_WRITER]); -it('can handle exceptions', function (string $path) { - $audio = Audio::get($path); +// it('can use arrow function safe with unsupported formats', function (string $path) { +// $audio = Audio::get($path); - $tag = $audio->update() - ->tags([ - 'title' => 'New Title', - 'albumArtist' => 'New Album Artist', - ]) - ->options(['encoding' => 'UTF-8']); +// $tag = $audio->update() +// ->title('New Title Alac'); - expect(fn () => $tag->save())->toThrow(Exception::class); -})->with([MP3_WRITER]); +// expect(fn () => $tag->save())->toThrow(Exception::class); +// })->with([ALAC_WRITER]); -it('can skip exceptions', function (string $path) { - $audio = Audio::get($path); +// it('can get core before save', function (string $path) { +// $audio = Audio::get($path); - $tag = $audio->update() - ->tags([ - 'title' => 'New Title', - 'albumArtist' => 'New Album Artist', - ]) - ->preventFailOnError(); +// $tag = $audio->update() +// ->title('New Title') +// ->tags([ +// 'title' => 'New Title tag', +// 'band' => 'New Band', +// ]); - $tag->save(); +// expect($tag->getCore())->toBeInstanceOf(AudioCore::class); +// })->with([MP3_WRITER]); - $audio = Audio::get($path); - expect($audio->getTitle())->toBe('New Title'); - expect($audio->getAlbumArtist())->toBeNull(); -})->with([MP3_WRITER]); +// it('can handle exceptions', function (string $path) { +// $audio = Audio::get($path); -it('can remove old tags', function (string $path) { - $audio = Audio::get($path); +// $tag = $audio->update() +// ->tags([ +// 'title' => 'New Title', +// 'albumArtist' => 'New Album Artist', +// ]) +// ->options(['encoding' => 'UTF-8']); - $tag = $audio->update() - ->title('New Title') - ->removeOldTags() - ->path('tests/output/new.mp3'); +// expect(fn () => $tag->save())->toThrow(Exception::class); +// })->with([MP3_WRITER]); - $tag->save(); +// it('can skip exceptions', function (string $path) { +// $audio = Audio::get($path); - $audio = Audio::get('tests/output/new.mp3'); - expect($audio->getTitle())->toBe('New Title'); - expect($audio->getAlbumArtist())->toBeNull(); -})->with([MP3]); +// $tag = $audio->update() +// ->tags([ +// 'title' => 'New Title', +// 'albumArtist' => 'New Album Artist', +// ]) +// ->preventFailOnError(); -it('can use tags with cover', function (string $path) { - $audio = Audio::get($path); +// $tag->save(); - $tag = $audio->update() - ->tags([ - 'title' => 'New Title', - ]) - ->cover(FOLDER); +// $audio = Audio::get($path); +// expect($audio->getTitle())->toBe('New Title'); +// expect($audio->getAlbumArtist())->toBeNull(); +// })->with([MP3_WRITER]); - $tag->save(); +// it('can remove old tags', function (string $path) { +// $audio = Audio::get($path); - $audio = Audio::get($path); +// $tag = $audio->update() +// ->title('New Title') +// ->removeOldTags() +// ->path('tests/output/new.mp3'); + +// $tag->save(); + +// $audio = Audio::get('tests/output/new.mp3'); +// expect($audio->getTitle())->toBe('New Title'); +// expect($audio->getAlbumArtist())->toBeNull(); +// })->with([MP3]); + +// it('can use tags with cover', function (string $path) { +// $audio = Audio::get($path); + +// $tag = $audio->update() +// ->tags([ +// 'title' => 'New Title', +// ]) +// ->cover(FOLDER); - $content = file_get_contents(FOLDER); - expect($audio->getTitle())->toBe('New Title'); - expect($tag->getCore()->getCover()->data())->toBe(base64_encode($content)); -})->with([MP3_WRITER]); +// $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)); +// })->with([MP3_WRITER]); // it('can change podcast description and language', function () { // $audio = Audio::get(AUDIOBOOK); @@ -297,3 +297,33 @@ // $audio = Audio::get('tests/output/new.mp3'); // expect($audio->getTitle())->toBe('Introduction'); // })->with([MP3]); + +it('can update tags', function () { + $audio = Audio::get(MP3); + + expect($audio->getAlbum())->toBe('P1PDD Le conclave de Troie'); + expect($audio->getArtist())->toBe('Mr Piouf'); + expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf'); + expect($audio->getTitle())->toBe('Introduction'); + expect($audio->getTrackNumber())->toBe('1'); + expect($audio->getYear())->toBe(2016); + + ray($audio->getGenre()); + expect($audio->getGenre())->toBe('Roleplaying game'); + + // $audio->update() + // ->album('P1PDD') + // ->save(); + + // $audio->update() + // ->album('P1PDD') + // ->save(); + + // $tag = $audio->update() + // ->tags([ + // 'title' => 'New Title', + // ]) + // ->save(); + + // expect($audio->getAlbum())->toBe('P1PDD'); +}); 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