App-YTDL

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


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.

Changes  view on Meta::CPAN


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.

Changes  view on Meta::CPAN

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

Changes  view on Meta::CPAN

        - 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.

Changes  view on Meta::CPAN

        - 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".

Changes  view on Meta::CPAN

        - 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.

Changes  view on Meta::CPAN


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



( run in 2.512 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )