aboutsummaryrefslogtreecommitdiff
path: root/yt
diff options
context:
space:
mode:
authorGuillermo Ramos2021-04-13 18:17:14 +0200
committerGuillermo Ramos2021-04-13 18:17:14 +0200
commit04446011f364909096cb49f2b66e4df96d713209 (patch)
tree6c2bbb722d63487a766d91e5ecc9560f01442f44 /yt
downloadcli-04446011f364909096cb49f2b66e4df96d713209.tar.gz
Initial commit
Diffstat (limited to 'yt')
-rwxr-xr-xyt144
1 files changed, 144 insertions, 0 deletions
diff --git a/yt b/yt
new file mode 100755
index 0000000..b246b07
--- /dev/null
+++ b/yt
@@ -0,0 +1,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");
+}