README
¶
gocue
gocue is a Go audio analysis tool for professional playout workflows. It detects cue-in, cue-out, and overlay points and measures EBU R128 loudness, then prints JSON on stdout for Liquidsoap’s autocue: protocol.
gocue is a stand-alone analyzer: it reads existing liq_* / ReplayGain tags when they are complete (to skip a full ffmpeg scan) and does not write tags back to files. Tag write-back, fades, and playlist wiring belong in a Liquidsoap .liq script. You can start from autocue.gocue.liq (adapted from Moonbase59’s autocue). See the Liquidsoap autocue docs for details.
For algorithm background, see Moonbase59’s autocue presentation.
Features
- Audio analysis — cue-in / cue-out and crossfade overlay points
- EBU R128 — integrated loudness, loudness range, and true peak via ffmpeg
- Tag cache (read-only) — reuses existing file tags when complete; recomputes amplify for a new target LUFS without re-decoding
- Formats — WAV, OGG, MP3, FLAC, M4A, WMA, ASF, AIFF, and more (anything ffmpeg/ffprobe support)
- Liquidsoap-oriented JSON — unit-suffixed loudness/gain fields matching the autocue protocol
- Configurable thresholds — silence, overlay, longtail, blankskip, clipping prevention
Quick Start
Prerequisites
- Go 1.26 or higher (to build from source)
- FFmpeg and FFprobe on
PATH - An audio file to analyze
Installation
From source
git clone https://github.com/iSerganov/gocue.git
cd gocue
make build
# binary: ./dist/gocue (version injected via ldflags from Makefile VERSION)
Using Go
go install github.com/iSerganov/gocue@latest
Basic usage
# Analyze with defaults (JSON on stdout; diagnostics on stderr)
./gocue audio_file.wav
# Pretty-print JSON
./gocue -n audio_file.wav
# Custom loudness target (-16 LUFS instead of -18)
./gocue -t -16 audio_file.wav
Usage
Command-line options
| Flag | Short | Default | Description |
|---|---|---|---|
--target |
-t |
-18.0 |
LUFS reference target (-23.0 to 0.0) |
--silence |
-s |
-42.0 |
LU below integrated loudness for cue-in & cue-out |
--overlay |
-o |
-8.0 |
LU below integrated loudness to start the next track |
--longtail |
-l |
15.0 |
Overlay duration (s) that triggers long-tail recalculation (0–60) |
--extra |
-x |
-12.0 |
Extra LU below overlay for long-tail / sustained endings |
--drop |
-d |
40.0 |
Max % loudness drop still counted as sustained ending (0–100; 0 disables) |
--noclip |
-k |
false |
Lower gain if needed so true peak stays at or below −1 dBFS |
--nice |
-n |
false |
Pretty-print JSON |
--blankskip |
-b |
0.0 |
Early cue-out on in-track silence longer than this many seconds (0 = off) |
--exec_timeout |
-e |
20s |
Timeout for ffprobe/ffmpeg |
--print_flags |
-p |
false |
Log flag values to stderr (stdout stays JSON-only) |
Parameter ranges
- Target LUFS: −23.0 to 0.0
- Silence / overlay / extra: −96.0 to 0.0
- Longtail / blankskip: 0.0 to 60.0 seconds
- Sustained drop: 0.0 to 100.0 percent
Output format
JSON is written to stdout. Progress/analysis lines go to stderr, so pipes and Liquidsoap stay machine-readable.
Loudness and gain fields are unit-suffixed strings (as required by the autocue protocol). Time fields and true peak (linear) are JSON numbers; booleans are JSON booleans.
{
"duration": 180.5,
"liq_cue_duration": 175.2,
"liq_cue_in": 2.8,
"liq_cue_out": 178.0,
"liq_cross_start_next": 173.5,
"liq_longtail": false,
"liq_sustained_ending": true,
"liq_loudness": "-14.200 LUFS",
"liq_loudness_range": "8.500 LU",
"liq_amplify": "3.800 dB",
"liq_amplify_adjustment": "0.000 dB",
"liq_reference_loudness": "-18.000 LUFS",
"liq_blankskip": 0.0,
"liq_blank_skipped": false,
"liq_true_peak": 0.95,
"liq_true_peak_db": "-0.400 dBFS"
}
Output fields
| Field | Meaning |
|---|---|
duration |
File duration in seconds (from ffprobe when available) |
liq_cue_duration |
Playout length (cue_out − cue_in), seconds |
liq_cue_in / liq_cue_out |
Cue points from start of file, seconds |
liq_cross_start_next |
Suggested next-track overlay point, seconds |
liq_longtail |
Long fade-out handling was applied |
liq_sustained_ending |
Sustained ending handling was applied |
liq_loudness |
Integrated loudness ("… LUFS") |
liq_loudness_range |
Loudness range ("… LU") |
liq_amplify |
Gain to reach target ("… dB") |
liq_amplify_adjustment |
Extra reduction from --noclip ("… dB") |
liq_reference_loudness |
Target used ("… LUFS") |
liq_blankskip |
Blank-skip setting used, seconds |
liq_blank_skipped |
Early cue-out due to in-track silence |
liq_true_peak |
Linear true peak |
liq_true_peak_db |
True peak ("… dBFS") |
Note: gocue picks the latest of the normal / sustained / longtail overlay candidates so special endings stay intact.
Real-world examples
Hidden track
The Nirvana track Something in the Way / Endless, Nameless (Nevermind, 1991) has ~3:48 of song, ~10 minutes of silence, then the hidden track Endless, Nameless:
Normal mode (blankskip off, default):
$ gocue "Nirvana - Something in the Way _ Endless, Nameless.mp3"
# diagnostics on stderr, e.g.:
# Overlay: -18.47 LUFS, Longtail: -33.47 LUFS, Measured end avg: -41.05 LUFS, Drop: 38.91%
# Overlay times: 1222.30/1228.10/0.00 s (normal/sustained/longtail), using: 1228.10s.
# Cue out time: 1232.20 s
{"duration":1235.1,"liq_cue_duration":1232.2,"liq_cue_in":0,"liq_cue_out":1232.2,"liq_cross_start_next":1228.1,"liq_longtail":false,"liq_sustained_ending":true,"liq_loudness":"-10.470 LUFS","liq_loudness_range":"7.900 LU","liq_amplify":"-7.530 dB","liq_amplify_adjustment":"0.000 dB","liq_reference_loudness":"-18.000 LUFS","liq_blankskip":0,"liq_blank_skipped":false,"liq_true_peak":1.632,"liq_true_peak_db":"4.250 dBFS"}
With blank detection (cue-out at start of silence):
$ gocue -b 5 -- "Nirvana - Something in the Way _ Endless, Nameless.mp3"
# Cue out near the start of the long silence (~227.5 s) instead of the end of the file
{"duration":1235.1,"liq_cue_duration":227.5,"liq_cue_in":0,"liq_cue_out":227.5,"liq_cross_start_next":224.1,"liq_longtail":false,"liq_sustained_ending":false,"liq_loudness":"-10.470 LUFS","liq_loudness_range":"7.900 LU","liq_amplify":"-7.530 dB","liq_amplify_adjustment":"0.000 dB","liq_reference_loudness":"-18.000 LUFS","liq_blankskip":5,"liq_blank_skipped":true,"liq_true_peak":1.632,"liq_true_peak_db":"4.250 dBFS"}
Long tail handling
Bohemian Rhapsody has a long ending that should not be cut by an early overlay:
$ gocue "Queen - Bohemian Rhapsody.flac"
# Overlay times: 336.50/348.50/348.50 s (normal/sustained/longtail), using: 348.50s.
{"duration":355.1,"liq_cue_duration":353,"liq_cue_in":0,"liq_cue_out":353,"liq_cross_start_next":348.5,"liq_longtail":true,"liq_sustained_ending":true,"liq_loudness":"-15.500 LUFS","liq_loudness_range":"15.960 LU","liq_amplify":"-2.500 dB","liq_amplify_adjustment":"0.000 dB","liq_reference_loudness":"-18.000 LUFS","liq_blankskip":0,"liq_blank_skipped":false,"liq_true_peak":0.99,"liq_true_peak_db":"-0.090 dBFS"}
How that result is chosen:
- Cue-out — scan backwards from the end for momentary loudness above
loudness + --silence(default −42 LU → ~−60 LU noise floor at −18 LUFS target). - Normal overlay — scan backwards from cue-out for loudness above
loudness + --overlay(default −8 LU). Here that yields ~336.5 s (too early). - Long tail — if
cue_out − overlayexceeds--longtail(default 15 s), recalculate with--extra(default −12 LU). Sustained endings use a similar path via--drop. - Final overlay — the latest of normal / sustained / longtail is used (
348.5s here,liq_longtail: true).
Fade length after overlay is configured in Liquidsoap (not by gocue), e.g.:
settings.autocue.gocue.fade_out := 2.5 # seconds
Blank (silence) detection
Use --blankskip for “hidden track” gaps. It is off by default (0.0) in gocue—enable it only when you want early cue-outs on long in-track silence. Avoid it for spoken word, jingles, ads, DJ sets, or podcasts.
./gocue -b 5 audio_file.mp3 # cue-out after ≥5 s of silence
./gocue -b 0 audio_file.mp3 # explicit disable (same as default)
./gocue -b 10 audio_file.mp3 # require longer silence
Liquidsoap protocol
Requires Liquidsoap 2.2.5+.
Prefix a playlist or request with autocue::
radio = playlist(prefix="autocue:", "/path/to/playlist.m3u")
Or use enable_autocue_metadata() for all files—use one approach, not both. For video streams, exclude media with liq_gocue=false (full video analysis is expensive).
Typical settings (defaults shown; exact keys depend on your .liq script):
settings.autocue.gocue.path := "gocue"
settings.autocue.gocue.fade_in := 0.1 # seconds
settings.autocue.gocue.fade_out := 2.5 # seconds
settings.autocue.gocue.timeout := 60.0 # seconds
settings.autocue.gocue.target := -18.0 # LUFS
settings.autocue.gocue.silence := -42.0 # LU below track loudness
settings.autocue.gocue.overlay := -8.0 # LU below track loudness
settings.autocue.gocue.longtail := 15.0 # seconds
settings.autocue.gocue.overlay_longtail := -12.0 # extra LU
settings.autocue.gocue.sustained_loudness_drop := 40.0
settings.autocue.gocue.noclip := false
settings.autocue.gocue.blankskip := 0.0
settings.autocue.gocue.unify_loudness_correction := true
settings.autocue.gocue.write_tags := false # Liquidsoap writes liq_* tags (not gocue itself)
settings.autocue.gocue.write_replaygain := false
settings.autocue.gocue.force_analysis := false
settings.autocue.gocue.nice := false
Advanced configuration
# Long tail: longer threshold, deeper extra LU
./gocue -l 20 -x -15 audio_file.wav
# Sustained ending sensitivity
./gocue -d 30 -x -10 audio_file.wav
# Clipping prevention (true peak ≤ −1 dBFS)
./gocue -k audio_file.wav
# Blank skip (hidden tracks)
./gocue -b 5 audio_file.wav
Use cases
Radio automation
Wire gocue through your Liquidsoap autocue script (see links above). A minimal sketch:
radio = playlist(prefix="autocue:", "~/music/")
Batch analysis
for file in *.wav; do
./gocue -t -16 -s -45 -o -6 "$file" > "${file%.wav}.json"
done
Strict EBU R128 target
./gocue -t -23 -s -60 -o -10 content.wav
Architecture
| Piece | Role |
|---|---|
cmd/cue |
Cobra CLI: flags, validation, JSON on stdout |
pkg/cue.Calculator |
ffprobe tag probe + ffmpeg ebur128 scan |
pkg/cue.Result |
Numeric result; unit suffixes only at JSON/YAML marshal |
pkg/cue.Frame |
Per-frame momentary loudness (~100 ms) |
Dependencies: FFmpeg/FFprobe (external), Cobra, yaml.v3 (library marshal helper).
Performance notes:
- Streams ffmpeg stdout; does not load the whole audio file into Go memory
- Reuses complete tags from the file when possible (read-only cache)
- Configurable
--exec_timeoutfor probe/scan
Testing
go test ./...
go test -race -count=1 ./...
go test ./pkg/cue -v
Requires ffmpeg / ffprobe on PATH for scan/probe tests.
Project structure
gocue/
├── cmd/cue/ # CLI
├── pkg/cue/ # Library
│ ├── calculator.go
│ ├── scan.go
│ ├── result.go
│ ├── frame.go
│ ├── error.go
│ └── test_data/ # Audio fixtures
├── main.go
├── Makefile
└── go.mod
Contributing
Contributions are welcome. For larger changes, open an issue first.
- Fork and create a feature branch
- Add tests where practical
- Run
go test -race ./... - Open a pull request
License
Apache 2.0 — see LICENSE.
Acknowledgments
Inspired by Moonbase59/autocue (Python). That project is no longer maintained and drifted from current Liquidsoap; gocue is a Go port with a focus on performance and a cleaner library/CLI split.
- FFmpeg — analysis pipeline
- Liquidsoap — autocue protocol
- EBU — R128 loudness standard
Support
- Open a GitHub issue
./gocue --help
Documentation
¶
There is no documentation for this package.