2022-12-21 23:40:09 -08:00
|
|
|
import io
|
2022-11-19 15:14:12 -08:00
|
|
|
import logging
|
|
|
|
import os
|
2022-12-21 21:16:06 -08:00
|
|
|
import sys
|
2022-11-19 15:14:12 -08:00
|
|
|
import typer
|
|
|
|
|
2022-11-20 16:26:40 -08:00
|
|
|
from pathlib import Path
|
2022-12-06 16:34:58 -08:00
|
|
|
from typing import Optional
|
2022-12-21 21:16:06 -08:00
|
|
|
from textwrap import dedent
|
|
|
|
|
2022-11-19 15:14:12 -08:00
|
|
|
from dotenv import load_dotenv
|
2022-12-21 21:16:06 -08:00
|
|
|
from rich import print
|
2022-12-21 15:17:13 -08:00
|
|
|
from rich.logging import RichHandler
|
2022-11-20 01:00:54 -08:00
|
|
|
|
2022-12-21 21:16:06 -08:00
|
|
|
import groove.path
|
|
|
|
|
2022-11-30 00:09:23 -08:00
|
|
|
from groove.shell import interactive_shell
|
2022-11-20 09:28:00 -08:00
|
|
|
from groove.db.manager import database_manager
|
2022-12-02 21:43:51 -08:00
|
|
|
from groove.webserver import webserver
|
2022-12-21 21:16:06 -08:00
|
|
|
from groove.exceptions import ConfigurationError
|
|
|
|
from groove.console import Console
|
2022-11-19 15:14:12 -08:00
|
|
|
|
2022-12-21 22:27:30 -08:00
|
|
|
SETUP_HELP = """
|
2022-12-21 23:40:09 -08:00
|
|
|
# Please make sure you set MEDIA_ROOT and SECRET_KEY in your environment.
|
|
|
|
# By default, Groove on Demand will attempt to load these variables from
|
2022-12-31 10:45:22 -08:00
|
|
|
# ~/.groove/defaults, which may contain the following variables as well. See
|
|
|
|
# also the --root paramter.
|
2022-12-21 22:27:30 -08:00
|
|
|
|
|
|
|
# Set this one. The path containing your media files
|
|
|
|
MEDIA_ROOT=
|
|
|
|
|
|
|
|
# the kinds of files to import
|
2022-12-21 23:40:09 -08:00
|
|
|
MEDIA_GLOB=*.mp3,*.flac,*.m4a
|
2022-12-21 22:27:30 -08:00
|
|
|
|
2022-12-31 10:45:22 -08:00
|
|
|
# If defined, transcode media before streaming it, and cache it to disk. The
|
|
|
|
# strings INFILE and OUTFILE will be replaced with the media source file and
|
2022-12-31 12:42:31 -08:00
|
|
|
# the cached output location, respectively. The default below uses ffmpeg to
|
|
|
|
# transcode to webm with a reasonable trade-off between file size and quality.
|
2022-12-31 10:45:22 -08:00
|
|
|
TRANSCODER=/usr/bin/ffmpeg -i INFILE -c:v libvpx-vp9 -crf 30 -b:v 0 -b:a 256k -c:a libopus OUTFILE
|
|
|
|
|
|
|
|
# where to cache transcoded media files
|
|
|
|
CACHE_ROOT=~/.groove/cache
|
|
|
|
|
2022-12-21 22:27:30 -08:00
|
|
|
# where to store the groove_on_demand.db sqlite database.
|
2022-12-21 23:40:09 -08:00
|
|
|
DATABASE_PATH=~
|
2022-12-21 22:27:30 -08:00
|
|
|
|
|
|
|
# Try 'groove themes' to see a list of available themes.
|
2022-12-21 23:40:09 -08:00
|
|
|
DEFAULT_THEME=blue_train
|
2022-12-21 22:27:30 -08:00
|
|
|
|
|
|
|
# Web interface configuration
|
2022-12-21 23:40:09 -08:00
|
|
|
HOST=127.0.0.1
|
|
|
|
PORT=2323
|
2022-12-21 22:27:30 -08:00
|
|
|
|
|
|
|
# Set this to a suitably random string.
|
2022-12-21 23:40:09 -08:00
|
|
|
SECRET_KEY=
|
2022-12-21 22:27:30 -08:00
|
|
|
|
|
|
|
# Console configuration
|
2022-12-21 23:40:09 -08:00
|
|
|
EDITOR=vim
|
|
|
|
CONSOLE_WIDTH=auto
|
2022-12-21 22:27:30 -08:00
|
|
|
"""
|
|
|
|
|
2022-11-19 15:14:12 -08:00
|
|
|
app = typer.Typer()
|
|
|
|
|
|
|
|
|
2022-12-21 21:16:06 -08:00
|
|
|
@app.callback()
|
|
|
|
def main(
|
|
|
|
context: typer.Context,
|
2022-12-31 10:45:22 -08:00
|
|
|
root: Optional[Path] = typer.Option(
|
2022-12-21 21:16:06 -08:00
|
|
|
Path('~/.groove'),
|
|
|
|
help="Path to the Groove on Demand environment",
|
|
|
|
)
|
|
|
|
):
|
2022-12-31 10:45:22 -08:00
|
|
|
load_dotenv(root.expanduser() / Path('defaults'))
|
2022-12-21 23:40:09 -08:00
|
|
|
load_dotenv(stream=io.StringIO(SETUP_HELP))
|
2022-11-20 09:28:00 -08:00
|
|
|
debug = os.getenv('DEBUG', None)
|
2022-12-21 15:17:13 -08:00
|
|
|
logging.basicConfig(
|
|
|
|
format='%(message)s',
|
|
|
|
level=logging.DEBUG if debug else logging.INFO,
|
|
|
|
handlers=[
|
|
|
|
RichHandler(rich_tracebacks=True, tracebacks_suppress=[typer])
|
|
|
|
]
|
|
|
|
)
|
|
|
|
logging.getLogger('asyncio').setLevel(logging.ERROR)
|
2022-11-20 01:00:54 -08:00
|
|
|
|
2022-12-21 21:16:06 -08:00
|
|
|
try:
|
|
|
|
groove.path.media_root()
|
|
|
|
groove.path.static_root()
|
|
|
|
groove.path.themes_root()
|
|
|
|
groove.path.database()
|
|
|
|
except ConfigurationError as e:
|
2022-12-21 22:27:30 -08:00
|
|
|
sys.stderr.write(f'{e}\n\n{SETUP_HELP}')
|
2022-12-21 21:16:06 -08:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
|
|
|
def setup(context: typer.Context):
|
|
|
|
"""
|
|
|
|
(Re)Initialize Groove on Demand.
|
|
|
|
"""
|
2022-12-21 23:40:09 -08:00
|
|
|
sys.stderr.write("Interactive setup is not yet available. Sorry!\n")
|
|
|
|
print(dedent(SETUP_HELP))
|
2022-12-21 21:16:06 -08:00
|
|
|
|
2022-11-20 01:00:54 -08:00
|
|
|
|
2022-12-21 15:17:13 -08:00
|
|
|
@app.command()
|
2022-12-21 21:16:06 -08:00
|
|
|
def themes(context: typer.Context):
|
2022-11-25 15:40:24 -08:00
|
|
|
"""
|
2022-12-21 21:16:06 -08:00
|
|
|
List the available themes.
|
|
|
|
"""
|
|
|
|
print("Available themes:")
|
|
|
|
themes = [theme for theme in groove.path.themes_root().iterdir()]
|
|
|
|
tags = ('artist', 'title', 'bold', 'dim', 'link', 'prompt', 'bright', 'text', 'help')
|
|
|
|
for theme in themes:
|
|
|
|
text = ''
|
|
|
|
for tag in tags:
|
|
|
|
text += f'[{tag}]◼◼◼◼◼◼'
|
|
|
|
Console(theme=theme.name).print(f' ▪ [title]{theme.name}[/title] {text}')
|
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
|
|
|
def playlists(context: typer.Context):
|
|
|
|
"""
|
|
|
|
List all playlists
|
2022-11-25 15:40:24 -08:00
|
|
|
"""
|
|
|
|
with database_manager() as manager:
|
2022-12-21 15:17:13 -08:00
|
|
|
shell = interactive_shell.InteractiveShell(manager)
|
|
|
|
shell.list(None)
|
2022-11-25 15:40:24 -08:00
|
|
|
|
|
|
|
|
2022-11-20 16:26:40 -08:00
|
|
|
@app.command()
|
|
|
|
def scan(
|
2022-12-21 21:16:06 -08:00
|
|
|
context: typer.Context,
|
2022-12-21 15:17:13 -08:00
|
|
|
path: Optional[Path] = typer.Option(
|
|
|
|
'',
|
|
|
|
help="A path to scan, relative to your MEDIA_ROOT. "
|
|
|
|
"If not specified, the entire MEDIA_ROOT will be scanned."
|
2022-11-20 16:26:40 -08:00
|
|
|
),
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Scan the filesystem and create track entries in the database.
|
|
|
|
"""
|
|
|
|
with database_manager() as manager:
|
2022-12-21 15:17:13 -08:00
|
|
|
shell = interactive_shell.InteractiveShell(manager)
|
|
|
|
shell.console.print("Starting the Groove on Demand scanner...")
|
|
|
|
shell.scan([str(path)])
|
2022-11-27 18:42:46 -08:00
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
2022-12-21 21:16:06 -08:00
|
|
|
def shell(context: typer.Context):
|
|
|
|
"""
|
|
|
|
Start the Groove on Demand interactive shell.
|
|
|
|
"""
|
|
|
|
with database_manager() as manager:
|
|
|
|
interactive_shell.InteractiveShell(manager).start()
|
2022-11-20 16:26:40 -08:00
|
|
|
|
2022-11-24 13:45:09 -08:00
|
|
|
|
2022-11-19 15:14:12 -08:00
|
|
|
@app.command()
|
|
|
|
def server(
|
2022-12-21 21:16:06 -08:00
|
|
|
context: typer.Context,
|
2022-11-19 15:14:12 -08:00
|
|
|
host: str = typer.Argument(
|
|
|
|
'0.0.0.0',
|
|
|
|
help="bind address",
|
|
|
|
),
|
|
|
|
port: int = typer.Argument(
|
|
|
|
2323,
|
|
|
|
help="bind port",
|
|
|
|
),
|
|
|
|
debug: bool = typer.Option(
|
|
|
|
False,
|
|
|
|
help='Enable debugging output'
|
|
|
|
),
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Start the Groove on Demand playlsit server.
|
|
|
|
"""
|
2022-11-20 16:26:40 -08:00
|
|
|
with database_manager() as manager:
|
2022-11-20 09:28:00 -08:00
|
|
|
manager.import_from_filesystem()
|
|
|
|
webserver.start(host=host, port=port, debug=debug)
|
2022-11-19 15:14:12 -08:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
app()
|