tangetools/ytv/ytv

228 lines
4.8 KiB
Perl
Executable file

#!/usr/bin/perl
=pod
=head1 NAME
ytv - Start downloading youtube video and play it immediately
=head1 SYNOPSIS
B<ytv> [--tor] [--kodi] I<youtube-url>
cat urlfile | B<ytv> [--tor] [--kodi]
=head1 DESCRIPTION
B<ytv> starts B<youtube-dl> in the background. When the partial
downloaded file exists, it is played by B<vlc>.
=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<kodi> 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 <http://www.gnu.org/licenses/>.
=head1 DEPENDENCIES
B<ytv> uses B<youtube-dl> and B<vlc>.
=head1 SEE ALSO
B<ytv>, B<vlc>
=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 } @_ }};
}