#!/usr/bin/env perl
use warnings;
use strict;
use 5.010000;
use utf8;

our $VERSION = '0.203_01';

BEGIN { $SIG{__WARN__} = sub { die @_ } }

use Encode                qw( decode );
use File::Path            qw( make_path );
use File::Spec::Functions qw( catdir catfile curdir );
use Getopt::Long          qw( GetOptions );
use Pod::Usage            qw( pod2usage );

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::ReadLine::Simple qw();
use Try::Tiny              qw( try catch );

use if $^O eq 'MSWin32', 'Win32::Console::ANSI';
print "\e(U" if $^O eq 'MSWin32';

use App::YTDL::Arguments qw( from_arguments_to_choices );
use App::YTDL::Download  qw( download_youtube );
use App::YTDL::Helper    qw( encode_fs uni_capture );
use App::YTDL::History   qw( read_history_files channel_history_menu );
use App::YTDL::Info      qw( get_download_infos );
use App::YTDL::Merge     qw( search_and_merge );
use App::YTDL::Options   qw( read_config_file set_options );

binmode STDIN,  ':encoding(console_in)';
binmode STDOUT, ':encoding(console_out)';
binmode STDERR, ':encoding(console_out)';



my ( $arg_file, $help );
GetOptions( 'f|file=s@' => \$arg_file, 'h|?|help' => \$help )
or pod2usage( -message => $!, -verbose => 99, -sections => "SYNOPSIS" );


my $my_videos = decode 'locale_fs', File::HomeDir->my_videos || curdir;
my $config_home;
if ( which( 'xdg-user-dir' ) ) {
    $config_home = decode 'locale_fs', File::HomeDir::FreeDesktop->my_config();
}
else {
    $config_home = decode 'locale_fs', File::HomeDir->my_data();
}
my $config_dir = catdir $config_home, 'getvideo';
make_path encode_fs( $config_dir );


my $opt = {
    yt_api_v             => 2,
    youtube_dl           => which( 'youtube-dl' ) // 'youtube-dl',
    ffmpeg               => which( 'ffmpeg' )     // 'ffmpeg',
    ffprobe              => which( 'ffprobe' )    // 'ffprobe',

    config_dir           => $config_dir,
    config_file          => catfile( $config_dir, 'config.json' ),
    video_dir            => $my_videos,
    preferred_file       => catfile( $config_dir, 'preferred_fmt.json' ),
    log_file             => catfile( $config_dir, 'download_info.log' ),
    h_channel_file       => catfile( $config_dir, 'channel_history.json' ),
    s_channel_file       => catfile( $config_dir, 'channel_sticky.json' ),
    dir_stream_files     => 'STREAM_FILES',

    invalid_char         => quotemeta( '#$&+,/:;=?@' ),
    yt_regexp            => qr/(?:youtube|youtu\.be|yourepeat|tube\.majestyc)/i,

    linefold             => { Charset => 'utf-8', Newline => "\n", OutputCharset => '_UNICODE_', Urgent => 'FORCE' },
    kb_sec_len           => 5,
    max_info_width       => 120,
    right_margin         => $^O eq 'MSWin32' ? 1 : 2,

    useragent            => 'Mozilla/5.0',
    retries              => 5,
    timeout              => 60,
    overwrite            => 0,

    max_len_f_name       => 62,
    replace_spaces       => 1,
    sanitize_filename    => 1,
    text_unidecode       => 0,
    modify_timestamp     => 1,

    merge_enabled        => 0,
    merge_ext            => 'mkv',
    merge_overwrite      => 1,
    merge_loglevel       => 'warning',
    merged_in_files      => 1,

    auto_quality         => 'keep_channel_playlist',
    preferred            => [ 43 ],
    pref_qual_slots      => 3,

    extractor_dir        => 0,
    channel_dir          => 1,

    log_info             => 0,
    max_channels         => 15,
    channel_by_timestamp => 1,

    list_size_idx        => 0,
    list_sort_item       => 'upload_date',
    list_sort_order      => 'Desc',
    show_view_count      => 0,
    fast_list_youtube    => 1,
    fast_list_vimeo      => 1,

    use_netrc            => 0,

    error_get_download_infos => [],
    incomplete_download      => [],
    error_merge              => [],
};

read_config_file( $opt, $opt->{config_file} );
set_options( $opt ) if $help;


if ( ! -d $opt->{video_dir} ) {
    say "Could not find the video directory '$opt->{video_dir}'!";
    exit 1;
}
try {
    my @cmd = ( $opt->{youtube_dl}, '--version' );
    my $capture = uni_capture( @cmd );
}
catch {
    say "Could not find 'youtube-dl' - 'youtube-dl' is required - http://rg3.github.io/youtube-dl/.";
    exit 1;
};


local $| = 1;
print locate( 1, 1 ), cldown;



read_history_files( $opt );
my @ids = _gather_arguments( $opt, $arg_file, @ARGV );
my $info = from_arguments_to_choices( $opt, @ids );
get_download_infos( $opt, $info );
download_youtube( $opt, $info );



if ( $opt->{merge_enabled} ) {
    my $merge_ok = 1;
    if ( $opt->{merge_enabled} == 1 ) {
        print "\n";
        $merge_ok = choose(
            [ '  NO', '  YES' ],
            { prompt => 'Enable Merge', layout => 3, index => 1 }
        );
        up( 1 );
    }
    if ( $merge_ok ) {
        print "\n";
        search_and_merge( $opt, $info );
    }
}



my $error;
if ( @{$opt->{error_get_download_infos}} ) {
    print "\n";
    say "Error fetching download infos:";
    for my $video_id ( @{$opt->{error_get_download_infos}} ) {
        say '  ' . $video_id;
    }
    $error++;
}
if ( @{$opt->{incomplete_download}} ) {
    print "\n";
    say "Incomplete download:";
    for my $video_id ( @{$opt->{incomplete_download}} ) {
        say '  ' . $video_id;
    }
    $error++;
}
if ( @{$opt->{error_merge}} ) {
    print "\n";
    say "Merge error:";
    for my $video_id ( @{$opt->{error_merge}} ) {
        say '  ' . $video_id;
    }
    $error++;
}
print "\n" if $error;


sub _gather_arguments {
    my ( $opt, $arg_file, @ids ) = @_;
    for my $file ( @$arg_file ) {
        open my $fh, '<:encoding(utf-8)', encode_fs( $file ) or die $!;
        while ( my $line = <$fh> ) {
            next if $line =~ /^\s*\z/;
            next if $line =~ /^\s*#/;
            $line =~ s/^\s+|\s+\z//g;
            push @ids, split /\s+/, $line;
        }
        close $fh or die $!;
    }
    if ( ! @ids ) {
        my $trs = Term::ReadLine::Simple->new();
        my $ids = $trs->readline( 'Enter url/id: ' );
        @ids = split /\s+/, $ids;
        print up( 1 ), cldown;
    }
    if ( ! @ids && ( %{$opt->{channel_history}} || %{$opt->{channel_sticky}} ) ) {
        @ids = channel_history_menu( $opt );
    }
    say "No arguments" and exit if ! @ids;
    return @ids;
}


__END__

=pod

=encoding UTF-8

=head1 NAME

getvideo - Download YouTube and other videos.

=head1 VERSION

Version 0.203_01

=cut

=head1 SYNOPSIS

    getvideo -h|-?|--help

    getvideo

    getvideo url [url ...]

    getvideo -f|--file filename

The urls can be entered after calling C<getvideo> - this is useful if urls contain shell metacharacters like C<&>.

The urls can also be passed with a file: C<getvideo -f|--file filename>. The urls in the file have to be space
separated.

=head1 BACKWARD INCOMPATIBLE CHANGES

This changes were introduced with the version C<0.200>.

The name of the extractor directories may have changed (probably the first letter has changed from uppercase to
lowercase).

The location of the configuration directory may have changed.

The names of the configuration files have changed.

The option L</File timestamp> is now enabled by default.

The option I<Extractor directory> is disabled by default.

=head1 DESCRIPTION

Download single videos or/and choose videos from playlists or/and channels.

When choosing from a channel or a playlist it is possible to filter the displayed video titles with a regexp. The filter
can be inverted by adding C<!~> and a space in front of the regexp.

See L<Term::Choose/USAGE-AND-RETURN-VALUES> how to select more items with the C<SpaceBar> or C<Ctrl>-C<SpaceBar>.

Before the download the script shows some video info and lets you choose the video quality from the available qualities.
It is possible to choose more than one video format with the C<SpaceBar> key.

Instead of choosing the quality manually it is possible to set and use preferred qualities.

To set the different options call C<getvideo -h>.

C<App::YTDL> uses L<youtube-dl|http://rg3.github.io/youtube-dl/> to get the data required for the video download. To
list the supported extractors call C<getvideo -h> and select the entry I<Extractors>.

=head3 Auto merge

Downloaded files from the same video-id are tried to merge if

- the option I<Merge> is enabled. See L</Merge>.

- C<ffmpeg> and C<ffprobe> are available.

- not more and not less than 2 files from the same video-id and the same extractor have been downloaded.

- one of these two files has only an audio stream.

- one of these two files has a video stream.

=head3 Legacy encodings

Non mappable characters in file names are replaced with C<&#xNNN;> where NNN is the Unicode code point in a decimal
number.

=head1 Options

=head2 HELP

Shows this HELP text.

=head2 INFO

Shows the path and the version of the running C<getvideo>, the path of the video and configuration directories and the
version of C<youtube-dl>. If C<ffmpeg> and C<ffprobe> are available, their version is also shown.

=head2 Directory

=head3 Video directory

Choose an alternative main video directory.

=head3 Extractor directory

=over

=item

no

Don't create/use extractor directories.

=item

yes

Create/use extractor directories.

=back

=head3 Channel directory

=over

=item

no

Don't create/use channel directories.

=item

if from channel or playlist

Create/use channel directories if the videos are chosen from a channel or a playlist.

=item

always

Always create/use channel directories.

=back

=head2 File

=head3 Max filename length

Set the maximum length of the filename. Filenames longer as the maximum length are truncated.

=head3 Replace spaces

If enabled, spaces in filenames are replaced with underscores.

=head3 Sanitize filename

=over

=item

replace /

Keep all apart from C</> and C<\0> .

=item

replace / \ : " * ? < > |

Replace each character matching the character class C<[\x{00}-\x{1F}"\/\\:*?E<lt>E<gt>|]> with a hyphen.

=back

=head3 File timestamp

Change the timestamps of the downloaded videos to the their upload date.

=head2 Quality

=head3 Auto quality mode

Set the I<auto quality mode>:

=over

=item

manually

Choose the video quality always manually

=item

keep_channel_playlist

Keep the first quality chosen for a video of a playlist/channel for all videos of that playlist/channel if
possible.

=item

keep_extractor

Keep the first quality chosen for a video of an extractor for all videos of that extractor if possible.

=item

preferred

Use preferred qualities.

=item

default

Use the default (best) quality.

=back

=head3 Slots 'preferred qualities'

Set the number of the available I<preferred qualities> slots.

If the preferred quality/qualities set in a slot (beginning at the first slot) is/are not available, the
quality/qualities from the next set slot is/are used.

=head3 Preferred qualities

Prints the set preferred qualities.

How to set the I<preferred qualities>:

Set the option I<auto quality mode> to C<manually>, C<keep_channel_playlist> or C<keep_extractor>. Then, when
downloading a video, instead of selecting the desired video quality select the entry C<Menu> and then the entry
C<Preferred qualities>.

=head2 Download

=head3 UserAgent

Set the useragent string.

If entered nothing, the default useragent string (Mozilla/5.0) is used.

=head3 Overwrite

If enabled, C<getvideo> overwrites existing files else getvideo appends to partially downloaded files.

=head3 Download retries

Set the number of download retries.

=head3 Timeout

Connection timeout in seconds.

=head2 Merge

=head3 Enable Merge

=over

=item

no

=item

ask

=item

yes

=back

=head3 ffmpeg overwrites

If set to 'yes', C<ffmpeg> overwrites an existing file when merging two files into one file else
the user will be asked what to do.

=head3 Output formats

Choose the container format for the new file: C<mkv>, C<mp4>, C<ogg>, C<webm> or C<flv>.

=head3 ffmpeg verbosity

=over

=item

error

Show all errors.

=item

warning

Show all warnings and errors.

=item

info

Show informative messages during processing. This is in addition to warnings and errors.

=back

=head3 Input files

=over

=item

keep

Keep the merged input files.

=item

move to "STREAM_FILES"

Move the merged input files to the directory "STREAM_FILES". The directory "STREAM_FILES" is created (if it not already
exits) in the directory where the input files were downloaded.

=item

remove

Remove the merged input files.

=back

=head2 History

=head3 Logging

Enable info logging to a log file.

=head3 Channel history

If no arguments are passed to C<getvideo>, the user can choose from the channels saved in the channel-history and the
channel-sticky file.

I<Channel history> sets the limit of the number of channels saved in the channel history file. Setting
I<Channel history> to C<0> disables the channel history.

A channel can be made sticky. Channels made sticky don't count regarding the I<Channel history> limit. If a channel
is made sticky, it gets also a new timestamp.

When added to the channel history channels get the return value of C<time()> as their timestamp. If the I<Channel history>
limit is reached, the channel with the oldest timestamp is removed first.

The supported extractors for the I<Channel history> are YouTube and Vimeo.

=head3 History sort

Sort the channels from the history file

=over

=item

by name

=item

by timestamp

=back

=head2 Video List

=head3 List size

This option has only meaning if the extractor is Youtube or Vimeo. In addition it is required that the option
I<fast list-menu> is enabled.

=over

=item

all

Show (fetch info for) all videos of the channel/playlist.

=item

latest 50

If the list has more than 50 videos, show (fetch info for) only the latest 50 videos of the list.

If the extractor is Vimeo, it is 48 instead of 50.

=back

=head3 Sort order

Sort the list-menu entries by

=over

=item

upload date

If there is no upload date data for the list, the title is used instead to the sort the list.

=item

title

=item

view count

If there is no view count data for the list, the upload date is used instead to the sort the list.

=item

duration

If there is no duration data for the list, the upload date is used instead to the sort the list.

=back

=head3 Show view count

=over

=item

if sorted by view count

Show the view count in the list-menu entries only when sorted by view count.

=item

always

Show the view count always.

=back

Some extractors don't provide a view count.

=head3 Fast list-menu

If I<fast list-menu> is enabled, the download of the required data for the list-menu takes less time since the data
required for the video download is fetch only later (before the video download for the chosen videos).

With I<fast list-menu> enabled a progress info is shown.

The option I<list size> is only available if I<fast list-menu> is enabled.

If the extractor is "vimeo" and I<fast list-menu> is enabled, no video-duration is available in the list-menu.

Supported extractors: youtube and vimeo.

=head2 Output

=head3 Unmappable characters

=over

=item

replace with *

If I<replace with *> is selected, unmappable characters are replaced with C<*> before the output on the screen.

=item

use Text::Unidecode

C<unidecode()> from L<Text::Unidecode> tries to "translate" non ASCII data to ASCII characters. The output of
C<unidecode()> always consists entirely of US-ASCII characters.

=back

=head3 Max info width

Set the maximum width of the video info output.

=head3 Digits for "k/s"

Set the number of digits allocated for the "kilobyte per seconds" template.

=head2 Extractors

List the extractors supported by C<youtube-dl>.

=head1 REQUIREMENTS

=head2 Perl version

Requires Perl version 5.10.0 or greater.

=head2 youtube-dl

A recent version of L<youtube-dl|http://rg3.github.io/youtube-dl/> is required.

=head2 ffmpeg and ffprobe

The I<merge> feature requires C<ffmpeg> and C<ffprobe>.

=head2 Monospaced font

It is required a terminal that uses a monospaced font which supports the printed characters.

=head1 CREDITS

C<App::YTDL> uses L<youtube-dl|http://rg3.github.io/youtube-dl/> to get the data required for the video download.

Thanks to the L<Perl-Community.de|http://www.perl-community.de> and the people form
L<stackoverflow|http://stackoverflow.com> for the help.

=head1 AUTHOR

Kuerbis <cuer2s@gmail.com>

=head1 LICENSE AND COPYRIGHT

Copyright (C) 2013-2015 Kuerbis.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl 5.10.0. For
details, see the full text of the licenses in the file LICENSE.

=cut
