212 lines
4.5 KiB
Perl
Executable file
212 lines
4.5 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.
|
|
|
|
=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) {
|
|
my @existing =
|
|
uniq(
|
|
grep { -f $_ }
|
|
map { $a=$b=$_; $b=~s/.part//; s/.temp//; $a,$b,$_ } @files
|
|
);
|
|
for (my $i = 0; $i <= $#existing; $i++) {
|
|
my $answer;
|
|
$_ = $existing[$i];
|
|
do {
|
|
print "KODI playing $_\n";
|
|
if(not fork()) {
|
|
exec("idok", $_);
|
|
}
|
|
print "Press (r)etry, (d)elete, (p)revious, (n)ext\n";
|
|
$answer = undef;
|
|
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) {
|
|
$i -= 2;
|
|
}
|
|
}
|
|
} while($answer =~ /^r?$/i);
|
|
}
|
|
if($answer =~ m,http.?://,) {
|
|
play($answer);
|
|
}
|
|
} 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 } @_ }};
|
|
}
|