media

package
v3.1.3 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package media is WaxTap's in-process audio engine: it probes, transcodes, remuxes, and cuts local audio files by calling the pure-Go WaxFlow library rather than shelling out to ffmpeg.

Callers pass file paths; the package opens them as WaxFlow sources, bounds the number of concurrent operations, and stages output atomically next to the destination. A Codec selects the target format; Spec adds an optional bitrate, a downmix, and a normalization gain, all fused into one WaxFlow pass. CodecCopy is the no-re-encode mode, served by a container-level remux.

Index

Constants

View Source
const ContainerProgressive = waxflow.ContainerProgressive

ContainerProgressive is WaxFlow's flat (moov+mdat) MP4 container override, re-exported for callers that remux through RemuxContainer.

Variables

This section is empty.

Functions

func CheckOutputContainer

func CheckOutputContainer(codec Codec, output string) error

CheckOutputContainer reports whether output's extension can hold codec. Copy passes (its container follows the source), as does an extensionless or codec-named output (force-muxed, so the extension does not constrain it). A recognized extension that cannot hold codec returns waxerr.ErrIncompatibleSpec with suggested containers.

func ContainerAccepts

func ContainerAccepts(ext, codecName string) bool

ContainerAccepts reports whether the container named by ext can hold the given codec unchanged (a copy/remux). Some extensions support several codecs, so this consults a compatibility table rather than comparing names; unknown extensions are permissive. codecName may be a codecName-mapped value ("opus", "aac", "pcm", "wav", "aiff").

PCM has two container-defining names: "wav" and "aiff" are Codec.String() values naming a container, while "pcm" is what a probe reports for a source. isPCM covers RIFF and probed sources only; AIFF has its own branch, and Matroska takes PCM through WaxFlow's wav row.

func ContainersFor

func ContainersFor(codecName string) []string

ContainersFor returns a short list of conventional container extensions, each with a leading dot, that can hold codecName unchanged. The result is a subset of the extensions ContainerAccepts allows. Unknown codecs return nil.

func IsAIFFExt

func IsAIFFExt(ext string) bool

IsAIFFExt reports whether ext names an AIFF container. WaxFlow's aiff row registers four spellings for one container; .aifc/.afc name the compressed-capable variant, which the muxer selects from the sample format rather than the filename.

The list lives here rather than in each table because the output extension is what picks between PCM's two containers, so a spelling missed by one table gets RIFF bytes in an AIFF file. ext must be lowercased and undotted, which every caller already guarantees.

func OutputFormats

func OutputFormats() []string

OutputFormats lists the audio formats the in-process engine can produce. The doctor command reports it as WaxTap's capability set.

func ValidateCrossfade

func ValidateCrossfade(keeps []cutrange.Range, d time.Duration) error

ValidateCrossfade checks whether the retained spans can supply the requested overlap. A crossfade consumes d from both sides of each join, so an interior span must be at least 2*d. Rejecting short spans up front avoids an encode that emits no audio.

Types

type Codec

type Codec uint8

Codec identifies an output audio encoding. CodecCopy rewrites the source packets into a new container without re-encoding (a remux). The lossless codecs (FLAC, ALAC, WAV, AIFF) decode and encode; they are not stream copies.

const (
	CodecCopy   Codec = iota // container rewrite / remux (the only no-re-encode path)
	CodecFLAC                // lossless re-encode (.flac)
	CodecALAC                // lossless re-encode (Apple Lossless in .m4a)
	CodecWAV                 // lossless PCM (.wav)
	CodecMP3                 // MP3, CBR 320 by default
	CodecAAC                 // AAC-LC in .m4a
	CodecOpus                // Opus (.opus)
	CodecVorbis              // Vorbis (.ogg)
	CodecAIFF                // lossless PCM (.aiff)
)

func (Codec) Extension

func (c Codec) Extension() string

Extension returns the canonical file extension (without a dot) for c, or "" for CodecCopy (whose container follows the source).

func (Codec) IsLossless

func (c Codec) IsLossless() bool

IsLossless reports whether c is a remux or a lossless encoder (FLAC, ALAC, WAV, AIFF). They keep the source bit depth unless Spec.BitDepth asks for a specific one, so by default they never narrow a higher-depth source.

func (Codec) String

func (c Codec) String() string

type CutResult

type CutResult struct {
	Output  string
	Removed time.Duration
	Mode    Mode
	Applied bool
	// Levels is WaxFlow's level measurement of the cut re-encode; see
	// Result.Levels. It is always zero for a lossless cut-remux.
	Levels Levels
}

CutResult reports a completed cut.

type CutSpec

type CutSpec struct {
	Keeps     []cutrange.Range
	Total     time.Duration
	Crossfade time.Duration
	CopyCut   bool
	// RequireCopy fails the cut rather than re-encoding when WaxFlow declines the
	// lossless cut-remux. It expresses an explicit copy request (--format copy or
	// --cut-mode copy): silently re-encoding would break the caller's promise.
	RequireCopy bool
	Encode      Spec
}

CutSpec describes a resolved cut. Keeps are the spans to retain, in order, on the source timeline; Total is the source duration. CopyCut asks for a lossless cut-remux (kept codec, byte-identical packets), which WaxTap tries first and falls back from to Encode when WaxFlow declines the source codec. Encode names the re-encode used for the re-encode path (or the CopyCut fallback).

type Levels added in v3.1.3

type Levels struct {
	ClippedSamples int64   // channel samples the integer output clamped
	Samples        int64   // output frames (per channel)
	Channels       int     // output channel count
	TruePeak       float64 // output true peak, linear, 1.0 = full scale
	Quantized      bool    // a quantizer ran (float cut to integer)
}

Levels carries WaxFlow's level measurement of one encode as numbers, so the caller sets the warning policy (thresholds, per-cause remedies, aggregation) instead of receiving a pre-rendered sentence. See waxflow.TranscodeResult for the field semantics. The zero value reports nothing.

func (Levels) Note added in v3.1.3

func (l Levels) Note() string

Note renders WaxFlow's one-line level warning for these levels, or "" when they warrant none: the clipped-sample count, else a true peak past full scale on a quantized output. The wording is WaxFlow's own, reconstructed through its LevelNote so the two never drift; callers may append remedies.

type Mode

type Mode uint8

Mode selects how a cut is rendered. The facade maps waxtap.CutMode onto these.

const (
	// ModeSmart copies (lossless cut-remux) when the cut keeps the source codec,
	// and re-encodes when a transcode, downmix, gain, or crossfade is involved.
	ModeSmart Mode = iota
	// ModeCopy forces a lossless cut-remux. It cannot transcode, downmix, or
	// crossfade, and it fails when WaxFlow cannot cut-remux the source codec.
	ModeCopy
	// ModeAccurate forces a re-encode.
	ModeAccurate
)

func (Mode) String

func (m Mode) String() string

type ProbeFormat

type ProbeFormat struct {
	Container string        // identified container name ("webm", "mka", "flac", ...)
	Duration  time.Duration // container duration, or 0 when unknown
	Size      int64         // bytes, or 0 when unknown
	// BitRate is always 0: WaxFlow does not report a container bit rate, so
	// callers fall back to a size/duration estimate (mapping.applyProbe).
	BitRate int
}

ProbeFormat describes the container.

type ProbeResult

type ProbeResult struct {
	Format  ProbeFormat   // container metadata
	Streams []ProbeStream // audio tracks
}

ProbeResult is a probe of a local audio file: container metadata plus the audio track's codec details.

func (ProbeResult) AudioStream

func (p ProbeResult) AudioStream() (ProbeStream, bool)

AudioStream returns the first audio track and true, or a zero stream and false when the result carries none. Probe rejects no-audio inputs, so a result obtained from Probe always has one.

type ProbeStream

type ProbeStream struct {
	CodecType  string        // always "audio"
	CodecName  string        // codecName-mapped: "opus", "aac", "flac", "pcm", ...
	SampleRate int           // Hz
	Channels   int           // channel count
	BitRate    int           // always 0 (WaxFlow reports none)
	Duration   time.Duration // track duration, or 0 when unknown
}

ProbeStream describes one audio track.

type Result

type Result struct {
	Output string // final output path
	Size   int64  // output size in bytes (0 if it could not be stat'd)
	Codec  Codec  // codec the output was encoded with
	// Levels is WaxFlow's level measurement of the encode. It is zero for a
	// container copy, which never re-derives samples.
	Levels Levels
}

Result reports a completed transcode.

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner drives WaxFlow's engine for local audio files. It bounds concurrency, and it is safe for concurrent use.

func NewRunner

func NewRunner(cfg RunnerConfig) *Runner

NewRunner builds a Runner. WaxFlow's engine construction cannot fail, so there is no error to return.

func (*Runner) AnalyzeFile

func (r *Runner) AnalyzeFile(ctx context.Context, input string, channels int) (*waxflow.AnalyzeResult, error)

AnalyzeFile measures the loudness of a whole local file. channels, when 1 or 2, measures after folding to that channel count so a two-pass gain matches a later downmixing encode; 0 keeps the source layout. The loudness package maps the result to its own type; this keeps the WaxFlow engine and the concurrency bound in one place.

func (*Runner) AnalyzeMedia

func (r *Runner) AnalyzeMedia(ctx context.Context, med format.Media, channels int) (*waxflow.AnalyzeResult, error)

AnalyzeMedia measures the loudness of an already-open Media, so a cut/downmix composition is measured as it will be encoded. channels folds the measurement to a downmix target (0 keeps the source layout). The caller owns med.

func (*Runner) Engine

func (r *Runner) Engine() *waxflow.Engine

Engine returns the underlying WaxFlow engine, for callers (loudness) that measure through it directly.

func (*Runner) OpenAlbumConcat

func (r *Runner) OpenAlbumConcat(inputs []string) (format.Media, func() error, error)

OpenAlbumConcat opens the gapless concatenation of inputs as one Media, for a group loudness measurement. WaxFlow conforms members whose rate differs from the envelope's, and opens each member lazily: only the track headers are read up front (one descriptor at a time), and Concat opens and closes each member's file as the timeline reaches it, so a large album holds one descriptor open rather than one per track. The caller closes the returned Media via closer.

func (*Runner) OpenComposed

func (r *Runner) OpenComposed(input string, keeps []cutrange.Range, total, crossfade time.Duration) (format.Media, func() error, error)

OpenComposed opens the cut-composed Media for measurement (loudness), without re-encoding. The caller closes it.

func (*Runner) Probe

func (r *Runner) Probe(ctx context.Context, input string) (ProbeResult, error)

Probe inspects a local input and returns the parsed result. Undecodable media and media with no audio track are reported as waxerr.ErrUnsupportedInput.

func (*Runner) RemuxContainer

func (r *Runner) RemuxContainer(ctx context.Context, input, output, container string) error

RemuxContainer remuxes input to output using an explicit WaxFlow container override, e.g. "progressive" to flatten a fragmented MP4 into a tag-writable progressive one. It is a packet copy (no re-encode). input and output may be the same path; the source is closed before the atomic rename.

func (*Runner) Render

func (r *Runner) Render(ctx context.Context, input, output string, spec CutSpec) (CutResult, error)

Render applies spec's cut to input and writes the result to output. Output is staged and atomically renamed on success. When CopyCut is set and no downmix, gain, or crossfade is requested, Render tries a lossless cut-remux first and re-encodes only if WaxFlow declines the source codec.

func (*Runner) Transcode

func (r *Runner) Transcode(ctx context.Context, input, output string, spec Spec) (Result, error)

Transcode reads input, applies spec, and writes the result to output. The output is staged in a temp file in output's directory and atomically renamed into place on success; on failure or cancellation the temp is removed and any existing file at output is left untouched.

CodecCopy is a whole-file container remux (no re-encode); it rejects a channel or gain change, which require decoding.

type RunnerConfig

type RunnerConfig struct {
	// MaxProcs bounds concurrent WaxFlow operations (0 = unlimited). Each
	// operation runs one goroutine, so this maps operation count onto cores and
	// caps peak memory from pooled decode/DSP/encode buffers.
	MaxProcs int
	// Logger receives debug logs. Nil discards them.
	Logger *slog.Logger
}

RunnerConfig configures a Runner.

type Spec

type Spec struct {
	Codec    Codec
	Bitrate  int
	BitDepth int     // forced output depth (16 or 24); 0 follows the decoded stream
	Channels int     // output channel count (downmix); 0 keeps the source layout
	GainDB   float64 // scalar normalization gain in dB; 0 is a no-op
}

Spec describes a transcode. Codec selects the output codec; Bitrate overrides lossy preset defaults in bits per second; BitDepth forces integer output for the codecs that hold integer PCM; Channels downmixes (1 or 2) when positive; GainDB applies a normalization gain (0 is a no-op). The zero value is a container remux (CodecCopy) with no processing.

Directories

Path Synopsis
Package loudness measures EBU R128 loudness with WaxFlow's analyzer and derives the scalar gain that normalizes a track to a target.
Package loudness measures EBU R128 loudness with WaxFlow's analyzer and derives the scalar gain that normalizes a track to a target.

Jump to

Keyboard shortcuts

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