aboutsummaryrefslogtreecommitdiff
path: root/yt
blob: b246b073d5f0b95efc4982b27ff980a7c2032051 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env perl

use strict;
use warnings;

use IPC::Open2 qw<open2>;
use JSON;

my $ENDPOINT = "https://www.googleapis.com/youtube/v3";
my $YT_API_KEY = $ENV{'YOUTUBE_API_KEY'}
    || die "Error: You need to define the env variable 'YOUTUBE_API_KEY'\n";

my $MAX_RESULTS = 30;

# Currently unused
my $graphical;

sub decode_json_utf8 {
    my $json = join "", @_;
    utf8::encode($json);
    return decode_json($json);
}

sub api_search {
    my $query = shift;
    # TODO sanitize query properly...
    $query =~ s/'/'\\''/g;
    return (decode_json_utf8 `curl -s '$ENDPOINT/search?part=snippet&q=$query&maxResults=$MAX_RESULTS&key=$YT_API_KEY'`)->{items};
}

sub api_videos {
    my $ids = join(',', @_);
    return (decode_json_utf8 `curl -s '$ENDPOINT/videos?part=snippet,statistics,contentDetails&id=$ids&key=$YT_API_KEY'`)->{items};
}

sub api_channel_videos {
    use Data::Dumper;
    my $id = shift;
    my $uploads = (decode_json_utf8 `curl -s '$ENDPOINT/channels?part=contentDetails&id=$id&key=$YT_API_KEY'`)->{items}[0]{contentDetails}{relatedPlaylists}{uploads};
    my $videos = (decode_json_utf8 `curl -s '$ENDPOINT/playlistItems?part=snippet&maxResults=$MAX_RESULTS&playlistId=$uploads&key=$YT_API_KEY'`)->{items};
    return map { $_->{snippet}{resourceId}{videoId} } @$videos;
}

sub play {
    my $url = shift;
    `mpv --really-quiet $url`;
}

sub ask_query {
   my $query = `rofi -dmenu -no-fixed-num-lines -p "Youtube search" | sed 's: :+:g'`;
   $query =~ s/\s*$//g;
   $graphical = 1;
   return $query;
}

sub sanitize_rofi {
    return shift =~ s/&/&amp;/gr;
}

sub select_entry {
    my ($channels, $video_ids) = @_;

    my @entries;

    open2(my $outh, my $inh, 'rofi -i -markup-rows -dmenu -format i -p "Select video"');
    foreach my $chan (@$channels) {
        my $id = $chan->{channelId};
        push @entries, $id;

        print $inh sanitize_rofi("<b>[$chan->{title}] $chan->{description}</b>\n");
    }

    foreach my $video (@{api_videos(@$video_ids)}) {
        my $id = $video->{id};
        push @entries, $id;

        my $publishedAt = $video->{snippet}{publishedAt};
        my ($y, $mo, $d, $h, $mi) = $publishedAt =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):\d{2}Z$/;
        my (undef, $cmi, $ch, $cd, $cmo, $cy) = gmtime();
        $cy += 1900;
        $cmo++;
        my $published;
        if ($y < $cy-1) {
            $published = $cy - $y . " years ago";
        } elsif ($mo != $cmo || $y == $cy-1) {
            $published = (12 * ($cy-$y) + $cmo) - $mo . " months ago";
        } elsif ($cd != $d) {
            $published = $cd - $d . " days ago";
        } elsif ($ch != $h) {
            $published = $ch - $h . " hours ago";
        } elsif ($cmi != $mi) {
            $published = $cmi - $mi . " minutes ago";
        }

        my $stats = $video->{statistics};
        my $likes = $stats->{likeCount};
        my $dislikes = $stats->{dislikeCount};
        my $rating = "";
        if ($likes && $dislikes) {
            my $total = $likes + $dislikes;
            $rating = "*" x (6 * $likes / $total);
        }

        print $inh sanitize_rofi("($published) [$video->{snippet}{channelTitle}] $video->{snippet}{title} $rating\n");
    }
    close $inh;

    my $idx = <$outh> || die "cancelled\n";
    if ($idx < @$channels) {
        return ('channel', $entries[$idx]);
    } else {
        return ('video', $entries[$idx]);
    }
}


## MAIN

my $query = join("+", @ARGV) || ask_query();
die "empty query\n" unless $query;

if ($query =~ /^http/) {
    play($query);
    exit 0;
}

my @channels;
my @video_ids;
foreach my $entry (@{api_search($query)}) {
    if ($entry->{id}{kind} eq 'youtube#channel') {
        push @channels, $entry->{snippet};
    } elsif ($entry->{id}{kind} eq 'youtube#video') {
        push @video_ids, $entry->{id}{videoId};
    }
}

my ($type, $entry) = select_entry(\@channels, \@video_ids);
if ($type eq 'channel') {
    my @videos = api_channel_videos($entry);
    my (undef, $entry) = select_entry([], \@videos);
    play("https://youtu.be/$entry");
} elsif ($type eq 'video') {
    play("https://youtu.be/$entry");
}