Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Niklas20114552

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and to permit
persons to whom the Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# No main release available.
# Deezium

Please see the ```dev``` branch
An alternative open-source lightweight graphical front-end for [Deezer](https://deezer.com).

## Currently early release!

This application is currently in an early release state!
Not every important feature of the official deezer app is already present.

Also not every button does something currently.

## How to install

**Currently you don't**, if you don't like to tinker arround.

As this is in alpha state, I'm not providing installation instructions.
Binary file added deezer_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions deezium.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Desktop Entry]
Type=Application
Name=Deezium
Exec=deezium
Path=/usr/share/deezium
Terminal=false
Hidden=false
Icon=/usr/share/deezium/deezium.png
Keywords=deezer;music;streaming
Categories=Audio
Comment=An alternative lightweight graphical front-end for Deezer
Binary file added deezium.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added deezium128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added deezium256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
222 changes: 222 additions & 0 deletions deezium_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
from importlib.util import spec_from_file_location, module_from_spec
import platform
import sys
import deezer
import os as _os
import requests
import numpy as _numpy
import cv2 as _cv2
import math as _math
from glob import glob as _glob

if platform.system() == 'Windows':
APP_DATA_PATH: str = "C:\\Program Files\\Deezium\\"
elif platform.system() == 'Linux':
APP_DATA_PATH: str = '/usr/share/deezium/'
else:
print('Sorry, but your Operating System is not supported.')
sys.exit()

import_spec = spec_from_file_location('deezloader2', APP_DATA_PATH + 'deezloader2.py')
deezloader2 = module_from_spec(import_spec)
sys.modules['deezloader2'] = deezloader2
import_spec.loader.exec_module(deezloader2)

MprisAppAdapter = None
try:
from mpris_server.adapters import MprisAdapter
from mpris_server.events import EventAdapter
from mpris_server.server import Server
from mpris_server import Metadata

class MprisAppAdapter(MprisAdapter):
def __init__(self, window):
super().__init__()
self.window = window
except ImportError:
print('[D> Mpris_server not found')
finally:
print('[D> Mpris_server found')


def _sectime(time: int) -> str:
if time <= 9:
return f'0{time}'
else:
return str(time)


def convert_sec_to_min(sec):
ms = sec * 1000
return ms_to_str(ms)


def _rgb_to_hex(rgb: tuple) -> str: return "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2])


def get_oauth_token():
_os.makedirs(_os.path.expanduser('~/.config/deezium'), exist_ok=True)
if _os.path.exists(_os.path.expanduser('~/.config/deezium/aro.dat')):
with open(_os.path.expanduser('~/.config/deezium/aro.dat'), 'r') as f:
return f.read()
return False


def gen_oauth_token(datapath: str, forced: bool = False):
_os.makedirs(_os.path.expanduser('~/.config/deezium'), exist_ok=True)
if (not _os.path.exists(_os.path.expanduser('~/.config/deezium/aro.dat'))) or forced:
_os.system(f'python{'3' if platform.system() != 'Windows' else ''} "{datapath}oauth.py"')


def get_login_token():
_os.makedirs(_os.path.expanduser('~/.config/deezium'), exist_ok=True)
if _os.path.exists(_os.path.expanduser('~/.config/deezium/arl.dat')):
with open(_os.path.expanduser('~/.config/deezium/arl.dat'), 'r') as f:
return f.read()
return False


def download_track(login: deezloader2.Login2, id, quality="MP3_128") -> str:
fmat = '.mp3'
if quality == 'FLAC':
fmat = '.flac'
_os.makedirs(_os.path.expanduser('~/.cache/deezium'), exist_ok=True)
if _os.path.exists(_os.path.expanduser('~/.cache/deezium/') + f'{id}{fmat}'):
return _os.path.expanduser('~/.cache/deezium/') + f'{id}{fmat}'
path = login.download_trackdee(f"https://www.deezer.com/track/{id}", quality=quality, output=_os.path.expanduser('~/.cache/deezium'))
return path


def clean_trackids(ids):
for id in ids:
for file in _glob(_os.path.expanduser(f'~/.cache/deezium/{id}.*')):
_os.remove(file)


def clean_albumcovers(ids, size: str):
for id in ids:
file = _os.path.expanduser(f'~/.cache/deezium/cover_as{size}/{id}')
_os.remove(file)


def get_cached_tracks() -> list[int]:
try:
ids = []
for file in _glob(_os.path.expanduser(f'~/.cache/deezium/*.*')):
if file.endswith('.mp3'):
ids.append(int(file.removesuffix('.mp3').split('/')[-1]))
elif file.endswith('.flac'):
ids.append(int(file.removesuffix('.flac').split('/')[-1]))
return ids
except FileNotFoundError:
return []


def logout():
if _os.path.exists(_os.path.expanduser('~/.config/deezium/arl.dat')):
_os.remove(_os.path.expanduser('~/.config/deezium/arl.dat'))
if _os.path.exists(_os.path.expanduser('~/.config/deezium/aro.dat')):
_os.remove(_os.path.expanduser('~/.config/deezium/aro.dat'))


def get_cached_albumcovers(size: str) -> list[int]:
try:
files = _os.listdir(_os.path.expanduser(f'~/.cache/deezium/cover_as{size}'))
ids = []
for file in files:
ids.append(int(file))
return ids
except FileNotFoundError:
return []


def download_albumcover_s(login: deezer.Client, albumid: int) -> bytes:
_os.makedirs(_os.path.expanduser('~/.cache/deezium/cover_ass'), exist_ok=True) # "Album Size Small" not the human thingy
if _os.path.exists(_os.path.expanduser('~/.cache/deezium/cover_ass/') + str(albumid)):
with open(_os.path.expanduser('~/.cache/deezium/cover_ass/') + str(albumid), 'rb') as f:
return f.read()
album = login.get_album(albumid)
content = requests.get(album.cover_small).content
with open(_os.path.expanduser('~/.cache/deezium/cover_ass/') + str(albumid), 'wb') as f:
f.write(content)
return content


def download_albumcover_m(login: deezer.Client, albumid: int, getpath: bool = False):
_os.makedirs(_os.path.expanduser('~/.cache/deezium/cover_asm'), exist_ok=True)
if _os.path.exists(_os.path.expanduser('~/.cache/deezium/cover_asm/') + str(albumid)):
if getpath:
return _os.path.expanduser('~/.cache/deezium/cover_asm/') + str(albumid)
else:
with open(_os.path.expanduser('~/.cache/deezium/cover_asm/') + str(albumid), 'rb') as f:
return f.read()
album = login.get_album(albumid)
content = requests.get(album.cover_medium).content
with open(_os.path.expanduser('~/.cache/deezium/cover_asm/') + str(albumid), 'wb') as f:
f.write(content)
if getpath:
return _os.path.expanduser('~/.cache/deezium/cover_asm/') + str(albumid)
else:
return content


def download_playlcover_m(login: deezer.Client, plid: int, getpath: bool = False):
_os.makedirs(_os.path.expanduser('~/.cache/deezium/cover_psm'), exist_ok=True)
if _os.path.exists(_os.path.expanduser('~/.cache/deezium/cover_psm/') + str(plid)):
if getpath:
return _os.path.expanduser('~/.cache/deezium/cover_psm/') + str(plid)
else:
with open(_os.path.expanduser('~/.cache/deezium/cover_psm/') + str(plid), 'rb') as f:
return f.read()
pl = login.get_playlist(plid)
content = requests.get(pl.picture_medium).content
with open(_os.path.expanduser('~/.cache/deezium/cover_psm/') + str(plid), 'wb') as f:
f.write(content)
if getpath:
return _os.path.expanduser('~/.cache/deezium/cover_psm/') + str(plid)
else:
return content


def calc_background_color(data: bytes) -> str:
numpyarr = _numpy.frombuffer(data, _numpy.uint8)
img = _cv2.imdecode(numpyarr, _cv2.IMREAD_COLOR)
height, width, _ = _numpy.shape(img)
avg_color_per_row = _numpy.average(img, axis=0)
avg_colors = _numpy.average(avg_color_per_row, axis=0)
avg_colors[0] -= 100
colors = []
for o in avg_colors:
oi = int(o)
oi -= 25
if oi < 0: oi = 0
colors.append(oi)
return _rgb_to_hex(colors)


def calc_foreground_color(hexstr: str) -> str:
rgb = tuple(int(hexstr.removeprefix('#')[i:i+2], 16) for i in (0, 2, 4))
if (rgb[0] and rgb[1] and rgb[2]) >= 150:
return '#000000'
return '#FFFFFF'


def ms_to_str(ms: int) -> str:
sec = round(ms / 1000)
if sec >= 3600:
hr = _math.floor(sec / 3600)
r = _math.floor(sec % 3600)
min = _math.floor(r / 60)
rsec = _math.floor(r % 60)
return f'{hr}:{_sectime(min)}:{_sectime(rsec)}'
else:
min = _math.floor(sec / 60)
rsec = _math.floor(sec % 60)
return f'{_sectime(min)}:{_sectime(rsec)}'


def conv_paginated_ids(plist: deezer.PaginatedList) -> list[int]:
ids = []
for track in plist:
ids.append(track.id)
return ids
Loading