Class: VideoBasePresenter

Inherits:
BasePresenter show all
Defined in:
app/presenters/video_base_presenter.rb

Direct Known Subclasses

Crm::VideoPresenter, Www::VideoPresenter

Instance Attribute Summary

Attributes inherited from BasePresenter

#current_account, #options, #url_helper

Delegated Instance Attributes collapse

Methods inherited from BasePresenter

#can?, #capture, #concat, #content_tag, #fa_icon, #link_to, #number_to_currency, #simple_format

Instance Method Summary collapse

Methods inherited from BasePresenter

#h, #initialize, #present, presents, #r, #safe_present, #u

Constructor Details

This class inherits a constructor from BasePresenter

Instance Method Details

#build_transcript_schemaObject



139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/presenters/video_base_presenter.rb', line 139

def build_transcript_schema
  return nil unless video.transcript.present?

  # Prefer structured transcript JSON if available for better formatting
  if video.structured_transcript_json.present?
    # Use structured data for better formatting
    formatted_text = format_structured_paragraphs_as_text
    return formatted_text if formatted_text.present?
  end

  # Fallback to existing transcript field - parse HTML and extract plain text
  doc = Nokogiri::HTML(video.transcript)
  doc.text&.strip&.gsub(/\n\s*\n/, "\n\n") || ''
end

#cf_poster_urlObject

Common URL and thumbnail methods



162
163
164
165
166
167
168
# File 'app/presenters/video_base_presenter.rb', line 162

def cf_poster_url
  # Prefer our own poster image when available
  return video.poster_image.image_url(width: 1280, height: 720, thumbnail: true) if video.respond_to?(:poster_image) && video.poster_image.present?
  return unless video.cloudflare_uid

  "#{CF_STREAM_URL}/#{video.cloudflare_uid}/thumbnails/thumbnail.jpg"
end

#cloudflare_embed_urlObject



154
155
156
157
158
159
# File 'app/presenters/video_base_presenter.rb', line 154

def cloudflare_embed_url
  return unless video.cloudflare_uid

  poster_param = cf_poster_url ? "&poster=#{URI.encode_www_form_component(cf_poster_url)}" : ''
  "#{CF_STREAM_URL}/#{video.cloudflare_uid}/iframe#{poster_param}"
end

#cloudflare_mp4_urlObject

MP4 URL for SEO and fallback purposes



178
179
180
181
182
# File 'app/presenters/video_base_presenter.rb', line 178

def cloudflare_mp4_url
  return nil unless video.is_cloudflare?

  "https://customer-ikxw003vtz2iah2s.cloudflarestream.com/#{video.cloudflare_uid}/downloads/default.mp4"
end

#cloudflare_video_urlObject



170
171
172
173
174
175
# File 'app/presenters/video_base_presenter.rb', line 170

def cloudflare_video_url
  return nil unless video.is_cloudflare?

  # Use HLS manifest for proper streaming instead of direct video download
  "https://customer-ikxw003vtz2iah2s.cloudflarestream.com/#{video.cloudflare_uid}/manifest/video.m3u8"
end

#created_atObject

Alias for Video#created_at

Returns:

  • (Object)

    Video#created_at

See Also:



241
# File 'app/presenters/video_base_presenter.rb', line 241

delegate :title, :created_at, to: :video

#format_duration(seconds) ⇒ Object

Duration formatting methods



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'app/presenters/video_base_presenter.rb', line 185

def format_duration(seconds)
  # Convert seconds to ISO 8601 duration format (PT1M30S)
  return "PT#{seconds.to_i}S" if seconds < 60

  minutes = (seconds / 60).to_i
  remaining_seconds = (seconds % 60).to_i

  if remaining_seconds == 0
    "PT#{minutes}M"
  else
    "PT#{minutes}M#{remaining_seconds}S"
  end
end

#format_duration_for_display(seconds) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'app/presenters/video_base_presenter.rb', line 199

def format_duration_for_display(seconds)
  return '0:00' if seconds.nil? || seconds == 0

  minutes = (seconds / 60).to_i
  remaining_seconds = (seconds % 60).to_i

  if minutes >= 60
    hours = (minutes / 60).to_i
    remaining_minutes = (minutes % 60).to_i
    format('%d:%02d:%02d', hours, remaining_minutes, remaining_seconds)
  else
    format('%d:%02d', minutes, remaining_seconds)
  end
end

#format_paragraphs_as_text(paragraphs) ⇒ Object



37
38
39
40
41
42
# File 'app/presenters/video_base_presenter.rb', line 37

def format_paragraphs_as_text(paragraphs)
  paragraphs.map do |paragraph|
    text = paragraph['text'] || ''
    text
  end.join("\n\n").strip
end

#format_sentences_as_text(sentences) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/presenters/video_base_presenter.rb', line 23

def format_sentences_as_text(sentences)
  sentences.map do |sentence|
    # Include timestamp if available
    timestamp = sentence['start'] ? format_timestamp(sentence['start']) : nil
    text = sentence['text'] || ''

    if timestamp
      "[#{timestamp}] #{text}"
    else
      text
    end
  end.join("\n\n")
end

#format_structured_paragraphs_as_textObject



44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/presenters/video_base_presenter.rb', line 44

def format_structured_paragraphs_as_text
  return nil unless video.structured_transcript_json.present?

  paragraphs = video.structured_transcript_paragraphs
  return nil if paragraphs.empty?

  # Format as clean text with paragraph breaks
  formatted = paragraphs.map do |paragraph|
    paragraph['text']
  end.join("\n\n")

  formatted.strip
end

#format_timestamp(milliseconds) ⇒ Object

Transcript formatting methods



13
14
15
16
17
18
19
20
21
# File 'app/presenters/video_base_presenter.rb', line 13

def format_timestamp(milliseconds)
  return '00:00' if milliseconds.nil?

  total_seconds = milliseconds / 1000.0
  minutes = (total_seconds / 60).floor
  seconds = (total_seconds % 60).floor

  format('%02d:%02d', minutes, seconds)
end

#has_assemblyai_transcript_id?Boolean

Additional convenience methods for views

Returns:

  • (Boolean)


236
237
238
# File 'app/presenters/video_base_presenter.rb', line 236

def has_assemblyai_transcript_id?
  video.assemblyai_transcript_id.present?
end

#has_audio_extraction?Boolean

Returns:

  • (Boolean)


219
220
221
# File 'app/presenters/video_base_presenter.rb', line 219

def has_audio_extraction?
  video.audio_extraction_upload.present?
end

#has_cloudflare_captions?Boolean

Returns:

  • (Boolean)


223
224
225
# File 'app/presenters/video_base_presenter.rb', line 223

def has_cloudflare_captions?
  video.cloudflare_captions_status.present?
end

#has_html_transcript?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'app/presenters/video_base_presenter.rb', line 58

def has_html_transcript?
  video.transcript.present? && video.transcript.include?("\n\n") && !video.transcript.include?('<')
end

#has_structured_transcript_json?Boolean

Presenter convenience methods

Returns:

  • (Boolean)


215
216
217
# File 'app/presenters/video_base_presenter.rb', line 215

def has_structured_transcript_json?
  video.structured_transcript_json.present?
end

#page_descriptionObject



243
244
245
246
# File 'app/presenters/video_base_presenter.rb', line 243

def page_description
  # Allow subclasses to override this method for localization
  video.meta_description.presence || video.title
end

#schema_dot_org_structure(position: nil, exclude: []) ⇒ Object

Schema.org methods



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'app/presenters/video_base_presenter.rb', line 86

def schema_dot_org_structure(position: nil, exclude: [])
  ssv = SchemaDotOrg::VideoObject.new

  # Set required attributes (unless excluded)
  ssv.name = title unless exclude.include?(:name)
  ssv.description = page_description unless exclude.include?(:description)
  ssv.thumbnailUrl = thumbnail_url(width: 1280, height: 720) unless exclude.include?(:thumbnailUrl)
  ssv.uploadDate = created_at unless exclude.include?(:uploadDate)
  ssv.contentUrl = cloudflare_mp4_url unless exclude.include?(:contentUrl)
  ssv.embedUrl = cloudflare_embed_url unless exclude.include?(:embedUrl)
  ssv.url = path if respond_to?(:path) && !exclude.include?(:url)
  ssv.id_iri = id_iri if respond_to?(:id_iri)

  # Add missing properties from old inline markup
  ssv.requiresSubscription = false unless exclude.include?(:requiresSubscription)
  ssv.identifier = video.cloudflare_uid unless exclude.include?(:identifier)

  # Create author organization (unless excluded)
  ssv.author = Www::SeoHelper.online_store_schema unless exclude.include?(:author)
  ssv.publisher = Www::SeoHelper.online_store_schema unless exclude.include?(:publisher)

  # Create thumbnail image object (unless excluded)
  # Deprecated explicit nested ImageObject for VideoObject per Google guidelines; keep thumbnailUrl only

  ssv.playerType = 'HTML5' unless exclude.include?(:playerType)
  ssv.width = 1280 unless exclude.include?(:width)
  ssv.height = 720 unless exclude.include?(:height)
  ssv.isFamilyFriendly = true unless exclude.include?(:isFamilyFriendly)
  # ssv.regionsAllowed = 'US,CA'
  ssv.datePublished = created_at unless exclude.include?(:datePublished)
  ssv.genre = 'Home Improvement' unless exclude.include?(:genre)

  # Set optional attributes only if they have values (unless excluded)
  ssv.duration = ActiveSupport::Duration.build(video.duration_in_seconds.to_i).iso8601 if video.duration_in_seconds.to_i > 0 && !exclude.include?(:duration)

  # Set transcript (unless excluded)
  unless exclude.include?(:transcript)
    transcript_text = build_transcript_schema
    ssv.transcript = transcript_text if transcript_text.present?
  end

  # If position is provided, wrap in ItemListElement structure
  if position
    {
      '@type' => 'ListItem',
      'position' => position,
      'item' => ssv.to_json_struct
    }
  else
    ssv
  end
end

#titleObject

Alias for Video#title

Returns:

  • (Object)

    Video#title

See Also:



241
# File 'app/presenters/video_base_presenter.rb', line 241

delegate :title, :created_at, to: :video

#transcript_displayObject

Common transcript display method



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/presenters/video_base_presenter.rb', line 63

def transcript_display
  return 'No transcript available' unless video.transcript.present?

  # Prefer structured transcript JSON if available
  if video.structured_transcript_json.present?
    # Use structured data for better formatting
    formatted_text = format_structured_paragraphs_as_text
    return h.simple_format(formatted_text) if formatted_text.present?
  end

  # Fallback to existing transcript field
  if has_html_transcript?
    # Display paragraphs with timestamps
    video.transcript.split("\n\n").map do |paragraph|
      h.simple_format(paragraph)
    end.join.html_safe
  else
    # Fallback to plain text
    h.simple_format(video.transcript)
  end
end

#transcript_durationObject



227
228
229
# File 'app/presenters/video_base_presenter.rb', line 227

def transcript_duration
  video.duration_in_seconds
end

#transcript_languageObject



231
232
233
# File 'app/presenters/video_base_presenter.rb', line 231

def transcript_language
  'en' # Default language for now
end

#video_medias_urlObject



248
249
250
251
# File 'app/presenters/video_base_presenter.rb', line 248

def video_medias_url
  # Default URL for video media index - subclasses can override for localization
  "#{WEB_HOSTNAME}/video-media"
end