Class: Crm::VideoPresenter
Instance Attribute Summary
#current_account, #options, #url_helper
Delegated Instance Attributes
collapse
#created_at, #title
#can?, #capture, #concat, #content_tag, #fa_icon, #link_to, #number_to_currency, #simple_format
Instance Method Summary
collapse
#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
#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_icon ⇒ Object
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_display ⇒ Object
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_icon ⇒ Object
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_display ⇒ Object
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?
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_display ⇒ Object
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_display ⇒ Object
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_display ⇒ Object
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_display ⇒ Object
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
size_mb = (size / 1024.0 / 1024.0).round(1)
"#{size_mb} MB"
end
|
#cloudflare_ready_status_display ⇒ Object
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_display ⇒ Object
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_display ⇒ Object
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_display ⇒ Object
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_display ⇒ Object
49
50
51
52
53
54
55
56
57
|
# File 'app/presenters/crm/video_presenter.rb', line 49
def cms_url_display
h.link_to(
cms_url,
cms_url,
target: '_blank',
rel: 'noopener'
)
end
|
#expanded_description_badge_class ⇒ Object
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_icon ⇒ Object
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
|
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
|
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
|
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
|
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_data ⇒ Object
Alias for Video#original_transcript_data
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_data ⇒ Object
Alias for Video#sentences_data
105
|
# File 'app/presenters/crm/video_presenter.rb', line 105
delegate :sentences_data, to: :video
|
#seo_check ⇒ Object
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
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_json ⇒ Object
Alias for Video#structured_transcript_json
99
|
# File 'app/presenters/crm/video_presenter.rb', line 99
delegate :structured_transcript_json, to: :video
|
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 structured_transcript_metadata_display
return nil unless video.structured_transcript_json.present?
metadata = video.structured_transcript_metadata
return nil if metadata.empty?
parts = []
parts << "ID: #{metadata['id']}" if metadata['id'].present?
if metadata['confidence'].present?
confidence_percentage = (metadata['confidence'] * 100).round(1)
parts << "Confidence: #{confidence_percentage}%"
end
parts << "Duration: #{format_duration(metadata['audio_duration'])}" if metadata['audio_duration'].present?
parts.join(' | ')
end
|
#structured_transcript_paragraphs ⇒ Object
Alias for Video#structured_transcript_paragraphs
107
|
# File 'app/presenters/crm/video_presenter.rb', line 107
delegate :structured_transcript_paragraphs, to: :video
|
#structured_transcript_paragraphs_count ⇒ Object
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_icon ⇒ Object
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
|
311
312
313
314
315
316
317
|
# File 'app/presenters/crm/video_presenter.rb', line 311
def
case seo_check.
when 'good' then 'badge bg-success'
when 'poor' then 'badge bg-warning'
else 'badge bg-danger'
end
end
|
246
247
248
|
# File 'app/presenters/crm/video_presenter.rb', line 246
def
seo_status_icon(seo_check.)
end
|
#transcript_badge_class ⇒ Object
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_status ⇒ Object
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
|
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_options ⇒ Object
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_icon ⇒ Object
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_data ⇒ Object
Alias for Video#vtt_original_data
101
|
# File 'app/presenters/crm/video_presenter.rb', line 101
delegate :vtt_original_data, to: :video
|
#vtt_polished_data ⇒ Object
Alias for Video#vtt_polished_data
103
|
# File 'app/presenters/crm/video_presenter.rb', line 103
delegate :vtt_polished_data, to: :video
|
#youtube_id_display ⇒ Object
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
|