Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/perl
- #
- # Copyright (C) 2010-2012 Trizen <echo dHJpemVueEBnbWFpbC5jb20K | base64 -d>.
- #
- # 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/>.
- #
- #-------------------------------------------------------
- # Appname: youtube-viewer
- # Created on: 02 June 2010
- # Latest edit on: 09 May 2012
- # Website: http://trizen.googlecode.com
- #-------------------------------------------------------
- #
- # [?] What is this script for?
- # - This script is useful to search and watch YouTube videos with MPlayer...
- # - Have fun!
- #
- # [!] The most important changes are written in the changelog!
- #
- # [CHANGELOG]
- # - Added support for detailed results (usage: -D or --details) // Support for comments - NEW (v2.5.8)
- # - Switched to Term::ReadLine for a better STDIN support // Better colors // Info support - NEW (v2.5.7)
- # - Added support for: -duration, -caption=s, -safe-search=s, -hd // Improved code quality - NEW (v2.5.6)
- # - Added support for configuration file, improved stability, improved debug mode - NEW (v2.5.5)
- # - Switched to XML::Fast for parsing gdata XML, in consequence, youtube-viewer is faster! - NEW (v2.5.5)
- # - Switched to Getopt::Long, added SIGINT handler and a better way to execute mplayer - NEW (v2.5.5)
- # - Added support to list playlists created by a specific user (usage: -up <USERNAME>) - NEW (v2.5.4)
- # - Improved parsing support for arguments, including arguments specified via STDIN. - NEW (v2.5.4)
- # - Added support to search for videos uploaded by a particular YouTube user (-author=USER) - NEW (v2.5.4)
- # - Added support to get video results starting with a predefined page (e.g.: -page=4) - NEW (v2.5.4)
- # - Added support for previous page and support to list youtube usernames from a file - (v2.5.2)
- # - Added few options to control cache of MPlayer and lower cache for lower video resolutions - (v2.5.1)
- # - Added colors for text (--use_colors), 360p support (-3), playlist support - (v2.5.0)
- # - Added support for today and all time Youtube tops (usage: -t, --tops, -a, --all-time) - (v2.4.*)
- # - Re-added the support for the next page / Added support for download (-d, --download) - (v2.4.*)
- # - Added support for Youtube CCaptions. (Depends on: 'gcap' - http://gcap.googlecode.com) - (v2.4.*)
- # - First version with Windows support. Require SMPlayer to play videos. See MPlayer Line - (v2.4.*)
- # - Code has been changed in a proportion of ~60% and optimized for speed // --480 became -4 - (v2.4.*)
- # - Added mega-powers of omnibox to the STDIN :) - (v2.3.*)
- # - Re-added the option to list and play youtube videos from a user profile. Usage: -u [user] - (v2.3.*)
- # - Added a new option to play only the audio track of a videoclip. Usage: [words] -n - (v2.3.*)
- # - Added option for fullscreen (-f, --fullscreen). Usage: youtube-viewer [words] -f - (v2.3.*)
- # - Added one new option '-c'. It shows available categories and will let you to choose one. - (v2.3.*)
- # - Added one new option '-m'. It shows 3 pages of youtube video results. Usage: [words] -m - (v2.3.*)
- # - For "-A" option has been added 3 pages of youtube video results (50 clips) - (v2.3.*)
- # - Added "-prefer-ipv4" to the mplayer line (videoclips starts in no time now). - (v2.3.*)
- # - Search and play videos at 480p, 720p. Ex: [words] --480, [words] -A --480 - (v2.3.*)
- # - Added support to play a video at 480p even if it's resolution is higher. Ex: [url] --480 - (v2.2.*)
- # - Added a nice feature which prints some informations about the current playing video - (v2.2.*)
- # - Added support to play videos by your order. Example: after search results, insert: 3 5 2 1 - (v2.1.*)
- # - Added support for next pages of video results (press <ENTER> after search results) - (v2.1.*)
- # - Added support to continue playing searched videos, usage: "youtube-viewer [words] -A" - (v2.1.*)
- # - Added support to print counted videos and support to insert a number instead of video code - (v2.1.*)
- # - Added support to search YouTube Videos in script (e.g.: youtube-viewer avatar trailer) - (v2.0.*)
- # - Added support for script to choose automat quality if it is lower than 1080p - (v2.0.*)
- # - Added support to choose the quality only between 720p and 1080p (if it is available) - (v2.0.*)
- # - Added support for YouTube video codes (e.g.: youtube-viewer WVTWCPoUt8w) - (v1.0.*)
- # - Added support for 720p and 1080p YouTube Videos... - (v1.0.*)
- # Special thanks to:
- # - Army (for bugs reports and for his great ideas)
- # - dhn (for adding youtube-viewer in freshports.org)
- # - stressat (for the great review of youtube-viewer: http://stressat.blogspot.com/2012/01/youtube-viewer.html)
- # - symbianflo (for packaging youtube-viewer for Mandriva distribution)
- # - gotbletu (for the great video review of youtube-viewer: http://www.youtube.com/watch?v=FnJ67oAxVQ4)
- # - Julian Ospald for adding youtube-viewer in gentoo portage tree
- eval 'exec perl -S $0 ${1+"$@"}'
- if 0; # not running under some shell
- use 5.010;
- use strict;
- use autouse 'XML::Fast' => qw(xml2hash);
- use autouse 'URI::Escape' => qw(uri_escape);
- use File::Spec::Functions qw(catdir curdir path rel2abs tmpdir);
- #-------FOR DEBUG ONLY-------#
- # use diagnostics -v;
- # use warnings FATAL => 'all';
- #----------------------------#
- my $appname = 'Youtube Viewer';
- my $version = '2.5.9';
- my $execname = 'youtube-viewer';
- # Configuration dir/file
- my $config_dir = (
- exists $ENV{XDG_CONFIG_HOME}
- ? $ENV{XDG_CONFIG_HOME}
- : ( $ENV{HOME}
- || $ENV{LOGDIR}
- || (getpwuid($<))[7]
- || `echo -n ~`)
- . '/.config'
- ) . "/$execname";
- my $config_file = "$config_dir/$execname.conf";
- my $noconfig = qr/^--?(?>N|noconfig)$/ ~~ @ARGV;
- # A better <STDIN> support:
- require Term::ReadLine;
- my $term = Term::ReadLine->new("$appname $version");
- # Unchangeable variables goes here
- my %constant = (
- gdata_version => 2,
- dash_line => q{-} x 80,
- win32 => $^O =~ /win|dos/i || 0,
- );
- # Set $PATH to @path
- my @path = path();
- # Locating gcap
- my $gcap;
- foreach my $path (@path, @INC) {
- if (-e (my $gcap_path = catdir($path, 'gcap'))) {
- $gcap = $gcap_path;
- last;
- }
- }
- # Get mplayer line
- sub get_mplayer {
- if ($constant{win32}) {
- my $smplayer = $ENV{ProgramFiles} . '\\SMPlayer\\mplayer\\mplayer.exe';
- if (-e $smplayer) {
- return $smplayer; # Windows MPlayer
- }
- else {
- warn "\n\n!!! Please install SMPlayer in order to stream YouTube videos.\n\n";
- return 'mplayer.exe';
- }
- }
- else {
- my $mplayer_path = '/usr/bin/mplayer';
- return -x $mplayer_path ? $mplayer_path : q{mplayer} # *NIX MPlayer
- }
- }
- # Main configuration
- my %CONFIG = (
- # MPlayer options
- cache => 30000,
- cache_min => 5,
- lower_cache => 2000,
- lower_cache_min => 3,
- mplayer => get_mplayer(),
- mplayer_srt_settings => '-unicode -utf8',
- mplayer_arguments => '-prefer-ipv4 -really-quiet -cache %d -cache-min %d',
- # Youtube options
- results => 20,
- resolution => 1080,
- only_hd_videos => undef,
- safe_search => undef,
- only_videos_with_caption => undef,
- duration => undef,
- query_parameters => undef,
- time_sort => 'all_time',
- order_by => 'relevance',
- # URI options
- youtube_video_url => 'http://www.youtube.com/watch?v=%s',
- feeds_main_url => 'http://gdata.youtube.com/feeds/api',
- get_video_info => 'http://www.youtube.com/get_video_info',
- google_client_login => 'https://www.google.com/accounts/ClientLogin',
- # Subtitle options
- srt_language => 'en',
- tmp_dir => tmpdir(),
- gcap => $gcap,
- # Others
- use_colors => 0,
- lwp_downloading => 0,
- fullscreen => 0,
- use_lower_cache => 0,
- results_with_details => 0,
- perl_bin => $^X,
- thousand_separator => q{,},
- downloads_folder => curdir(),
- editor => $ENV{EDITOR} || 'nano',
- users_list => "$config_dir/youtube_users.txt",
- );
- my $stdin_help = <<'STDIN_HELP';
- all : play all the results in order
- next : go to the next page (same as <ENTER>)
- back : return to the previous page
- login : will prompt you for login
- logout : will delete the authentication key
- [integer] : play the corresponding video
- i, info [i] : show more informations about one video
- c, comments [i] : show video comments (e.g.: c 19)
- r, related [i] : show related videos (e.g.: r 6)
- v, videos [i] : show author's latest videos
- p, playlists [i] : show author's latest playlists
- subscribe [i] : subscribe to author's channel
- like, dislike [i] : like or dislike a video
- fav, favorite [i] : favorite a video (e.g.: fav 3)
- [keywords] : search for youtube videos
- 3-8, 3..8 : same as 3 4 5 6 7 8
- 8 2 12 4 6 5 1 : play the videos in your order
- -argv -argv2=v : set some arguments (e.g.: -u=google)
- e, edit-config : edit and apply the configuration
- load-config : (re)load the configuration file
- /my?[regex]*$/ : play videos matched by a regex (/i)
- reset, reload : restart the application
- q, quit, exit : close the application
- STDIN_HELP
- my %MPLAYER;
- # MPlayer variable arguments
- sub set_mplayer_arguments {
- my ($cache, $cache_min) = @_;
- $MPLAYER{mplayer_arguments} = sprintf $CONFIG{mplayer_arguments}, $cache, $cache_min;
- $MPLAYER{fullscreen} = $CONFIG{fullscreen} ? q{-fs} : q{};
- return 1;
- }
- # Save hash config to file
- sub write_config_to_file {
- Config::save_hash($config_file, \%CONFIG);
- }
- # Creating config unless it exists
- unless (-e $config_file) {
- require File::Path;
- File::Path::make_path($config_dir);
- write_config_to_file();
- }
- # itag => resolution
- my %itags = (
- 37 => 1080,
- 22 => 720,
- 35 => 480,
- 43 => 210,
- 34 => 360,
- 5 => 240
- );
- # For YouTube
- my $key = 'AI39si5xZtotT-ABtXEHNYpPnfez4T9hfNTkMlWti5gVCbFOZ-wyw70RTTguH_53klpmj3G98sTJGXJF5YY61Zcu1r5XmR2w3Q';
- my %lwp_header = ('X-GData-Key' => "key=$key");
- #----------------------- COLORS -----------------------#
- my %c = (
- bold => q{},
- bred => q{},
- bgreen => q{},
- reset => q{},
- cblack => q{},
- byellow => q{},
- bpurle => q{},
- bblue => q{},
- );
- if (not $constant{win32}) { # if not running under Windows
- $c{cblack} = "\e[40m"; # background black
- $c{byellow} = "\e[1;33m"; # bold yellow
- $c{bpurle} = "\e[1;35m"; # bold purple
- $c{bblue} = "\e[1;34m"; # bold blue
- $c{bold} = "\e[1m"; # bold terminal color
- $c{bred} = "\e[1;31m"; # bold red
- $c{bgreen} = "$c{cblack}\e[1;32m"; # bold green on black background
- $c{reset} = "\e[0m"; # reset color
- }
- if (qr/^--?nocolors$/ ~~ \@ARGV) { # --nocolors
- %c = map { $_ => q{} } keys %c;
- }
- #---------------------- GLOBAL VARIABLES ----------------------#
- my $keywords = q{}; # used to store keywords for search
- my $youtube_gdata_url; # used to store the gdata URL with API query parameters
- # Other global variables
- my ($lwp, @picks, @results, %streaming);
- my %prompt = (
- comments_next_page => "$c{reset}\n$c{bold}=>> $c{bgreen}Press <ENTER> for the next page of comments (? - help)$c{reset}\n> ",
- select_video_to_play => "$c{reset}\n$c{bold}=>> $c{bgreen}Insert a number or search something else (? - help)$c{reset}\n> ",
- init_text => "$c{reset}\n$c{bold}=>> $c{bgreen}Insert an YouTube URL or search something...$c{reset}\n> ",
- email => "$c{reset}\n$c{bold}=>> $c{bgreen}Email:$c{reset} ",
- password => "$c{reset}$c{bold}=>> $c{bgreen}Password:$c{reset} ",
- user_from_list => "$c{reset}\n$c{bold}=>> $c{bgreen}Pick one username$c{reset}\n> ",
- video_playlists => "$c{reset}\n$c{bold}=>> $c{bgreen}Pick one playlist or search something else$c{reset}\n> ",
- user_playlists => "$c{reset}\n$c{bold}=>> $c{bgreen}Pick one playlist or insert another username$c{reset}\n> ",
- categories => "$c{reset}\n$c{bold}=>> $c{bgreen}Pick one category$c{reset}\n> ",
- needs_login => "$c{bred}You need to login in order to use this feature!$c{reset}\n",
- );
- # Options
- my %opt = (start_with_page => 1);
- #------------------------ REGEXP AREA ------------------------#
- my $contains_arguments = qr/(?>\s|^)-+\w/;
- my $match_regexp = qr{/([^\\/]*(?:\\.[^\\/]*)*)/};
- my $valid_playlist_code = qr/^(?:PL)?([0-9A-Z]{16})$/;
- my $get_youtube_code = qr{\b(?>v|embed|youtu[.]be)(?>[=/]|%3D)([\w\-]{11})};
- my $looks_like_gdata_url = qr{^https?://gdata[.]youtube[.]com/feeds/.};
- # $1 will be the playlist code
- my $get_playlist_code = qr{(?:(?:(?>playlist[?]list|view_play_list[?]p)=)|\w#p/c/)(?:PL)?([A-Z0-9]{16})};
- # $1 will be the Youtube username
- my $get_username = qr{^https?://(?:www[.])?youtube[.]com/(?:user/)?(\w{2,})(?:[?].*)?$};
- # The bellow regex will validate an HTTP url.
- # If it is valid, we will try to get an youtube
- # video code from that website using /$get_youtube_code/
- my $valid_url = qr{^
- ################# This regex will validate an HTTP URL #################
- https?:// # http or https followed by ://
- [[:alnum:]] # first character must be a-zA-Z0-9
- (?:(?:(?:\w*-+\w+|\w+)* # words, dash, words OR only words
- \.(?=\w))+? # point if followed by word char
- | # OR (validates http://x.yz)
- \.) # a single dot
- \w{2,6} # domain (words between 2 and 6 chars)
- (?:[#/?!] # characters after domain
- [#-)+-;=?\\~\w]*)* # the rest characters of the string
- $}x;
- #---------------------- LOOKING FOR WGET ----------------------#
- sub locate_wget {
- foreach my $dir (@path) {
- if (-x (my $wget_path = catdir($dir, 'wget'))) {
- $opt{wget} = $wget_path;
- return 1;
- }
- }
- return;
- }
- #---------------------- LWP::UserAgent ----------------------#
- sub set_lwp_useragent {
- require LWP::UserAgent;
- $lwp = 'LWP::UserAgent'->new(
- keep_alive => 1,
- env_proxy => 1,
- timeout => 60,
- show_progress => $opt{debug} ? 1 : 0,
- agent => 'Mozilla/5.0 (X11; U; Linux i686; en-US) Chrome/10.0.648.45',
- );
- $opt{lwp_is_set} = 1;
- binmode *STDOUT, ":encoding(UTF-8)";
- }
- sub lwp_get {
- set_lwp_useragent() unless $opt{lwp_is_set};
- return $lwp->get($_[0], %lwp_header)->content;
- }
- # True if looks like a YouTube code
- # XXX: it may not validate any code
- sub is_code {
- length $_[0] == 11
- and $_[0] =~ /^[\w-]{11}$/ # must contain only word chars and dashes
- and $_[0] =~ /[0-9A-Z_\-]/ # must contain, at least, one 'unusual' char
- and not $_[0] =~ /^--?[a-z]+$/; # and is not an argument (e.g.: -fullscreen)
- }
- # Set config file to %CONFIG
- sub apply_configuration {
- my $config_ref = do $config_file;
- if (ref $config_ref eq 'HASH') {
- while (my ($key, $value) = each %$config_ref) {
- $CONFIG{$key} = $value;
- }
- }
- else {
- warn "\n[!] Configuration file contains some errors!\n",
- "[*] Trying to regenerate the configuration file...\n",
- (write_config_to_file() ? "[*] Done!\n" : "[!] Unable to regenerate $config_file: $!\n");
- }
- # Auto-login
- if (defined $CONFIG{auth}) {
- set_auth_key($CONFIG{auth});
- }
- }
- apply_configuration() unless $noconfig;
- # Convert args (of length of 11) to Youtube URLs
- {
- my @argv;
- foreach my $arg (@ARGV) {
- if (is_code($arg)) {
- push @argv, sprintf($CONFIG{youtube_video_url}, $arg);
- }
- else {
- push @argv, $arg;
- }
- }
- @ARGV = @argv;
- }
- # __DIE__ handle
- $SIG{__DIE__} = sub {
- if (join(q{}, @_) =~ m{^Can't locate (.+?)\.pm\b}) { #'
- my $module = $1;
- $module =~ s{[/\\]+}{::}g;
- die <<"REQ";
- Module $module is required!\n
- To install it, just type in terminal:
- sudo cpan -i $module
- REQ
- }
- return 1;
- };
- sub require_getopt_long {
- require Getopt::Long;
- Getopt::Long::Configure('no_ignore_case');
- $opt{required_getopt} = 1;
- }
- # Parsing arguments
- if (@ARGV) {
- parse_arguments(@ARGV);
- }
- # Return value for start_index
- sub get_start_index {
- return $CONFIG{results} * $opt{start_with_page} - $CONFIG{results} + 1;
- }
- # Returns a list of arguments
- sub get_arguments_from_string {
- return grep { chr ord eq q{-} } split(q{ }, shift);
- }
- # Returns a list of keywords
- sub get_keywords_from_string {
- return grep { chr ord ne q{-} } split(q{ }, shift);
- }
- # Returns a list of keywords
- sub get_keywords_from_array {
- return grep { chr ord ne q{-} } ref $_[0] eq 'ARRAY' ? @{$_[0]} : @_;
- }
- sub parse_arguments {
- require_getopt_long() unless $opt{required_getopt};
- Getopt::Long::GetOptionsFromArray(
- \@_, \%CONFIG,
- # Main options
- 'help|usage|h|?' => \&help,
- 'tricks|tips|T' => \&tricks,
- 'version|V|v' => \&version,
- # Resolutions
- '240p|2' => sub { $CONFIG{resolution} = 240 },
- '360p|3' => sub { $CONFIG{resolution} = 360 },
- '480p|4' => sub { $CONFIG{resolution} = 480 },
- '720p|7' => sub { $CONFIG{resolution} = 720 },
- '1080p|1' => sub { $CONFIG{resolution} = 1080 },
- # Other options
- 'categories|c' => sub { push @{$opt{subs}}, [\&categories_area] },
- 'movies|M' => sub { push @{$opt{subs}}, [\&youtube_movies] },
- 'today|t' => sub { push @{$opt{subs}}, [\&youtube_tops] },
- 'a|all-time' => sub { push @{$opt{subs}}, [\&youtube_tops, 'all_time'] },
- 'subscriptions|S:s' => sub { push @{$opt{subs}}, [\&get_new_subsc, $_[-1] ? pop() : 'default'] },
- 'favorites|favorited|F:s' => sub { push @{$opt{subs}}, [\&get_favorited_videos, $_[-1] ? pop() : 'default'] },
- 'recommended|R:s' => sub { push @{$opt{subs}}, [\&get_recommended_videos, $_[-1] ? pop() : 'default'] },
- 'watched|W:s' => sub { push @{$opt{subs}}, [\&get_watch_history, $_[-1] ? pop() : 'default'] },
- 'login' => sub { push @{$opt{subs}}, [\&authenticate] },
- 'user|u=s' => sub { push @{$opt{subs}}, [\&videos_from_username, pop] },
- 'user-playlists|up=s' => sub { push @{$opt{subs}}, [\&playlists_from_username, pop] },
- 'users:s' => sub { push @{$opt{subs}}, [\&list_user_names => $_[-1] ? pop() : $CONFIG{users_list}] },
- 'author=s' => \$opt{author},
- 'all|A!' => \$opt{playback},
- 'nocolors' => \$opt{no_colors},
- 'nostdin' => \$opt{no_stdin},
- 'noconfig|N!' => \$noconfig,
- 'playlists|p!' => \$opt{playlists},
- 'debug!' => \$opt{debug},
- 'update-config|U!' => \$opt{update_config},
- 'download|d!' => \$opt{download_video},
- 'print_video_id' => \$opt{print_video_id},
- 'safe-search=s' => \$CONFIG{safe_search},
- 'hd!' => \$CONFIG{only_hd_videos},
- 'cache-min=i' => \$CONFIG{cache_min},
- 'colors|C' => \$CONFIG{use_colors},
- 'details|D!' => \$CONFIG{results_with_details},
- 'caption=s' => \$CONFIG{only_videos_with_caption},
- 'sub|lang=s' => \$CONFIG{srt_language},
- 'fs|f!' => \$CONFIG{fullscreen},
- 'l|lower-cache!' => \$CONFIG{use_lower_cache},
- 'query-params|Q=s' => \$CONFIG{query_parameters},
- 'lwp-download|L!' => \$CONFIG{lwp_downloading},
- 'order-by=s' => \$CONFIG{order_by},
- 'time=s' => \$CONFIG{time_sort},
- 'page=i' => sub { $opt{start_with_page} = pop },
- 'append_mplayer=s' => sub { $MPLAYER{argv} = pop },
- 'novideo|n!' => sub { $MPLAYER{novideo} = $_[-1] ? q{-novideo} : q{} },
- 'more|m!' => sub { $CONFIG{results} = $_[-1] ? 50 : 20 },
- 'download-dir|downloads-dir=s' => \$CONFIG{downloads_folder},
- 'quiet|q' => sub {
- close STDOUT;
- close STDERR;
- },
- map { defined $CONFIG{$_} && $CONFIG{$_} =~ /^[01]\z/ ? "$_!" : "$_=s" } keys %CONFIG
- );
- $keywords = join(q{ }, get_keywords_from_array(\@_)) if @_;
- # Start with page N
- $opt{start_index} = get_start_index();
- if ($opt{no_stdin}) { # poor implementation of --nostdin
- $term = q{};
- $SIG{__DIE__} = sub { exit 0 };
- close STDERR;
- }
- # Dump config
- if ($opt{debug}) {
- print "=>> CONFIG:\n" => Config::_dump(\%CONFIG),
- "=>> MPLAYER:\n" => Config::_dump(\%MPLAYER);
- }
- # Go to selected subroutines
- if (exists $opt{subs}) {
- while (@{$opt{subs}}) {
- my $goto = shift @{$opt{subs}};
- my $sub = shift @{$goto};
- $sub->(@{$goto});
- }
- }
- }
- $opt{start_index} ||= get_start_index() || 1;
- #---------------------- PARSING VIDEO CODES SPECIFIED AS ARGUMENTS ----------------------#
- foreach my $code (@ARGV) {
- given ($code) {
- when (/$get_playlist_code/o) {
- list_playlist($1);
- }
- when (/$valid_playlist_code/o) {
- list_playlist($1);
- }
- when (/$get_youtube_code/o) {
- $opt{video_id_from_arguments} = 1;
- get_youtube($1);
- }
- when (/$get_username/o) {
- videos_from_username($1);
- }
- when (/$valid_url/o) {
- code_from_content($_);
- }
- }
- }
- sub quit_required {
- $_[0] ~~ ['q', 'quit', 'exit'];
- }
- #---------------------- GO TO insert_url() IF $non_argv is FALSE ----------------------#
- sub insert_url {
- {
- given ($term->readline($prompt{init_text})) {
- when (\&check_user_input) {
- redo;
- }
- when (q{}) {
- die "\n$c{bred}(x_x) Unable to continue...$c{reset}\n\n";
- }
- default {
- search(join(q{ }, get_keywords_from_string($_)) or redo);
- }
- }
- }
- }
- insert_url() unless length $keywords;
- #---------------------- GET A VIDEO CODE FROM AN WEBSITE CONTENT ----------------------#
- sub code_from_content {
- set_lwp_useragent() unless $opt{lwp_is_set};
- if ($lwp->get($_[0])->content =~ /$get_youtube_code/o) {
- get_youtube($1);
- }
- else {
- search($_[0]);
- }
- }
- #---------------------- YOUTUBE-VIEWER USAGE ----------------------#
- sub help {
- my $eqs = q{=} x 30;
- print <<"HELP";
- \n $eqs $c{bgreen}\U$appname\E$c{reset} $eqs
- \t\t\t\t\t\t by Trizen (trizenx\@gmail.com)
- \n$c{bold}usage:$c{reset} $execname [options] ([code] || [url] || [keywords])
- \n$c{bgreen}Base Options:$c{reset}
- <url> : play an YouTube video by URL
- <code> : play an YouTube video by code
- <keywords> : search and list YouTube videos
- <playlist_url> : list a playlist of YouTube videos
- \n$c{bgreen}YouTube options:$c{reset}
- -t --today : show YouTube tops of today
- -a --all-time : show YouTube tops of all time
- -c --categories : show available YouTube categories
- -p --playlists : search for playlists of videos
- -M --movies : show YouTube category of movies
- -results=[1-50] : how many videos to display per page
- -u=s -user=s : list videos uploaded by a specific user
- -up=s : list playlists created by a specific user
- -author=s : search videos uploaded by a particular user
- -duration=s : valid values are: short, medium, long
- -caption=s : valid values are: true, false
- -safe-search=s : valid values are: none, moderate, strict
- -order-by=s : order entries by: published, viewCount and rating
- -time=s : valid values are: today, this_week and this_month
- -page=i : show video results starting with the page 'i'
- -hd : show only the videos available in at least 720p
- -2 -3 -4 -7 -1 : resolution of videos: 240p, 360p, 480p, 720p or 1080p
- -F --favorites : show the latest favorited videos *
- -R --recommended : show the recommended videos for you *
- -S --subscriptions : show the new subscription videos *
- -W --watched : show the latest watched videos on YouTube *
- \n$c{bgreen}MPlayer options:$c{reset}
- -f --fullscreen : set the fullscreen mode for mplayer (-fs)
- -n --novideo : play the music only without a video in the foreground
- -l --lower-cache : use a lower cache for MPlayer (for slow connections)
- -sub=s -lang=s : subtitle language (default: en) (depends on gcap)
- -cache=i : set the cache for MPlayer (set: $CONFIG{cache})
- -cache-min=i : set the cache-min for MPlayer (set: $CONFIG{cache_min})
- -mplayer=s : set a media player (set: $CONFIG{mplayer})
- -mplayer_arguments=s : replace default arguments for the media player
- -append_mplayer=s : add some arguments for the media player
- \n$c{bgreen}Other options:$c{reset}
- -d --download : download the video(s)
- -A --all : play all the video results in order
- -C --colors : use colors to delimit the video results
- -D --details : a new look for the results, with more details
- -L --lwp-download : download the videos with LWP (default: wget)
- -T --tricks : show more 'hidden' features of $appname
- -U --update-config : update the configuration file before exit
- -N --noconfig : start the $appname with the default config
- -Q --query-params : set query parameters (e.g.: 'duration=long&caption')
- -users=file.txt : list YouTube usernames from a file
- -login : will prompt you for login
- -downloads-dir : downloads directory (set: '$CONFIG{downloads_folder}')
- \n$c{bgreen}Main options:$c{reset}
- -q --quiet : display no output
- -v --version : print version and exit
- -h --help : print help and exit
- $c{bold}NOTE:$c{reset}
- * == requires authentication
- -no-[argv] will negate the value of the argument (e.g.: -no-fullscreen)
- each config key is a valid argument (if it's preceded by a dash (-))
- $c{bgreen}Tips and tricks:$c{reset}
- 1. After the search results, press <ENTER> for the next page
- 2. After the search results, insert 'back' for the previous page
- 3. View more 'hidden' features by executing '$execname -T'\n
- HELP
- main_quit();
- }
- #---------------------- YOUTUBE-VIEWER TIPS AND TRICKS ----------------------#
- sub tricks {
- print <<"TRICKS";
- $c{bold}>>$c{reset} $c{bgreen}Tips and tricks$c{reset} $c{bold}<<$c{reset}
- \n$c{bold}* $c{bgreen}STDIN arguments:$c{reset}
- $stdin_help
- \n$c{bold}* $c{bgreen}Did you know that...?$c{reset}
- -A option will play ALL video results, including videos from the next pages;\n
- /REGEXP/ will match case-insensitive (e.g.: /test/ matches 'TeSt');\n
- When multiple videos are selected to play, pressing CTRL+C
- will just empty the playlist and return to the video results.
- \n$c{bold}* $c{bgreen}Configuration file$c{reset}
- Since 2.5.5 version, $appname supports a configuration file.
- Config file is: '$config_file'
- \n$c{bold}* $c{bgreen}Usage examples:$c{reset}
- ** Show videos uploaded by 'MIT' that matches 'computer science',
- starting with page number 2, in fullscreen mode and 720p resolution.
- % $execname --author=MIT computer science --page=2 -fs --720p\n
- ** Show playlists created by a specific user
- % $execname -up khanacademy\n
- ** Show latest videos (50) uploaded by a specific user and a colorful output
- % $execname -results=50 -u google -C\n
- TRICKS
- main_quit();
- }
- # Print version
- sub version {
- print "Youtube Viewer $version\n";
- main_quit();
- }
- # ------------------ Authentication ------------------ #
- sub set_auth_key {
- my ($auth) = @_;
- $lwp_header{Authorization} = "GoogleLogin auth=$auth";
- }
- sub log_out {
- delete $lwp_header{Authorization};
- set_lwp_useragent();
- }
- sub authenticate {
- my ($email, $password);
- $email = $term->readline($prompt{email});
- if (defined $CONFIG{password}) {
- $password = $CONFIG{password};
- }
- else {
- if ($constant{win32}) {
- eval { require Term::ReadKey };
- if ($@) {
- say "[!] Please install Term::ReadKey if you don't want your password to be visible while typing!";
- $password = $term->readline($prompt{password});
- }
- else {
- $password = q{};
- print $prompt{password};
- Term::ReadKey::ReadMode('noecho');
- while (ord(my $key = Term::ReadKey::ReadKey(0)) != 10) {
- $password .= $key;
- }
- Term::ReadKey::ReadMode('restore');
- }
- }
- else {
- print $prompt{password};
- system('stty', '-echo');
- chomp($password = <STDIN>);
- system('stty', 'echo');
- }
- }
- print "\n\n$c{bold}**$c{reset} Should I save your authentification key into configuration?\n",
- "-> if 'yes', you will be logged automatically next time\n\n",
- "$c{bold}=>>$c{reset} Your answer [y/N]: ";
- my $remember_me = <STDIN> =~ /^y(?:es)?$/i ? 1 : 0;
- set_lwp_useragent() unless $opt{lwp_is_set};
- my $resp = $lwp->post(
- $CONFIG{google_client_login},
- [Content => 'application/x-www-form-urlencoded',
- Email => $email,
- Passwd => $password,
- service => 'youtube',
- source => "$appname $version"
- ]
- );
- if ($resp->{_content} =~ /^Auth=(.+)/m) {
- my $auth = $1;
- if ($remember_me) {
- $CONFIG{auth} = $auth;
- $opt{update_config} = 1;
- }
- say "\n$c{bold}* $c{bgreen}Logged!$c{reset}";
- set_auth_key($auth);
- }
- else {
- warn "\nUnable to login: $resp->{_content}\n";
- return 0;
- }
- return 1;
- }
- sub get_new_subsc {
- my ($user) = @_;
- unless ($lwp_header{Authorization}) {
- warn $prompt{needs_login};
- authenticate();
- }
- parse_url("$CONFIG{feeds_main_url}/users/$user/newsubscriptionvideos");
- }
- sub get_recommended_videos {
- my ($user) = @_;
- unless ($lwp_header{Authorization}) {
- warn $prompt{needs_login};
- authenticate();
- }
- parse_url("$CONFIG{feeds_main_url}/users/$user/recommendations");
- }
- sub get_favorited_videos {
- my ($user) = @_;
- unless ($lwp_header{Authorization}) {
- warn $prompt{needs_login};
- authenticate();
- }
- parse_url("$CONFIG{feeds_main_url}/users/$user/favorites");
- }
- sub get_watch_history {
- my ($user) = @_;
- unless ($lwp_header{Authorization}) {
- warn $prompt{needs_login};
- authenticate();
- }
- parse_url("$CONFIG{feeds_main_url}/users/$user/watch_history");
- }
- #---------------------- LIST YOUTUBE USERNAMES FROM A FILE ----------------------#
- sub list_user_names {
- my ($users_file) = @_;
- return unless -T $users_file;
- my $i = 0;
- my %usernames_table;
- print "\n";
- open my $fh, '<:crlf', $users_file or die $!;
- while (defined(my $username = <$fh>)) {
- next unless $username =~ /^\w+$/;
- chomp $username;
- printf "%s%2d%s - %s%s%s\n", $c{bold}, ++$i, $c{reset}, $c{bgreen}, $username, $c{reset};
- $usernames_table{$i} = $username;
- }
- close $fh;
- {
- given ($term->readline($prompt{user_from_list})) {
- when (\&check_user_input) {
- redo;
- }
- when (exists $usernames_table{$_}) {
- videos_from_username($usernames_table{$_});
- }
- when (/^\w+$/) {
- videos_from_username($_);
- }
- when (/$match_regexp/o) {
- my $match = qr/$1/i;
- my ($found, @found) = 0;
- print "\n";
- while (my ($number, $username) = each %usernames_table) {
- if ($username =~ /$match/o) {
- printf "%s%2d%s - %s%s%s\n", $c{bold}, ++$found, $c{reset}, $c{bgreen}, $username, $c{reset};
- push @found, $found;
- $usernames_table{$found} = $username;
- }
- }
- if (@found > 1) {
- given ($term->readline($prompt{user_from_list})) {
- when (\&quit_required) {
- main_quit();
- }
- when (exists $usernames_table{$_}) {
- videos_from_username($usernames_table{$_});
- }
- default {
- insert_url();
- }
- }
- }
- elsif (@found) {
- videos_from_username($usernames_table{$found[0]});
- }
- continue;
- }
- default {
- insert_url();
- }
- }
- }
- }
- #---------------------- GET VIDEOS FROM A SPECIFIC USER ----------------------#
- sub videos_from_username {
- parse_url("$CONFIG{feeds_main_url}/users/$_[0]/uploads");
- }
- #---------------------- PRINT PLAYLISTS ----------------------#
- sub print_playlists {
- my ($playlist, $num) = @_;
- # Number, Title, Author, VideosCount
- $CONFIG{use_colors}
- # Colorful
- ? printf(
- "%s%s%2d%s%s - %s%s%s%s (%sby %s%s%s) (%s%s%s%s)%s\n",
- $c{cblack} => $c{bold} => $num => $c{reset},
- $c{cblack} => $c{byellow} => $playlist->{title} => $c{reset},
- $c{cblack} => $c{bpurle} => $playlist->{author} => $c{reset},
- $c{cblack} => $c{bblue} => $playlist->{count} => $c{reset},
- $c{cblack} => $c{reset}
- )
- : printf("%s%2d%s - %s (by %s) (%s)\n",
- $c{bold}, $num, $c{reset}, $playlist->{title}, $playlist->{author}, $playlist->{count});
- }
- #---------------------- GET PLAYLISTS FROM A SPECIFIC USER ----------------------#
- my $playlist_index;
- sub playlists_from_username {
- my ($username) = @_;
- search_playlists("$CONFIG{feeds_main_url}/users/$username/playlists?" . default_gdata_arguments(), 'username');
- }
- sub search_playlists {
- return unless @_;
- my ($arg, $mode) = @_;
- $youtube_gdata_url =
- $arg =~ /$looks_like_gdata_url/o
- ? $arg
- : $CONFIG{feeds_main_url} . "/playlists/snippets?q=$arg&" . default_gdata_arguments();
- $opt{playlists} = 1;
- parse_content($youtube_gdata_url);
- my $i = 0;
- foreach my $playlist (@results) {
- print_playlists($playlist, ++$i);
- }
- playlists_waiting_input($mode);
- }
- #---------------------- SEARCH FOR YOUTUBE PLAYLISTS ----------------------#
- sub playlists_waiting_input {
- my ($mode) = @_;
- {
- my $prompt =
- defined $mode && $mode eq 'playlists'
- ? $prompt{video_playlists}
- : $prompt{user_playlists};
- given ($term->readline($prompt)) {
- when (\&check_user_input) {
- redo;
- }
- when ([qr/^\s*$/, 'next']) {
- next_page($mode);
- }
- when ('back') {
- if (
- do {
- $youtube_gdata_url =~ /[&?]start-index=(\d+)/;
- $1 > $CONFIG{results};
- }
- ) {
- previous_page($mode);
- }
- else {
- continue;
- }
- }
- when (/^\d+$/ and $_ > 0 and $_ <= @results) {
- list_playlist($results[$_ - 1]{playlistID});
- }
- default {
- if ($mode eq 'playlists') {
- search($_);
- }
- elsif ($mode eq 'username') {
- playlists_from_username($_);
- }
- else {
- print_results();
- }
- }
- }
- }
- }
- #---------------------- LIST A YOUTUBE PLAYLIST ----------------------#
- sub list_playlist {
- $opt{playlists} = 0;
- parse_url("$CONFIG{feeds_main_url}/playlists/$_[0]");
- }
- #---------------------- LIST YOUTUBE MOVIE CATEGORIES ----------------------#
- sub youtube_movies {
- print "\n";
- my $i = 0;
- my %movie_table;
- foreach my $movie_cat_name ('most_popular', 'most_recent', 'trending') {
- my $cat_name = ucfirst $movie_cat_name;
- $cat_name =~ tr/_/ /;
- print q{ }, $c{bold}, ++$i, "$c{reset} - $cat_name\n";
- $movie_table{$i} = $movie_cat_name;
- }
- {
- given ($term->readline($prompt{categories})) {
- when (\&check_user_input) {
- redo;
- }
- when (exists $movie_table{$_}) {
- parse_url("$CONFIG{feeds_main_url}/charts/movies/$movie_table{$_}");
- }
- }
- }
- }
- #---------------------- LIST YOUTUBE TOP VIDEO CATEGORIES ----------------------#
- sub youtube_tops {
- my $i = 0;
- my $today = $_[0] && $_[0] eq 'all_time' ? 0 : 1;
- my $standardfeeds = "$CONFIG{feeds_main_url}/standardfeeds";
- my %tops_table;
- print "\n";
- foreach my $cat_top_name (
- 'top_rated', 'top_favorites', 'most_viewed', 'most_popular',
- 'most_recent', 'most_discussed', 'most_responded', 'recently_featured'
- ) {
- my $top_name = ucfirst $cat_top_name;
- $top_name =~ tr/_/ /;
- print q{ }, $c{bold}, ++$i, "$c{reset} - $top_name\n";
- $tops_table{$i} = $cat_top_name;
- }
- {
- given ($term->readline($prompt{categories})) {
- when (\&check_user_input) {
- redo;
- }
- when (exists $tops_table{$_}) {
- my $url = "$standardfeeds/$tops_table{$_}";
- if ($today and not $url =~ /recent/) {
- $url .= '?time=today';
- }
- parse_url($url);
- }
- default {
- insert_url();
- }
- }
- }
- }
- #---------------------- LIST YOUTUBE VIDEO CATEGORIES ----------------------#
- sub categories_area {
- my $n = 0;
- my %categories_table;
- print "\n";
- foreach
- my $cat (@{xml2hash(lwp_get('http://gdata.youtube.com/schemas/2007/categories.cat'))->{'app:categories'}{'atom:category'}})
- {
- next if exists $cat->{'yt:deprecated'};
- printf "%s%2d%s - %s\n", $c{bold}, ++$n, $c{reset}, $cat->{'-label'};
- $categories_table{$n} = $cat->{'-term'};
- }
- {
- given ($term->readline($prompt{categories})) {
- when (\&check_user_input) {
- redo;
- }
- when (exists $categories_table{$_}) {
- parse_url("$CONFIG{feeds_main_url}/videos?category=$categories_table{$_}");
- }
- default {
- insert_url();
- }
- }
- }
- }
- sub update_mplayer_arguments {
- if ( $CONFIG{use_lower_cache}
- or not exists $streaming{720}
- or $CONFIG{resolution} < 720) {
- set_mplayer_arguments($CONFIG{lower_cache}, $CONFIG{lower_cache_min});
- }
- else {
- set_mplayer_arguments($CONFIG{cache}, $CONFIG{cache_min});
- }
- }
- #---------------------- PLAY OR DOWNLOAD AN YOUTUBE VIDEO ----------------------#
- sub play_or_download {
- my $streaming = shift;
- print "** STREAMING: $streaming\n\n" if $opt{debug};
- if ($opt{download_video}) { # DOWNLOADING
- my $title = shift @_;
- if (defined $CONFIG{downloads_folder}) {
- chdir $CONFIG{downloads_folder};
- }
- if (not defined $opt{located_wget} and not $CONFIG{lwp_downloading}) {
- $opt{located_wget} = locate_wget() || -1;
- }
- # Replacing reserved characters with a space
- $title =~ tr[ "*/:<>?\\|][ ]s;
- if (not -e "$title.mp4") {
- if (defined $opt{wget}) { # Download video with wget
- system $opt{wget}, q{-nc}, $streaming, '-O', "$title.mp4";
- }
- else { # Downloading video with LWP
- say "** Saving to: '$title.mp4'";
- $lwp->show_progress(1);
- $lwp->mirror($streaming, "$title.mp4");
- $lwp->show_progress(0) unless $opt{debug};
- }
- }
- else {
- warn "** '$title.mp4' already exists...\n";
- }
- }
- else { # STREAMING
- update_mplayer_arguments(); # Update mplayer's arguments
- my @mplayer_line = split(' ', join(' ', $CONFIG{mplayer}, values %MPLAYER));
- if ($opt{debug}) {
- print "** MPlayer Line: @mplayer_line\n\n";
- }
- else {
- system @mplayer_line, $streaming;
- }
- # Change directory back to the main working directory
- chdir delete $constant{cwd} if exists $constant{cwd};
- }
- print "\n" unless $opt{video_id_from_arguments};
- if ($?) { # if non-zero exit code
- $opt{playback} = 0;
- print_results();
- }
- if (@picks) {
- foreach_pick(); # play the next video (if any)
- }
- elsif ($opt{playback}) {
- next_page();
- }
- if (@results and not $opt{video_id_from_arguments}) {
- print_results(); # back to video results
- }
- main_quit() unless $opt{video_id_from_arguments};
- return 1;
- }
- #---------------------- SEARCH YOUTUBE VIDEOS ----------------------#
- if (length($keywords)) {
- search() unless $opt{video_id_from_arguments};
- }
- sub get_defined_pairs_from_array {
- # ('arg', undef, 'option', 'value')
- # --to--
- # ('option', 'value')
- foreach (my $i = 0 ; $i <= $#_ ; ++$i) {
- if (not defined $_[$i]) {
- splice(@_, --$i, 2);
- }
- }
- return @_;
- }
- sub array_to_gdata_arguments {
- my @options = &get_defined_pairs_from_array;
- # ('arg', 'value', 'option', 'true')
- # --to--
- # 'arg=value&option=true'
- my $i = -2;
- my $x = $#options - 1;
- my $str = q{};
- while (1) {
- if ($i + 3 < $x) {
- $str .= $options[$i += 2] . '=' . $options[$i + 1] . '&';
- }
- else {
- return
- $str .=
- $i + 3 == $x ? $options[-3] . '=' . $options[-2] . '&' . $options[-1]
- : $i + 2 == $x ? $options[-2] . '=' . $options[-1]
- : $options[-1];
- }
- }
- }
- sub default_gdata_arguments {
- array_to_gdata_arguments(
- 'max-results' => $CONFIG{results},
- 'start-index' => $opt{start_index},
- 'v' => $constant{gdata_version},
- );
- }
- sub search {
- $keywords = shift() // $keywords; #/
- # Get words which doesn't begins with a dash (-);
- $keywords = uri_escape(join(q{ }, get_keywords_from_string($keywords)));
- if ($opt{playlists}) {
- search_playlists($keywords, 'playlists');
- return;
- }
- $youtube_gdata_url = "$CONFIG{feeds_main_url}/videos?"
- . array_to_gdata_arguments(
- 'q' => $keywords,
- 'max-results' => $CONFIG{results},
- 'time' => $CONFIG{time_sort},
- 'orderby' => $CONFIG{order_by},
- 'start-index' => $opt{start_index},
- 'safeSearch' => $CONFIG{safe_search},
- 'hd' => $CONFIG{only_hd_videos} ? 'true' : undef,
- 'caption' => $CONFIG{only_videos_with_caption},
- 'duration' => $CONFIG{duration},
- 'author' => $opt{author},
- 'v' => $constant{gdata_version}
- );
- if (defined $CONFIG{query_parameters}) {
- unless ($CONFIG{query_parameters} =~ /^&/) {
- substr($CONFIG{query_parameters}, 0, 0, '&');
- }
- $youtube_gdata_url .= $CONFIG{query_parameters};
- }
- parse_content($youtube_gdata_url);
- print_results();
- }
- #---------------------- PREPARE GDATA FEEDS URL ----------------------#
- sub parse_url {
- ($youtube_gdata_url) = @_;
- $youtube_gdata_url .= $youtube_gdata_url =~ /\?/ ? '&' : '?';
- $youtube_gdata_url .= default_gdata_arguments();
- parse_content($youtube_gdata_url);
- print_results();
- }
- #---------------------- GET AND PARSE GDATA CONTENT ----------------------#
- sub parse_content {
- undef @results;
- my $number = 0;
- my $hash;
- eval { $hash = xml2hash(lwp_get($_[0])) };
- if ($@) {
- if ($@ =~ /Module \S+ is required!/) {
- warn $@;
- main_quit();
- }
- else {
- warn "Error ocurred while parsing $_[0]\n$@\n";
- exit 1 if ++$opt{retry} == 16;
- parse_content($_[0]);
- }
- }
- while (
- my $gdata =
- ref $hash->{feed}{entry} eq 'ARRAY' ? $hash->{feed}{entry}[$number]
- : ref $hash->{feed}{entry} eq 'HASH' ? $hash->{feed}{entry}
- : $hash->{entry}
- ) {
- last unless defined $gdata;
- push @results, $opt{playlists}
- # Playlists
- ? {
- 'playlistID' => $gdata->{'yt:playlistId'},
- 'title' => $gdata->{title},
- 'author' => $gdata->{author}{name},
- 'count' => $gdata->{'yt:countHint'}
- }
- # Videos
- : {
- 'code' => $gdata->{'media:group'}{'yt:videoid'},
- 'title' => $gdata->{'media:group'}{'media:title'}{'#text'},
- 'author' => $gdata->{author}{name},
- 'rating' => $gdata->{'gd:rating'}{'-average'} || 0,
- 'likes' => $gdata->{'yt:rating'}{'-numLikes'} || 0,
- 'dislikes' => $gdata->{'yt:rating'}{'-numDislikes'} || 0,
- 'favorited' => $gdata->{'yt:statistics'}{'-favoriteCount'},
- 'duration' => format_time($gdata->{'media:group'}{'yt:duration'}{'-seconds'} || 0),
- 'views' => $gdata->{'yt:statistics'}{'-viewCount'},
- 'published' => $gdata->{published},
- 'description' => $gdata->{'media:group'}{'media:description'}{'#text'},
- 'category' => ref $gdata->{category} eq 'ARRAY' ? $gdata->{category}[1]{'-label'}
- : $gdata->{category}{'-label'} || 'Unknown',
- };
- ++$number;
- last unless ref $hash->{feed}{entry} eq 'ARRAY';
- }
- if ($opt{debug}) {
- chdir($CONFIG{tmp_dir});
- open my $fh, '>', "$execname.debug" or die "Unable to write to $execname.debug: $!\n";
- local $, = "\n";
- print {$fh} (
- "=>> URL:" => $_[0],
- "=>> HASH:" => Config::_dump($hash),
- "=>> Results:" => Config::_dump(\@results)
- );
- close $fh;
- }
- print "\n";
- }
- # Format seconds to HH:MM:SS
- sub format_time {
- $_[0] >= 3600
- ? join ':', map { sprintf '%02d', $_ } $_[0] / 3600 % 24, $_[0] / 60 % 60, $_[0] % 60
- : join ':', map { sprintf '%02d', $_ } $_[0] / 60 % 60, $_[0] % 60;
- }
- sub check_user_input {
- given (shift) {
- when (\&quit_required) {
- main_quit();
- }
- when (['e', 'edit-config']) {
- system $CONFIG{editor}, $config_file;
- apply_configuration;
- return 1;
- }
- when ('load-config') {
- apply_configuration();
- return 1;
- }
- when ('login') {
- authenticate();
- return 1;
- }
- when ('logout') {
- print "Logging out...\n";
- log_out();
- return 1;
- }
- when (/$contains_arguments/o) {
- parse_arguments(get_arguments_from_string($_));
- continue;
- }
- when (['reset', 'reload']) {
- @ARGV = ();
- do $0;
- }
- when (/$get_youtube_code/o) {
- get_youtube($1);
- }
- when (/$get_playlist_code/o) {
- list_playlist($1);
- }
- when (/$valid_playlist_code/o) {
- list_playlist($_);
- }
- when (/$valid_url/o) {
- code_from_content($_);
- }
- when (\&is_code) {
- get_youtube($_);
- }
- }
- return;
- }
- #---------------------- PRINT VIDEO RESULTS ----------------------#
- sub print_results {
- unless (@results) {
- warn "$c{bred}(x_x) No video results!$c{reset}\n";
- insert_url();
- }
- my $num = 0;
- foreach my $video (@results) {
- print "$video->{code} - " if $opt{print_video_id};
- if ($CONFIG{results_with_details}) { # Results with details (when using --details or -D)
- printf(
- "$c{bold}%2d$c{reset}. $c{bblue}%s$c{reset}\n"
- . " $c{bold}Views:$c{reset} %-16s $c{bold}Rating:$c{reset} %-12s $c{bold}Category:$c{reset} %s\n"
- . " $c{bold}Published:$c{reset} %-12s $c{bold}Duration:$c{reset} %-10s $c{bold}Author:$c{reset} %s\n\n",
- ++$num => $video->{title},
- set_thousands($video->{views}) => sprintf('%.2f', $video->{rating}) => $video->{category},
- format_date($video->{published}) => $video->{duration} => $video->{author}
- );
- }
- elsif ($CONFIG{use_colors}) { # Colorful results (when using --colors or -C)
- printf(
- "%s%s%2d%s%s - %s%s%s%s (%sby %s%s%s) (%s%s%s%s)%s\n",
- $c{cblack} => $c{bold} => ++$num => $c{reset},
- $c{cblack} => $c{byellow} => $video->{title} => $c{reset},
- $c{cblack} => $c{bpurle} => $video->{author} => $c{reset},
- $c{cblack} => $c{bblue} => $video->{duration} => $c{reset},
- $c{cblack} => $c{reset}
- );
- }
- else { # Normal results
- printf("%s%2d%s - %s (by %s) (%s)\n",
- $c{bold}, ++$num, $c{reset}, $video->{title}, $video->{author}, $video->{duration});
- }
- }
- if ($opt{playback}) {
- @picks = 1 .. @results;
- foreach_pick();
- }
- {
- given ($term->readline($prompt{select_video_to_play})) {
- when (['help', '?']) {
- print $stdin_help;
- redo;
- }
- when (\&check_user_input) {
- redo;
- }
- when ([qr/^\s*$/, 'next']) {
- next_page();
- }
- when ('back') {
- if (
- do {
- $youtube_gdata_url =~ /[&?]start-index=(\d+)/;
- $1 > $CONFIG{results};
- }
- ) {
- previous_page();
- }
- else {
- continue;
- }
- }
- when (/^((?:dis)?like)\s+(\d+)\s*$/) {
- my ($rating, $i) = ($1, $2);
- continue if $i == 0 or $i > @results;
- send_rating_to_video($results[$i - 1]->{code}, $rating);
- redo;
- }
- when (/^c(?:omments)?\s+(\d+)\s*$/) {
- my $i = $1;
- continue if $i == 0 or $i > @results;
- show_comments($results[$i - 1]->{code});
- }
- when (/^fav(?:orite)?\s+(\d+)\s*$/) {
- my $i = $1;
- continue if $i == 0 or $i > @results;
- favorite_video($results[$i - 1]->{code});
- redo;
- }
- when (/^r(?:elated(?:[- _]videos)?)?\s+(\d+)\s*$/) {
- my $i = $1;
- continue if $i == 0 or $i > @results;
- show_related_videos($results[$i - 1]->{code});
- }
- when (/^sub(?:scribe)?\s+(\d+)\s*$/) {
- my $i = $1;
- continue if $i == 0 or $i > @results;
- subscribe_channel($results[$i - 1]->{author});
- }
- when (/^i(?:nfo)?\s+(\d+)\s*$/) {
- my $i = $1;
- continue if $i == 0 or $i > @results;
- $opt{show_info_only} = 1;
- get_youtube($results[$i - 1]);
- $opt{show_info_only} = 0;
- redo;
- }
- when (/^v(?:ideos)?\s+(\d+)\s*$/) {
- my $i = $1;
- continue if $i == 0 or $i > @results;
- videos_from_username($results[$i - 1]->{author});
- }
- when (/^p(?:laylists)?\s+(\d+)\s*$/) {
- my $i = $1;
- continue if $i == 0 or $i > @results;
- playlists_from_username($results[$i - 1]->{author});
- }
- when ('all') {
- @picks = 1 .. scalar @results;
- foreach_pick();
- }
- when (chr ord eq q{/} and /$match_regexp/o) {
- my $match = qr/$1/i;
- @picks = grep { $results[$_ - 1]->{title} =~ /$match/ } 1 .. @results;
- if (@picks) {
- foreach_pick();
- }
- else {
- warn "\n$c{bold}(X_X) No video matched by the regexp: $c{bgreen}/$match/$c{reset}\n\n";
- sleep 1;
- print_results();
- }
- }
- when (/\d/ and not /(?:\s|^)[^\d-]/) {
- # remove numeric arguments (e.g.: -4, -arg=\d);
- s/(?:\D|^)[-=]+\d+(?:\w+)?//g;
- # '2..5' or '2-5' to '2 3 4 5', or '3..1 to '3 2 1'
- s/(\d+)(?:-|\.\.)(\d+)/join q{ }, $1 < $2 ? $1 .. $2 : reverse($2 .. $1);/eg;
- @picks = grep { /^\d+$/ and $_ > 0 and $_ <= @results } split /[\s,]+/, $_;
- @picks
- ? foreach_pick()
- : continue;
- }
- default {
- search(join(q{ }, get_keywords_from_string($_)) or redo);
- }
- }
- }
- }
- sub foreach_pick {
- while (@picks) {
- get_youtube($results[shift(@picks) - 1]);
- }
- }
- #---------------------- NEXT PAGE ----------------------#
- sub next_page {
- if ($youtube_gdata_url =~ s/[&?]start-index=\K(\d+)/$1 + $CONFIG{results}/e) {
- if ($opt{playlists}) {
- search_playlists($youtube_gdata_url, @_);
- }
- else {
- parse_content($youtube_gdata_url);
- print_results();
- }
- }
- }
- #---------------------- PREVIOUS PAGE ----------------------#
- sub previous_page {
- if ($youtube_gdata_url =~ s/[&?]start-index=\K(\d+)/$1 - $CONFIG{results}/e) {
- if ($opt{playlists}) {
- search_playlists($youtube_gdata_url, @_);
- }
- else {
- parse_content($youtube_gdata_url);
- print_results();
- }
- }
- }
- sub lower_quality {
- foreach my $itag (sort { $b <=> $a } values %itags) {
- if (exists($streaming{$itag})) {
- return $streaming{$itag};
- }
- }
- }
- sub select_resolution {
- given ($CONFIG{resolution}) {
- when (1080) {
- return $streaming{1080} // lower_quality();
- }
- when (720) {
- return $streaming{720} // lower_quality();
- }
- when (480) {
- return $streaming{480} // lower_quality();
- }
- when (360) {
- return $streaming{360} // lower_quality();
- }
- when (240) {
- return $streaming{240} // lower_quality(); #/
- }
- default {
- return lower_quality();
- }
- }
- }
- sub format_itags {
- my @itags;
- foreach my $itag (@_) {
- if (exists($itags{$itag})) {
- push @itags, $itags{$itag};
- }
- }
- @itags;
- }
- # Getting YouTube closed captions with gcap
- sub get_closed_caption {
- my ($code) = @_;
- unless (exists $constant{cwd}) {
- $constant{cwd} = rel2abs(curdir());
- }
- # Change dir to $TMP and get the SRT file
- chdir $CONFIG{tmp_dir};
- my $srt_file;
- my $i = 0;
- {
- $srt_file =
- -e "${code}_$CONFIG{srt_language}.srt"
- ? "${code}_$CONFIG{srt_language}.srt"
- : do {
- opendir(my $dir_h, $CONFIG{tmp_dir}) or return q{};
- my $srt = (grep /^\Q$code\E[\w-]*[.](?i:srt)\z/, readdir($dir_h))[0];
- closedir $dir_h;
- $srt;
- };
- unless (defined $srt_file) {
- system $CONFIG{perl_bin}, $CONFIG{gcap}, $code;
- if ($? == 0 and not $i++) {
- redo;
- }
- }
- }
- return defined $srt_file
- ? "$CONFIG{mplayer_srt_settings} -sub $srt_file"
- : q{};
- }
- sub format_date {
- return "$3.$2.$1"
- if $_[0] =~ /^(\d{4})-(\d{2})-(\d{2})/;
- }
- # Thousand separator
- sub set_thousands {
- return 0 unless $_[0];
- length($_[0]) > 3 or return $_[0];
- my $n = shift;
- my $l = length($n) - 3;
- my $i = ($l - 1) % 3 + 1;
- my $x = substr($n, 0, $i) . $CONFIG{thousand_separator};
- while ($i < $l) {
- $x .= substr($n, $i, 3) . $CONFIG{thousand_separator};
- $i += 3;
- }
- $x . substr($n, $i);
- }
- sub get_youtube {
- my $info = shift();
- if (ref $info ne 'HASH') {
- parse_content("$CONFIG{feeds_main_url}/videos/$info?v=2");
- $info = $results[0] if @results;
- if (@results and ref $info eq 'HASH' and not defined $info->{code}) {
- die <<"ERROR";
- ** Something is REALLY wrong... Unable to continue!\n
- Tips:
- 1. (Edit/delete) the configuration file
- 2. Run in -debug mode and send '${execname}.debug' to developer
- ERROR
- }
- elsif (ref $info ne 'HASH') {
- warn "$c{bred}(x_x) Unable to stream:$c{reset} ", sprintf($CONFIG{youtube_video_url}, $info), "\n\n";
- return;
- }
- }
- my $code = $info->{code};
- my $content = lwp_get("$CONFIG{get_video_info}?&video_id=$code&el=detailpage&ps=default&eurl=&gl=US&hl=en");
- my $url = sprintf($CONFIG{youtube_video_url}, $code);
- $MPLAYER{arguments} = q{};
- if ( not $opt{download_video}
- and -e $CONFIG{gcap}
- and not exists $MPLAYER{novideo}
- and $content =~ /&has_cc=True&/) {
- $MPLAYER{arguments} = get_closed_caption($code);
- }
- if ($content =~ /url_encoded_fmt_stream_map=(.+?)&/) {
- my $streaming = $1;
- $streaming =~ s/%253A/:/gi;
- $streaming =~ s{%252F}{/}gi;
- $streaming =~ s/%2526/&/g;
- $streaming =~ s/%253D/=/gi;
- $streaming =~ s/%253F/?/gi;
- $streaming =~ s/%25252C/,/gi;
- undef %streaming;
- my (@streaming_urls) =
- grep m{^https?:}, split(m{url%3D(.+?)%26}, $streaming);
- @streaming{format_itags(map m{&itag=(\d+)&}, @streaming_urls)} =
- grep {
- exists $itags{
- do { m{&itag=(\d+)&}; $1 }
- }
- } @streaming_urls;
- if ($opt{debug}) {
- while (my ($key, $value) = each %streaming) {
- print "KEY = $key\nVALUE = $value\n\n";
- }
- }
- my $rating = sprintf('%.2f', $info->{rating});
- if (!defined $info->{description}) {
- $info->{description} = 'No description available...';
- }
- print "\n$c{bold}=>>$c{reset} Description\n",
- "$constant{dash_line}\n",
- "$info->{description}\n$constant{dash_line}\n",
- "$c{bold}=>>$c{reset} View & Download\n",
- "$constant{dash_line}\n$c{bold}* URL:$c{reset} ";
- print STDOUT $url;
- print "\n$c{bold}* GET:$c{reset} $streaming_urls[0]\n$constant{dash_line}\n",
- q{ } x ((length($constant{dash_line}) - length($info->{title})) / 2 - 4),
- "$c{bold}=>>$c{reset} $c{bgreen}$info->{title}$c{reset} $c{bold}<<=$c{reset}\n\n",
- "** $c{bold}Author$c{reset} : $info->{author}\n",
- "** $c{bold}Category$c{reset} : $info->{category}\n",
- "** $c{bold}Duration$c{reset} : $info->{duration}\n",
- "** $c{bold}Rating$c{reset} : $rating\n",
- "** $c{bold}Likes$c{reset} : ", set_thousands($info->{likes}) . "\n",
- "** $c{bold}Dislikes$c{reset} : ", set_thousands($info->{dislikes}) . "\n",
- "** $c{bold}Favorited$c{reset} : " . set_thousands($info->{favorited}) . "\n",
- "** $c{bold}Views$c{reset} : " . set_thousands($info->{views}) . "\n", do {
- $info->{published}
- ? sprintf("** $c{bold}Published$c{reset} : %s\n", format_date($info->{published}))
- : q{};
- }, "$constant{dash_line}\n";
- return if $opt{show_info_only};
- # Select one resolution and play video
- play_or_download(select_resolution(), $info->{title});
- }
- else {
- # This happens when a video has been deleted or forbidden
- warn "\n$c{bred}(x_x) Something went wrong...$c{reset}\n\n", "$c{bred}(x_x) Unable to stream: $c{reset}$url\n\n";
- if (@results and not $opt{video_id_from_arguments}) {
- unless (@picks) {
- sleep 1;
- print_results();
- }
- }
- else {
- unless (@results or $opt{video_id_from_arguments}) {
- main_quit();
- }
- }
- }
- }
- sub show_related_videos {
- my ($code) = @_;
- parse_url("$CONFIG{feeds_main_url}/videos/$code/related");
- }
- sub send_rating_to_video {
- my ($code, $rating) = @_;
- my $uri = "$CONFIG{feeds_main_url}/videos/$code/ratings";
- say _save(
- 'POST', $uri, <<"XML_HEADER"
- <?xml version="1.0" encoding="UTF-8"?>
- <entry xmlns="http://www.w3.org/2005/Atom"
- xmlns:yt="http://gdata.youtube.com/schemas/2007">
- <yt:rating value="$rating"/>
- </entry>
- XML_HEADER
- ) ? "\u${rating}d!" : 'Error!';
- }
- sub send_comment_to_video {
- my ($code, $comment) = @_;
- return unless length $comment;
- my $uri = "$CONFIG{feeds_main_url}/videos/$code/comments";
- say _save(
- 'POST', $uri, <<"XML_HEADER"
- <?xml version="1.0" encoding="UTF-8"?>
- <entry xmlns="http://www.w3.org/2005/Atom"
- xmlns:yt="http://gdata.youtube.com/schemas/2007">
- <content>$comment</content>
- </entry>
- XML_HEADER
- ) ? 'Comment sent' : 'Error!';
- }
- sub subscribe_channel {
- my ($user) = @_;
- my $uri = "$CONFIG{feeds_main_url}/users/default/subscriptions";
- say _save(
- 'POST', $uri, <<"XML_HEADER"
- <?xml version="1.0" encoding="UTF-8"?>
- <entry xmlns="http://www.w3.org/2005/Atom"
- xmlns:yt="http://gdata.youtube.com/schemas/2007">
- <category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat"
- term="channel"/>
- <yt:username>$user</yt:username>
- </entry>
- XML_HEADER
- ) ? "Successfully subscribed to user: $user" : 'Error!';
- }
- sub favorite_video {
- my ($code) = @_;
- my $uri = "$CONFIG{feeds_main_url}/users/default/favorites";
- say _save(
- 'POST', $uri, <<"XML_HEADER"
- <?xml version="1.0" encoding="UTF-8"?>
- <entry xmlns="http://www.w3.org/2005/Atom">
- <id>$code</id>
- </entry>
- XML_HEADER
- ) ? "** Successfully favorited video: $code" : 'Error!';
- }
- sub _request {
- my ($req) = @_;
- my $res = $lwp->request($req);
- if ($res->is_success) {
- return $res->content();
- }
- else {
- warn "Error: $res->code\n", $res->content;
- return;
- }
- }
- sub _prepare_request {
- my ($req, $length) = @_;
- $req->header('GData-Version' => 2);
- $req->header('Content-Length' => $length) if ($length);
- if ($lwp_header{Authorization}) {
- $req->header(Authorization => $lwp_header{Authorization});
- $req->header('X-GData-Key' => $lwp_header{'X-GData-Key'});
- }
- else {
- warn $prompt{needs_login};
- }
- }
- sub _save {
- my ($method, $uri, $content) = @_;
- my $req = HTTP::Request->new("$method" => $uri);
- $req->content_type('application/atom+xml; charset=UTF-8');
- _prepare_request($req, length($content));
- $req->content($content);
- return _request($req);
- }
- sub show_comments {
- my ($code, $index) = @_;
- print "\n";
- $index ||= 1;
- my $hash = xml2hash(
- lwp_get(
- "$CONFIG{feeds_main_url}/videos/$code/comments?"
- . array_to_gdata_arguments(
- 'v' => $constant{gdata_version},
- 'start-index' => $index
- )
- )
- );
- my $number = 0;
- while (
- my $gdata =
- ref $hash->{feed}{entry} eq 'ARRAY' ? $hash->{feed}{entry}[$number]
- : ref $hash->{feed}{entry} eq 'HASH' ? $hash->{feed}{entry}
- : $hash->{entry}
- ) {
- last unless defined $gdata;
- printf("$c{bold}%s$c{reset} on %s said:\n\t%s\n\n",
- $gdata->{author}{name},
- format_date($gdata->{updated}),
- $gdata->{content});
- ++$number;
- last unless ref $hash->{feed}{entry} eq 'ARRAY';
- }
- if ($number == 0) {
- print "No comments!\n\n";
- print_results();
- }
- {
- given ($term->readline($prompt{comments_next_page})) {
- when (q{}) {
- show_comments($code, $index + 25);
- }
- when (['?', 'help', 'h']) {
- print "\n", <<"HELP";
- <ENTER> : next page of comments
- c, comment : sent a comment to this video
- d, done : return to video results
- ?, h, help : this message
- HELP
- redo;
- }
- when (['c', 'comment']) {
- print "\n$c{bold}=>> $c{bgreen}Write your comment here - press CTRL+D when you are done$c{reset}\n";
- chomp(my $comment = join(q{}, <STDIN>));
- $comment =~ s/[^\s[:^cntrl:]]+//g;
- send_comment_to_video($code, $comment);
- redo;
- }
- default {
- print "\n";
- print_results();
- }
- }
- }
- return 1;
- }
- sub main_quit {
- write_config_to_file() if $opt{update_config};
- exit 0;
- }
- main_quit();
- package Config;
- sub _dump {
- require Data::Dumper;
- return Data::Dumper::Dumper(shift);
- }
- sub _sort_items {
- my ($data) = @_;
- my ($items) = $data =~ /\{(.+?)\s*\};?\s*\z/s;
- $items .= ',';
- $data = "#!/usr/bin/perl\n\nscalar {"
- . join(
- "\n",
- (
- sort { lc $a cmp lc $b }
- split(/\n/, $items, 0)
- )
- ) . "\n};\n";
- $data =~ s{=>\s*'(\d+)',\s*$} {=> $1,}gm;
- $data =~ s{(.+?)\s*=>\s*(.+)}{
- sprintf '%s%*s', $1, 45 - length($1) + length($2), ' => ' . $2;
- }egm;
- return $data;
- }
- sub save_hash {
- my ($file, $config) = @_;
- return unless ref $config eq 'HASH';
- open(my $fh, '>', $file) or return;
- print {$fh} _sort_items(_dump($config));
- close $fh;
- return 1;
- }
- 1;
Add Comment
Please, Sign In to add comment