Move python_apps/airtime_analyzer/ to analyzer/
This commit is contained in:
parent
a7d06ad076
commit
2ef63e8c4e
39 changed files with 0 additions and 0 deletions
0
analyzer/tests/__init__.py
Normal file
0
analyzer/tests/__init__.py
Normal file
48
analyzer/tests/analyzer_pipeline_test.py
Normal file
48
analyzer/tests/analyzer_pipeline_test.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
from queue import Queue
|
||||
|
||||
import pytest
|
||||
from airtime_analyzer.analyzer_pipeline import AnalyzerPipeline
|
||||
|
||||
from .conftest import AUDIO_FILENAME, AUDIO_IMPORT_DEST
|
||||
|
||||
|
||||
def test_run_analysis(src_dir, dest_dir):
|
||||
queue = Queue()
|
||||
AnalyzerPipeline.run_analysis(
|
||||
queue,
|
||||
os.path.join(src_dir, AUDIO_FILENAME),
|
||||
dest_dir,
|
||||
AUDIO_FILENAME,
|
||||
"file",
|
||||
"",
|
||||
)
|
||||
metadata = queue.get()
|
||||
|
||||
assert metadata["track_title"] == "Test Title"
|
||||
assert metadata["artist_name"] == "Test Artist"
|
||||
assert metadata["album_title"] == "Test Album"
|
||||
assert metadata["year"] == "1999"
|
||||
assert metadata["genre"] == "Test Genre"
|
||||
assert metadata["mime"] == "audio/mp3"
|
||||
assert metadata["length_seconds"] == pytest.approx(10.0, abs=0.1)
|
||||
assert metadata["length"] == str(
|
||||
datetime.timedelta(seconds=metadata["length_seconds"])
|
||||
)
|
||||
assert os.path.exists(os.path.join(dest_dir, AUDIO_IMPORT_DEST))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"params,exception",
|
||||
[
|
||||
((Queue(), "", "", ""), TypeError),
|
||||
((Queue(), "", "", ""), TypeError),
|
||||
((Queue(), "", "", ""), TypeError),
|
||||
((Queue(), "", "", ""), TypeError),
|
||||
],
|
||||
)
|
||||
def test_run_analysis_wrong_params(params, exception):
|
||||
with pytest.raises(exception):
|
||||
AnalyzerPipeline.run_analysis(*params)
|
8
analyzer/tests/analyzer_test.py
Normal file
8
analyzer/tests/analyzer_test.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import pytest
|
||||
from airtime_analyzer.analyzer import Analyzer
|
||||
|
||||
|
||||
def test_analyze():
|
||||
with pytest.raises(NotImplementedError):
|
||||
abstract_analyzer = Analyzer()
|
||||
abstract_analyzer.analyze(u"foo", dict())
|
26
analyzer/tests/conftest.py
Normal file
26
analyzer/tests/conftest.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import os
|
||||
import shutil
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
from .fixtures import fixtures_path
|
||||
|
||||
AUDIO_FILENAME = "s1-stereo-tagged.mp3"
|
||||
AUDIO_FILE = fixtures_path / AUDIO_FILENAME
|
||||
AUDIO_IMPORT_DEST = f"Test Artist/Test Album/{AUDIO_FILENAME}"
|
||||
|
||||
# TODO: Use pathlib for file manipulation
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def dest_dir():
|
||||
with TemporaryDirectory(prefix="dest") as tmpdir:
|
||||
yield tmpdir
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def src_dir():
|
||||
with TemporaryDirectory(prefix="src") as tmpdir:
|
||||
shutil.copy(AUDIO_FILE, tmpdir)
|
||||
yield tmpdir
|
43
analyzer/tests/cuepoint_analyzer_test.py
Normal file
43
analyzer/tests/cuepoint_analyzer_test.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import distro
|
||||
import pytest
|
||||
from airtime_analyzer.cuepoint_analyzer import CuePointAnalyzer
|
||||
|
||||
from .fixtures import FILE_INVALID_DRM, FILES, Fixture
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filepath,length,cuein,cueout",
|
||||
map(lambda i: (str(i.path), i.length, i.cuein, i.cueout), FILES),
|
||||
)
|
||||
def test_analyze(filepath, length, cuein, cueout):
|
||||
metadata = CuePointAnalyzer.analyze(filepath, dict())
|
||||
|
||||
assert metadata["length_seconds"] == pytest.approx(length, abs=0.1)
|
||||
|
||||
# Silan does not work with m4a files yet
|
||||
if filepath.endswith("m4a"):
|
||||
return
|
||||
|
||||
# Silan does not work with mp3 on debian buster
|
||||
if filepath.endswith("mp3") and "buster" == distro.codename():
|
||||
return
|
||||
|
||||
assert float(metadata["cuein"]) == pytest.approx(cuein, abs=0.5)
|
||||
assert float(metadata["cueout"]) == pytest.approx(cueout, abs=0.5)
|
||||
|
||||
|
||||
def test_analyze_missing_silan():
|
||||
old = CuePointAnalyzer.SILAN_EXECUTABLE
|
||||
CuePointAnalyzer.SILAN_EXECUTABLE = "foobar"
|
||||
CuePointAnalyzer.analyze(str(FILES[0].path), dict())
|
||||
CuePointAnalyzer.SILAN_EXECUTABLE = old
|
||||
|
||||
|
||||
def test_analyze_invalid_filepath():
|
||||
with pytest.raises(KeyError):
|
||||
test_analyze("non-existent-file", None, None, None)
|
||||
|
||||
|
||||
def test_analyze_invalid_wma():
|
||||
with pytest.raises(KeyError):
|
||||
test_analyze(FILE_INVALID_DRM, None, None, None)
|
120
analyzer/tests/filemover_analyzer_test.py
Normal file
120
analyzer/tests/filemover_analyzer_test.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from airtime_analyzer.filemover_analyzer import FileMoverAnalyzer
|
||||
|
||||
from .conftest import AUDIO_FILENAME
|
||||
|
||||
|
||||
def test_analyze():
|
||||
with pytest.raises(Exception):
|
||||
FileMoverAnalyzer.analyze("foo", dict())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"params,exception",
|
||||
[
|
||||
((42, "", "", dict()), TypeError),
|
||||
(("", 23, "", dict()), TypeError),
|
||||
(("", "", 5, dict()), TypeError),
|
||||
(("", "", "", 12345), TypeError),
|
||||
],
|
||||
)
|
||||
def test_move_wrong_params(params, exception):
|
||||
with pytest.raises(exception):
|
||||
FileMoverAnalyzer.move(*params)
|
||||
|
||||
|
||||
def test_move(src_dir, dest_dir):
|
||||
FileMoverAnalyzer.move(
|
||||
os.path.join(src_dir, AUDIO_FILENAME),
|
||||
dest_dir,
|
||||
AUDIO_FILENAME,
|
||||
dict(),
|
||||
)
|
||||
assert os.path.exists(os.path.join(dest_dir, AUDIO_FILENAME))
|
||||
|
||||
|
||||
def test_move_samefile(src_dir):
|
||||
FileMoverAnalyzer.move(
|
||||
os.path.join(src_dir, AUDIO_FILENAME),
|
||||
src_dir,
|
||||
AUDIO_FILENAME,
|
||||
dict(),
|
||||
)
|
||||
assert os.path.exists(os.path.join(src_dir, AUDIO_FILENAME))
|
||||
|
||||
|
||||
def import_and_restore(src_dir, dest_dir) -> dict:
|
||||
"""
|
||||
Small helper to test the FileMoverAnalyzer.move function.
|
||||
Move the file and restore it back to it's origine.
|
||||
"""
|
||||
# Import the file
|
||||
metadata = FileMoverAnalyzer.move(
|
||||
os.path.join(src_dir, AUDIO_FILENAME),
|
||||
dest_dir,
|
||||
AUDIO_FILENAME,
|
||||
dict(),
|
||||
)
|
||||
|
||||
# Copy it back to the original location
|
||||
shutil.copy(
|
||||
os.path.join(dest_dir, AUDIO_FILENAME),
|
||||
os.path.join(src_dir, AUDIO_FILENAME),
|
||||
)
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
def test_move_duplicate_file(src_dir, dest_dir):
|
||||
# Import the file once
|
||||
import_and_restore(src_dir, dest_dir)
|
||||
|
||||
# Import it again. It shouldn't overwrite the old file and instead create a new
|
||||
metadata = import_and_restore(src_dir, dest_dir)
|
||||
|
||||
assert metadata["full_path"] != os.path.join(dest_dir, AUDIO_FILENAME)
|
||||
assert os.path.exists(metadata["full_path"])
|
||||
assert os.path.exists(os.path.join(dest_dir, AUDIO_FILENAME))
|
||||
|
||||
|
||||
def test_move_triplicate_file(src_dir, dest_dir):
|
||||
# Here we use mock to patch out the time.localtime() function so that it
|
||||
# always returns the same value. This allows us to consistently simulate this test cases
|
||||
# where the last two of the three files are imported at the same time as the timestamp.
|
||||
with mock.patch("airtime_analyzer.filemover_analyzer.time") as mock_time:
|
||||
mock_time.localtime.return_value = time.localtime() # date(2010, 10, 8)
|
||||
mock_time.side_effect = time.time
|
||||
|
||||
# Import the file once
|
||||
import_and_restore(src_dir, dest_dir)
|
||||
# Import it again. It shouldn't overwrite the old file and instead create a new
|
||||
metadata1 = import_and_restore(src_dir, dest_dir)
|
||||
|
||||
# Reimport for the third time, which should have the same timestamp as the second one
|
||||
# thanks to us mocking out time.localtime()
|
||||
metadata2 = import_and_restore(src_dir, dest_dir)
|
||||
|
||||
# Check if file exists and if filename is <original>_<date>.<ext>
|
||||
assert os.path.exists(metadata1["full_path"])
|
||||
assert len(os.path.basename(metadata1["full_path"]).split("_")) == 2
|
||||
|
||||
# Check if file exists and if filename is <original>_<date>_<uuid>.<ext>
|
||||
assert os.path.exists(metadata2["full_path"])
|
||||
assert len(os.path.basename(metadata2["full_path"]).split("_")) == 3
|
||||
|
||||
|
||||
def test_move_bad_permissions_dest_dir(src_dir):
|
||||
with pytest.raises(OSError):
|
||||
# /sys is using sysfs on Linux, which is unwritable
|
||||
FileMoverAnalyzer.move(
|
||||
os.path.join(src_dir, AUDIO_FILENAME),
|
||||
"/sys/foobar",
|
||||
AUDIO_FILENAME,
|
||||
dict(),
|
||||
)
|
1
analyzer/tests/fixtures/.gitignore
vendored
Normal file
1
analyzer/tests/fixtures/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
s*-*
|
282
analyzer/tests/fixtures/__init__.py
vendored
Normal file
282
analyzer/tests/fixtures/__init__.py
vendored
Normal file
|
@ -0,0 +1,282 @@
|
|||
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 MP3 -12dB
|
||||
Fixture(here / "s1-mono-12.mp3", *s1, +8.3),
|
||||
Fixture(here / "s1-stereo-12.mp3", *s1, +10.0),
|
||||
# Sample 1 MP3 +12dB
|
||||
Fixture(here / "s1-mono+12.mp3", *s1, -13.6),
|
||||
Fixture(here / "s1-stereo+12.mp3", *s1, -12.0),
|
||||
# Sample 1 FLAC
|
||||
Fixture(here / "s1-mono.flac", *s1, -1.6),
|
||||
Fixture(here / "s1-stereo.flac", *s1, -2.3),
|
||||
# Sample 1 FLAC -12dB
|
||||
Fixture(here / "s1-mono-12.flac", *s1, +10.0),
|
||||
Fixture(here / "s1-stereo-12.flac", *s1, +9.3),
|
||||
# Sample 1 FLAC +12dB
|
||||
Fixture(here / "s1-mono+12.flac", *s1, -12.0),
|
||||
Fixture(here / "s1-stereo+12.flac", *s1, -12.0),
|
||||
# Sample 1 AAC
|
||||
Fixture(here / "s1-mono.m4a", *s1, -4.5),
|
||||
Fixture(here / "s1-stereo.m4a", *s1, -2.9),
|
||||
# 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",
|
||||
},
|
||||
),
|
||||
]
|
92
analyzer/tests/fixtures/generate.sh
vendored
Executable file
92
analyzer/tests/fixtures/generate.sh
vendored
Executable file
|
@ -0,0 +1,92 @@
|
|||
#!/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!"
|
||||
|
||||
# <metadata> <input> <output>
|
||||
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
|
||||
}
|
||||
|
||||
# <input> <output> <flags...>
|
||||
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
|
||||
}
|
||||
|
||||
# Generate 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
|
||||
|
||||
# Generate sample 1 +/-12dB
|
||||
generate s1.flac s1-mono-12.flac -ac 1 -acodec flac -af volume=-12dB
|
||||
generate s1.flac s1-stereo-12.flac -ac 2 -acodec flac -af volume=-12dB
|
||||
generate s1.flac s1-mono-12.mp3 -ac 1 -acodec libmp3lame -af volume=-12dB
|
||||
generate s1.flac s1-stereo-12.mp3 -ac 2 -acodec libmp3lame -af volume=-12dB
|
||||
|
||||
generate s1.flac s1-mono+12.flac -ac 1 -acodec flac -af volume=+12dB
|
||||
generate s1.flac s1-stereo+12.flac -ac 2 -acodec flac -af volume=+12dB
|
||||
generate s1.flac s1-mono+12.mp3 -ac 1 -acodec libmp3lame -af volume=+12dB
|
||||
generate s1.flac s1-stereo+12.mp3 -ac 2 -acodec libmp3lame -af volume=+12dB
|
||||
|
||||
# Generate 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
|
1
analyzer/tests/fixtures/invalid.txt
vendored
Normal file
1
analyzer/tests/fixtures/invalid.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
test-file
|
BIN
analyzer/tests/fixtures/invalid.wma
vendored
Normal file
BIN
analyzer/tests/fixtures/invalid.wma
vendored
Normal file
Binary file not shown.
8
analyzer/tests/fixtures/metadata-utf8.txt
vendored
Normal file
8
analyzer/tests/fixtures/metadata-utf8.txt
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
;FFMETADATA1
|
||||
album=Ä ä Ü ü ß
|
||||
artist=てすと
|
||||
title=アイウエオカキクケコサシスセソタチツテ
|
||||
track=1/10
|
||||
date=1999
|
||||
genre=Я Б Г Д Ж Й
|
||||
comment=Ł Ą Ż Ę Ć Ń Ś Ź
|
8
analyzer/tests/fixtures/metadata.txt
vendored
Normal file
8
analyzer/tests/fixtures/metadata.txt
vendored
Normal file
|
@ -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
|
BIN
analyzer/tests/fixtures/s1.flac
vendored
Normal file
BIN
analyzer/tests/fixtures/s1.flac
vendored
Normal file
Binary file not shown.
BIN
analyzer/tests/fixtures/s2.flac
vendored
Normal file
BIN
analyzer/tests/fixtures/s2.flac
vendored
Normal file
Binary file not shown.
64
analyzer/tests/metadata_analyzer_test.py
Normal file
64
analyzer/tests/metadata_analyzer_test.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
from datetime import timedelta
|
||||
from unittest import mock
|
||||
|
||||
import mutagen
|
||||
import pytest
|
||||
from airtime_analyzer.metadata_analyzer import MetadataAnalyzer
|
||||
|
||||
from .fixtures import FILE_INVALID_DRM, FILE_INVALID_TXT, FILES_TAGGED, FixtureMeta
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"params,exception",
|
||||
[
|
||||
((42, dict()), TypeError),
|
||||
(("foo", 3), TypeError),
|
||||
],
|
||||
)
|
||||
def test_analyze_wrong_params(params, exception):
|
||||
with pytest.raises(exception):
|
||||
MetadataAnalyzer.analyze(*params)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filepath,metadata",
|
||||
map(lambda i: (str(i.path), i.metadata), FILES_TAGGED),
|
||||
)
|
||||
def test_analyze(filepath: str, metadata: dict):
|
||||
found = MetadataAnalyzer.analyze(filepath, dict())
|
||||
|
||||
# Mutagen does not support wav files yet
|
||||
if filepath.endswith("wav"):
|
||||
return
|
||||
|
||||
assert len(found["md5"]) == 32
|
||||
del found["md5"]
|
||||
|
||||
# Handle track formatted length/cueout
|
||||
assert metadata["length"] in found["length"]
|
||||
assert metadata["length"] in found["cueout"]
|
||||
del metadata["length"]
|
||||
del found["length"]
|
||||
del found["cueout"]
|
||||
|
||||
# mp3,ogg,flac files does not support comments yet
|
||||
if not filepath.endswith("m4a"):
|
||||
del metadata["comment"]
|
||||
|
||||
assert found == metadata
|
||||
|
||||
|
||||
def test_invalid_wma():
|
||||
metadata = MetadataAnalyzer.analyze(str(FILE_INVALID_DRM), dict())
|
||||
assert metadata["mime"] == "audio/x-ms-wma"
|
||||
|
||||
|
||||
def test_unparsable_file():
|
||||
metadata = MetadataAnalyzer.analyze(str(FILE_INVALID_TXT), dict())
|
||||
assert metadata == {
|
||||
"filesize": 10,
|
||||
"ftype": "audioclip",
|
||||
"hidden": False,
|
||||
"md5": "4d5e4b1c8e8febbd31fa9ce7f088beae",
|
||||
"mime": "text/plain",
|
||||
}
|
42
analyzer/tests/playability_analyzer_test.py
Normal file
42
analyzer/tests/playability_analyzer_test.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
import distro
|
||||
import pytest
|
||||
from airtime_analyzer.playability_analyzer import (
|
||||
PlayabilityAnalyzer,
|
||||
UnplayableFileError,
|
||||
)
|
||||
|
||||
from .fixtures import FILE_INVALID_DRM, FILES, Fixture
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filepath",
|
||||
map(lambda i: str(i.path), FILES),
|
||||
)
|
||||
def test_analyze(filepath):
|
||||
PlayabilityAnalyzer.analyze(filepath, dict())
|
||||
|
||||
|
||||
def test_analyze_missing_liquidsoap():
|
||||
old = PlayabilityAnalyzer.LIQUIDSOAP_EXECUTABLE
|
||||
PlayabilityAnalyzer.LIQUIDSOAP_EXECUTABLE = "foobar"
|
||||
PlayabilityAnalyzer.analyze(str(FILES[0].path), dict())
|
||||
PlayabilityAnalyzer.LIQUIDSOAP_EXECUTABLE = old
|
||||
|
||||
|
||||
def test_analyze_invalid_filepath():
|
||||
with pytest.raises(UnplayableFileError):
|
||||
test_analyze("non-existent-file")
|
||||
|
||||
|
||||
def test_analyze_invalid_wma():
|
||||
# Liquisoap does not fail with wma files on debian buster
|
||||
if "buster" == distro.codename():
|
||||
return
|
||||
|
||||
with pytest.raises(UnplayableFileError):
|
||||
test_analyze(FILE_INVALID_DRM)
|
||||
|
||||
|
||||
def test_analyze_unknown():
|
||||
with pytest.raises(UnplayableFileError):
|
||||
test_analyze("https://www.google.com")
|
30
analyzer/tests/replaygain_analyzer_test.py
Normal file
30
analyzer/tests/replaygain_analyzer_test.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import pytest
|
||||
from airtime_analyzer.replaygain_analyzer import ReplayGainAnalyzer
|
||||
|
||||
from .fixtures import FILE_INVALID_DRM, FILES, Fixture
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filepath,replaygain",
|
||||
map(lambda i: (str(i.path), i.replaygain), FILES),
|
||||
)
|
||||
def test_analyze(filepath, replaygain):
|
||||
metadata = ReplayGainAnalyzer.analyze(filepath, dict())
|
||||
assert metadata["replay_gain"] == pytest.approx(replaygain, abs=0.6)
|
||||
|
||||
|
||||
def test_analyze_missing_replaygain():
|
||||
old = ReplayGainAnalyzer.REPLAYGAIN_EXECUTABLE
|
||||
ReplayGainAnalyzer.REPLAYGAIN_EXECUTABLE = "foobar"
|
||||
ReplayGainAnalyzer.analyze(str(FILES[0].path), dict())
|
||||
ReplayGainAnalyzer.REPLAYGAIN_EXECUTABLE = old
|
||||
|
||||
|
||||
def test_analyze_invalid_filepath():
|
||||
with pytest.raises(KeyError):
|
||||
test_analyze("non-existent-file", None)
|
||||
|
||||
|
||||
def test_analyze_invalid_wma():
|
||||
with pytest.raises(KeyError):
|
||||
test_analyze(FILE_INVALID_DRM, None)
|
Loading…
Add table
Add a link
Reference in a new issue