Back in the Straddle Again

13 weeks after right total hip arthroplasty, I’m riding my Onewheel Pint again.

https://youtu.be/FviShE8eV38 via @YouTube

Muscle memory is an amazing thing.

Advertisement

My Favorite Way to Download from YouTube

I discovered a method for downloading YouTube videos that is based on the 4k Video Downloader method at Robservatory.com, but works faster and better for me. The software needed is called yt-dlp, and you can install it using Homebrew. Here’s one recommended Terminal command line for installing the most recently released yt-dlp:

brew install yt-dlp/taps/yt-dlp

To update the installation of yt-dlp performed this way, use the following command.

brew upgrade yt-dlp/taps/yt-dlp

After you’ve built this Automator Quick Action as described below, you’ll be able to select the URL of the video you’d like to download in the URL field of the web browser (Safari), then right-click and choose Services to select the action.

You’ll need to be able to set up a Quick Action (Monterey) or a Service (if you have a version of macOS older than Monterey, as shown in Rob’s article mentioned above, or as explained by Ben Waldie) in Automator. The Quick Action will be saved as a Contextual Menu Item and will be available from the Services menu when you right-click text (in our case, a URL link in Safari’s URL field).

Use one of the two paths to the yt-dlp executable, not both.

In the script included below, I used the first of the two because my Silicon Mac uses the M1 native version of HomeBrew to install yt-dlp:

So, if you have installed Homebrew natively on your Silicon (M1, M2, etc.) Mac, use the following line

set ExecLoc to "/opt/homebrew/bin"

If you have an Intel Mac or you have upgraded to Silicon, but haven’t migrated your Homebrew installation, use the following line instead:

ExecLoc to "/usr/local/bin"

You can normally determine the location of the executable by running the Terminal command:

which yt-dlp

It is possible to include that command in the script, but that would make the script more complex than it really needs to be, so I suggest performing that step yourself and using the result to decide which line of the included script to use. (Both lines specifying the typical locations for the yt-dlp executable are included in the code below, but I’ve started the unneeded line with “#” to include it only as a comment, nullifying its ability to affect the AppleScript. If you like, you may leave both lines, and simply “comment” the line that isn’t appropriate for your configuration. I’ve used two “#”‘s, just to make them easier to see. One is enough to “comment” the line. In AppleScript, 2 hyphens can be used at the beginning of a line as an alternative method of transforming it into a comment.)

The first step in making the contextual menu item that will download videos is to create a new document in the Automator app on your Mac. If you open Automator and it presents an Open dialog window, just close it and select New from the File menu. Here’s where you specify the Quick Action workflow type.

Choosing the Quick Action Automator document type

You’ll need a Run AppleScript action in your Quick Action or Service workflow. The AppleScript I’ve composed contains the command line that will be sent to the yt-dlp executable. I’ve tried several forms of the command and the one I’ve provided is my favorite. It combines high quality audio and video with available English subtitles, including the automatically generated subtitle track (combining these files requires that ffmpeg also be installed using, for example, the Homebrew installation instructions for ffmpeg provided elsewhere on this blog). I’ve found that the action built using this command line, depending on the content and duration of the video, downloads the video very quickly, often before the ad timer expires. There are many, many combinations of command options that may be substituted, of course, if you have familiarized yourself with the capabilities of yt-dlp.

Replace the default contents of the Run AppleScript action with the following text, specifying the correct value for the ExecLoc variable as explained above:

on run {input, parameters}
	(* Your script goes here *)
	set ExecLoc to "/opt/homebrew/bin"
	##set ExecLoc to "/usr/local/bin"
	
	set startyt to ExecLoc & "/yt-dlp" & space & "-f" & space & "\"" & "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4] / bv*+mergeall[vcodec=none]" & "\"" & space & "-o ~/Downloads/'%(title)s.%(ext)s'" & space & "--ffmpeg-location" & space & ExecLoc & space & "--embed-subs --write-auto-subs --restrict-filenames"
	set entireCommand to startyt & space & input
	do shell script entireCommand
end run

At the top of the Quick Action window in Automator, set Workflow receives current to URLs. You can limit the Quick Action‘s availability to Safari or make it available in any application. You can use the screen window snapshot below as a guide for determining what to use for the remaining settings, or try your own.

Automator Quick Action to download a video from its URL

Save the workflow (I’ve put an underscore at the beginning of its name so that in will be listed at or near the top of Quick Action or Services list when the URL of the displayed video is selected in Safari’s URL field and the contextual click is performed on that link.) When you perform the action, an indeterminate progress indicator (it looks like a spinning gear cog) should appear in your menu bar. When it disappears, you should find the downloaded video in your Downloads folder.

Create an AppleScript application to concatenate media files using ffmpeg.

Please read the following, or you’ll probably reach the conclusion that this doesn’t work right, and then you’ll just be mad at me for wasting your time.

The AppleScript below is configured to exploit a special relationship between AppleScript and the Mac Finder. Here are two suggested methods for using it to concatenate media files by passing the appropriate commands to ffmpeg. First, the media files must be compatible for concatenation. The documentation for ffmpeg can help with that. The AppleScript will create the concatenated output file in the same folder as the source files. The ffmpeg command generated by the AppleScript is ultimately placed in the trash, so you can retrieve it from there and view it if something doesn’t work as expected.

Method 1

I suggest this as the most straightforward method for using the AppleScript. Paste the AppleScript into a Script Editor window, then save it as an Application with any name you choose. The Application allows dropping multiple items onto its icon. Make a new folder in Finder, and place only the files to be concatenated into it. Ensure that the files in the folder are displayed in list view (as List in the Finder’s View menu), then sort the list as desired with the first file at the top of the window (name them so that they can be correctly sorted). Select all of the files (Select All from the Finder’s Edit menu), then click and drag the first file (the file at the top of the list) to the application’s icon (all of the selected files will be included in the drag). Accept the default name for the output or enter a new one and press OK.

Method 2

This method may be more useful if there are only a few files to concatenate. Paste the AppleScript into a Script Editor window and press RUN (The triangle pointing right near the top of the Script Editor window), or open the Application created from the AppleScript in a Script Editor window and press RUN. Navigate to the folder containing the source files in the Open dialog, then hold the Command key and click the files one at a time in the order in which you want them concatenated. While the clicked files remain selected in the Open dialog window, click OK. The output file will have a name based on the first file you clicked in the Open dialog.

It’s possible that you’ll need to provide a full path name for the ffmpeg executable rather than just the name “ffmpeg” (line 47, right after “sleep 3;”). To determine where your ffmpeg executable resides, you can run the following command in Terminal:

which ffmpeg

If you don’t have a compatible version of ffmpeg installed, install it using Homebrew. Here’s one recommended command line for installing the most recently released ffmpeg:

brew install homebrew-ffmpeg/ffmpeg/ffmpeg --HEAD

Here’s an example for including non-default options when installing ffmpeg

brew install homebrew-ffmpeg/ffmpeg/ffmpeg –HEAD –with-fdk-aac –with-two-lame

To update the installation of ffmpeg performed this way, use the following command.

brew update && brew upgrade homebrew-ffmpeg/ffmpeg/ffmpeg --fetch-HEAD

--begin AppleScript--
property temppath : "/private/tmp/"
property startnum : 0
property tmpfile : "/tmp/execme.command"
property outname : ""

on open the_items
	my ffmpeg_cat(the_items)
end open

on ffmpeg_cat(the_items)
	set theshellscript to ""
	repeat with i from 1 to (count of the_items)
		set itemcount to (count of the_items)
		set the_item to item i of the_items as alias
		try
			tell application "Finder"
				set sost to (container of the_item) as string
			end tell
			set pos_filepath to POSIX path of sost
		end try
		set this_filepath to (the_item as string)
		
		if last character of this_filepath is ":" then
			tell me to set it_is_a_folder to true
		else
			set it_is_a_folder to false
		end if
		set thesourcename to (name of (info for the_item))
		set namepart to (name extension of (info for the_item))
		set the_source_file to POSIX path of this_filepath
		if outname is "" then
			set outname to (text returned of (display dialog "Enter a name for the concatenated output file:" default answer thesourcename))
		end if
		set finalname to replace_chars(outname, "." & namepart, "")
		try
			if i = 1 then
				set the filelistbody to "# comment" & return & "file" & space & (quoted form of the_source_file) & return
			else
				set the filelistbody to filelistbody & "file" & space & (quoted form of the_source_file) & return
			end if
		on error onerr
			activate
			display dialog onerr
		end try
	end repeat
	set fileData to "echo" & space & "\"" & filelistbody & "\"" & space & "> ffmpegCatList"
	set theshellscript to fileData & ";sleep 3;ffmpeg -f concat -safe 0 -i ffmpegCatList -c copy" & space
	set shellExec to space & (quoted form of (pos_filepath & finalname & "_1-" & i & "." & namepart))
	set theshellscript to the theshellscript & shellExec & ";sleep 3" & return
	set the clipboard to theshellscript
	set theshellscript to theshellscript & ";/bin/echo '
			
			==========================
			
			" & finalname & "_1-" & i & "." & namepart & space & "FINISHED!" & "
			
			==========================
			
			';mv" & space & (quoted form of tmpfile) & space & (quoted form of (POSIX path of (path to trash))) & ";mv ffmpegCatList" & space & (quoted form of (POSIX path of (path to trash)))
	do shell script "echo " & quoted form of theshellscript & " > " & tmpfile
	
	repeat
		try
			do shell script "chmod +x " & tmpfile
			do shell script "open -a Terminal.app" & space & tmpfile
			exit repeat
		on error
			delay 1
		end try
	end repeat
	
end ffmpeg_cat

on replace_chars(this_text, _bad, _good)
	set AppleScript's text item delimiters to the _bad
	set the item_list to every text item of this_text
	set AppleScript's text item delimiters to the _good as string
	set this_text to the item_list as string
	set AppleScript's text item delimiters to ""
	return this_text
end replace_chars

on run
	set the_items to ((choose file with multiple selections allowed) as list)
	ffmpeg_cat(the_items)
end run--end AppleScript--

Create an AppleScript application to drop or open a folder of files in ffmpeg-normalize.

Audio content can be normalized and converted using ffmpeg-normalize. Here’s an AppleScript that can be saved as an application for dropping or choosing a folder of files to be processed by ffmpeg-normalize.

To use it, paste the code below into a Script Editor.app document window. Customize the six audio property variables at the start of the AppleScript, then run the AppleScript in Script Editor or save the AppleScript as an application. You can choose a folder of source files to which ffmpeg-normalize will be applied, or you can drop the folder onto the application file. The local path to the ffmpeg executable is specified by the first property. If you need to determine where your ffmpeg executable resides, you can run the following command in Terminal:

which ffmpeg

If you don’t have a compatible version of ffmpeg installed, install it using Homebrew. Here’s one recommended command line for installing the most recently released ffmpeg:

brew install homebrew-ffmpeg/ffmpeg/ffmpeg --HEAD

Here’s an example for including non-default options when installing ffmpeg

brew install homebrew-ffmpeg/ffmpeg/ffmpeg –HEAD –with-fdk-aac –with-two-lame

To update the installation of ffmpeg performed this way, use the following command.

brew update && brew upgrade homebrew-ffmpeg/ffmpeg/ffmpeg --fetch-HEAD

Instructions for installing and using ffmpeg-normalize are available at the ffmpeg-normalize Github site. Further explanation for the properties in the AppleScript are included after each property construct in the AppleScript. Some properties can be set to “” to accept ffmpeg-normalize default settings. The AppleScript identifies the properties that can be left empty. However, you must use combinations of settings that are compatible with the media and processing that you want to use. A working knowledge of ffmpeg-normalize is required, but the AppleScript may make batch processing for ffmpeg-normalize easier.

I should also probably point out that although I’ve only tested this using Monterey (macOS 12.3.1), it should be compatible for previous Mac systems, although you may need to change the start of the line that specifies the shell environment from

set full_command to "/bin/zsh

to

set full_command to "/bin/bash

--begin AppleScript--
property ffmpegrootPath : "/opt/homebrew/bin/ffmpeg" ## full path to the ffmpeg executable. It's needed to allow the AppleScript shell environment to tell ffmpeg-normalize where to find ffmpeg.
property outFolder : "normalized" ## This folder will be created inside the chosen/dropped folder and will contain the output. (The default output folder name is "normalized". Most names should be OK, but don't give the output folder the same name as the input folder.)
property sample_rate : "44100" ##Examples: 11025, 22050, 44100, 48000 (May be set to "" to apply default values.)
property rate_spec : "256K" ##Examples: 128K, 256K, 320K, 640K (May be set to "" to apply default values.)
property aud_Encoder : "libmp3lame" ##Examples: libmp3lame, aac, pcm_s16le, libfdk_aac -e='-vbr 3', eac3 (See all by running "ffmpeg encoders" in Terminal. Optional constructs can be added here also, as suggested in the "libfdk_aac -e='-vbr 3'" example. May be set to "" to apply default values.)
property out_spec : "mp3" ##Examples: mp3, aac, aif, wav, m4a, mp4 (The output container extension is designated by this variable, so ensure that displayed names of the input files having different name extensions are not duplicated when they are named with this extension. May be set to "" to apply default values.)

property a_folder : missing value

on run
	set these_items to ((choose folder with prompt "Choose a folder containing files to convert:") as list)
	my do_normalizing(these_items)
end run

on open these_items
	my do_normalizing(these_items)
end open

on do_normalizing(these_items)
	repeat with i from 1 to the count of these_items
		set this_item to item i of these_items
		set gen_item to this_item
		set gen_info to info for gen_item
		set gen_kind to folder of gen_info as Unicode text
		if gen_kind is "true" then
			tell me to set a_folder to true
		else
			set a_folder to false
		end if
		if a_folder then
			process_folder(this_item)
		else
			if (alias of gen_info is false) then
				process_item(this_item)
			end if
		end if
	end repeat
end do_normalizing

on process_folder(this_folder)
	set these_items to paragraphs of (do shell script "ls" & space & (quoted form of (POSIX path of this_folder)))
	set this_folder to this_folder as Unicode text
	set my_parent to this_folder
	repeat with i from 1 to the count of these_items
		set this_item to (this_folder & (item i of these_items))
		try
			set the item_info to (info for (this_item as alias))
			if folder of the item_info is true then
				set this_item to this_item & ":"
				process_folder(this_item)
			else if (alias of the item_info is false) then
				--display dialog this_item
				process_item(this_item as alias)
			end if
		on error the error_message number the error_number
			if the error_number is in {-43, -2706, -2753, -48} then
			else
				display dialog the (error_number as string) & space & the error_message
			end if
		end try
	end repeat
end process_folder

on process_item(this_item)
	tell application "Finder" to get (container of this_item) as text
	set my_parent to result
	tell application "Finder"
		set namepart to name extension of file this_item
		set namefull to name of file this_item
	end tell
	set the commandfolder to ((POSIX path of my_parent) as text)
	try
		set sample_rate to my replace_chars(sample_rate, " -ar ", "")
	end try
	try
		set aud_Encoder to my replace_chars(aud_Encoder, " -c:a ", "")
	end try
	try
		set rate_spec to my replace_chars(rate_spec, " -b:a ", "")
	end try
	try
		set out_spec to my replace_chars(out_spec, " -ext ", "")
	end try
	if sample_rate is not "" then
		set sample_rate to ((space & "-ar" & space & sample_rate) as text)
	end if
	if aud_Encoder is not "" then
		set aud_Encoder to ((space & "-c:a" & space & aud_Encoder) as text)
	end if
	if rate_spec is not "" then
		set rate_spec to ((space & "-b:a" & space & rate_spec) as text)
	end if
	if out_spec is not "" then
		set out_spec to ((space & "-ext" & space & out_spec) as text)
	end if
	set full_command to "/bin/zsh -c ;export FFMPEG_PATH=" & ffmpegrootPath & space & "&&" & space & ffmpegrootPath & "-normalize" & sample_rate & aud_Encoder & rate_spec & space & (quoted form of (commandfolder & namefull)) & space & "-of" & space & (quoted form of (commandfolder & outFolder)) & out_spec
	if full_command contains (outFolder & "/" & outFolder) then
	else
		do shell script full_command
	end if
end process_item

on replace_chars(this_text, reprap, goohood)
	set AppleScript's text item delimiters to the reprap
	set the item_list to every text item of this_text
	set AppleScript's text item delimiters to the goohood as string
	set this_text to the item_list as string
	set AppleScript's text item delimiters to ""
	return this_text
end replace_chars
--end AppleScript--

Create an AppleScript application to use stl2scad as a droplet app.

Joshua Flanagan as written a handy Python tool called stl2scad for converting .stl files (commonly distributed for 3D printing) to .scad files which can be opened in OpenSCAD on a Mac.

To use it, paste the code below into a Script Editor.app document window. Customizing the first two property variables is necessary, but (hopefully), trivial. The local path to the Python executable is specified by the first property, and the local path to the stl2scad.py script is specified by the second property. If you need to determine where your Python executable resides, you should be able to run the following command in Terminal:

which python3

If you don’t have a compatible version of Python installed, you should be able to install it using Homebrew.

Important: A space probably won’t exist in the path to the Python executable, but if a space (or any of a few other uncommon characters) exists in the path to the stl2scad.py script, simply create an escape for it by using two backslashes before each space. An example follows.

"~/my\\ python\\ scripts/stl2scad.py"

Download the stl2scad.py script. Run in the Script Editor and select a folder containing .stl files to convert, or save as an application for dropping the files, providing system permissions as prompted or in the Security & Privacy prefpane as needed. Drop one .stl file, a group of .stl files, a folder of .stl files or a hierarchy of folders of .stl files onto the application’s icon. The .scad files are created in the same folders as the .stl files just as though stl2scad had been run on each .stl file as a Terminal command.

--begin script--
property my_python_path : "/opt/homebrew/bin/python3"
property my_stl2scal_path : "~/py/stl2scad.py"
property a_folder : missing value

on run
	set these_items to ((choose folder with prompt "Choose a folder containing .stl files to convert:") as list)
	my convert_stl2scad(these_items)
end run

on open these_items
	my convert_stl2scad(these_items)
end open

on convert_stl2scad(these_items)
	repeat with i from 1 to the count of these_items
		set this_item to item i of these_items
		set gen_item to this_item
		set gen_info to info for gen_item
		set gen_kind to folder of gen_info as Unicode text
		if gen_kind is "true" then
			tell me to set a_folder to true
		else
			set a_folder to false
		end if
		if a_folder then
			process_folder(this_item)
		else
			if (alias of gen_info is false) then
				process_item(this_item)
			end if
		end if
	end repeat
end convert_stl2scad

on process_folder(this_folder)
	set these_items to paragraphs of (do shell script "ls" & space & (quoted form of (POSIX path of this_folder)))
	set this_folder to this_folder as Unicode text
	repeat with i from 1 to the count of these_items
		set this_item to (this_folder & (item i of these_items))
		try
			set the item_info to (info for (this_item as alias))
			if folder of the item_info is true then
				set this_item to this_item & ":"
				process_folder(this_item)
			else if (alias of the item_info is false) then
				process_item(this_item as alias)
			end if
		on error the error_message number the error_number
			if the error_number is in {-43, -2706, -2753, -48} then
			else
				display dialog the (error_number as string) & space & the error_message
			end if
		end try
	end repeat
end process_folder

on process_item(this_item)
	set sourcepath to (quoted form of (POSIX path of (this_item as Unicode text)))
	tell application "Finder"
		set namepart to name extension of file this_item
		set namefull to name of file this_item
	end tell
	set full_command to my_python_path & space & my_stl2scal_path & space & sourcepath
	if namepart is "stl" then
		try
			do shell script full_command
		on error error_message number the error_number
			display dialog "Error: " & the error_number & ". " & the error_message & return & namefull giving up after 5
		end try
	end if
end process_item
--end script--