Added tidal as a service

This commit is contained in:
Manuel Cortez 2019-06-12 17:44:45 -05:00
parent efeb0fbec6
commit d0491d8dd0
7 changed files with 98 additions and 5 deletions

View File

@ -1,5 +1,11 @@
## Changelog ## Changelog
## Version 0.6
* Added a new and experimental extractor for supporting tidal.
* Take into account that this extractor requires you to have a paid account on tidal. Depending in the account level, you will be able to play and download music in high quality or lossless audio. MusicDL will handle both, though at the current moment, only downloading of lossless audio is implemented.
* There is a new search mode supported in this service. You can retrieve all work for a certain artist by using the protocol artist://, plus the name of the artist you want to retrieve. For example, artist://The beatles will retrieve everything made by the beatles available in the service. The search results will be grouped by albums, compilations and singles, in this order.
## Version 0.4 ## Version 0.4
* Fixed an error when creating a directory located in %appdata%, when using MusicDL as an installed version. MusicDL should be able to work normally again. * Fixed an error when creating a directory located in %appdata%, when using MusicDL as an installed version. MusicDL should be able to work normally again.
@ -7,7 +13,7 @@
* MusicDL will no longer set volume at 50% when it starts. It will save the volume in a settings file, so it will remember volume settings across restarts. * MusicDL will no longer set volume at 50% when it starts. It will save the volume in a settings file, so it will remember volume settings across restarts.
* Added an option in the help menu to report an issue. You can use this feature for sending reports of problems you have encountered while using the application. You will need to provide your email address, though it will not be public anywhere. Your email address will be used only for contacting you if necessary. * Added an option in the help menu to report an issue. You can use this feature for sending reports of problems you have encountered while using the application. You will need to provide your email address, though it will not be public anywhere. Your email address will be used only for contacting you if necessary.
* changes in Youtube module: * changes in Youtube module:
* Updated YoutubeDL to version 2018.10.05 * Updated YoutubeDL to latest version.
## Version 0.3 ## Version 0.3

View File

@ -10,3 +10,4 @@ pyinstaller
isodate isodate
configobj configobj
winpaths winpaths
tidalapi

View File

@ -1,2 +1,8 @@
[main] [main]
volume = integer(default=50) volume = integer(default=50)
[services]
[[tidal]]
username = string(default="")
password = string(default="")
quality=string(default="lossless")

View File

@ -96,7 +96,7 @@ class Controller(object):
# Event functions. These functions will call other functions in a thread and are bound to widget events. # Event functions. These functions will call other functions in a thread and are bound to widget events.
def on_search(self, *args, **kwargs): def on_search(self, *args, **kwargs):
utils.call_threaded(self.search) wx.CallAfter(self.search)
def on_activated(self, *args, **kwargs): def on_activated(self, *args, **kwargs):
self.on_play() self.on_play()

View File

@ -1,3 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
import config
from . import mailru, youtube, zaycev from . import mailru, youtube, zaycev
# conditional imports
if config.app["services"]["tidal"]["username"] != "" and config.app["services"]["tidal"]["password"] != "":
from . import tidal

76
src/extractors/tidal.py Normal file
View File

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
import logging
import tidalapi
import config
from .import baseFile
from update.utils import seconds_to_string
log = logging.getLogger("extractors.tidal.com")
class interface(object):
name = "tidal"
def __init__(self):
self.results = []
self.needs_transcode = False
log.debug("started extraction service for {0}".format(self.name,))
if hasattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"]):
quality = getattr(tidalapi.Quality, config.app["services"]["tidal"]["quality"])
else:
quality = tidalapi.Quality.high
_config = tidalapi.Config(quality=quality)
username = config.app["services"]["tidal"]["username"]
password = config.app["services"]["tidal"]["password"]
log.debug("Using quality: %s" % (quality,))
self.session = tidalapi.Session(config=_config)
self.session.login(username=username, password=password)
def search(self, text, page=1):
if text == "" or text == None:
raise ValueError("Text must be passed and should not be blank.")
log.debug("Retrieving data from Tidal...")
fieldtypes = ["artist", "album", "playlist"]
for i in fieldtypes:
if text.startswith(i+"://"):
field = i
text = text.replace(i+"://", "")
log.debug("Searching for %s..." % (field))
search_response = self.session.search(value=text, field=field)
self.results = []
if field == "tracks":
data = search_response.tracks
elif field == "artist":
data = []
artist = search_response.artists[0].id
albums = self.session.get_artist_albums(artist)
for album in albums:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
compilations = self.session.get_artist_albums_other(artist)
for album in compilations:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
singles = self.session.get_artist_albums_ep_singles(artist)
for album in singles:
tracks = self.session.get_album_tracks(album.id)
for track in tracks:
data.append(track)
for search_result in data:
s = baseFile.song(self)
s.title = search_result.name
s.artist = search_result.artist.name
s.duration = seconds_to_string(search_result.duration)
s.url = search_result.id
self.results.append(s)
log.debug("{0} results found.".format(len(self.results)))
def get_download_url(self, url):
url = self.session.get_media_url(url)
if url.startswith("https://") or url.startswith("http://") == False:
url = "rtmp://"+url
return url
def format_track(self, item):
return "{title}. {artist}. {duration}".format(title=item.title, duration=item.duration, artist=item.artist)

View File

@ -45,7 +45,7 @@ class mainWindow(wx.Frame):
box.Add(lbl2, 0, wx.GROW) box.Add(lbl2, 0, wx.GROW)
box.Add(self.text, 1, wx.GROW) box.Add(self.text, 1, wx.GROW)
box.Add(wx.StaticText(self.panel, wx.NewId(), _(u"Search in")), 0, wx.GROW) box.Add(wx.StaticText(self.panel, wx.NewId(), _(u"Search in")), 0, wx.GROW)
self.extractor = wx.ComboBox(self.panel, wx.NewId(), choices=["youtube", "mail.ru", "zaycev.net"], value="youtube", style=wx.CB_READONLY) self.extractor = wx.ComboBox(self.panel, wx.NewId(), choices=["youtube", "tidal", "mail.ru", "zaycev.net"], value="youtube", style=wx.CB_READONLY)
box.Add(self.extractor, 1, wx.GROW) box.Add(self.extractor, 1, wx.GROW)
self.search = wx.Button(self.panel, wx.NewId(), _(u"Search")) self.search = wx.Button(self.panel, wx.NewId(), _(u"Search"))
self.search.SetDefault() self.search.SetDefault()