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

Media File Laravel Package

wapmorgan/media-file

wapmorgan/media-file is a PHP library for reading metadata from audio and video files, including duration, bitrate, codecs, tracks, and tags. It parses popular media formats and provides a simple API for inspecting media in Laravel and other PHP projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer — there is no service provider, facade, or config file to publish:

composer require wapmorgan/media-file

The first place to look is the README, then src/MediaFile.php (the base class), src/AudioFile.php, and src/VideoFile.php. Format-specific readers live under src/Formats/ (MP3/ID3, MP4/M4A, OGG, FLAC, WAV, AVI, MKV, etc.). That directory tells you exactly which containers and tag sets are supported before you promise features to a product owner.

The smallest useful first use case is "read tags from an uploaded MP3 and persist them":

use wapmorgan\MediaFile\AudioFile;
use wapmorgan\MediaFile\VideoFile;
use wapmorgan\MediaFile\MediaFile;

$media = new MediaFile(storage_path('app/media/song.mp3'));

$media->getType();    // MediaFile::TYPE_AUDIO / TYPE_VIDEO
$media->getFormat();  // e.g. "mp3", "mp4"

if ($media->getType() === MediaFile::TYPE_AUDIO) {
    $audio = $media->getAudio();      // AudioFile
    $audio->getDuration();            // seconds (float)
    $audio->getBitrate();             // kbps
    $audio->getArtist();
    $audio->getTitle();
    $audio->getAlbum();
    $audio->getYear();
    $audio->getGenre();
    $audio->getTrackNumber();
}

Or construct AudioFile / VideoFile directly if you already know the type. For video you also get getWidth(), getHeight(), getFramerate(), and codec info via the video object.

That is the whole onboarding: one new, a handful of getters, done.

Implementation Patterns

Wrap it in a small service, not scattered new calls. This library is a passive dependency; give it one seam you can later swap for ffprobe/getID3 if you outgrow it.

namespace App\Services;

use wapmorgan\MediaFile\MediaFile;

class MediaMetadataReader
{
    public function read(string $absolutePath): array
    {
        $media = new MediaFile($absolutePath);

        $data = [
            'type'   => $media->getType(),
            'format' => $media->getFormat(),
        ];

        if ($media->getType() === MediaFile::TYPE_AUDIO) {
            $audio = $media->getAudio();
            $data += [
                'duration' => $audio->getDuration(),
                'bitrate'  => $audio->getBitrate(),
                'artist'   => $audio->getArtist(),
                'title'    => $audio->getTitle(),
                'album'    => $audio->getAlbum(),
                'year'     => $audio->getYear(),
                'genre'    => $audio->getGenre(),
            ];
        }

        if ($media->getType() === MediaFile::TYPE_VIDEO) {
            $video = $media->getVideo();
            $data += [
                'width'  => $video->getWidth(),
                'height' => $video->getHeight(),
            ];
        }

        return $data;
    }
}

Extract after storage, never inside the request if files are large. Store the upload first, then dispatch a job:

// Controller
$path = $request->file('media')->store('media');
ExtractMediaMetadata::dispatch($path);

// Job
public function handle(MediaMetadataReader $reader): void
{
    $absolute = Storage::disk('local')->path($this->path);

    try {
        $meta = $reader->read($absolute);
    } catch (\Throwable $e) {
        report($e);
        return;
    }

    MediaAsset::where('path', $this->path)->update($meta);
}

For S3 / remote disks, copy to a temp file first. The reader wants a local, seekable file handle. Storage::path() won't work for S3; do:

$tmp = tempnam(sys_get_temp_dir(), 'media');
file_put_contents($tmp, Storage::disk('s3')->get($path));

try {
    $meta = $reader->read($tmp);
} finally {
    @unlink($tmp);
}

Persist to typed columns, not a JSON blob, if you plan to filter or sort by duration/bitrate/artist. Store duration as seconds (float), bitrate as kbps integer, and keep raw tags in a nullable json column for anything the schema doesn't model.

Batch importer command. For a library scan, iterate files in chunks and wrap each read in try/catch — one corrupt file should not abort the run:

foreach (File::allFiles($dir) as $file) {
    try {
        $this->reader->read($file->getRealPath());
    } catch (\Throwable $e) {
        $this->warn("Skipped {$file->getFilename()}: {$e->getMessage()}");
    }
}

Validate early. Use Laravel's mimes: rule so the package only sees files it's expected to handle; the library throws on junk input, which is noisy in logs.

Gotchas and Tips

The package has not been released since March 2018. It predates PHP 8 typed-property culture and may emit deprecations on 8.1+. Pin the version in composer.json, run your test suite on the target PHP version, and keep the wrapper service thin so you can replace it with ffprobe or getID3 without touching call sites.

No Laravel integration ships with it — and that's fine. Don't expect a facade, config, or publishable migrations. Register your wrapper as a singleton in a service provider if you need it injected, otherwise let the container autowire it.

Local seekable files only. MP4/M4A moov atoms often sit at the end of the file; if the reader can't seek (streams, some remote wrappers), metadata parsing fails or returns nothing. Download to a temp path (see above) rather than passing a stream.

Tags are frequently null or empty. Untagged MP3s are common. Guard with ?? null and store nullable columns; don't assume getArtist() returns a string. Treat every getter as "best effort."

Expect exceptions on unsupported or corrupt files. Catch \Throwable at the job/command boundary rather than letting a single bad upload fail a queue worker. Log the file path and the exception message; that's your only debugging signal — the library won't tell you much beyond "can't parse."

Units are inconsistent across getters. Duration is seconds (float), bitrate is kbps. Format them in your API layer, not in the DB, so a future swap of the reader keeps the same storage contract.

Don't read metadata on every request. Compute once at upload time and cache it on the model. The library re-parses on each new MediaFile(...); there's no built-in caching.

Cover art / embedded images are unreliable across formats. If artwork extraction matters, verify it per-format in your target files rather than assuming uniform support, and be prepared to fall back to a placeholder.

Extension points are your own code, not the vendor directory. Everything you'd want to customize (unit conversion, null handling, exception mapping, logging) belongs in the wrapper service and a small DTO. Never patch vendor/wapmorgan/...; the project is dormant, so your fork will drift.

Watch memory on batch jobs. Release objects and unlink temp files explicitly; the reader keeps file handles open for the object's lifetime. On a queue worker processing thousands of files, that matters.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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