Use Rake file tasks for downloads and transcripts
Each episode gets a file task for its audio and transcript. Rake
handles skip-if-exists logic, so the Sync class is no longer needed.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/Rakefile b/Rakefile
index 0427c62..b1f1c35 100644
--- a/Rakefile
+++ b/Rakefile
@@ -2,7 +2,7 @@ require "fileutils"
require "net/http"
require "uri"
require_relative "lib/feed"
-require_relative "lib/sync"
+require_relative "lib/download"
CACHE_DIR = "cache"
AUDIO_DIR = "audio"
@@ -11,6 +11,8 @@ HRN_FEED = File.join(CACHE_DIR, "hrn_feed.xml")
HRN_FEED_URL = "https://rss.art19.com/cooking-issues"
directory CACHE_DIR
+directory AUDIO_DIR
+directory TRANSCRIPTS_DIR
file HRN_FEED => CACHE_DIR do
puts "Downloading HRN feed..."
@@ -25,44 +27,55 @@ def episodes
@episodes ||= CookingIssues::Feed.parse(HRN_FEED)
end
+def define_episode_tasks
+ episodes.sort_by(&:number).each do |ep|
+ audio = File.join(AUDIO_DIR, "#{ep.slug}.mp3")
+ transcript = File.join(TRANSCRIPTS_DIR, "#{ep.slug}.json")
+
+ file audio => AUDIO_DIR do
+ puts "Downloading #{ep.slug}..."
+ CookingIssues::Download.fetch(ep.audio_url, audio)
+ end
+
+ file transcript => [audio, TRANSCRIPTS_DIR] do
+ puts "Transcribing #{ep.number}. #{ep.title}..."
+ puts " TODO: sous_chef #{audio} #{transcript}"
+ end
+ end
+end
+
+# Parse the feed and register file tasks once it exists.
+task setup: HRN_FEED do
+ define_episode_tasks
+end
+
# --- Tasks ---
task default: :sync
-desc "Fetch the feed, download episodes missing transcripts"
-task sync: HRN_FEED do
- puts "Found #{episodes.size} episodes in feed."
-
- sync = CookingIssues::Sync.new(episodes)
- sync.run
+desc "Download and transcribe all episodes"
+task sync: :setup do
+ episodes.sort_by(&:number).each do |ep|
+ transcript = File.join(TRANSCRIPTS_DIR, "#{ep.slug}.json")
+ Rake::Task[transcript].invoke
+ end
end
desc "List all episodes from the feed"
-task episodes: HRN_FEED do
+task episodes: :setup do
episodes.sort_by(&:number).each do |ep|
- status = Dir.glob(File.join(TRANSCRIPTS_DIR, "#{ep.slug}.*")).any? ? "✓" : " "
+ status = File.exist?(File.join(TRANSCRIPTS_DIR, "#{ep.slug}.json")) ? "✓" : " "
puts "[#{status}] #{ep.slug} #{ep.title}"
end
end
desc "Transcribe an episode by number (e.g., rake transcribe[42])"
-task :transcribe, [:number] => HRN_FEED do |_t, args|
+task :transcribe, [:number] => :setup do |_t, args|
abort "Usage: rake transcribe[NUMBER]" unless args[:number]
ep = episodes.find { |e| e.number == args[:number].to_i }
abort "Episode #{args[:number]} not found in feed." unless ep
- audio = File.join(AUDIO_DIR, "#{ep.slug}.mp3")
- abort "Audio not found: #{audio}\nRun `rake sync` first." unless File.exist?(audio)
-
transcript = File.join(TRANSCRIPTS_DIR, "#{ep.slug}.json")
- if File.exist?(transcript)
- puts "Already transcribed: #{ep.slug}"
- exit
- end
-
- FileUtils.mkdir_p(TRANSCRIPTS_DIR)
- puts "TODO: transcribe #{ep.number}. #{ep.title}"
- puts " audio: #{audio}"
- puts " transcript: #{transcript}"
+ Rake::Task[transcript].invoke
end
diff --git a/lib/download.rb b/lib/download.rb
new file mode 100644
index 0000000..1bce7a5
--- /dev/null
+++ b/lib/download.rb
@@ -0,0 +1,27 @@
+require "net/http"
+require "uri"
+
+module CookingIssues
+ module Download
+ def self.fetch(url, dest, limit = 5)
+ raise "Too many redirects" if limit == 0
+
+ uri = URI(url)
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
+ request = Net::HTTP::Get.new(uri)
+ http.request(request) do |response|
+ case response
+ when Net::HTTPRedirection
+ fetch(response["location"], dest, limit - 1)
+ when Net::HTTPSuccess
+ File.open(dest, "wb") do |file|
+ response.read_body { |chunk| file.write(chunk) }
+ end
+ else
+ raise "Download failed for #{url}: #{response.code}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sync.rb b/lib/sync.rb
deleted file mode 100644
index 9aa5f74..0000000
--- a/lib/sync.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require "fileutils"
-require "net/http"
-require "uri"
-
-module CookingIssues
- # Downloads episodes that don't yet have transcripts.
- class Sync
- AUDIO_DIR = File.expand_path("../audio", __dir__)
- TRANSCRIPTS_DIR = File.expand_path("../transcripts", __dir__)
-
- def initialize(episodes)
- @episodes = episodes
- end
-
- def run
- FileUtils.mkdir_p(AUDIO_DIR)
- missing = untranscribed_episodes
- if missing.empty?
- puts "All episodes have transcripts."
- return
- end
-
- puts "#{missing.size} episodes need transcripts."
- missing.each do |episode|
- download(episode)
- end
- end
-
- private
-
- def untranscribed_episodes
- @episodes.reject { |ep| transcript_exists?(ep) }.sort_by(&:number)
- end
-
- def transcript_exists?(episode)
- Dir.glob(File.join(TRANSCRIPTS_DIR, "#{episode.slug}.*")).any?
- end
-
- def download(episode)
- dest = File.join(AUDIO_DIR, "#{episode.slug}.mp3")
- if File.exist?(dest)
- puts " already downloaded: #{episode.slug}"
- return
- end
-
- puts " downloading: #{episode.slug}"
- uri = URI(episode.audio_url)
- download_with_redirects(uri, dest)
- end
-
- def download_with_redirects(uri, dest, limit = 5)
- raise "Too many redirects" if limit == 0
-
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
- request = Net::HTTP::Get.new(uri)
- http.request(request) do |response|
- case response
- when Net::HTTPRedirection
- download_with_redirects(URI(response["location"]), dest, limit - 1)
- when Net::HTTPSuccess
- File.open(dest, "wb") do |file|
- response.read_body { |chunk| file.write(chunk) }
- end
- else
- raise "Download failed for #{uri}: #{response.code}"
- end
- end
- end
- end
- end
-end