fireworkweb/smpte
A PHP package for working with SMPTE timecodes: parse and validate timecode strings, convert between timecodes, frames, and seconds, and handle drop-frame and non-drop-frame rates for video, broadcast, and media applications.
The package is a single, dependency-free value object. There is no service provider, facade, config file, or published assets — installation is one line and then you read one class.
composer require fireworkweb/smpte
use Fireworkweb\SMPTE\SMPTE;
$tc = new SMPTE('01:23:45:12', 25);
echo $tc; // 01:23:45:12
$tc->toFrames(); // 125637
$tc->toSeconds(); // float, frame-accurate
Where to look first:
src/SMPTE.php — the entire public API lives here (parse, toFrames(), toSeconds(), and the reverse constructors). It is small enough to read end-to-end before you use it; do that rather than trusting README snippets alone.fromFrames, fromSeconds, or similar) — confirm signatures against the source, since the surface is tiny but not heavily documented.composer.json for the PHP version constraint, so you know whether the 2024-11-05 release drops into your oldest supported runtime.First real use case: you have frame counts or seconds coming out of a media pipeline (ffprobe, an upload transcode, an edit decision list) and you need to show or exchange SMPTE timecode — HH:MM:SS:FF — in a timeline, an export, or a broadcast rundown. Parse once at the ingestion boundary, keep integers in your domain, and render timecode only at the edges.
Store frames, render timecode. The most robust pattern is to persist an integer frame count (plus the frame rate, plus a drop-frame flag) and treat SMPTE as a presentation-layer helper:
// migration: clips.duration_frames (int), clips.fps (decimal), clips.drop_frame (bool)
public function getDurationTimecodeAttribute(): string
{
return (string) new SMPTE(
$this->duration_frames,
$this->fps,
$this->drop_frame
);
}
Arithmetic goes through frames, never through strings. Summing a rundown of clips by string manipulation is where off-by-one bugs breed:
$total = collect($clips)
->sum(fn ($clip) => (new SMPTE($clip->timecode, $clip->fps))->toFrames());
$rundown = new SMPTE($total, $fps);
Wrap it in your own Timecode value object. Given the package's size and install base, define a thin application-level class (or Eloquent cast) that owns the fps and drop-frame policy and delegates to SMPTE. That gives you a single seam to swap implementations later, and a single place to validate input.
final class Timecode
{
private function __construct(
public readonly int $frames,
public readonly float $fps,
public readonly bool $dropFrame,
) {}
public static function parse(string $tc, float $fps, bool $df = false): self
{
$smpte = new SMPTE($tc, $fps, $df);
return new self($smpte->toFrames(), $fps, $df);
}
public function __toString(): string
{
return (string) new SMPTE($this->frames, $this->fps, $this->dropFrame);
}
}
Adapters for export formats. SRT, WebVTT, and ffmpeg all want something slightly different from SMPTE. Keep those as toSrt(), toVtt(), toFfmpeg() methods on your wrapper, each derived from toSeconds() or toFrames() — do not reimplement timecode parsing.
public function toSrt(): string // 00:01:23,480
{
$seconds = $this->smpte->toSeconds();
// format with comma milliseconds; floor, don't round, to avoid drift on concatenation
}
Fixtures you can assert on. Pin down canonical conversions as PHPUnit tests so an upstream change can't silently shift results: at 25 fps, one hour is exactly 90000 frames and 3600 seconds; at 24 fps, 00:00:00:23 + 1 frame rolls the seconds field.
Use it at the seams of a media pipeline. Practical spots: an upload handler that records duration_frames from ffprobe, an admin UI that shows timecode next to a video scrubber, a CSV/broadcast export that must be in HH:MM:SS:FF, and a subtitle generator that needs frame-accurate in/out points.
HH:MM:SS;FF) is the common convention for flagging drop-frame, but do not assume the parser infers it from the separator. Carry (fps, dropFrame) alongside every stored timecode and pass both explicitly.FF must be less than the frame rate. Decide up front whether invalid input throws or coerces, and enforce it in your wrapper so bad values never reach the library.toSeconds() is floating-point. Round for display, floor for frame math, and never round-trip seconds → timecode → seconds expecting bit-exact equality. Frames are the lossless representation.fps - 1, fps, and fps + 1 frames explicitly."fireworkweb/smpte": "x.y.z") rather than using a caret constraint, and keep your own tests around the conversions your app depends on.Timecode class) beats inheritance here — it keeps the third-party import confined to one file if you ever need to replace it.(string) $tc, toFrames(), and toSeconds() side by side for the same input. In practice the culprit is the frame rate passed in, not the library.How can I help you explore Laravel packages today?