Class: Crm::VideoPresenter

Inherits:
VideoBasePresenter show all
Includes:
ActionView::Helpers::TextHelper
Defined in:
app/presenters/crm/video_presenter.rb

Overview

Presenter: video presenter.

Instance Attribute Summary

Attributes inherited from BasePresenter

#current_account, #options, #url_helper

Delegated Instance Attributes collapse

Methods inherited from VideoBasePresenter

#created_at, #title

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 VideoBasePresenter

#build_transcript_schema, #cf_poster_url, #chapter_clips, #cloudflare_embed_url, #cloudflare_mp4_url, #cloudflare_video_url, #format_duration, #format_duration_for_display, #format_paragraphs_as_text, #format_sentences_as_text, #format_structured_paragraphs_as_text, #format_timestamp, #has_assemblyai_transcript_id?, #has_audio_extraction?, #has_cloudflare_captions?, #has_html_transcript?, #has_structured_transcript_json?, #page_description, #published_on, #schema_dot_org_structure, #transcript_display, #transcript_duration, #transcript_language, #video_medias_url

Methods inherited from BasePresenter

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

Constructor Details

#initialize(model, view = nil, options = nil) ⇒ VideoPresenter

Returns a new instance of VideoPresenter.



7
8
9
10
# File 'app/presenters/crm/video_presenter.rb', line 7

def initialize(model, view = nil, options = nil)
  @url_helper = Crm::BasePresenter::Router.new
  super
end

Instance Method Details

#assemblyai_transcript_status_iconObject



260
261
262
# File 'app/presenters/crm/video_presenter.rb', line 260

def assemblyai_transcript_status_icon
  video.has_assemblyai_transcript_id? ? seo_status_icon('good') : seo_status_icon('missing')
end

#cloudflare_audio_download_displayObject



212
213
214
215
216
217
218
219
220
# File 'app/presenters/crm/video_presenter.rb', line 212

def cloudflare_audio_download_display
  if video.has_audio_download?
    status = video.cloudflare_download_status('audio')
    status_text = status ? " (#{status})" : ''
    "✅ Enabled#{status_text}"
  else
    '❌ Not enabled'
  end
end

#cloudflare_audio_download_status_iconObject

General health check icon helpers



256
257
258
# File 'app/presenters/crm/video_presenter.rb', line 256

def cloudflare_audio_download_status_icon
  video.audio_download_ready? ? seo_status_icon('good') : seo_status_icon('missing')
end

#cloudflare_captions_status_displayObject

Cloudflare-specific methods



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
138
139
140
# File 'app/presenters/crm/video_presenter.rb', line 113

def cloudflare_captions_status_display
  return 'Not available' unless video.is_cloudflare?

  # Check for missing captions
  missing = video.missing_cloudflare_captions
  missing_text = if missing.any?
                   " | #{h.tag.span("⚠️ Missing: #{missing.pluck(:name).join(', ')}", class: 'text-warning')}"
                 else
                   ''
                 end

  if video.cloudflare_captions_status.present?
    status = video.cloudflare_captions_status
    if status
      parts = []
      parts << '✅ Available'
      parts << "Language: #{status['language']}" if status['language']
      parts << "Label: #{status['label']}" if status['label']
      parts << "Status: #{status['status']}" if status['status']
      parts << "Generated: #{status['generated'] ? 'Yes' : 'No'}" if status.key?('generated')
      "#{parts.join(' | ')}#{missing_text}".html_safe
    else
      "✅ Available#{missing_text}".html_safe
    end
  else
    '❌ Not uploaded'
  end
end

#cloudflare_default_download_displayObject



202
203
204
205
206
207
208
209
210
# File 'app/presenters/crm/video_presenter.rb', line 202

def cloudflare_default_download_display
  if video.has_default_download?
    status = video.cloudflare_download_status('default')
    status_text = status ? " (#{status})" : ''
    "✅ Enabled#{status_text}"
  else
    '❌ Not enabled'
  end
end

#cloudflare_dimensions_displayObject



166
167
168
169
170
171
172
173
# File 'app/presenters/crm/video_presenter.rb', line 166

def cloudflare_dimensions_display
  return 'Not available' unless video.is_cloudflare?

  dimensions = video.cloudflare_input_dimensions
  return 'Not available' unless dimensions

  "#{dimensions[:width]} × #{dimensions[:height]}"
end

#cloudflare_duration_displayObject



185
186
187
188
189
190
191
192
# File 'app/presenters/crm/video_presenter.rb', line 185

def cloudflare_duration_display
  return 'Not available' unless video.is_cloudflare?

  duration = video.cloudflare_duration
  return 'Not available' unless duration

  format_duration(duration)
end

#cloudflare_file_size_displayObject



155
156
157
158
159
160
161
162
163
164
# File 'app/presenters/crm/video_presenter.rb', line 155

def cloudflare_file_size_display
  return 'Not available' unless video.is_cloudflare?

  size = video.cloudflare_file_size
  return 'Not available' unless size

  # Convert bytes to MB
  size_mb = (size / 1024.0 / 1024.0).round(1)
  "#{size_mb} MB"
end

#cloudflare_ready_status_displayObject



175
176
177
178
179
180
181
182
183
# File 'app/presenters/crm/video_presenter.rb', line 175

def cloudflare_ready_status_display
  return 'Not available' unless video.is_cloudflare?

  if video.cloudflare_ready_to_stream?
    '✅ Ready to stream'
  else
    '⏳ Processing'
  end
end

#cloudflare_status_displayObject



142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/presenters/crm/video_presenter.rb', line 142

def cloudflare_status_display
  return 'Not available' unless video.is_cloudflare?

  status = video.cloudflare_status
  return 'Not available' unless status

  parts = []
  parts << "State: #{status['state']}" if status['state']
  parts << "#{status['pctComplete']}% complete" if status['pctComplete']
  parts << status['errorReasonText'] if status['errorReasonText']
  parts.join(' | ')
end

#cloudflare_uid_displayObject



28
29
30
31
32
33
34
35
36
37
# File 'app/presenters/crm/video_presenter.rb', line 28

def cloudflare_uid_display
  return if video.cloudflare_uid.blank?

  h.link_to_unless(
    video.cloudflare_uid.blank?,
    h.fa_icon('arrow-up-right-from-square', text: video.cloudflare_uid),
    "https://dash.cloudflare.com/79b7f58cf035093b5ad11747df30369a/stream/videos/#{video.cloudflare_uid}",
    target: '_blank', rel: 'noopener'
  )
end

#cloudflare_vtt_upload_status_displayObject



194
195
196
197
198
199
200
# File 'app/presenters/crm/video_presenter.rb', line 194

def cloudflare_vtt_upload_status_display
  if video.has_polished_vtt?
    '✅ Available (generated from transcript)'
  else
    '❌ Not available'
  end
end

#cms_url_displayObject



50
51
52
53
54
55
56
57
58
# File 'app/presenters/crm/video_presenter.rb', line 50

def cms_url_display
  # Use WWW router to generate the public website URL
  h.link_to(
    cms_url,
    cms_url,
    target: '_blank',
    rel: 'noopener'
  )
end

#expanded_description_badge_classObject



320
321
322
323
324
325
326
# File 'app/presenters/crm/video_presenter.rb', line 320

def expanded_description_badge_class
  case seo_check.expanded_description_quality_status
  when 'good' then 'badge bg-success'
  when 'poor' then 'badge bg-warning'
  else 'badge bg-danger'
  end
end

#expanded_description_status_iconObject



251
252
253
# File 'app/presenters/crm/video_presenter.rb', line 251

def expanded_description_status_icon
  seo_status_icon(seo_check.expanded_description_quality_status)
end

#meta_description_badge_classObject



304
305
306
307
308
309
310
# File 'app/presenters/crm/video_presenter.rb', line 304

def meta_description_badge_class
  case seo_check.meta_description_quality_status
  when 'good' then 'badge bg-success'
  when 'poor' then 'badge bg-warning'
  else 'badge bg-danger'
  end
end

#meta_description_status_iconObject



243
244
245
# File 'app/presenters/crm/video_presenter.rb', line 243

def meta_description_status_icon
  seo_status_icon(seo_check.meta_description_quality_status)
end

#meta_title_badge_classObject



296
297
298
299
300
301
302
# File 'app/presenters/crm/video_presenter.rb', line 296

def meta_title_badge_class
  case seo_check.meta_title_quality_status
  when 'good' then 'badge bg-success'
  when 'poor' then 'badge bg-warning'
  else 'badge bg-danger'
  end
end

#meta_title_status_iconObject



239
240
241
# File 'app/presenters/crm/video_presenter.rb', line 239

def meta_title_status_icon
  seo_status_icon(seo_check.meta_title_quality_status)
end

#original_transcript_dataObject

Alias for Video#original_transcript_data

Returns:

  • (Object)

    Video#original_transcript_data

See Also:



110
# File 'app/presenters/crm/video_presenter.rb', line 110

delegate :original_transcript_data, to: :video


60
61
62
63
64
65
66
67
# File 'app/presenters/crm/video_presenter.rb', line 60

def related_sources
  Source.where(
    'name LIKE ? OR name LIKE ? OR description LIKE ?',
    video.title.present? ? "%#{video.title}%" : nil,
    video.youtube_title.present? ? "%#{video.youtube_title}%" : nil,
    video.youtube_id.present? ? "%#{video.youtube_id}%" : nil
  )
end

#sentences_dataObject

Alias for Video#sentences_data

Returns:

  • (Object)

    Video#sentences_data

See Also:



106
# File 'app/presenters/crm/video_presenter.rb', line 106

delegate :sentences_data, to: :video

#seo_checkObject

SEO analysis helper



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

def seo_check
  @seo_check ||= VideoSeoCheckService.new(video)
end

#seo_status_icon(status) ⇒ Object

SEO status icon helpers



228
229
230
231
232
233
234
235
236
237
# File 'app/presenters/crm/video_presenter.rb', line 228

def seo_status_icon(status)
  case status
  when 'good'
    h.fa_icon('check-circle', class: 'text-success')
  when 'poor'
    h.fa_icon('exclamation-triangle', class: 'text-warning')
  else
    h.fa_icon('ban', class: 'text-danger')
  end
end

#structured_transcript_jsonObject

Alias for Video#structured_transcript_json

Returns:

  • (Object)

    Video#structured_transcript_json

See Also:



100
# File 'app/presenters/crm/video_presenter.rb', line 100

delegate :structured_transcript_json, to: :video

#structured_transcript_metadata_displayObject

Structured transcript methods



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'app/presenters/crm/video_presenter.rb', line 71

def 
  return nil if video.structured_transcript_json.blank?

   = video.
  return nil if .empty?

  parts = []

  # Add ID if available
  parts << "ID: #{['id']}" if ['id'].present?

  # Add confidence if available
  if ['confidence'].present?
    confidence_percentage = (['confidence'] * 100).round(1)
    parts << "Confidence: #{confidence_percentage}%"
  end

  # Add duration if available
  parts << "Duration: #{format_duration(['audio_duration'])}" if ['audio_duration'].present?

  parts.join(' | ')
end

#structured_transcript_paragraphsObject

Alias for Video#structured_transcript_paragraphs

Returns:

  • (Object)

    Video#structured_transcript_paragraphs

See Also:



108
# File 'app/presenters/crm/video_presenter.rb', line 108

delegate :structured_transcript_paragraphs, to: :video

#structured_transcript_paragraphs_countObject



94
95
96
97
98
# File 'app/presenters/crm/video_presenter.rb', line 94

def structured_transcript_paragraphs_count
  return nil if video.structured_transcript_json.blank?

  video.structured_transcript_paragraphs.length
end

#structured_transcript_status_iconObject



264
265
266
# File 'app/presenters/crm/video_presenter.rb', line 264

def structured_transcript_status_icon
  video.has_structured_transcript_json? ? seo_status_icon('good') : seo_status_icon('missing')
end

#sub_header_badge_classObject



312
313
314
315
316
317
318
# File 'app/presenters/crm/video_presenter.rb', line 312

def sub_header_badge_class
  case seo_check.sub_header_quality_status
  when 'good' then 'badge bg-success'
  when 'poor' then 'badge bg-warning'
  else 'badge bg-danger'
  end
end

#sub_header_status_iconObject



247
248
249
# File 'app/presenters/crm/video_presenter.rb', line 247

def sub_header_status_icon
  seo_status_icon(seo_check.sub_header_quality_status)
end

#transcript_badge_classObject

Badge helper methods



273
274
275
276
277
278
279
280
281
# File 'app/presenters/crm/video_presenter.rb', line 273

def transcript_badge_class
  return 'badge bg-secondary opacity-75' if video.video_has_no_spoken_words?

  case transcript_quality_status
  when 'good' then 'badge bg-success'
  when 'poor' then 'badge bg-warning'
  else 'badge bg-danger'
  end
end

#transcript_quality_statusObject



328
329
330
331
332
333
# File 'app/presenters/crm/video_presenter.rb', line 328

def transcript_quality_status
  return 'missing' if video.transcript.blank?
  return 'poor' if video.transcript.length < 100

  'good'
end

#transcript_tooltipObject



283
284
285
286
287
288
289
290
291
292
293
294
# File 'app/presenters/crm/video_presenter.rb', line 283

def transcript_tooltip
  return 'No spoken words; transcript not required' if video.video_has_no_spoken_words?

  case transcript_quality_status
  when 'good'
    'Transcript is present and has sufficient content (>100 characters)'
  when 'poor'
    'Transcript is present but too short (<100 characters)'
  else
    'Transcript is missing'
  end
end

#video_tab_optionsObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'app/presenters/crm/video_presenter.rb', line 12

def video_tab_options
  tabs = {}
  tabs[:video_preview] = { partial: '/crm/videos/video_preview', locals: { vp: self } }
  tabs[:general] = { partial: '/crm/videos/general', locals: { vp: self } }
  tabs[:web] = { partial: '/crm/videos/web', locals: { vp: self } }
  tabs[:youtube] = { partial: '/crm/videos/youtube', locals: { vp: self } }
  tabs[:youtube_chapters] = { partial: '/crm/videos/youtube_chapters', locals: { vp: self } }
  tabs[:transcript] = { partial: '/crm/videos/transcript', locals: { vp: self } }
  tabs[:schema] = { partial: '/crm/videos/schema', locals: { vp: self } }
  tabs[:cloudflare] = { partial: '/crm/videos/cloudflare', locals: { vp: self } } if video.is_cloudflare?
  tabs[:attachments] = { partial: '/crm/videos/attachments', locals: { vp: self } }
  tabs[:associations] = { partial: '/crm/videos/associations', locals: { vp: self } }
  tabs[:related] = { partial: '/crm/videos/related', locals: { vp: self } }
  tabs
end

#vtt_captions_status_iconObject



268
269
270
# File 'app/presenters/crm/video_presenter.rb', line 268

def vtt_captions_status_icon
  video.has_polished_vtt? ? seo_status_icon('good') : seo_status_icon('missing')
end

#vtt_original_dataObject

Alias for Video#vtt_original_data

Returns:

  • (Object)

    Video#vtt_original_data

See Also:



102
# File 'app/presenters/crm/video_presenter.rb', line 102

delegate :vtt_original_data, to: :video

#vtt_polished_dataObject

Alias for Video#vtt_polished_data

Returns:

  • (Object)

    Video#vtt_polished_data

See Also:



104
# File 'app/presenters/crm/video_presenter.rb', line 104

delegate :vtt_polished_data, to: :video

#youtube_id_displayObject



39
40
41
42
43
44
45
46
47
48
# File 'app/presenters/crm/video_presenter.rb', line 39

def youtube_id_display
  return if video.youtube_id.blank?

  h.link_to_unless(
    video.youtube_id.blank?,
    h.fa_icon('arrow-up-right-from-square', text: video.youtube_id),
    "https://www.youtube.com/watch?v=#{video.youtube_id}",
    target: '_blank', rel: 'noopener'
  )
end