Jump to content
Cheater

[RST] RST Tidal MP4 Downloader by Cheater

Recommended Posts

Salutare Guys,

 

Cum se mai intampla sa te plictisesti in concediu, am facut azi, un script in python cu care poti descarca melodii de pe Tidal (https://tidal.com).

Tidal este un serviciu de streaming online asemanator Spotify, doar ca are o calitate net superioara: HI-FI - 44.1 kHz/16 bit flac si mp4 si Master - 96 kHz/24 bit (MQA) - flac .

Daca ai niste scule decente (fie ca e vorba de casti sau un sistem) e must have!

 

Povestea a inceput de la nevoia de a descarca niste melodii pentru a le asculta offline, si cum nu am gasit nimic functional, am decis sa scriu eu aceasta aplicatie.

Si m-am gandit sa impartasesc cu voi!

 

Dependinte (e posibil sa imi fi scapat cateva):
pip install tidalapi Unidecode
ffmpeg - trebuie sa fie in system path

 

Testand jucaria am gasit un bug in tidalapi: in cazul in care o melodie nu are relese date, va crapa, este un caz extrem de rar, si se intampla inspecial la unele melodii vechi. Daca iti place doar muzica noua si foarte comerciala cu siguranta nu ai neaparat nevoie de acest fix.
# fix in tidalapi:
# edit __init__.py
# from line 224
# change:
    # if 'releaseDate' in json_obj:
    #     try:
    #         kwargs['release_date'] = datetime.datetime(*map(int, json_obj['releaseDate'].split('-')))
    #     except ValueError:
    #         pass
    # return Album(**kwargs)
# with:
    # if 'releaseDate' in json_obj:
    #     if json_obj['releaseDate'] is None:
    #         json_obj['releaseDate'] = '2008-10-14'
    #     try:
    #         kwargs['release_date'] = datetime.datetime(*map(int, json_obj['releaseDate'].split('-')))
    #     except ValueError:
    #         pass
    # return Album(**kwargs)

 

Salvati codul intr-un fisier .py, si rulati-l. Apropo, caile catre fisierele salvate/creerea de directoare este hardcodata in format *nix, deci nu va asteptati sa va mearga pe windoza fara mici finisaje.

Este scris si testat in Python 2,7 (defaultul la macOS Majave) dar am cautat sa il tin compatibil si cu Python 3.x (sper ca mi-a iesit).

# -*- coding: utf-8 -*-

# RST Tidal MP4 Downloader by Cheater v1.0 (https://rstforums.com)
# All tracks will be download in PCM (MPEG AAC Audio coded, MP4A) at 44100 Hz/16 bits, 320 kbps and stored in MP4 container

# requirements:
# pip install tidalapi Unidecode
# ffmpeg

# tidalapi has a bug, so if some album/playlist contains one song with no date, it will exit, this is very rare, however, there is some workaround.
# fix in tidalapi:
# edit __init__.py
# from line 224
# change:
    # if 'releaseDate' in json_obj:
    #     try:
    #         kwargs['release_date'] = datetime.datetime(*map(int, json_obj['releaseDate'].split('-')))
    #     except ValueError:
    #         pass
    # return Album(**kwargs)
# with:
    # if 'releaseDate' in json_obj:
    #     if json_obj['releaseDate'] is None:
    #         json_obj['releaseDate'] = '2008-10-14'
    #     try:
    #         kwargs['release_date'] = datetime.datetime(*map(int, json_obj['releaseDate'].split('-')))
    #     except ValueError:
    #         pass
    # return Album(**kwargs)

import tidalapi
import os
import subprocess
import errno
import shlex
from aigpy.cmdHelper import myinput
from subprocess import Popen, PIPE, STDOUT
from random import randint
from time import sleep
import unidecode

# compatibility workaround for py27/py3
try:
    from subprocess import DEVNULL # py3k
except ImportError:
    import os
    DEVNULL = open(os.devnull, 'wb')

# fill this with your tidal user and pass
tidalUser = ''
tidalPass = ''


cwd = os.getcwd()

config = tidalapi.Config()
# using HIGH quality in order to get mp4's unencrypted url instead of enctyped flac
config.quality = 'HIGH'

session = tidalapi.Session(config)
session.login(tidalUser, tidalPass)

def getTidalTrackUrl(track_id):
	try:
		url = session.get_media_url(track_id)
		return url
	except:
		# in case we need to retry we add a random sleep, in order to worckaround bot detection
		sleep(randint(1,10))
		print('Tidal responds with 401. Retrying track url discovery for track id: ' + str(track_id))
		generatePlaylistTrackUrl(track_id)

def downloadAlbum():
	while True:
		print("----------------ALBUM------------------")
		sID = myinput("Enter AlbumID(Enter '0' go back) :")
		if sID == '0':
		    return
		tracks = session.get_album_tracks(album_id=sID)

		queue = []

		for track in tracks:
		    trackNo = str(tracks.index(track) + 1)
		    # don't try to download unavailable track, it will fail
		    if track.available is False:
		    	continue
		    # replace utf-8 diacritics with ascii equivalent, and cleanup " and ' from album/artist/track name
		    trackName = unidecode.unidecode(track.name).replace('"', '').replace("'", "")
		    artistName = unidecode.unidecode(track.artist.name).replace('"', '').replace("'", "")
		    albumName = unidecode.unidecode(track.album.name).replace('"', '').replace("'", "")
		    print('Adding to queue: ' + artistName + ' - ' + albumName + ' - ' + trackNo + '.' + trackName)

		    # create dw directory and subdirs if it not exits
		    if not os.path.exists(cwd + '/tidalDownloaded/' + albumName):
		    	os.makedirs(cwd + '/tidalDownloaded/' + albumName)

		    cmd = 'ffmpeg -y -i "rtmp://' + getTidalTrackUrl(track.id) + '" -acodec copy "' + cwd + '/tidalDownloaded/' + albumName + '/' + trackNo + '.' + artistName + ' - ' + trackName + '.mp4"'
		    queue.append(cmd)

		print('All track has been added to queue successfully. Download begins....')
		processes = []
		for cmd in queue:

		    p = subprocess.Popen(shlex.split(cmd), shell=False, universal_newlines=True, stdout=DEVNULL, stderr=subprocess.STDOUT)
		    processes.append(p)

		print('All tracks download is in progress. Please wait....')

		# wait for all started ffmpeg processes to be finished
		for p in processes:
		    if p.wait() != 0:
		        print("There was an error")

		print("Finished. All tracks has been download successfully!")
	return True


def downloadPlaylist():
	while True:
		print("----------------PlayList------------------")
		sID = myinput("Enter PlayList(Enter '0' go back) :")
		if sID == '0':
		    return

		playlist = session.get_playlist(playlist_id=sID)

		tracks = session.get_playlist_tracks(playlist_id=sID)

		
		queue = []

		for track in tracks:
		    trackNo = str(tracks.index(track) + 1)

		    # don't try to download unavailable track, it will fail
		    if track.available is False:
		    	continue

		    # replace utf-8 diacritics with ascii equivalent, and cleanup " and ' from playlist/artist/track name
		    playlistName = unidecode.unidecode(playlist.name).replace('"', '').replace("'", "")
		    trackName = unidecode.unidecode(track.name).replace('"', '').replace("'", "")
		    artistName = unidecode.unidecode(track.artist.name).replace('"', '').replace("'", "")
		    print('Adding to queue: ' + playlistName + ' - ' + trackNo + '.' + artistName + ' - ' + trackName)

		    # create dw directory and subdirs if it not exits
		    if not os.path.exists(cwd + '/tidalDownloaded/' + playlistName):
		    	os.makedirs(cwd + '/tidalDownloaded/' + playlistName)

		    cmd = 'ffmpeg -y -i "rtmp://' + getTidalTrackUrl(track.id) + '" -acodec copy "' + cwd + '/tidalDownloaded/' + playlistName + '/' + trackNo + '.' + artistName + ' - ' + trackName + '.mp4"'
		    queue.append(cmd)

		print('All track has been added to queue successfully. Download begins....')
		processes = []
		for cmd in queue:

		    p = subprocess.Popen(shlex.split(cmd), shell=False, universal_newlines=True, stdout=DEVNULL, stderr=subprocess.STDOUT)
		    processes.append(p)

		print('All tracks download is in progress. Please wait....')

		# wait for all started ffmpeg processes to be finished
		for p in processes:
		    if p.wait() != 0:
		        print("There was an error")

		print("Finished. All tracks has been download successfully!")
	return True

while True:
    print(" RST Tidal MP4 Downloader by Cheater v1.0 (https://rstforums.com)")
    print("=====================Choice=========================")
    print(" Enter '0' : Exit")
    print(" Enter '1' : Download Album.")
    print(" Enter '2' : Download PlayList.")
    print("====================================================")
    print("All tracks will be download in PCM (MPEG AAC Audio coded, MP4A) at 44100 Hz/16 bits, 320 kbps and stored in MP4 container")
    choice = myinput("Choice:")
    if choice == '0':
    	quit()
    elif choice == '1':
        downloadAlbum()
    elif choice == '2':
        downloadPlaylist()


 

Ce stie sa faca?

1. Poti descarca un album

2. Poti descarca un playlist

3. Adauga melodiile intr-o coada, si le descarca simultan pentru a scurta timpul de asteptare semnificativ.

 

Daca aveti intrebari sau nu va descurcati puteti scrie aici, si va voi ajuta in limita timpului disponibil (adica sper sa nu fie nevoie :))) ).

 

PS: Fiti blanzi cu code review, sunt programator si python nu este specialitatea mea, este al 2-lea script scris in python si prima interactiune am avut-o in decembrie.

PS2: Distractie si La multi ani! 

PS3: Feel free to improve it!

Later: Am gasit un tool functional de download scris de altcineva (daca il gaseam mai repede probabil ca nu il mai scriam eu pe asta, deci nu e neaparat bine): https://github.com/redsudo/RedSea acest tool spre deosebire de ce am scris eu, stie de si decripteze flacurile, astfel poate descarca inclusiv MQA de 92k / 24bit (cea mai intalta calitate disponibila pe tidal), si flac  44.1k / 16bit cu un bitrate de 1.411 kbps. Decriptarea nu e rocket sience, dar cu siguranta a fost nevoie de un reverse engineering serios pentru aflarea algoritmului si key de criptare (AES cu key binara, tinuta in base64 in cod).

Edited by Cheater
  • Thanks 2
  • Upvote 7
Link to comment
Share on other sites

1 minute ago, Gecko said:

Sa-mi fie cu pardon daca ma bag aiurea, dar atata timp cat Tidal nu-ti ofera posibilitatea de a descarca melodiile direct din aplicatie, ceea ce faci aici e acelasi lucru cu a le descarca direct prin torrenti. Corect?

Pentru android si ios, ai optiunea de a le descarca si asculta offline, diferenta intre ce gasesti pe torrent si asta, este calitatea.

Daca te gandesti la piraterie, ma gandesc la compatibilitate, eu in continuare platesc abonamentul, si daca masina nu e compatibila si am placerea de a asculta melodiile in masina, gresesc cu ceva?

Edited by Cheater
Link to comment
Share on other sites

  • Active Members

GG pentru initiativa.

 

Vad ca ai ceva timp pe forumu' asta si eu intru din ce in ce mai rar. Am incercat sa dau c/p intr-uun IDE sa bag cateva imbunatatiri da' sa-mi bag pula daca nu mai mult m-am enervat.

 

Daca vrei sa faci ceva cum trebe' pentru forum (chiar daca spui ca ii facut asa intr-o doara in conced), fa si tu un commit pe git sau ce folosesti tu,  pune un link aici, alege o versiune de Python recenta nu ceva care o sa fie deprecated maine poimaine. Ca mai vine azi unu' cu un edit, maine altu' cu un issue, si pac te trezesti cu meleonu' de la Dragnea care iti cumpara aplicatia si o baga pe RATB.

 

Pwp & no homo :) 

  • Upvote 3
Link to comment
Share on other sites

11 hours ago, MrGrj said:

GG pentru initiativa.

 

Vad ca ai ceva timp pe forumu' asta si eu intru din ce in ce mai rar. Am incercat sa dau c/p intr-uun IDE sa bag cateva imbunatatiri da' sa-mi bag pula daca nu mai mult m-am enervat.

 

Daca vrei sa faci ceva cum trebe' pentru forum (chiar daca spui ca ii facut asa intr-o doara in conced), fa si tu un commit pe git sau ce folosesti tu,  pune un link aici, alege o versiune de Python recenta nu ceva care o sa fie deprecated maine poimaine. Ca mai vine azi unu' cu un edit, maine altu' cu un issue, si pac te trezesti cu meleonu' de la Dragnea care iti cumpara aplicatia si o baga pe RATB.

 

Pwp & no homo :) 

Daca face asta cred ca o sa fie dat jos continutul (DMCA), iar cei de la tidal vor patch-ui.

 

Sunteti mult prea dusmanosi/invidioasi. Invatati sa fiti mai buni!

  • Like 1
Link to comment
Share on other sites

4 minutes ago, Gecko said:

A intrebat daca ce face aici e sau nu piraterie. Ce am scris nu interzice cuiva sa mai pirateze, ba chiar am oferit ca alternativa sa descarce muzica de pe torrenti direct, sa nu se mai chinuie atat.

Sa nu fim mai catolici decat papa. Probabil esti apple user, iti inteleg abordarile...oarecum :) .

Link to comment
Share on other sites

11 hours ago, MrGrj said:

GG pentru initiativa.

 

Vad ca ai ceva timp pe forumu' asta si eu intru din ce in ce mai rar. Am incercat sa dau c/p intr-uun IDE sa bag cateva imbunatatiri da' sa-mi bag pula daca nu mai mult m-am enervat.

 

Daca vrei sa faci ceva cum trebe' pentru forum (chiar daca spui ca ii facut asa intr-o doara in conced), fa si tu un commit pe git sau ce folosesti tu,  pune un link aici, alege o versiune de Python recenta nu ceva care o sa fie deprecated maine poimaine. Ca mai vine azi unu' cu un edit, maine altu' cu un issue, si pac te trezesti cu meleonu' de la Dragnea care iti cumpara aplicatia si o baga pe RATB.

 

Pwp & no homo :) 

Nu intentionez sa postez jucaria in alta parte. Am facut-o pt mine si am zis sa o pun si aici, nu vreau sa o stie chiar tot netul dupa :)))

Daca nu te descurci la ceva, scrie-mi PM si o sa incerc sa te ajut.

Link to comment
Share on other sites

1 hour ago, Gecko said:

Din: https://tidal.com/us/terms#ref-content

Offline mode, adica acel feature care-ti permite sa descarci melodiile local, le descarca sub forma catorva sute/mii de fisiere mici care le face imposibil de redat in alte aplicatii. Cu alte cuvinte, nu-ti descarca fisiere MP3 sau MP4, in schimb tu le descarci asa pentru ca nu descarci fisierul lor, ci inregistrezi stream-ul prin ffmpeg. Orice astfel de incercare de copiere intra la ultima parte din quote, "circumventing the Services' technical protection system". Chiar daca o faci intr-un mediu care nu e conectat la net, tot nu e ok.

 

M-am lovit de acelasi lucru la aplicatia Audible, de unde voiam sa descarc un audiobook, dupa ce l-am cumparat, pentru a-l asculta pe un sistem de nu avea acces la net si nici posibilitatea de a instala aplicatia lor. Am luat fisierul descarcat prin aplicatie (AAX), si am descoperit ca e un container criptat, redabil doar de aplicatia lor. Tidal urmeaza aceeasi abordare, insa nu le mai pune in container, salveaza toate bucatile de streaming, criptate.

 

Cu alte cuvinte, cand platesti un abonament la un serviciu de streaming, nu obtii acces la fisierele lor media, ci doar dreptul de a le reda prin aplicatiile lor, fara sa le modifici in niciun fel. Deci, da, ce faci tu aici intra la piraterie. E simplu de inteles, daca nu exista un buton pentru a face ce vrei tu, in aplicatiile lor, iti e interzis s-o faci.

 

In alta ordine de idei, HiFi e un mod de a reda FLAC prin internet, iti livreaza fisierul FLAC la maxim 1141 kbps in loc de a-ti livra unul comprimat (OGG / MP3), la 320 kbps, cum fac Spotify, Apple Music si restul cu abonamente premium. Faptul ca tu le descarci local elimina nevoia de HiFi, pentru ca nu mai descarci nimic dupa. Daca le descarci FLAC de oriunde, le poti reda la aceeasi calitate, asa cum spuneai si tu, daca ai scule pe masura. Tidal nu reinventeaza nimic, doar foloseste libFLAC si sacrifica mai mult bandwidth pentru a incerca sa livreze FLAC direct.

 

Faptul ca ai citit intr-un advertorial Tidal ca ei livreaza muzica la alt nivel nu zice prea multe, si, sincer sa fiu, sunt surprins ca ii ridici atat in slavi doar pentru asta.

 

P.S. Spotify are un sistem mult mai bun de discovery. :D 

Vad ca te-ai documentat putin. Diferenta e ca pot sa iti descarc si flac-uri (prin link de app desktop/mobile) doar ca, sunt cum spui si tu criptate. Foloesc WidevineCDM.

Iti dau multe fisiere prin web doar. Si calitate master ai doar pe Mac/Win cu aplicatia de desktop.

Eu folosesc de cativa ani Tidal, si sunt fan, chiar face diferenta pe un sistem bun/casti bune, adica e asta si restul din punctul meu de vedere. (legat de valorile exacte le-am mentionat in primul post)

 

Da asa este Spotify are un sistem de recomandari incredibil de bine pus la punct! Asta e motivul pentru care am si Spotify :)))).

  • Upvote 1
Link to comment
Share on other sites

1 hour ago, OKQL said:

Hai ccca se face troll, zbori

 

@Cheater

API e scris de tine?

 

Nu, API nu e scris de mine, l-am gasit gata scris, si am inceput de acolo. Poate il scriam eu daca nu exista, cand m-am lovit de bugul mentionat m-am gandit sa imi scriu singur api, apoi am zis ca e in afara scopului...voiam sa ajung repede la rezultat fara prea mare bataie de cap. Este un proiect de fun, nu vreau sa investesc in el sau sa il dezvolt mai mult.

  • Upvote 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...