File Coverage

blib/lib/StreamFinder/Youtube.pm
Criterion Covered Total %
statement 18 227 7.9
branch 0 140 0.0
condition 0 39 0.0
subroutine 6 17 35.2
pod 11 11 100.0
total 35 434 8.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             StreamFinder::Youtube - Fetch actual raw streamable URLs from YouTube and others.
4              
5             =head1 AUTHOR
6              
7             This module is Copyright (C) 2017-2019 by
8              
9             Jim Turner, C<< >>
10            
11             Email: turnerjw784@yahoo.com
12              
13             All rights reserved.
14              
15             You may distribute this module under the terms of either the GNU General
16             Public License or the Artistic License, as specified in the Perl README
17             file.
18              
19             =head1 SYNOPSIS
20              
21             use strict;
22              
23             use StreamFinder::Youtube;
24              
25             my $video = new StreamFinder::Youtube();
26              
27             die "Invalid URL or no streams found!\n" unless ($video);
28              
29             my $firstStream = $video->get();
30              
31             print "First Stream URL=$firstStream\n";
32              
33             my $url = $video->getURL();
34              
35             print "Stream URL=$url\n";
36              
37             my $videoTitle = $video->getTitle();
38            
39             print "Title=$videoTitle\n";
40            
41             my $videoDescription = $video->getTitle('desc');
42            
43             print "Description=$videoDescription\n";
44            
45             my $videoID = $video->getID();
46              
47             print "Video ID=$videoID\n";
48            
49             my $artist = $video->{'artist'};
50              
51             print "Artist=$artist\n" if ($artist);
52            
53             my $icon_url = $video->getIconURL();
54              
55             if ($icon_url) { #SAVE THE ICON TO A TEMP. FILE:
56              
57             my ($image_ext, $icon_image) = $video->getIconData();
58              
59             if ($icon_image && open IMGOUT, ">/tmp/${videoID}.$image_ext") {
60              
61             binmode IMGOUT;
62              
63             print IMGOUT $icon_image;
64              
65             close IMGOUT;
66              
67             }
68              
69             }
70              
71             my $stream_count = $video->count();
72              
73             print "--Stream count=$stream_count=\n";
74              
75             my @streams = $video->get();
76              
77             foreach my $s (@streams) {
78              
79             print "------ stream URL=$s=\n";
80              
81             }
82              
83             =head1 DESCRIPTION
84              
85             StreamFinder::Youtube accepts a valid full YouTube video ID or URL on
86             youtube, et. al. that the "youtube-dl" program supports,
87             and returns the actual stream URL, title, and cover art icon for that video.
88             The purpose is that one needs this URL in order to have the option to
89             stream the video in one's own choice of media player software rather
90             than using their web browser and accepting any / all flash, ads,
91             javascript, cookies, trackers, web-bugs, and other crapware that can
92             come with that method of playing. The author uses his own custom all-purpose
93             media player called "fauxdacious" (his custom hacked version of the
94             open-source "audacious" audio player). "fauxdacious" incorporates this
95             module to decode and play youtube.com videos. This is a submodule of the
96             general StreamFinder module.
97              
98             Depends:
99              
100             L, L, L,
101             and the separate application program: youtube-dl.
102              
103             =head1 SUBROUTINES/METHODS
104              
105             =over 4
106              
107             =item B(I [, "debug" [ => 0|(1)|2 ]] [, "fast" [ => 0|(1) ]])
108              
109             Accepts a youtube.com video ID, or any full URL that youtube-dl supports
110             and creates and returns a new video object, or I if the URL is
111             not a youtube-supported video URL or no streams are found. The URL can
112             be the full URL,
113             ie. https://www.youtube.com/watch?v=B, or just I
114             (if the site is www.youtube.com, since YouTube has multiple sites).
115              
116             If "fast" or "-fast" is specified (or set to 1), a separate probe of the
117             page to fetch the video's title and artist is skipped. This is useful
118             if you know the video is NOT a YouTube video or you don't care about
119             the artist (youtube channel's owner) field.
120              
121             =item $video->B()
122              
123             Returns an array of strings representing all stream URLs found.
124              
125             =item $video->B([I])
126              
127             Similar to B() except it only returns a single stream representing
128             the first valid stream found.
129              
130             Current options are: I<"random"> and I<"noplaylists">. By default, the
131             first ("best"?) stream is returned. If I<"random"> is specified, then
132             a random one is selected from the list of streams found.
133             If I<"noplaylists"> is specified, and the stream to be returned is a
134             "playlist" (.pls or .m3u? extension), it is first fetched and the first entry
135             in the playlist is returned. This is needed by Fauxdacious Mediaplayer.
136              
137             =item $video->B()
138              
139             Returns the number of streams found for the video.
140              
141             =item $video->B()
142              
143             Returns the video's YouTube ID (numeric).
144              
145             =item $video->B(['desc'])
146              
147             Returns the station's title, or (long description).
148              
149             =item $video->B()
150              
151             Returns the URL for the video's "cover art" icon image, if any.
152              
153             =item $video->B()
154              
155             Returns a two-element array consisting of the extension (ie. "png",
156             "gif", "jpeg", etc.) and the actual icon image (binary data), if any.
157              
158             =item $video->B()
159              
160             Returns the URL for the video's "cover art" banner image, which for
161             YouTube videos is always the icon image, as YouTube does not support
162             a separate banner image at this time.
163              
164             =item $video->B()
165              
166             Returns a two-element array consisting of the extension (ie. "png",
167             "gif", "jpeg", etc.) and the actual video's banner image (binary data).
168              
169             =item $video->B()
170              
171             Returns the video's type ("Youtube").
172              
173             =back
174              
175             =head1 CONFIGURATION FILES
176              
177             =over 4
178              
179             =item ~/.config/StreamFinder/Youtube/config
180              
181             Optional text file for specifying various configuration options
182             for a specific site (submodule). Each option is specified on a
183             separate line in the format below:
184              
185             'option' => 'value' [,]
186              
187             and the options are loaded into a hash used only by the specific
188             (submodule) specified. Valid options include
189             I<-debug> => [0|1|2], and most of the L options.
190             Blank lines and lines starting with a "#" sign are ignored.
191              
192             Options specified here override any specified in I<~/.config/StreamFinder/config>.
193              
194             =item ~/.config/StreamFinder/config
195              
196             Optional text file for specifying various configuration options.
197             Each option is specified on a separate line in the format below:
198              
199             'option' => 'value' [,]
200              
201             and the options are loaded into a hash used by all sites
202             (submodules) that support them. Valid options include
203             I<-debug> => [0|1|2], and most of the L options.
204              
205             =back
206              
207             NOTE: Options specified in the options parameter list will override
208             those corresponding options specified in these files.
209              
210             =head1 KEYWORDS
211              
212             youtube
213              
214             =head1 DEPENDENCIES
215              
216             youtube-dl
217              
218             L, L, L, youtube-dl
219              
220             =head1 RECCOMENDS
221              
222             wget
223              
224             =head1 BUGS
225              
226             Please report any bugs or feature requests to C, or through
227             the web interface at L. I will be notified, and then you'll
228             automatically be notified of progress on your bug as I make changes.
229              
230             =head1 SUPPORT
231              
232             You can find documentation for this module with the perldoc command.
233              
234             perldoc StreamFinder::Youtube
235              
236             You can also look for information at:
237              
238             =over 4
239              
240             =item * RT: CPAN's request tracker (report bugs here)
241              
242             L
243              
244             =item * AnnoCPAN: Annotated CPAN documentation
245              
246             L
247              
248             =item * CPAN Ratings
249              
250             L
251              
252             =item * Search CPAN
253              
254             L
255              
256             =back
257              
258             =head1 LICENSE AND COPYRIGHT
259              
260             Copyright 2017-2019 Jim Turner.
261              
262             This program is free software; you can redistribute it and/or modify it
263             under the terms of the the Artistic License (2.0). You may obtain a
264             copy of the full license at:
265              
266             L
267              
268             Any use, modification, and distribution of the Standard or Modified
269             Versions is governed by this Artistic License. By using, modifying or
270             distributing the Package, you accept this license. Do not use, modify,
271             or distribute the Package, if you do not accept this license.
272              
273             If your Modified Version has been derived from a Modified Version made
274             by someone other than you, you are nevertheless required to ensure that
275             your Modified Version complies with the requirements of this license.
276              
277             This license does not grant you the right to use any trademark, service
278             mark, tradename, or logo of the Copyright Holder.
279              
280             This license includes the non-exclusive, worldwide, free-of-charge
281             patent license to make, have made, use, offer to sell, sell, import and
282             otherwise transfer the Package with respect to any patent claims
283             licensable by the Copyright Holder that are necessarily infringed by the
284             Package. If you institute patent litigation (including a cross-claim or
285             counterclaim) against any party alleging that the Package constitutes
286             direct or contributory patent infringement, then this Artistic License
287             to you shall terminate on the date that such litigation is filed.
288              
289             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
290             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
291             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
292             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
293             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
294             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
295             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
296             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
297              
298             =cut
299              
300             package StreamFinder::Youtube;
301              
302 1     1   6 use strict;
  1         2  
  1         27  
303 1     1   4 use warnings;
  1         2  
  1         22  
304 1     1   4 use URI::Escape;
  1         2  
  1         41  
305 1     1   5 use HTML::Entities ();
  1         2  
  1         10  
306 1     1   4 use LWP::UserAgent ();
  1         2  
  1         26  
307 1     1   5 use vars qw(@ISA @EXPORT);
  1         2  
  1         2812  
308              
309             my $DEBUG = 0;
310             my $FAST = 0;
311             my %uops = ();
312             my @userAgentOps = ();
313              
314             require Exporter;
315              
316             @ISA = qw(Exporter);
317             @EXPORT = qw(get getURL getType getID getTitle getIconURL getIconData getImageURL getImageData);
318              
319             sub new
320             {
321 0     0 1   my $class = shift;
322 0           my $url = shift;
323              
324 0           my $self = {};
325 0 0         return undef unless ($url);
326              
327 0           foreach my $p ("$ENV{HOME}/.config/StreamFinder/config", "$ENV{HOME}/.config/StreamFinder/Youtube/config") {
328 0 0         if (open IN, $p) {
329 0           my ($atr, $val);
330 0           while () {
331 0           chomp;
332 0 0         next if (/^\s*\#/o);
333 0           ($atr, $val) = split(/\s*\=\>\s*/o, $_, 2);
334 0           eval "\$uops{$atr} = $val";
335             }
336 0           close IN;
337             }
338             }
339 0           foreach my $i (qw(agent from conn_cache default_headers local_address ssl_opts max_size
340             max_redirect parse_head protocols_allowed protocols_forbidden requests_redirectable
341             proxy no_proxy)) {
342 0 0         push @userAgentOps, $i, $uops{$i} if (defined $uops{$i});
343             }
344             push (@userAgentOps, 'agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0')
345 0 0         unless (defined $uops{'agent'});
346 0 0         $uops{'timeout'} = 10 unless (defined $uops{'timeout'});
347 0 0         $DEBUG = $uops{'debug'} if (defined $uops{'debug'});
348 0 0         $FAST = $uops{'fast'} if (defined $uops{'fast'});
349              
350 0           while (@_) {
351 0 0         if ($_[0] =~ /^\-?debug$/o) {
    0          
352 0           shift;
353 0 0 0       $DEBUG = (defined($_[0]) && $_[0] =~/^[0-9]$/) ? shift : 1;
354             } elsif ($_[0] =~ /^\-?fast$/o) {
355 0           shift;
356 0 0 0       $FAST = (defined($_[0]) && $_[0] =~/^[0-9]$/) ? shift : 1;
357             }
358             }
359              
360 0 0         print STDERR "-0(Youtube): URL=$url=\n" if ($DEBUG);
361 0           $url =~ s/\?autoplay\=true$//; #STRIP THIS OFF SO WE DON'T HAVE TO.
362 0           (my $url2fetch = $url);
363             #DEPRECIATED (STATION-IDS NOW INCLUDE STUFF BEFORE THE DASH: ($self->{'id'} = $url) =~ s#^.*\-([a-z]\d+)\/?$#$1#;
364 0 0         if ($url2fetch =~ m#^https?\:#) {
365 0 0         $self->{'id'} = $1 if ($url2fetch =~ m#\/([^\/]+)\/?$#);
366 0           $self->{'id'} =~ s/^watch\?v\=//;
367 0           $self->{'id'} =~ s/[\?\&].*$//;
368             } else {
369 0           $self->{'id'} = $url;
370 0           $url2fetch = 'https://www.youtube.com/watch?v=' . $url;
371             }
372 0 0         print STDERR "-1 FETCHING URL=$url= VIA youtube-dl: ID=".$self->{'id'}."=\n" if ($DEBUG);
373 0           $self->{'iconurl'} = '';
374 0           $self->{'title'} = '';
375 0           $self->{'artist'} = '';
376              
377             #FIRST: GET STREAMS, THUMBNAIL, ETC. FROM youtube-dl:
378              
379             my $ytdlArgs = '--get-url --get-thumbnail --get-title --get-description -f "'
380             . ((defined $uops{'format'}) ? $uops{'format'} : 'mp4')
381 0 0         . '" ' . ((defined $uops{'youtube-dl-args'}) ? $uops{'youtube-dl-args'} : '');
    0          
382 0           my $try = 0;
383 0           my ($more, @ytdldata);
384             RETRYIT:
385 0 0 0       if (defined($uops{'userid'}) && defined($uops{'userpw'})) { #USER HAS A LOGIN CONFIGURED:
386 0           my $uid = $uops{'userid'};
387 0           my $upw = $uops{'userpw'};
388 0           $_ = `youtube-dl --username "$uid" --password "$upw" $ytdlArgs "$url"`;
389             } else {
390 0           $_ = `youtube-dl --get-url $ytdlArgs "$url"`;
391             }
392 0 0         print STDERR "--TRY($try of 1): youtube-dl returned=$_= ARGS=$ytdlArgs=\n" if ($DEBUG);
393 0           @ytdldata = split /\r?\n/s;
394 0 0 0       unless ($try || scalar(@ytdldata) > 0) { #IF NOTHING FOUND, RETRY WITHOUT THE SPECIFIC FILE-FORMAT:
395 0           print STDERR "..1:No MP4 streams found, try again for any (audio, etc.)...\n";
396 0           $try++;
397 0           $ytdlArgs =~ s/\-f\s+\"([^\"]+)\"//;
398 0 0         goto RETRYIT if ($1);
399             }
400 0 0         return undef unless (scalar(@ytdldata) > 0);
401              
402 0 0         unless ($ytdldata[0] =~ m#^https?\:\/\/#) {
403 0           $_ = shift(@ytdldata);
404 0   0       $self->{'title'} ||= $_;
405             }
406 0           $self->{'description'} = '';
407 0           $more = 1;
408 0           while (@ytdldata) {
409 0           $_ = shift @ytdldata;
410 0 0         $more = 0 unless (m#^https?\:\/\/#o);
411 0 0         if ($more) {
412 0           push @{$self->{'streams'}}, $_;
  0            
413             } else {
414 0           $self->{'description'} .= $_ . ' ';
415             }
416             }
417 0           $self->{'cnt'} = scalar @{$self->{'streams'}};
  0            
418 0 0         $self->{'iconurl'} = pop(@{$self->{'streams'}}) if ($self->{'cnt'} > 1);
  0            
419 0           $self->{'cnt'} = scalar @{$self->{'streams'}};
  0            
420 0 0 0       unless ($try || $self->{'cnt'} > 0) { #IF NO STREAMS FOUND, RETRY WITHOUT THE SPECIFIC FILE-FORMAT:
421 0           print STDERR "..2:No MP4 streams found, try again for any (audio, etc.)...\n";
422 0           $try++;
423 0           $ytdlArgs =~ s/\-f\s+\"([^\"]+)\"//;
424 0 0         goto RETRYIT if ($1);
425             }
426              
427             #NOW MANUALLY SCAN PAGE TO TRY TO GET artist, description, year, ETC. DIRECTLY FROM PAGE (IF A YOUTUBE SITE):
428              
429 0 0 0       unless ($FAST || $url2fetch !~ /\b(?:youtube\.|youtu.be|ytimg\.)\b/) {
430 0 0         print STDERR "-2 FETCHING SCREEN URL=$url2fetch= ID=".$self->{'id'}."=\n" if ($DEBUG);
431 0           my $ua = LWP::UserAgent->new(@userAgentOps);
432 0           $ua->timeout($uops{'timeout'});
433 0           $ua->cookie_jar({});
434 0           $ua->env_proxy;
435 0           my $html = '';
436 0           my $response = $ua->get($url2fetch);
437 0 0         if ($response->is_success) {
438 0           $html = $response->decoded_content;
439             } else {
440 0 0         print STDERR $response->status_line if ($DEBUG);
441 0           my $no_wget = system('wget','-V');
442 0 0         unless ($no_wget) {
443 0 0         print STDERR "\n..trying wget...\n" if ($DEBUG);
444 0           $html = `wget -t 2 -T 20 -O- -o /dev/null \"$url2fetch\" 2>/dev/null `;
445             }
446             }
447 0           $html =~ s/\\\"/\"\;/gs;
448 0 0         if ($html =~ s#\]\}\,\"title\"\:\{\"runs\"\:\[\{\"text\"\:\"([^\"]+)\"\,\"navigationEndpoint\"\:([^\}]+)##s) {
449 0           my $two = $2;
450 0           $self->{'artist'} = $1;
451 0 0         $self->{'artist'} .= ' - https://www.youtube.com' . $1 if ($two =~ m#\"url\"\:\"([^\"]+)#);
452             }
453 0 0         if ($html =~ s#\"videoDetails\"\:\{\"videoId\"\:\"([^\"]+)\"([^\}]+)##s) {
454 0           my $two = $2;
455 0           $self->{'id'} = $1;
456 0 0         $self->{'title'} = $1 if ($two =~ m#\"title\"\:\"([^\"]+)#);
457 0 0         $self->{'iconurl'} = $1 if ($two =~ m#\"thumbnails\"\:\[\{\"url\"\:\"([^\"]+)#);
458 0           $self->{'iconurl'} =~ s/\?.*$//;
459             }
460 0 0         if ($html =~ m#\"dateText\"\:\{([^\}]+)\}#s) {
461 0           my $one = $1;
462 0 0         $self->{'year'} = $1 if ($one =~ /(\d\d\d\d)/);
463             }
464             }
465 0           $self->{'total'} = $self->{'cnt'};
466 0           $self->{'imageurl'} = $self->{'iconurl'};
467 0 0         $self->{'Url'} = ($self->{'cnt'} > 0) ? $self->{'streams'}->[0] : '';
468 0 0         print STDERR "-count=".$self->{'cnt'}."= iconurl=".$self->{'iconurl'}."=\n" if ($DEBUG);
469 0 0         print STDERR "--SUCCESS: stream url=".$self->{'Url'}."=\n" if ($DEBUG);
470 0 0         if ($self->{'description'} =~ /\w/) {
471 0           $self->{'description'} =~ s/\s+$//;
472             } else {
473 0           $self->{'description'} = $self->{'title'};
474             }
475 0           foreach my $i (qw(title artist description)) {
476 0           $self->{$i} = HTML::Entities::decode_entities($self->{$i});
477 0           $self->{$i} = uri_unescape($self->{$i});
478 0           $self->{$i} =~ s/(?:\%|\\?u?00)([0-9A-Fa-f]{2})/chr(hex($1))/egso;
  0            
479             }
480 0 0         print STDERR "-2: title=".$self->{'title'}."= id=".$self->{'id'}."= artist=".$self->{'artist'}."= year(Published)=".$self->{'year'}."=\n" if ($DEBUG);
481             #print STDERR "\n--ID=".$self->{'id'}."=\n--TITLE=".$self->{'title'}."=\n--CNT=".$self->{'cnt'}."=\n--ICON=".$self->{'iconurl'}."=\n--1ST=".$self->{'Url'}."=\n" if ($DEBUG);
482              
483 0           bless $self, $class; #BLESS IT!
484              
485 0           return $self;
486             }
487              
488             sub get
489             {
490 0     0 1   my $self = shift;
491              
492 0 0         return wantarray ? @{$self->{'streams'}} : $self->{'Url'};
  0            
493             }
494              
495             sub getURL #LIKE GET, BUT ONLY RETURN THE SINGLE ONE W/BEST BANDWIDTH AND RELIABILITY:
496             {
497 0     0 1   my $self = shift;
498 0 0         my $arglist = (defined $_[0]) ? join('|',@_) : '';
499 0 0         my $idx = ($arglist =~ /\b\-?random\b/) ? int rand scalar @{$self->{'streams'}} : 0;
  0            
500 0 0 0       if ($arglist =~ /\b\-?noplaylists\b/ && ${$self->{'streams'}}[$idx] =~ /\.(pls|m3u8?)$/i) {
  0            
501 0           my $plType = $1;
502 0           my $firstStream = ${$self->{'streams'}}[$idx];
  0            
503 0 0         print STDERR "-YT:getURL($idx): NOPLAYLISTS and (".${$self->{'streams'}}[$idx].")\n" if ($DEBUG);
  0            
504 0           my $ua = LWP::UserAgent->new(@userAgentOps);
505 0           $ua->timeout($uops{'timeout'});
506 0           $ua->cookie_jar({});
507 0           $ua->env_proxy;
508 0           my $html = '';
509 0           my $response = $ua->get($firstStream);
510 0 0         if ($response->is_success) {
511 0           $html = $response->decoded_content;
512             } else {
513 0 0         print STDERR $response->status_line if ($DEBUG);
514 0           my $no_wget = system('wget','-V');
515 0 0         unless ($no_wget) {
516 0 0         print STDERR "\n..trying wget...\n" if ($DEBUG);
517 0           $html = `wget -t 2 -T 20 -O- -o /dev/null \"$firstStream\" 2>/dev/null `;
518             }
519             }
520 0           my @lines = split(/\r?\n/, $html);
521 0           $firstStream = '';
522 0 0         if ($plType =~ /pls/) { #PLS:
523 0           my $firstTitle = '';
524 0           foreach my $line (@lines) {
525 0 0         if ($line =~ m#^\s*File\d+\=(.+)$#o) {
    0          
526 0   0       $firstStream ||= $1;
527             } elsif ($line =~ m#^\s*Title\d+\=(.+)$#o) {
528 0   0       $firstTitle ||= $1;
529             }
530             }
531 0   0       $self->{'title'} ||= $firstTitle;
532 0           $self->{'title'} = HTML::Entities::decode_entities($self->{'title'});
533 0           $self->{'title'} = uri_unescape($self->{'title'});
534 0 0         print STDERR "-YT:getURL(PLS): first=$firstStream= title=$firstTitle=\n" if ($DEBUG);
535             } else { #m3u8:
536 0           (my $urlpath = ${$self->{'streams'}}[$idx]) =~ s#[^\/]+$##;
  0            
537 0           foreach my $line (@lines) {
538 0 0         if ($line =~ m#^\s*([^\#].+)$#o) {
539 0           my $urlpart = $1;
540 0           $urlpart =~ s#^\s+##o;
541 0           $urlpart =~ s#^\/##o;
542 0 0         $firstStream = ($urlpart =~ m#https?\:#o) ? $urlpart : ($urlpath . $urlpart);
543 0           last;
544             }
545             }
546 0 0         print STDERR "-YT:getURL(m3u?): first=$firstStream=\n" if ($DEBUG);
547             }
548 0   0       return $firstStream || ${$self->{'streams'}}[$idx];
549             }
550 0           return ${$self->{'streams'}}[$idx];
  0            
551             }
552              
553             sub count
554             {
555 0     0 1   my $self = shift;
556 0           return $self->{'total'}; #TOTAL NUMBER OF PLAYABLE STREAM URLS FOUND.
557             }
558              
559             sub getType
560             {
561 0     0 1   my $self = shift;
562 0           return 'Youtube'; #STATION TYPE (FOR PARENT StreamFinder MODULE).
563             }
564              
565             sub getID
566             {
567 0     0 1   my $self = shift;
568 0           return $self->{'id'}; #VIDEO'S YOUTUBE-ID.
569             }
570              
571             sub getTitle
572             {
573 0     0 1   my $self = shift;
574 0 0 0       return $self->{'description'} if (defined($_[0]) && $_[0] =~ /^\-?(?:long|desc)/i);
575 0           return $self->{'title'}; #STATION'S TITLE(DESCRIPTION), IF ANY.
576             }
577              
578             sub getIconURL
579             {
580 0     0 1   my $self = shift;
581 0           return $self->{'iconurl'}; #URL TO THE VIDEO'S THUMBNAIL ICON, IF ANY.
582             }
583              
584             sub getIconData
585             {
586 0     0 1   my $self = shift;
587 0 0         return () unless ($self->{'iconurl'});
588 0           my $ua = LWP::UserAgent->new(@userAgentOps);
589 0           $ua->timeout($uops{'timeout'});
590 0           $ua->cookie_jar({});
591 0           $ua->env_proxy;
592 0           my $art_image = '';
593 0           my $response = $ua->get($self->{'iconurl'});
594 0 0         if ($response->is_success) {
595 0           $art_image = $response->decoded_content;
596             } else {
597 0 0         print STDERR $response->status_line if ($DEBUG);
598 0           my $no_wget = system('wget','-V');
599 0 0         unless ($no_wget) {
600 0 0         print STDERR "\n..trying wget...\n" if ($DEBUG);
601 0           my $iconUrl = $self->{'iconurl'};
602 0           $art_image = `wget -t 2 -T 20 -O- -o /dev/null \"$iconUrl\" 2>/dev/null `;
603             }
604             }
605 0 0         return () unless ($art_image);
606 0           (my $image_ext = $self->{'iconurl'}) =~ s/^.+\.//;
607 0           $image_ext =~ s/[^A-Za-z].*$//;
608              
609 0           return ($image_ext, $art_image);
610             }
611              
612             sub getImageURL
613             {
614 0     0 1   my $self = shift;
615 0           return $self->{'imageurl'}; #URL TO THE VIDEO'S BANNER IMAGE, IF ANY.
616             }
617              
618             sub getImageData
619             {
620 0     0 1   my $self = shift;
621 0           return $self->getIconData();
622             }
623              
624             1