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
change pnxnssktmwtovkpvtksuqypkpyqzpzzt
commit ac432d7e32ad7a22117b88cd34ff274736551b25
author Alpha Chen <alpha@kejadlen.dev>
date
parent ysrqpxpl
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