Extract transcribers into self-registering Rake classes
Each transcriber owns its setup as Rake file tasks, so
prereqs flow through the dependency graph naturally.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/Rakefile b/Rakefile
index 5264796..1c16d92 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,22 +1,18 @@
require "net/http"
require "pathname"
require "uri"
+
require_relative "lib/feed"
require_relative "lib/download"
-TRANSCRIBER = ENV.fetch("TRANSCRIBER", "whisperx")
-
CACHE_DIR = Pathname("cache")
AUDIO_DIR = CACHE_DIR / "audio"
TRANSCRIPTS_DIR = Pathname("transcripts")
-SOUS_CHEF = Pathname("sous_chef/.build/release/sous_chef")
HRN_FEED = CACHE_DIR / "hrn_feed.xml"
HRN_FEED_URL = "https://rss.art19.com/cooking-issues"
-MODELS_DIR = CACHE_DIR / "models"
directory CACHE_DIR.to_s
directory AUDIO_DIR.to_s
-directory MODELS_DIR.to_s
file HRN_FEED.to_s => CACHE_DIR.to_s do
puts "Downloading HRN feed..."
@@ -27,24 +23,10 @@ file HRN_FEED.to_s => CACHE_DIR.to_s do
HRN_FEED.write(response.body)
end
-file SOUS_CHEF.to_s do
- sh "cd sous_chef && swift build -c release"
-end
-
-DOWNLOAD_SCRIPT = CACHE_DIR / "download-ggml-model.sh"
-DOWNLOAD_SCRIPT_URL = "https://raw.githubusercontent.com/ggml-org/whisper.cpp/master/models/download-ggml-model.sh"
+load File.expand_path("transcribers.rake", __dir__)
-file DOWNLOAD_SCRIPT.to_s => CACHE_DIR.to_s do
- puts "Downloading whisper model script..."
- CookingIssues::Download.fetch(DOWNLOAD_SCRIPT_URL, DOWNLOAD_SCRIPT.to_s)
- chmod 0o755, DOWNLOAD_SCRIPT.to_s
-end
-
-desc "Download a whisper.cpp GGML model (e.g., rake model[large-v3-turbo])"
-task :model, [:name] => [DOWNLOAD_SCRIPT.to_s, MODELS_DIR.to_s] do |_t, args|
- abort "Usage: rake model[NAME]" unless args[:name]
- sh DOWNLOAD_SCRIPT.to_s, args[:name], MODELS_DIR.to_s
-end
+TRANSCRIBER = Transcribers.resolve(ENV.fetch("TRANSCRIBER", "whisperx"))
+TRANSCRIBER.register
Rake::Task[HRN_FEED.to_s].invoke
@@ -55,38 +37,11 @@ def audio_path(ep)
end
def transcript_path(ep)
- (TRANSCRIPTS_DIR / TRANSCRIBER / "#{ep.slug}.txt").to_s
-end
-
-def transcribe(ep, audio_path, transcript_path)
- case TRANSCRIBER
- when "whisperx"
- hf_token = ENV.fetch("HUGGING_FACE_TOKEN") { abort "Set HUGGING_FACE_TOKEN for diarization." }
- sh "whisperx", audio_path,
- "--model", "large-v3",
- "--compute_type", "int8",
- "--device", "cpu",
- "--diarize", "--hf_token", hf_token,
- "--output_dir", File.dirname(transcript_path),
- "--output_format", "txt"
- when "whisper-cpp-large"
- model_path = MODELS_DIR / "ggml-large-v3-turbo.bin"
- Rake::Task[:model].invoke("large-v3-turbo") unless model_path.exist?
- sh "whisper-cli", "--model", model_path.to_s, "--output-txt", "--output-file", transcript_path.delete_suffix(".txt"), audio_path
- when "whisper-cpp-tdrz"
- model_path = MODELS_DIR / "ggml-small.en-tdrz.bin"
- Rake::Task[:model].invoke("small.en-tdrz") unless model_path.exist?
- sh "whisper-cli", "--model", model_path.to_s, "-tdrz", "--output-txt", "--output-file", transcript_path.delete_suffix(".txt"), audio_path
- when "sous_chef"
- Rake::Task[SOUS_CHEF.to_s].invoke
- sh SOUS_CHEF.to_s, audio_path, transcript_path
- else
- abort "Unknown transcriber: #{TRANSCRIBER}. Use 'whisperx', 'whisper-cpp-large', 'whisper-cpp-tdrz', or 'sous_chef'."
- end
+ (TRANSCRIPTS_DIR / TRANSCRIBER.name / "#{ep.slug}.txt").to_s
end
EPISODES.values.each do |ep|
- transcript_dir = (TRANSCRIPTS_DIR / TRANSCRIBER).to_s
+ transcript_dir = (TRANSCRIPTS_DIR / TRANSCRIBER.name).to_s
directory transcript_dir
audio = audio_path(ep)
@@ -97,13 +52,11 @@ EPISODES.values.each do |ep|
CookingIssues::Download.fetch(ep.audio_url, audio)
end
- file transcript => [audio, transcript_dir] do
- transcribe(ep, audio, transcript)
+ file transcript => [audio, transcript_dir, *TRANSCRIBER.prereqs] do
+ TRANSCRIBER.call(audio, transcript)
end
end
-# --- Tasks ---
-
task default: :sync
desc "Download and transcribe all episodes"
diff --git a/transcribers.rake b/transcribers.rake
new file mode 100644
index 0000000..916de2d
--- /dev/null
+++ b/transcribers.rake
@@ -0,0 +1,118 @@
+module Transcribers
+ MODELS_DIR = CACHE_DIR / "models"
+ DOWNLOAD_SCRIPT = CACHE_DIR / "download-ggml-model.sh"
+ DOWNLOAD_SCRIPT_URL = "https://raw.githubusercontent.com/ggml-org/whisper.cpp/master/models/download-ggml-model.sh"
+
+ def self.resolve(name)
+ case name
+ when "whisperx" then Whisperx.new
+ when "whisper-cpp-large" then WhisperCppLarge.new
+ when "whisper-cpp-tdrz" then WhisperCppTdrz.new
+ when "sous_chef" then SousChef.new
+ else
+ abort "Unknown transcriber: #{name}. Use 'whisperx', 'whisper-cpp-large', 'whisper-cpp-tdrz', or 'sous_chef'."
+ end
+ end
+
+ def self.model_path(name)
+ MODELS_DIR / "ggml-#{name}.bin"
+ end
+
+ class Base
+ include Rake::DSL
+
+ def prereqs = []
+ def register; end
+
+ private
+
+ # Shared setup for whisper.cpp model downloads.
+ def register_model(name)
+ models_dir = MODELS_DIR
+ script = DOWNLOAD_SCRIPT
+
+ directory models_dir.to_s
+
+ file script.to_s => CACHE_DIR.to_s do
+ puts "Downloading whisper model script..."
+ CookingIssues::Download.fetch(DOWNLOAD_SCRIPT_URL, script.to_s)
+ script.chmod(0o755)
+ end
+
+ file Transcribers.model_path(name).to_s => [script.to_s, models_dir.to_s] do
+ sh script.to_s, name, models_dir.to_s
+ end
+ end
+ end
+
+ class Whisperx < Base
+ def name = "whisperx"
+
+ def call(audio_path, transcript_path)
+ hf_token = ENV.fetch("HUGGING_FACE_TOKEN") { abort "Set HUGGING_FACE_TOKEN for diarization." }
+ sh "whisperx", audio_path,
+ "--model", "large-v3",
+ "--compute_type", "int8",
+ "--device", "cpu",
+ "--diarize", "--hf_token", hf_token,
+ "--output_dir", File.dirname(transcript_path),
+ "--output_format", "txt"
+ end
+ end
+
+ class WhisperCppLarge < Base
+ MODEL = "large-v3-turbo"
+
+ def name = "whisper-cpp-large"
+ def prereqs = [Transcribers.model_path(MODEL).to_s]
+
+ def register
+ register_model(MODEL)
+ end
+
+ def call(audio_path, transcript_path)
+ sh "whisper-cli",
+ "--model", Transcribers.model_path(MODEL).to_s,
+ "--output-txt",
+ "--output-file", transcript_path.delete_suffix(".txt"),
+ audio_path
+ end
+ end
+
+ class WhisperCppTdrz < Base
+ MODEL = "small.en-tdrz"
+
+ def name = "whisper-cpp-tdrz"
+ def prereqs = [Transcribers.model_path(MODEL).to_s]
+
+ def register
+ register_model(MODEL)
+ end
+
+ def call(audio_path, transcript_path)
+ sh "whisper-cli",
+ "--model", Transcribers.model_path(MODEL).to_s,
+ "-tdrz",
+ "--output-txt",
+ "--output-file", transcript_path.delete_suffix(".txt"),
+ audio_path
+ end
+ end
+
+ class SousChef < Base
+ BINARY = Pathname("sous_chef/.build/release/sous_chef")
+
+ def name = "sous_chef"
+ def prereqs = [BINARY.to_s]
+
+ def register
+ file BINARY.to_s do
+ sh "cd sous_chef && swift build -c release"
+ end
+ end
+
+ def call(audio_path, transcript_path)
+ sh BINARY.to_s, audio_path, transcript_path
+ end
+ end
+end