wimp-viewer 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/usr/bin/perl
  2. # List and play the most recent videos from: https://www.wimp.com/
  3. # Requires 'youtube-viewer' and 'mpv'
  4. use 5.010;
  5. use strict;
  6. use warnings;
  7. use open ':std' => ':utf8';
  8. use Encode qw(encode_utf8);
  9. use XML::Fast qw(xml2hash);
  10. use Term::ANSIColor qw(colored);
  11. use Getopt::Std qw(getopts);
  12. my $appname = 'wimp-viewer';
  13. my $version = '0.33';
  14. my $BASE_URL = 'https://www.wimp.com';
  15. require Term::ReadLine;
  16. my $term = Term::ReadLine->new($appname);
  17. require WWW::Mechanize;
  18. my $mech = WWW::Mechanize->new(
  19. autocheck => 1,
  20. env_proxy => 1,
  21. show_progress => 0,
  22. agent => 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1941.0 Safari/537.36',
  23. );
  24. $mech->default_header('Accept-Encoding' => scalar HTTP::Message::decodable());
  25. sub output_usage {
  26. print <<"HELP";
  27. usage: $0 [options] [url]
  28. options:
  29. -f : fullscreen mode
  30. -r <i> : play i of random videos and exit
  31. -v : print the version number and exit
  32. -h : print this help message and exit
  33. HELP
  34. }
  35. sub output_version {
  36. say "$appname $version";
  37. }
  38. my %opt;
  39. if (@ARGV) {
  40. getopts('r:fvh', \%opt);
  41. }
  42. if ($opt{h}) {
  43. output_usage();
  44. exit 0;
  45. }
  46. if ($opt{v}) {
  47. output_version();
  48. exit 0;
  49. }
  50. if (exists $opt{r}) {
  51. if (defined($opt{r}) and $opt{r} > 0) {
  52. for my $i (1 .. $opt{r}) {
  53. play_random_video();
  54. }
  55. }
  56. else {
  57. die "error: option '-r' requires a positive integer!\n";
  58. }
  59. exit;
  60. }
  61. # Play the command-line URIs
  62. foreach my $url (@ARGV) {
  63. play($url);
  64. exit;
  65. }
  66. sub play {
  67. my ($url) = @_;
  68. my $resp = $mech->get($url);
  69. my $content = $resp->decoded_content // $resp->content;
  70. my $real_url = $mech->uri;
  71. if ( $content =~ m{\byoutube\.com/watch\?v=([\w-]{11})"}
  72. or $content =~ m{<div data-autoplay='1' data-start='0' data-id='([\w-]{11})'}
  73. or $content =~ m{src="https://www.youtube.com/embed/([\w-]{11})}) {
  74. system 'youtube-viewer', "--no-interactive", "--id=$1", ($opt{f} ? '-fs' : ());
  75. }
  76. elsif ( $content =~ /"file"\h*,\h*"(.*?)"/
  77. or $content =~ m{source type="video/mp4" src="(https://.*?)"}) {
  78. system('mpv', ($opt{f} ? '--fullscreen' : ()), $1);
  79. }
  80. else {
  81. warn "error: can't find any streaming URL for: $real_url\n";
  82. return;
  83. }
  84. return 1;
  85. }
  86. my @results;
  87. foreach my $url ("$BASE_URL/feed/?hot=1", "$BASE_URL/feed/") {
  88. my $hash_xml = xml2hash(encode_utf8($mech->get($url)->decoded_content));
  89. push @results, @{$hash_xml->{rss}{channel}{item}};
  90. }
  91. sub play_picked_videos {
  92. my (@list) = @_;
  93. $#list >= 0 or return;
  94. foreach my $num (@list) {
  95. play($results[$num - 1]->{link});
  96. }
  97. return 1;
  98. }
  99. sub play_random_video {
  100. play("$BASE_URL/random/");
  101. return 1;
  102. }
  103. sub parse_date {
  104. my ($date) = @_;
  105. # Turns "Mon, 06 Feb 2012 00:00:00 -0600" into "Feb 06"
  106. if ($date =~ /^\S+ (\d+) (\S+)/) {
  107. return "$2 $1";
  108. }
  109. return $date // '';
  110. }
  111. {
  112. print "\n";
  113. my $num = 0;
  114. foreach my $video (@results) {
  115. $video->{title} =~ s/\s*\[VIDEO\]//;
  116. printf "%s. %s [%s]\n", colored(sprintf("%2d", ++$num), 'bold'), $video->{title}, parse_date($video->{pubDate});
  117. }
  118. {
  119. my $line = $term->readline(colored("\n=>> Insert a number ('?' for help)", 'bold') . "\n> ");
  120. if ($line eq 'help' or $line eq '?') {
  121. print "\n", <<'STDIN_HELP';
  122. i : play the corresponding video
  123. all : play all the video results
  124. 3-8, 3..8 : same as 3 4 5 6 7 8
  125. /my?[regex]*$/ : play videos matched by a regex (/i)
  126. q, quit, exit : exit application
  127. STDIN_HELP
  128. redo;
  129. }
  130. elsif ($line =~ /^(?:q|quit|exit)\z/) {
  131. exit 0;
  132. }
  133. elsif ($line eq 'all') {
  134. play_picked_videos(1 .. @results);
  135. }
  136. elsif ($line =~ m{^/(.*?)/\h*$}) {
  137. my $match = eval { qr/$1/i };
  138. if ($@) {
  139. warn "\nError in regex: $@\n";
  140. redo;
  141. }
  142. play_picked_videos(grep { $results[$_ - 1]->{'title'} =~ /$match/ } 1 .. @results) || do {
  143. warn "\n(X_X) No video matched by the regex: /$match/\n";
  144. redo;
  145. };
  146. }
  147. elsif ($line =~ /\d/ and not $line =~ /(?>\s|^)[^\d-]/) {
  148. $line =~ s/(\d+)(?>-|\.\.)(\d+)/join q{ }, $1 .. $2;/eg; # '2..5' or '2-5' to '2 3 4 5'
  149. play_picked_videos(grep { $_ > 0 and $_ <= @results if /^\d+$/ } split(/[\s[:punct:]]+/, $line));
  150. }
  151. elsif ($line =~ /^(?:r|random)\z/) {
  152. play_random_video();
  153. }
  154. elsif ($line =~ m{^https?://.}) {
  155. play($_);
  156. }
  157. }
  158. redo;
  159. }