Class: Crm::VideoPresenter

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

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, #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, #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.



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

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

Instance Method Details

#assemblyai_transcript_status_iconObject



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

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

#cloudflare_audio_download_displayObject



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

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



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

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



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

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.map { |m| m[: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



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

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



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

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



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

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



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

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



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

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



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

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



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

def cloudflare_uid_display
  return unless video.cloudflare_uid.present?

  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



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

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

#cms_url_displayObject



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

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



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

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



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

def expanded_description_status_icon
  seo_status_icon(seo_check.expanded_description_quality_status)
end

#meta_description_badge_classObject



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

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



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

def meta_description_status_icon
  seo_status_icon(seo_check.meta_description_quality_status)
end

#meta_title_badge_classObject



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

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



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

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:



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

delegate :original_transcript_data, to: :video


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

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:



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

delegate :sentences_data, to: :video

#seo_checkObject

SEO analysis helper



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

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

#seo_status_icon(status) ⇒ Object

SEO status icon helpers



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

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:



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

delegate :structured_transcript_json, to: :video

#structured_transcript_metadata_displayObject

Structured transcript methods



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

def 
  return nil unless video.structured_transcript_json.present?

   = 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:



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

delegate :structured_transcript_paragraphs, to: :video

#structured_transcript_paragraphs_countObject



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

def structured_transcript_paragraphs_count
  return nil unless video.structured_transcript_json.present?

  video.structured_transcript_paragraphs.length
end

#structured_transcript_status_iconObject



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

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

#sub_header_badge_classObject



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

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



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

def sub_header_status_icon
  seo_status_icon(seo_check.sub_header_quality_status)
end

#transcript_badge_classObject

Badge helper methods



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

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



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

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

  'good'
end

#transcript_tooltipObject



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

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



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

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



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

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:



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

delegate :vtt_original_data, to: :video

#vtt_polished_dataObject

Alias for Video#vtt_polished_data

Returns:

  • (Object)

    Video#vtt_polished_data

See Also:



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

delegate :vtt_polished_data, to: :video

#youtube_id_displayObject



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

def youtube_id_display
  return unless video.youtube_id.present?

  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