Hi djk,
I think I can reproduce what you’re seeing - my shell is set up a bit differently which is probably why I didn’t notice it before. Try this new release, which also adds --capto to allow you to specify a path to capto, as well as --capto-args that lets you pass specific arguments to capto itself:
#!/usr/bin/perl -w
use strict; use warnings;
use Getopt::Long;
# use Data::Dumper;
my $VERSION = "1.2";
my $verbose = 0;
my $quiet = 0;
my $get = 0;
my $test = 0;
my $all = 0;
my $do_update = 0;
my $show_help = 0;
my $name = '';
my $range = '';
my $filename = '';
my @episode_list = ();
my $capto_args = "";
my $capto;
die(usage()) unless @ARGV;
GetOptions (
'quiet' => \$quiet,
'verbose' => \$verbose,
'range=s' => \$range,
'name=s' => \$name,
'get' => \$get,
'test' => \$test,
'all' => \$all,
'update' => \$do_update,
'help' => \$show_help,
'filename=s' => \$filename,
'capto=s' => \$capto,
'capto-args=s' => \$capto_args,
);
# Remove leading/trailing quotes if Getopt doesn't
$range =~ s/^['"]//g;
$range =~ s/['"]$//g;
$name =~ s/^['"]//g;
$name =~ s/['"]$//g;
$filename =~ s/^['"]//g;
$filename =~ s/['"]$//g;
$capto =~ s/^['"]//g;
$capto =~ s/['"]$//g;
$capto_args =~ s/^['"]//g;
$capto_args =~ s/['"]$//g;
if ($show_help) {
print show_help();
exit;
}
warn("wrapto: Starting up with verbose='$verbose', name='$name', range='$range', "
. "get='$get', test='$test', all='$all',\n") if $verbose;
$capto ||= "./capto";
my @filename_args = ("-f", "plex");
my @capto_args = ();
if ($capto_args) {
@capto_args = split(/\s+/, $capto_args);
}
my @search = ("-s");
my $success = 0;
my $fail = 0;
my $start = time();
my %all_list = ();
if ($get || $test) {
# Fetch a list of all files first
my @list = ();
@list = `$capto -s ALL`;
foreach my $line (@list) {
chomp $line;
if ($line =~ /\[capto\] \[(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d)\] \((\d+)\) (.*?) - s(\d\d)e(\d\d) - ?(.*)?/) {
my $id = $3;
my $item = { date => $1, time => $2, id => $3, showname => $4,
season => $5, episode => $6, epname => $7 };
if ($id && $item->{showname}) {
$item->{showname} =~ s/^\s*//g;
$item->{showname} =~ s/\s*$//g;
$item->{epname} =~ s/^\s*//g;
$item->{epname} =~ s/\s*$//g;
$all_list{$id} = $item;
}
else {
warn("Warning: skipping invalid entry for item '$id' ($line)\n");
}
}
}
if ($all && ! $name) {
warn("Retrieving all content...\n") if $verbose;
@episode_list = sort { $a <=> $b } keys %all_list;
# print Dumper %all_list;
}
elsif (($name && $range) || ($name && $all)) {
my ($start_season, $start_ep, $end_season, $end_ep);
if ($range) {
warn("Looking at episode range '$range' for program '$name'\n") if $verbose;
($start_season, $start_ep, $end_season, $end_ep) =
$range =~ /s(\d+)e(\d+)-s(\d+)e(\d+)/;
die("Invalid range '$range'\n") unless defined $start_season
&& defined $start_ep && defined $end_season && defined $end_ep;
$start_season *= 1;
$end_season *= 1;
$start_ep *= 1;
$end_ep *= 1;
warn("Looking for episodes of '$name' between s${start_season}e${start_ep} "
. "and s${end_season}e${end_ep}\n") if $verbose;
}
else {
warn("Looking for all episodes for program '$name'\n") if $verbose;
}
my @episodes = `$capto @search "$name"`;
warn("Found all possible episodes of '$name':\n@episodes\n") if $verbose;
foreach my $ep (sort @episodes) {
chomp $ep;
my ($id, $sno, $eno) = $ep =~ /\((\d+)\) $name - s(\d+)e(\d+)/;
if (defined $sno && defined $eno && (
($all)
||
# range case 1: when start season == end season
($start_season == $end_season && $sno == $start_season
&& $eno >= $start_ep && $eno <= $end_ep)
||
# range case 2: when episode is in start season
($start_season != $end_season && $sno == $start_season && $eno >= $start_ep)
||
# range case 3: when episode is in end season
($start_season != $end_season && $sno == $end_season && $eno <= $end_ep)
||
# range case 4: when episode is in middle-of-range season
($start_season != $end_season && $sno > $start_season && $sno < $end_season)
))
{
push(@episode_list, $id);
warn("Adding episode #$id ($ep)\n") if $verbose;
}
}
}
else {
while (my $id = shift @ARGV) {
# xxxx-yyyy range of IDs
if ($id =~ /(\d+)-(\d+)/) {
my $start = $1 * 1;
my $end = $2 * 1;
warn("Found ID range '$start' to '$end'\n") if $verbose;
if ($end > $start) {
while ($end >= $start) {
warn("Adding range ID '$start' to fetch list\n") if $verbose;
push(@episode_list, $start);
$start++;
}
}
elsif ($start > $end) {
while ($start >= $end) {
warn("Adding range ID '$end' to fetch list\n") if $verbose;
push(@episode_list, $end);
$end++;
}
}
else {
warn("Adding single ID '$start' to fetch list\n") if $verbose;
push(@episode_list, $start);
}
}
# Single ID
else {
warn("Adding episode id '$id' to fetch list\n") if $verbose;
push(@episode_list, $id);
}
}
}
if (@episode_list) {
foreach my $id (@episode_list) {
my @capto_cmd = ($capto, "-e", $id);
if ($filename) {
my $output_file = $filename;
#warn("id='$id', showname='$all_list{$id}->{showname}', epname='$all_list{$id}->{epname}'\n");
$output_file =~ s/\%D/\Q$all_list{$id}->{date} $all_list{$id}->{time}\E/g;
$output_file =~ s/\%d/\Q$all_list{$id}->{date}\E/g;
$output_file =~ s/\%i/\Q$all_list{$id}->{id}\E/g;
$output_file =~ s/\%s/\Q$all_list{$id}->{season}\E/g;
$output_file =~ s/\%e/\Q$all_list{$id}->{episode}\E/g;
$output_file =~ s/\%n/\Q$all_list{$id}->{showname}\E/g;
if ($filename =~ /\%t/) {
if ($all_list{$id}->{epname}) {
$output_file =~ s/\%t/\Q$all_list{$id}->{epname}\E/g;
}
else {
warn("Retrieving file with no episode name, but filename template calls for it. Using file ID instead.\n");
$output_file =~ s/\%t/$id/g;
}
}
$output_file =~ s@\\@@g;
$output_file =~ s@"@@g;
@filename_args = ("-f", '"' . $output_file . '"');
}
else {
@filename_args = ("-f", "plex");
}
push(@capto_cmd, @capto_args, @filename_args);
warn("Retrieving item '$id' via command '@capto_cmd' "
. "([" . join("],[", @capto_cmd) . "]" # comment out for production
. "\n") if $verbose;
if ($get && ! $test) {
my $rv = system(@capto_cmd);
if ($rv) {
warn("Failed to retrieve $id: $!") unless $quiet;
$fail++;
}
else {
warn("Retrieved '$id' OK\n") unless $quiet;
$success++;
}
}
}
}
}
print("Finished " . @episode_list
. " job" . (@episode_list == 1 ? "" : "s")
. " in " . (time-$start) . " sec: "
. $success . " file" . ($success == 1 ? "" : "s")
. " transferred OK, $fail "
. "file" . ($fail == 1 ? "" : "s")
. " failed.\n") unless $quiet;
exit;
sub usage {
return qq~wrapto version $VERSION\n\nUsage:
# Fetch a list of episode IDs found with a previous capto -i command:
wrapto [--verbose] [--get | --test] 555555 666666 777777
# Fetch a numerical range of episodes
wrapto [--verbose] [--get | --test] 555555-555566
# Fetch a range of episodes of a specific show name:
wrapto [--verbose] [--get | --test] --name "Program Name" --range "s01e01-s04e16"
# Fetch all recorded episodes of a specific show name:
wrapto [--verbose] [--get | --test] --name "Program Name" --all
# Fetch everything on the Tablo and save using a custom filename template
wrapto [--verbose] [--get | --test] --all --filename "\%n - S\%sE\%e - \%t.mp4"
# List all options:
wrapto --help
~; #
}
sub show_help {
my %options = (
"verbose" => "Toggle verbose messaging. Defaults to false.",
"get" => "Tell wrapto to fetch episodes matched with --name, --range "
. "and/or --all. Cannot be combined with --test. Defaults to false.",
"test" => "Tell wrapto to go through the motions of searching for episodes "
. "and preparing a download list, but don't actually download any files. "
. "Cannot be combined with --get. Defaults to false.",
"range" => "Specify the range of episodes to download in the format sAAeXX-sBBeYY. "
. "For example, to download all of season 1 might be s01e01-s01e22, and to "
. "download from the middle of season 2 through season 3 might be s02e13-s03e22. "
. "Used in conjunction with --name",
"name" => "The canonical name of the series to be recorded, as provided from the "
. "output of a `capto -s \"some series name\"` command. Used in conjunction with "
. "--range or --all",
"help" => "Show this help text and exit",
"update" => "Tell wrapto to do a capto -u to update the cache before running the query.",
"filename" => "Format filenames using template variables like:\n"
. "\%D - datetime (YYYY-MM-DD HH:MM)\n"
. "\%d - date (YYYY-MM-DD)\n"
. "\%i - Tablo file ID\n"
. "\%s - Season number (zero-padding intact)\n"
. "\%e - Episode number (zero-padding intact)\n"
. "\%n - Show name\n"
. "\%t - Episode title\n"
. "For example: --filename \"\%n - S\%sE\%e - \%t.mp4\" would produce output like '"
. "The Simpsons - S30E19 - Girl's in the Band.mp4'\n",
"all" => "Fetch all episodes of a given show, or all content on the Tablo. "
. "Used by itself or with something like --name",
"capto" => "If supplied wrapto will use this path to capto. Default is ./ "
. "(current directory). Note: don't enclose the path in quotes, for some reason.",
"capto-args" => "If supplied, this string will be passed to capto as arguments "
. "on the commandline. For example --capto_args \"-ffmpeg /path/to/special_ffmpeg\" "
. "would allow you to run capto with a specific ffmpeg version per its own "
. "internal capabilities.",
);
my $help_str = "";
my $maxlen = 0;
foreach my $opt(keys %options) { $maxlen = length($opt) if length($opt) > $maxlen; }
my $spacestr = " "x($maxlen+3);
my $maxtext = 80 - $maxlen;
foreach my $opt (sort keys %options) {
my $optlines = "";
my @lines = split(/\r?\n/, $options{$opt});
foreach my $line (@lines) {
my $currline = "";
my @words = split(/\s+/, $line);
foreach my $word (@words) {
if (length($currline) + length($word) > $maxtext) {
$optlines .= "\n" . $spacestr . $currline;
$currline = "";
}
$currline .= $word . " ";
}
$optlines .= "\n" . $spacestr . $currline;
}
$help_str .= "--$opt" . " "x(($maxlen - length($opt))+1) . $optlines . "\n";
}
return usage() . "\n" . '#'x80 . "\n# Options:\n" . '#'x80 . "\n\n" . $help_str . "\n";
}
As always, wrapto --help gives all of the available options:
Usage:
# Fetch a list of episode IDs found with a previous capto -i command:
wrapto [--verbose] [--get | --test] 555555 666666 777777
# Fetch a numerical range of episodes
wrapto [--verbose] [--get | --test] 555555-555566
# Fetch a range of episodes of a specific show name:
wrapto [--verbose] [--get | --test] --name "Program Name" --range "s01e01-s04e16"
# Fetch all recorded episodes of a specific show name:
wrapto [--verbose] [--get | --test] --name "Program Name" --all
# Fetch everything on the Tablo and save using a custom filename template
wrapto [--verbose] [--get | --test] --all --filename "%n - S%sE%e - %t.mp4"
# List all options:
wrapto --help
################################################################################
# Options:
################################################################################
--all
Fetch all episodes of a given show, or all content on the Tablo. Used
by itself or with something like --name
--capto
If supplied wrapto will use this path to capto. Default is ./ (current
directory). Note: don't enclose the path in quotes, for some reason.
--capto-args
If supplied, this string will be passed to capto as arguments on the
commandline. For example --capto_args "-ffmpeg
/path/to/special_ffmpeg" would allow you to run capto with a specific
ffmpeg version per its own internal capabilities.
--filename
Format filenames using template variables like:
%D - datetime (YYYY-MM-DD HH:MM)
%d - date (YYYY-MM-DD)
%i - Tablo file ID
%s - Season number (zero-padding intact)
%e - Episode number (zero-padding intact)
%n - Show name
%t - Episode title
For example: --filename "%n - S%sE%e - %t.mp4" would produce output
like 'The Simpsons - S30E19 - Girl's in the Band.mp4'
--get
Tell wrapto to fetch episodes matched with --name, --range and/or
--all. Cannot be combined with --test. Defaults to false.
--help
Show this help text and exit
--name
The canonical name of the series to be recorded, as provided from the
output of a `capto -s "some series name"` command. Used in conjunction
with --range or --all
--range
Specify the range of episodes to download in the format sAAeXX-sBBeYY.
For example, to download all of season 1 might be s01e01-s01e22, and
to download from the middle of season 2 through season 3 might be
s02e13-s03e22. Used in conjunction with --name
--test
Tell wrapto to go through the motions of searching for episodes and
preparing a download list, but don't actually download any files.
Cannot be combined with --get. Defaults to false.
--update
Tell wrapto to do a capto -u to update the cache before running the
query.
--verbose
Toggle verbose messaging. Defaults to false.
When I run it now in a totally stock bash shell, I get unescaped output file names, even when they contain strange characters:
usr@w550s:~/capto.v0.6.linux$ ./wrapto.pl --test --verbose --name "The Simpsons" --range "s30e18-s30e20" --filename "%n - s%se%e - %t.mp4" --capto ~/capto.v0.6.linux/capto --capto-args "-debug"
wrapto: Starting up with verbose='1', name='The Simpsons', range='s30e18-s30e20', get='0', test='1', all='0',
Looking at episode range 's30e18-s30e20' for program 'The Simpsons'
Looking for episodes of 'The Simpsons' between s30e18 and s30e20
Found all possible episodes of 'The Simpsons':
[capto] [2019-03-24 20:00] (1792741) The Simpsons - s30e18 - Bart vs. Itchy & Scratchy
[capto] [2019-03-31 20:00] (1800558) The Simpsons - s30e19 - Girl's in the Band
[capto] [2019-04-07 20:00] (1808877) The Simpsons - s30e20 - I'm Just a Girl Who Can't Say D'oh
Adding episode #1792741 ([capto] [2019-03-24 20:00] (1792741) The Simpsons - s30e18 - Bart vs. Itchy & Scratchy)
Adding episode #1800558 ([capto] [2019-03-31 20:00] (1800558) The Simpsons - s30e19 - Girl's in the Band)
Adding episode #1808877 ([capto] [2019-04-07 20:00] (1808877) The Simpsons - s30e20 - I'm Just a Girl Who Can't Say D'oh)
Retrieving item '1792741' via command '/home/bill/capto.v0.6.linux/capto -e 1792741 -debug -f "The Simpsons - s30e18 - Bart vs. Itchy & Scratchy.mp4"' ([/home/bill/capto.v0.6.linux/capto],[-e],[1792741],[-debug],[-f],["The Simpsons - s30e18 - Bart vs. Itchy & Scratchy.mp4"]
Retrieving item '1800558' via command '/home/bill/capto.v0.6.linux/capto -e 1800558 -debug -f "The Simpsons - s30e19 - Girl's in the Band.mp4"' ([/home/bill/capto.v0.6.linux/capto],[-e],[1800558],[-debug],[-f],["The Simpsons - s30e19 - Girl's in the Band.mp4"]
Retrieving item '1808877' via command '/home/bill/capto.v0.6.linux/capto -e 1808877 -debug -f "The Simpsons - s30e20 - I'm Just a Girl Who Can't Say D'oh.mp4"' ([/home/bill/capto.v0.6.linux/capto],[-e],[1808877],[-debug],[-f],["The Simpsons - s30e20 - I'm Just a Girl Who Can't Say D'oh.mp4"]
Finished 3 jobs in 0 sec: 0 files transferred OK, 0 files failed.
Let me know if that solves the problem for you!