Skip to content

Commit 0cd9a54

Browse files
committed
- Added the use_invidious_api config-option (disabled by default).
When enabled, it will the API of `invidio.us` to extract the streaming URLs for videos with encrypted signatures. By default, `youtube-dl` is used. Implements and closes https://github.com/trizen/youtube-viewer/issues/258.
1 parent b874e65 commit 0cd9a54

File tree

5 files changed

+92
-32
lines changed

5 files changed

+92
-32
lines changed

META.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,5 @@
117117
]
118118
},
119119
"version" : "v3.5.4",
120-
"x_serialization_backend" : "JSON::PP version 4.02"
120+
"x_serialization_backend" : "JSON::PP version 4.04"
121121
}

bin/gtk-youtube-viewer

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#-------------------------------------------------------
1616
# GTK YouTube Viewer
1717
# Created on: 12 September 2010
18-
# Latest edit on: 20 February 2019
18+
# Latest edit on: 11 July 2019
1919
# https://github.com/trizen/youtube-viewer
2020
#-------------------------------------------------------
2121

@@ -226,6 +226,8 @@ my %CONFIG = (
226226
fullscreen => 0,
227227
audio_only => 0,
228228

229+
use_invidious_api => 0,
230+
229231
thousand_separator => q{,},
230232
downloads_dir => curdir(),
231233
web_browser => undef, # defaults to $ENV{WEBBROWSER} or xdg-open
@@ -730,6 +732,7 @@ my $yv_obj = WWW::YoutubeViewer->new(
730732
hl => $CONFIG{hl},
731733
lwp_env_proxy => $CONFIG{env_proxy},
732734
cache_dir => $CONFIG{cache_dir},
735+
use_invidious_api => $CONFIG{use_invidious_api},
733736
authentication_file => $authentication_file,
734737
);
735738

@@ -787,7 +790,7 @@ sub apply_configuration {
787790
videoEmbeddable videoLicense
788791
publishedAfter publishedBefore
789792
regionCode videoCategoryId
790-
debug http_proxy
793+
debug http_proxy use_invidious_api
791794
)
792795
) {
793796

bin/youtube-viewer

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#-------------------------------------------------------
1616
# youtube-viewer
1717
# Created on: 02 June 2010
18-
# Latest edit on: 10 February 2019
18+
# Latest edit on: 11 July 2019
1919
# https://github.com/trizen/youtube-viewer
2020
#-------------------------------------------------------
2121
#
@@ -296,6 +296,7 @@ my %CONFIG = (
296296
cache_dir => undef, # will be defined later
297297

298298
# Others
299+
use_invidious_api => 0,
299300
http_proxy => undef,
300301
env_proxy => 1,
301302
confirm => 0,
@@ -611,6 +612,7 @@ my $yv_obj = WWW::YoutubeViewer->new(
611612
config_dir => $config_dir,
612613
cache_dir => $opt{cache_dir},
613614
lwp_env_proxy => $opt{env_proxy},
615+
use_invidious_api => $opt{use_invidious_api},
614616
authentication_file => $authentication_file,
615617
);
616618

@@ -799,6 +801,7 @@ usage: $execname [options] ([url] | [keywords])
799801
--use-colors! : enable or disable the ANSI colors for text
800802
801803
* Other
804+
--invidious! : use the API of invidio.us to get the streaming URLs
802805
--proxy=s : set HTTP(S)/SOCKS proxy: 'proto://domain.tld:port/'
803806
If authentication required,
804807
use 'proto://user:pass\@domain.tld:port/'
@@ -1478,6 +1481,7 @@ sub parse_arguments {
14781481
'merge-into-mkv|mkv-merge!' => \$opt{merge_into_mkv},
14791482
'merge-with-captions|merge-captions!' => \$opt{merge_with_captions},
14801483

1484+
'invidious!' => \$opt{use_invidious_api},
14811485
'convert-command|convert-cmd=s' => \$opt{convert_cmd},
14821486
'dash-m4a|dash-mp4-audio|dash-mp4a!' => \$opt{dash_mp4_audio},
14831487
'wget-dl|wget-download!' => \$opt{download_with_wget},

lib/WWW/YoutubeViewer.pm

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ my %valid_options = (
8383
escape_utf8 => {valid => [1, 0], default => 0},
8484
prefer_mp4 => {valid => [1, 0], default => 0},
8585

86+
use_invidious_api => {valid => [1, 0], default => 0},
87+
8688
# OAuth stuff
8789
client_id => {valid => [qr/^.{5}/], default => undef},
8890
client_secret => {valid => [qr/^.{5}/], default => undef},
@@ -251,15 +253,15 @@ sub set_lwp_useragent {
251253
or $response->request->method ne 'GET' # cache only GET requests
252254

253255
# don't cache if "cache-control" specifies "max-age=0" or "no-store"
254-
or $response->header('cache-control') =~ /\b(?:max-age=0|no-store)\b/
256+
or (($response->header('cache-control') // '') =~ /\b(?:max-age=0|no-store)\b/)
255257

256258
# don't cache video or audio files
257-
or $response->header('content-type') =~ /\b(?:video|audio)\b/;
259+
or (($response->header('content-type') // '') =~ /\b(?:video|audio)\b/);
258260
},
259261

260262
recache_if => sub {
261263
my ($response, $path) = @_;
262-
not($response->is_fresh) # recache if the response expired
264+
not($response->is_fresh) # recache if the response expired
263265
or ($response->code == 404 && -M $path > 1); # recache any 404 response older than 1 day
264266
}
265267
)
@@ -269,10 +271,10 @@ sub set_lwp_useragent {
269271
);
270272

271273
require LWP::ConnCache;
272-
my $cache = LWP::ConnCache->new;
274+
state $cache = LWP::ConnCache->new;
273275
$cache->total_capacity(undef); # no limit
274276

275-
my $accepted_encodings = do {
277+
state $accepted_encodings = do {
276278
require HTTP::Message;
277279
HTTP::Message::decodable();
278280
};
@@ -309,7 +311,7 @@ sub prepare_access_token {
309311
return;
310312
}
311313

312-
sub _get_lwp_header {
314+
sub _auth_lwp_header {
313315
my ($self) = @_;
314316

315317
my %lwp_header;
@@ -344,7 +346,7 @@ sub lwp_get {
344346
$url // return;
345347
$self->{lwp} // $self->set_lwp_useragent();
346348

347-
my %lwp_header = ($opt{simple} ? () : $self->_get_lwp_header);
349+
my %lwp_header = ($opt{simple} ? () : $self->_auth_lwp_header);
348350
my $response = $self->{lwp}->get($url, %lwp_header);
349351

350352
if ($response->is_success) {
@@ -358,7 +360,7 @@ sub lwp_get {
358360
$self->set_access_token($refresh_token->{access_token});
359361

360362
# Don't be tempted to use recursion here, because bad things will happen!
361-
$response = $self->{lwp}->get($url, $self->_get_lwp_header);
363+
$response = $self->{lwp}->get($url, $self->_auth_lwp_header);
362364

363365
if ($response->is_success) {
364366
$self->save_authentication_tokens();
@@ -490,38 +492,89 @@ sub _make_feed_url {
490492
$self->get_feeds_url() . $path . '?' . $self->default_arguments(%args);
491493
}
492494

493-
sub _get_formats_from_ytdl {
495+
sub _extract_from_indivious {
494496
my ($self, $videoID) = @_;
495497

498+
my $url = sprintf("https://invidio.us/api/v1/videos/%s?fields=formatStreams,adaptiveFormats", $videoID);
499+
500+
(my $resp = $self->{lwp}->get($url))->is_success() || return;
501+
my $json = $resp->decoded_content() // return;
502+
my $ref = $self->parse_json_string($json) // return;
503+
504+
my @formats;
505+
506+
# The entries are already in the format that we want.
507+
if (exists($ref->{adaptiveFormats}) and ref($ref->{adaptiveFormats}) eq 'ARRAY') {
508+
push @formats, @{$ref->{adaptiveFormats}};
509+
}
510+
511+
if (exists($ref->{formatStreams}) and ref($ref->{formatStreams}) eq 'ARRAY') {
512+
push @formats, @{$ref->{formatStreams}};
513+
}
514+
515+
return @formats;
516+
}
517+
518+
sub _fallback_extract_urls {
519+
my ($self, $videoID) = @_;
520+
521+
my @array;
522+
523+
if ($self->get_use_invidious_api) { # use the API of invidio.us
524+
525+
if ($self->get_debug) {
526+
say STDERR ":: Using the API of invidio.us...";
527+
}
528+
529+
push @array, $self->_extract_from_indivious($videoID);
530+
531+
if ($self->get_debug) {
532+
say STDERR ":: Found ", scalar(@array), " streaming URLs.";
533+
}
534+
535+
if (@array) {
536+
return @array;
537+
}
538+
}
539+
496540
((state $x = $self->proxy_system('youtube-dl', '--version')) == 0)
497541
|| return;
498542

543+
if ($self->get_debug) {
544+
say STDERR ":: Using youtube-dl to extract streaming URLs...";
545+
}
546+
499547
my $json = $self->proxy_stdout('youtube-dl', '--all-formats', '--dump-single-json',
500548
quotemeta("https://www.youtube.com/watch?v=" . $videoID));
501549

502-
my @array;
503550
my $ref = $self->parse_json_string($json) // return;
551+
504552
if (ref($ref) eq 'HASH' and exists($ref->{formats}) and ref($ref->{formats}) eq 'ARRAY') {
505553
foreach my $format (@{$ref->{formats}}) {
506554
if (exists($format->{format_id}) and exists($format->{url})) {
507555

508-
push @array,
509-
{
510-
itag => $format->{format_id},
511-
url => $format->{url},
512-
type => (
513-
(
514-
(defined($format->{format_note}) && $format->{format_note} eq 'DASH audio')
515-
? 'audio/'
516-
: 'video/'
517-
)
518-
. $format->{ext}
519-
),
520-
};
556+
my $entry = {
557+
itag => $format->{format_id},
558+
url => $format->{url},
559+
type => (
560+
(
561+
(defined($format->{format_note}) and $format->{format_note} eq 'DASH audio')
562+
? 'audio/'
563+
: 'video/'
564+
)
565+
. $format->{ext}
566+
),
567+
};
568+
569+
push @array, $entry;
521570
}
522571
}
523572
}
524573

574+
if ($self->get_debug) {
575+
say STDERR ":: Found ", scalar(@array), " streaming URLs.";
576+
}
577+
525578
return @array;
526579
}
527580

@@ -601,7 +654,7 @@ sub _extract_streaming_urls {
601654
foreach my $video (@results) {
602655
if (exists $video->{s}) { # has an encrypted signature :(
603656

604-
my @formats = $self->_get_formats_from_ytdl($videoID);
657+
my @formats = $self->_fallback_extract_urls($videoID);
605658

606659
foreach my $format (@formats) {
607660
foreach my $ref (@results) {
@@ -617,7 +670,7 @@ sub _extract_streaming_urls {
617670
}
618671

619672
if ($info->{livestream} or $info->{live_playback}) {
620-
if (my @formats = $self->_get_formats_from_ytdl($videoID)) {
673+
if (my @formats = $self->_fallback_extract_urls($videoID)) {
621674
@results = @formats;
622675
}
623676
elsif (exists $info->{hlsvp}) {
@@ -643,7 +696,7 @@ Returns a list of streaming URLs for a videoID.
643696
sub get_streaming_urls {
644697
my ($self, $videoID) = @_;
645698

646-
my $url = ($self->get_video_info_url() . sprintf($self->get_video_info_args(), $videoID));
699+
my $url = ($self->get_video_info_url() . sprintf($self->get_video_info_args(), $videoID));
647700
my $content = $self->lwp_get($url, simple => 1) // return;
648701
my %info = $self->parse_query_string($content);
649702

@@ -670,7 +723,7 @@ sub get_streaming_urls {
670723

671724
# Try again with youtube-dl
672725
if (!@streaming_urls or $info{status} =~ /fail|error/i) {
673-
@streaming_urls = $self->_get_formats_from_ytdl($videoID);
726+
@streaming_urls = $self->_fallback_extract_urls($videoID);
674727
}
675728

676729
if ($self->get_prefer_mp4) {

lib/WWW/YoutubeViewer/Videos.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ sub send_rating_to_video {
9797

9898
if ($rating eq 'none' or $rating eq 'like' or $rating eq 'dislike') {
9999
my $url = $self->_simple_feeds_url('videos/rate', id => $video_id, rating => $rating);
100-
return defined($self->lwp_post($url, $self->_get_lwp_header()));
100+
return defined($self->lwp_post($url, $self->_auth_lwp_header()));
101101
}
102102

103103
return;

0 commit comments

Comments
 (0)