Backup, clone, and erase drives with Atola's MultiDrive

Atola recently released MultiDrive, a freeware drive utility for 64-bit versions of Windows 10/11 & 2019/2022/2025.

Pros:

Cons:

❧ 2025-08-07


Convert MKV (DVD rip) with VobSub to MP4 with burned-in bitmap subtitles

DVD rips often include VobSub (dvdsub) subtitles, image-based (bitmap) tracks1 that aren't supported in MP4 containers. Rather than extracting and OCRing (which can be slow and error-prone), burn them directly into the MP4 with ffmpeg:

  1. Identify the subtitle stream:

    Use ffprobe to confirm that the MKV contains VobSub (dvd_subtitle) subtitles:

    ffprobe -v error -select_streams s -show_entries stream=index,codec_name:stream_tags=language -of csv=p=0 foo.mkv
    3,dvd_subtitle,eng

    Stream 3 is dvd_subtitle (VobSub), in English.

  2. Convert & burn-in with ffmpeg:

    This command burns in the bitmap subtitles, retains all audio, preserves chapters, and encodes the video using Apple's hardware-accelerated hevc_videotoolbox:

    ffmpeg -i foo.mkv \
      -filter_complex "[0:v][0:s:0]overlay,format=nv12[v]" \
      -map "[v]" \
      -map 0:a \
      -map_chapters 0 \
      -c:v hevc_videotoolbox -b:v 6000k -tag:v hvc1 \
      -c:a copy \
      -movflags +faststart \
      foo.mp4

Flag exegesis

Footnote

  1. Blu-ray subtitles use PGS, an image-based format like VobSub that's also best burned in for MP4 compatibility and to skip OCR. 

❧ 2025-08-06


Compile MacDown for Apple silicon

Restores "Recent Documents" to the MacDown Dock icon in Sequoia and drops the Rosetta 2 dependency (macOS 27 will be the last to fully support it). Install Xcode and brew then run:

#!/bin/bash
set -euo pipefail

echo "Installing dependencies..."
brew install ruby@3.2

# Add Ruby and gem binaries to PATH (avoids duplicate lines in ~/.zshrc)
echo "Configuring Ruby path..."
LINE='export PATH="/opt/homebrew/opt/ruby@3.2/bin:/opt/homebrew/lib/ruby/gems/3.2.0/bin:$PATH"'
grep -Fxq "$LINE" ~/.zshrc || echo "$LINE" >> ~/.zshrc
export PATH="/opt/homebrew/opt/ruby@3.2/bin:/opt/homebrew/lib/ruby/gems/3.2.0/bin:$PATH"

# Uninstall any outdated Bundler versions
echo "Cleaning bundler..."
[ -d /opt/homebrew/lib/ruby/gems/3.2.0 ] && gem uninstall -aIx bundler || true

echo "Installing bundler and CocoaPods..."
gem install bundler cocoapods

echo "Cloning MacDown..."
git clone --recurse-submodules https://github.com/MacDownApp/macdown
cd macdown

# The next 4 commands are only necessary if you want the last official pre-release version, 0.8.0d71 (1079)
# rather than the default 0.8.0d32 (1111) version:

git checkout v0.8.0d71
git submodule update --init --recursive

# Work around broken Slovak localization file in v0.8.0d71
echo "Replacing broken Slovak localization file..."
echo '"DUMMY_KEY" = "DUMMY_VALUE";' > MacDown/Localization/sk.lproj/Localizable.strings

echo "Removing outdated Gemfile.lock..."
rm -f Gemfile.lock

echo "Installing Ruby dependencies..."
bundle install

echo "Installing CocoaPods dependencies..."
ruby -e 'require "logger"; exec "pod", *ARGV' install

echo "Building PEG Markdown parser..."
make -C Dependency/peg-markdown-highlight -j$(sysctl -n hw.ncpu)

echo "Opening Xcode..."
open MacDown.xcworkspace

cat <<EOF

Setup complete.

When Xcode opens, press Cmd+B to build; find MacDown.app via
Product > Show Build Folder in Finder > Products/Debug
EOF

Notes

❧ 2025-08-06


macOS Finder modification date off? Try stat

stat foo.sparsebundle
..."Aug  2 07:53:48 2025" "Feb 23 09:09:25 2022" "Aug  2 07:53:10 2025" "May  7 08:30:24 2014"...
  1. Access (atime): Last time file was read (not always updated on APFS or recent macOS versions).

  2. Modify (mtime): Last time file content was modified. This is what "Date Modified" in Finder usually shows.

  3. Change (ctime): Last time file metadata changed (e.g., permissions, ownership, even renaming the file).

  4. Birth (btime): File creation date — what Finder calls "Created".

❧ 2025-08-06


Batch delete thousands of Gmail drafts in seconds

Mail in macOS has long suffered from a bug that can generate thousands of near-duplicate Gmail drafts (the simplest workaround is switching draft storage from server to local).

Selecting more than 50 drafts in the Gmail web interface via "Select all x messages in Drafts" disables the "Discard drafts" option, making large-scale deletion tedious.

A faster alternative is using Google Apps Script. While script executions are limited to 6 minutes, the following API-based method can delete thousands of drafts in seconds:

  1. Go to https://script.google.comNew project

  2. Paste and save the following script:

    function deleteAllDrafts_FinalPaginated() {
      try {
        let allMessageIds = [];
        let pageToken;
        let pageCount = 0;
    
        // 1. Loop through all pages of drafts to get every ID.
        do {
          const response = Gmail.Users.Drafts.list('me', {
            maxResults: 500, // Fetch up to 500 per page for speed
            pageToken: pageToken
          });
    
          if (response.drafts && response.drafts.length > 0) {
            const messageIdsOnPage = response.drafts.map(draft => draft.message.id);
            allMessageIds = allMessageIds.concat(messageIdsOnPage);
            pageCount++;
          }
    
          // Get the token for the next page. If there isn't one, the loop will end.
          pageToken = response.nextPageToken;
        } while (pageToken);
    
        const totalDrafts = allMessageIds.length;
    
        if (totalDrafts === 0) {
          Logger.log("No drafts to delete.");
          return;
        }
    
        Logger.log("Found " + totalDrafts + " drafts across " + pageCount + " page(s). Processing in batches...");
    
        // 2. Process the complete list of IDs in chunks.
        const batchSize = 100;
        for (let i = 0; i < totalDrafts; i += batchSize) {
          const chunk = allMessageIds.slice(i, i + batchSize);
          const request = { ids: chunk };
          Gmail.Users.Messages.batchDelete(request, 'me');
          Logger.log("Deleted batch: " + (i + 1) + " through " + Math.min(i + batchSize, totalDrafts));
        }
    
        Logger.log("Successfully initiated deletion of all " + totalDrafts + " drafts.");
    
      } catch (e) {
        Logger.log("Error deleting drafts: " + e.name + " - " + e.message);
      }
    }
  3. In the left-hand menu, click + next to Services

  4. Select Gmail APIAdd

  5. Click Run

During testing, used this script to quickly generate 105 drafts:

function createTestDrafts() {
  for (var i = 1; i <= 105; i++) {
    var subject = "Test Draft " + i;
    var body = "This is test draft number " + i + ".\nGenerated for batch deletion testing.";
    var recipient = Session.getActiveUser().getEmail();
    
    GmailApp.createDraft(recipient, subject, body);
  }
    
  Logger.log("105 test drafts created.");
}

❧ 2025-08-06


Blink reminders for macOS

TitleTriggerPriceSizeNotes
AutoBlink 2.1.1time/camera$9.99 (3-day free trial)2MBApp Privacy: "Data Not Collected"
Blinks 1.7time$4.992.5MBApp Privacy: "Data Not Collected"
SightKick 1.0.64time/camerafree3.7MB"Data Not Linked to You: Health & Fitness, Usage Data, Identifiers, Diagnostics"
eyeREST 0.2.0camerafree25.5MB"Data Not Linked to You: Location, Usage Data, Identifiers, Diagnostics"
Eyeblink 3.7.4time/camerafree27.8MBWindows version also available
ScreenBlink 1.0.0time/cameraopen source390.7MBWindows version also available

❧ 2025-08-03


Links page reborn

The Links page, fallow since 2006, has been resurrected as a stream of periodic headlines. Not enough to justify an RSS feed, but an occasional scroll may surface something of interest.

This Python script make posting fairly frictionless while allowing for editing of existing entries:

❧ 2025-07-30


WSL for macOS

❧ 2025-07-26


Offline Apple serial number database

Apple's serial number lookup page was down today, first with a looping GIF saying "We'll be back. We're busy updating our support tools and will be back soon." in 19 different languages and later with a page simply stating "We are unable to complete your request at this time."

Needing to identify a Mac but unwilling to use a third-party web service, retrieved the serial number via dmidecode -s system-serial-number in GParted Live then searched OpenCore's modelinfo_autogen.h for the last 3 characters (8QR), which corresponded to "MacBook (13-inch, Aluminum, Late 2008)".

While the model identifier can be obtained via dmidecode -s system-product-name (in this case "MacBook5,1" which Mactracker also reports as "MacBook (13-inch, Aluminum, Late 2008)"), some identifiers like "MacBook5,2" correspond to multiple models ("MacBook (13-inch, Early 2009)" and "MacBook (13-inch, Mid 2009)").

Note that Apple replaced structured serial numbers with randomized ones in 2021:

❧ 2025-07-12


Same store, same product, different price by platform

Searching for a Brother laser printer on OfficeDepot.com in Safari while not signed in, the HL-L2405W was priced at $134.99 on iOS and $148.49 on macOS, a $13.50 difference at the same time and store location.

❧ 2025-07-08