diff --git a/.github/scripts/install-bionic.sh b/.github/scripts/install-bionic.sh index 0078e9769..a39de7135 100755 --- a/.github/scripts/install-bionic.sh +++ b/.github/scripts/install-bionic.sh @@ -15,6 +15,7 @@ apt-get install -y gstreamer1.0-plugins-base \ liquidsoap-plugin-vorbis \ python3-gst-1.0 \ silan \ + ffmpeg \ gcc \ gir1.2-gtk-3.0 \ python3-setuptools \ diff --git a/python_apps/airtime_analyzer/Makefile b/python_apps/airtime_analyzer/Makefile index 3e7580b48..9d4260e07 100644 --- a/python_apps/airtime_analyzer/Makefile +++ b/python_apps/airtime_analyzer/Makefile @@ -10,7 +10,10 @@ lint: pylint ${MODULE_APP} pylint ${MODULE_TESTS} -test: +fixtures: + bash tests/fixtures/generate.sh + +test: fixtures pytest -n ${CPU_CORES} --color=yes -v --cov=${MODULE_APP} ${MODULE_TESTS} all: lint test diff --git a/python_apps/airtime_analyzer/tests/fixtures/.gitignore b/python_apps/airtime_analyzer/tests/fixtures/.gitignore new file mode 100644 index 000000000..7d5670696 --- /dev/null +++ b/python_apps/airtime_analyzer/tests/fixtures/.gitignore @@ -0,0 +1 @@ +s*-* diff --git a/python_apps/airtime_analyzer/tests/fixtures/__init__.py b/python_apps/airtime_analyzer/tests/fixtures/__init__.py new file mode 100644 index 000000000..d6a02ee35 --- /dev/null +++ b/python_apps/airtime_analyzer/tests/fixtures/__init__.py @@ -0,0 +1,270 @@ +from collections import namedtuple +from datetime import timedelta +from pathlib import Path + +from pytest import approx + +here = Path(__file__).parent +fixtures_path = here + +FILE_INVALID_DRM = here / "invalid.wma" +FILE_INVALID_TXT = here / "invalid.txt" + +Fixture = namedtuple( + "Fixture", + ["path", "length", "cuein", "cueout", "replaygain"], +) + +# length,cuein,cueout +s1 = [10.0, 2.3, 10.0] +s2 = [3.9, 0.0, 3.9] + +FILES = [ + # Sample 1 MP3 + Fixture(here / "s1-jointstereo.mp3", *s1, -1.6), + Fixture(here / "s1-mono.mp3", *s1, -0.7), + Fixture(here / "s1-stereo.mp3", *s1, -1.6), + # Sample 1 FLAC + Fixture(here / "s1-mono.flac", *s1, -1.6), + Fixture(here / "s1-stereo.flac", *s1, -2.3), + # Sample 1 AAC + Fixture(here / "s1-mono.m4a", *s1, -4.5), + Fixture(here / "s1-stereo.m4a", *s1, -2.3), + # Sample 1 Vorbis + Fixture(here / "s1-mono.ogg", *s1, -4.3), + Fixture(here / "s1-stereo.ogg", *s1, -2.3), + # Sample 2 MP3 + Fixture(here / "s2-jointstereo.mp3", *s2, 6.1), + Fixture(here / "s2-mono.mp3", *s2, 6.1), + Fixture(here / "s2-stereo.mp3", *s2, 6.1), + # Sample 2 FLAC + Fixture(here / "s2-mono.flac", *s2, 5.2), + Fixture(here / "s2-stereo.flac", *s2, 5.2), + # Sample 2 AAC + Fixture(here / "s2-mono.m4a", *s2, 2.6), + Fixture(here / "s2-stereo.m4a", *s2, 6.1), + # Sample 2 Vorbis + Fixture(here / "s2-mono.ogg", *s2, 2.3), + Fixture(here / "s2-stereo.ogg", *s2, 5.2), +] + +FixtureMeta = namedtuple( + "FixtureMeta", + ["path", "metadata"], +) + +meta = { + "cuein": 0.0, + "sample_rate": 48000, + "length": str(timedelta(seconds=10)), + "length_seconds": approx(10.0, abs=0.1), + "ftype": "audioclip", + "hidden": False, + # Tags + "album_title": "Test Album", + "artist_name": "Test Artist", + "track_title": "Test Title", + "track_number": "1", + "track_total": "10", + "year": "1999", + "genre": "Test Genre", + "comment": "Test Comment", +} + +FILES_TAGGED = [ + FixtureMeta( + here / "s1-jointstereo-tagged.mp3", + { + **meta, + "bit_rate": approx(128000, abs=1e2), + "channels": 2, + "filesize": approx(161094, abs=1e2), + "mime": "audio/mp3", + }, + ), + FixtureMeta( + here / "s1-mono-tagged.mp3", + { + **meta, + "bit_rate": approx(64000, abs=1e2), + "channels": 1, + "filesize": approx(80646, abs=1e2), + "mime": "audio/mp3", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged.mp3", + { + **meta, + "bit_rate": approx(128000, abs=1e2), + "channels": 2, + "filesize": approx(161094, abs=1e2), + "mime": "audio/mp3", + }, + ), + FixtureMeta( + here / "s1-mono-tagged.flac", + { + **meta, + "bit_rate": approx(454468, abs=1e2), + "channels": 1, + "filesize": approx(576516, abs=1e2), + "mime": "audio/flac", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged.flac", + { + **meta, + "bit_rate": approx(687113, abs=1e2), + "channels": 2, + "filesize": approx(867323, abs=1e2), + "mime": "audio/flac", + }, + ), + FixtureMeta( + here / "s1-mono-tagged.m4a", + { + **meta, + "bit_rate": approx(65000, abs=5e4), + "channels": 2, # Weird + "filesize": approx(80000, abs=1e5), + "mime": "audio/mp4", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged.m4a", + { + **meta, + "bit_rate": approx(128000, abs=1e5), + "channels": 2, + "filesize": approx(150000, abs=1e5), + "mime": "audio/mp4", + }, + ), + FixtureMeta( + here / "s1-mono-tagged.ogg", + { + **meta, + "bit_rate": approx(80000, abs=1e2), + "channels": 1, + "filesize": approx(81340, abs=1e2), + "mime": "audio/vorbis", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged.ogg", + { + **meta, + "bit_rate": approx(112000, abs=1e2), + "channels": 2, + "filesize": approx(104036, abs=1e2), + "mime": "audio/vorbis", + }, + ), +] + +meta = { + **meta, + "album_title": "Ä ä Ü ü ß", + "artist_name": "てすと", + "track_title": "アイウエオカキクケコサシスセソタチツテ", + "track_number": "1", + "track_total": "10", + "year": "1999", + "genre": "Я Б Г Д Ж Й", + "comment": "Ł Ą Ż Ę Ć Ń Ś Ź", +} + +FILES_TAGGED += [ + FixtureMeta( + here / "s1-jointstereo-tagged-utf8.mp3", + { + **meta, + "bit_rate": approx(128000, abs=1e2), + "channels": 2, + "filesize": approx(161161, abs=1e2), + "mime": "audio/mp3", + }, + ), + FixtureMeta( + here / "s1-mono-tagged-utf8.mp3", + { + **meta, + "bit_rate": approx(64000, abs=1e2), + "channels": 1, + "filesize": approx(80713, abs=1e2), + "mime": "audio/mp3", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged-utf8.mp3", + { + **meta, + "bit_rate": approx(128000, abs=1e2), + "channels": 2, + "filesize": approx(161161, abs=1e2), + "mime": "audio/mp3", + }, + ), + FixtureMeta( + here / "s1-mono-tagged-utf8.flac", + { + **meta, + "bit_rate": approx(454468, abs=1e2), + "channels": 1, + "filesize": approx(576583, abs=1e2), + "mime": "audio/flac", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged-utf8.flac", + { + **meta, + "bit_rate": approx(687113, abs=1e2), + "channels": 2, + "filesize": approx(867390, abs=1e2), + "mime": "audio/flac", + }, + ), + FixtureMeta( + here / "s1-mono-tagged-utf8.m4a", + { + **meta, + "bit_rate": approx(65000, abs=5e4), + "channels": 2, # Weird + "filesize": approx(80000, abs=1e5), + "mime": "audio/mp4", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged-utf8.m4a", + { + **meta, + "bit_rate": approx(128000, abs=1e5), + "channels": 2, + "filesize": approx(150000, abs=1e5), + "mime": "audio/mp4", + }, + ), + FixtureMeta( + here / "s1-mono-tagged-utf8.ogg", + { + **meta, + "bit_rate": approx(80000, abs=1e2), + "channels": 1, + "filesize": approx(81408, abs=1e2), + "mime": "audio/vorbis", + }, + ), + FixtureMeta( + here / "s1-stereo-tagged-utf8.ogg", + { + **meta, + "bit_rate": approx(112000, abs=1e2), + "channels": 2, + "filesize": approx(104104, abs=1e2), + "mime": "audio/vorbis", + }, + ), +] diff --git a/python_apps/airtime_analyzer/tests/fixtures/generate.sh b/python_apps/airtime_analyzer/tests/fixtures/generate.sh new file mode 100755 index 000000000..fdc283a4f --- /dev/null +++ b/python_apps/airtime_analyzer/tests/fixtures/generate.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +set -u + +error() { + echo >&2 "error: $*" + exit 1 +} + +command -v ffmpeg > /dev/null || error "ffmpeg command not found!" + +cd "$(dirname "${BASH_SOURCE[0]}")" || error "could not change directory!" + +# +tag() { + metadata="$1" && shift + input="$1" && shift + output="$1" && shift + if [[ ! -f "$output" ]]; then + echo "tagging $output from $input with $metadata" + ffmpeg -y -i "$input" -f ffmetadata -i "$metadata" -c copy -map_metadata 1 "$output" \ + 2> /dev/null || + error "could not tag $output" + fi +} + +# +generate() { + input="$1" && shift + output="$1" && shift + if [[ ! -f "$output" ]]; then + echo "generating $output from $input" + ffmpeg -y -i "$input" -vn "$@" "$output" \ + 2> /dev/null || + error "could not generate $output" + fi +} + +# Generated sample 1 +generate s1.flac s1-mono.flac -ac 1 -acodec flac +generate s1.flac s1-mono.m4a -ac 1 -acodec aac +generate s1.flac s1-mono.mp3 -ac 1 -acodec libmp3lame +generate s1.flac s1-mono.ogg -ac 1 -acodec libvorbis +generate s1.flac s1-stereo.flac -ac 2 -acodec flac +generate s1.flac s1-stereo.m4a -ac 2 -acodec aac +generate s1.flac s1-stereo.mp3 -ac 2 -acodec libmp3lame +generate s1.flac s1-stereo.ogg -ac 2 -acodec libvorbis +generate s1.flac s1-jointstereo.mp3 -ac 2 -acodec libmp3lame -joint_stereo 1 + +# Generated sample 2 +generate s2.flac s2-mono.flac -ac 1 -acodec flac +generate s2.flac s2-mono.m4a -ac 1 -acodec aac +generate s2.flac s2-mono.mp3 -ac 1 -acodec libmp3lame +generate s2.flac s2-mono.ogg -ac 1 -acodec libvorbis +generate s2.flac s2-stereo.flac -ac 2 -acodec flac +generate s2.flac s2-stereo.m4a -ac 2 -acodec aac +generate s2.flac s2-stereo.mp3 -ac 2 -acodec libmp3lame +generate s2.flac s2-stereo.ogg -ac 2 -acodec libvorbis +generate s2.flac s2-jointstereo.mp3 -ac 2 -acodec libmp3lame -joint_stereo 1 + +# Tag sample 1 +tag metadata.txt s1-mono.flac s1-mono-tagged.flac +tag metadata.txt s1-mono.m4a s1-mono-tagged.m4a +tag metadata.txt s1-mono.mp3 s1-mono-tagged.mp3 +tag metadata.txt s1-mono.ogg s1-mono-tagged.ogg +tag metadata.txt s1-stereo.flac s1-stereo-tagged.flac +tag metadata.txt s1-stereo.m4a s1-stereo-tagged.m4a +tag metadata.txt s1-stereo.mp3 s1-stereo-tagged.mp3 +tag metadata.txt s1-stereo.ogg s1-stereo-tagged.ogg +tag metadata.txt s1-jointstereo.mp3 s1-jointstereo-tagged.mp3 + +# Tag utf8 sample 1 +tag metadata-utf8.txt s1-mono.flac s1-mono-tagged-utf8.flac +tag metadata-utf8.txt s1-mono.m4a s1-mono-tagged-utf8.m4a +tag metadata-utf8.txt s1-mono.mp3 s1-mono-tagged-utf8.mp3 +tag metadata-utf8.txt s1-mono.ogg s1-mono-tagged-utf8.ogg +tag metadata-utf8.txt s1-stereo.flac s1-stereo-tagged-utf8.flac +tag metadata-utf8.txt s1-stereo.m4a s1-stereo-tagged-utf8.m4a +tag metadata-utf8.txt s1-stereo.mp3 s1-stereo-tagged-utf8.mp3 +tag metadata-utf8.txt s1-stereo.ogg s1-stereo-tagged-utf8.ogg +tag metadata-utf8.txt s1-jointstereo.mp3 s1-jointstereo-tagged-utf8.mp3 diff --git a/python_apps/airtime_analyzer/tests/test_data/unparsable.txt b/python_apps/airtime_analyzer/tests/fixtures/invalid.txt similarity index 100% rename from python_apps/airtime_analyzer/tests/test_data/unparsable.txt rename to python_apps/airtime_analyzer/tests/fixtures/invalid.txt diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo-invalid.wma b/python_apps/airtime_analyzer/tests/fixtures/invalid.wma similarity index 100% rename from python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo-invalid.wma rename to python_apps/airtime_analyzer/tests/fixtures/invalid.wma diff --git a/python_apps/airtime_analyzer/tests/fixtures/metadata-utf8.txt b/python_apps/airtime_analyzer/tests/fixtures/metadata-utf8.txt new file mode 100644 index 000000000..b950d9662 --- /dev/null +++ b/python_apps/airtime_analyzer/tests/fixtures/metadata-utf8.txt @@ -0,0 +1,8 @@ +;FFMETADATA1 +album=Ä ä Ü ü ß +artist=てすと +title=アイウエオカキクケコサシスセソタチツテ +track=1/10 +date=1999 +genre=Я Б Г Д Ж Й +comment=Ł Ą Ż Ę Ć Ń Ś Ź diff --git a/python_apps/airtime_analyzer/tests/fixtures/metadata.txt b/python_apps/airtime_analyzer/tests/fixtures/metadata.txt new file mode 100644 index 000000000..5173fa5f0 --- /dev/null +++ b/python_apps/airtime_analyzer/tests/fixtures/metadata.txt @@ -0,0 +1,8 @@ +;FFMETADATA1 +album=Test Album +artist=Test Artist +title=Test Title +track=1/10 +date=1999 +genre=Test Genre +comment=Test Comment diff --git a/python_apps/airtime_analyzer/tests/fixtures/s1.flac b/python_apps/airtime_analyzer/tests/fixtures/s1.flac new file mode 100644 index 000000000..b347c5411 Binary files /dev/null and b/python_apps/airtime_analyzer/tests/fixtures/s1.flac differ diff --git a/python_apps/airtime_analyzer/tests/fixtures/s2.flac b/python_apps/airtime_analyzer/tests/fixtures/s2.flac new file mode 100644 index 000000000..7ed131704 Binary files /dev/null and b/python_apps/airtime_analyzer/tests/fixtures/s2.flac differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-dualmono.mp3 b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-dualmono.mp3 deleted file mode 100644 index 1e98b616e..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-dualmono.mp3 and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-jointstereo.mp3 b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-jointstereo.mp3 deleted file mode 100644 index 649dc371d..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-jointstereo.mp3 and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mono.mp3 b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mono.mp3 deleted file mode 100644 index 0c12b1b39..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mono.mp3 and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mono.ogg b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mono.ogg deleted file mode 100644 index 097ccf1f5..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mono.ogg and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mp3-missingid3header.mp3 b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mp3-missingid3header.mp3 deleted file mode 100644 index 0e7181f64..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-mp3-missingid3header.mp3 and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-simplestereo.mp3 b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-simplestereo.mp3 deleted file mode 100644 index 1ae7f078c..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-simplestereo.mp3 and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo-utf8.mp3 b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo-utf8.mp3 deleted file mode 100644 index 2e29e3f2a..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo-utf8.mp3 and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.m4a b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.m4a deleted file mode 100644 index ee6ef7b3d..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.m4a and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.mp3 b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.mp3 deleted file mode 100644 index 649dc371d..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.mp3 and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.ogg b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.ogg deleted file mode 100644 index 83bd04f77..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.ogg and /dev/null differ diff --git a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.wav b/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.wav deleted file mode 100644 index 88b733078..000000000 Binary files a/python_apps/airtime_analyzer/tests/test_data/44100Hz-16bit-stereo.wav and /dev/null differ