Files
2024-03-24 23:34:26 +01:00

236 lines
7.1 KiB
Perl

package YTMusicAPI::Parsers::Playlists;
use strict;
use warnings;
use Exporter 'import';
use YTMusicAPI::Parsers::Utils;
use YTMusicAPI::Parsers::Songs;
use YTMusicAPI::Navigation;
sub parse_playlist_header {
my ($response) = @_;
my $playlist = {};
my $header;
my $own_playlist =
exists $response->{"header"}{"musicEditablePlaylistDetailHeaderRenderer"};
if ( !$own_playlist ) {
$header = $response->{"header"}{"musicDetailHeaderRenderer"};
$playlist->{"privacy"} = "PUBLIC";
}
else {
$header =
$response->{"header"}{"musicEditablePlaylistDetailHeaderRenderer"};
$playlist->{"privacy"} =
$header->{"editHeader"}{"musicPlaylistEditHeaderRenderer"}{"privacy"};
$header = $header->{"header"}{"musicDetailHeaderRenderer"};
}
$playlist->{"owned"} = $own_playlist;
$playlist->{"title"} = nav( $header, $TITLE_TEXT );
$playlist->{"thumbnails"} = nav( $header, $THUMBNAIL_CROPPED );
$playlist->{"description"} = nav( $header, $DESCRIPTION, 1 );
my $run_count = scalar( @{ nav( $header, $SUBTITLE_RUNS ) } );
if ( $run_count > 1 ) {
$playlist->{"author"} = {
"name" => nav( $header, $SUBTITLE2 ),
"id" =>
nav( $header, [ $SUBTITLE_RUNS, 2, $NAVIGATION_BROWSE_ID ], 1 ),
};
if ( $run_count == 5 ) {
$playlist->{"year"} = nav( $header, $SUBTITLE3 );
}
}
$playlist->{"views"} = undef;
$playlist->{"duration"} = undef;
$playlist->{"trackCount"} = undef;
if ( exists $header->{"secondSubtitle"}{"runs"} ) {
my $second_subtitle_runs = $header->{"secondSubtitle"}{"runs"};
my $has_views = ( $#$second_subtitle_runs > 3 ) * 2;
$playlist->{"views"} =
!$has_views ? undef : scalar $second_subtitle_runs->[0]->{"text"};
my $has_duration = ( $#$second_subtitle_runs > 1 ) * 2;
$playlist->{"duration"} = (
!$has_duration
? undef
: $second_subtitle_runs->[ $has_views + $has_duration ]->{"text"}
);
my @song_count =
split( " ", $second_subtitle_runs->[ $has_views + 0 ]->{"text"} );
@song_count = $#song_count > 1 ? ( scalar $song_count[0] ) : 0;
$playlist->{"trackCount"} = @song_count;
}
return $playlist;
}
sub parse_playlist_items {
my ( $results, $menu_entries, $is_album ) = @_;
$menu_entries //= [];
$is_album //= 0;
my $songs = [];
foreach my $result (@$results) {
next unless exists $result->{$MRLIR};
my $data = $result->{$MRLIR};
my $song = parse_playlist_item( $data, $menu_entries, $is_album );
push @$songs, $song if $song;
}
return $songs;
}
sub parse_playlist_item {
my ( $data, $menu_entries, $is_album ) = @_;
$menu_entries //= [];
$is_album //= 0;
my ( $videoId, $setVideoId ) = ( undef, undef );
my ( $like, $feedback_tokens, $library_status ) = ( undef, undef, undef );
# if the item has a menu, find its setVideoId
if ( exists $data->{"menu"} ) {
foreach my $item ( @{ nav( $data, $MENU_ITEMS ) } ) {
if ( exists $item->{"menuServiceItemRenderer"} ) {
my $menu_service = nav( $item, $MENU_SERVICE );
if ( exists $menu_service->{"playlistEditEndpoint"} ) {
$setVideoId = nav(
$menu_service,
[ "playlistEditEndpoint", "actions", 0, "setVideoId" ],
1
);
$videoId = nav(
$menu_service,
[
"playlistEditEndpoint", "actions",
0, "removedVideoId"
],
1
);
}
}
if ( exists $item->{$TOGGLE_MENU} ) {
$feedback_tokens = parse_song_menu_tokens($item);
$library_status = parse_song_library_status($item);
}
}
}
# if item is not playable, the videoId was retrieved above
if ( defined nav( $data, $PLAY_BUTTON, 1 ) ) {
if ( exists nav( $data, $PLAY_BUTTON )->{"playNavigationEndpoint"} ) {
$videoId = nav( $data, $PLAY_BUTTON )->{"playNavigationEndpoint"}
->{"watchEndpoint"}->{"videoId"};
if ( exists $data->{"menu"} ) {
$like = nav( $data, $MENU_LIKE_STATUS, 1 );
}
}
}
my $title = get_item_text( $data, 0 );
return undef if $title eq "Song deleted";
my $flex_column_count = scalar @{ $data->{"flexColumns"} };
my $artists = parse_song_artists( $data, 1 );
my $album =
!$is_album ? parse_song_album( $data, $flex_column_count - 1 ) : undef;
my $views =
$flex_column_count == 4 || $is_album ? get_item_text( $data, 2 ) : undef;
my $duration;
if ( exists $data->{"fixedColumns"} ) {
if (
exists get_fixed_column_item( $data, 0 )->{"text"}->{"simpleText"} )
{
$duration =
get_fixed_column_item( $data, 0 )->{"text"}->{"simpleText"};
}
else {
$duration = get_fixed_column_item( $data, 0 )->{"text"}->{"runs"}[0]
->{"text"};
}
}
my $thumbnails = nav( $data, $THUMBNAILS, 1 );
my $isAvailable =
exists $data->{"musicItemRendererDisplayPolicy"}
? $data->{"musicItemRendererDisplayPolicy"} ne
"MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT"
: 1;
my $isExplicit = defined nav( $data, $BADGE_LABEL, 1 );
my $videoType = nav(
$data,
[
$MENU_ITEMS, 0, $MNIR, "navigationEndpoint",
@$NAVIGATION_VIDEO_TYPE
],
1,
);
my $song = {
"videoId" => $videoId,
"title" => $title,
"artists" => $artists,
"album" => $album,
"likeStatus" => $like,
"inLibrary" => $library_status,
"thumbnails" => $thumbnails,
"isAvailable" => $isAvailable,
"isExplicit" => $isExplicit,
"videoType" => $videoType,
"views" => $views,
};
if ($is_album) {
$song->{"trackNumber"} =
$isAvailable
? int( nav( $data, [ "index", "runs", 0, "text" ] ) )
: undef;
}
if ( defined $duration ) {
$song->{"duration"} = $duration;
$song->{"duration_seconds"} = parse_duration($duration);
}
$song->{"setVideoId"} = $setVideoId if defined $setVideoId;
$song->{"feedbackTokens"} = $feedback_tokens if defined $feedback_tokens;
if ($menu_entries) {
foreach my $menu_entry (@$menu_entries) {
$song->{ $menu_entry->[-1] } =
nav( $data, [ @{$MENU_ITEMS}, @$menu_entry ] );
}
}
return $song;
}
sub validate_playlist_id {
my ($playlistId) = @_;
return $playlistId =~ /^VL/ ? substr( $playlistId, 2 ) : $playlistId;
}
our @EXPORT = qw(
parse_playlist_header
parse_playlist_items
parse_playlist_item
validate_playlist_id
);
1;