python-tidal icon indicating copy to clipboard operation
python-tidal copied to clipboard

[Bug] get_tracks_by_isrc acting strangly

Open yodaluca23 opened this issue 6 months ago • 3 comments

Running even something as basic as this:

def convertToTidal(isrc):
    tracks = session.get_tracks_by_isrc(isrc)
    if len(tracks) > 0:
        return tracks[0].id
    else:
        return None
    
print(convertToTidal("USUM70972068"))

Almost always returns a bunch of errors.

Track '34354645' is unavailable
Traceback (most recent call last):
  File "C:\Users\*****\*****\venv\Lib\site-packages\tidalapi\request.py", line 151, in request
    request.raise_for_status()
    ~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "C:\Users\*****\*****\venv\Lib\site-packages\requests\models.py", line 1024, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://api.tidal.com/v1/tracks/34354645?sessionId=REDACTED&countryCode=US&limit=1000

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\*****\*****\venv\Lib\site-packages\tidalapi\media.py", line 331, in _get
    request = self.requests.request("GET", "tracks/%s" % media_id)
  File "C:\Users\*****\*****\venv\Lib\site-packages\tidalapi\request.py", line 166, in request
    raise ObjectNotFound
tidalapi.exceptions.ObjectNotFound

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\Users\*****\*****\import tidalapi.py", line 50, in <module>
    print(convertToTidal("USUM70972068"))
          ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "c:\Users\*****\*****\import tidalapi.py", line 44, in convertToTidal
    tracks = session.get_tracks_by_isrc(isrc)
  File "C:\Users\*****\*****\venv\Lib\site-packages\tidalapi\session.py", line 958, in get_tracks_by_isrc
    return [self.track(tr["id"]) for tr in res["data"]]
            ~~~~~~~~~~^^^^^^^^^^
  File "C:\Users\*****\*****\venv\Lib\site-packages\tidalapi\session.py", line 928, in track
    item = media.Track(session=self, media_id=track_id)
  File "C:\Users\*****\*****\venv\Lib\site-packages\tidalapi\media.py", line 212, in __init__
    self._get(self.id)
    ~~~~~~~~~^^^^^^^^^
  File "C:\Users\*****\*****\venv\Lib\site-packages\tidalapi\media.py", line 333, in _get
    raise ObjectNotFound("Track not found or unavailable")
tidalapi.exceptions.ObjectNotFound: Track not found or unavailable

I don't think it always did this, or I'm just stupid, which is a possibility, in which case I apologize. But it's quite strange if, you put the track ID into the Tidal listen URL (https://listen.tidal.com/track/34354645) it returns a not found page. But in the browse URL it returns like a mix or something? (https://tidal.com/browse/track/34354645) Clicking play brings to the same not found page...

yodaluca23 avatar May 25 '25 00:05 yodaluca23

I don't think it always did this, or I'm just stupid, which is a possibility, in which case I apologize.

The issue is related to the ISRC (The API endpoint returns a list of tracks, of which one of them is not available for playback. The current python-tidal code assumes that all results are valid for playback, but clearly they are not. For instance, session.get_tracks_by_isrc("USSM12209515") works fine.

But in the browse URL it returns like a mix or something?

The same ID can be reused for a track and mix, even though it is not the same item.

I believe this is indeed a bug, as the results returned from tidal should be validated somehow when returning the list of Tracks.

tehkillerbee avatar May 25 '25 17:05 tehkillerbee

This fixes the issue that you found (looks like quite a few of the results are not valid for playback). The fact that Tidal returns "invalid" tracks in the results sounds like a bug in TIDAL's own API (https://developer.tidal.com/apiref?spec=catalogue-v2&ref=get-all-tracks&at=THIRD_PARTY). Or perhaps the misuse of the way we get tracks using python-tidal.

    def get_tracks_by_isrc(self, isrc: str) -> list[media.Track]:
        """Function to search all tracks with a specific ISRC code. (eg. "USSM12209515")
        This method uses the TIDAL openapi (v2). See the apiref below for more details:
        https://apiref.developer.tidal.com/apiref?spec=catalogue-v2&ref=get-tracks-v2

        :param isrc: The ISRC of the Track.
        :return: Returns a list of :class:`.Track` objects that have access to the session instance used.
                 An empty list will be returned if no tracks matches the ISRC
        """
        try:
            params = {
                "filter[isrc]": isrc,
            }
            res = self.request.request(
                "GET",
                "tracks",
                params=params,
                base_url=self.config.openapi_v2_location,
            ).json()
            if res["data"]:
                tracks = []
                for tr in res["data"]:
                    try:
                        tracks.append(self.track(tr["id"]))
                    except ObjectNotFound:
                        continue
                return tracks
...

I'll add it in the next release 👍

tehkillerbee avatar May 25 '25 17:05 tehkillerbee

This fixes the issue that you found (looks like quite a few of the results are not valid for playback). The fact that Tidal returns "invalid" tracks in the results sounds like a bug in TIDAL's own API (developer.tidal.com/apiref?spec=catalogue-v2&ref=get-all-tracks&at=THIRD_PARTY). Or perhaps the misuse of the way we get tracks using python-tidal. I'll add it in the next release 👍

So I cloned the repo and fixed the function to be like what you recommended:

    def get_tracks_by_isrc(self, isrc: str) -> list[media.Track]:
        """Function to search all tracks with a specific ISRC code. (eg. "USSM12209515")
        This method uses the TIDAL openapi (v2). See the apiref below for more details:
        https://apiref.developer.tidal.com/apiref?spec=catalogue-v2&ref=get-tracks-v2

        :param isrc: The ISRC of the Track.
        :return: Returns a list of :class:`.Track` objects that have access to the session instance used.
                 An empty list will be returned if no tracks matches the ISRC
        """
        try:
            params = {
                "filter[isrc]": isrc,
            }
            res = self.request.request(
                "GET",
                "tracks",
                params=params,
                base_url=self.config.openapi_v2_location,
            ).json()
            if res["data"]:
                tracks = []
                for tr in res["data"]:
                    try:
                        tracks.append(self.track(tr["id"]))
                    except ObjectNotFound:
                        continue
                return tracks
        except HTTPError:
            log.error("Invalid ISRC code '%s'", isrc)
            # Get latest detailed error response and return the response given from the TIDAL api
            resp_str = self.request.get_latest_err_response_str()
            if resp_str:
                log.error("API Response: '%s'", resp_str)
            raise InvalidISRC

That does make it not completely crash, and outputs this from the same example:

Track '123904398' is unavailable
Track '34354645' is unavailable
316416376

However, going to https://tidal.com/browse/track/316416376 it still seems to be one of those weird Mixes, though trying to play it is possible https://listen.tidal.com/album/316416364/track/316416376 and this does indeed confirm it is a part of a mix of some sort. Is it possible to also filter these out?

yodaluca23 avatar Jun 07 '25 20:06 yodaluca23

With and without my above fix, no errors are returned anymore. So perhaps Tidal fixed it in their end?

tehkillerbee avatar Jul 09 '25 21:07 tehkillerbee

Can confirm it seems to be fixed on Tidal's end, thank you!

yodaluca23 avatar Jul 11 '25 04:07 yodaluca23