Skip to content

Commit

Permalink
Merge pull request #26 from redsudo/develop
Browse files Browse the repository at this point in the history
Daggertooth 2.0
  • Loading branch information
redsudo authored Sep 21, 2018
2 parents ce07293 + 950d0e5 commit 44320b5
Show file tree
Hide file tree
Showing 8 changed files with 491 additions and 276 deletions.
8 changes: 1 addition & 7 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]

mutagen = "==1.31"
requests = "==2.9.1"
pycryptodome = "*"


[dev-packages]

pylint = "*"
yapf = "*"

rope = "*"

[requires]

python_version = "3.6"
3 changes: 3 additions & 0 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
'''

# BRUTEFORCEREGION: Attempts to download the track/album with all available accounts if dl fails
BRUTEFORCEREGION = True

PRESETS = {

# Default settings / only download FLAC_16
Expand Down
5 changes: 5 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ How to add accounts/sessions
redsea.py auth add
redsea.py auth remove
redsea.py auth default
redsea.py auth reauth

positional arguments:

Expand All @@ -55,6 +56,8 @@ How to add accounts/sessions
default Set a default account for redsea to use when the
-a flag has not been passed

reauth Reauthenticates with server to get new sessionId

How to use
----------
usage: redsea.py [-h] [-p PRESET] [-a ACCOUNT] [-s] urls [urls ...]
Expand Down Expand Up @@ -92,6 +95,8 @@ TODO
Config reference
----------------

`BRUTEFORCEREGION`: When True, redsea will iterate through every available account and attempt to download when the default or specified session fails to download the release

### `Stock Presets`

`default`: FLAC 44.1k / 16bit only
Expand Down
218 changes: 148 additions & 70 deletions redsea.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

from redsea.mediadownloader import MediaDownloader
from redsea.tagger import Tagger
from redsea.tidal_api import TidalApi, TidalError, TidalSessionStore
from redsea.tidal_api import TidalApi, TidalRequestError, TidalError
from redsea.sessions import RedseaSessionFile

from config.settings import PRESETS
from config.settings import PRESETS, BRUTEFORCEREGION


logo = """
LOGO = """
/$$$$$$$ /$$ /$$$$$$
| $$__ $$ | $$ /$$__ $$
| $$ \ $$ /$$$$$$ /$$$$$$$| $$ \__/ /$$$$$$ /$$$$$$
Expand All @@ -25,43 +26,45 @@
https://github.com/svbnet/RedSea
\n"""

MEDIA_TYPES = {'t': 'track', 'p': 'playlist', 'a': 'album', 'f':'album'}

def main():
# Get args
args = cli.get_args()

# Check for auth flag / session settings
sessions = TidalSessionStore('./config/sessions.pk')
RSF = RedseaSessionFile('./config/sessions.pk')
if args.urls[0] == 'auth' and len(args.urls) == 1:
print('\nThe "auth" command provides the following methods:')
print('\n list: list the currently stored sessions')
print(' add: login and store a new session')
print(' remove: permanently remove a stored session')
print(' default: set a session as default')
print(' reauth: reauthenticate with Tidal to get new sessionId')
print('\nUsage: redsea.py auth add\n')
exit()
elif args.urls[0] == 'auth' and len(args.urls) > 1:
if args.urls[1] == 'list':
sessions.list_accounts()
RSF.list_sessions()
exit()
elif args.urls[1] == 'add':
sessions.new_session()
sessions.save_session()
RSF.new_session()
exit()
elif args.urls[1] == 'remove':
sessions.remove_session()
RSF.remove_session()
exit()
elif args.urls[1] == 'default':
sessions.set_default()
RSF.set_default()
exit()
elif args.urls[1] == 'reauth':
RSF.reauth()
exit()

print(LOGO)

# Load config
print(logo)
BRUTEFORCE = args.bruteforce or BRUTEFORCEREGION
preset = PRESETS[args.preset]
session = sessions.load_session(args.account)

# Create a new API object
api = TidalApi(session['sessionId'], session['countryCode'])

# Parse options
preset['quality'] = []
Expand All @@ -71,72 +74,147 @@ def main():
preset['quality'].append('LOW') if preset['AAC_96'] else None
media_to_download = cli.parse_media_option(args.urls)

# Create a media downloader
md = MediaDownloader(api, preset, Tagger(preset))

# Loop through media and download if possible
cm = 0
for mt in media_to_download:

# Is it an acceptable media type? (skip if not)
if not mt['type'] in MEDIA_TYPES:
print('Unknown media type - ' + mt['type'])
continue

cm += 1
id = mt['id']
tracks = []
print('<<< Getting {0} info... >>>'.format(MEDIA_TYPES[mt['type']]), end='\r')

# Create a new TidalApi and pass it to a new MediaDownloader
md = MediaDownloader(TidalApi(RSF.load_session(args.account)), preset, Tagger(preset))

# Single track
if mt['type'] == 't':
print('<<< Getting track info... >>>', end='\r')
track = api.get_track(id)
# Create a new session generator in case we need to switch sessions
session_gen = RSF.get_session()

# Download and tag file
print('<<< Downloading single track... >>>')
try:
_, filepath = md.download_media(track, preset['quality'])
except ValueError as e:
print("\t" + str(e))
if args.skip is True:
print('Skipping track "{} - {}" due to insufficient quality'.format(
track['artist']['name'], track['title']))
else:
print('Halting on track "{} - {}" due to insufficient quality'.format(
track['artist']['name'], track['title']))
quit()

print('=== 1/1 complete (100% done) ===\n')

# Collection
elif mt['type'] == 'p' or mt['type'] == 'a' or mt['type'] == 'f':
typename = 'playlist' if mt['type'] == 'p' else 'album'
print('<<< Getting {0} info... >>>'.format(typename), end='\r')
# Get media info
def get_tracks(media):
tracks = []
media_info = None
if mt['type'] == 'p':

# Make sure only tracks are in playlist items
playlistItems = api.get_playlist_items(id)['items']
for item in playlistItems:
if item['type'] == 'track':
tracks.append(item['item'])
else:

while True:
try:
media_info = api.get_album(id)
except:
print('api error, skipping\n', end='\r')
continue
tracks = api.get_album_tracks(id)['items']
# Track
if media['type'] == 't':
tracks.append(md.api.get_track(media['id']))

# Playlist
elif media['type'] == 'p':

# Make sure only tracks are in playlist items
playlistItems = md.api.get_playlist_items(media['id'])['items']
for item in playlistItems:
if item['type'] == 'track':
tracks.append(item['item'])

# Album
else:
# Get album information
media_info = md.api.get_album(media['id'])

# Get a list of the tracks from the album
tracks = md.api.get_album_tracks(media['id'])['items']

return tracks, media_info

# Catch region error
except TidalError as e:
if 'not found. This might be region-locked.' in str(e) and BRUTEFORCE:
# Try again with a different session
try:
session, name = next(session_gen)
md.api = TidalApi(session)
print('Checking info fetch with session "{}" in region {}'.format(name, session.country_code))
continue

# Ran out of sessions
except StopIteration as s:
print(e)
raise s

# Skip or halt
else:
raise(e)

try:
tracks, media_info = get_tracks(media=mt)
except StopIteration:
# Let the user know we cannot download this release and skip it
print('None of the available accounts were able to get info for release {}. Skipping..'.format(mt['id']))
continue

total = len(tracks)

# Single
if total == 1:
print('<<< Downloading single track... >>>')

total = len(tracks)
print('<<< Downloading {0}: {1} track(s) in total >>>'.format(
typename, total))
cur = 0

for track in tracks:
md.download_media(track, preset['quality'],
media_info)
cur += 1
print('=== {0}/{1} complete ({2:.0f}% done) ===\n'.format(
cur, total, (cur / total) * 100))
# Playlist or album
else:
print('Unknown media type - ' + mt['type'])
print('<<< Downloading {0}: {1} track(s) in total >>>'.format(
MEDIA_TYPES[mt['type']], total))

cur = 0
for track in tracks:
first = True

# Actually download the track (finally)
while True:
try:
md.download_media(track, preset['quality'], media_info)
break

# Catch quality error
except ValueError as e:
print("\t" + str(e))
if args.skip is True:
print('Skipping track "{} - {}" due to insufficient quality'.format(
track['artist']['name'], track['title']))
break
else:
print('Halting on track "{} - {}" due to insufficient quality'.format(
track['artist']['name'], track['title']))
quit()

# Catch session audio stream privilege error
except AssertionError as e:
if 'Unable to download track' in str(e) and BRUTEFORCE:

# Try again with a different session
try:
# Reset generator if this is the first attempt
if first:
session_gen = RSF.get_session()
first = False
session, name = next(session_gen)
md.api = TidalApi(session)
print('Attempting audio stream with session "{}" in region {}'.format(name, session.country_code))
continue

# Ran out of sessions, skip track
except StopIteration:
# Let the user know we cannot download this release and skip it
print('None of the available accounts were able to download track {}. Skipping..'.format(track['id']))
break

# Skip
else:
print(str(e) + '. Skipping..')

# Progress of current track
cur += 1
print('=== {0}/{1} complete ({2:.0f}% done) ===\n'.format(
cur, total, (cur / total) * 100))

# Progress of queue
print('> Download queue: {0}/{1} items complete ({2:.0f}% done) <\n'.
format(cm, len(media_to_download),
(cm / len(media_to_download)) * 100))
format(cm, len(media_to_download),
(cm / len(media_to_download)) * 100))

print('> All downloads completed. <')

Expand Down
9 changes: 8 additions & 1 deletion redsea/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ def get_args():
default='default',
help='Select a download preset. Defaults to Lossless only. See /config/settings.py for presets')

parser.add_argument(
'-b',
'--bruteforce',
action='store_true',
default=False,
help='Brute force the download with all available accounts')

parser.add_argument(
'-a',
'--account',
Expand All @@ -26,7 +33,7 @@ def get_args():
'-s',
'--skip',
action='store_true',
default='False',
default=False,
help='Pass this flag to skip track and continue when a track does not meet the requested quality')

parser.add_argument(
Expand Down
11 changes: 4 additions & 7 deletions redsea/mediadownloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from .decryption import decrypt_file, decrypt_security_token
from .tagger import FeaturingFormat
from .tidal_api import TidalApi, TidalError
from .tidal_api import TidalApi, TidalRequestError


def _mkdir_p(path):
Expand Down Expand Up @@ -87,7 +87,7 @@ def try_get_url(ntries):
print('\tGrabbing stream URL...')
try:
return self.api.get_stream_url(track_id, quality)
except TidalError as te:
except TidalRequestError as te:
if te.payload['status'] == 404:
print('\tTrack does not exist.')
elif te.payload['subStatus'] == 4005:
Expand Down Expand Up @@ -125,11 +125,8 @@ def print_track_info(self, track_info, album_info):

def download_media(self, track_info, quality, album_info=None):
track_id = track_info['id']
if not track_info['allowStreaming']:
print(
'Unable to download track {0}: not allowed to stream/download. Continuing...'.
format(track_id))
return
assert track_info['allowStreaming'], 'Unable to download track {0}: not allowed to stream/download'.format(track_id)

print('=== Downloading track ID {0} ==='.format(track_id))
self.print_track_info(track_info, album_info)

Expand Down
Loading

0 comments on commit 44320b5

Please sign in to comment.