Added tidal as a service
This commit is contained in:
parent
efeb0fbec6
commit
d0491d8dd0
@ -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
|
||||||
|
|
||||||
|
@ -10,3 +10,4 @@ pyinstaller
|
|||||||
isodate
|
isodate
|
||||||
configobj
|
configobj
|
||||||
winpaths
|
winpaths
|
||||||
|
tidalapi
|
@ -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")
|
@ -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()
|
||||||
|
@ -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
76
src/extractors/tidal.py
Normal 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)
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user