Class: VideoChapter
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- VideoChapter
- Defined in:
- app/models/video_chapter.rb
Overview
A single YouTube-style chapter marker for a Video. Persisted locally so we
can edit and re-push to the YouTube description without re-running AI
generation.
Constant Summary
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
- #start_ms ⇒ void readonly
- #title ⇒ void readonly
Belongs to collapse
Class Method Summary collapse
-
.format_ms_to_hms(ms) ⇒ String?
Format a millisecond offset for display.
-
.parse_timestamp_to_ms(value) ⇒ Integer?
Parse a user-entered timestamp into milliseconds.
Instance Method Summary collapse
-
#start_within_video_duration ⇒ void
The authoritative out-of-bounds guard: a chapter must start strictly before the video ends.
-
#timestamp_hms ⇒ String?
Hours-minutes-seconds string (zero-padded MM/SS), used by the CRM form.
-
#timestamp_hms=(value) ⇒ Integer?
Setter paired with #timestamp_hms for
accepts_nested_attributes_forforms.
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#start_ms ⇒ void (readonly)
This method returns an undefined value.
17 |
# File 'app/models/video_chapter.rb', line 17 validates :start_ms, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } |
#title ⇒ void (readonly)
This method returns an undefined value.
15 |
# File 'app/models/video_chapter.rb', line 15 validates :title, presence: true, length: { maximum: 100 } |
Class Method Details
.format_ms_to_hms(ms) ⇒ String?
Format a millisecond offset for display. Drops the hour part when zero.
76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'app/models/video_chapter.rb', line 76 def self.format_ms_to_hms(ms) return nil if ms.nil? total_seconds = ms.to_i / 1000 hours = total_seconds / 3600 minutes = (total_seconds % 3600) / 60 seconds = total_seconds % 60 if hours.positive? format('%<h>d:%<m>02d:%<s>02d', h: hours, m: minutes, s: seconds) else format('%<m>d:%<s>02d', m: minutes, s: seconds) end end |
.parse_timestamp_to_ms(value) ⇒ Integer?
Parse a user-entered timestamp into milliseconds.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'app/models/video_chapter.rb', line 56 def self.(value) return nil if value.nil? s = value.to_s.strip return nil if s.empty? parts = s.split(':') return nil unless parts.all? { |p| p.match?(/\A\d+\z/) } nums = parts.map(&:to_i) case nums.length when 1 then nums[0] * 1000 when 2 then ((nums[0] * 60) + nums[1]) * 1000 when 3 then ((nums[0] * 3600) + (nums[1] * 60) + nums[2]) * 1000 end end |
Instance Method Details
#start_within_video_duration ⇒ void
This method returns an undefined value.
The authoritative out-of-bounds guard: a chapter must start strictly before
the video ends. A marker at or past the duration is invalid both as a
YouTube chapter and as a VideoObject Clip (startOffset must be < duration),
and it strands the final Clip without an endOffset. Enforced at the model
so no edit path — CRM form, AI generation, YouTube pull, console — can
persist one. Skipped when the duration is unknown (nothing to bound against);
the generation-time clamp and the presenter still defend the render side.
29 30 31 32 33 34 35 36 37 |
# File 'app/models/video_chapter.rb', line 29 def start_within_video_duration return if start_ms.blank? duration_ms = video&.duration_ms || 0 return unless duration_ms.positive? return if start_ms < duration_ms errors.add(:start_ms, "must be before the video ends (#{self.class.format_ms_to_hms(duration_ms)})") end |
#timestamp_hms ⇒ String?
Hours-minutes-seconds string (zero-padded MM/SS), used by the CRM form.
41 42 43 |
# File 'app/models/video_chapter.rb', line 41 def self.class.format_ms_to_hms(start_ms) end |
#timestamp_hms=(value) ⇒ Integer?
Setter paired with #timestamp_hms for accepts_nested_attributes_for forms.
Accepts "H:MM:SS", "M:SS", or a bare integer seconds string. Blank clears.
49 50 51 |
# File 'app/models/video_chapter.rb', line 49 def (value) self.start_ms = self.class.(value) end |
#video ⇒ Video
10 |
# File 'app/models/video_chapter.rb', line 10 belongs_to :video, foreign_key: :digital_asset_id, class_name: 'Video', inverse_of: :video_chapters |