2024-03-01 01:00:17 -08:00
|
|
|
import io
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
from pathlib import Path
|
|
|
|
from textwrap import dedent
|
|
|
|
from typing import List, Optional
|
|
|
|
|
|
|
|
import typer
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
from typing_extensions import Annotated
|
|
|
|
|
|
|
|
import croaker.path
|
|
|
|
from croaker.exceptions import ConfigurationError
|
|
|
|
from croaker.playlist import Playlist
|
2024-03-05 22:15:51 -08:00
|
|
|
from croaker.server import server
|
2024-03-01 01:00:17 -08:00
|
|
|
|
|
|
|
SETUP_HELP = """
|
|
|
|
# Root directory for croaker configuration and logs. See also croaker --root.
|
|
|
|
CROAKER_ROOT=~/.dnd/croaker
|
|
|
|
|
2024-03-05 22:51:04 -08:00
|
|
|
# where to store playlist sources
|
|
|
|
#PLAYLIST_ROOT=$CROAKER_ROOT/playlists
|
2024-03-01 01:00:17 -08:00
|
|
|
|
2024-03-05 22:51:04 -08:00
|
|
|
# Where the record the daemon's PID
|
|
|
|
#PIDFILE=$CROAKER_ROOT/croaker.pid
|
2024-03-01 01:00:17 -08:00
|
|
|
|
2024-03-05 22:51:04 -08:00
|
|
|
# Command and Control TCP Server bind address
|
2024-03-04 17:56:32 -08:00
|
|
|
HOST=0.0.0.0
|
2024-03-01 01:00:17 -08:00
|
|
|
PORT=8003
|
|
|
|
|
|
|
|
# the kinds of files to add to playlists
|
|
|
|
MEDIA_GLOB=*.mp3,*.flac,*.m4a
|
|
|
|
|
|
|
|
# Icecast2 configuration for Liquidsoap
|
|
|
|
ICECAST_PASSWORD=
|
|
|
|
ICECAST_MOUNT=
|
|
|
|
ICECAST_HOST=
|
|
|
|
ICECAST_PORT=
|
|
|
|
ICECAST_URL=
|
|
|
|
"""
|
|
|
|
|
|
|
|
app = typer.Typer()
|
|
|
|
app_state = {}
|
|
|
|
|
2024-03-05 22:21:56 -08:00
|
|
|
logger = logging.getLogger('cli')
|
|
|
|
|
2024-03-01 01:00:17 -08:00
|
|
|
|
|
|
|
@app.callback()
|
|
|
|
def main(
|
|
|
|
context: typer.Context,
|
|
|
|
root: Optional[Path] = typer.Option(
|
|
|
|
Path("~/.dnd/croaker"),
|
|
|
|
help="Path to the Croaker environment",
|
|
|
|
),
|
|
|
|
debug: Optional[bool] = typer.Option(None, help="Enable debugging output"),
|
|
|
|
):
|
|
|
|
load_dotenv(root.expanduser() / Path("defaults"))
|
|
|
|
load_dotenv(stream=io.StringIO(SETUP_HELP))
|
|
|
|
if debug is not None:
|
|
|
|
if debug:
|
2024-03-05 22:15:51 -08:00
|
|
|
os.environ["DEBUG"] = "1"
|
2024-03-01 01:00:17 -08:00
|
|
|
else:
|
|
|
|
del os.environ["DEBUG"]
|
|
|
|
|
|
|
|
logging.basicConfig(
|
2024-03-05 22:15:51 -08:00
|
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
2024-03-01 01:00:17 -08:00
|
|
|
level=logging.DEBUG if debug else logging.INFO,
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
2024-03-04 17:56:32 -08:00
|
|
|
croaker.path.root()
|
|
|
|
croaker.path.playlist_root()
|
2024-03-01 01:00:17 -08:00
|
|
|
except ConfigurationError as e:
|
|
|
|
sys.stderr.write(f"{e}\n\n{SETUP_HELP}")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
|
|
|
def setup(context: typer.Context):
|
|
|
|
"""
|
|
|
|
(Re)Initialize Croaker.
|
|
|
|
"""
|
|
|
|
sys.stderr.write("Interactive setup is not yet available. Sorry!\n")
|
|
|
|
print(dedent(SETUP_HELP))
|
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
|
|
|
def start(
|
|
|
|
context: typer.Context,
|
2024-03-05 22:51:04 -08:00
|
|
|
daemonize: bool = typer.Option(True, help="Daemonize the server."),
|
2024-03-01 01:00:17 -08:00
|
|
|
):
|
|
|
|
"""
|
2024-03-05 22:51:04 -08:00
|
|
|
Start the Croaker command and control server.
|
2024-03-01 01:00:17 -08:00
|
|
|
"""
|
2024-03-06 17:04:08 -08:00
|
|
|
server.start(daemonize=daemonize)
|
2024-03-01 01:00:17 -08:00
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
|
|
|
def stop():
|
|
|
|
"""
|
2024-03-05 22:51:04 -08:00
|
|
|
Terminate the server.
|
2024-03-01 01:00:17 -08:00
|
|
|
"""
|
|
|
|
server.stop()
|
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
|
|
|
def add(
|
|
|
|
playlist: str = typer.Argument(
|
|
|
|
...,
|
|
|
|
help="Playlist name",
|
|
|
|
),
|
|
|
|
theme: Optional[bool] = typer.Option(False, help="Make the first track the theme song."),
|
|
|
|
tracks: Annotated[Optional[List[Path]], typer.Argument()] = None,
|
|
|
|
):
|
|
|
|
"""
|
2024-03-04 17:56:32 -08:00
|
|
|
Recursively add one or more paths to the specified playlist.
|
|
|
|
|
|
|
|
Tracks can be any combination of individual audio files and directories
|
|
|
|
containing audio files; anything not already on the playlist will be
|
|
|
|
added to it.
|
2024-03-01 01:00:17 -08:00
|
|
|
|
|
|
|
If --theme is specified, the first track will be designated the playlist
|
|
|
|
"theme." Theme songs get played first whenever the playlist is loaded,
|
|
|
|
after which the playlist order is randomized.
|
|
|
|
"""
|
|
|
|
pl = Playlist(name=playlist)
|
|
|
|
pl.add(tracks, make_theme=theme)
|
|
|
|
print(pl)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
app.main()
|