Compare commits

..

No commits in common. "main" and "beta" have entirely different histories.
main ... beta

23 changed files with 46 additions and 379 deletions

View File

@ -12,24 +12,29 @@ Groove on Demand was developed against python 3.10 and should work on anything 3
I have no idea if it will function on platforms besides Linux. Code was written to be portable but not tested to be portable. I also don't know if the dependencies support diverse platforms or not. `¯\_(ツ)_/¯` I have no idea if it will function on platforms besides Linux. Code was written to be portable but not tested to be portable. I also don't know if the dependencies support diverse platforms or not. `¯\_(ツ)_/¯`
### 1. Download and install the latest release ### 1. Download the latest release
[ check the releases tab ] [ check the releases tab ]
``` ```
pip3 install https://github.com/evilchili/grooveondemand/releases/download/beta/grooveondemand-0.10.tar.gz wget https://github.com/evilchili/grooveondemand/releases/grooveondemand-0.9.tar.gz
```
### 2. Install
```
pip3 install grooveondemand-0.9.tar.gz
``` ```
### 3. Generate the default configuration ### 3. Generate the default configuration
``` ```
mkdir ~/.groove ~/.local/bin/groove setup > ~/.groove
~/.local/bin/groove setup > ~/.groove/defaults
``` ```
### 3. Set the Media Root ### 4. Set the Media Root
Edit `~/.groove/defaults` and define `MEDIA_ROOT` to point to the directory containing your local audio files. For example: Edit `~/.groove` and defined `MEDIA_ROOT` to point to the directory containing your local audio files. For example:
``` ```
MEDIA_ROOT=/media/audio/lossless MEDIA_ROOT=/media/audio/lossless
@ -43,12 +48,7 @@ Before creating playlists, you must scan your media and build a database of trac
groove scan groove scan
``` ```
This may take a long time depending on the size of your library and the capabilities of your system. Progress will be displayed as the scan progresses: This may take a long time depending on the size of your library and the capabilities of your system. Progress will be displayed as the scan progresses.
<pre>
groove&gt; scan
1:27:34 <font color="#70BC45">━━━━━━━━━━━━━━━</font> <font color="#70BC45">100%</font> <font color="#555555">|</font> <font color="#70BC45"> 29946</font> <font color="#F1F2F6"><b>total</b></font> <font color="#555555">|</font> <font color="#70BC45"> 29946</font> <font color="#F1F2F6"><b>new</b></font> <font color="#555555">|</font> <font color="#F1F2F6">Scan of </font><font color="#9999FF">/mnt/grunt/music/FLAC</font><font color="#F1F2F6"> complete!</font>
</pre>
## Start the Interactive Shell ## Start the Interactive Shell

View File

@ -23,8 +23,8 @@ from groove.console import Console
SETUP_HELP = """ SETUP_HELP = """
# Please make sure you set MEDIA_ROOT and SECRET_KEY in your environment. # 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 # By default, Groove on Demand will attempt to load these variables from
# ~/.groove/defaults, which may contain the following variables as well. See # ~/.groove, which may contain the following variables as well. See also
# also the --root paramter. # the --env paramter.
# Set this one. The path containing your media files # Set this one. The path containing your media files
MEDIA_ROOT= MEDIA_ROOT=
@ -32,15 +32,6 @@ MEDIA_ROOT=
# the kinds of files to import # the kinds of files to import
MEDIA_GLOB=*.mp3,*.flac,*.m4a MEDIA_GLOB=*.mp3,*.flac,*.m4a
# 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
# the cached output location, respectively. The default below uses ffmpeg to
# transcode to webm with a reasonable trade-off between file size and quality.
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
# where to store the groove_on_demand.db sqlite database. # where to store the groove_on_demand.db sqlite database.
DATABASE_PATH=~ DATABASE_PATH=~
@ -51,9 +42,6 @@ DEFAULT_THEME=blue_train
HOST=127.0.0.1 HOST=127.0.0.1
PORT=2323 PORT=2323
# The URL to use when constructing links. Defaults to http://HOST:PORT.
#BASE_URL=http://127.0.0.1:2323
# Set this to a suitably random string. # Set this to a suitably random string.
SECRET_KEY= SECRET_KEY=
@ -68,12 +56,12 @@ app = typer.Typer()
@app.callback() @app.callback()
def main( def main(
context: typer.Context, context: typer.Context,
root: Optional[Path] = typer.Option( env: Optional[Path] = typer.Option(
Path('~/.groove'), Path('~/.groove'),
help="Path to the Groove on Demand environment", help="Path to the Groove on Demand environment",
) )
): ):
load_dotenv(root.expanduser() / Path('defaults')) load_dotenv(env.expanduser())
load_dotenv(stream=io.StringIO(SETUP_HELP)) load_dotenv(stream=io.StringIO(SETUP_HELP))
debug = os.getenv('DEBUG', None) debug = os.getenv('DEBUG', None)
logging.basicConfig( logging.basicConfig(

View File

@ -110,13 +110,6 @@ class Console(_Console):
""" """
super().print(txt, overflow=self._overflow, **kwargs) super().print(txt, overflow=self._overflow, **kwargs)
def debug(self, txt: str, **kwargs) -> None:
"""
Print text to the console with the current theme's debug style applied, if debugging is enabled.
"""
if os.environ.get('DEBUG', None):
self.print(dedent(txt), style='debug')
def error(self, txt: str, **kwargs) -> None: def error(self, txt: str, **kwargs) -> None:
""" """
Print text to the console with the current theme's error style applied. Print text to the console with the current theme's error style applied.

View File

@ -1,138 +0,0 @@
import asyncio
import logging
import os
import subprocess
from typing import Union, List
import rich.repr
from rich.console import Console
from rich.progress import (
Progress,
TextColumn,
BarColumn,
SpinnerColumn,
TimeRemainingColumn
)
import groove.path
@rich.repr.auto(angular=True)
class Transcoder:
"""
SYNOPSIS
USAGE
ARGS
EXAMPLES
INSTANCE ATTRIBUTES
"""
def __init__(self, console: Union[Console, None] = None) -> None:
self.console = console or Console()
self._transcoded = 0
self._processed = 0
self._total = 0
def transcode(self, sources: List) -> int:
"""
Transcode the list of source files
"""
count = len(sources)
if not os.environ.get('TRANSCODER', None):
self.console.error("Cannot transcode tracks without a TRANSCODER defined in your environment.")
return
cache = groove.path.cache_root()
if not cache.exists():
cache.mkdir()
async def _do_transcode(progress, task_id):
tasks = set()
for relpath in sources:
self._total += 1
progress.update(task_id, total=self._total)
tasks.add(asyncio.create_task(self._transcode_one_track(relpath, progress, task_id)))
progress.start_task(task_id)
progress = Progress(
TimeRemainingColumn(compact=True, elapsed_when_finished=True),
BarColumn(bar_width=15),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%", justify="left"),
TextColumn("[dim]|"),
TextColumn("[title]{task.total:-6d}[/title] [b]total", justify="right"),
TextColumn("[dim]|"),
TextColumn("[title]{task.fields[transcoded]:-6d}[/title] [b]new", justify="right"),
TextColumn("[dim]|"),
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=self.console,
)
with progress:
task_id = progress.add_task(
f"[bright]Transcoding [link]{count} tracks[/link]...",
transcoded=0,
total=0,
start=False
)
asyncio.run(_do_transcode(progress, task_id))
progress.update(
task_id,
transcoded=self._transcoded,
completed=self._total,
description=f"[bright]Transcode of [link]{count} tracks[/link] complete!",
)
def _get_or_create_cache_dir(self, relpath):
cached_path = groove.path.transcoded_media(relpath)
cached_path.parent.mkdir(parents=True, exist_ok=True)
return cached_path
def _run_transcoder(self, infile, outfile):
cmd = []
for part in os.environ['TRANSCODER'].split():
if part == 'INFILE':
cmd.append(str(infile))
elif part == 'OUTFILE':
cmd.append(str(outfile))
else:
cmd.append(part)
output = ''
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
self.console.error(f"Transcoder failed with status {e.returncode}: {' '.join(cmd)}")
self.console.error(output)
async def _transcode_one_track(self, relpath, progress, task_id):
"""
Transcode one track, if it isn't already cached.
"""
self._processed += 1
source_path = groove.path.media(relpath)
if not source_path.exists():
logging.error(f"Source does not exist: [link]{source_path}[/link].")
return
cached_path = self._get_or_create_cache_dir(relpath)
if cached_path.exists():
self.console.debug(f"Skipping existing [link]{cached_path}[/link].")
return
self.console.debug(f"Transcoding [link]{cached_path}[/link]")
self._run_transcoder(source_path, cached_path)
progress.update(
task_id,
transcoded=self._transcoded,
processed=self._processed,
description=f"[bright]Transcoded [link]{relpath}[/link]",
)

View File

@ -4,7 +4,7 @@ import os
from pathlib import Path from pathlib import Path
from groove.exceptions import ConfigurationError, ThemeMissingException, ThemeConfigurationError from groove.exceptions import ConfigurationError, ThemeMissingException, ThemeConfigurationError
_setup_hint = "You may be able to solve this error by running 'groove setup' or specifying the --root parameter." _setup_hint = "You may be able to solve this error by running 'groove setup' or specifying the --env parameter."
_reinstall_hint = "You might need to reinstall Groove On Demand to fix this error." _reinstall_hint = "You might need to reinstall Groove On Demand to fix this error."
@ -27,25 +27,11 @@ def media_root():
return path return path
def cache_root():
path = os.environ.get('CACHE_ROOT', None)
if not path:
raise ConfigurationError(f"CACHE_ROOT is not defined in your environment.\n\n{_setup_hint}")
path = Path(path).expanduser()
logging.debug(f"Media cache root is {path}")
return path
def media(relpath): def media(relpath):
path = media_root() / Path(relpath) path = media_root() / Path(relpath)
return path return path
def transcoded_media(relpath):
path = cache_root() / Path(relpath + '.webm')
return path
def static_root(): def static_root():
dirname = os.environ.get('STATIC_PATH', 'static') dirname = os.environ.get('STATIC_PATH', 'static')
path = root() / Path(dirname) path = root() / Path(dirname)

View File

@ -73,8 +73,7 @@ class Playlist:
@property @property
def url(self) -> str: def url(self) -> str:
base = os.environ.get('BASE_URL', f"http://{os.environ['HOST']}:{os.environ['PORT']}") return f"http://{os.environ['HOST']}:{os.environ['PORT']}/playlist/{self.slug}"
return f"{base}/playlist/{self.slug}"
@property @property
def slug(self) -> str: def slug(self) -> str:
@ -128,8 +127,7 @@ class Playlist:
playlist = { playlist = {
'name': self.name, 'name': self.name,
'slug': self.slug, 'slug': self.slug,
'description': self.description, 'description': self.description
'url': self.url
} }
if self.record: if self.record:
playlist.update(dict(self.record)) playlist.update(dict(self.record))

View File

@ -1,8 +1,7 @@
from slugify import slugify from slugify import slugify
from groove.db.manager import database_manager from groove.db.manager import database_manager
from groove.media.scanner import MediaScanner from groove.db.scanner import MediaScanner
from groove.media.transcoder import Transcoder
from groove.shell.base import BasePrompt, command from groove.shell.base import BasePrompt, command
from groove.exceptions import InvalidPathError from groove.exceptions import InvalidPathError
from groove import db from groove import db
@ -91,28 +90,6 @@ class InteractiveShell(BasePrompt):
return True return True
scanner.scan() scanner.scan()
@command(usage="""
[title]TRANSCODING[/title]
Groove on Demand will stream audio to web clients in the native format of your source media files, but for maximum
portability, performance, and interoperability with reverse proxies, it's a good idea to transcode to .webm first.
Use the [b]transcode[/b] command to cache transcoded copies of every track currently in a playlist that isn't
already in .webm format. Existing cache entries will be skipped.
The default Groove on Demand configuration uses ffmpeg; try [b]groove setup[/b] from the command-line.
[title]USAGE[/title]
[link]> transcode[/link]
""")
def transcode(self, parts):
"""
Run the transcoder.
"""
tracks = self.manager.session.query(db.track).filter(db.entry.c.track_id == db.track.c.id).all()
transcoder = Transcoder(console=self.console)
transcoder.transcode([track['relpath'] for track in tracks])
@command(""" @command("""
[title]LISTS FOR THE LIST LOVER[/title] [title]LISTS FOR THE LIST LOVER[/title]

View File

@ -18,7 +18,6 @@ help = #999999
background = #001321 background = #001321
info = #88FF88 info = #88FF88
debug = #888888
error = #FF8888 error = #FF8888
danger = #FF8888 danger = #FF8888

View File

@ -1,89 +0,0 @@
<?xml version="1.0"?>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:svg="http://www.w3.org/2000/svg" id="svg2" viewBox="0 0 652.5 652.5" version="1.0">
<defs id="defs4">
<linearGradient id="linearGradient3181">
<stop id="stop3183" stop-color="#fff" stop-opacity=".30928" offset="0"/>
<stop id="stop3185" stop-color="#c3ffff" stop-opacity="0" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient3306" xlink:href="#linearGradient3181" gradientUnits="userSpaceOnUse" cy="159.54" cx="163.41" gradientTransform="matrix(1.4977 0 0 1.4977 -121.21 -108.37)" r="120.19"/>
<radialGradient id="radialGradient3320" xlink:href="#linearGradient3181" gradientUnits="userSpaceOnUse" cy="-138.31" cx="200.03" gradientTransform="matrix(1.1233 0 0 -1.1233 288.85 353.75)" r="120.19"/>
<linearGradient id="linearGradient3342" y2="273.03" gradientUnits="userSpaceOnUse" y1="159.44" x2="93.862" x1="-20.319">
<stop id="stop3352" stop-color="#fff" offset="0"/>
<stop id="stop3354" stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient3348" y2="273.03" gradientUnits="userSpaceOnUse" y1="334.97" x2="93.862" x1="156.12">
<stop id="stop3338" stop-opacity=".50588" offset="0"/>
<stop id="stop3340" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient3258" y2="330.78" gradientUnits="userSpaceOnUse" y1="260.13" x2="331.06" x1="263.75">
<stop id="stop3254" offset="0"/>
<stop id="stop3256" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient3384" y2="330.78" gradientUnits="userSpaceOnUse" y1="394.54" x2="331.06" x1="391.81">
<stop id="stop3380" stop-color="#fff" offset="0"/>
<stop id="stop3382" stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
</defs>
<g id="layer1">
<path id="path2178" d="m326.25 16.875c-170.77 0-309.38 138.6-309.38 309.38 0.005 170.76 138.61 309.36 309.38 309.36s309.38-138.6 309.37-309.37c0-170.77-138.59-309.38-309.37-309.38zm0 237.38c39.74 0 72 32.26 72 72s-32.26 72-72 72-72-32.26-72-72 32.26-72 72-72z"/>
<path id="path3189" opacity=".5" fill="url(#radialGradient3320)" d="m513.53 378.69c-5.17 0-10.29 0.27-15.31 0.84-17.53 56.57-62.23 101.23-118.81 118.72-0.58 5.06-0.88 10.22-0.88 15.44 0 74.52 60.48 135 135 135s135-60.48 135-135-60.48-135-135-135z"/>
<path id="path3171" fill="url(#radialGradient3306)" d="m268.34 22.312c-124.26 23.562-222.28 121.51-245.96 245.72 26.392 45.7 72.284 78.74 126.28 87.72-1.59-9.6-2.41-19.45-2.41-29.5 0-99.36 80.64-180 180-180 10.11 0 20.03 0.83 29.69 2.44-8.92-54.014-41.93-99.935-87.6-126.38zm57.91 226.78c-42.58 0-77.16 34.58-77.16 77.16 0 5.73 0.66 11.31 1.85 16.69 3.03-1.34 6.02-2.78 8.97-4.28-0.75-4.03-1.16-8.17-1.16-12.41 0-37.26 30.24-67.5 67.5-67.5 4.26 0 8.42 0.43 12.47 1.19 1.5-2.94 2.94-5.94 4.28-8.97-5.4-1.2-10.99-1.88-16.75-1.88z"/>
<path id="path3325" d="m326.25 16.875c-170.77 0-309.38 138.6-309.38 309.38 0.005 170.76 138.61 309.36 309.38 309.36s309.38-138.6 309.37-309.37c0-170.77-138.59-309.38-309.37-309.38zm0 10.125c165.19 0 299.25 134.06 299.25 299.25s-134.06 299.25-299.25 299.25-299.25-134.06-299.25-299.25 134.06-299.25 299.25-299.25z"/>
<path id="path3332" d="m88.844 264.11c-53.275 53.67-142.62 53.79-196.23 0.56-53.98-53.58-54.11-143.45-0.57-197.38 53.896-54.293 144.28-54.42 198.53-0.569 54.61 54.209 54.73 145.12 0.569 199.68-54.525 54.93-145.96 55.06-200.84 0.58-55.24-54.84-55.37-146.81-0.58-202 55.161-55.566 147.66-55.695 203.17-0.584 55.88 55.474 56.01 148.5 0.585 204.34-55.798 56.2-149.36 56.33-205.52 0.58-56.53-56.12-56.66-150.22-0.59-206.7 56.439-56.851 151.08-56.982 207.89-0.589 57.18 56.769 57.31 151.96 0.59 209.08-57.091 57.51-152.83 57.64-210.28 0.6-57.83-57.42-57.97-153.7-0.6-211.49 57.747-58.16 154.58-58.293 212.69-0.596 58.5 58.076 58.63 155.47 0.607 213.91-58.407 58.83-156.35 58.96-215.13 0.61-59.16-58.74-59.3-157.24-0.61-216.35 59.077-59.502 158.14-59.638 217.58-0.614 59.84 59.414 59.97 159.02 0.62 218.81-59.75 60.17-159.93 60.31-220.06 0.62-60.51-60.09-60.65-160.84-0.62-221.3 60.428-60.859 161.75-60.998 222.55-0.624 61.2 60.764 61.34 162.65 0.63 223.8-61.112 61.54-163.58 61.68-225.07 0.63-61.89-61.46-62.03-164.5-0.63-226.34 61.8-62.232 165.42-62.372 227.61-0.626 62.58 62.146 62.72 166.35 0.63 228.88-62.494 62.94-167.28 63.08-230.16 0.64-63.29-62.84-63.43-168.22-0.64-231.45 63.192-63.64 169.15-63.783 232.74-0.642 64 63.552 64.14 170.1 0.65 234.04-63.906 64.35-171.05 64.5-235.35 0.65-64.71-64.26-64.85-172-0.65-236.66 64.615-65.066 172.96-65.211 237.97-0.65 65.43 64.97 65.57 173.91 0.65 239.29-65.329 65.79-174.87 65.94-240.61 0.66-66.15-65.7-66.3-175.85-0.66-241.95 66.057-66.519 176.82-66.666 243.28-0.662 66.89 66.422 67.04 177.79 0.67 244.62-66.791 67.26-178.78 67.41-245.97 0.67-67.63-67.16-67.78-179.76-0.68-247.32 67.53-68 180.75-68.15 248.69-0.679 68.37 67.899 68.52 181.74 0.68 250.05-68.274 68.74-182.74 68.89-251.42 0.68-69.12-68.64-69.27-183.73-0.69-252.8 69.022-69.49 184.74-69.643 254.18-0.681 69.87 69.401 70.03 185.75 0.69 255.56-69.776 70.26-186.76 70.41-256.96 0.69-70.63-70.15-70.79-187.77-0.69-258.35 70.534-71.021 188.79-71.176 259.76-0.697 71.4 70.917 71.56 189.82 0.7 261.17-71.307 71.79-190.85 71.95-262.59 0.7-72.18-71.69-72.34-191.88-0.7-264 72.074-72.572 192.92-72.729 265.43-0.71 72.96 72.47 73.12 193.96 0.71 266.87-72.857 73.35-195.01 73.51-268.3 0.71-73.75-73.25-73.91-196.06-0.72-269.75 73.644-74.144 197.11-74.304 271.2-0.719 74.54 74.039 74.7 198.17 0.72 272.66-74.436 74.94-199.23 75.1-274.12 0.72-75.34-74.83-75.5-200.3-0.72-275.58 75.233-75.748 201.37-75.911 277.05-0.734 76.15 75.634 76.32 202.44 0.74 278.53-76.044 76.56-203.53 76.72-280.02 0.74-76.97-76.45-77.13-204.61-0.75-281.51 76.86-77.376 205.71-77.541 283.02-0.747 77.78 77.267 77.95 206.8 0.74 284.52-77.67 78.19-207.89 78.36-286.02 0.75-78.61-78.09-78.78-209-0.75-287.54 78.495-79.027 210.1-79.195 289.06-0.758 79.44 78.918 79.61 211.21 0.76 290.59-79.335 79.86-212.33 80.03-292.13 0.76-80.28-79.75-80.45-213.44-0.76-293.66 80.169-80.702 214.57-80.873 295.21-0.767 81.12 80.591 81.29 215.7 0.77 296.76-81.017 81.55-216.83 81.73-298.32 0.78-81.98-81.44-82.15-217.97-0.78-299.88 81.87-82.413 219.11-82.587 301.45-0.784 82.84 82.294 83.02 220.25 0.79 303.02-82.727 83.27-221.41 83.45-304.61 0.79-83.71-83.16-83.88-222.56-0.79-306.2 83.589-84.14 223.72-84.316 307.79-0.791 84.58 84.024 84.76 224.88 0.8 309.39-84.465 85.02-226.06 85.2-311 0.8-85.46-84.9-85.64-227.22-0.81-312.61 85.345-85.904 228.41-86.083 314.24-0.8084 86.34 85.782 86.52 229.59 0.81 315.86-86.23 86.79-230.78 86.97-317.5 0.81-87.23-86.67-87.41-231.96-0.81-319.13 87.118-87.686 233.16-87.867 320.78-0.8157 88.13 87.567 88.32 234.36 0.82 322.43-88.022 88.59-235.57 88.77-324.09 0.82-89.04-88.47-89.23-236.77-0.83-325.74 88.929-89.506 237.99-89.69 327.42-0.8338 89.96 89.38 90.15 239.2 0.84 329.09-89.84 90.42-240.44 90.61-330.78 0.84-90.88-90.3-91.07-241.66-0.84-332.47 90.756-91.345 242.89-91.532 334.16-0.8434 91.81 91.22 92 244.13 0.85 335.87-91.685 92.27-245.38 92.46-337.58 0.84-92.74-92.15-92.93-246.62-0.85-339.29 92.619-93.214 247.87-93.404 341.02-0.8548 93.68 93.089 93.87 249.13 0.85 342.74-93.557 94.16-250.39 94.35-344.48 0.86-94.63-94.03-94.82-251.66-0.86-346.22 94.508-95.11 252.93-95.31 347.97-0.8688 95.59 94.987 95.79 254.21 0.87 349.73-95.464 96.07-255.49 96.27-351.49 0.87-96.55-95.94-96.75-256.77-0.87-353.26 96.424-97.04 258.06-97.23 355.03-0.876 97.52 96.912 97.72 259.36 0.88 356.82" stroke-opacity=".039216" transform="matrix(1.2034 0 0 1.2094 335.56 126.67)" stroke="#f0f0ff" stroke-width="1px" fill="none"/>
<path id="path3193" fill="#ff8a3c" d="m326.25 168.75c-86.94 0-157.5 70.56-157.5 157.5s70.56 157.5 157.5 157.5 157.5-70.56 157.5-157.5-70.56-157.5-157.5-157.5zm0 90c37.26 0 67.5 30.24 67.5 67.5s-30.24 67.5-67.5 67.5-67.5-30.24-67.5-67.5 30.24-67.5 67.5-67.5z"/>
<path id="path3374" d="m88.844 264.11c-53.275 53.67-142.62 53.79-196.23 0.56-53.98-53.58-54.11-143.45-0.57-197.38 53.896-54.293 144.28-54.42 198.53-0.569 54.61 54.209 54.73 145.12 0.569 199.68-54.525 54.93-145.96 55.06-200.84 0.58-55.24-54.84-55.37-146.81-0.58-202 55.161-55.566 147.66-55.695 203.17-0.584 55.88 55.474 56.01 148.5 0.585 204.34-55.798 56.2-149.36 56.33-205.52 0.58-56.53-56.12-56.66-150.22-0.59-206.7 56.439-56.851 151.08-56.982 207.89-0.589 57.18 56.769 57.31 151.96 0.59 209.08-57.091 57.51-152.83 57.64-210.28 0.6-57.83-57.42-57.97-153.7-0.6-211.49 57.747-58.16 154.58-58.293 212.69-0.596 58.5 58.076 58.63 155.47 0.607 213.91-58.407 58.83-156.35 58.96-215.13 0.61-59.16-58.74-59.3-157.24-0.61-216.35 59.077-59.502 158.14-59.638 217.58-0.614 59.84 59.414 59.97 159.02 0.62 218.81-59.75 60.17-159.93 60.31-220.06 0.62-60.51-60.09-60.65-160.84-0.62-221.3 60.428-60.859 161.75-60.998 222.55-0.624 61.2 60.764 61.34 162.65 0.63 223.8-61.112 61.54-163.58 61.68-225.07 0.63-61.89-61.46-62.03-164.5-0.63-226.34 61.8-62.232 165.42-62.372 227.61-0.626 62.58 62.146 62.72 166.35 0.63 228.88-62.494 62.94-167.28 63.08-230.16 0.64-63.29-62.84-63.43-168.22-0.64-231.45 63.192-63.64 169.15-63.783 232.74-0.642 64 63.552 64.14 170.1 0.65 234.04-63.906 64.35-171.05 64.5-235.35 0.65-64.71-64.26-64.85-172-0.65-236.66 64.615-65.066 172.96-65.211 237.97-0.65 65.43 64.97 65.57 173.91 0.65 239.29-65.329 65.79-174.87 65.94-240.61 0.66-66.15-65.7-66.3-175.85-0.66-241.95 66.057-66.519 176.82-66.666 243.28-0.662 66.89 66.422 67.04 177.79 0.67 244.62-66.791 67.26-178.78 67.41-245.97 0.67-67.63-67.16-67.78-179.76-0.68-247.32 67.53-68 180.75-68.15 248.69-0.679 68.37 67.899 68.52 181.74 0.68 250.05-68.274 68.74-182.74 68.89-251.42 0.68-69.12-68.64-69.27-183.73-0.69-252.8 69.022-69.49 184.74-69.643 254.18-0.681 69.87 69.401 70.03 185.75 0.69 255.56-69.776 70.26-186.76 70.41-256.96 0.69-70.63-70.15-70.79-187.77-0.69-258.35 70.534-71.021 188.79-71.176 259.76-0.697 71.4 70.917 71.56 189.82 0.7 261.17-71.307 71.79-190.85 71.95-262.59 0.7-72.18-71.69-72.34-191.88-0.7-264 72.074-72.572 192.92-72.729 265.43-0.71 72.96 72.47 73.12 193.96 0.71 266.87-72.857 73.35-195.01 73.51-268.3 0.71-73.75-73.25-73.91-196.06-0.72-269.75 73.644-74.144 197.11-74.304 271.2-0.719 74.54 74.039 74.7 198.17 0.72 272.66-74.436 74.94-199.23 75.1-274.12 0.72-75.34-74.83-75.5-200.3-0.72-275.58 75.233-75.748 201.37-75.911 277.05-0.734 76.15 75.634 76.32 202.44 0.74 278.53-76.044 76.56-203.53 76.72-280.02 0.74-76.97-76.45-77.13-204.61-0.75-281.51 76.86-77.376 205.71-77.541 283.02-0.747 77.78 77.267 77.95 206.8 0.74 284.52-77.67 78.19-207.89 78.36-286.02 0.75-78.61-78.09-78.78-209-0.75-287.54 78.495-79.027 210.1-79.195 289.06-0.758 79.44 78.918 79.61 211.21 0.76 290.59-79.335 79.86-212.33 80.03-292.13 0.76-80.28-79.75-80.45-213.44-0.76-293.66 80.169-80.702 214.57-80.873 295.21-0.767 81.12 80.591 81.29 215.7 0.77 296.76-81.017 81.55-216.83 81.73-298.32 0.78-81.98-81.44-82.15-217.97-0.78-299.88 81.87-82.413 219.11-82.587 301.45-0.784 82.84 82.294 83.02 220.25 0.79 303.02-82.727 83.27-221.41 83.45-304.61 0.79-83.71-83.16-83.88-222.56-0.79-306.2 83.589-84.14 223.72-84.316 307.79-0.791 84.58 84.024 84.76 224.88 0.8 309.39-84.465 85.02-226.06 85.2-311 0.8-85.46-84.9-85.64-227.22-0.81-312.61 85.345-85.904 228.41-86.083 314.24-0.8084 86.34 85.782 86.52 229.59 0.81 315.86-86.23 86.79-230.78 86.97-317.5 0.81-87.23-86.67-87.41-231.96-0.81-319.13 87.118-87.686 233.16-87.867 320.78-0.8157 88.13 87.567 88.32 234.36 0.82 322.43-88.022 88.59-235.57 88.77-324.09 0.82-89.04-88.47-89.23-236.77-0.83-325.74 88.929-89.506 237.99-89.69 327.42-0.8338 89.96 89.38 90.15 239.2 0.84 329.09-89.84 90.42-240.44 90.61-330.78 0.84-90.88-90.3-91.07-241.66-0.84-332.47 90.756-91.345 242.89-91.532 334.16-0.8434 91.81 91.22 92 244.13 0.85 335.87-91.685 92.27-245.38 92.46-337.58 0.84-92.74-92.15-92.93-246.62-0.85-339.29 92.619-93.214 247.87-93.404 341.02-0.8548 93.68 93.089 93.87 249.13 0.85 342.74-93.557 94.16-250.39 94.35-344.48 0.86-94.63-94.03-94.82-251.66-0.86-346.22 94.508-95.11 252.93-95.31 347.97-0.8688 95.59 94.987 95.79 254.21 0.87 349.73-95.464 96.07-255.49 96.27-351.49 0.87-96.55-95.94-96.75-256.77-0.87-353.26 96.424-97.04 258.06-97.23 355.03-0.876 97.52 96.912 97.72 259.36 0.88 356.82" stroke-opacity=".49727" transform="matrix(1.2034 0 0 1.2094 333.56 126.67)" stroke="#000" stroke-width="1px" fill="none"/>
<path id="path3334" d="m155.49 247.16a87.629 87.18 0 1 1 -175.26 0 87.629 87.18 0 1 1 175.26 0z" stroke-opacity=".25098" transform="matrix(1.7862 0 0 1.7954 205.04 -117.5)" stroke="url(#linearGradient3342)" stroke-linecap="round" stroke-width="1.2564" fill="none"/>
<path id="path3346" d="m155.49 247.16a87.629 87.18 0 1 1 -175.26 0 87.629 87.18 0 1 1 175.26 0z" stroke-opacity=".25098" transform="matrix(1.7862 0 0 1.7954 205.04 -117.5)" stroke="url(#linearGradient3348)" stroke-linecap="round" stroke-width="2.5128" fill="none"/>
<path id="path2280" d="m390.96 327.15a63.363 66.508 0 1 1 -126.72 0 63.363 66.508 0 1 1 126.72 0z" stroke-opacity=".25098" transform="matrix(1.0746 0 0 1.0238 -25.801 -8.6896)" stroke="url(#linearGradient3258)" stroke-width="1.7311" fill="none"/>
<path id="path3376" d="m390.96 327.15a63.363 66.508 0 1 1 -126.72 0 63.363 66.508 0 1 1 126.72 0z" stroke-opacity=".25098" transform="matrix(1.0746 0 0 1.0238 -25.801 -8.6896)" stroke="url(#linearGradient3384)" stroke-width="1.7311" fill="none"/>
</g>
<g id="layer3">
<path id="path3273" d="m155.49 247.16a87.629 87.18 0 1 1 -175.26 0 87.629 87.18 0 1 1 175.26 0z" stroke-opacity=".37647" transform="matrix(1.6841 0 0 1.6928 211.97 -92.147)" stroke="#fff" stroke-linecap="round" stroke-width="1.0896" fill="none"/>
</g>
<metadata>
<rdf:RDF>
<cc:Work>
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
<dc:publisher>
<cc:Agent rdf:about="http://openclipart.org/">
<dc:title>Openclipart</dc:title>
</cc:Agent>
</dc:publisher>
<dc:title>45 Record Album</dc:title>
<dc:date>2007-08-18T16:13:11</dc:date>
<dc:description>A 45 record album. &#xD;\n&#xD;\nThe spiral grooves really slow a computer down so if you're editing it on older system, you may want to delete those two objects first thing.</dc:description>
<dc:source>http://openclipart.org/detail/4835/45-record-album-by-masonmouse</dc:source>
<dc:creator>
<cc:Agent>
<dc:title>masonmouse</dc:title>
</cc:Agent>
</dc:creator>
<dc:subject>
<rdf:Bag>
<rdf:li>45 record</rdf:li>
<rdf:li>album</rdf:li>
<rdf:li>clip art</rdf:li>
<rdf:li>clipart</rdf:li>
<rdf:li>disc jockey</rdf:li>
<rdf:li>dj</rdf:li>
<rdf:li>how i did it</rdf:li>
<rdf:li>image</rdf:li>
<rdf:li>media</rdf:li>
<rdf:li>music</rdf:li>
<rdf:li>png</rdf:li>
<rdf:li>public domain</rdf:li>
<rdf:li>svg</rdf:li>
<rdf:li>vinyl</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -6,15 +6,16 @@ html {
margin: 0; margin: 0;
outline: 0; outline: 0;
font-family: 'Clarendon MT Std', sans-serif; font-family: 'Clarendon MT Std', sans-serif;
font-size: 16px;
} }
body { body {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
background: rgb(0, 4, 16); background: rgb(0,4,16);
background: linear-gradient(0deg, rgba(0,4,16,1) 31%, rgba(1,125,147,1) 97%);
} }
a, a:visited { a, a:visited {
@ -26,13 +27,11 @@ a:active, a:hover {
} }
#container { #container {
position: relative;
background: linear-gradient(0deg, rgba(0,4,16,1) 1%, rgba(1,125,147,1) 97%);
overflow-x: wrap; overflow-x: wrap;
overflow-y: auto; width: 96%;
width: calc(100% - 4em); height: 96%;
height: calc(100% - 4em); margin: auto;
padding: 2em; margin-top: 1em;
} }
#details { #details {
@ -116,40 +115,14 @@ a:active, a:hover {
line-height: 5em; line-height: 5em;
} }
#playBtn { #playBtn {
color: #000; font-family: sans-serif;
font-size: 5em;
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs,1));
width: 100%;
height: 100%;
}
#playBtn:before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
top: 0.20em;
left:0.35em;
border-top: 0.3em solid transparent;
border-bottom: 0.3em solid transparent;
border-left: 0.4em solid;
}
#pauseBtn {
display: none;
position: absolute;
width: 50%;
height: 50%;
font-size: 5em; font-size: 5em;
margin: 0px auto; padding-left: 0.1em;
left: 0.3em; }
top: 0.25em; #pauseBtn {
border-color: #000; font-family: sans-serif;
border-style: double; display: none;
border-width: 0px 0px 0px 37px; font-size: 4em;
} }
#prevBtn { #prevBtn {
color: #fff; color: #fff;
@ -208,12 +181,10 @@ a:active, a:hover {
} }
.list-song { .list-song {
padding: 0.25em; padding: 0.25em;
color: #70bc45;
} }
.list-song:hover { .list-song:hover {
cursor: pointer; cursor: pointer;
background: #f1f2f6; background: #f1f2f6;
color: #000;
} }
#footer { #footer {
@ -223,6 +194,7 @@ a:active, a:hover {
color: #888; color: #888;
font-size: 0.9em; font-size: 0.9em;
letter-spacing: 0.1em; letter-spacing: 0.1em;
margin: 0;
margin-top: 1em; margin-top: 1em;
padding-top: 1em; padding-top: 1em;
} }
@ -285,4 +257,3 @@ a:active, a:hover {
from { opacity: 1; } from { opacity: 1; }
to { opacity: 0; } to { opacity: 0; }
} }

View File

@ -1,26 +1,11 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<title>{{playlist['name']}} - Groove On Demand</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="user-scalable=no">
<meta name="description" content="{{playlist['name']}}: {{playlist['description']}}"> <title>Groove On Demand</title>
<meta name="og:title" content="{{playlist['name']}}">
<meta name="og:description" content="{{playlist['description']}}">
<meta name="og:url" content="{{playlist['url']}}">
<meta name="og:type" content="audio">
<meta name="og:provider_name" content="Groove on Demand">
<meta name="og:image" content="/static/45.svg">
<link rel='stylesheet' href='/static/styles.css' /> <link rel='stylesheet' href='/static/styles.css' />
<link rel='stylesheet' href="https://fonts.cdnfonts.com/css/clarendon-mt-std" /> <link rel='stylesheet' href="https://fonts.cdnfonts.com/css/clarendon-mt-std" />
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
<script defer crossorigin src='https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.3/howler.core.min.js'></script> <script defer crossorigin src='https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.3/howler.core.min.js'></script>
<script defer src='/static/player.js'></script> <script defer src='/static/player.js'></script>
<script> <script>
@ -51,9 +36,9 @@
<tr> <tr>
<td id='controls'> <td id='controls'>
<div id='big_button' class='btn'> <div id='big_button' class='btn'>
<div id="loading"></div> <div id="loading"></div>
<div id="playBtn"></div> <div id="playBtn"></div>
<div id="pauseBtn"></div> <div id="pauseBtn"></div>
</div> </div>
</td> </td>
<td> <td>
@ -74,7 +59,7 @@
<div id="playlist"> <div id="playlist">
<div id="list"></div> <div id="list"></div>
</div> </div>
<div id='footer'>groove on demand : an <a alt="evilchili at liner notes dot club" href="https://linernotes.club/@evilchili">@evilchili</a> jam</div> <div id='footer'>groove on demand : an <a alt="evilchili at liner notes dot club" href="https://linernotes.club/@evilchili">@evilchili</a> jam</div>
</div> </div>
</div> </div>
</body> </body>

View File

@ -9,7 +9,7 @@ def encode(args: List, uri: str) -> str:
Encode a request and cryptographically sign it. This serves two purposes: Encode a request and cryptographically sign it. This serves two purposes:
First, it enables the handler that receives the request to verify that the First, it enables the handler that receives the request to verify that the
request was meant for it, preventing various routing and relay-induced bugs. request was meant for it, preventing various routing and relay-induced bugs.
Second, it ensures the request wasn't corrupted or tampered with during Second, it ensures the request wasn't corrutped or tampered with during
transmission. transmission.
Args: Args:

View File

@ -78,10 +78,8 @@ def serve_track(request, track_id, db):
except (NoResultFound, MultipleResultsFound): except (NoResultFound, MultipleResultsFound):
return HTTPResponse(status=404, body="Not found") return HTTPResponse(status=404, body="Not found")
path = groove.path.transcoded_media(track['relpath']) path = groove.path.media(track['relpath'])
if not path.exists(): logging.debug(f"Service track {path.name} from {path.parent}")
path = groove.path.media(track['relpath'])
logging.debug(f"Serving track {path.name}")
return static_file(path.name, root=path.parent) return static_file(path.name, root=path.parent)

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "grooveondemand" name = "grooveondemand"
version = "0.10" version = "0.9"
description = "audio playlist server" description = "audio playlist server"
authors = ["evilchili <evilchili@gmail.com>"] authors = ["evilchili <evilchili@gmail.com>"]
license = "MIT License" license = "MIT License"

View File

@ -5,8 +5,7 @@ from unittest.mock import MagicMock
from sqlalchemy import func from sqlalchemy import func
import groove.exceptions import groove.exceptions
from groove.media import scanner from groove.db import scanner, track
from groove.db import track
def test_scanner(monkeypatch, in_memory_db): def test_scanner(monkeypatch, in_memory_db):