restructure project for poetry-slam
This commit is contained in:
parent
c94fb127ed
commit
7ded43476e
|
@ -5,7 +5,7 @@ description = ""
|
||||||
authors = ["evilchili <evilchili@gmail.com>"]
|
authors = ["evilchili <evilchili@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
packages = [
|
packages = [
|
||||||
{ include = "croaker" }
|
{ include = "*", from = "src" }
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
|
@ -26,12 +26,16 @@ ffmpeg-python = "^0.2.0"
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
croaker = "croaker.cli:app"
|
croaker = "croaker.cli:app"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.3.0"
|
pytest = "^8.1.1"
|
||||||
isort = "^5.12.0"
|
pytest-cov = "^5.0.0"
|
||||||
pyproject-autoflake = "^1.0.2"
|
|
||||||
pytest = "^7.2.0"
|
[build-system]
|
||||||
pytest-cov = "^4.0.0"
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
||||||
|
### SLAM
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
|
@ -51,7 +55,8 @@ ignore-init-module-imports = true # exclude __init__.py when removing unused
|
||||||
remove-duplicate-keys = true # remove all duplicate keys in objects
|
remove-duplicate-keys = true # remove all duplicate keys in objects
|
||||||
remove-unused-variables = true # remove unused variables
|
remove-unused-variables = true # remove unused variables
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
log_cli_level = "DEBUG"
|
||||||
|
addopts = "--cov=src --cov-report=term-missing"
|
||||||
|
|
||||||
[build-system]
|
### ENDSLAM
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[pytest]
|
|
||||||
log_cli_level = DEBUG
|
|
||||||
addopts = --cov=croaker/ --cov-report=term-missing
|
|
|
@ -42,7 +42,7 @@ ICECAST_URL=
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
app_state = {}
|
app_state = {}
|
||||||
|
|
||||||
logger = logging.getLogger('cli')
|
logger = logging.getLogger("cli")
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||||
|
|
||||||
from daemon import pidfile as _pidfile
|
from daemon import pidfile as _pidfile
|
||||||
|
|
||||||
logger = logging.getLogger('daemon')
|
logger = logging.getLogger("daemon")
|
||||||
|
|
||||||
|
|
||||||
def pidfile(pidfile_path: Path, sig=signal.SIGQUIT, terminate_if_running: bool = True):
|
def pidfile(pidfile_path: Path, sig=signal.SIGQUIT, terminate_if_running: bool = True):
|
|
@ -9,7 +9,7 @@ from typing import List
|
||||||
|
|
||||||
import croaker.path
|
import croaker.path
|
||||||
|
|
||||||
logger = logging.getLogger('playlist')
|
logger = logging.getLogger("playlist")
|
||||||
|
|
||||||
playlists = {}
|
playlists = {}
|
||||||
|
|
|
@ -13,7 +13,7 @@ from croaker.pidfile import pidfile
|
||||||
from croaker.playlist import load_playlist
|
from croaker.playlist import load_playlist
|
||||||
from croaker.streamer import AudioStreamer
|
from croaker.streamer import AudioStreamer
|
||||||
|
|
||||||
logger = logging.getLogger('server')
|
logger = logging.getLogger("server")
|
||||||
|
|
||||||
|
|
||||||
class RequestHandler(socketserver.StreamRequestHandler):
|
class RequestHandler(socketserver.StreamRequestHandler):
|
||||||
|
@ -22,6 +22,7 @@ class RequestHandler(socketserver.StreamRequestHandler):
|
||||||
command and control protocol and sends commands to the shoutcast source
|
command and control protocol and sends commands to the shoutcast source
|
||||||
client on behalf of the user.
|
client on behalf of the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
supported_commands = {
|
supported_commands = {
|
||||||
# command # help text
|
# command # help text
|
||||||
"PLAY": "PLAYLIST - Switch to the specified playlist.",
|
"PLAY": "PLAYLIST - Switch to the specified playlist.",
|
||||||
|
@ -30,7 +31,7 @@ class RequestHandler(socketserver.StreamRequestHandler):
|
||||||
"HELP": " - Display command help.",
|
"HELP": " - Display command help.",
|
||||||
"KTHX": " - Close the current connection.",
|
"KTHX": " - Close the current connection.",
|
||||||
"STOP": " - Stop the current track and stream silence.",
|
"STOP": " - Stop the current track and stream silence.",
|
||||||
"STFU": " - Terminate the Croaker server."
|
"STFU": " - Terminate the Croaker server.",
|
||||||
}
|
}
|
||||||
|
|
||||||
should_listen = True
|
should_listen = True
|
||||||
|
@ -92,7 +93,7 @@ class RequestHandler(socketserver.StreamRequestHandler):
|
||||||
return self.send("\n".join(f"{cmd} {txt}" for cmd, txt in self.supported_commands.items()))
|
return self.send("\n".join(f"{cmd} {txt}" for cmd, txt in self.supported_commands.items()))
|
||||||
|
|
||||||
def handle_STOP(self, args):
|
def handle_STOP(self, args):
|
||||||
return(self.server.stop_event.set())
|
return self.server.stop_event.set()
|
||||||
|
|
||||||
def handle_STFU(self, args):
|
def handle_STFU(self, args):
|
||||||
self.send("Shutting down.")
|
self.send("Shutting down.")
|
||||||
|
@ -103,6 +104,7 @@ class CroakerServer(socketserver.TCPServer):
|
||||||
"""
|
"""
|
||||||
A Daemonized TCP Server that also starts a Shoutcast source client.
|
A Daemonized TCP Server that also starts a Shoutcast source client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
allow_reuse_address = True
|
allow_reuse_address = True
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -173,7 +175,7 @@ class CroakerServer(socketserver.TCPServer):
|
||||||
def list(self, playlist_name: str = None):
|
def list(self, playlist_name: str = None):
|
||||||
if playlist_name:
|
if playlist_name:
|
||||||
return str(load_playlist(playlist_name))
|
return str(load_playlist(playlist_name))
|
||||||
return '\n'.join([str(p.name) for p in path.playlist_root().iterdir()])
|
return "\n".join([str(p.name) for p in path.playlist_root().iterdir()])
|
||||||
|
|
||||||
def load(self, playlist_name: str):
|
def load(self, playlist_name: str):
|
||||||
logger.debug(f"Switching to {playlist_name = }")
|
logger.debug(f"Switching to {playlist_name = }")
|
|
@ -1,6 +1,6 @@
|
||||||
import queue
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import queue
|
||||||
import threading
|
import threading
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
@ -9,7 +9,7 @@ import shout
|
||||||
|
|
||||||
from croaker import transcoder
|
from croaker import transcoder
|
||||||
|
|
||||||
logger = logging.getLogger('streamer')
|
logger = logging.getLogger("streamer")
|
||||||
|
|
||||||
|
|
||||||
class AudioStreamer(threading.Thread):
|
class AudioStreamer(threading.Thread):
|
||||||
|
@ -17,6 +17,7 @@ class AudioStreamer(threading.Thread):
|
||||||
Receive filenames from the controller thread and stream the contents of
|
Receive filenames from the controller thread and stream the contents of
|
||||||
those files to the icecast server.
|
those files to the icecast server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, queue, skip_event, stop_event, load_event, chunk_size=4096):
|
def __init__(self, queue, skip_event, stop_event, load_event, chunk_size=4096):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
@ -27,7 +28,7 @@ class AudioStreamer(threading.Thread):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def silence(self):
|
def silence(self):
|
||||||
return transcoder.open(Path(__file__).parent / 'silence.mp3', bufsize=2*self.chunk_size)
|
return transcoder.open(Path(__file__).parent / "silence.mp3", bufsize=2 * self.chunk_size)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def _shout(self):
|
def _shout(self):
|
||||||
|
@ -51,7 +52,6 @@ class AudioStreamer(threading.Thread):
|
||||||
self._shout.close()
|
self._shout.close()
|
||||||
|
|
||||||
def do_one_loop(self):
|
def do_one_loop(self):
|
||||||
|
|
||||||
# If the user said STOP, clear the queue.
|
# If the user said STOP, clear the queue.
|
||||||
if self.stop_requested.is_set():
|
if self.stop_requested.is_set():
|
||||||
logger.debug("Stop requested; clearing queue.")
|
logger.debug("Stop requested; clearing queue.")
|
||||||
|
@ -76,7 +76,7 @@ class AudioStreamer(threading.Thread):
|
||||||
if not_playing:
|
if not_playing:
|
||||||
try:
|
try:
|
||||||
self.silence.seek(0, 0)
|
self.silence.seek(0, 0)
|
||||||
self._shout.set_metadata({"song": '[NOTHING PLAYING]'})
|
self._shout.set_metadata({"song": "[NOTHING PLAYING]"})
|
||||||
self.play_from_stream(self.silence)
|
self.play_from_stream(self.silence)
|
||||||
except Exception as exc: # pragma: no cover
|
except Exception as exc: # pragma: no cover
|
||||||
logger.error("Caught exception trying to loop silence!", exc_info=exc)
|
logger.error("Caught exception trying to loop silence!", exc_info=exc)
|
||||||
|
@ -102,7 +102,6 @@ class AudioStreamer(threading.Thread):
|
||||||
self._shout.get_connected()
|
self._shout.get_connected()
|
||||||
input_buffer = self._read_chunk(stream)
|
input_buffer = self._read_chunk(stream)
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
# To load a playlist, stop streaming the current track and clear the queue
|
# To load a playlist, stop streaming the current track and clear the queue
|
||||||
# but do not clear the event. run() will detect it and
|
# but do not clear the event. run() will detect it and
|
||||||
if self.load_requested.is_set():
|
if self.load_requested.is_set():
|
|
@ -1,10 +1,10 @@
|
||||||
from pathlib import Path
|
|
||||||
import subprocess
|
|
||||||
import logging
|
import logging
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
|
|
||||||
logger = logging.getLogger('transcoder')
|
logger = logging.getLogger("transcoder")
|
||||||
|
|
||||||
|
|
||||||
def open(infile: Path, bufsize: int = 4096):
|
def open(infile: Path, bufsize: int = 4096):
|
||||||
|
@ -16,15 +16,14 @@ def open(infile: Path, bufsize: int = 4096):
|
||||||
a pipe to ffmpeg's STDOUT.
|
a pipe to ffmpeg's STDOUT.
|
||||||
"""
|
"""
|
||||||
suffix = infile.suffix.lower()
|
suffix = infile.suffix.lower()
|
||||||
if suffix == '.mp3':
|
if suffix == ".mp3":
|
||||||
logger.debug(f"Not transcoding mp3 {infile = }")
|
logger.debug(f"Not transcoding mp3 {infile = }")
|
||||||
return infile.open('rb', buffering=bufsize)
|
return infile.open("rb", buffering=bufsize)
|
||||||
|
|
||||||
ffmpeg_args = (
|
ffmpeg_args = (
|
||||||
ffmpeg
|
ffmpeg.input(str(infile))
|
||||||
.input(str(infile))
|
.output("-", format="mp3", q=2)
|
||||||
.output('-', format='mp3', q=2)
|
.global_args("-hide_banner", "-loglevel", "quiet")
|
||||||
.global_args('-hide_banner', '-loglevel', 'quiet')
|
|
||||||
.compile()
|
.compile()
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,12 +5,12 @@ import pytest
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_env(monkeypatch):
|
def mock_env(monkeypatch):
|
||||||
fixtures = Path(__file__).parent / 'fixtures'
|
fixtures = Path(__file__).parent / "fixtures"
|
||||||
monkeypatch.setenv('CROAKER_ROOT', str(fixtures))
|
monkeypatch.setenv("CROAKER_ROOT", str(fixtures))
|
||||||
monkeypatch.setenv('MEDIA_GLOB', '*.mp3,*.foo,*.bar')
|
monkeypatch.setenv("MEDIA_GLOB", "*.mp3,*.foo,*.bar")
|
||||||
monkeypatch.setenv('ICECAST_URL', 'http://127.0.0.1')
|
monkeypatch.setenv("ICECAST_URL", "http://127.0.0.1")
|
||||||
monkeypatch.setenv('ICECAST_HOST', 'localhost')
|
monkeypatch.setenv("ICECAST_HOST", "localhost")
|
||||||
monkeypatch.setenv('ICECAST_MOUNT', 'mount')
|
monkeypatch.setenv("ICECAST_MOUNT", "mount")
|
||||||
monkeypatch.setenv('ICECAST_PORT', '6523')
|
monkeypatch.setenv("ICECAST_PORT", "6523")
|
||||||
monkeypatch.setenv('ICECAST_PASSWORD', 'password')
|
monkeypatch.setenv("ICECAST_PASSWORD", "password")
|
||||||
monkeypatch.setenv('DEBUG', '1')
|
monkeypatch.setenv("DEBUG", "1")
|
||||||
|
|
|
@ -6,19 +6,30 @@ import pytest
|
||||||
from croaker import pidfile
|
from croaker import pidfile
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('pid,terminate,kill_result,broken', [
|
@pytest.mark.parametrize(
|
||||||
('pid', False, None, False), # running proc, no terminate
|
"pid,terminate,kill_result,broken",
|
||||||
('pid', True, True, False), # running proc, terminate
|
[
|
||||||
('pid', True, ProcessLookupError, True), # stale pid
|
("pid", False, None, False), # running proc, no terminate
|
||||||
|
("pid", True, True, False), # running proc, terminate
|
||||||
|
("pid", True, ProcessLookupError, True), # stale pid
|
||||||
(None, None, None, False), # no running proc
|
(None, None, None, False), # no running proc
|
||||||
])
|
],
|
||||||
|
)
|
||||||
def test_pidfile(monkeypatch, pid, terminate, kill_result, broken):
|
def test_pidfile(monkeypatch, pid, terminate, kill_result, broken):
|
||||||
monkeypatch.setattr(pidfile._pidfile, 'TimeoutPIDLockFile', MagicMock(**{
|
monkeypatch.setattr(
|
||||||
'return_value.read_pid.return_value': pid,
|
pidfile._pidfile,
|
||||||
}))
|
"TimeoutPIDLockFile",
|
||||||
monkeypatch.setattr(pidfile.os, 'kill', MagicMock(**{
|
MagicMock(
|
||||||
'side_effect': kill_result if type(kill_result) is Exception else [kill_result]
|
**{
|
||||||
}))
|
"return_value.read_pid.return_value": pid,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
pidfile.os,
|
||||||
|
"kill",
|
||||||
|
MagicMock(**{"side_effect": kill_result if type(kill_result) is Exception else [kill_result]}),
|
||||||
|
)
|
||||||
|
|
||||||
ret = pidfile.pidfile(pidfile_path=Path('/dev/null'), terminate_if_running=terminate)
|
ret = pidfile.pidfile(pidfile_path=Path("/dev/null"), terminate_if_running=terminate)
|
||||||
assert ret.break_lock.called == broken
|
assert ret.break_lock.called == broken
|
||||||
|
|
|
@ -2,17 +2,17 @@ from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import croaker.playlist
|
|
||||||
import croaker.path
|
import croaker.path
|
||||||
|
import croaker.playlist
|
||||||
|
|
||||||
|
|
||||||
def test_playlist_loading():
|
def test_playlist_loading():
|
||||||
pl = croaker.playlist.Playlist(name='test_playlist')
|
pl = croaker.playlist.Playlist(name="test_playlist")
|
||||||
path = str(pl.path)
|
path = str(pl.path)
|
||||||
tracks = [str(t) for t in pl.tracks]
|
tracks = [str(t) for t in pl.tracks]
|
||||||
|
|
||||||
assert path == str(croaker.path.playlist_root() / pl.name)
|
assert path == str(croaker.path.playlist_root() / pl.name)
|
||||||
assert pl.name == 'test_playlist'
|
assert pl.name == "test_playlist"
|
||||||
assert tracks[0] == f"{path}/_theme.mp3"
|
assert tracks[0] == f"{path}/_theme.mp3"
|
||||||
assert f"{path}/one.mp3" in tracks
|
assert f"{path}/one.mp3" in tracks
|
||||||
assert f"{path}/two.mp3" in tracks
|
assert f"{path}/two.mp3" in tracks
|
||||||
|
@ -20,22 +20,25 @@ def test_playlist_loading():
|
||||||
assert f"{path}/one.baz" not in tracks
|
assert f"{path}/one.baz" not in tracks
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('paths, make_theme, expected_count', [
|
@pytest.mark.parametrize(
|
||||||
(['test_playlist'], True, 4),
|
"paths, make_theme, expected_count",
|
||||||
(['test_playlist'], False, 4),
|
[
|
||||||
(['test_playlist', 'sources/one.mp3'], True, 5),
|
(["test_playlist"], True, 4),
|
||||||
(['test_playlist', 'sources/one.mp3'], False, 5),
|
(["test_playlist"], False, 4),
|
||||||
])
|
(["test_playlist", "sources/one.mp3"], True, 5),
|
||||||
|
(["test_playlist", "sources/one.mp3"], False, 5),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_playlist_creation(monkeypatch, paths, make_theme, expected_count):
|
def test_playlist_creation(monkeypatch, paths, make_theme, expected_count):
|
||||||
new_symlinks = []
|
new_symlinks = []
|
||||||
|
|
||||||
def symlink(target):
|
def symlink(target):
|
||||||
new_symlinks.append(target)
|
new_symlinks.append(target)
|
||||||
|
|
||||||
pl = croaker.playlist.Playlist(name='foo')
|
pl = croaker.playlist.Playlist(name="foo")
|
||||||
monkeypatch.setattr(croaker.playlist.Path, 'unlink', MagicMock())
|
monkeypatch.setattr(croaker.playlist.Path, "unlink", MagicMock())
|
||||||
monkeypatch.setattr(croaker.playlist.Path, 'symlink_to', MagicMock(side_effect=symlink))
|
monkeypatch.setattr(croaker.playlist.Path, "symlink_to", MagicMock(side_effect=symlink))
|
||||||
monkeypatch.setattr(croaker.playlist.Path, 'mkdir', MagicMock())
|
monkeypatch.setattr(croaker.playlist.Path, "mkdir", MagicMock())
|
||||||
|
|
||||||
pl.add([croaker.path.playlist_root() / p for p in paths], make_theme)
|
pl.add([croaker.path.playlist_root() / p for p in paths], make_theme)
|
||||||
assert len(new_symlinks) == expected_count
|
assert len(new_symlinks) == expected_count
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import shout
|
import shout
|
||||||
|
|
||||||
from croaker import streamer, playlist
|
from croaker import playlist, streamer
|
||||||
|
|
||||||
|
|
||||||
def get_stream_output(stream):
|
def get_stream_output(stream):
|
||||||
|
@ -16,9 +15,9 @@ def get_stream_output(stream):
|
||||||
return stream.read()
|
return stream.read()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope="session")
|
||||||
def silence_bytes():
|
def silence_bytes():
|
||||||
return (Path(streamer.__file__).parent / 'silence.mp3').read_bytes()
|
return (Path(streamer.__file__).parent / "silence.mp3").read_bytes()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -30,10 +29,9 @@ def output_stream():
|
||||||
def mock_shout(output_stream, monkeypatch):
|
def mock_shout(output_stream, monkeypatch):
|
||||||
def handle_send(buf):
|
def handle_send(buf):
|
||||||
output_stream.write(buf)
|
output_stream.write(buf)
|
||||||
mm = MagicMock(spec=shout.Shout, **{
|
|
||||||
'return_value.send.side_effect': handle_send
|
mm = MagicMock(spec=shout.Shout, **{"return_value.send.side_effect": handle_send})
|
||||||
})
|
monkeypatch.setattr("shout.Shout", mm)
|
||||||
monkeypatch.setattr('shout.Shout', mm)
|
|
||||||
return mm
|
return mm
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +39,7 @@ def mock_shout(output_stream, monkeypatch):
|
||||||
def input_queue():
|
def input_queue():
|
||||||
return queue.Queue()
|
return queue.Queue()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def skip_event():
|
def skip_event():
|
||||||
return threading.Event()
|
return threading.Event()
|
||||||
|
@ -80,7 +79,7 @@ def test_streamer_load(audio_streamer, load_event, output_stream):
|
||||||
|
|
||||||
|
|
||||||
def test_clear_queue(audio_streamer, input_queue):
|
def test_clear_queue(audio_streamer, input_queue):
|
||||||
pl = playlist.Playlist(name='test_playlist')
|
pl = playlist.Playlist(name="test_playlist")
|
||||||
for track in pl.tracks:
|
for track in pl.tracks:
|
||||||
input_queue.put(bytes(track))
|
input_queue.put(bytes(track))
|
||||||
assert input_queue.not_empty
|
assert input_queue.not_empty
|
||||||
|
@ -90,7 +89,7 @@ def test_clear_queue(audio_streamer, input_queue):
|
||||||
|
|
||||||
def test_streamer_defaults_to_silence(audio_streamer, input_queue, output_stream, silence_bytes):
|
def test_streamer_defaults_to_silence(audio_streamer, input_queue, output_stream, silence_bytes):
|
||||||
audio_streamer.do_one_loop()
|
audio_streamer.do_one_loop()
|
||||||
track = playlist.Playlist(name='test_playlist').tracks[0]
|
track = playlist.Playlist(name="test_playlist").tracks[0]
|
||||||
input_queue.put(bytes(track))
|
input_queue.put(bytes(track))
|
||||||
audio_streamer.do_one_loop()
|
audio_streamer.do_one_loop()
|
||||||
audio_streamer.do_one_loop()
|
audio_streamer.do_one_loop()
|
||||||
|
@ -98,15 +97,16 @@ def test_streamer_defaults_to_silence(audio_streamer, input_queue, output_stream
|
||||||
|
|
||||||
|
|
||||||
def test_streamer_plays_silence_on_error(monkeypatch, audio_streamer, input_queue, output_stream, silence_bytes):
|
def test_streamer_plays_silence_on_error(monkeypatch, audio_streamer, input_queue, output_stream, silence_bytes):
|
||||||
monkeypatch.setattr(audio_streamer, 'play_file', MagicMock(side_effect=Exception))
|
monkeypatch.setattr(audio_streamer, "play_file", MagicMock(side_effect=Exception))
|
||||||
track = playlist.Playlist(name='test_playlist').tracks[0]
|
track = playlist.Playlist(name="test_playlist").tracks[0]
|
||||||
input_queue.put(bytes(track))
|
input_queue.put(bytes(track))
|
||||||
audio_streamer.do_one_loop()
|
audio_streamer.do_one_loop()
|
||||||
assert get_stream_output(output_stream) == silence_bytes
|
assert get_stream_output(output_stream) == silence_bytes
|
||||||
|
|
||||||
|
|
||||||
def test_streamer_plays_from_queue(audio_streamer, input_queue, output_stream):
|
def test_streamer_plays_from_queue(audio_streamer, input_queue, output_stream):
|
||||||
pl = playlist.Playlist(name='test_playlist')
|
pl = playlist.Playlist(name="test_playlist")
|
||||||
expected = b''
|
expected = b""
|
||||||
for track in pl.tracks:
|
for track in pl.tracks:
|
||||||
input_queue.put(bytes(track))
|
input_queue.put(bytes(track))
|
||||||
expected += track.read_bytes()
|
expected += track.read_bytes()
|
||||||
|
@ -119,14 +119,14 @@ def test_streamer_handles_stop_interrupt(audio_streamer, output_stream, stop_eve
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
audio_streamer.silence.seek(0, 0)
|
audio_streamer.silence.seek(0, 0)
|
||||||
audio_streamer.play_from_stream(audio_streamer.silence)
|
audio_streamer.play_from_stream(audio_streamer.silence)
|
||||||
assert get_stream_output(output_stream) == b''
|
assert get_stream_output(output_stream) == b""
|
||||||
|
|
||||||
|
|
||||||
def test_streamer_handles_load_interrupt(audio_streamer, input_queue, output_stream, load_event):
|
def test_streamer_handles_load_interrupt(audio_streamer, input_queue, output_stream, load_event):
|
||||||
pl = playlist.Playlist(name='test_playlist')
|
pl = playlist.Playlist(name="test_playlist")
|
||||||
input_queue.put(bytes(pl.tracks[0]))
|
input_queue.put(bytes(pl.tracks[0]))
|
||||||
load_event.set()
|
load_event.set()
|
||||||
audio_streamer.silence.seek(0, 0)
|
audio_streamer.silence.seek(0, 0)
|
||||||
audio_streamer.play_from_stream(audio_streamer.silence)
|
audio_streamer.play_from_stream(audio_streamer.silence)
|
||||||
assert get_stream_output(output_stream) == b''
|
assert get_stream_output(output_stream) == b""
|
||||||
assert input_queue.empty
|
assert input_queue.empty
|
||||||
|
|
|
@ -3,23 +3,32 @@ from unittest.mock import MagicMock
|
||||||
import ffmpeg
|
import ffmpeg
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from croaker import playlist
|
from croaker import playlist, transcoder
|
||||||
from croaker import transcoder
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('suffix, expected', [
|
@pytest.mark.parametrize(
|
||||||
('.mp3', b'_theme.mp3\n'),
|
"suffix, expected",
|
||||||
('.foo', b'transcoding!\n'),
|
[
|
||||||
])
|
(".mp3", b"_theme.mp3\n"),
|
||||||
|
(".foo", b"transcoding!\n"),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_transcoder_open(monkeypatch, suffix, expected):
|
def test_transcoder_open(monkeypatch, suffix, expected):
|
||||||
monkeypatch.setattr(transcoder, 'ffmpeg', MagicMock(spec=ffmpeg, **{
|
monkeypatch.setattr(
|
||||||
'input.return_value.'
|
transcoder,
|
||||||
'output.return_value.'
|
"ffmpeg",
|
||||||
'global_args.return_value.'
|
MagicMock(
|
||||||
'compile.return_value': ['echo', 'transcoding!'],
|
spec=ffmpeg,
|
||||||
}))
|
**{
|
||||||
|
"input.return_value."
|
||||||
|
"output.return_value."
|
||||||
|
"global_args.return_value."
|
||||||
|
"compile.return_value": ["echo", "transcoding!"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
pl = playlist.Playlist(name='test_playlist')
|
pl = playlist.Playlist(name="test_playlist")
|
||||||
track = [t for t in pl.tracks if t.suffix == suffix][0]
|
track = [t for t in pl.tracks if t.suffix == suffix][0]
|
||||||
with transcoder.open(track) as handle:
|
with transcoder.open(track) as handle:
|
||||||
assert handle.read() == expected
|
assert handle.read() == expected
|
||||||
|
|
Loading…
Reference in New Issue
Block a user