Class: VideoBasePresenter
- Inherits:
-
BasePresenter
- Object
- SimpleDelegator
- BasePresenter
- VideoBasePresenter
- Defined in:
- app/presenters/video_base_presenter.rb
Direct Known Subclasses
Instance Attribute Summary
Attributes inherited from BasePresenter
#current_account, #options, #url_helper
Delegated Instance Attributes collapse
-
#created_at ⇒ Object
Alias for Video#created_at.
-
#title ⇒ Object
Alias for Video#title.
Methods inherited from BasePresenter
#can?, #capture, #concat, #content_tag, #fa_icon, #link_to, #number_to_currency, #simple_format
Instance Method Summary collapse
- #build_transcript_schema ⇒ Object
-
#cf_poster_url ⇒ Object
Common URL and thumbnail methods.
- #cloudflare_embed_url ⇒ Object
-
#cloudflare_mp4_url ⇒ Object
MP4 URL for SEO and fallback purposes.
- #cloudflare_video_url ⇒ Object
-
#format_duration(seconds) ⇒ Object
Duration formatting methods.
- #format_duration_for_display(seconds) ⇒ Object
- #format_paragraphs_as_text(paragraphs) ⇒ Object
- #format_sentences_as_text(sentences) ⇒ Object
- #format_structured_paragraphs_as_text ⇒ Object
-
#format_timestamp(milliseconds) ⇒ Object
Transcript formatting methods.
-
#has_assemblyai_transcript_id? ⇒ Boolean
Additional convenience methods for views.
- #has_audio_extraction? ⇒ Boolean
- #has_cloudflare_captions? ⇒ Boolean
- #has_html_transcript? ⇒ Boolean
-
#has_structured_transcript_json? ⇒ Boolean
Presenter convenience methods.
- #page_description ⇒ Object
-
#schema_dot_org_structure(position: nil, exclude: []) ⇒ Object
Schema.org methods.
-
#transcript_display ⇒ Object
Common transcript display method.
- #transcript_duration ⇒ Object
- #transcript_language ⇒ Object
- #video_medias_url ⇒ Object
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_schema ⇒ Object
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_url ⇒ Object
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_url ⇒ Object
154 155 156 157 158 159 |
# File 'app/presenters/video_base_presenter.rb', line 154 def 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_url ⇒ Object
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_url ⇒ Object
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_at ⇒ Object
Alias for Video#created_at
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 = sentence['start'] ? (sentence['start']) : nil text = sentence['text'] || '' if "[#{}] #{text}" else text end end.join("\n\n") end |
#format_structured_paragraphs_as_text ⇒ Object
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 (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
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
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
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
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
215 216 217 |
# File 'app/presenters/video_base_presenter.rb', line 215 def has_structured_transcript_json? video.structured_transcript_json.present? end |
#page_description ⇒ Object
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..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. = 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. = 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 |
#title ⇒ Object
Alias for Video#title
241 |
# File 'app/presenters/video_base_presenter.rb', line 241 delegate :title, :created_at, to: :video |
#transcript_display ⇒ Object
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_duration ⇒ Object
227 228 229 |
# File 'app/presenters/video_base_presenter.rb', line 227 def transcript_duration video.duration_in_seconds end |
#transcript_language ⇒ Object
231 232 233 |
# File 'app/presenters/video_base_presenter.rb', line 231 def transcript_language 'en' # Default language for now end |
#video_medias_url ⇒ Object
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 |