Class: VideoChapterGenerationWorker
- Inherits:
-
Object
- Object
- VideoChapterGenerationWorker
- Includes:
- Sidekiq::Job, Workers::StatusBroadcastable
- Defined in:
- app/workers/video_chapter_generation_worker.rb
Overview
Generates video chapter markers from the AssemblyAI transcript and persists
them as VideoChapter rows on the Video. Chapters are hosted-first: they feed
the on-site VideoObject "key moments" schema (VideoBasePresenter#chapter_clips)
and are independently pushable to YouTube from the CRM via
YouTube::ChapterService#push_chapters when the video is linked. Generation
needs only a structured transcript — it does NOT require a YouTube link.
Reports progress through the standard job-status page (Sidekiq::Status via
Workers::StatusBroadcastable); pass a redirect_path to send the user back
there when the job finishes. Headless callers (the transcription webhook
chain) omit it.
Instance Attribute Summary
Attributes included from Workers::StatusBroadcastable
Class Method Summary collapse
-
.enqueue_guarded(video, redirect_path: nil) ⇒ String?
The one guarded enqueue path — used by the CRM action, the transcription worker (step 7) and the AssemblyAI webhook chain, so no caller can queue a second run while one is queued/processing (the row lock serializes concurrent checks).
Instance Method Summary collapse
-
#perform(video_id, redirect_path = nil) ⇒ Object
Runs the job.
Methods included from Workers::StatusBroadcastable::Overrides
Class Method Details
.enqueue_guarded(video, redirect_path: nil) ⇒ String?
The one guarded enqueue path — used by the CRM action, the transcription
worker (step 7) and the AssemblyAI webhook chain, so no caller can queue a
second run while one is queued/processing (the row lock serializes
concurrent checks). Marks the video queued and enqueues.
29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'app/workers/video_chapter_generation_worker.rb', line 29 def self.enqueue_guarded(video, redirect_path: nil) queued = false video.with_lock do unless video.youtube_chapters_generation_in_progress? video.update!(youtube_chapters_generation_status: 'queued', youtube_chapters_generation_error: nil) queued = true end end return nil unless queued redirect_path.present? ? perform_async(video.id, redirect_path) : perform_async(video.id) end |
Instance Method Details
#perform(video_id, redirect_path = nil) ⇒ Object
Runs the job.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'app/workers/video_chapter_generation_worker.rb', line 47 def perform(video_id, redirect_path = nil) total(100) at(5, 'Starting chapter generation...') video = Video.find_by(id: video_id) unless video Rails.logger.warn("[VideoChapterGenerationWorker] Video #{video_id} not found, skipping") store status: 'failed' store error_message: "Video #{video_id} not found." return end unless video.has_structured_transcript_json? && video.structured_transcript_paragraphs.present? Rails.logger.info("[VideoChapterGenerationWorker] Video #{video_id} has no structured transcript, skipping") fail_job(video, 'Video has no transcript paragraphs.', redirect_path) return end video.update!( youtube_chapters_generation_status: 'processing', youtube_chapters_generation_error: nil ) Rails.logger.info("[VideoChapterGenerationWorker] Generating chapters for video #{video_id}") at(20, 'Generating chapters from the transcript with AssemblyAI...') chapter_service = YouTube::ChapterService.new chapters = chapter_service.preview_chapters(video) if chapters.empty? fail_job( video, chapter_service..presence || 'No chapters could be generated. The transcript may be too short, or paragraph timings may be missing — re-run transcript polish, then try again.', redirect_path ) Rails.logger.warn("[VideoChapterGenerationWorker] No chapters for video #{video_id}") return end at(80, 'Saving chapters...') chapter_service.replace_chapters!(video, chapters) video.update!( youtube_chapters_generation_status: 'complete', youtube_chapters_generation_error: nil ) Rails.logger.info("[VideoChapterGenerationWorker] Saved #{chapters.length} chapters for video #{video_id}") store status: 'completed' at(100, "Saved #{chapters.length} chapters. Review and adjust them below.") store redirect_to: redirect_path if redirect_path.present? rescue StandardError => e v = Video.find_by(id: video_id) mark_failed(v, e.) if v store error_message: "Chapter generation failed: #{e.}" store redirect_to: redirect_path if redirect_path.present? Rails.logger.error("[VideoChapterGenerationWorker] Error for video #{video_id}: #{e.}") raise end |