Wrapto: Wrapper for Capto for Tablo

Here is wrapto v1.3, which offers a few small changes and bugfixes:

  • Per djk44883’s comment, without --verbose wrapto provided no output, and only displayed capto output. This version modifies the behavior slightly to provide minimal but useful output without --verbose
  • Removed too-verbose Data::Dumper output when --verbose is passed
  • When passing numeric file IDs, check each one to make sure it actually exists before attempting to fetch
  • Removed logspam from attempting to use uninitialized values
  • Fixed help message formatting

The source is below. The forum is giving me a hard time about formatting (preformatted isn’t really preformatted, apparently), so here’s a link as well: wrapto 1.3

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

my $VERSION      = "1.3";

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
if (defined $range) {
	$range      =~ s/^['"]//g;
	$range      =~ s/['"]$//g;
}
if (defined $name) {
	$name       =~ s/^['"]//g;
	$name       =~ s/['"]$//g;
}
if (defined $filename) {
	$filename   =~ s/^['"]//g;
	$filename   =~ s/['"]$//g;
}
if (defined $capto) {
	$capto      =~ s/^['"]//g;
	$capto      =~ s/['"]$//g;
}
if (defined $capto_args) {
	$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 || "unknown_date"), time => ($2 || "unknown_time"),
				id => ($3 || "unknown_id"), showname => ($4 || "unknown_showname"), 
				season => ($5 || "unknown_season"), episode => ($6 || "unknown_ep"),
				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");
			}
			# warn Dumper $item;
		}
	}
	
	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) {
			unless (defined $all_list{$id}) {
				print("Item '$id' not found in list. Skipping.\n");
				next;
			}

			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);
			print("Retrieving item '$id' via command '@capto_cmd'\n");
			warn("([" . 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/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";
}

Looks like you’ve got something great! I like --capto-args to pass on the delete when done option.

It’s not really not easy for me. Before tablo I used my pc with tv cards to ‘dvr’ OTA programs, and had control over everything. The software became outdated. With streaming services, DVB wasn’t getting much support.

I wasn’t real happy with the way tablo worked, but it did what I needed and the only problem was… well it didn’t work the way I wanted it to.

Then I discovered this create_mp4_from_tablo_directory.sh and had a way to get shows off the device, not just to off load them but to use a decent media player. (and watching network traffic in the browser)

capto cleaned things up giving me an easy way to get names to match the id’s. So I just -s ALL > text.file and quick edits and sent it to my script. Yea, it was a bit tedious, but I only have a handful of shows here and there. But it just bothered me having the long filenames with the meaningless episode titles. OCD is a battle.

I just ran wrapto --capto-args "-a" --filename "%n - s%se%e.mp4" --name "Criminal Minds" --all --get and gave me exactly what I need to the universe from imploding.

  • THANKS GREATLY!

Just keeps getting better - thanks! A small batch of shows may not be sequential id’s. Perhaps crude, I have a quick and dirty shell scrip to loop via for in through some random id’s passed to it.

#!/bin/bash
set -x
for id in $1
do
	wrapto --capto-args "-a" --filename "%n - s%se%e.mp4" --get $id
done

Your need to wrap the id’s in quotes to pass it to a single variable $1.
~$ wraptoscript "1234 5678 9012 3456"
and, of course, even it capto has a quoted filename passed to it with an ’ in it… it’s logic removes it.
I’m sure it was never in doubt, of course you can specify a complete path for --filename (because I can’t use %n for the directory… spaces, The or extraneous characters in show names will cause problems I can’t explain.

Thanks

@wbill3d and @tgwaste for all the over-the-top work

1 Like

Example of sorting and no need to quote the numbers:

#!/bin/bash

for id in `(for i in $@; do echo $i; done) | sort -n`
do
        echo $id
done

./t.sh 9 2761 23 4 1
1
4
9
23
2761
1 Like

Out of curiosity why not just use --name and --range together and skip the tablo file IDs altogether, like wrapto --name "The Showname" --range "s02e03-s02e05" ?

wrapto won’t care whether the file IDs are sequential or not, it’s matching the text in the string that capto sends back with the season and episode info in it.

:boom: COOL :boom:

they don’t really need sorted, I mentioned sequential because wrapto can get a range of id’s [10101-10115] (or season/episode) - I don’t always have.
Now, this is just makes it that much less hassle. :smiley:

They are different show names. I have 4 different show I want. Different seasons from each other, who-knows what episode and no relational order to id’s At least I don’t want shows sequential between them to specify a range, that’s why I mentioned show “sequential”.

# Fetch a list of episode IDs found with a previous capto -i command:
  wrapto [--verbose] [--get | --test] 555555 666666 777777

This help line suggest wrapto can get multiple single episodes, although capto -i still gives me tablo info.
but with the script, I have the options --capto-args "-a" --filename "/path/%n - s%se%e.mp4" in place, so I just pass the id’s to it. For specific shows, I use --name, this just for a handful of “random” shows.

sorry for the misunderstanding.

Ah, I see.

Yes, if there’s more than one show involved you’d need to pass individual file IDs like you’re doing now, or else issue multiple wrapto --name "The Showname" --all commands to fetch everything for the given show name (assuming that’s what you want).

1 Like

In the event someday somebody comes back through these, and my little script means something… To just specify some id’s on the command line unquoted use $@ instead of $1

#!/bin/bash
# getshow
for id in $@
 do wrapto --capto-args "-a" --filename "%n - s%se%e.mp4" --get $id
done

~ $ getshow 1234 2345 3456 4567

Hi there,
I guess I’m still a little fuzzy on why that script is necessary. I just did a quick capto -s ALL to my dvr and then picked three random show IDs from three different shows, and ran this wrapto command:

wrapto --capto-args "-a" --filename "%n - s%se%e.mp4" --test 1796172 1802881 1800558

Retrieving item '1796172' via command './capto -e 1796172 -a -f "Superstore - s04e13.mp4"'
Retrieving item '1802881' via command './capto -e 1802881 -a -f "Abby's - s01e01.mp4"'
Retrieving item '1800558' via command './capto -e 1800558 -a -f "The Simpsons - s30e19.mp4"'
Preformatted text Finished 3 jobs in 0 sec: 0 files transferred OK, 0 files failed.

So it would seem that if you knew the IDs in advance, wrapto should still do what you want… unless I’m still missing something.

well, if nothing else it’s a lot shorter and this way I have all the parameters I know I’m going to use already there.

The loop, well, now I guess it’s unnecessary.

The line from the help

# Fetch a list of episode IDs found with a previous capto -i command:
  wrapto [--verbose] [--get | --test] 555555 666666 777777

I almost suggest it’s one line, yet

[capto] -i - get tablo info

It’s a bit ambivalent since capto -i doesn’t provid IDs never got to it, was getting

Your script has put a nice polish on capto for me - thanks greatly! Fun time with wrapto here. Have a stumble with a show, suspect it’s the apostrophe in the show name - it seems to be the only unique difference.
The show “The People’s Court”. I get Item '76587' not found in list. Skipping. I first searched via capto -s to confirm it’s existence. I added --verbose which shows it it was found, and added, yet subsequently not found:

$ wrapto --test --filename "%n - s%se%e.mp4" --name "The People's Court" --all --verbose
wrapto: Starting up with verbose='1', name='The People's Court', range='', get='0', test='1', all='1',
Looking for all episodes for program 'The People's Court'
Found all possible episodes of 'The People's Court':
[capto] [2019-04-19 12:00] (76587) The People's Court - s22e110 - 
 [capto] [2019-04-22 12:00] (77926) The People's Court - s22e111 - 
 [capto] [2019-04-23 12:00] (78450) The People's Court - s22e112 - 
 [capto] [2019-04-24 12:00] (78919) The People's Court - s22e113 - 
 [capto] [2019-04-25 12:00] (79358) The People's Court - s22e114 - 
 [capto] [2019-04-26 12:00] (79815) The People's Court - s22e115 - 

Adding episode #76587 ([capto] [2019-04-19 12:00] (76587) The People's Court - s22e110 - )
Adding episode #77926 ([capto] [2019-04-22 12:00] (77926) The People's Court - s22e111 - )
Adding episode #78450 ([capto] [2019-04-23 12:00] (78450) The People's Court - s22e112 - )
Adding episode #78919 ([capto] [2019-04-24 12:00] (78919) The People's Court - s22e113 - )
Adding episode #79358 ([capto] [2019-04-25 12:00] (79358) The People's Court - s22e114 - )
Adding episode #79815 ([capto] [2019-04-26 12:00] (79815) The People's Court - s22e115 - )
Item '76587' not found in list. Skipping.
Item '77926' not found in list. Skipping.
Item '78450' not found in list. Skipping.
Item '78919' not found in list. Skipping.
Item '79358' not found in list. Skipping.
Item '79815' not found in list. Skipping.

I did try escaping “The People\'s Court” with the same results. Then to make sure I had things right I copied another name in the command with positive results [edited down to one episode]

$ wrapto --test --filename "%n - s%se%e.mp4" --name "The Carol Burnett Show" --all --verbose
wrapto: Starting up with verbose='1', name='The Carol Burnett Show', range='', get='0', test='1', all='1',
Looking for all episodes for program 'The Carol Burnett Show'
Found all possible episodes of 'The Carol Burnett Show':
[capto] [2019-04-21 22:30] (77933) The Carol Burnett Show - s01e12 - 

Adding episode #77933 ([capto] [2019-04-21 22:30] (77933) The Carol Burnett Show - s01e12 - )
Retrieving item '77933' via command 'capto -e 77933 -f "The Carol Burnett Show - s01e12.mp4"'
([capto],[-e],[77933],[-f],["The Carol Burnett Show - s01e12.mp4"]

directly $ capto -e 76587 -f "The People's Court - s22e110" worked as expected.

I don’t want to make this an issue, I have options. Your work (and tgwast) has already help to keep my universe from imploding :neutral_face: (yes, I have issues).

I think there is some limitation in the way that capto processes filenames. I’ve uploaded a new wrapto, version 1.4, that should allow you to download a file with any valid shell characters. Technically it first downloads the file to $EPISODE_ID.mp4, and then moves it to the final destination name. The practical upshot is that you can also save the file to a completely different path, even on an NFS or CIFS share, as long as your permissions allow it, for example:

./wrapto --get --verbose --name "Abby's" --range "s01e03-s01e05" --filename "/home/wbill3d/%n - S%sE%e - %t.mp4"

Will grab two episode of the show “Abby’s” (which has an apostrophe in it), and move each to my home directory after successfully fetching it from my DVR and temporarily saving it to my wrapto directory.

wrapto v1.4

Changelog:

  • Fixes bug that skipped actually doing capto -u when --update was passed in
  • Adds improved file naming support

That’s really screwy! I have retrieved Abby’s - by it’s ID so I tried The People’s Court with just it’s ID number… same hassle -that’s just flubbed up. I have requested shows with & in --name field hassle free. I had any episode of America’s Funniest Home Videos I up-arrowed to the command with People’s Court, that didn’t work and changed the ID to the AFV and… of course it worked!? Didn’t change anything else, exact same command, very similar titles both not having an episode title (ie "show - s01e01 - ") except People Court has a 3digit episode, but why would that matter.

OH WAIT… I don’t know what this means, line 96…
(.*?) - s(\d\d)e(\d\d) - ? maybe, episodes can only be 2 digits long? If so, well, now we can move on, probably past this.
Yup, I made a copy of wrapto.court and added a \d and well, it’s working… sorry for any inconvenience. (I suppose if you add the extra character and there isn’t one, that cause other problems, I’m not going doing any further research at this point)

Wow, that’s some good troubleshooting. Apologies for my freshman error, I’m definitely not a programmer. That said, here’s a new build that adds support for variable-length season and episode lists from capto (so we’ll be in good shape if the Tonight Show or The Simpsons ever make it to season 100).

wrapto v1.5

Changelog:

  • Adds support for season/episode lengths of with any number of digits (previously it expected a 2-digit season number and 2-digit episode number)

Way back when… it was called computer programming as opposed to coding I believe they told us we need to ‘idiot proof’ things. (now that’s offensive I think). Grateful it was relatively painless for you - two revision in a day!!
Yup, just when you think you have something figured out… they’ll change the way it works. Wonder what what capto v1 will look like and how wrapto v2 will work with it :wink:

fwiw: I’ve never upgraded to firmware 2.2.26 since I don’t need commercial skip and not sure what other changes there are… things work as they are for me. If capto and/or wrapto needs the firmware upgrade for future version, well I wait.

I just discovered this bypassed capto’s routine of removing things like & and ’ from filenames. In previous versions it had been possible to pass /path/to/save with --filename already.

I discovered this as I had some auto run scripts with a rename line, generally to rename The Show to Show (just makes me feel better). Trying to rename “Rizzoli and Isiles”, as capto renames it to “Riaaoli Isiles” doesn’t work when the naming changes to “Rizzoli & Isiles” And the sort order of Peoples vs People’s is enough to mix things.

My perception is certain characters shouldn’t be in filenames… but someone uses a file manager primarily, it rarely matters and some prefer the proper punctuation.

Just so I know which way to look, all is working. I’ve set up cron.d jobs to get weekly shows - beautiful.

Not passing --filename “/path/where/I/want/%n - s%se%e.mp4” directly to capto, not only by-passes capto’s routines to sanitize filenames - technically, first, it downloads it to the current working directory as $EPISODE_ID.mp4 then moves it to the device I asked for. So If I don’t want to be writing gigs needlessly to an SSD just to have the file deleted/moved, I need to change before executing wrapto, including scripts run via cron… it ok, just so I know now.

I would mount CIFS shares //device-name/pvr /media/pvr cifs defaults,[etc] it doen’t require any special accommodations.

So If I don’t want to be writing gigs needlessly to an SSD just to have the file deleted/moved, I need to change before executing wrapto,

That’s not a bad idea anyway (and would be the safest option when writing to an NFS or CIFS mount regardless), however if you’re running wrapto/capto on the same drive that the file will ultimately be saved to and using any reasonably modern filesystem, doing a ‘mv’ won’t delete and re-write the data, it will just update the inode and journal entries.

I’m not sure that’s accurate, unless you’re concerned with network issues perhaps, no matter.

Exactly. Up until an update there was no mv issued, just passing parameters to capto. My point being, I understand the journal update… if I was on the same device, but not realizing the change, I wasn’t.

When I relax a bit, I don’t mean to be overly critical of all your personal time and work you’ve put into this project. Final comment, I wanted to verify my versions, it says 1.4, yet the final download link is for 1.5 so maybe I never got the final version.