Wrapto: Wrapper for Capto for Tablo

I wrote a small wrapper script that invokes capto (Capto for Tablo (CLI Grabber) - #30 by tgwaste) to retrieve content from a connected Tablo device. wrapto adds some useful functionality, like being able to download a range of episodes (even across multiple seasons), specify a file naming format, download all episodes of a given show, or even download all content from the Tablo at once.

wrapto version 1.1

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

Here’s the source code. As wrapto is written in Perl, you’ll need to drop it into the same directory as your capto installation, save the file and mark it as executable. It doesn’t require any Perl modules that don’t already come with the default installation. Generally speaking, if capto works for you, then wrapto should too!

#!/usr/bin/perl -w
use strict; use warnings;
use Getopt::Long;
use Data::Dumper;

my $VERSION      = "1.1";

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 = ();

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,
);

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;

my $capto    = "./capto";
my @default_options  = ("-f", "plex");
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 = `$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;
				$output_file =~ s/\%t/\Q$all_list{$id}->{epname}\E/g;
				push(@capto_cmd, "-f", $output_file);
			}
			else { push(@capto_cmd, @default_options); }
			warn("Retrieving item '$id' via command '@capto_cmd'\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.\n"
			. "Cannot be combined with --get. Defaults to false.",
		"range" => "Specify the range of episodes to download in the format sAAeXX-sBBeYY.\n"
			. "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.\n"
			. "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.\nUsed 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.\n"
			. "Used by itself or with --name",
	);
	
	my $help_str = "";
	my $maxlen = 0;
	foreach my $opt(keys %options) { $maxlen = length($opt) if length($opt) > $maxlen; }
	my $spacestr = " "x($maxlen+3) . "| ";
	foreach my $opt (sort keys %options) {
		$options{$opt} =~ s/\r?\n$//;
		$options{$opt} =~ s/\r?\n/\n$spacestr/g;
		$help_str .= "--$opt" . " "x(($maxlen - length($opt))+1) . "| " . $options{$opt} . "\n";
	}
	return usage() . "\n" . '#'x80 . "\n# Options:\n" . '#'x80 . "\n\n" . $help_str . "\n";

}
1 Like

Me, I like the file names to end at the S00e00 like “Show name - s01e02” I don’t need or like the title and have to cut them off. It’s mostly just tedious… just that you asked :neutral_face:

That is I do a -s ALL > file.list with output so the file names are wrapped in " " and the lines start with the script I use to encode the shows.

Sure, that’s easy enough. Below is version 1.1, which a) adds fetching a range of IDs (e.g. 8876000-8876020), b) adds fetching all recordings for a given show name using --name and --all, c) adds fetching all recorded content, period, by using just the --all, and d) lets you specify a template for the resulting filename. Note I’ve tested this on a few programs that have a show name but no episode name (live specials, etc.), but I don’t have any sports recordings (deleted my last few NCAA tournament files in a fit of rage) so there may be bugs in some cases. Here’s the help text:

wrapto version 1.1

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

Here’s the updated source:

#!/usr/bin/perl -w
use strict; use warnings;
use Getopt::Long;
use Data::Dumper;

my $VERSION      = "1.1";

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 = ();

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,
);

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;

my $capto    = "./capto";
my @default_options  = ("-f", "plex");
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 = `$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;
				$output_file =~ s/\%t/\Q$all_list{$id}->{epname}\E/g;
				push(@capto_cmd, "-f", $output_file);
			}
			else { push(@capto_cmd, @default_options); }
			warn("Retrieving item '$id' via command '@capto_cmd'\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.\n"
			. "Cannot be combined with --get. Defaults to false.",
		"range" => "Specify the range of episodes to download in the format sAAeXX-sBBeYY.\n"
			. "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.\n"
			. "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.\nUsed 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.\n"
			. "Used by itself or with --name",
	);
	
	my $help_str = "";
	my $maxlen = 0;
	foreach my $opt(keys %options) { $maxlen = length($opt) if length($opt) > $maxlen; }
	my $spacestr = " "x($maxlen+3) . "| ";
	foreach my $opt (sort keys %options) {
		$options{$opt} =~ s/\r?\n$//;
		$options{$opt} =~ s/\r?\n/\n$spacestr/g;
		$help_str .= "--$opt" . " "x(($maxlen - length($opt))+1) . "| " . $options{$opt} . "\n";
	}
	return usage() . "\n" . '#'x80 . "\n# Options:\n" . '#'x80 . "\n\n" . $help_str . "\n";

}
1 Like

The filename template is awesome!! I think I can make that work… how I want things to, with a bit less hassle! That was part of my bottleneck.

I don’t think I’ll get to seriously try it out tonight :frowning:

Thanks!:confetti_ball:

Virtually all my recordings are ‘tv shows’. I know movies and sports are stored/retreived differently, but probably not reverent here.

The filename template is awesome!! I think I can make that work… how I want things to, with a bit less hassle! That was part of my bottleneck.

Thanks, I hope it works for you. I use this script to pull files down, pass to MCEBuddy/Comskip and ultimately send over to Plex, but as of tonight I’ve switched over to using the name template function instead of capto’s built-in plex file naming to be able to preserve some extra info in the filename, so your suggestion has helped me out too :slight_smile:

Good luck. Let me know if you run into any issues.

I truly appreciate all the work you’ve put into wrapto! some notes…

I have capto in my path since it’s hard coded my $capto = "./capto"; to work specifically from current directory… you say to put it in the same as capto, not necessarily you have to work from there, suggest to verify, create link or edit wrapto.

Warning to others, I just copied and pasted your examples from above for trial (removing the [ | ] ) Probably due to the forum formatting, seems some of the – turned into one big dash and the goofy opening quote, you know, isn’t the same thing.

Finally got a command formatted to work…
1 wrapto --verbose --test --all --filename "%n - s%se%e.mp4"
2 wrapto --test --all --filename "%n - s%se%e.mp4"

1 seems to go through 2 loops starting with:

wrapto: Starting up with verbose='1', name='', range='', get='1', test='1', all='1',
Retrieving all content...

$VAR1 = ‘67830’;
$VAR2 = {
‘date’ => ‘2019-04-02’,
‘season’ => ‘06’,
‘showname’ => ‘Criminal Minds’,
‘epname’ => ‘The Longest Night’,
‘time’ => ‘20:00’,
‘id’ => ‘67830’,
‘episode’ => ‘01’
};

through each record. Then looping through lines appearing to be capto’s output

Retrieving item '48871' via command 'capto -e 48871 -f Rizzoli\ \&\ Isles - s06e10.mp4'

Subsequent runs I’ve notices the first loop is in a different random order, the second iteration is sequential.

The second command, without --verbose just went through the first loop, always in a different order.

Not sure if something got missed while coping the text. What am I missing? My perl,
~$ perl --version

This is perl 5, version 28, subversion 1 (v5.28.1) built for x86_64-linux-gnu-thread-multi
(with 61 registered patches, see perl -V for more detail)

Pardon my ignorance; but where exactly does this Perl script run from? Do you load it into the Tablo somehow, or does it run on your computer, or what?

Hi there,

First, thanks for the note about the double dash. I’ll try to make sure that future release notes show up properly on the board.

Second, with regard to the output of the first list that you see, I’ve been using a perl module to display an internal data structure for debugging. I think by default it doesn’t sort the output, which is why it appeared differently during each of your runs. I’m going to take it out in the next release since it doesn’t really serve a purpose anymore. Regardless, though, that second and final list should always come out in sorted order, which as you suggest it does.

Just so I’m clear, have you gotten it to work the way you wanted yet (the issue of not having it in the same directory as capto itself aside), and have you had any trouble getting it to actually fetch the files from your Tablo?

1 Like

Hi ACC,

This wrapper, called wrapto, works alongside another utility, called capto, to retrieve files from your Tablo DVR. You’d need to download both capto and wrapto to a Mac or Linux machine on the same LAN as your Tablo and execute the commands from there. People use utilities like these to batch-offload files from the DVR onto a home server for further processing (e.g. for commercial removal), or just to move them onto another storage medium so the Tablo has more space to record.

Ok. I am a Mac guy; but I have never touched Perl (LOL). That will work GREAT for me.

I assume I can just copy this Script into a Text Editor, then save it as [something].pl, right?

Where can I find Capto for Mac? Is it available as a MacOS Application.

Are you talking about THIS?

https://www.globaldelight.com/captoformac/

Sorry for the stupidity…

BTW, That’s a GREAT utility! I’m a moderate data-hoarder; so that will be perfect, once I get my Tablo Quad… :wink:

Capto can be downloaded from http://www.twg.org/capto/

The original forum thread about it is here: Capto for Tablo (CLI Grabber)

Capto can be used without wrapto. Wrapto just adds more functionality. You shouldn’t need to know any perl to use the utility. Just copy the source blurb into a text editor, save, and change the executable bit so you can run it like a program - from a terminal just do chmod 755 /wherever/you/put/wrapto

Thanks!

Since I don’t have a Tablo (or Capto) yet, is there any way I can “bookmark” your post with the pl script so I can find it again? :wink:

There should be a bookmark button on the bottom of this forum page. Just click that and it will be saved under your profile.

The order was benign, I just wondered what the code was

As for the directory path, I just deleted ./ from line 43 - non issue. The way I wanted things to work :stuck_out_tongue_winking_eye::roll_eyes: of course it’s never that easy…
I tried using wrapto and capto “out-of-the-box” to see what I can get. Seems to get close except spaces get escaped with backspaces when it passes %n to capto

Retrieving item '77925' via command 'capto -e 77925 -f The\ Orville - s02e12.mp4'

Here, in the debugging thing, showname is single quoted

$VAR35 = '77925';
$VAR36 = {
           'date' => '2019-04-11',
           'episode' => '12',
           'time' => '21:01',
           'showname' => 'The Orville',
           'id' => '77925',
           'epname' => 'Sanctuary',
           'season' => '02'
         };

and this command gives me a file with a \ in it

$ wrapto --verbose --get --filename "%n - s%se%e.mp4" 77925
$ stat The*
File: The\ Orville - s02e12.mp4

I would suspect that may come from the shell… but I don’t think it comes into play directly. Other than these TV shows, I generally avoid spaces in filenames, but never had issues with them otherwise. I get why the apostate and ampersand are escaped, and to some degree spaces, but why even periods. I recall @tgwaste did a bunch to get rid of illicit characters in filenames for capto.

Ok… trying to work out what I can do with wrapto, help with what’s wrong with this line:

$ wrapto --verbose --get --name "The Orville" --filename "%n - s%se%e.mp4"
wrapto: Starting up with verbose='1', name='The Orville', range='', get='1', test='0', all='0',
Finished 0 jobs in 0 sec: 0 files transferred OK, 0 files failed.

Just to confirm: as noted: --name | The canonical name of the series to be recorded, as provided from the output of a capto -s "some series name" command. (I think line 208 referencing capto -i may be a typo)

$ capto -s "The Orville"
[capto] [2019-04-11 21:01] (77925) The Orville - s02e12 - Sanctuary

So that name exsist and yet I get nothing that way :man_shrugging:
But please to understand, if wrapto works for you and others that’s awesome!! Don’t beat yourself up over this up if it’s not an issue moving froward collectively. My backstory isn’t this straight forward and I’ve gone on enough for now. :tired_face::sleeping:

You should quote file names with spaces.

Retrieving item ‘77925’ via command ‘capto -e 77925 -f “The Orville - s02e12.mp4”’

or if within quotes:

“This is a \“File Name.mp4\””

Absolutely! This is the output I get using wrapto’s variable for showname %n

When I use the --test command to list all the content with the --filename template the list has all the spaces escaped wrapto --verbose --test --all --filename "%n - s%se%e.mp4"

So, not really have much more than barely a clue I’m trying to see if I can get wrapto to quote the output. Fiddling with line 175 $output_file, building the @capto_cmd. Maybe it’s not even the right place, failed.

Then I realized wrapto should accept everything including " for --filename, but since they are necessary for the command… well I added them --filename "\"%n - s%se%e.mp4\"" escaping them with backslashes. Can you believe I got output with quoted filename - still with backslashes!

Retrieving item '77925' via command 'capto -e 77925 -f "The\ Orville - s02e12.mp4"'

So I’m SOL that’s just messed up… and then some.

ok, so being as stoopid as I am, I was smart enough not to edit my primary wrapto in my path… but not smart enough to specify ~/ when I trialed the edits from the test file… which explains why I never generated any errors! So who knows, maybe I had stumbled on something, but wasted my time and won’t know about it.
I did try
push(@capto_cmd, "-f", "\"", $output_file, "\""); not ideal, of cours it’s not the answer I get spaces, and still backslashes!? (but then again, I dont know what I’m doing)
'capto -e 77925 -f " The\ Orville - s02e12.mp4 "'

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!

time constraints… Broke the major hurdle,

$ wrapto --verbose --get --filename "%n - s%se%e.mp4" 77925

yields a filename as expected (as far as I’m concerned)
I did get some messages

Use of uninitialized value $capto in substitution (s///) at /usr/local/sbin/wrapto line 46.
Use of uninitialized value $capto in substitution (s///) at /usr/local/sbin/wrapto line 47.

but again I deleted the ./ at line 59 since capto and wrapto are both in /usr/local/sbin

If I use wrapto --test --all without verbose should I see nothing? (never did before well, Finished message) note: capto -i = “get tablo info”

Beyond recognizing a perl regex when I see it, and the most basic rudimentary, match this, function… I always knew it was something to understand. Thanks for everything you and tgwast (and other 3rd party apps) put into this!

oh yeah, time constraints…