Class: Jekyll::PodlovePlayerTag
- Inherits:
-
Liquid::Tag
- Object
- Liquid::Tag
- Jekyll::PodlovePlayerTag
show all
- Includes:
- OctopodFilters
- Defined in:
- lib/jekyll/podlove_player_tag.rb
Constant Summary
collapse
- SHARE_CHANNELS =
Every channel the player itself supports (per the config docs below) - sent unconditionally
rather than exposed as a site/post setting, so the share tab always has something to offer
instead of defaulting to the player's own empty channel list.
%w[twitter facebook whats-app linkedin pinterest xing mail link].freeze
OctopodFilters::JSON_ENTITIES
Instance Method Summary
collapse
#audio, #audio_type, #cdata_escape, #download_url_with_fallback, #episode_feeds, #episode_feeds_html, #episode_feeds_rss, #expand_urls, #fediverse_handle, #file_size, #host_from_url, #image_with_fallback, #in_megabytes, #j, #mime_type, #navbar_item, #navigation_list, #navigation_list_item, #otherwise, #remove_script_and_audio, #sha1, #size_by_format, #slug, #split_chapter, #string_of_duration, #string_of_size, #time_to_rssschema, #version_string
Instance Method Details
#ms_from_vtt_timestamp(timestamp) ⇒ Object
111
112
113
114
115
116
117
118
|
# File 'lib/jekyll/podlove_player_tag.rb', line 111
def ms_from_vtt_timestamp(timestamp)
parts = timestamp.split(':')
seconds, millis = parts.pop.split('.')
minutes = parts.pop.to_i
hours = parts.empty? ? 0 : parts.pop.to_i
(((hours * 60) + minutes) * 60 + seconds.to_i) * 1000 + millis.to_i
end
|
#parse_vtt(content) ⇒ Object
Parses WebVTT cues, including Auphonic's "text" voice-tag convention. Not a
full WebVTT implementation (no styling, regions, or nested tags) - just enough to turn
timed, optionally speaker-tagged captions into Podlove's transcript-cue shape.
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
# File 'lib/jekyll/podlove_player_tag.rb', line 85
def parse_vtt(content)
content = content.sub(/\A\xEF\xBB\xBF/, '').gsub("\r\n", "\n")
content.split(/\n\n+/).filter_map do |block|
lines = block.strip.split("\n")
next nil if lines.empty?
lines.shift while lines.first && !lines.first.include?('-->')
timing = lines.shift
next nil unless timing
match = timing.match(/((?:\d{2}:)?\d{2}:\d{2}\.\d{3})\s*-->\s*((?:\d{2}:)?\d{2}:\d{2}\.\d{3})/)
next nil unless match
text = lines.join(' ').strip
voice = text[/\A<v\s+([^>]+)>/, 1]
text = text.sub(/\A<v\s+[^>]+>/, '').sub(%r{</v>\z}, '').strip
{ start: match[1], start_ms: ms_from_vtt_timestamp(match[1]),
end: match[2], end_ms: ms_from_vtt_timestamp(match[2]),
speaker: nil, voice: voice, text: text }
end
end
|
#playerbaseconfig(context) ⇒ Object
120
121
122
123
|
# File 'lib/jekyll/podlove_player_tag.rb', line 120
def playerbaseconfig(context)
config = context.registers[:site].config
{ version: 5, base: "#{config["url"]}/assets/podlove-player/" }.to_json
end
|
#playerconfig(context) ⇒ Object
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
# File 'lib/jekyll/podlove_player_tag.rb', line 13
def playerconfig(context)
config = context.registers[:site].config
page = context.registers[:page]
download_url = download_url_with_fallback(config)
audio = (page["audio"] || {}).map do |format, filename|
size = if page["filesize"] && page["filesize"][format]
size_by_format(page, format)
else
file_size(filename)
end
{ url: download_url + "/" + filename,
size: size,
mimeType: mime_type(format),
title: format }
end
title = page["subtitle"] ? "#{page["title"]} - #{page["subtitle"]}" : page["title"]
{ version: 5,
title: title,
subtitle: page["subtitle"],
summary: page["summary"],
poster: config['url'] + "/assets/img/" + (page["image"] || "logo-360x360.png"),
link: config['url'] + page["url"],
publicationDate: page["date"].respond_to?(:xmlschema) ? page["date"].xmlschema : page["date"].to_s,
duration: page["duration"],
audio: audio,
chapters: page["chapters"] ? page["chapters"].map { |chapter| split_chapter(chapter) }.compact : nil,
transcripts: transcripts_for(page),
share: { channels: SHARE_CHANNELS }
}.to_json
end
|
#render(context) ⇒ Object
125
126
127
128
129
130
131
132
133
134
135
136
137
|
# File 'lib/jekyll/podlove_player_tag.rb', line 125
def render(context)
page = context.registers[:page]
return unless page["audio"]
config = context.registers[:site].config
id = "podlove-player-#{page['id'] ? sha1(page['id'], 8) : 'embed'}"
return "<div id=\"\#{id}\"></div>\n<script src=\"\#{config[\"url\"]}/assets/podlove-player/embed.js\"></script>\n<script>\n window.podlovePlayer('#\#{id}', \#{playerconfig(context)}, \#{playerbaseconfig(context)});\n</script>\n"
end
|
#transcripts_for(page) ⇒ Object
Finds a WebVTT transcript for this episode and parses it into the cue list Podlove Web
Player's "transcripts" config field expects (see the same docs link above the config
method: a plain list of { start, start_ms, end, end_ms, speaker, voice, text } cues - the
player has no native understanding of WebVTT itself, so this is doing that conversion, not
just passing the file through). Looks for an explicit page filename first,
then falls back to the audio file's own basename with a .vtt extension (i.e.
"episode1.mp3" -> "episode1.vtt"), resolved on disk the same way file_size() locates audio
files: relative to the site root if the filename already contains a "/", otherwise under
episodes/. Returns nil if there's no matching, parseable file, so the config field is
simply omitted rather than sent empty.
63
64
65
66
67
68
69
70
71
72
|
# File 'lib/jekyll/podlove_player_tag.rb', line 63
def transcripts_for(page)
filename = page["transcript"] || vtt_sibling_of(page["audio"])
return nil unless filename
path = filename =~ /\// ? filename : File.join('episodes', filename)
return nil unless File.exist?(path)
cues = parse_vtt(File.read(path, encoding: 'UTF-8'))
cues.empty? ? nil : cues
end
|
#vtt_sibling_of(audio_hash) ⇒ Object
74
75
76
77
78
79
80
|
# File 'lib/jekyll/podlove_player_tag.rb', line 74
def vtt_sibling_of(audio_hash)
return nil unless audio_hash
primary = audio(audio_hash)
return nil unless primary
primary.sub(/\.[^.\/]+\z/, '.vtt')
end
|