mediaprobe

package module
v0.0.0-...-b1935ac Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 30 Imported by: 0

README

mediaprobe

mediaprobe is a Go library for bounded observation of media streams, currently focused on live sources. It reports tracks, timing, cadence, bitrate, and H.264 DTS diagnostics.

The API and report format are early and versioned; they may evolve as the library develops.

Install

go get github.com/zbiljic/mediaprobe

Usage

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/zbiljic/mediaprobe"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	report, err := mediaprobe.ProbeURL(ctx, "https://example.com/live/index.m3u8")
	if err != nil {
		fmt.Printf("probe error: %v (partial tracks: %d)\n", err, len(report.Tracks))
		return
	}

	fmt.Printf("complete: %v, tracks: %d\n", report.IsComplete, len(report.Tracks))
}

When probing returns an error, the returned report may still contain partial measurements.

Supported URLs

ProbeURL currently supports HTTP(S) HLS, RTSP(S), RTMP(S), SRT, and raw MPEG-TS over TCP or UDP.

Finite/local-file inspection is not yet a built-in API; callers can use ProbeStream with their own Stream adapter.

Development

Run the unit tests:

go test ./...

Run the full project checks:

mise run pre-commit

License

Licensed under the MIT License.

Documentation

Overview

Package mediaprobe observes live media streams and reports codec and timestamp properties without exposing protocol-specific parser types.

Index

Examples

Constants

View Source
const (
	// DefaultObservationDuration is the media timeline covered by a probe when
	// no observation-duration option is provided.
	DefaultObservationDuration = 15 * time.Second
	// DefaultSampleLimit is the maximum number of sample records retained by a
	// probe when no sample-limit option is provided.
	DefaultSampleLimit = 100_000
)
View Source
const ReportSchemaVersion = "0.1"

ReportSchemaVersion is the version of the JSON representation produced for Report values.

Variables

View Source
var (
	ErrNoTracks                   = errors.New("mediaprobe: stream has no tracks")
	ErrInvalidTrack               = errors.New("mediaprobe: invalid track")
	ErrInvalidSample              = errors.New("mediaprobe: invalid sample")
	ErrUnknownTrack               = errors.New("mediaprobe: sample references unknown track")
	ErrObservationIncomplete      = errors.New("mediaprobe: observation incomplete")
	ErrInvalidObservationDuration = errors.New("mediaprobe: invalid observation duration")
	ErrInvalidSampleLimit         = errors.New("mediaprobe: invalid sample limit")
	ErrSampleLimitExceeded        = errors.New("mediaprobe: sample limit exceeded")
	ErrInvalidURL                 = errors.New("mediaprobe: invalid URL")
	ErrUnsupportedScheme          = errors.New("mediaprobe: unsupported URL scheme")
)

Functions

This section is empty.

Types

type Codec

type Codec string

Codec identifies a media codec. It is intentionally open-ended so adapters can report codecs added by their underlying media libraries.

const (
	CodecAC3        Codec = "ac3"
	CodecAAC        Codec = "aac"
	CodecAV1        Codec = "av1"
	CodecEAC3       Codec = "eac3"
	CodecFLAC       Codec = "flac"
	CodecG711       Codec = "g711"
	CodecH264       Codec = "h264"
	CodecH265       Codec = "h265"
	CodecJPEG       Codec = "jpeg"
	CodecKLV        Codec = "klv"
	CodecLPCM       Codec = "lpcm"
	CodecMPEG1Audio Codec = "mpeg1audio"
	CodecMPEG1Video Codec = "mpeg1video"
	CodecMPEG4Audio Codec = "mpeg4audio"
	CodecMPEG4Video Codec = "mpeg4video"
	CodecOpus       Codec = "opus"
	CodecVP8        Codec = "vp8"
	CodecVP9        Codec = "vp9"
)

type H264DTSError

type H264DTSError struct {
	At      time.Duration    `json:"at"`
	Code    H264DTSErrorCode `json:"code"`
	Message string           `json:"message"`
}

H264DTSError describes the first DTS derivation failure observed for a track. At is elapsed media time from the first received access unit.

type H264DTSErrorCode

type H264DTSErrorCode string

H264DTSErrorCode identifies a DTS derivation failure without requiring callers to parse the implementation-specific error message.

const (
	H264DTSErrorTooManyReorderedFrames H264DTSErrorCode = "too_many_reordered_frames"
	H264DTSErrorNonMonotonic           H264DTSErrorCode = "non_monotonic_dts"
	H264DTSErrorAfterPTS               H264DTSErrorCode = "dts_after_pts"
	H264DTSErrorUnsupportedPOC         H264DTSErrorCode = "unsupported_picture_order"
	H264DTSErrorMissingSPS             H264DTSErrorCode = "missing_sps"
	H264DTSErrorInvalidSPS             H264DTSErrorCode = "invalid_sps"
	H264DTSErrorOther                  H264DTSErrorCode = "other"
)

type H264DTSReport

type H264DTSReport struct {
	// Samples is the number of access units examined. Examination stops after
	// the first error.
	Samples int           `json:"samples"`
	Error   *H264DTSError `json:"error,omitempty"`
}

H264DTSReport describes attempts to derive DTS from H.264 picture order and PTS, as required by muxers that do not retain source-provided DTS.

type H264Report

type H264Report struct {
	DTS H264DTSReport `json:"dts"`
}

H264Report contains diagnostics that require access to H.264 access units.

type MediaType

type MediaType string

MediaType identifies the kind of samples carried by a track.

const (
	MediaTypeUnknown MediaType = "unknown"
	MediaTypeVideo   MediaType = "video"
	MediaTypeAudio   MediaType = "audio"
	MediaTypeData    MediaType = "data"
)

type Option

type Option func(*probeOptions)

Option configures ProbeStream and ProbeURL.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets the client used for HTTP-based streams. ProbeURL does not mutate the client. A nil client uses default HTTP settings.

func WithHTTPHeaders

func WithHTTPHeaders(headers map[string]string) Option

WithHTTPHeaders adds headers to every request made for HTTP-based streams, including cross-origin resources referenced by a playlist. The values are copied when this option is created and whenever it is applied.

func WithObservationDuration

func WithObservationDuration(duration time.Duration) Option

WithObservationDuration overrides DefaultObservationDuration. The duration must be greater than zero.

func WithSampleLimit

func WithSampleLimit(limit int) Option

WithSampleLimit sets the maximum number of sample records retained across all tracks. The limit must be greater than zero.

type Rate

type Rate struct {
	Numerator   uint32 `json:"numerator"`
	Denominator uint32 `json:"denominator"`
}

Rate is an exact rational rate, such as 30000/1001 frames per second.

func (Rate) Float64

func (r Rate) Float64() float64

Float64 returns the decimal representation of the rate.

func (Rate) IsValid

func (r Rate) IsValid() bool

IsValid reports whether the rate has a non-zero numerator and denominator.

func (Rate) String

func (r Rate) String() string

String returns the rate in numerator/denominator form.

type Report

type Report struct {
	Tracks              []TrackReport `json:"tracks"`
	ObservationDuration time.Duration `json:"observation_duration"`
	IsComplete          bool          `json:"complete"`
}

Report contains measurements from one stream incarnation.

func ProbeStream

func ProbeStream(ctx context.Context, stream Stream, options ...Option) (Report, error)

ProbeStream observes a stream until it covers the configured duration of media time. Callers should create a new Stream for each upstream incarnation or reconnect. On failure, the returned report contains any measurements collected so far.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"time"

	"github.com/zbiljic/mediaprobe"
)

func main() {
	stream := &exampleStream{
		samples: []mediaprobe.Sample{
			{TrackID: 1, PTS: 0, DTS: 0, Size: 1_000},
			{TrackID: 1, PTS: time.Second, DTS: time.Second, Size: 1_000},
			{TrackID: 1, PTS: 2 * time.Second, DTS: 2 * time.Second, Size: 1_000},
		},
	}

	report, err := mediaprobe.ProbeStream(
		context.Background(),
		stream,
		mediaprobe.WithObservationDuration(2*time.Second),
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	track := report.Tracks[0]
	normalizeInCaller := track.IsVariableFrameRate ||
		track.NonMonotonicDTS > 0 ||
		track.TimestampDiscontinuities > 0 ||
		(track.H264 != nil && track.H264.DTS.Error != nil)

	fmt.Println("complete:", report.IsComplete)
	fmt.Println("samples:", track.SampleCount)
	fmt.Printf("observed frame rate: %.1f\n", track.ObservedFrameRate)
	fmt.Println("normalize in caller:", normalizeInCaller)

}

var _ mediaprobe.Stream = (*exampleStream)(nil)

type exampleStream struct {
	samples []mediaprobe.Sample
	next    int
}

func (*exampleStream) Tracks(context.Context) ([]mediaprobe.Track, error) {
	return []mediaprobe.Track{{
		ID:                1,
		MediaType:         mediaprobe.MediaTypeVideo,
		Codec:             mediaprobe.CodecH264,
		DeclaredFrameRate: mediaprobe.Rate{Numerator: 1, Denominator: 1},
	}}, nil
}

func (s *exampleStream) ReadSample(context.Context) (mediaprobe.Sample, error) {
	if s.next == len(s.samples) {
		return mediaprobe.Sample{}, io.EOF
	}

	sample := s.samples[s.next]
	s.next++
	return sample, nil
}
Output:
complete: true
samples: 3
observed frame rate: 1.0
normalize in caller: false

func ProbeURL

func ProbeURL(ctx context.Context, rawURL string, options ...Option) (Report, error)

ProbeURL connects to a live media URL and observes it for the configured duration of media time. HTTP(S) HLS, RTSP(S), RTMP(S), SRT, and raw MPEG-TS over TCP or UDP are currently supported. Callers should provide a context with an appropriate wall-clock deadline.

func (Report) MarshalJSON

func (r Report) MarshalJSON() ([]byte, error)

MarshalJSON returns the stable JSON representation of a probe report. The Go model remains flat for convenient caller access, while the wire model groups declared media metadata separately from measurements made during probing.

type Sample

type Sample struct {
	TrackID    int
	PTS        time.Duration
	DTS        time.Duration
	Duration   time.Duration
	IsKeyFrame bool
	Size       int
	// contains filtered or unexported fields
}

Sample describes the timing of one compressed media sample. When a transport does not distinguish PTS and DTS, its adapter should set both to the same value.

type Stream

type Stream interface {
	Tracks(context.Context) ([]Track, error)
	ReadSample(context.Context) (Sample, error)
}

Stream is the transport-neutral input consumed by ProbeStream. Implementations can wrap HLS, RTSP, RTMP, SRT, MPEG-TS, or another live demuxer. The caller retains ownership of the stream and is responsible for closing it when necessary.

type Track

type Track struct {
	ID                int       `json:"id"`
	MediaType         MediaType `json:"type"`
	Codec             Codec     `json:"codec"`
	ClockRate         int       `json:"clock_rate,omitempty"`
	Width             int       `json:"width,omitempty"`
	Height            int       `json:"height,omitempty"`
	SampleRate        int       `json:"sample_rate,omitempty"`
	Channels          int       `json:"channels,omitempty"`
	DeclaredFrameRate Rate      `json:"declared_frame_rate,omitzero"`
}

Track describes a logical media track. Protocol-specific codec data remains owned by the source implementation.

type TrackReport

type TrackReport struct {
	Track
	SampleCount               int           `json:"sample_count"`
	ObservedPTSSpan           time.Duration `json:"observed_pts_span"`
	ObservedDTSSpan           time.Duration `json:"observed_dts_span"`
	Bitrate                   uint64        `json:"bitrate,omitempty"`
	ObservedFrameRate         float64       `json:"fps,omitempty"`
	NominalFrameDuration      time.Duration `json:"nominal_frame_duration,omitempty"`
	MaxFrameDurationDeviation time.Duration `json:"max_frame_duration_deviation,omitempty"`
	IrregularFrameIntervals   int           `json:"irregular_frame_intervals,omitempty"`
	NonMonotonicDTS           int           `json:"non_monotonic_dts,omitempty"`
	TimestampDiscontinuities  int           `json:"timestamp_discontinuities,omitempty"`
	IsVariableFrameRate       bool          `json:"variable_frame_rate,omitempty"`
	H264                      *H264Report   `json:"h264,omitempty"`
}

TrackReport contains measurements for one track.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL