diff --git a/src/Audio.php b/src/Audio.php index 1c7a7b6..ad50b12 100755 --- a/src/Audio.php +++ b/src/Audio.php @@ -6,6 +6,7 @@ use Kiwilan\Audio\Enums\AudioFormatEnum; use Kiwilan\Audio\Enums\AudioTypeEnum; use Kiwilan\Audio\Id3\Id3Reader; +use Kiwilan\Audio\Id3\Id3Writer; use Kiwilan\Audio\Models\AudioCover; use Kiwilan\Audio\Models\AudioMetadata; @@ -134,6 +135,11 @@ public function getId3Reader(): ?Id3Reader return Id3Reader::make($this->path); } + public function update(): Id3Writer + { + return Id3Writer::make($this); + } + /** * Get duration of the audio file in seconds, limited to 2 decimals, like `180.66` * @@ -465,7 +471,7 @@ private function parseTags(?\Kiwilan\Audio\Id3\Id3Reader $id3_reader): self }; $tags = $id3_reader->getTags(); - if (! $tags || $tags->isEmpty()) { + if (! $tags || $tags->is_empty) { return $this; } @@ -475,12 +481,12 @@ private function parseTags(?\Kiwilan\Audio\Id3\Id3Reader $id3_reader): self } $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()), + 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, }; @@ -505,25 +511,25 @@ private function convertCore(?AudioCore $core): self return $this; } - $this->title = $core->getTitle(); - $this->artist = $core->getArtist(); - $this->album = $core->getAlbum(); - $this->genre = $core->getGenre(); - $this->year = $core->getYear(); - $this->track_number = $core->getTrackNumber(); - $this->comment = $core->getComment(); - $this->album_artist = $core->getAlbumArtist(); - $this->composer = $core->getComposer(); - $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->synopsis = $core->getSynopsis(); - $this->language = $core->getLanguage(); - $this->lyrics = $core->getLyrics(); + $this->title = $core->title; + $this->artist = $core->artist; + $this->album = $core->album; + $this->genre = $core->genre; + $this->year = $core->year; + $this->track_number = $core->track_number; + $this->comment = $core->comment; + $this->album_artist = $core->album_artist; + $this->composer = $core->composer; + $this->disc_number = $core->disc_number; + $this->is_compilation = $core->is_compilation; + $this->creation_date = $core->creation_date; + $this->encoding_by = $core->encoding_by; + $this->encoding = $core->encoding; + $this->copyright = $core->copyright; + $this->description = $core->description; + $this->synopsis = $core->synopsis; + $this->language = $core->language; + $this->lyrics = $core->lyrics; return $this; } diff --git a/src/Core/AudioCore.php b/src/Core/AudioCore.php index 79c7657..55e9ec0 100644 --- a/src/Core/AudioCore.php +++ b/src/Core/AudioCore.php @@ -7,435 +7,174 @@ class AudioCore { public function __construct( - 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 bool $has_cover = false, - protected ?AudioCoreCover $cover = null, + public ?string $title = null, + public ?string $artist = null, + public ?string $album = null, + public ?string $genre = null, + public ?int $year = null, + public ?string $track_number = null, + public ?string $comment = null, + public ?string $album_artist = null, + public ?string $composer = null, + public ?string $disc_number = null, + public ?bool $is_compilation = null, + public ?string $creation_date = null, + public ?string $copyright = null, + public ?string $encoding_by = null, + public ?string $encoding = null, + public ?string $description = null, + public ?string $synopsis = null, + public ?string $language = null, + public ?string $lyrics = null, + public bool $has_cover = false, + public ?AudioCoreCover $cover = null, ) {} - public function getTitle(): ?string - { - return $this->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 + public function toArray(): array { - return $this->year; - } + // parse all properties + $properties = get_object_vars($this); - public function getTrackNumber(): ?string - { - return $this->track_number; - } + // filter out null values + $properties = array_filter($properties, fn ($value) => $value !== null); + $properties = array_filter($properties, fn ($value) => $value !== ''); - public function getComment(): ?string - { - return $this->comment; + return $properties; } - public function getAlbumArtist(): ?string + private function parseCompilation(AudioCore $core): ?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; + if ($core->is_compilation === null) { + return null; } - 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(), - ]; + return $core->is_compilation ? '1' : '0'; } 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(), + album: $core->album, + artist: $core->artist, + band: $core->album_artist, + comment: $core->comment, + composer: $core->composer, + part_of_a_set: $core->disc_number, + genre: $core->genre, + part_of_a_compilation: $core->parseCompilation($core), + title: $core->title, + track_number: $core->track_number, + year: (string) $core->year, + copyright: $core->copyright, + text: $core->synopsis, + unsynchronised_lyric: $core->lyrics, + language: $core->language, ); } 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(), + album: $core->album, + artist: $core->artist, + comment: $core->comment, + genre: $core->genre, + title: $core->title, + track_number: $core->track_number, + year: (string) $core->year, ); } public static function toVorbisComment(AudioCore $core): Tag\Id3TagVorbisComment { return new Tag\Id3TagVorbisComment( - album: $core->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(), + album: $core->album, + artist: $core->artist, + albumartist: $core->album_artist, + comment: $core->comment, + composer: $core->composer, + compilation: $core->parseCompilation($core), + discnumber: $core->disc_number, + genre: $core->genre, + title: $core->title, + tracknumber: $core->track_number, + date: (string) $core->year, + encoder: $core->encoding, + description: $core->description, ); } public static function toQuicktime(AudioCore $core): Tag\Id3TagQuicktime { return new Tag\Id3TagQuicktime( - title: $core->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(), + title: $core->title, + track_number: $core->track_number, + disc_number: $core->disc_number, + compilation: $core->parseCompilation($core), + album: $core->album, + genre: $core->genre, + composer: $core->composer, + creation_date: $core->creation_date, + copyright: $core->copyright, + artist: $core->artist, + album_artist: $core->album_artist, + encoded_by: $core->encoding, + encoding_tool: $core->encoding, + description: $core->description, + description_long: $core->synopsis, + lyrics: $core->lyrics, + comment: $core->comment, ); } public static function toMatroska(AudioCore $core): Tag\Id3TagMatroska { return new Tag\Id3TagMatroska( - title: $core->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(), + title: $core->title, + album: $core->album, + artist: $core->artist, + album_artist: $core->album_artist, + comment: $core->comment, + composer: $core->composer, + disc: $core->disc_number, + compilation: $core->parseCompilation($core), + genre: $core->genre, + part_number: $core->track_number, + date: (string) $core->year, + encoder: $core->encoding, ); } public static function toApe(AudioCore $core): Tag\Id3TagApe { return new Tag\Id3TagApe( - album: $core->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(), + album: $core->album, + artist: $core->artist, + album_artist: $core->album_artist, + comment: $core->comment, + composer: $core->composer, + disc: $core->disc_number, + compilation: $core->parseCompilation($core), + genre: $core->genre, + title: $core->title, + track: $core->track_number, + date: (string) $core->year, + encoder: $core->encoding, ); } public static function toAsf(AudioCore $core): Tag\Id3TagAsf { return new Tag\Id3TagAsf( - album: $core->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(), + album: $core->album, + artist: $core->artist, + albumartist: $core->album_artist, + composer: $core->composer, + partofset: $core->disc_number, + genre: $core->genre, + track_number: $core->track_number, + year: (string) $core->year, + encodingsettings: $core->encoding, ); } @@ -500,10 +239,30 @@ public static function fromId3v1(Tag\Id3TagAudioV1 $tag): AudioCore public static function fromQuicktime(Tag\Id3TagQuicktime $tag): AudioCore { - $creation_date = $tag->creation_date; + $date = $tag->creation_date; $description = $tag->description; $description_long = $tag->description_long; + $creation_date = null; + $year = null; + + if ($date) { + if (strlen($date) === 4) { + $year = (int) $date; + } else { + try { + $parsedCreationDate = new \DateTimeImmutable($date); + } catch (\Exception $e) { + // ignore the issue so the rest of the data will be available + } + + if (! empty($parsedCreationDate)) { + $creation_date = $parsedCreationDate->format('Y-m-d\TH:i:s\Z'); + $year = (int) $parsedCreationDate->format('Y'); + } + } + } + $core = new AudioCore( title: $tag->title, artist: $tag->artist, @@ -518,30 +277,14 @@ public static function fromQuicktime(Tag\Id3TagQuicktime $tag): AudioCore encoding_by: $tag->encoded_by, encoding: $tag->encoding_tool, language: $tag->language, + copyright: $tag->copyright, + description: $description, + synopsis: $description_long, + lyrics: $tag->lyrics, + creation_date: $creation_date, + year: $year, ); - 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; } diff --git a/src/Id3/Id3Reader.php b/src/Id3/Id3Reader.php index eae4a5e..d282d06 100644 --- a/src/Id3/Id3Reader.php +++ b/src/Id3/Id3Reader.php @@ -10,8 +10,6 @@ class Id3Reader { - protected array $raw = []; - protected function __construct( protected getID3 $instance, protected bool $is_writable = false, @@ -33,6 +31,7 @@ protected function __construct( protected ?float $playtime_seconds = null, protected ?float $bitrate = null, protected ?string $playtime_string = null, + protected array $raw = [], ) {} public static function make(string $path): self diff --git a/src/Id3/Id3Writer.php b/src/Id3/Id3Writer.php new file mode 100644 index 0000000..48b0271 --- /dev/null +++ b/src/Id3/Id3Writer.php @@ -0,0 +1,199 @@ + $options + * @param array $new_tags + * @param string[] $warnings + * @param string[] $errors + * @param string[] $tag_formats + */ + protected function __construct( + protected Audio $audio, + protected getid3_writetags $writer, + protected AudioCore $core, + // protected array $options = ['encoding' => 'UTF-8'], + protected array $new_tags = [], + // protected array $warnings = [], + // protected array $errors = [], + // protected bool $override_tags = true, + protected bool $remove_old_tags = false, + // protected bool $fail_on_error = true, + protected array $tag_formats = [], + // protected ?string $path = null, + protected bool $success = false, + ) {} + + public static function make(Audio $audio): self + { + $self = new self( + audio: $audio, + writer: new getid3_writetags, + core: new AudioCore, + ); + + $self->writer->filename = $audio->getPath(); + + return $self; + } + + /** + * Allow to remove other tags when writing tags. + */ + public function removeOtherTags(): self + { + $this->writer->remove_other_tags = true; + + return $this; + } + + public function title(string $title): self + { + $this->core->title = $title; + + return $this; + } + + public function album(string $album): self + { + $this->core->album = $album; + + return $this; + } + + public function save(): void + { + $this->assignTags(); + + $this->writer->tagformats = $this->tag_formats; + $this->writer->tag_data = $this->new_tags; + + $this->success = $this->writer->WriteTags(); + ray($this); + } + + /** + * Assign tags from core to tag formats. + */ + private function assignTags(): self + { + $this->parseTagFormats(); + + $convert = match ($this->audio->getType()) { + AudioTypeEnum::id3 => AudioCore::toId3v2($this->core), + AudioTypeEnum::vorbiscomment => AudioCore::toVorbisComment($this->core), + AudioTypeEnum::quicktime => AudioCore::toQuicktime($this->core), + AudioTypeEnum::matroska => AudioCore::toMatroska($this->core), + AudioTypeEnum::ape => AudioCore::toApe($this->core), + AudioTypeEnum::asf => AudioCore::toAsf($this->core), + default => null, + }; + + if (! $convert) { + return $this; + } + + $this->new_tags = [ + ...$this->audio->getRawTags(), // old tags + ...$convert->toArray(), // new tags + ]; + $this->new_tags = $this->convertTags($this->new_tags); + + // $tags = []; + // if ($convert) { + // $tags = $convert->toArray(); + // } + + // $tags = $this->convertTags($tags); + // $this->attachCover($tags); + + // $this->tags = [ + // ...$this->tags, + // ...$tags, + // ]; + + return $this; + } + + /** + * Assign tag formats to know how to write tags. + * + * - ID3v1 (v1 & v1.1) + * - ID3v2 (v2.3, v2.4) + * - APE (v2) + * - Ogg Vorbis comments (need `vorbis-tools`) + * - FLAC comments + * + * Options: `id3v1`, `id3v2.2`, `id2v2.3`, `id3v2.4`, `ape`, `vorbiscomment`, `metaflac`, `real` + */ + private function parseTagFormats(): self + { + if (! empty($this->tagFormats)) { + return $this; + } + + $this->tag_formats = match ($this->audio->getFormat()) { + AudioFormatEnum::aac => [], + AudioFormatEnum::aif => [], + AudioFormatEnum::aifc => [], + AudioFormatEnum::aiff => [], + AudioFormatEnum::dsf => [], + AudioFormatEnum::flac => ['metaflac'], + AudioFormatEnum::m4a => [], + AudioFormatEnum::m4b => [], + AudioFormatEnum::m4v => [], + AudioFormatEnum::mpc => [], + AudioFormatEnum::mka => [], + AudioFormatEnum::mkv => [], + AudioFormatEnum::ape => [], + AudioFormatEnum::mp3 => ['id3v1', 'id3v2.4'], + AudioFormatEnum::mp4 => [], + AudioFormatEnum::ogg => ['vorbiscomment'], + AudioFormatEnum::opus => [], + AudioFormatEnum::ofr => [], + AudioFormatEnum::ofs => [], + AudioFormatEnum::spx => [], + AudioFormatEnum::tak => [], + AudioFormatEnum::tta => [], + AudioFormatEnum::wav => [], + AudioFormatEnum::webm => [], + AudioFormatEnum::wma => [], + AudioFormatEnum::wv => [], + default => [], + }; + + return $this; + } + + /** + * @param array $tags + * @return array + */ + private function convertTags(array $tags): array + { + $attached = $tags['attached_picture'] ?? null; + $items = []; + if (! empty($tags)) { + foreach ($tags as $key => $tag) { + if ($tag && gettype($tag) === 'string') { + $items[$key] = [$tag]; + } + } + } + + if ($attached) { + $items['attached_picture'] = $attached; + } + + return $items; + } +} diff --git a/src/Id3/Reader/Id3Audio.php b/src/Id3/Reader/Id3Audio.php index 348bd27..c476d95 100644 --- a/src/Id3/Reader/Id3Audio.php +++ b/src/Id3/Reader/Id3Audio.php @@ -2,10 +2,25 @@ namespace Kiwilan\Audio\Id3\Reader; -class Id3Audio extends Id3AudioBase +class Id3Audio { - /** @var Id3Stream[] */ - protected array $streams = []; + /** + * @param Id3Stream[] $streams + */ + protected function __construct( + readonly public ?string $data_format = null, + readonly public ?int $channels = null, + readonly public ?int $sample_rate = null, + readonly public ?float $bitrate = null, + readonly public ?string $channel_mode = null, + readonly public ?string $bitrate_mode = null, + readonly public ?string $codec = null, + readonly public ?string $encoder = null, + readonly public bool $lossless = false, + readonly public ?string $encoder_options = null, + readonly public ?float $compression_ratio = null, + readonly public array $streams = [], + ) {} public static function make(?array $metadata): ?self { @@ -20,73 +35,24 @@ public static function make(?array $metadata): ?self } } - $self = new self($metadata); - $self->streams = $streams; + $self = new self( + data_format: $metadata['dataformat'] ?? null, + channels: $metadata['channels'] ?? null, + sample_rate: $metadata['sample_rate'] ?? null, + bitrate: $metadata['bitrate'] ?? null, + channel_mode: $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, + 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 deleted file mode 100644 index 0a97dfb..0000000 --- a/src/Id3/Reader/Id3AudioBase.php +++ /dev/null @@ -1,102 +0,0 @@ -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 index 53ee36d..7b508e1 100644 --- a/src/Id3/Reader/Id3AudioTag.php +++ b/src/Id3/Reader/Id3AudioTag.php @@ -8,15 +8,15 @@ class Id3AudioTag { protected function __construct( - protected ?Tag\Id3TagAudioV1 $id3v1 = null, - protected ?Tag\Id3TagAudioV2 $id3v2 = null, - protected ?Tag\Id3TagQuicktime $quicktime = null, - protected ?Tag\Id3TagAsf $asf = null, - protected ?Tag\Id3TagVorbisComment $vorbiscomment = null, - protected ?Tag\Id3TagRiff $riff = null, - protected ?Tag\Id3TagMatroska $matroska = null, - protected ?Tag\Id3TagApe $ape = null, - protected bool $is_empty = false, + readonly public ?Tag\Id3TagAudioV1 $id3v1 = null, + readonly public ?Tag\Id3TagAudioV2 $id3v2 = null, + readonly public ?Tag\Id3TagQuicktime $quicktime = null, + readonly public ?Tag\Id3TagAsf $asf = null, + readonly public ?Tag\Id3TagVorbisComment $vorbiscomment = null, + readonly public ?Tag\Id3TagRiff $riff = null, + readonly public ?Tag\Id3TagMatroska $matroska = null, + readonly public ?Tag\Id3TagApe $ape = null, + readonly public bool $is_empty = false, ) {} public static function make(?array $metadata): ?self @@ -34,6 +34,11 @@ public static function make(?array $metadata): ?self $matroska = Id3Reader::cleanTags($metadata['matroska'] ?? null); $ape = Id3Reader::cleanTags($metadata['ape'] ?? null); + $is_empty = false; + if (! $id3v1 && ! $id3v2 && ! $quicktime && ! $asf && ! $vorbiscomment && ! $riff && ! $matroska && ! $ape) { + $is_empty = true; + } + $self = new self( id3v1: Tag\Id3TagAudioV1::make($id3v1), id3v2: Tag\Id3TagAudioV2::make($id3v2), @@ -43,57 +48,9 @@ public static function make(?array $metadata): ?self riff: Tag\Id3TagRiff::make($riff), matroska: Tag\Id3TagMatroska::make($matroska), ape: Tag\Id3TagApe::make($ape), + is_empty: $is_empty, ); - if (! $self->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/Id3Stream.php b/src/Id3/Reader/Id3Stream.php index d7cd966..0428423 100644 --- a/src/Id3/Reader/Id3Stream.php +++ b/src/Id3/Reader/Id3Stream.php @@ -2,15 +2,41 @@ namespace Kiwilan\Audio\Id3\Reader; -class Id3Stream extends Id3AudioBase +class Id3Stream { + protected function __construct( + readonly public ?string $data_format = null, + readonly public ?int $channels = null, + readonly public ?int $sample_rate = null, + readonly public ?float $bitrate = null, + readonly public ?string $channel_mode = null, + readonly public ?string $bitrate_mode = null, + readonly public ?string $codec = null, + readonly public ?string $encoder = null, + readonly public bool $lossless = false, + readonly public ?string $encoder_options = null, + readonly public ?float $compression_ratio = null, + ) {} + public static function make(?array $metadata): ?self { if (! $metadata) { return null; } - $self = new self($metadata); + $self = new self( + data_format: $metadata['dataformat'] ?? null, + channels: $metadata['channels'] ?? null, + sample_rate: $metadata['sample_rate'] ?? null, + bitrate: $metadata['bitrate'] ?? null, + channel_mode: $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; } diff --git a/src/Id3/Tag/Id3Tag.php b/src/Id3/Tag/Id3Tag.php new file mode 100644 index 0000000..48ad25c --- /dev/null +++ b/src/Id3/Tag/Id3Tag.php @@ -0,0 +1,29 @@ + $value !== null); + $properties = array_filter($properties, fn ($value) => $value !== ''); + + return $properties; + } +} diff --git a/src/Id3/Tag/Id3TagApe.php b/src/Id3/Tag/Id3TagApe.php index edecae0..2c3eb46 100644 --- a/src/Id3/Tag/Id3TagApe.php +++ b/src/Id3/Tag/Id3TagApe.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagApe +class Id3TagApe extends Id3Tag { public function __construct( readonly public ?string $title = null, @@ -31,25 +31,28 @@ public static function make(?array $metadata): ?self return null; } + $album_artist = $metadata['album_artist'] ?? $metadata['albumartist'] ?? null; + $disc = $metadata['disc'] ?? $metadata['discnumber'] ?? 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, + title: self::parseTag($metadata, 'title'), + artist: self::parseTag($metadata, 'artist'), + album: self::parseTag($metadata, 'album'), + album_artist: $album_artist, + composer: self::parseTag($metadata, 'composer'), + comment: self::parseTag($metadata, 'comment'), + genre: self::parseTag($metadata, 'genre'), + disc: $disc, + compilation: self::parseTag($metadata, 'compilation'), + track: self::parseTag($metadata, 'track'), + date: self::parseTag($metadata, 'date'), + encoder: self::parseTag($metadata, 'encoder'), + description: self::parseTag($metadata, 'description'), + copyright: self::parseTag($metadata, 'copyright'), + lyrics: self::parseTag($metadata, 'unsyncedlyrics'), + podcastdesc: self::parseTag($metadata, 'podcastdesc'), + language: self::parseTag($metadata, 'language'), + year: self::parseTag($metadata, 'year'), ); return $self; diff --git a/src/Id3/Tag/Id3TagAsf.php b/src/Id3/Tag/Id3TagAsf.php index 55d931d..e05b447 100644 --- a/src/Id3/Tag/Id3TagAsf.php +++ b/src/Id3/Tag/Id3TagAsf.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagAsf +class Id3TagAsf extends Id3Tag { public function __construct( readonly public ?string $title = null, @@ -23,16 +23,16 @@ public static function make(?array $metadata): ?self 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, + title: self::parseTag($metadata, 'title'), + artist: self::parseTag($metadata, 'artist'), + album: self::parseTag($metadata, 'album'), + albumartist: self::parseTag($metadata, 'albumartist'), + composer: self::parseTag($metadata, 'composer'), + partofset: self::parseTag($metadata, 'partofset'), + genre: self::parseTag($metadata, 'genre'), + track_number: self::parseTag($metadata, 'track_number'), + year: self::parseTag($metadata, 'year'), + encodingsettings: self::parseTag($metadata, 'encodingsettings'), ); return $self; diff --git a/src/Id3/Tag/Id3TagAudioV1.php b/src/Id3/Tag/Id3TagAudioV1.php index 0d5b252..c56bf9c 100644 --- a/src/Id3/Tag/Id3TagAudioV1.php +++ b/src/Id3/Tag/Id3TagAudioV1.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagAudioV1 +class Id3TagAudioV1 extends Id3Tag { public function __construct( readonly public ?string $title = null, @@ -21,13 +21,13 @@ public static function make(?array $metadata): ?self } $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, + title: self::parseTag($metadata, 'title'), + artist: self::parseTag($metadata, 'artist'), + album: self::parseTag($metadata, 'album'), + year: self::parseTag($metadata, 'year'), + genre: self::parseTag($metadata, 'genre'), + comment: self::parseTag($metadata, 'comment'), + track_number: self::parseTag($metadata, 'track_number'), ); return $self; diff --git a/src/Id3/Tag/Id3TagAudioV2.php b/src/Id3/Tag/Id3TagAudioV2.php index eeb1d4f..b17debe 100644 --- a/src/Id3/Tag/Id3TagAudioV2.php +++ b/src/Id3/Tag/Id3TagAudioV2.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagAudioV2 +class Id3TagAudioV2 extends Id3Tag { public function __construct( readonly public ?string $album = null, @@ -29,21 +29,21 @@ public static function make(?array $metadata): ?self } $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, + album: self::parseTag($metadata, 'album'), + artist: self::parseTag($metadata, 'artist'), + band: self::parseTag($metadata, 'band'), + comment: self::parseTag($metadata, 'comment'), + composer: self::parseTag($metadata, 'composer'), + part_of_a_set: self::parseTag($metadata, 'part_of_a_set'), + genre: self::parseTag($metadata, 'genre'), + part_of_a_compilation: self::parseTag($metadata, 'part_of_a_compilation'), + title: self::parseTag($metadata, 'title'), + track_number: self::parseTag($metadata, 'track_number'), + year: self::parseTag($metadata, 'year'), + copyright: self::parseTag($metadata, 'copyright_message'), + text: self::parseTag($metadata, 'text'), + unsynchronised_lyric: self::parseTag($metadata, 'unsynchronised_lyric'), + language: self::parseTag($metadata, 'language'), ); return $self; diff --git a/src/Id3/Tag/Id3TagMatroska.php b/src/Id3/Tag/Id3TagMatroska.php index b2c243a..06c1200 100644 --- a/src/Id3/Tag/Id3TagMatroska.php +++ b/src/Id3/Tag/Id3TagMatroska.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagMatroska +class Id3TagMatroska extends Id3Tag { public function __construct( readonly public ?string $title = null, @@ -29,21 +29,21 @@ public static function make(?array $metadata): ?self } $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, + title: self::parseTag($metadata, 'title'), + muxingapp: self::parseTag($metadata, 'muxingapp'), + writingapp: self::parseTag($metadata, 'writingapp'), + album: self::parseTag($metadata, 'album'), + artist: self::parseTag($metadata, 'artist'), + album_artist: self::parseTag($metadata, 'album_artist'), + comment: self::parseTag($metadata, 'comment'), + composer: self::parseTag($metadata, 'composer'), + disc: self::parseTag($metadata, 'disc'), + genre: self::parseTag($metadata, 'genre'), + compilation: self::parseTag($metadata, 'compilation'), + part_number: self::parseTag($metadata, 'part_number'), + date: self::parseTag($metadata, 'date'), + encoder: self::parseTag($metadata, 'encoder'), + duration: self::parseTag($metadata, 'duration'), ); return $self; diff --git a/src/Id3/Tag/Id3TagQuicktime.php b/src/Id3/Tag/Id3TagQuicktime.php index e734daf..093297e 100644 --- a/src/Id3/Tag/Id3TagQuicktime.php +++ b/src/Id3/Tag/Id3TagQuicktime.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagQuicktime +class Id3TagQuicktime extends Id3Tag { public function __construct( readonly public ?string $title = null, @@ -33,25 +33,25 @@ public static function make(?array $metadata): ?self } $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, + title: self::parseTag($metadata, 'title'), + track_number: self::parseTag($metadata, 'track_number'), + disc_number: self::parseTag($metadata, 'disc_number'), + compilation: self::parseTag($metadata, 'compilation'), + album: self::parseTag($metadata, 'album'), + genre: self::parseTag($metadata, 'genre'), + composer: self::parseTag($metadata, 'composer'), + creation_date: self::parseTag($metadata, 'creation_date'), + copyright: self::parseTag($metadata, 'copyright'), + artist: self::parseTag($metadata, 'artist'), + album_artist: self::parseTag($metadata, 'album_artist'), + encoded_by: self::parseTag($metadata, 'encoded_by'), + encoding_tool: self::parseTag($metadata, 'encoding_tool'), + description: self::parseTag($metadata, 'description'), + description_long: self::parseTag($metadata, 'description_long'), + language: self::parseTag($metadata, 'language'), + lyrics: self::parseTag($metadata, 'lyrics'), + comment: self::parseTag($metadata, 'comment'), + stik: self::parseTag($metadata, 'stik'), ); return $self; diff --git a/src/Id3/Tag/Id3TagRiff.php b/src/Id3/Tag/Id3TagRiff.php index b825152..4ddf42d 100644 --- a/src/Id3/Tag/Id3TagRiff.php +++ b/src/Id3/Tag/Id3TagRiff.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagRiff +class Id3TagRiff extends Id3Tag { public function __construct( readonly public ?string $artist = null, @@ -20,13 +20,13 @@ public static function make(?array $metadata): ?self 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, + artist: self::parseTag($metadata, 'artist'), + comment: self::parseTag($metadata, 'comment'), + creationdate: self::parseTag($metadata, 'creationdate'), + genre: self::parseTag($metadata, 'genre'), + title: self::parseTag($metadata, 'title'), + product: self::parseTag($metadata, 'product'), + software: self::parseTag($metadata, 'software'), ); return $self; diff --git a/src/Id3/Tag/Id3TagVorbisComment.php b/src/Id3/Tag/Id3TagVorbisComment.php index 229bd22..7b85311 100644 --- a/src/Id3/Tag/Id3TagVorbisComment.php +++ b/src/Id3/Tag/Id3TagVorbisComment.php @@ -2,7 +2,7 @@ namespace Kiwilan\Audio\Id3\Tag; -class Id3TagVorbisComment +class Id3TagVorbisComment extends Id3Tag { public function __construct( readonly public ?string $description = null, @@ -26,19 +26,19 @@ public static function make(?array $metadata): ?self 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, + description: self::parseTag($metadata, 'description'), + encoder: self::parseTag($metadata, 'encoder'), + title: self::parseTag($metadata, 'title'), + artist: self::parseTag($metadata, 'artist'), + album: self::parseTag($metadata, 'album'), + genre: self::parseTag($metadata, 'genre'), + comment: self::parseTag($metadata, 'comment'), + albumartist: self::parseTag($metadata, 'albumartist'), + composer: self::parseTag($metadata, 'composer'), + discnumber: self::parseTag($metadata, 'discnumber'), + compilation: self::parseTag($metadata, 'compilation'), + date: self::parseTag($metadata, 'date'), + tracknumber: self::parseTag($metadata, 'tracknumber'), ); return $self; diff --git a/src/Models/AudioMetadata.php b/src/Models/AudioMetadata.php index 909bec8..11d51b3 100644 --- a/src/Models/AudioMetadata.php +++ b/src/Models/AudioMetadata.php @@ -41,19 +41,19 @@ public static function make(Audio $audio, Id3Reader $id3_reader): self return new self( file_size: $id3_reader->getFileSize(), - data_format: $audio?->dataFormat(), + data_format: $audio?->data_format, 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(), - channel_mode: $audio?->channelMode(), - is_lossless: $audio?->lossless() ?? false, - compression_ratio: $audio?->compressionRatio(), - codec: $audio?->codec(), - encoder_options: $audio?->encoderOptions(), + bitrate_mode: $audio?->bitrate_mode, + sample_rate: $audio?->sample_rate, + channels: $audio?->channels, + channel_mode: $audio?->channel_mode, + is_lossless: $audio?->lossless ?? false, + compression_ratio: $audio?->compression_ratio, + codec: $audio?->codec, + encoder_options: $audio?->encoder_options, version: $id3_reader->getVersion(), av_data_offset: $id3_reader->getAvDataOffset(), av_data_end: $id3_reader->getAvDataEnd(), diff --git a/src/Models/Id3Writer.php b/src/Models/Id3Writer.php deleted file mode 100644 index e4c7c8f..0000000 --- a/src/Models/Id3Writer.php +++ /dev/null @@ -1,511 +0,0 @@ - - */ - protected array $options = [ - 'encoding' => 'UTF-8', - ]; - - /** - * @var array - */ - protected array $tags = []; - - /** - * @var string[] - */ - protected array $warnings = []; - - /** - * @var string[] - */ - protected array $errors = []; - - protected bool $overrideTags = true; - - protected bool $removeOldTags = false; - - protected bool $failOnError = true; - - /** - * @var string[] - */ - protected array $tagFormats = []; - - protected ?string $path = null; - - protected bool $success = false; - - protected function __construct( - protected Audio $audio, - protected getid3_writetags $instance, - protected AudioCore $core, - ) {} - - public static function make(Audio $audio): self - { - $self = new self( - audio: $audio, - instance: new getid3_writetags, - core: new AudioCore - ); - - return $self; - } - - public function getCore(): AudioCore - { - return $this->core; - } - - public function title(?string $title): self - { - $this->core->setTitle($title); - - return $this; - } - - public function artist(?string $artist): self - { - $this->core->setArtist($artist); - - return $this; - } - - public function album(?string $album): self - { - $this->core->setAlbum($album); - - return $this; - } - - public function genre(?string $genre): self - { - $this->core->setGenre($genre); - - return $this; - } - - public function year(int $year): self - { - $this->core->setYear($year); - - return $this; - } - - public function trackNumber(string|int|null $trackNumber): self - { - if (is_int($trackNumber)) { - $trackNumber = (string) $trackNumber; - } - - $this->core->setTrackNumber($trackNumber); - - return $this; - } - - public function comment(?string $comment): self - { - $this->core->setComment($comment); - - return $this; - } - - public function albumArtist(?string $albumArtist): self - { - $this->core->setAlbumArtist($albumArtist); - - return $this; - } - - public function composer(?string $composer): self - { - $this->core->setComposer($composer); - - return $this; - } - - public function discNumber(string|int|null $discNumber): self - { - if (is_int($discNumber)) { - $discNumber = (string) $discNumber; - } - - $this->core->setDiscNumber($discNumber); - - return $this; - } - - public function isCompilation(): self - { - $this->core->setIsCompilation(true); - - return $this; - } - - public function isNotCompilation(): self - { - $this->core->setIsCompilation(false); - - return $this; - } - - public function creationDate(string|DateTime|null $creationDate): self - { - if ($creationDate instanceof DateTime) { - $creationDate = $creationDate->format('Y-m-d'); - } - - $this->core->setCreationDate($creationDate); - - return $this; - } - - public function copyright(?string $copyright): self - { - $this->core->setCopyright($copyright); - - return $this; - } - - public function encodingBy(?string $encodingBy): self - { - $this->core->setEncodingBy($encodingBy); - - return $this; - } - - public function encoding(?string $encoding): self - { - $this->core->setEncoding($encoding); - - return $this; - } - - public function description(?string $description): self - { - $this->core->setDescription($description); - - return $this; - } - - public function podcastDescription(?string $podcastDescription): self - { - $this->core->setPodcastDescription($podcastDescription); - - return $this; - } - - public function language(?string $language): self - { - $this->core->setLanguage($language); - - return $this; - } - - public function lyrics(?string $lyrics): self - { - $this->core->setLyrics($lyrics); - - return $this; - } - - /** - * @param string $pathOrData Path to cover image or binary data - */ - public function cover(string $pathOrData): self - { - $this->core->setCover($pathOrData); - - return $this; - } - - public function options(array $options = ['encoding' => 'UTF-8']): self - { - $this->options = $options; - - return $this; - } - - public function path(string $path): self - { - $this->path = $path; - - if (file_exists($this->audio->getPath())) { - copy($this->audio->getPath(), $this->path); - } - - return $this; - } - - /** - * Prevent fail on error. - */ - public function preventFailOnError(): self - { - $this->failOnError = false; - - return $this; - } - - /** - * Override existing tags, default is true. - */ - // public function notOverrideTags(): self - // { - // $this->overrideTags = false; - - // return $this; - // } - - /** - * Remove other tags, default is false. - */ - public function removeOldTags(): self - { - $this->removeOldTags = true; - - return $this; - } - - /** - * Set manually tags. - * - * @param array $tags - */ - public function tags(array $tags): self - { - $this->tags = $this->convertTags($tags); - - return $this; - } - - /** - * Set tag format. - * - * @param string[] $tags Options are `id3v1`, `id3v2.2`, `id2v2.3`, `id3v2.4`, `ape`, `vorbiscomment`, `metaflac`, `real` - */ - public function tagFormats(array $tags): self - { - $this->tagFormats = $tags; - - return $this; - } - - /** - * Save tags. - * - * @throws \Exception - */ - public function save(): bool - { - if (! $this->path) { - $this->path = $this->audio->getPath(); - } - - $this->instance->filename = $this->path; - - $this->convertTagFormats(); - $this->automaticConvert(); - - $this->instance->overwrite_tags = $this->overrideTags; - $this->instance->remove_other_tags = $this->removeOldTags; - $this->instance->tagformats = $this->tagFormats; - $this->instance->tag_data = $this->tags; - - // $this->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; - $this->warnings = $this->instance->warnings; - - $errors = implode(', ', $this->errors); - $warnings = implode(', ', $this->warnings); - $supported = match ($this->audio->getFormat()) { - AudioFormatEnum::flac => true, - AudioFormatEnum::mp3 => true, - AudioFormatEnum::ogg => true, - default => false - }; - - if (! empty($this->errors)) { - $msg = 'Save tags failed.'; - - $errors = strip_tags($errors); - $errors = "Errors: {$errors}."; - if (! empty($this->errors)) { - $msg .= " {$errors}"; - } - - $warnings = "Warnings: {$warnings}."; - if (! empty($this->warnings)) { - $msg .= " {$warnings}"; - } - - $isSuccess = $this->success ? 'true' : 'false'; - $success = "Success: {$isSuccess}"; - $msg .= " {$success}"; - - error_log($msg); - - if ($this->failOnError) { - throw new \Exception($msg); - } - } - - if (! $supported && $this->failOnError) { - throw new \Exception("Format {$this->audio->getFormat()->value} is not supported."); - } - - if (! empty($this->warnings)) { - error_log($warnings); - } - - return $this->success; - } - - private function assignCurrentTags(): self - { - // - } - - private function automaticConvert(): self - { - $this->convertTagFormats(); - - $convert = match ($this->audio->getType()) { - AudioTypeEnum::id3 => AudioCore::toId3v2($this->core), - AudioTypeEnum::vorbiscomment => AudioCore::toVorbisComment($this->core), - AudioTypeEnum::quicktime => AudioCore::toQuicktime($this->core), - AudioTypeEnum::matroska => AudioCore::toMatroska($this->core), - AudioTypeEnum::ape => AudioCore::toApe($this->core), - AudioTypeEnum::asf => AudioCore::toAsf($this->core), - default => null, - }; - - $tags = []; - if ($convert) { - $tags = $convert->toArray(); - } - - $tags = $this->convertTags($tags); - $this->attachCover($tags); - - $this->tags = [ - ...$this->tags, - ...$tags, - ]; - - return $this; - } - - /** - * @param array $tags - * @return array - */ - private function convertTags(array $tags): array - { - $attached = $tags['attached_picture'] ?? null; - $items = []; - if (! empty($tags)) { - foreach ($tags as $key => $tag) { - if ($tag && gettype($tag) === 'string') { - $items[$key] = [$tag]; - } - } - } - - if ($attached) { - $items['attached_picture'] = $attached; - } - - return $items; - } - - /** - * - ID3v1 (v1 & v1.1) - * - ID3v2 (v2.3, v2.4) - * - APE (v2) - * - Ogg Vorbis comments (need `vorbis-tools`) - * - FLAC comments - * - * Options: `id3v1`, `id3v2.2`, `id2v2.3`, `id3v2.4`, `ape`, `vorbiscomment`, `metaflac`, `real` - */ - private function convertTagFormats(): self - { - if (! empty($this->tagFormats)) { - return $this; - } - - $formats = match ($this->audio->getFormat()) { - AudioFormatEnum::aac => [], - AudioFormatEnum::aif => [], - AudioFormatEnum::aifc => [], - AudioFormatEnum::aiff => [], - AudioFormatEnum::dsf => [], - AudioFormatEnum::flac => ['metaflac'], - AudioFormatEnum::m4a => [], - AudioFormatEnum::m4b => [], - AudioFormatEnum::m4v => [], - AudioFormatEnum::mpc => [], - AudioFormatEnum::mka => [], - AudioFormatEnum::mkv => [], - AudioFormatEnum::ape => [], - AudioFormatEnum::mp3 => ['id3v1', 'id3v2.4'], - AudioFormatEnum::mp4 => [], - AudioFormatEnum::ogg => ['vorbiscomment'], - AudioFormatEnum::opus => [], - AudioFormatEnum::ofr => [], - AudioFormatEnum::ofs => [], - AudioFormatEnum::spx => [], - AudioFormatEnum::tak => [], - AudioFormatEnum::tta => [], - AudioFormatEnum::wav => [], - AudioFormatEnum::webm => [], - AudioFormatEnum::wma => [], - AudioFormatEnum::wv => [], - default => null, - }; - $this->tagFormats = $formats; - - return $this; - } - - private function attachCover(array &$tags): void - { - $coverFormatsAllowed = [AudioFormatEnum::mp3]; - if ($this->core->getCover() && in_array($this->audio->getFormat(), $coverFormatsAllowed)) { - // $tags = [ - // ...$tags, - // 'CTOC' => $old_tags['id3v2']['CTOC'], - // 'CHAP' => $old_tags['id3v2']['CHAP'], - // 'chapters' => $old_tags['id3v2']['chapters'], - // ]; - $tags['attached_picture'][0] = [ - 'data' => base64_decode($this->core->getCover()->data()), - 'picturetypeid' => $this->core->getCover()->picturetypeid(), - 'description' => $this->core->getCover()->description(), - 'mime' => $this->core->getCover()->mime(), - ]; - $this->core->setHasCover(true); - } - } -} diff --git a/test.php b/test.php deleted file mode 100644 index a645ff2..0000000 --- a/test.php +++ /dev/null @@ -1 +0,0 @@ -1.9.23-202310190849366480/Users/ewilan/Workspace/php-audio/tests/mediaaudiobook.mp3/Users/ewilan/Workspace/php-audio/tests/media/audiobook.mp395396272609mp3ArrayArrayArrayUTF-8ArrayArrayArrayaudio/mpegArray11.0496875Array1280000:11 \ No newline at end of file diff --git a/tests/AudioCoreTest.php b/tests/AudioCoreTest.php index 02f3d0a..79d4ec9 100644 --- a/tests/AudioCoreTest.php +++ b/tests/AudioCoreTest.php @@ -26,17 +26,17 @@ lyrics: $audio->getLyrics(), ); - expect($core->getTitle())->toBe('Introduction'); - expect($core->getArtist())->toBe('Mr Piouf'); - expect($core->getAlbum())->toBe('P1PDD Le conclave de Troie'); - expect($core->getGenre())->toBe('Roleplaying game'); - expect($core->getYear())->toBe(2016); - expect($core->getTrackNumber())->toBe('1'); - expect($core->getComment())->toBe('http://www.p1pdd.com'); - expect($core->getAlbumArtist())->toBe('P1PDD & Mr Piouf'); - expect($core->getComposer())->toBe('P1PDD & Piouf'); - expect($core->getDiscNumber())->toBe('1'); - expect($core->isCompilation())->toBe(true); + expect($core->title)->toBe('Introduction'); + expect($core->artist)->toBe('Mr Piouf'); + expect($core->album)->toBe('P1PDD Le conclave de Troie'); + expect($core->genre)->toBe('Roleplaying game'); + expect($core->year)->toBe(2016); + expect($core->track_number)->toBe('1'); + expect($core->comment)->toBe('http://www.p1pdd.com'); + expect($core->album_artist)->toBe('P1PDD & Mr Piouf'); + expect($core->composer)->toBe('P1PDD & Piouf'); + expect($core->disc_number)->toBe('1'); + expect($core->is_compilation)->toBe(true); $id3v1 = AudioCore::toId3v1($core); $id3v2 = AudioCore::toId3v2($core); diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index c7821db..c13aa8a 100644 --- a/tests/ReaderTest.php +++ b/tests/ReaderTest.php @@ -8,21 +8,21 @@ it('can read mp3 stream', function () { $audio = Audio::get(MP3); - $streams = $audio->getId3Reader()->getAudio()->streams(); + $streams = $audio->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); + expect($streams[0]->data_format)->toBe('mp3'); + expect($streams[0]->channels)->toBe(2); + expect($streams[0]->sample_rate)->toBe(44100); + expect($streams[0]->bitrate)->toBe(128000.0); + expect($streams[0]->channel_mode)->toBe('joint stereo'); + expect($streams[0]->bitrate_mode)->toBe('cbr'); + expect($streams[0]->codec)->toBe('LAME'); + expect($streams[0]->encoder)->toBe('LAME3.100'); + expect($streams[0]->lossless)->toBeFalse(); + expect($streams[0]->encoder_options)->toBe('CBR128'); + expect($streams[0]->compression_ratio)->toBe(0.09070294784580499); }); it('can parse ID3 reader', function (string $path) { diff --git a/tests/WriterTest.php b/tests/WriterTest.php index a4d2d84..6df77a2 100644 --- a/tests/WriterTest.php +++ b/tests/WriterTest.php @@ -4,6 +4,40 @@ use Kiwilan\Audio\Enums\AudioFormatEnum; use Kiwilan\Audio\Models\AudioCore; +it('can update tags', function () { + $audio = Audio::get(MP3_WRITER); + + expect($audio->getTitle())->toBe('Introduction'); + expect($audio->getArtist())->toBe('Mr Piouf'); + expect($audio->getAlbum())->toBe('P1PDD Le conclave de Troie'); + expect($audio->getGenre())->toBe('Roleplaying game'); + expect($audio->getYear())->toBe(2016); + expect($audio->getTrackNumber())->toBe('1'); + expect($audio->getComment())->toBe('http://www.p1pdd.com'); + expect($audio->getAlbumArtist())->toBe('P1PDD & Mr Piouf'); + expect($audio->getComposer())->toBe('P1PDD & Piouf'); + expect($audio->getDiscNumber())->toBe('1'); + expect($audio->isCompilation())->toBeTrue(); + + $audio->update() + ->album('P1PDD') + ->save(); + + expect($audio->getAlbum())->toBe('P1PDD'); + + // $audio->update() + // ->album('P1PDD') + // ->save(); + + // $tag = $audio->update() + // ->tags([ + // 'title' => 'New Title', + // ]) + // ->save(); + + // expect($audio->getAlbum())->toBe('P1PDD'); +}); + // it('can update file', function (string $path) { // $audio = Audio::get($path); // $random = (string) rand(1, 1000); @@ -297,33 +331,3 @@ // $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'); -});