This commit is contained in:
Fusselkater 2022-11-11 06:23:00 +01:00
parent 7b6ae717ab
commit c039692fa4
5 changed files with 168 additions and 1 deletions

1
.gitignore vendored
View file

@ -14,7 +14,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/

55
lib/jamendo.py Normal file
View file

@ -0,0 +1,55 @@
from enum import Enum
from typing import Literal
import requests
import urllib.parse
API_BASE = 'https://api.jamendo.com/v3.0/'
ImageSize = Literal[25, 35, 50, 55, 60, 65, 70, 75, 85, 100, 130, 150, 200, 300, 400, 500, 600]
AudioFormat = Literal['mp31', 'mp32', 'ogg', 'flac']
VocalInstrumental = Literal['vocal', 'instrumental']
AcousticElectric = Literal['acoustic', 'electric']
Order = Literal['relevance', 'buzzrate', 'downloads_week', 'downloads_month', 'downloads_total', 'listens_week', 'listens_month', 'listens_total', 'popularity_week', 'popularity_month', 'popularity_total', 'name', 'album_name', 'artist_name', 'releasedate', 'duration', 'id']
Speed = Literal['verylow', 'low', 'medium', 'high', 'veryhigh']
class JamendoClient:
def __init__(self, client_id: str):
self._client_id = client_id
self._session = requests.session()
def tracks(self, tags: str, offset: int = 0, order: Order = 'relevance',
audioformat: AudioFormat = 'ogg', imagesize: ImageSize = 600,
vocalinstrumental: VocalInstrumental = None,
acousticelectric: AcousticElectric = None,
speed: Speed = None, ccsa: bool = False,
ccnd: bool = False, ccnc: bool = False,
):
params = {
'client_id': self._client_id,
'offset': offset,
'format': 'json',
'tags': urllib.parse.quote(tags),
'audioformat': audioformat,
'imagesize': imagesize,
'ccsa': ccsa,
'ccnd': ccnd,
'ccnc': ccnc,
'limit': 5
}
if vocalinstrumental: params['vocalinstrumental'] = urllib.parse.quote(vocalinstrumental)
if acousticelectric: params['acousticelectric'] = urllib.parse.quote(acousticelectric)
if speed: params['speed'] = urllib.parse.quote(speed)
url = urllib.parse.urljoin(API_BASE, f'tracks/?{urllib.parse.urlencode(params)}')
with self._session.get(url) as r:
r.raise_for_status()
result = r.json()
if 'results' in result:
return result['results']
def download(self, url: str, fp):
with self._session.get(url, stream=True) as r:
r.raise_for_status()
for chunk in r.iter_content(chunk_size=1024):
fp.write(chunk)

65
lib/player.py Normal file
View file

@ -0,0 +1,65 @@
import threading
from queue import Queue, Empty
import miniaudio
import logging
from lib.renderer import Renderer
class Player:
def __init__(self, renderer: Renderer, play_finished_handler):
self._queue = Queue()
self._next_event = threading.Event()
self._exit_event = threading.Event()
self._logger = logging.getLogger('Jamendo Player')
self._renderer = renderer
self._play_finished_handler = play_finished_handler
threading.Thread(target=self.worker, args=()).start()
def worker(self):
while True:
try:
item = self._queue.get(block=True, timeout=1)
if item:
self._renderer.render(item['cover_file'], item['artist'], item['album'], item['title'])
self._logger.info('playing {artist}, {album}, {title}...'.format(artist=item['artist'], album=item['album'], title=item['title']))
file_stream = miniaudio.stream_file(item['audio_file'])
callback_stream = miniaudio.stream_with_callbacks(file_stream, end_callback=self.stream_end_callback)
next(callback_stream)
with miniaudio.PlaybackDevice() as device:
device.start(callback_stream)
self._next_event.wait()
self._next_event.clear()
self._queue.task_done()
self._play_finished_handler(item['audio_file'], item['cover_file'])
except Empty:
pass
if self._exit_event.is_set():
break
def stream_end_callback(self):
self.next()
def add_file(self, audio_file, artist: str, album: str, title: str, cover_file):
self._queue.put(
{
'audio_file': audio_file,
'artist': artist,
'album': album,
'title': title,
'cover_file': cover_file
}
)
@property
def queue_size(self):
return self._queue.qsize()
def next(self):
self._next_event.set()
def quit(self):
self._exit_event.set()
self.next()
while not self._queue.empty():
item = self._queue.get()
self._play_finished_handler(item['audio_file'], item['cover_file'])

44
lib/png_renderer.py Normal file
View file

@ -0,0 +1,44 @@
from lib.renderer import Renderer
from PIL import Image, ImageDraw, ImageFont, ImageFile
class PNG_Renderer(Renderer):
def __init__(self, config):
self._config = config
ImageFile.LOAD_TRUNCATED_IMAGES = True
def render(self, cover, artist, album, title):
title_font = ImageFont.truetype(font=self._config['TitleFont'], size=int(self._config['TitleFontSize']))
title_y = 0
title_height = title_font.getbbox('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')[3]
text_width = title_font.getbbox(title)[2]
artist_font = ImageFont.truetype(font=self._config['ArtistFont'], size=int(self._config['ArtistFontSize']))
artist_y = title_y + title_height + 5
artist_height = artist_font.getbbox('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')[3]
if artist_font.getbbox(artist)[2] > text_width: text_width = artist_font.getbbox(artist)[2]
album_font = ImageFont.truetype(font=self._config['AlbumFont'], size=int(self._config['AlbumFontSize']))
album_y = artist_y + artist_height + 2
album_height = album_font.getbbox('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')[3]
if album_font.getbbox(album)[2] > text_width: text_width = album_font.getbbox(album)[2]
image_height = album_y + album_height + 10
image_width = text_width + image_height + 40
image = Image.new('RGBA', (image_width, image_height), color='#' + self._config['BackgroundColor'])
draw = ImageDraw.Draw(image)
text_x = 0
if self._config['Cover'] == '1':
cover = Image.open(cover)
cover.thumbnail((image_height, image_height), Image.ANTIALIAS)
image.paste(cover)
text_x = image_height + 20
draw.text((text_x,title_y), title, font=title_font, fill='#' + self._config['TitleFontColor'])
draw.text((text_x,artist_y), artist, font=artist_font, fill='#' + self._config['TitleFontColor'])
draw.text((text_x,album_y), album, font=album_font, fill='#' + self._config['TitleFontColor'])
image.save(self._config['OutputPath'])

4
lib/renderer.py Normal file
View file

@ -0,0 +1,4 @@
class Renderer:
def render(self, cover: str, artist: str, album: str, title: str):
pass