App-YTDL

 view release on metacpan or  search on metacpan

lib/App/YTDL/Options.pm  view on Meta::CPAN

package # hide from PAUSE
App::YTDL::Options;

use warnings;
use strict;
use 5.010000;

use Exporter qw( import );
our @EXPORT_OK = qw( read_config_file set_options get_defaults );

use Encode;
use File::Spec::Functions qw( catfile curdir );
use File::Temp            qw();
use FindBin               qw( $RealBin $RealScript );
use Pod::Usage            qw( pod2usage );

use File::HomeDir      qw();
use Term::Choose       qw( choose );
use Term::Choose::Util qw( choose_a_directory choose_a_file choose_a_number settings_menu insert_sep );
use Term::Form         qw();

use App::YTDL::ChooseVideos qw( set_sort_videolist );
use App::YTDL::Helper       qw( write_json read_json uni_capture HIDE_CURSOR SHOW_CURSOR );


sub _show_info {
    my ( $set, $opt ) = @_;
    my ( $youtube_dl_version, $ffmpeg_version, $ffprobe_version );
    if ( ! eval { $youtube_dl_version = uni_capture( @{$set->{youtube_dl}}, '--version' ); 1 } ) {
        $youtube_dl_version = $@;
    }
    chomp $youtube_dl_version;
    eval {
        $ffmpeg_version = uni_capture( $set->{ffmpeg}, '-version' );
        if ( $ffmpeg_version && $ffmpeg_version =~ /^ffmpeg version (\S+)/m ) {
            $ffmpeg_version = $1;
        }
        else {
            $ffmpeg_version = '';
        }
    };
    eval {
        $ffprobe_version = uni_capture( $set->{ffprobe}, '-version' );
        if ( $ffprobe_version && $ffprobe_version =~ /^ffprobe version (\S+)/m ) {
            $ffprobe_version = $1;
        }
        else {
            $ffprobe_version = '';
        }
    };
    my $prompt = "INFO\n\n";
    $prompt .= "Directories:\n";
    $prompt .= "    Video : $opt->{video_dir}"   . "\n";
    $prompt .= "    Config: $set->{config_file}" . "\n";
    $prompt .= "\n";
    $prompt .= "Versions:\n";
    $prompt .= '    ' . catfile( $RealBin, $RealScript ) . ": " . $main::VERSION                 . "\n";
    $prompt .= '    ' . $set->{youtube_dl}[0]            . ": " . ( $youtube_dl_version // '?' ) . "\n";
    if ( $set->{ffmpeg} ) {
        $prompt .= '    ' . $set->{ffmpeg} . " : " . ( $ffmpeg_version // '?' ) . "\n";
    }
    if ( $set->{ffprobe} ) {
        $prompt .= '    ' . $set->{ffprobe} . ": " . ( $ffprobe_version // '?' ) . "\n";
    }
    # Choose
    choose(
        [ '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"                   ],
        [ 'show_info',               "  INFO"                   ],
        [ 'group_directory',         "- Directories"            ],
        [ 'group_quality',           "- Video format"           ],
        [ 'group_download',          "- Download"               ],
        [ 'group_history',           "- History"                ],
        [ 'group_list_menu',         "- Uploader video list"    ],
        [ 'group_output',            "- Info output"            ],
        [ 'group_yt_dl_config_file', "- Youtube-dl config file" ],
        [ 'avail_extractors',        "  List extractors"        ],
        [ '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" ],
            [ 'timeout',    "- Timeout"          ],
        ],
        group_output => [
            [ 'no_warnings',          "- Disable warnings" ],
            [ 'max_info_width',       "- Max info width"   ],
            [ 'max_rows_description', "- Max rows of description" ],
        ],
        group_history => [
            [ 'max_size_history',          "- Size history" ],
            [ 'sort_history_by_timestamp', "- History sort" ],
        ],
        group_list_menu => [
            [ 'entries_with_info', "- Additional info" ],
            [ 'max_processes',     "- Max processes"   ],
            [ 'list_sort_item',    "- Sort order"      ],
            [ 'show_view_count',   "- Show view count" ],
            [ 'show_video_id',     "- Show video id"   ],
        ],
        group_yt_dl_config_file => [
            [ 'yt_dl_config_location',        "- set yt-dl config location"   ],
            [ '_reset_yt_dl_config_location', "- reset yt-dl config location" ],
            [ 'yt_dl_ignore_config',          "- Ignore yt-dl config file"    ],
        ],
    };
}


sub set_options {
    my ( $set, $opt ) = @_;
    my $main = _main();
    my $old_idx_main = 0;

    MAIN_MENU: while ( 1 ) {
        my $back_main     = '  QUIT';
        my $continue_main = '  CONTINUE';
        my @pre  = ( undef, $continue_main );
        my @choices_main = map( $_->[1], @$main );
        my $menu_main = [ @pre, @choices_main ];
        # Choose
        my $idx_main = choose(
            $menu_main,
            { prompt => 'Options:', layout => 3, index => 1, clear_screen => 1,
              undef => $back_main, default => $old_idx_main }
        );
        if ( ! defined $idx_main || ! defined $menu_main->[$idx_main] ) {
            if ( $set->{change} ) {
                _write_config_file( $opt, $set->{config_file} );
                delete $set->{change};
            }
            exit;
        }
        if ( $old_idx_main == $idx_main ) {
            $old_idx_main = 0;
            next MAIN_MENU;
        }
        $old_idx_main = $idx_main;
        my $choice = $menu_main->[$idx_main];
        if ( $choice eq $continue_main ) {
            if ( $set->{change} ) {
                _write_config_file( $opt, $set->{config_file} );
                delete $set->{change};
            }
            last MAIN_MENU;
        }
        my ( $main_key, $main_prompt ) = @{$main->[$idx_main-@pre]};
        $main_prompt =~ s/^-\s//;
        if ( $main_key !~ /^group_/ ) {
            if ( $main_key eq "show_help_text" ) {
                pod2usage( { -exitval => 'NOEXIT', -verbose => 2 } );
            }
            elsif ( $main_key eq "show_info" ) {
                _show_info( $set, $opt );
            }
            elsif ( $main_key eq "avail_extractors" ) {
                say 'Close with ENTER';
                my @capture;
                if ( ! eval { @capture = uni_capture( @{$set->{youtube_dl}}, '--list-extractors' ); 1 } ) {
                    say "List extractors: $@";
                }
                if ( @capture ) {
                    # Choose
                    choose(
                        [ map { "  $_" } @capture ],
                        { layout => 3 }
                    );
                }
            }
            elsif ( $main_key eq "extractor_descriptions" ) {
                say 'Close with ENTER';
                my @capture;
                if ( ! eval { @capture = uni_capture( @{$set->{youtube_dl}}, '--extractor-descriptions' ); 1 } ) {
                    say "Extractor descriptions: $@";
                }
                if ( @capture ) {
                    # Choose
                    choose(
                        [ map { '  ' . decode( 'UTF-8', $_ ) } @capture ],
                        { layout => 3 }
                    );
                }
            }
            next MAIN_MENU;
        }
        my $groups = _groups();
        my $group = $groups->{$main_key};
        my $old_idx_group = 0;

        GROUP: while ( 1 ) {
            my $back_group     = '  <<';
            my $continue_group = '  CONTINUE';
            my @pre  = ( undef );
            my @choices_group = map( $_->[1], @$group );
            my $menu_group = [ @pre, @choices_group ];
            # Choose
            my $idx_group = choose(
                $menu_group,
                { prompt => $main_prompt, layout => 3, index => 1, clear_screen => 1, undef => $back_group, default => $old_idx_group }
            );
            if ( ! defined $idx_group || ! defined $menu_group->[$idx_group] ) {
                next MAIN_MENU;
            }
            if ( $old_idx_group == $idx_group ) {
                $old_idx_group = 0;
                next GROUP;
            }
            $old_idx_group = $idx_group;
            my $choice = $menu_group->[$idx_group];
            if ( $choice eq $continue_group ) {
                next MAIN_MENU;
            }
            my $key = $group->[$idx_group-@pre][0];
            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 = [
                    'none (unsafe)',
                    'video-height (unsafe)',
                    'fmt-id',
                    'fmt-id and video-height',
                    'fmt-string',
                ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "useragent" ) {
                my $prompt = 'Set the UserAgent';
                my $list = [
                    [ 'UserAgent', $opt->{useragent} ]
                ];
                _local_readline( $set, $opt, $key, $prompt, $list );
                $opt->{useragent} = '' if $opt->{useragent} eq '""';
            }
            elsif ( $key eq "quality" ) {
                my $prompt = 'Video resolution';
                my $list = [
                    'manually',
                    ' 180 or less',
                    ' 360 or less',
                    ' 720 or less',
                    '1080 or less',
                    '1440 or less',
                    '2160 or less',
                    'best'
                ];
                _opt_choose_from_list_value( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "no_height_ok" ) {
                my $prompt = 'Download videos with unkown height';
                my $list = [ 'no', 'yes' ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "retries" ) {
                my $digits = 2;
                my $info = sprintf "Download retries\nNow: %${digits}s", insert_sep( $opt->{$key} );
                my $name = 'New: ';
                _opt_number_range( $set, $opt, $key, $name, $info, $digits );
            }
            elsif ( $key eq "timeout" ) {
                my $digits = 3;
                my $info = sprintf "Connection timeout (s)\nNow: %${digits}s", insert_sep( $opt->{$key} );
                my $name = 'New: ';
                _opt_number_range( $set, $opt, $key, $name, $info, $digits );
            }
            elsif ( $key eq "no_warnings" ) {
                my $prompt = 'Disable youtube-dl warnings';
                my $list = [ 'no', 'yes' ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "prefer_free_formats" ) {
                my $prompt = 'Prefer free formats';
                my $list = [ 'no', 'yes' ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "max_size_history" ) {
                my $digits = 3;
                my $info = sprintf "Max size of history\nNow: %${digits}s", insert_sep( $opt->{$key} );
                my $name = 'New: ';
                _opt_number_range( $set, $opt, $key, $name, $info, $digits );
            }
            elsif ( $key eq "sort_history_by_timestamp" ) {
                my $prompt = 'Sort history by';
                my $list = [ 'by name', 'by timestamp' ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "entries_with_info" ) {
                my $digits = 4;
                my $sep_w = 1;
                my $w = $digits + int( ( $digits - 1 ) / 3 ) * $sep_w;
                my $info = sprintf "Number of entries with additional information\nNow: %${w}s", insert_sep( $opt->{$key} );
                my $name = 'New: ';
                _opt_number_range( $set, $opt, $key, $name, $info, $digits );
            }
            elsif ( $key eq "max_processes" ) {
                my $digits = 2;
                my $info = sprintf "Fetching additional information: the maximum number of parallel downloads\nNow: %${digits}s", insert_sep( $opt->{$key} );
                my $name = 'New: ';
                _opt_number_range( $set, $opt, $key, $name, $info, $digits );
            }
            elsif ( $key eq "list_sort_item" ) {
                set_sort_videolist( $set, $opt );
            }
            elsif ( $key eq "show_view_count" ) {
                my $prompt = 'Show the view-count in list-menus';
                my $list = [ 'if sorted by "view count"', 'always' ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "show_video_id" ) {
                my $prompt = 'Show the video-id in list-menus';
                my $list = [ 'no', 'yes' ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            elsif ( $key eq "max_info_width" ) {
                my $digits = 3;
                my $info = sprintf "Max Info width\nNow: %${digits}s", insert_sep( $opt->{$key} );
                my $name = 'New: ';
                _opt_number_range( $set, $opt, $key, $name, $info, $digits );
            }
            elsif ( $key eq "max_rows_description" ) {
                my $digits = 2;
                my $info = sprintf "Max Rows of description\nNow: %${digits}s", insert_sep( $opt->{$key} );
                my $name = 'New: ';
                _opt_number_range( $set, $opt, $key, $name, $info, $digits );
            }
            elsif ( $key eq "yt_dl_config_location" ) {
                my $info = "Youtube-dl Config File\nNow: ";
                if ( defined $opt->{$key} ) {
                    $info .= $opt->{$key};
                }
                else {
                    $info .= 'default (';
                    $info .= $^O eq 'MSWin32' ? "%APPDATA%/youtube-dl/config.txt" : "~/.config/youtube-dl/config";
                    $info .= ')';
                }
                my $name = 'New: ';
                _opt_choose_a_file( $set, $opt, $key, $info, $name );
            }
            elsif ( $key eq "_reset_yt_dl_config_location" ) {
                my $prompt = 'Reset the youtube-dl config location to default';
                my $list = [ 'no', 'yes' ];
                $opt->{_reset_yt_dl_config_location} = 0;
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
                if ( $opt->{_reset_yt_dl_config_location} ) {
                    $opt->{yt_dl_config_location} = undef;
                }
                delete $opt->{_reset_yt_dl_config_location};
            }
            elsif ( $key eq "yt_dl_ignore_config" ) {
                my $prompt = 'Ignore youtube-dl config file';
                my $list = [ 'no', 'yes' ];
                _opt_choose_from_list_idx( $set, $opt, $key, $prompt, $list );
            }
            else { die $key }
        }
    }
    return;
}


sub _opt_settings_menu {
    my ( $set, $opt, $sub_menu, $current, $prompt ) = @_;
    # Settings_menu
    my $changed = settings_menu( $sub_menu, $current, { prompt => $prompt } );
    return if ! $changed;
    for my $key ( keys %$current ) {
        $opt->{$key} = $current->{$key};
    }
    $set->{change}++;
}


sub _opt_choose_a_directory {
    my( $set, $opt, $key, $info, $name ) = @_;
    # Choose_a_directory
    my $new_dir = choose_a_directory( { init_dir => $opt->{$key}, cs_label => $name, info => $info } );
    return if ! defined $new_dir;
    if ( $new_dir ne $opt->{$key} ) {
        if ( ! eval {
            my $fh = File::Temp->new( TEMPLATE => 'XXXXXXXXXXXXXXX', UNLINK => 1, DIR => $new_dir ); ##
            1 }
        ) {
            print "$@";
            # Choose
            choose(
                [ 'Press Enter:' ],
                { prompt => '' }
            );
        }
        else {
            $opt->{$key} = $new_dir;
            $set->{change}++;
        }
    }
}


sub _opt_choose_a_file {
    my( $set, $opt, $key, $info, $name ) = @_;
    # Choose_a_file
    my $file = choose_a_file( { cs_label => $name, info => $info } );
    return if ! defined $file;
    $opt->{$key} = $file;
    $set->{change}++;
}


sub _local_readline {
    my ( $set, $opt, $key, $prompt, $list ) = @_;
    # Fill_form
    my $trs = Term::Form->new();
    my $new_list = $trs->fill_form(
        $list,
        { prompt => $prompt, auto_up => 2, confirm => 'CONFIRM', back => 'BACK   ' }
    );
    if ( $new_list ) {
        for my $i ( 0 .. $#$new_list ) {
            $opt->{$key} = $new_list->[$i][1];
        }
        $set->{change}++;
    }
}


sub _opt_number_range {
    my ( $set, $opt, $key, $name, $info, $digits ) = @_;
    # Choose_a_number
    my $choice = choose_a_number( $digits, { cs_label => $name, info => $info, small_first => 1 } );
    return if ! defined $choice;
    $opt->{$key} = $choice eq '--' ? undef : $choice;
    $set->{change}++;
    return;
}


sub _opt_choose_from_list_idx {
    my ( $set, $opt, $key, $prompt, $list ) = @_;
    my $backup = $opt->{$key};
    my $confirm = '  CONFIRM';
    my @pre = ( undef, $confirm );
    my $choices = [ @pre, map { "- $_" } @$list ];
    while ( 1 ) {
        my $local_prompt = $prompt . ': [' . ( $list->[$opt->{$key}] // '--' ) . ']';
        # Choose
        my $idx = choose(
            $choices,
            { prompt => $local_prompt, layout => 3, undef => '  BACK', index => 1 }
        );
        if ( ! defined $idx || $idx == 0 ) {
            $opt->{$key} = $backup;
            return;
        }
        elsif ( $choices->[$idx] eq $confirm ) {
            $set->{change}++;
            return;
        }
        else {
            $opt->{$key} = $idx - @pre;
        }
    }
}


sub _opt_choose_from_list_value {
    my ( $set, $opt, $key, $prompt, $list ) = @_;
    my $backup = $opt->{$key};
    my $confirm = '  CONFIRM';
    my @pre = ( undef, $confirm );
    while ( 1 ) {
        my $local_prompt = $prompt . ': [' . ( $opt->{$key} // '--' ) . ']';
        # Choose
        my $choice = choose(
            [ @pre,  map { "- $_" } @$list ],
            { prompt => $local_prompt, layout => 3, undef => '  BACK', index => 0 }
        );
        if ( ! defined $choice ) {
            $opt->{$key} = $backup;
            return;
        }
        elsif ( $choice eq $confirm ) {
            $set->{change}++;
            return;
        }
        else {
            $choice =~ s/^-\s//;
            $opt->{$key} = $choice;
        }
    }
}


sub _write_config_file {
    my ( $opt, $file ) = @_;
    my $tmp = {};
    for my $key ( sort keys %{ get_defaults() } ) {
        $tmp->{$key} = $opt->{$key};
    }
    write_json( $file, $tmp );
}


sub read_config_file {
    my ( $opt, $file ) = @_;
    my $tmp = read_json( $file ) // {};
    my @keys = keys %$tmp;
    for my $key ( @keys ) {
        $opt->{$key} = $tmp->{$key} if defined $tmp->{$key};
    }
    ################################################### "... or better" removed in 0.413 - Keep this for a while.
    if ( $opt->{quality} =~ /^\s*\d+\sor\sbetter\z/ ) {
        $opt->{quality} = 'manually';
    }
    ###################################################
}


1;


__END__



( run in 0.970 second using v1.01-cache-2.11-cpan-0b5f733616e )