Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Track.artist and Track.extraArtists with Track.artists #1681

Open
wants to merge 24 commits into
base: master
Choose a base branch
from

Conversation

Lucki
Copy link
Contributor

@Lucki Lucki commented Aug 21, 2024

@nukeop
Copy link
Owner

nukeop commented Aug 22, 2024

Thanks for contributing this. Is it ready for review?

@Lucki
Copy link
Contributor Author

Lucki commented Aug 22, 2024

Yes. Please let me know when you find something non-functional.

Edit: Found one - the MPRIS thing is showing unknown artist
Edit2: Another one - A discogs track without an artist but with extraartists, wanna use the album artist when no specific track artist is available.
Edit3: LastFM track search needs adjustments converting to internal

Comment on lines +79 to +80
// @ts-expect-error NuclearMeta is not a descendant of Track, but Track.artists should be available here anyway
'xesam:artist': track.artists ?? [track.artist]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NuclearMeta should be updated the same way. I'm afraid this is also going to need that:

  • Scanned local library tracks will need to be migrated on first run
  • Scanner will need to return data in this new format

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I adjusted the obvious bits, although blind (no manual testing).

As written in the Discord thread and as I have no usage experience with that part of the program, I think it's better you take this part.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 203 should be updated to artists as well, otherwise it crashes on start.

Comment on lines 179 to 186
function getDownloadsBackwardsCompatible(): Download[] {
const downloads: Download[] = store.get('downloads');

downloads.forEach(download => {
// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
if (download.track.artists || !download.track.artist) {
return;
}

// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
if (download.track.artist) {
// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
download.track.artists = _.isString(download.track.artist) ? [download.track.artist] : [download.track.artist.name];
}

// Assuming we have `extraArtists` on a track, we must had an `artist` which
// was already saved into `artists`, so this `track.artists` shouldn't be undefined
// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
download.track.extraArtists?.forEach(artist => {
download.track.artists.push(artist);
});
});

return downloads;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function does the exact same thing as the one in playlists.ts and almost the same as the one in favorites.ts. The common parts of this logic should be extracted and can live in e.g. packages/app/app/actions/helpers.ts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite. All three are slightly different:

Download[]: { track: Track }[]
Playlist[]: { tracks: Track[] }[]
Favorites: { tracks: Track[] }

I tried combining all into a generic function but wasn't successful with it.

Just for laughs
export interface TrackMember {
  track: Track;
}

export interface TracksMember {
  tracks: Track[];
}

export interface Favorites {
  albums: Album[];
  artists: Artist[];
  tracks: Track[];
}

function isTracksMember(config: TrackMember | TracksMember): config is TracksMember {
  return (config as TracksMember).tracks !== undefined;
}

function isTrackMember(config: TrackMember | TracksMember): config is TrackMember {
  return (config as TrackMember).track !== undefined;
}

function isTracksArrayMember(config: TrackMember | TracksMember | Favorites): config is Favorites {
  return (config as Favorites).tracks !== undefined && (config as Favorites).tracks instanceof Array;
}

function rewriteTrackArtists(track: Track) {
  // @ts-expect-error For backwards compatibility we're trying to parse an invalid field
  if (track.artists || !track.artist) {
    // New format already present, do nothing
    return;
  }

  // @ts-expect-error For backwards compatibility we're trying to parse an invalid field
  track.artists = _.isString(track.artist) ? [track.artist] : [track.artist.name];

  // @ts-expect-error For backwards compatibility we're trying to parse an invalid field
  track.artists = track.artists.concat(track.extraArtists?.map(artist));
}

/**
+* Helper function to read the old track format into the new format.
+*
+* `Track.artist` and `Track.extraArtists` are written into {@link Track.artists}
+*/
export function getFromStoreBackwardsCompatible<T extends Favorites | TrackMember[] | TracksMember[]>(configName: 'downloads' | 'favorites' | 'playlists'): T {
  let config: T = store.get(configName);

  if (config instanceof Array) {
    // Playlist[] || Download[]
    config = config.map((member: TrackMember | TracksMember) => {
      if (isTrackMember(member)) {
        // Download[]
        rewriteTrackArtists(member.track);
      } else if (isTracksMember(member)) {
        // Playlist[]
        member.tracks.forEach(track => {
          rewriteTrackArtists(track);
        });
      }
      return member;
    });
  } else if (isTracksArrayMember(config)) {
    // Favorites: { tracks: Track[] }
    config.tracks = config.tracks.map(track => {
      rewriteTrackArtists(track);
      return track;
    });
    config.albums = config.albums.map(album => {
      album.tracklist = album.tracklist?.map(track => {
        rewriteTrackArtists(track);
        return track;
      });
      return album;
    });
  }

  return config;
}

Comment on lines 148 to 137
album.tracklist?.forEach(track => {
if (track.artists || !track.artist) {
return;
}

if (track.artist) {
track.artists = _.isString(track.artist) ? [track.artist] : [track.artist.name];
}

// Assuming we have `extraArtists` on a track, we must had an `artist` which
// was already saved into `artists`, so this `track.artists` shouldn't be undefined
track.extraArtists?.forEach(artist => {
track.artists.push(artist);
});
});
});

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part can use the function that can be extracted from all getXBackwardsCompatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the Discord thread, this is very strange.
I moved it in a separate helper function and then the tests start failing because the track is suddenly undefined.

Comment on lines 197 to 199
download.track.extraArtists?.forEach(artist => {
download.track.artists.push(artist);
});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of mutating by using forEach/push, you can use map and assign what map returns.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also applies to other places where forEach is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed them all to use map now.
Aside from inside the helper functions there was only one single occurrence when reading from the DB.

@nukeop nukeop added the needs changes The author needs to make changes to this pull request. label Aug 25, 2024
@nukeop
Copy link
Owner

nukeop commented Aug 28, 2024

I'm still working on the track table refactor, after that I'll get back to this.

@nukeop nukeop added under review This pull request is being reviewed by maintainers. and removed needs changes The author needs to make changes to this pull request. labels Aug 28, 2024
@nukeop
Copy link
Owner

nukeop commented Oct 15, 2024

Finally got some time (and willpower) to get this into a working shape! I'll do it and merge it asap.

@nukeop
Copy link
Owner

nukeop commented Oct 21, 2024

After these changes everything should be ready. If the CI passes I'll merge it tomorrow.

@GearKite
Copy link

This breaks lastfm scrobbling, all song artists are Undefined now.

@nukeop
Copy link
Owner

nukeop commented Oct 21, 2024

Thanks for testing. I'll take a look at this (once I'm back from 1955).

@nukeop
Copy link
Owner

nukeop commented Oct 21, 2024

Bad news. This is going to need replacing several JS files with TS, they also need to be typed correctly. These changes also break autoradio in addition to scrobbling. I'm currently working on this. The most troublesome component to update is going to be the SoundContainer which controls playback, and is in a catastrophically bad state due to being neglected over the years.

@nukeop
Copy link
Owner

nukeop commented Oct 28, 2024

Ok, I managed to fix this.

@nukeop
Copy link
Owner

nukeop commented Oct 29, 2024

There's still one more problem, the local library tracks still have a single artist field, and I'm not sure what to do about them. I think a backwards compatibility layer is needed like in all those other cases. Until that's added, you can't play anything from the library.

@GearKite
Copy link

Queue playback doesn't seem to really work now. It seems that the YouTube playback source expires after a few minutes, so, after listening to a song, all the next ones will show a:
The audio playback can not be loaded, either because the server or network failed or because the format is not supported.
Notably, this does not happen on an older version.

@waldo121
Copy link
Contributor

Queue playback doesn't seem to really work now. It seems that the YouTube playback source expires after a few minutes, so, after listening to a song, all the next ones will show a: The audio playback can not be loaded, either because the server or network failed or because the format is not supported. Notably, this does not happen on an older version.

Can you create an issue with more details please? I have also observed this problem and it is not related to this pr.

@nukeop
Copy link
Owner

nukeop commented Oct 30, 2024

I am pretty sure ytdl-core needs to be updated: https://www.npmjs.com/package/@distube/ytdl-core

Youtube changed something a couple of days ago.

@nukeop
Copy link
Owner

nukeop commented Nov 12, 2024

I'm not giving up yet, working on a one time migration for existing libraries that will hopefully solve this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
under review This pull request is being reviewed by maintainers.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants