Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mp3Info Laravel Package

wapmorgan/mp3info

A pure-PHP library for reading MP3 metadata without external tools or extensions. It parses ID3v1 and ID3v2 tags along with audio properties such as duration, bitrate, sample rate, and channels, making it easy to inspect music files in any PHP application.

Deep Wiki
Context7

Getting Started

Install with Composer. The package is small and has no Laravel-specific integration — everything is a single class you drive directly.

composer require wapmorgan/mp3info

The entry point is \wapmorgan\Mp3Info\Mp3Info. The constructor takes a string $filename plus an optional bool $tagsOnly = false flag. $tagsOnly = true skips the MPEG frame scan and reads only the ID3 tags — useful when all you need is artist/title/album on a large library.

use wapmorgan\Mp3Info\Mp3Info;

$info = new Mp3Info(storage_path('app/music/song.mp3'));

$info->duration;    // float, seconds (e.g. 213.42)
$info->bitRate;     // int, kbps (e.g. 320)
$info->sampleRate;  // int, Hz (e.g. 44100)
$info->channels;    // 1 (mono) or 2 (stereo)
$info->isVbr;       // bool
$info->tags;        // array of ID3 tags

Where to look first: dump $info->tags and $info->audioInfo (the per-frame array) on one real file. That tells you the shape of the data before you write a mapper around it.

The obvious first use case: a tracks table with duration, bitrate, title, artist columns, populated when a user uploads a file. Parse once at upload, store the result, never touch the package on the read path.

Implementation Patterns

Parse at upload, cache in the database. Metadata is immutable unless the file is rewritten, so store the normalized result. Re-invoking Mp3Info on every page render is wasted I/O:

Schema::create('tracks', function (Blueprint $t) {
    $t->id();
    $t->string('path');
    $t->string('title')->nullable();
    $t->string('artist')->nullable();
    $t->string('album')->nullable();
    $t->unsignedSmallInteger('duration_seconds')->nullable();
    $t->unsignedSmallInteger('bitrate')->nullable();
    $t->unsignedInteger('sample_rate')->nullable();
    $t->unsignedTinyInteger('channels')->nullable();
    $t->json('raw_tags')->nullable();
    $t->timestamps();
});

Wrap it in a small service. The class has no DI hooks, so build your own boundary for testing and to normalize inconsistencies across files (see Gotchas):

namespace App\Services;

use wapmorgan\Mp3Info\Mp3Info;

class AudioMetadataReader
{
    public function read(string $absolutePath, bool $tagsOnly = false): array
    {
        $mp3 = new Mp3Info($absolutePath, $tagsOnly);

        return [
            'duration'    => $mp3->duration ?? null,
            'bitrate'     => $mp3->bitRate ?? null,
            'sample_rate' => $mp3->sampleRate ?? null,
            'channels'    => $mp3->channels ?? null,
            'is_vbr'      => $mp3->isVbr ?? false,
            'tags'        => $this->normalizeTags($mp3->tags ?? []),
        ];
    }

    private function normalizeTags(array $tags): array
    {
        return array_map(
            fn ($v) => is_array($v) ? array_map('trim', $v) : trim((string) $v),
            $tags
        );
    }
}

Offload to a queue. For anything user-generated, dispatch a job so an upload response doesn't block on file reads:

class ExtractTrackMetadata implements ShouldQueue
{
    public function __construct(public Track $track) {}

    public function handle(AudioMetadataReader $reader): void
    {
        $data = $reader->read(Storage::path($this->track->path));

        $this->track->update([
            'title'    => $data['tags']['title']    ?? null,
            'artist'   => $data['tags']['artist']   ?? null,
            'album'    => $data['tags']['album']    ?? null,
            'duration_seconds' => (int) round($data['duration'] ?? 0),
            'bitrate'  => $data['bitrate'],
            'raw_tags' => $data['tags'],
        ]);
    }
}

Use $tagsOnly = true on the catalog-scan path. When you only need to rebuild an index of titles/artists across thousands of files, skipping the frame analysis is where the speed claim pays off.

Fallback filename parsing. Podcast/audiobook files routinely ship without ID3 tags. After the reader returns nulls, parse basename($path) with a regex like ^(?<artist>.+?)\s-\s(?<title>.+)$ and store it as a derived value, distinct from the parsed tags.

Validate MIME, not extension. Reject non-MP3 before parsing; check the first bytes are ID3 or an MPEG frame sync (0xFF 0xFB etc.), or let the library throw and catch.

Gotchas and Tips

VBR files are the main source of wrong durations. CBR files compute cleanly; for VBR, duration comes from the Xing/LAME header or a frame scan, and the two can disagree by seconds. Test with a mix. If accuracy matters, do not present duration as exact — round to whole seconds and label it "approx" in the UI if you're doing playback scrubbing.

tags shape is inconsistent between ID3v1 and ID3v2. v1 fields are capped at 30 bytes and are ISO-8859-1; v2 can be UTF-8 or UTF-16 and may carry multiple values as arrays (multiple artists, multiple comments). Normalize before persisting — a json column holding arrays is fine, but a string column isn't. track frequently comes through as "3/12"; split it if you store track number separately.

Encoding mojibake is common. Cyrillic/Asian tags written as Windows-1251 or Shift-JIS but declared as ISO-8859-1 will come through garbled. There's no config knob to fix it; sanitize at the boundary or run through mb_convert_encoding when you detect garbage.

Absent keys, not null keys. $info->tags['artist'] may simply not exist. Always use ?? null (as above) rather than assuming the key is present.

Corrupt or truncated MP3s throw. Wrap every construction in try/catch (\Throwable $e). In a batch import, let one bad file fail gracefully rather than aborting the whole job — treat a failure as "metadata unknown" and queue the file for manual review.

The file must be seekable on local disk. Mp3Info uses fopen/fseek, so stream resources and network URLs won't work. On S3-backed Storage, use Storage::path() only for local disks; for S3, download to a temp file ($tmp = tempnam(sys_get_temp_dir(), 'mp3'); file_put_contents($tmp, Storage::disk('s3')->get($path));) and clean up after.

duration is a float, not an int. Round before storing in a UNSIGNED SMALLINT, and cast to a display format (gmdate('i:s', $seconds)) in your view layer.

LGPL-3.0 compliance. Keeping this as a Composer dependency in vendor/ is the normal, compliant case for a Laravel app — you're linking, not statically embedding into a distributed binary. If you're building a closed-source desktop bundle or otherwise redistributing a compiled artifact, review obligations before shipping. For a hosted SaaS this is a non-issue.

No extension points. There are no events, no interfaces, no config. If you need custom behavior (e.g., a custom ID3 frame), subclass Mp3Info or wrap it — the latter is safer for upgrades. Cache aggressively on your side; the library does no caching.

Debugging tip. When durations look wrong, dump $info->audioInfo. If frames are missing or the array is short, the file has a broken frame header — and the fallback duration may be derived from a partial scan. That's the first thing to check before blaming your code.

Pin a version compatible with your PHP. The package tracks modern PHP (8.x); if you're still on 7.x, check the changelog for the last release supporting your version before composer update quietly moves you onto something incompatible. Last release was 2025-06-20, so the maintainer is active — but pin in composer.json to avoid surprise behavior changes in a batch pipeline.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin