add lib
This commit is contained in:
parent
7b6ae717ab
commit
c039692fa4
5 changed files with 168 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,7 +14,6 @@ dist/
|
|||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
|
|
55
lib/jamendo.py
Normal file
55
lib/jamendo.py
Normal 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
65
lib/player.py
Normal 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
44
lib/png_renderer.py
Normal 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
4
lib/renderer.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
class Renderer:
|
||||
|
||||
def render(self, cover: str, artist: str, album: str, title: str):
|
||||
pass
|
Loading…
Reference in a new issue