# Audio codec tester

Can you hear the difference between the original and a lossy copy of it? This
script makes a test file that finds out: it compresses your music with MP3
(LAME, constant or variable bitrate) or Opus, decompresses it again, and
splices both versions into one WAV file, switching every few seconds. Play it
anywhere that plays a WAV file, and write down when you hear the switch.

It runs from the shell and, as a CGI script, behind the
[online tester](https://www.geekabit.nl/projects/testing-audio-codecs/tester/).
Background and test results are in the
[article](https://www.geekabit.nl/projects/testing-audio-codecs/).

The script is one file, `src/codec_tester.py`, and needs only Python 3.11 or
later and FFmpeg with libmp3lame and libopus. Both `ffmpeg` and `ffprobe` must
be installed: the packages usually ship the two together.


## Use

```sh
src/codec_tester.py song.flac -c mp3-cbr -b 128 -l 10   # song-mp3-cbr-128k-10s.wav
src/codec_tester.py song.flac -c mp3-vbr -q 4
src/codec_tester.py song.flac -c opus -b 96 --force     # overwrite an existing result
```

| Option | Meaning |
|--------|---------|
| `-c`, `--codec` | `mp3-cbr` (default), `mp3-vbr` or `opus` |
| `-b`, `--bitrate` | kbps: 32 to 320 for MP3 CBR, 32 to 512 for Opus |
| `-q`, `--quality` | MP3 VBR quality, 9 (worst) to 0 (best) |
| `-l`, `--length` | seconds per block, 1 to 30 (default 10) |
| `-f`, `--force` | overwrite the output file |

The result is written to the current directory. Audio is cut off at 10
minutes, mixed down to stereo, and resampled to 48 kHz if it is higher.


## As a CGI script

Server dependencies: Python 3.11 or later (no extra modules), `ffmpeg` and
`ffprobe` with libmp3lame and libopus, and a web server with CGI.

Copy `src/codec_tester.py` to the web server's `cgi-bin/` as an executable
file; it detects CGI by `GATEWAY_INTERFACE`. It expects a POST of
`multipart/form-data` with the fields `file`, `codec`, `mp3-cbr-bitrate`,
`mp3-vbr-quality`, `opus-bitrate` and `blocklength`, and answers with the WAV
file as a download. Errors are plain text with a proper HTTP status: 400 for
wrong input, 405, 411, 413 for too large, 415 for what is no audio, 422 for
what FFmpeg cannot read, 503 when busy or too slow, and 500 for the rest.

It is meant for a public server, so it limits what a stranger can make it do;
the limits are constants at the top of the file, and the header explains the
rest. Upload size 64 MiB, 10 minutes of audio, two requests at a time, two
minutes per FFmpeg call. Set the web server's limits (request body, timeout)
at least that high. FFmpeg only gets files that ffprobe recognises as one of a
list of audio containers, so an uploaded playlist cannot make it read other
files or fetch URLs. See [DESIGN.md](DESIGN.md).


## Development

```sh
tools/setup_venv.sh    # once: the venv with the development tools
tools/check.sh         # mypy, ruff, pylint, shellcheck, spelling, tests and fuzzers
```

The tests need `ffmpeg` and `ffprobe`. `FUZZ_EXAMPLES=20000 tools/check.sh`
runs the fuzzers for much longer, as before a release.


## License

CC0 1.0: public domain. FFmpeg, LAME and libopus are not part of this
repository and have their own licences.
