package YTMusicAPI::Parsers::Songs; use strict; use warnings; use Exporter 'import'; use Regexp::Common qw(number); use YTMusicAPI::Parsers::Utils; use YTMusicAPI::Navigation; sub parse_song_artists { my ( $data, $index ) = @_; my $flex_item = get_flex_column_item( $data, $index ); if ( !$flex_item ) { return undef; } else { my $runs = $flex_item->{"text"}{"runs"}; return parse_song_artists_runs($runs); } } sub parse_song_artists_runs { my ($runs) = @_; my @artists = (); for my $j ( 0 .. int( @$runs / 2 ) ) { push @artists, { "name" => $runs->[ $j * 2 ]{"text"}, "id" => nav( $runs->[ $j * 2 ], $NAVIGATION_BROWSE_ID, 1 ) }; } return \@artists; } sub parse_song_runs { my ($runs) = @_; my %parsed = ( "artists" => [] ); my $i = 0; for my $run (@$runs) { if ( $i % 2 ) { # uneven items are always separators $i++; next; } my $text = $run->{"text"}; if ( exists $run->{"navigationEndpoint"} ) { # artist or album my $item = { "name" => $text, "id" => nav( $run, $NAVIGATION_BROWSE_ID, 1 ) }; if ( $item->{"id"} && ( $item->{"id"} =~ /^MPRE/ || $item->{"id"} =~ /release_detail/ ) ) { # album $parsed{"album"} = $item; } else { # artist push @{ $parsed{"artists"} }, $item; } } else { # note: YT uses non-breaking space \xa0 to separate number and magnitude if ( $text =~ /^\d([^ ])* [^ ]*$/ && $i > 0 ) { ( $parsed{"views"} ) = split( / /, $text, 2 ); } elsif ( $text =~ /^(\d+:)*\d+:\d+$/ ) { $parsed{"duration"} = $text; $parsed{"duration_seconds"} = parse_duration($text); } elsif ( $text =~ /^\d{4}$/ ) { $parsed{"year"} = $text; } else { # artist without id push @{ $parsed{"artists"} }, { "name" => $text, "id" => undef }; } } $i++; } return \%parsed; } sub parse_song_album { my ( $data, $index ) = @_; my $flex_item = get_flex_column_item( $data, $index ); my $browse_id = nav( $flex_item, $TEXT_RUN + $NAVIGATION_BROWSE_ID, 1 ); return !$flex_item ? undef : { "name" => get_item_text( $data, $index ), "id" => $browse_id }; } sub parse_song_library_status { my ($item) = @_; my $library_status = nav( $item, [ $TOGGLE_MENU, "defaultIcon", "iconType" ], 1 ); return $library_status eq "LIBRARY_SAVED"; } sub parse_song_menu_tokens { my ($item) = @_; my $toggle_menu = $item->{$TOGGLE_MENU}; my $library_add_token = nav( $toggle_menu, [ "defaultServiceEndpoint", $FEEDBACK_TOKEN ], 1 ); my $library_remove_token = nav( $toggle_menu, [ "toggledServiceEndpoint", $FEEDBACK_TOKEN ], 1 ); my $in_library = parse_song_library_status($item); if ($in_library) { my $tmp = $library_remove_token; $library_add_token = $library_remove_token; $library_remove_token = $tmp; } return { "add" => $library_add_token, "remove" => $library_remove_token }; } sub parse_like_status { my ($service) = @_; my @status = ( "LIKE", "INDIFFERENT" ); my $status_index = 0; for my $i ( 0 .. $#status ) { if ( $status[$i] eq $service->{"likeEndpoint"}{"status"} ) { $status_index = $i; last; } } return $status[ $status_index - 1 ]; } our @EXPORT = qw( parse_song_artists parse_song_artists_runs parse_song_runs parse_song_album parse_song_library_status parse_song_menu_tokens parse_like_status ); 1;