view release on metacpan or search on metacpan
0.401 2020-07-22
- Code refactoring.
- New format for sticky and history files.
- Bugfix in history sort.
- Info output modification.
0.400 2020-07-21
- Use 'youtube-dl' the fetch video info.
- Bugfix: restore chosen file format after an error.
- Bugfix in add_uploaders_to_history
- Bugfix for the 'Search' entry in the list menu.
0.318 2020-03-31
- Update option 'filename format info'.
0.317 2020-03-31
- Update documentation.
0.316 2020-03-30
- Added "LWP::Protocol::https" as prerequisite.
- Bugfix: add format to filename.
- Option to set the format added to the filename.
0.315 2020-03-29
- Bugfix in uploader menu.
0.314 2020-03-25
- UI improvement.
0.313 2020-03-24
- Refactoring 'retries'.
0.312 2020-03-24
- Code refactoring.
- Update UI.
0.250 2016-02-21
- Requires "Term::Choose::LineFold".
- "Term::Choose" minimum version is "1.500".
- "Term::Choose-Util" minimum version is "0.050".
- Code refactoring.
- Update year copyright.
0.226 2015-12-08
- Modified regexp in GetData::_parse_yt_list_html.
- Marked the hidden menu entry in the uploader menu.
0.225 2015-10-06
- Bugfix: handle undefined date in "timestamp_to_upload_date".
0.224 2015-09-17
- Allow to reset preferred qualities.
- Added hidden menu entry.
0.223 2015-08-15
- Update css selectors.
0.222 2015-07-25
- Update regexps for arguments.
0.221 2015-07-10
- Uploader-menu: remove chosen items from the list of available items.
0.220 2015-06-06
- Bugfix in a download "sprintf".
0.219 2015-06-05
- Fixed bug in previous bugfix.
0.218 2015-06-05
- Bugfix: check if the "uploader_id" is an user-id or a channel-id.
0.217 2015-06-02
- Filename: remove leading dots.
- Bugfix in "_generic".
- Removed: check for deprecated "log_info".
0.216 2015-05-18
- Code refactoring.
0.215 2015-05-15
- Removed option "sleep interval".
- Update documentation.
0.209 2015-05-07
- Bugfix: undefined values if single yt ids.
- New option "sleep interval".
- Update documentation.
0.208 2015-05-06
- Bugfix "view count".
- New function to fetch uploader videos.
- Update documentation (youtube: fast list-menu is not supported any more).
0.207 2015-05-05
- "getvideo" requires now less time to get ready:
use "which('youtube-dl')" instead of "youtube-dl --version" to check if "youtube-dl" is available.
- Code refactoring.
0.206 2015-04-29
- New option "Encode filename".
- Bug fixes.
- Update documentation.
0.203_01 2015-04-18
- New options "fast list-menu" and "use netrc".
- New dependency "Mojo::DOM";
- Bugfixes;
- Code refactoring.
- Update documentation.
0.203 2015-04-14
- "view count" and "upload date": adapt behavior if they are not available.
- Bugfix video count.
- Update documentation.
0.202 2015-04-13
- Update list-menu layout.
- Set default timeout to 60s.
- Bugfix: set "video_id" explicitly.
0.201 2015-04-11
- New options "Replace spaces" and "Sanitize filename" and "Unmappable characters".
- Update documentation.
0.109_04 2015-04-03
- New dependency "File::BaseDir".
- Get config dir from "File::BaseDir::config_home" instead from "File::HomeDir::my_data".
- Bug fixes.
- Code refactoring.
- Update documentation.
0.109_03 2015-04-02
- "Timestamp to uploaddate" enabled by default.
- No "ucfirst" for extractor directory name.
- Invert filter: now space required between "!~" and pattern.
- Code refactoring.
- Update documentation.
0.109_02 2015-04-01
- New option: "Slots 'preferred qualities'".
- Update option menu.
- Bug fix.
- Code refactoring.
0.103 2015-01-29
- Code refactoring.
- Changed shebang line.
- Channel history: mark the channel that will be removed next.
0.102 2015-01-22
- Fix and refactoring "FILTER".
0.101 2015-01-18
- Option to change the file timestamp to the upload date.
- New dependencies: "File::Touch", "Time::Local".
- "Term::Choose-Util" minimum version is "0.022".
- Code refactoring.
- Update help menu.
- Update documentation.
0.100 2015-01-17
- Changed the format of different files.
- "preferred qualities" can be set for all extractors.
- New option "channel sort".
bin/getvideo view on Meta::CPAN
use Encode::Locale qw( decode_argv );
use File::HomeDir qw();
use File::Which qw( which );
use Term::ANSIScreen qw( :cursor :screen );
use Term::Choose qw( choose );
use Term::Form qw();
use App::YTDL::Arguments qw( from_arguments_to_choices );
use App::YTDL::Download qw( download );
use App::YTDL::Helper qw( uni_capture );
use App::YTDL::History qw( add_uploaders_to_history read_history_files choose_videos_from_saved_uploaders edit_sticky_file );
use App::YTDL::Video_Info qw( print_video_infos );
use App::YTDL::Options qw( read_config_file set_options get_defaults );
binmode STDIN, ':encoding(utf-8)';
binmode STDOUT, ':encoding(utf-8)';
binmode STDERR, ':encoding(utf-8)';
my $old_out_codepage;
BEGIN {
bin/getvideo view on Meta::CPAN
my $config_dir = catdir $config_home, 'getvideo';
make_path $config_dir;
my $set = {
ffmpeg => which( 'ffmpeg' ),
ffprobe => which( 'ffprobe' ),
config_dir => $config_dir,
config_file => catfile( $config_dir, 'config.json' ),
history_file => catfile( $config_dir, 'uploader_history.json' ),
sticky_file => catfile( $config_dir, 'uploader_sticky.json' ),
right_margin => $^O eq 'MSWin32' ? 1 : 2,
list_sort_order => 'Desc',
failed_fetching_data => 'FAILED fetching required data!',
};
my $opt = get_defaults();
my $youtube_dl = which( 'youtube-dl' );
if ( ! $youtube_dl ) {
bin/getvideo view on Meta::CPAN
print up( 1 ), cldown;
}
my $urls_data = {};
my $data = {};
if ( @ids ) {
my $chosen = from_arguments_to_choices( $set, $opt, $urls_data, $data, @ids );
print_video_infos( $set, $opt, $data, $chosen );
download( $set, $opt, $data, $chosen );
add_uploaders_to_history( $set, $opt, $data ) if $opt->{max_size_history};
}
elsif ( ! @ids && ( %{$set->{history}} || %{$set->{sticky}} ) ) {
my $clear_screen = 1;
my $old_idx = 0;
MENU: while ( 1 ) {
my ( $uploader, $sticky ) = ( '- Uploaders', ' Edit Sticky' );
my $menu = [ undef, $uploader, $sticky ];
# Choose
my $idx = choose(
$menu,
{ prompt => 'Choose:', layout => 3, undef => ' QUIT',
clear_screen => $clear_screen, index => 1, default => $old_idx }
);
if ( ! defined $idx || ! defined $menu->[$idx] ) {
exit;
}
#if ( $opt->{menu_memory} ) {
# if ( $old_idx == $idx && ! $ENV{TC_RESET_AUTO_UP} ) {
# $old_idx = 0;
# next MENU;
# }
# $old_idx = $idx;
#}
if ( $menu->[$idx] eq $uploader ) {
my @chosen_uploaders = choose_videos_from_saved_uploaders( $set, $opt );
next if ! @chosen_uploaders;
my $chosen = from_arguments_to_choices( $set, $opt, $urls_data, $data, @chosen_uploaders );
print_video_infos( $set, $opt, $data, $chosen );
my $total = download( $set, $opt, $data, $chosen );
$clear_screen = $total ? 0 : 1;
say "";
}
elsif ( $menu->[$idx] eq $sticky ) {
edit_sticky_file( $set, $opt );
}
}
}
bin/getvideo view on Meta::CPAN
getvideo -h|-?|--help
getvideo
getvideo url [url ...]
getvideo -f|--file filename
=head1 DESCRIPTION
Download single videos or choose videos from a playlist or an uploader.
Call C<getvideo> followed by the space separated urls or enter the urls after calling C<getvideo>. If the urls are
entered after calling C<getvideo>, shell metacharacters are escaped automatically.
The urls can also be passed with a file: C<getvideo -f|--file filename>. The urls in the file have to be space separated.
If a passed url results in more than one video, it is shown a menu with the video titles. The user can then choose from
the menu which videos to download. It is possible to filter the video titles of the list menu with a regexp. The filter
can be inverted by adding C<!~> and a space in front of the regexp. Use the C<SpaceBar> to select more than one video -
see L<Term::Choose/USAGE-AND-RETURN-VALUES>.
bin/getvideo view on Meta::CPAN
=head3 Extractor directory
Create/use extractor directories.
- no
- yes
=head3 Uploader directory
Create/use uploader directories
- no
- yes
- if chosen from an uploader or a playlist
=head3 Filename format info
Which format info should be added to the filename.
- none (unsafe)
- video-height (unsafe)
- format-id
bin/getvideo view on Meta::CPAN
Set the number of download retries (I<--retries>).
=head3 Timeout
Connection timeout in seconds (I<--socket-timeout>).
=head2 History
=head3 Size history
If no arguments are passed to C<getvideo>, the user can choose videos from uploaders saved in the history file and the
sticky file.
I<Size history> sets the limit of the number of uploaders saved in the history file. Setting I<size history> to C<0>
disables the uploader-history.
An uploader can be made sticky. Uploaders made sticky don't count regarding the I<size history> limit. An uploader made
sticky gets also a new timestamp.
When added to the uploader-history an uploader gets the current timestamp. If the I<size history> limit is reached, the
uploader with the oldest timestamp is removed first.
=head3 History sort
Sort the history
- by name
- by timestamp
=head2 Uploader video list
=head3 Additional information
For the first [value of I<additional information>] uploader video list entries is downloaded and shown additional
information. The uploader video list is ordered is adopted from the output returned by C<--dump-single-json> from
C<youtube-dl>. See also the following option I<Max processes>.
=head3 Max processes
If I<Additional information> is enabled I<Max processes> sets how many data-downloads are done in parallel.
=head3 Sort order
Sort the first [value of I<additional information>] videos in the list-menu by
- upload date (fallback title);
- title
- view count (fallback upload date)
- duration (fallback upload date)
Fallback is used, if the chosen entry is not available.
=head3 Show view count
Show the view count in the first [value of I<additional information>] uploader video list entries
- if sorted by view count
- always
=head3 Show video id
Show the video id in the entries of the video list.
- no
lib/App/YTDL/Arguments.pm view on Meta::CPAN
else {
if ( $opt->{entries_with_info} ) {
_get_video_data( $set, $opt, $data, $ex, $up, $ids, $progress );
}
}
}
URL_DATA: for my $webpage_url ( @args ) {
my ( $ex, $up, $ids ) = @{$urls_data->{$webpage_url}};
if ( @$ids > 1 ) {
my $prompt = $data->{$ex}{$up}{$ids->[0]}{uploader} || $webpage_url;
my $chosen_ids = choose_videos( $set, $opt, $data, $ex, $up, $ids, $prompt );
if ( ! defined $chosen_ids ) {
next URL_DATA;
}
push @{$chosen->{$ex}{$up}}, @$chosen_ids;
}
else {
push @{$chosen->{$ex}{$up}}, @$ids;
}
}
lib/App/YTDL/ChooseVideos.pm view on Meta::CPAN
( $s[0] // '' ) cmp ( $s[1] // '' )
|| $h_ref->{$a}{title} cmp $h_ref->{$b}{title};
}
}
sub choose_videos {
my ( $set, $opt, $data, $ex, $up, $ids, $prompt ) = @_;
my $view_count_w = 0;
my $id_w = 0;
my $upload_date_w = 0;
my $duration_w = 7;
my $has_date_sort = 0;
my $has_view_count = 0;
my $has_duration = 0;
my $has_upload_date = 0;
my $dummy;
for my $id ( keys %{$data->{$ex}{$up}} ) {
$has_duration++ if defined $data->{$ex}{$up}{$id}{duration} && $data->{$ex}{$up}{$id}{duration} ne '-:--:--';
$has_date_sort++ if defined $data->{$ex}{$up}{$id}{date_sort};
$has_view_count++ if defined $data->{$ex}{$up}{$id}{view_count};
$has_upload_date++ if defined $data->{$ex}{$up}{$id}{upload_date} && $data->{$ex}{$up}{$id}{upload_date} ne '';
( $dummy, $id_w ) = minmax( length $id, $id_w );
( $dummy, $view_count_w ) = minmax( length( $data->{$ex}{$up}{$id}{view_count} // '' ), $view_count_w );
( $dummy, $upload_date_w ) = minmax( length( $data->{$ex}{$up}{$id}{upload_date} // '' ), $upload_date_w );
}
if ( $upload_date_w && $upload_date_w < 10 ) {
$upload_date_w = 10;
}
my $regexp;
my $back = 'BACK';
my $filter = 'FILTER';
my $sort = 'SORT';
my $enter = 'ENTER';
my $add_info = $has_view_count + $has_duration + $has_upload_date;
my $chosen_ids = {};
my @last_chosen_ids = ();
FILTER: while ( 1 ) {
my $sort_item = $opt->{list_sort_item};
my $sort_order = $set->{list_sort_order};
if ( $sort_item eq 'view_count_raw' && ! $has_view_count ) {
$sort_item = 'upload_date';
}
if ( $sort_item eq 'duration' && ! $has_duration ) {
$sort_item = 'upload_date';
}
if ( $sort_item eq 'upload_date' ) {
if ( ! $upload_date_w ) {
$sort_item = 'title';
$sort_order = 'Asc';
}
elsif ( $has_date_sort ) {
$sort_item = 'date_sort';
$sort_order = $sort_order eq 'Asc' ? 'Desc' : 'Asc';
}
}
my @pre = (
length $regexp ?
lib/App/YTDL/ChooseVideos.pm view on Meta::CPAN
}
else {
next VIDEO_ID if $title !~ /$regexp/i;
}
}
$data->{$ex}{$up}{$id}{from_list} = 1;
my $line = '';
$line .= sprintf "%*s | ", $id_w, $id if $opt->{show_video_id};
if ( $opt->{entries_with_info} ) {
$line .= sprintf "%*s ", $duration_w, $data->{$ex}{$up}{$id}{duration} // '' if $has_duration;
$line .= sprintf "%*s ", $upload_date_w, $data->{$ex}{$up}{$id}{upload_date} // '' if $has_upload_date;
$line .= sprintf "%*s ", $view_count_w, $data->{$ex}{$up}{$id}{view_count} // '' if $has_view_count && ( $opt->{list_sort_item} eq 'view_count_raw' || $opt->{show_view_count} );
}
$line .= sprintf "%s", $title;
push @str_videos, $line;
push @avail_ids, $id;
$index++;
push @$mark, $index if any { $id eq $_ } keys %$chosen_ids;
}
my $menu = [ @pre, @str_videos ];
# Choose
lib/App/YTDL/ChooseVideos.pm view on Meta::CPAN
}
}
}
}
sub set_sort_videolist {
my ( $set, $opt ) = @_;
my $backup_item = $opt->{list_sort_item};
my $backup_order = $set->{list_sort_order};
my $sort_items = [ 'upload_date', 'title', 'view_count_raw', 'duration' ];
my $confirm = ' CONFIRM';
my @pre = ( undef, $confirm );
my $menu = [ @pre, map { my $s = $_; $s =~ s/_raw\z//; $s =~ s/_/ /g; '- ' . $s } @$sort_items ];
ITEM: while ( 1 ) {
my $current = $opt->{list_sort_item};
$current =~ s/_raw\z//;
$current =~ s/_/ /g;
my $prompt = sprintf 'Sort item: [%s]', $current;
my $idx = choose (
lib/App/YTDL/Download.pm view on Meta::CPAN
EXTRACTOR_KEY: for my $ex ( sort keys %$data ) {
UPLOADER_ID: for my $up ( sort keys %{$data->{$ex}} ) {
if ( ! exists $chosen->{$ex}{$up} ) {
next;
}
my @sorted_ids = sort { $data->{$ex}{$up}{$a}{count} <=> $data->{$ex}{$up}{$b}{count} } @{$chosen->{$ex}{$up}};
my $keep_fmt_choices;
if ( $qty eq 'manually' && @sorted_ids > 1 ) {
my ( $yes, $no ) = ( '- YES', '- NO' );
my $prompt = "\n" . ( $data->{$ex}{$up}{$sorted_ids[0]}{uploader} // $up ) . ':' . "\n";
$prompt .= 'Keep format choices?';
# Choose
my $choice = choose(
[ undef, $yes, $no ],
{ prompt => $prompt, undef => '<<', layout => 3 }
);
if ( ! defined $choice ) {
next UPLOADER_ID;
}
elsif ( $choice eq $no ) {
lib/App/YTDL/Download.pm view on Meta::CPAN
$el = 'bestvideo+bestaudio/best';
}
}
$fmt_str = join '', @fmt_data;
push @cmd, '-f', $fmt_str;
if ( ! $keep_fmt_choices ) {
$fmt_str = undef;
}
my $output = $opt->{video_dir};
$output .= '/%(extractor)s' if $opt->{use_extractor_dir};
$output .= '/%(uploader)s' if $opt->{use_uploader_dir} == 1 || $opt->{use_uploader_dir} == 2 && $data->{$ex}{$up}{$id}{from_list};
if ( $opt->{filename_format} == 0 ) { $output .= '/%(title)s.%(ext)s'; }
elsif ( $opt->{filename_format} == 1 ) { $output .= '/%(title)s_%(height)s.%(ext)s'; }
elsif ( $opt->{filename_format} == 2 ) { $output .= '/%(title)s_%(format_id)s.%(ext)s'; }
elsif ( $opt->{filename_format} == 3 ) { $output .= '/%(title)s_%(format_id)s_%(height)s.%(ext)s'; }
else { $output .= '/%(title)s_%(format)s.%(ext)s'; }
push @cmd, '-o', $output;
push @cmd, '--', $data->{$ex}{$up}{$id}{webpage_url};
$download_cmds{$ex}{$up}{$id} = [ @cmd ];
if ( @backup_fmt_data ) {
lib/App/YTDL/ExtractData.pm view on Meta::CPAN
our @EXPORT_OK = qw( extract_data_list extract_data_single );
use Term::Choose::Util qw( insert_sep );
use App::YTDL::Helper qw( sec_to_time );
sub extract_data_list {
my ( $set, $opt, $data, $h_ref ) = @_;
my $ex;
my $up = $h_ref->{uploader_id} // 'no_up_id';
my $ids = [];
if ( exists $h_ref->{entries} ) {
$ex = $h_ref->{entries}[0]{ie_key} // 'no_ex_key';
ENTRY: for my $entry ( @{$h_ref->{entries}} ) {
my $id = $entry->{id};
my @keys = qw(url title ie_key);
for my $key ( @keys ) {
$data->{$ex}{$up}{$id}{$key} = $entry->{$key} if defined $entry->{$key};
}
$data->{$ex}{$up}{$id}{uploader_url} = $h_ref->{uploader_url};
$data->{$ex}{$up}{$id}{uploader} = $h_ref->{uploader};
$data->{$ex}{$up}{$id}{title} ||= sprintf 'no_title_%s', $id;
push @$ids, $id;
}
}
else {
$ex = $h_ref->{extractor_key} // 'no_ex_key';
my $id = $h_ref->{id};
my $single_data = extract_data_single( $set, $opt, $h_ref );
for my $key ( keys %$single_data ) {
$data->{$ex}{$up}{$id}{$key} = $single_data->{$key};
lib/App/YTDL/ExtractData.pm view on Meta::CPAN
my $c = 1;
for my $format ( @{$h_ref->{formats}} ) {
my $fmt = $format->{format_id} // 'no_fmt_' . $c++;
$fmt_to_info->{$fmt}{ext} = $format->{ext};
$fmt_to_info->{$fmt}{format} = $format->{format};
$fmt_to_info->{$fmt}{format_note} = $format->{format_note};
$fmt_to_info->{$fmt}{height} = $format->{height};
$fmt_to_info->{$fmt}{width} = $format->{width};
$fmt_to_info->{$fmt}{url} = $format->{url};
}
my @keys = qw(uploader uploader_url description format_id like_count dislike_count average_rating
raters duration extractor extractor_key title upload_date view_count webpage_url); # age_limit annotations categories
for my $key ( @keys ) {
$single_data->{$key} = $h_ref->{$key} if defined $h_ref->{$key};
}
$single_data->{fmt_to_info} = $fmt_to_info;
_prepare_data( $single_data );
return $single_data;
}
sub _prepare_data {
my ( $single_data ) = @_;
$single_data->{url} = $single_data->{webpage_url};
if ( $single_data->{upload_date} ) {
if ( $single_data->{upload_date} =~ /^(\d{4})(\d{2})(\d{2})\z/ ) {
$single_data->{upload_date} = $1 . '-' . $2 . '-' . $3;
$single_data->{upload_datetime} = $1 . '-' . $2 . '-' . $3 . 'T00:00:00';
}
if ( $single_data->{upload_date} =~ /^(\d\d\d\d-\d\d-\d\d)T(\d\d:\d\d:\d\d)/ ) {
$single_data->{upload_date} = $1;
$single_data->{upload_datetime} = $1 . 'T' . $2;
}
}
if ( ! $single_data->{upload_date} && $single_data->{upload_date_rel} ) {
$single_data->{upload_date} = $single_data->{upload_date_rel};
}
$single_data->{duration} ||= '-:--:--';
$single_data->{upload_date} ||= '';
$single_data->{upload_datetime} ||= '0000-00-00T00:00:00';
if ( $single_data->{duration} =~ /^[0-9]+\z/ ) {
$single_data->{duration} = sec_to_time( $single_data->{duration}, 1 );
}
if ( $single_data->{like_count} && $single_data->{dislike_count} ) {
$single_data->{raters} ||= $single_data->{like_count} + $single_data->{dislike_count};
$single_data->{average_rating}
||= ( $single_data->{like_count} * 5 + $single_data->{dislike_count} ) / $single_data->{raters}; #
}
if ( $single_data->{average_rating} ) {
$single_data->{average_rating} = sprintf "%.2f", $single_data->{average_rating};
lib/App/YTDL/History.pm view on Meta::CPAN
package # hide from PAUSE
App::YTDL::History;
use warnings;
use strict;
use 5.010000;
use Exporter qw( import );
our @EXPORT_OK = qw( read_history_files add_uploaders_to_history choose_videos_from_saved_uploaders edit_sticky_file );
use List::MoreUtils qw( any );
use Term::Choose qw( choose );
use App::YTDL::Helper qw( read_json write_json );
sub read_history_files {
my ( $set, $opt ) = @_;
$set->{sticky} = {};
lib/App/YTDL/History.pm view on Meta::CPAN
# ### ############################################################ ### #
}
}
}
sub _old_file_fmt_to_new_file_fmt { # ### # remove that after a while # added 0.401
my ( $h_ref ) = @_;
my $new_h_ref = {};
for my $key ( keys %$h_ref ) {
my $uploader_url = $key;
my $uploader_id = $h_ref->{$uploader_url}{uploader_id};
my $uploader = $h_ref->{$uploader_url}{uploader};
my $extractor_key = $h_ref->{$uploader_url}{extractor};
my $timestamp = $h_ref->{$uploader_url}{timestamp};
$new_h_ref->{$uploader_id} = {
uploader_url => $uploader_url,
uploader => $uploader,
extractor_key => $extractor_key,
timestamp => $timestamp,
};
}
return $new_h_ref;
}
sub choose_videos_from_saved_uploaders {
my ( $set, $opt ) = @_;
my $truncated = _to_history_size_limit( $set, $opt );
if ( $truncated ) {
write_json( $set->{history_file}, $set->{history} );
}
my $prompt = 'Chosen:' . "\n";
my @urls;
my $sticky = _get_sorted_sticky( $set->{sticky} );
my $history = _get_sorted_history( $opt, $set->{history} );
CHANNEL: while ( 1 ) {
my $mark = '*';
my $confirm = ' CONFIRM';
my $hidden = 'Your choice:' . $mark;
my @pre = ( $hidden, undef, $confirm );
my $available_uploader = _available_uploader( $set, $opt, $sticky, $history );
my $menu = [ @pre, @$available_uploader ];
# Choose
my @indexes = choose(
$menu,
{ prompt => $prompt, layout => 3, index => 1, default => 1,
undef => ' BACK', meta_items => [ 0 .. $#pre ], include_highlighted => 2 }
);
if ( ! defined $indexes[0] || ! defined $menu->[$indexes[0]] ) {
if ( @urls ) {
$prompt = 'Channels:' . "\n";
@urls = ();
lib/App/YTDL/History.pm view on Meta::CPAN
$history = _get_sorted_history( $opt, $set->{history} );
$mark = '*';
next CHANNEL;
}
else {
return;
}
}
elsif ( $menu->[$indexes[0]] eq $hidden ) {
for my $i ( 0 .. $#$sticky ) {
$prompt .= sprintf "= %s (%s)\n", $sticky->[$i]{uploader}, $sticky->[$i]{uploader_id};
push @urls, $sticky->[$i]{uploader_url};
}
@$sticky = ();
$mark = '';
next CHANNEL;
}
my $confirmed;
if ( $menu->[$indexes[0]] eq $confirm ) {
shift @indexes;
$confirmed = 1;
}
my $count = 0;
for my $i ( @indexes ) {
$i -= @pre + $count;
if ( $i <= $#$sticky ) {
$prompt .= sprintf "= %s (%s)\n", $sticky->[$i]{uploader}, $sticky->[$i]{uploader_id};
push @urls, $sticky->[$i]{uploader_url};
splice @$sticky, $i, 1;
}
else {
$i -= @$sticky;
$prompt .= sprintf "= %s (%s)\n", $history->[$i]{uploader}, $history->[$i]{uploader_id};
push @urls, $history->[$i]{uploader_url};
splice @$history, $i, 1;
}
$count++;
}
$mark = '*';
if ( $confirmed ) {
return @urls;
}
}
lib/App/YTDL/History.pm view on Meta::CPAN
sub edit_sticky_file {
my ( $set, $opt ) = @_;
my $sticky = _get_sorted_sticky( $set->{sticky} );
my $history = _get_sorted_history( $opt, $set->{history} );
my $changed = 0;
STICKY: while ( 1 ) {
my @pre = ( undef );
my $menu = [ @pre, map( "+ $_->{uploader}", @$sticky ), map( "- $_->{uploader}", @$history ) ];
# Choose
my $idx = choose(
$menu,
{ prompt => 'Stickify:', layout => 3, index => 1, undef => ' <<' }
);
if ( ! defined $idx || ! defined $menu->[$idx] ) {
if ( $changed ) {
write_json( $set->{sticky_file}, $set->{sticky} );
write_json( $set->{history_file}, $set->{history} );
}
return;
}
else {
$changed++;
$idx-= @pre;
if ( $idx > $#$sticky ) {
$idx -= @$sticky;
my $up = $history->[$idx]{uploader_id};
$set->{sticky}{$up} = delete $set->{history}{$up};
}
else {
my $up = $sticky->[$idx]{uploader_id};
$set->{history}{$up} = delete $set->{sticky}{$up};
$set->{history}{$up}{timestamp} = time();
}
$sticky = _get_sorted_sticky( $set->{sticky} );
$history = _get_sorted_history( $opt, $set->{history} );
}
}
}
sub _uploader_id_removed_next {
my ( $set, $opt ) = @_;
my $amount = 3;
if ( ! $opt->{max_size_history} || $opt->{max_size_history} - $amount > keys %{$set->{history}} ) {
return;
}
my $removed_next = [];
my $last_added = [];
my $count = 0;
for my $key ( sort { $set->{history}{$b}{timestamp} <=> $set->{history}{$a}{timestamp} } keys %{$set->{history}} ) {
$count++;
if ( $count > $opt->{max_size_history} - $amount ) {
push @$removed_next, $key;
}
}
return $removed_next;
}
sub _available_uploader {
my ( $set, $opt, $sticky, $history ) = @_;
my $choices = [ map( "* $_->{uploader}", @$sticky ) ];
my $removed_next = _uploader_id_removed_next( $set, $opt );
for my $i ( 0 .. $#$history ) {
if ( any { $history->[$i]{uploader_id} eq $_ } @$removed_next ) {
push @$choices, "| $history->[$i]{uploader}";
}
else {
push @$choices, " $history->[$i]{uploader}";
}
}
return $choices;
}
sub _get_sorted_history {
my ( $opt, $ref ) = @_;
my $history = [];
for my $key ( sort { $opt->{sort_history_by_timestamp} ?
$ref->{$b}{timestamp} <=> $ref->{$a}{timestamp}
: lc $ref->{$a}{uploader} cmp lc $ref->{$b}{uploader} } keys %$ref
) {
push @$history, {
uploader_id => $key,
uploader => $ref->{$key}{uploader},
uploader_url => $ref->{$key}{uploader_url},
};
}
return $history;
}
sub _get_sorted_sticky {
my ( $ref ) = @_;
my $sticky;
for my $key ( sort { lc $ref->{$a}{uploader} cmp lc $ref->{$b}{uploader} } keys %$ref ) {
push @$sticky, {
uploader_id => $key,
uploader => $ref->{$key}{uploader},
uploader_url => $ref->{$key}{uploader_url},
};
}
return $sticky;
}
sub add_uploaders_to_history {
my ( $set, $opt, $data ) = @_;
EXTRACTOR_KEY: for my $ex ( keys %$data ) {
UPLOADER_ID: for my $up ( keys %{$data->{$ex}} ) {
if ( $up eq 'no_up_id' ) {
return;
}
if ( exists $set->{sticky}{$up} ) {
return;
}
my @keys = ( keys %{$data->{$ex}{$up}} );
my $id = $keys[0];
$set->{history}{$up} = {
uploader => $data->{$ex}{$up}{$id}{uploader},
uploader_url => $data->{$ex}{$up}{$id}{uploader_url},
extractor_key => $ex,
timestamp => time(),
};
}
}
_to_history_size_limit( $set, $opt );
write_json( $set->{history_file}, $set->{history} );
}
lib/App/YTDL/Options.pm view on Meta::CPAN
[ 'Close' ],
{ prompt => $prompt }
);
}
sub get_defaults {
return {
entries_with_info => 0,
filename_format => 3,
list_sort_item => 'upload_date',
max_info_width => 120,
max_processes => 5,
max_rows_description => 3,
max_size_history => 50,
no_height_ok => 1,
no_warnings => 0,
prefer_free_formats => 1,
quality => 'manually',
retries => 7,
show_video_id => 0,
show_view_count => 0,
sort_history_by_timestamp => 1,
timeout => 60,
useragent => 'Mozilla/5.0',
use_extractor_dir => 1,
use_uploader_dir => 1,
video_dir => File::HomeDir->my_videos() // File::HomeDir->my_home() // curdir,
yt_dl_config_location => undef,
yt_dl_ignore_config => 0,
};
}
sub _main {
return [
[ 'show_help_text', " HELP" ],
lib/App/YTDL/Options.pm view on Meta::CPAN
[ 'extractor_descriptions', " Extractor descriptions" ],
];
}
sub _groups {
return {
group_directory => [
[ 'video_dir', "- Main video directory" ],
[ 'use_extractor_dir', "- Extractor directory" ],
[ 'use_uploader_dir', "- Uploader directory" ],
[ 'filename_format', "- Filename format info" ],
],
group_quality => [
[ 'quality', "- Resolution" ],
[ 'no_height_ok', "- No video height" ],
[ 'prefer_free_formats', "- Prefer free formats" ],
],
group_download => [
[ 'useragent', "- UserAgent" ],
[ 'retries', "- Download retries" ],
lib/App/YTDL/Options.pm view on Meta::CPAN
if ( $key eq "video_dir" ) {
my $info = "Video Directory\nNow: " . $opt->{$key} // 'current directory';
my $name = 'New: ';
_opt_choose_a_directory( $set, $opt, $key, $info, $name );
}
elsif ( $key eq "use_extractor_dir" ) {
my $prompt = 'Use extractor directories';
my $list = [ 'no', 'yes' ];
_opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
}
elsif ( $key eq "use_uploader_dir" ) {
my $prompt = 'Use uploader directories';
my $list = [
'no',
'yes',
'if from list-menu',
];
_opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
}
elsif ( $key eq "filename_format" ) {
my $prompt = 'Filename: choose the format info:';
my $list = [
lib/App/YTDL/Video_Info.pm view on Meta::CPAN
if ( eval { $h_ref = get_download_info( $set, $opt, $url, 0 ); 1 } ) {
my $single_data = extract_data_single( $set, $opt, $h_ref );
for my $key ( keys %$single_data ) {
$data->{$ex}{$up}{$id}{$key} = $single_data->{$key};
}
}
else {
$data->{$ex}{$up}{$id}{description} = $set->{failed_fetching_data};
}
}
$data->{$ex}{$up}{$id}{published} = $data->{$ex}{$up}{$id}{upload_date} if defined $data->{$ex}{$up}{$id}{upload_date};
$data->{$ex}{$up}{$id}{author} = $data->{$ex}{$up}{$id}{uploader} if defined $data->{$ex}{$up}{$id}{uploader};
$data->{$ex}{$up}{$id}{avg_rating} = $data->{$ex}{$up}{$id}{average_rating} if defined $data->{$ex}{$up}{$id}{average_rating};
$data->{$ex}{$up}{$id}{id_extractor} = $id . ' ' . $data->{$ex}{$up}{$id}{extractor} if defined $data->{$ex}{$up}{$id}{extractor};
for my $key ( @keys ) {
next if ! $data->{$ex}{$up}{$id}{$key};
$data->{$ex}{$up}{$id}{$key} =~ s/\R/ /g;
}
return @keys;
}
xt/spelling.t view on Meta::CPAN
Vimeo
vimeo
getvideo
ffmpeg
ffprobe
unmappable
Unmappable
Uploader
Uploaders
netrc
uploader
uploaders
de