#!/usr/bin/perl =pod =head1 NAME ytv - Start downloading youtube video and play it immediately =head1 SYNOPSIS B [--tor] [--kodi] I cat urlfile | B [--tor] [--kodi] =head1 DESCRIPTION B starts B in the background. When the partial downloaded file exists, it is played by B. =head1 OPTIONS =over 4 =item B<--tor> Use tor proxy to download. It assumes you are running a tor proxy on 127.0.0.1:9050. =item B<--kodi> Play video using remote B server. =back =head1 AUTHOR Copyright (C) 2017-2019 Ole Tange, http://ole.tange.dk and Free Software Foundation, Inc. =head1 LICENSE Copyright (C) 2012 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or at your option any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . =head1 DEPENDENCIES B uses B and B. =head1 SEE ALSO B, B =cut use Getopt::Long; Getopt::Long::Configure("bundling","require_order"); get_options_from_array(\@ARGV) || die_usage(); my @tor = $opt::tor ? qw(--proxy socks4a://127.0.0.1:9050/) : (); `mkdir -p ~/tmp/Videos`; chdir($ENV{'HOME'}."/tmp/Videos"); map { $before{$_} = -M $_ } <*>; my @youtubeid; for (@ARGV) { play($_) } if(not @ARGV) { while(<>) { chomp; play($_) } } sub playfiles { my @files = @_; if($opt::kodi) { # Playlist includes partial files my @plist = uniq(map { $a=$b=$_; $b=~s/.part//; s/.temp//; $a,$b,$_ } @files); my $i = 0; while(1) { $_ = $plist[$i]; if(-e $_) { # Only try playing if the file exists print "KODI playing $_\n"; if(not fork()) { exec("idok", $_); } print "Press (r)etry, (d)elete, (p)revious, (n)ext\n"; my $answer; while(not defined $answer) { open(my $tty_fh, "<", "/dev/tty") || ::die_bug("interactive-tty"); $answer = <$tty_fh>; close $tty_fh; } if($answer =~ /^d$/i) { unlink $_; } if($answer =~ /^p$/i) { # Find previous existing file do { $i--; } until (-e $plist[$i] or $i == 0); } if($answer =~ /^n$/i) { # Find next existing file do { $i++; } until (-e $plist[$i] or $i > $#plist); } } else { # Find the first existing file do { $i++; } until (-e $plist[$i] or $i > $#plist); } if($i > $#plist) { last; } } } else { system("vlc", map { $a=$b=$_; $b=~s/.part//; s/.temp//; $a,$b,$_ } @files); } } sub play { my $url = shift; print "Playing $url\n"; if(-e $url) { playfiles($url); return; } if(not fork()) { # Download in the background system(qw(youtube-dl --all-subs --skip-download), @tor,$url); system(qw(youtube-dl -f best), @tor,$url); exit; } do { # Sleep until there are matching files @new = ( # Remove fragments and subtitles grep { !/vtt$|Frag\d/ } # Include $url if that is a file (grep { -e $_ } $url), # Look for files > 1M, and that we have not seen before (grep { -s $_ > 1000000 } grep { not $before{$_} or $before{$_} > -M $_ } <*>), # Include all files that match the id if $urls is a youtube ID map { my $id = $_; grep { /$id/ } <*> } grep { s/^.*watch.v=([a-z0-9]+).*$/$1/i or s/^.*youtu.be.([a-z0-9]+).*$/$1/i } $url); usleep(10); } until @new; while(@new) { # Mark as seen map { $before{$_} = -M $_ } @new; # Run VLC on the matching files playfiles(@new); @new = grep { not $before{$_} or $before{$_} > -M $_ } <*>; } } sub get_options_from_array { # Run GetOptions on @array # Returns: # true if parsing worked # false if parsing failed # @array is changed my $array_ref = shift; # A bit of shuffling of @ARGV needed as GetOptionsFromArray is not # supported everywhere my @save_argv; my $this_is_ARGV = (\@::ARGV == $array_ref); if(not $this_is_ARGV) { @save_argv = @::ARGV; @::ARGV = @{$array_ref}; } my @retval = GetOptions ("debug|D" => \$opt::debug, "tor" => \$opt::tor, "kodi|k" => \$opt::kodi, "version|V" => \$opt::version, ); if(not $this_is_ARGV) { @{$array_ref} = @::ARGV; @::ARGV = @save_argv; } return @retval; } sub usleep { # Sleep this many milliseconds. # Input: # $ms = milliseconds to sleep my $ms = shift; select(undef, undef, undef, $ms/1000); } sub uniq { # Remove duplicates and return unique values return keys %{{ map { $_ => 1 } @_ }}; }