Skip to content
Draft
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
20 changes: 17 additions & 3 deletions zotify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"""

import argparse
import sys

from zotify.app import client
from zotify.config import Zotify, CONFIG_VALUES, DEPRECIATED_CONFIGS
from zotify.termoutput import Printer
from zotify.termoutput import Printer, PrintChannel
from zotify.exceptions import LoginError, RateLimitError, ConnectionDropError


class DepreciatedAction(argparse.Action):
Expand Down Expand Up @@ -134,8 +136,20 @@ def main():
)

args = parser.parse_args()
Zotify.boot(args)
client(args, modes)
try:
Zotify.boot(args)
client(args, modes)
except LoginError:
sys.exit(133)
except RateLimitError:
sys.exit(102)
except ConnectionDropError:
sys.exit(101)

if Zotify.DOWNLOAD_ERRORS:
for err in Zotify.DOWNLOAD_ERRORS:
Printer.hashtaged(PrintChannel.ERROR, err)
sys.exit(100)


if __name__ == '__main__':
Expand Down
4 changes: 4 additions & 0 deletions zotify/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,7 @@ def download(self, parent_stack: ParentStack) -> None:
if stream is None:
Printer.hashtaged(PrintChannel.ERROR, 'SKIPPING TRACK - FAILED TO GET CONTENT STREAM\n' +
f'Track_ID: {self.id}')
Zotify.DOWNLOAD_ERRORS.append(f"Track {self.id}: Failed to get content stream")
return

self.set_dl_status("Downloading Stream")
Expand All @@ -970,6 +971,7 @@ def download(self, parent_stack: ParentStack) -> None:
except Exception as e:
Printer.hashtaged(PrintChannel.ERROR, 'FAILED TO WRITE METADATA\n')
Printer.traceback(e)
Zotify.DOWNLOAD_ERRORS.append(f"Track {self.id}: Failed to write metadata ({e})")

Interface.dl_complete(self, path, time_elapsed_dl, time_elapsed_ffmpeg)

Expand Down Expand Up @@ -1167,6 +1169,7 @@ def download(self, parent_stack: ParentStack):
if stream is None:
Printer.hashtaged(PrintChannel.ERROR, 'SKIPPING EPISODE - FAILED TO GET CONTENT STREAM\n' +
f'Episode_ID: {self.id}')
Zotify.DOWNLOAD_ERRORS.append(f"Episode {self.id}: Failed to get content stream")
return
time_elapsed_dl = self.fetch_content_stream(stream, temppath, parent_stack)
else:
Expand All @@ -1175,6 +1178,7 @@ def download(self, parent_stack: ParentStack):
except Exception as e:
Printer.hashtaged(PrintChannel.ERROR, 'FAILED TO DOWNLOAD EPISODE DIRECTLY')
Printer.traceback(e)
Zotify.DOWNLOAD_ERRORS.append(f"Episode {self.id}: Failed to download directly ({e})")
return

try:
Expand Down
32 changes: 28 additions & 4 deletions zotify/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
import sys
import queue
import re
import requests
from binascii import hexlify
Expand All @@ -21,6 +22,7 @@

from zotify.const import *
from zotify.termoutput import Printer, PrintChannel, Loader
from zotify.exceptions import LoginError, RateLimitError, ConnectionDropError

Streamer = CdnManager.Streamer

Expand Down Expand Up @@ -653,13 +655,15 @@ class Zotify:
# DYNAMIC PER QUERY
TOTAL_API_CALLS : int = None
DATETIME_LAUNCH : str = None
DOWNLOAD_ERRORS : list[str] = []

@classmethod
def start(cls) -> None:
if cls.TOTAL_API_CALLS:
Printer.debug(f"Total API Calls: {cls.TOTAL_API_CALLS}")
cls.DATETIME_LAUNCH = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
cls.TOTAL_API_CALLS = 0
cls.DOWNLOAD_ERRORS = []

@classmethod
def login(cls, args):
Expand Down Expand Up @@ -766,11 +770,17 @@ def boot(cls, args):
login_try = 0
while login_try <= cls.CONFIG.get_retry_attempts():
login_try += 1
try: cls.login(args)
try:
cls.login(args)
break
except ConnectionError as e:
pause = 3 ** login_try
Printer.hashtaged(PrintChannel.WARNING, f'LOGIN FAILED ({e.args[0]})\n' +
'TRYING AGAIN AFTER SMALL WAIT')
sleep(3)
f'TRYING AGAIN AFTER {pause}s WAIT')
sleep(pause)
else:
Printer.hashtaged(PrintChannel.ERROR, 'MAX LOGIN RETRIES REACHED. EXITING.')
raise LoginError("Max login retries reached")
cls.LOGGER = logging.getLogger("zotify.debug")

prem, quality, bitrate = cls.parse_dl_quality(cls.CONFIG.get_download_qual_pref())
Expand Down Expand Up @@ -861,7 +871,7 @@ def choose_token() -> str:
tryCount += 1
if tryCount > cls.CONFIG.get_retry_attempts():
break
sleep(retry_delay if not expectFail else 1)
sleep((retry_delay * tryCount) if not expectFail else 1)

if not expectFail:
Printer.hashtaged(PrintChannel.API_ERROR, f'RETRY LIMIT EXCEDED\n' +
Expand Down Expand Up @@ -950,12 +960,26 @@ def get_content_stream(cls, content, use_qual_pref: bool = True) -> Streamer | N
'MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING `BULK_WAIT_TIME`\n' +
f'GID: {gid[5:]} - File_ID: {fileid[8:]}')
Printer.logger("\n".join(e.args), PrintChannel.ERROR)
raise RateLimitError("Rate limited when fetching audio key")
except ConnectionError as e:
if "Status code " not in e.args[0]: raise
status_code = e.args[0].split("Status code ")[1]
Printer.hashtaged(PrintChannel.ERROR, 'FAILED TO FETCH AUDIO FILE\n' +
f'CONNECTION ERROR WHEN FETCHING CONTENT STREAM - STATUS CODE {status_code}')
Printer.logger("\n".join(e.args), PrintChannel.ERROR)
if status_code.strip() == "429":
raise RateLimitError("Rate limited (status code 429)")
raise ConnectionDropError(f"Connection error: status code {status_code}")
except OSError as e:
Printer.hashtaged(PrintChannel.ERROR, 'FAILED TO FETCH AUDIO STREAM\n' +
'NETWORK CONNECTION LOST - SPOTIFY TERMINATED SESSION')
Printer.traceback(e)
raise ConnectionDropError("Spotify terminated session")
except queue.Empty as e:
Printer.hashtaged(PrintChannel.ERROR, 'FAILED TO FETCH AUDIO STREAM\n' +
'NETWORK CONNECTION LOST - QUEUE TIMEOUT WAITING FOR RESPONSE')
Printer.traceback(e)
raise ConnectionDropError("Spotify terminated session (Queue Empty Timeout)")
except Exception as e:
if risky_method:
cls.FORCE_STREAM_API_CALLS = True
Expand Down
15 changes: 15 additions & 0 deletions zotify/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class ZotifyException(Exception):
"""Base exception for Zotify"""
pass

class LoginError(ZotifyException):
"""Raised when Zotify fails to log in"""
pass

class RateLimitError(ZotifyException):
"""Raised when Spotify rate limits the session"""
pass

class ConnectionDropError(ZotifyException):
"""Raised when the connection to Spotify drops unexpectedly"""
pass