Add playlist functions
This commit is contained in:
235
YTMusicAPI/Parsers/Playlists.pm
Normal file
235
YTMusicAPI/Parsers/Playlists.pm
Normal file
@@ -0,0 +1,235 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user