Adding transcoder

This commit is contained in:
evilchili 2022-12-31 12:28:07 -08:00
parent 17a6dcb4d2
commit f055511fe6
6 changed files with 169 additions and 5 deletions

View File

@ -110,6 +110,13 @@ 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.

0
groove/media/__init__.py Normal file
View File

139
groove/media/transcoder.py Normal file
View File

@ -0,0 +1,139 @@
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
from groove.exceptions import ConfigurationError
@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):
raise ConfigurationError("Cannot transcode tracks without a TRANSCODR defined in your environment.")
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

@ -32,10 +32,6 @@ def cache_root():
if not path: if not path:
raise ConfigurationError(f"CACHE_ROOT is not defined in your environment.\n\n{_setup_hint}") raise ConfigurationError(f"CACHE_ROOT is not defined in your environment.\n\n{_setup_hint}")
path = Path(path).expanduser() path = Path(path).expanduser()
if not path.exists() or not path.is_dir():
raise ConfigurationError(
"The cache_root directory (CACHE_ROOT) doesn't exist, or isn't a directory.\n\n{_setup_hint}"
)
logging.debug(f"Media cache root is {path}") logging.debug(f"Media cache root is {path}")
return path return path
@ -46,7 +42,7 @@ def media(relpath):
def transcoded_media(relpath): def transcoded_media(relpath):
path = cache_root() / Path(relpath) path = cache_root() / Path(relpath + '.webm')
return path return path

View File

@ -2,6 +2,7 @@ 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.media.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
@ -90,6 +91,26 @@ 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. See also [b]cache[/b].
[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,6 +18,7 @@ help = #999999
background = #001321 background = #001321
info = #88FF88 info = #88FF88
debug = #888888
error = #FF8888 error = #FF8888
danger = #FF8888 danger = #FF8888