All posts by JamesIsIn

Remove All Save One Subtitle Stream from Video

This can be done using ffmpeg.

ffmpeg -y -i 01\ –\ Chapter\ 1\:\ \ Le\ Collier\ de\ la\ reine.mkv -map 0:v -map 0:a? -map 0:s:5 -codec copy 01\ –\ Chapter\ 1\:\ \ Le\ Collier\ de\ la\ reine_.mkv

Take a look at the (third) -map 0:s:5. That bit tells ffmpeg which stream to keep. I got mine from just counting down the list of subtitles under the Subtitles menu in VLC. Remember that the first element is 0.

(This can also be used to remove audio streams by changing the s to an a in that same bit.)

Also, note that in my output file I added an underscore to the end of the file name: Don’t overwrite your original (you may not get it right the first go).

(See this source here.)


Convert ISO into MKV

First you want to either mount or unpack your ISO so you can work with the containing files directly.  Those are easy to do via the GUI using the mouse and its right-click menu.  I recommend unpacking.  Start that and walk away.  It will take some time to pack the entire disc image.

Once unpacked open your terminal and cd into the VIDEO_TS directory of your freshly unpacked files.  In there you will find some number of VOB files named like VTS_01_1.VOB.  The first of those (_0) is likely a menu item.  You can confirm this by playing it in VLC.  Once confirmed rename it from .VOB to _VOB to hide it from the next command.  The remaining (_1 through _?) should be the actual video content.  Again, confirm this using VLC.

Basically, the VOB files for actual content will be the same size except the last one which is smaller than the others.  If there are any other VOB files not part of your target content rename those also as _VOB.

Now we can merge those all together into a single file:  cat *.VOB > output.vob

(And now it’s obvious why we were hiding any superfluous .VOB files.)

Finally, use ffmpeg to go from .vob to .mkv:  ffmpeg -i output.vob  -c:v libx264 -c:a aac output.mkv

That’s the quick and dirty version which has worked for me.  You will notice it also shrinks the file size.  There is a ton of information about using ffmpeg out there so if you require different conversions, someone out there can probably guide your specific quest.


An All-Variable Method to Send a Bash Command over SSH

I needed to send a command (actually two separate) over ssh to check for the presence of a user (username) on a remote system. The command needed to be constructed entirely from variables. This is the solution I crafted (after much trial and error).

You will see that I send two ssh commands because I need to check for two styles of username (flast and first.last).  These functions are run inside a follower-script called by a leader-script responsible for gathering a host of data regarding departing users.

The leader passes a trio of variables into the follower.  The follower then crafts some variables of its own.  It is from all of this that the final command is crafted.

#! /usr/bin/env bash 
# Title   : 
# Parent  : 
# Author  :  JamesIsIn 20180719 Do something nice today. 

# Purpose :   
# SOP     :  https://some.url/display/ops/NOC+Terminator+Script 
#         :  https://some.url/display/ops/Termination+Procedure+-+NOC 

#  Variables  # 

# # debugging 
# scriptUser_linux= 
# termuser_flast= 
# termuser_firstDotLast= 
# # 

# the original script included the following note:  
# using test nagios results in halved execution times (compared to prod); all configs in sync 
readonly const_nagiosHost="nagios.some.url" 
readonly const_nagios_cgi="/usr/local/nagios/etc/cgi.cfg" 
declare termuser_firstDotLast_grep 
declare termuser_flast_grep 


#  Functions  # 

function func_sshCommand() { 
	local loc_sshArguments="-n -l ${scriptUser_linux} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet" 
	local loc_sshHostCommand="grep -c \"${1}\" ${const_nagios_cgi}" 
	# this is complex 
	# the export is for filling the passed parameter which is a variable name 
	# that passed variable name is then filled with the output from ssh 
	# where ssh is loaded from a trio of variables 
	export "$( printf '%s' "$( printf '%s' "${2}" )"="$( ssh $( printf '%s' " ${loc_sshArguments} ${const_nagiosHost} ${loc_sshHostCommand}" ) )" )" 

function main() { 
	# printf '%s\n' "Your password will be required for two tests.  " 
	func_sshCommand "${termuser_firstDotLast}" termuser_firstDotLast_grep 
	func_sshCommand "${termuser_flast}" termuser_flast_grep 
	if [ ! "${termuser_firstDotLast_grep}" == "0" ] || [ ! "${termuser_flast_grep}" == "0" ] ; then 
		printf '%b\n' "" "Nagios user found.  Please remove the following user from the nagios cgi.cfg file.  " 
		if [ ! "${termuser_firstDotLast_grep}" == "0" ] ; then 
			printf '%s\n' "${termuser_firstDotLast} (${termuser_firstDotLast_grep}) " 
		if [ ! "${termuser_flast_grep}" == "0" ] ; then 
			printf '%s\n' "${termuser_flast} (${termuser_flast_grep}) " 
		printf '\n' 
		return 1 
		return 0 


#  Main  # 

exit $? 



The magic finally happens in the admittedly somewhat cryptic export line (hence the preceding lines of comment).

Clever? You be the judge.


Using sort as a Version Test in Bash

I have several bash scripts where I use readarray (aka mapfile) only if it will function on that system. This is dependent upon the version of bash being used. I have this test which is interesting enough to be worthy of some small discussion.

Here is the function that gets the bash version, makes the test, and then either uses mapfile (if the version is high enough) or uses read (if it is not). Basically, you are asking if the local version of bash is greater than or equal to x.x.x (your target version).

function func_getTorrentList() {
    local loc_bashVersion
    loc_bashVersion="$( bash --version | head -n1 | cut -d " " -f4 | cut -d "(" -f1 )"
    # readarray or mapfile -d fails before bash 4.4.0
    if printf '%s\n' "4.4.0" "${loc_bashVersion}" | sort -V -C ; then
        mapfile -d $'\0' A_torrentList < <( transmission-remote --list | sed -e '1d;$d;s/^ *//' | cut --only-delimited --delimiter=' ' --fields=1 )
        while IFS= read -r ; do
            A_torrentList+=( "$REPLY" )
done < <( transmission-remote --list | sed -e '1d;$d;s/^ *//' | cut --only-delimited --delimiter=' ' --fields=1 )

# bonus: surround the local version for limiting the test to a version range
    if printf '%s\n' "4.4.0" "${loc_bashVersion}" "6.6.6" | sort -V -C ; then
# or with variables
    if printf '%s\n' "${version_lowerLimit}" "${loc_bashVersion}" "${version_upperLimit}" | sort -V -C ; then


If you look at the if printf line you will see the last operation of the if is a sort call. In that sort call the -V tells sort to use version sorting. The -C tells sort to give true or false on whether any sorting was done.

If the first number (your target version) is the same or smaller than the second number (the local version of bash), then sort passes (-C is true). Otherwise, sort fails (and -C is false and so goes the if statement).

You can further create a range by surrounding the local version with lower- and upper-limit version numbers, as you see in the bonus line at the end.



Understanding Perl Locality

Perl has a variable declaration called local.  One might  be tempted to interpret this as creating a local variable in the sense such as other scripting languages (like Bash or Python) use local.  Avoid temptation.  I’m going to unpack this a bit here.

The places on the Web where Perl documentation lives tend to be a little shy about revealing much concerning this distinction.  For example Perl Tutorial (org) cleverly avoids mention of localPerl Variables.  And w3schools (io) has a single ill-defined definition and an awkward attempt at scope:  Perl – Comments (see Variable Scopes and Perl Local Variable).

Geeks for Geeks provides a much better explanation of the my keyword which will help to understand how my is similar to local in other languages (like Python or Bash):  Perl | my Keyword.  Also, plover has a thorough explanation for the uses of localSeven Useful Uses of local.  (If you’d like to dive even deeper, plover also has a thorough discussion of scope in Perl:  Coping with Scoping.)

Ok, let’s see how simple we can make this.

If you are writing a Bash script and putting your executable code only into functions (in programming there should be no executable code outside of functions), then when you declare a local variable inside a function the scope of that variable will exist only inside that function.  The only things that can have access to your variable live inside that function.  Another function may call this function, but that calling function cannot see the variable.

Still in Bash, the reverse is also true with one caveat:  this function can call another function but the called function will also not be able to see calling function’s local variable.  However, this function can use export to export its local variable to the called function.

Let’s phrase this as a local variable in Bash can be exported down the call-stack (using export).

One other caveat of bash is that each function runs in its own subshell. This means that creating any variable inside a function will not overwrite any global variable (with the same name). This will catch one off-guard from time to time; it has me. (You cannot use export to send any variable up the call-stack.)

Back in Perl, a my variable would be stuck within it’s what’s called its lexical block.  This is usually defined by a pair of squigglies:  {}.  To continue with our functions example (and again, you want all of your code to live within functions when you script) if you declare a my variable inside a function in Perl (what Perl calls a subroutine declared using sub), that my variable will be trapped in its function (subroutine) the same that a local variable in Bash would be trapped in its function.

Now that we have that aligned, let’s look at the relationship between a global variable and a local variable in Perl, because this is a bit tricky.

In both Perl and Bash, you will want to prefer declaring your global variables in the declarations section at the beginning of your script.  (See my script templates for assistance.)  Once declared, these global variables will be available throughout the current script by any function or call-stack.

This is where the power of Perl’s poorly named local keyword comes into play, which you will likely never need or use.  Declaring an existing global variable as local inside a function temporarily re-assigns the global variable for use in this function and down any call-stack therein.  Once this function (and any call-stack it evoked) ends execution, the former global variable is restored for the remainder of the script.

(Attempting to declare a local variable for a non-existant global variable will throw an error.)

So, in Perl local temporarily substitutes a value for a global variable.  Could be powerfully useful in the right situation, but you probably just want my.


CherryTree’s Cryptic Is File Blocked Error

I have been testing several notes applications, among them CherryTree.  It has a lot to be said in its favor.  However, it started throwing an error which ended with the phrase “is file blocked by a sync program” whenever a save operation was attempted.

I tried the Googs but only the first two search results were of any value.  The remaining results had somehow gamed Google.  Each contained exactly my search string (without quotes) “cherrytree is file blocked by a sync program”.  One was even titled exactly that (but with a capital C).  The other results were articles with titles such as “Human earwax shows what kind of variation”, “Why am i hungry in the middle of night Why Do I Crave Sweets”, “Cattle Visions” (this from so you know it’s legit).

How the might have fallen…

Of course they will likely have fixed this by the time you read these words.  Otherwise, how did you find my article?  Are you psychic?!


Well, I suppose you’d like some insight.  What I can say is that in my case the error was due to a full disc.  Sorted that and solved the matter.  I discovered this only because when I did a save-as I (eventually) was presented a different error message that mentioned disc space.  So… [shrugs]… check your disc space?

Hope that helps.


testing & expansion in code blocks

WordPress insists on changing certain characters into their & equivilants (when those characters appear within a code block) each time the article is udpated.  This wouldn’t be a problem except that the ampersand is one of the characters which gets this expansion.

If the article is always saved via the Text side this bug is circumnavigated.  This only appears to happen when clicking the Update button while viewing the Visual side in the editor.

This is how the problem increases with each subsequent save.

Watch the ampersand go crazy:   &amp;amp;amp;amp;quot;&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp; 
function func_error() {
    printf '%s\n' &amp;amp;amp;amp;quot;[$( date +'%Y-%m-%d_%H:%M:%S%z' )]: $@&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;2

if ! do_something; then
    err &amp;amp;amp;amp;quot;Unable to do_something&amp;amp;amp;amp;quot;
    exit &amp;amp;amp;amp;quot;${error_did_nothing}&amp;amp;amp;amp;quot;

Just a fucking mess.

When I see that I’ve made this mistake (of click the Update button in an article with a code block while viewing the Visual side of the editor), if it’s one or two I just edit them directly and hit the update button from the Text side. If it’s a lot (I’m looking at you, “) then I pull the entire article from the Text side and paste that into VScode and make any find-and-replaces necessary.

The order is important here. First I change all amp;amp; into just amp;. Then I change all & into &. And finally I can fix each of the other expansions as required (usually <, >, and ").

Maybe this is fixed in a newer version? Could be. Have fun.


Remove Subtitles and Set Default Audio in Video Files

Some of the time my DLNA server fails to parse the main language correctly and at the same time fails to offer any language selection for that file. So I dug around and found a simple method using ffmpeg to set the file as I want it. (I used this thread as part of my solution.)

ffmpeg -i 01\ –\ Spellbound.mp4 -map 0:0 -map 0:2 -map 0:1 -sn -disposition:a:1 default -sn 01\ –\ Spellbound__ffmpeg.mkv 

In my case (outlined in the above command) English was the second language option and the original default was, I don’t remember, not English. The -map 0:0 just uses the existing video portion. The next two set the second language as the first and the first as the second (in order). Finally, that -sn removes all (or at least doesn’t bother to copy any) subtitles. The bit about disposition ensures the (new) first language is set as default.

It takes a while because it’s re-writing all the frames. If I come across a faster or more efficient method I’ll return to this article.

One could easily loop this command for all files in a folder.

Here is another example where I squashed six VOB files into one mkv (and reduced the total size from 5.78 GB to under 1 GB):

ffmpeg -i "concat:VTS_01_1.VOB|VTS_01_2.VOB|VTS_01_3.VOB|VTS_01_4.VOB|VTS_01_5.VOB|VTS_01_6.VOB" output.mkv 

Also, converting from mp4 to mkv was just an easy way to keep the same file name. You could convert either way or with the same extension and into a separate folder or with a different file name instead.