jekyll-podcast 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +130 -0
- data/LICENSE.txt +21 -0
- data/README.md +335 -0
- data/Rakefile +12 -0
- data/jekyll-podcast.gemspec +37 -0
- data/lib/jekyll/podcast/contributor_page_generator.rb +38 -0
- data/lib/jekyll/podcast/episode_data.rb +55 -0
- data/lib/jekyll/podcast/feed_generator.rb +34 -0
- data/lib/jekyll/podcast/file_exists.rb +28 -0
- data/lib/jekyll/podcast/liquid_tag_filters.rb +63 -0
- data/lib/jekyll/podcast/page_title_liquid_tag.rb +21 -0
- data/lib/jekyll/podcast/podcast.xml +71 -0
- data/lib/jekyll/podcast/podcast_data.rb +55 -0
- data/lib/jekyll/podcast/podcast_episode_drop.rb +12 -0
- data/lib/jekyll/podcast/tag_link_filter.rb +15 -0
- data/lib/jekyll/podcast/tag_page_generator.rb +47 -0
- data/lib/jekyll/podcast/utils.rb +30 -0
- data/lib/jekyll/podcast/validate_yaml_frontmatter.rb +61 -0
- data/lib/jekyll/podcast/version.rb +7 -0
- data/lib/jekyll/podcast.rb +26 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 69e80669ca56561da9578401d9afbd342b681eb5648dfcca7b395a03b502579a
|
4
|
+
data.tar.gz: f0884813435aa6d532fe45bf99b889c82fcf13d8601ab3b579eb12161448e134
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 78c530532b4b91b0fc0e17c7ce31f170a51a9582cf07e638ef25d7239cb674b8ccdeb45e0cc72581b5a491a63f7afcf1813a7406325a9a8cc6ebacaf822b3441
|
7
|
+
data.tar.gz: 1f7dfaa39fc4fa7a0e1e20db83061c3d811a8272a24338f0f55eca4c3199f1d7051ecc5f5935463457bd1897031a3ae2577f39b7f3c2b8de8d5c34e3d58529f6
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in jekyll-podcast.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'rake', '~> 13.0'
|
9
|
+
|
10
|
+
gem 'rspec', '~> 3.0'
|
11
|
+
|
12
|
+
gem 'rubocop', '~> 1.7'
|
13
|
+
gem 'rubocop-rspec', '~> 2.12'
|
14
|
+
|
15
|
+
gem 'jekyll', '~> 4.2'
|
16
|
+
|
17
|
+
gem 'nokogiri', '~> 1.13'
|
18
|
+
|
19
|
+
gem 'rubocop-rake', '~> 0.6.0'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
jekyll-podcast (0.9.0)
|
5
|
+
jekyll (~> 4.2)
|
6
|
+
json-schema (~> 2.6)
|
7
|
+
mp3info (~> 0.8.5)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
addressable (2.8.0)
|
13
|
+
public_suffix (>= 2.0.2, < 5.0)
|
14
|
+
ast (2.4.2)
|
15
|
+
colorator (1.1.0)
|
16
|
+
concurrent-ruby (1.1.10)
|
17
|
+
diff-lcs (1.5.0)
|
18
|
+
em-websocket (0.5.3)
|
19
|
+
eventmachine (>= 0.12.9)
|
20
|
+
http_parser.rb (~> 0)
|
21
|
+
eventmachine (1.2.7)
|
22
|
+
ffi (1.15.5)
|
23
|
+
forwardable-extended (2.6.0)
|
24
|
+
http_parser.rb (0.8.0)
|
25
|
+
i18n (1.12.0)
|
26
|
+
concurrent-ruby (~> 1.0)
|
27
|
+
jekyll (4.2.2)
|
28
|
+
addressable (~> 2.4)
|
29
|
+
colorator (~> 1.0)
|
30
|
+
em-websocket (~> 0.5)
|
31
|
+
i18n (~> 1.0)
|
32
|
+
jekyll-sass-converter (~> 2.0)
|
33
|
+
jekyll-watch (~> 2.0)
|
34
|
+
kramdown (~> 2.3)
|
35
|
+
kramdown-parser-gfm (~> 1.0)
|
36
|
+
liquid (~> 4.0)
|
37
|
+
mercenary (~> 0.4.0)
|
38
|
+
pathutil (~> 0.9)
|
39
|
+
rouge (~> 3.0)
|
40
|
+
safe_yaml (~> 1.0)
|
41
|
+
terminal-table (~> 2.0)
|
42
|
+
jekyll-sass-converter (2.2.0)
|
43
|
+
sassc (> 2.0.1, < 3.0)
|
44
|
+
jekyll-watch (2.2.1)
|
45
|
+
listen (~> 3.0)
|
46
|
+
json (2.6.2)
|
47
|
+
json-schema (2.8.1)
|
48
|
+
addressable (>= 2.4)
|
49
|
+
kramdown (2.4.0)
|
50
|
+
rexml
|
51
|
+
kramdown-parser-gfm (1.1.0)
|
52
|
+
kramdown (~> 2.0)
|
53
|
+
liquid (4.0.3)
|
54
|
+
listen (3.7.1)
|
55
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
56
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
57
|
+
mercenary (0.4.0)
|
58
|
+
mp3info (0.8.5)
|
59
|
+
ruby-mp3info (>= 0.8.5)
|
60
|
+
nokogiri (1.13.8-arm64-darwin)
|
61
|
+
racc (~> 1.4)
|
62
|
+
parallel (1.22.1)
|
63
|
+
parser (3.1.2.0)
|
64
|
+
ast (~> 2.4.1)
|
65
|
+
pathutil (0.16.2)
|
66
|
+
forwardable-extended (~> 2.6)
|
67
|
+
public_suffix (4.0.7)
|
68
|
+
racc (1.6.0)
|
69
|
+
rainbow (3.1.1)
|
70
|
+
rake (13.0.6)
|
71
|
+
rb-fsevent (0.11.1)
|
72
|
+
rb-inotify (0.10.1)
|
73
|
+
ffi (~> 1.0)
|
74
|
+
regexp_parser (2.5.0)
|
75
|
+
rexml (3.2.5)
|
76
|
+
rouge (3.30.0)
|
77
|
+
rspec (3.11.0)
|
78
|
+
rspec-core (~> 3.11.0)
|
79
|
+
rspec-expectations (~> 3.11.0)
|
80
|
+
rspec-mocks (~> 3.11.0)
|
81
|
+
rspec-core (3.11.0)
|
82
|
+
rspec-support (~> 3.11.0)
|
83
|
+
rspec-expectations (3.11.0)
|
84
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
85
|
+
rspec-support (~> 3.11.0)
|
86
|
+
rspec-mocks (3.11.1)
|
87
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
88
|
+
rspec-support (~> 3.11.0)
|
89
|
+
rspec-support (3.11.0)
|
90
|
+
rubocop (1.32.0)
|
91
|
+
json (~> 2.3)
|
92
|
+
parallel (~> 1.10)
|
93
|
+
parser (>= 3.1.0.0)
|
94
|
+
rainbow (>= 2.2.2, < 4.0)
|
95
|
+
regexp_parser (>= 1.8, < 3.0)
|
96
|
+
rexml (>= 3.2.5, < 4.0)
|
97
|
+
rubocop-ast (>= 1.19.1, < 2.0)
|
98
|
+
ruby-progressbar (~> 1.7)
|
99
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
100
|
+
rubocop-ast (1.19.1)
|
101
|
+
parser (>= 3.1.1.0)
|
102
|
+
rubocop-rake (0.6.0)
|
103
|
+
rubocop (~> 1.0)
|
104
|
+
rubocop-rspec (2.12.1)
|
105
|
+
rubocop (~> 1.31)
|
106
|
+
ruby-mp3info (0.8.10)
|
107
|
+
ruby-progressbar (1.11.0)
|
108
|
+
safe_yaml (1.0.5)
|
109
|
+
sassc (2.4.0)
|
110
|
+
ffi (~> 1.9)
|
111
|
+
terminal-table (2.0.0)
|
112
|
+
unicode-display_width (~> 1.1, >= 1.1.1)
|
113
|
+
unicode-display_width (1.8.0)
|
114
|
+
|
115
|
+
PLATFORMS
|
116
|
+
arm64-darwin-20
|
117
|
+
arm64-darwin-21
|
118
|
+
|
119
|
+
DEPENDENCIES
|
120
|
+
jekyll (~> 4.2)
|
121
|
+
jekyll-podcast!
|
122
|
+
nokogiri (~> 1.13)
|
123
|
+
rake (~> 13.0)
|
124
|
+
rspec (~> 3.0)
|
125
|
+
rubocop (~> 1.7)
|
126
|
+
rubocop-rake (~> 0.6.0)
|
127
|
+
rubocop-rspec (~> 2.12)
|
128
|
+
|
129
|
+
BUNDLED WITH
|
130
|
+
2.3.9
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Nathan Bottomley
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,335 @@
|
|
1
|
+
# Jekyll::Podcast
|
2
|
+
|
3
|
+
`jekyll-podcast` converts a Jekyll blog into a podcast webpage. It provides you with all the functionality you need to make your podcast fully accessible from your webpage, and it creates a podcast feed which you can submit to Apple Podcasts, Google Podcasts, Spotify or any other podcast directory.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'jekyll-podcast'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```shell
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```shell
|
22
|
+
gem install jekyll-podcast
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Providing information about your podcast
|
28
|
+
|
29
|
+
Most of the important information about your podcast — the title, the owner, the category, the subcategory and so on should appear in a `podcast` block in `_config.yml`.
|
30
|
+
|
31
|
+
```yaml
|
32
|
+
# _config.yml
|
33
|
+
|
34
|
+
podcast:
|
35
|
+
title: "Flight Through Entirety: A Doctor Who Podcast"
|
36
|
+
subtitle: &subtitle >-
|
37
|
+
Flying through the entirety of Doctor Who. Originally with cake,but now with guests.
|
38
|
+
# I've made the description and the summary
|
39
|
+
# the same as the subtitle, but you can do
|
40
|
+
# whatever you like here.
|
41
|
+
description: *subtitle
|
42
|
+
summary: *subtitle
|
43
|
+
email: flightthroughentirety@gmail.com
|
44
|
+
language: en-AU
|
45
|
+
author: Flight Through Entirety
|
46
|
+
owner: Nathan Bottomley
|
47
|
+
explicit: false
|
48
|
+
category: TV & Film
|
49
|
+
subcategory: TV Reviews
|
50
|
+
complete: no
|
51
|
+
```
|
52
|
+
|
53
|
+
>Explanations of all of the fields here can be found on [Apple's page about podcast feed tags](https://help.apple.com/itc/podcasts_connect/#/itcb54353390). A list of podcast categories and subcategories [can be found here](https://podcasters.apple.com/support/1691-apple-podcasts-categories).
|
54
|
+
|
55
|
+
You will also need to provide your podcast artwork, which should be a JPEG file 3000 × 3000 pixels in size. `jekyll-podcast` will expect to find this file at `/assets/images/podcast-logo.jpeg`.
|
56
|
+
|
57
|
+
### Providing information about your podcast episodes
|
58
|
+
|
59
|
+
Each post in the `_posts` directory will correspond to an episode of your podcast. So, for example, the publication date of the post will be the publication date of the episode, and the content of the post will be the episode's description or shownotes, which will (normally) be displayed in your listeners' podcast player.
|
60
|
+
|
61
|
+
Other information about your podcast episodes should be provided in each post's front matter. Here's an example.
|
62
|
+
|
63
|
+
```yaml
|
64
|
+
tags: #optional
|
65
|
+
- Series 6
|
66
|
+
- The Eleventh Doctor
|
67
|
+
contributors: #also optional
|
68
|
+
- Nathan Bottomley
|
69
|
+
podcast:
|
70
|
+
# the episode number
|
71
|
+
episode: 217
|
72
|
+
# the episode filename, including the extension
|
73
|
+
file: >-
|
74
|
+
FTE 217, Gaslight Girlboss Ginger (Day of the Moon).mp3
|
75
|
+
# optional
|
76
|
+
recording_date: 2021-08-01
|
77
|
+
```
|
78
|
+
|
79
|
+
### The episode files
|
80
|
+
|
81
|
+
By default, you will store your episodes in `/assets/episodes`, and they will be served directly from your website.
|
82
|
+
|
83
|
+
If you use an analytics service that requires a URL prefix to track your episodes' data, you will need to provide that prefix as part of the `podcast` block in `_config.yml`.
|
84
|
+
|
85
|
+
```yaml
|
86
|
+
# _config.yml
|
87
|
+
|
88
|
+
podcast:
|
89
|
+
tracking_prefix: https://dts.podtrac.com/redirect.mp3
|
90
|
+
```
|
91
|
+
|
92
|
+
If you use a CDN or an S3-compatible storage solution to host your podcast episodes, you will need to provide the URL for that sevice. You must also store your episodes in a folder called `/_episodes`, so that Jekyll doesn't include the episode files in the website it builds.
|
93
|
+
|
94
|
+
```yaml
|
95
|
+
# _config.yml
|
96
|
+
|
97
|
+
podcast:
|
98
|
+
remote_episode_host: https://flight-through-entirety.us-east-1.linodeobjects.com
|
99
|
+
```
|
100
|
+
|
101
|
+
> If you store your episodes on some other service, you will have to take care of uploading the episodes to that service yourself. I generally write a build script using `rsync` or `s3cmd sync` to do this.
|
102
|
+
|
103
|
+
### Creating your podcast feed
|
104
|
+
|
105
|
+
If you set up your `_config.yml` file and the front matter of your posts as specified above, `jekyll-podcast` will have enough information to create your podcast feed.
|
106
|
+
|
107
|
+
Your podcast feed will appear in your `_site` directory at `feed/podcast`.
|
108
|
+
|
109
|
+
### Your shownotes
|
110
|
+
|
111
|
+
The contents of each episode's post will appear on the website, as usual, but it will also be used as the description or shownotes for your episode and displayed in your listeners' podcast players.
|
112
|
+
|
113
|
+
It's easy to make a podcast player show a different description from the post displayed on your site. Just create an include called `post-feed-content.html` in the `_includes` folder and use it to define the content that you want to appear in a podcast player. Here's an example.
|
114
|
+
|
115
|
+
```html
|
116
|
+
<!-- post-feed-content.html -->
|
117
|
+
|
118
|
+
<p>
|
119
|
+
<em>
|
120
|
+
{{ post.star_trek.series }},
|
121
|
+
{{ post.star_trek.episode }}.
|
122
|
+
First broadcast on {{ post.star_trek.broadcast | date: "%A, %e %B %Y" }}
|
123
|
+
</em>
|
124
|
+
</p>
|
125
|
+
|
126
|
+
{{ post.content }}
|
127
|
+
```
|
128
|
+
|
129
|
+
### Podcast episode permalinks
|
130
|
+
|
131
|
+
Unlike a blog, a podcast website should have simple permalinks. Best practice tends to be just the episode number.
|
132
|
+
|
133
|
+
```url
|
134
|
+
https://flightthroughentirety.com/217
|
135
|
+
```
|
136
|
+
|
137
|
+
To make this possible, `jekyll-podcast` provides a `:podcast_episode` placeholder, which you can use in your default permalink definitions in `_config.yml`.
|
138
|
+
|
139
|
+
```yaml
|
140
|
+
# _config.yml
|
141
|
+
|
142
|
+
- scope:
|
143
|
+
path: ""
|
144
|
+
type: posts
|
145
|
+
values:
|
146
|
+
permalink: /:podcast_episode/
|
147
|
+
```
|
148
|
+
|
149
|
+
> You can combine this placeholder with other placeholders if you like. One possibility might be combining the episode number with the slug, like this:
|
150
|
+
>
|
151
|
+
> ```yaml
|
152
|
+
> # _config.yml
|
153
|
+
>
|
154
|
+
> values:
|
155
|
+
> permalink: /:podcast_episode-:slug/
|
156
|
+
> ```
|
157
|
+
>
|
158
|
+
>
|
159
|
+
> Remember to put the `/` at the end of the permalink pattern, so that you don't end up with the `.html` suffix at the end of your URLs.
|
160
|
+
|
161
|
+
### Podcast episode data
|
162
|
+
|
163
|
+
When `jekyll-podcast` builds your site, it analyses the podcast episodes in your `assets/episodes` folder (or your `_episodes` folder). As a result, the following information becomes available to your Liquid templates.
|
164
|
+
|
165
|
+
```liquid
|
166
|
+
{{ post.podcast.duration }}
|
167
|
+
→ 0:57:22
|
168
|
+
|
169
|
+
{{ post.podcast.size_in_megabytes }}
|
170
|
+
→ 40.1 MB
|
171
|
+
```
|
172
|
+
|
173
|
+
You might want to consider adding the recording date to the podcast blog in the front matter of your posts, so that you can display it in your posts as well.
|
174
|
+
|
175
|
+
```yaml
|
176
|
+
podcast:
|
177
|
+
recording_date: 2022-07-13
|
178
|
+
```
|
179
|
+
|
180
|
+
```liquid
|
181
|
+
{{ post.podcast.recording_date | date: "%A, %e %B %Y" }}
|
182
|
+
→ Wednesday, 13 July 2022
|
183
|
+
```
|
184
|
+
|
185
|
+
### Including an `<audio>` tag
|
186
|
+
|
187
|
+
You should also create an include for your post so that your listeners can listen to the episodes on your website. The easiest way to do this is using an `<audio>` tag. `jekyll-podcast` provides a liquid filter called `episode_url`, which will add the correct URL to the filename you provide in your front matter.
|
188
|
+
|
189
|
+
Here's an example.
|
190
|
+
|
191
|
+
```html
|
192
|
+
<!-- _includes/audio-player.html -->
|
193
|
+
|
194
|
+
<audio controls preload="metadata" src="{{ post.podcast.file | episode_url }}"></audio>
|
195
|
+
```
|
196
|
+
|
197
|
+
### Podcast data
|
198
|
+
|
199
|
+
While `jekyll-podcast` is analysing your podcast files, it collects some interesting information about your podcast, and logs it to screen as part of the build process. This information includes the number of episodes, their total size in megabytes and their total duration (to the nearest millisecond).
|
200
|
+
|
201
|
+
```shell
|
202
|
+
236 episodes; 15786.9 MB; 9 d 16 h 6 min 25.577 s
|
203
|
+
```
|
204
|
+
|
205
|
+
### Contributor pages
|
206
|
+
|
207
|
+
`jekyll-podcasts` has a feature which allows you to create bio pages for each of your contributors. These pages can include whatever information you like, including a chronological list of all of the episodes that a contributor has appeared in.
|
208
|
+
|
209
|
+
To get this to work, you need to create a Jekyll collection for your contributor pages. To do this, add a block like this to `_config.yml`.
|
210
|
+
|
211
|
+
```yaml
|
212
|
+
# _config.yml
|
213
|
+
|
214
|
+
collections:
|
215
|
+
contributors:
|
216
|
+
output: true
|
217
|
+
permalink: /contributors/:slug/
|
218
|
+
```
|
219
|
+
|
220
|
+
Then create a layout for your contributor pages, and specify it as a default in `_config.yml`.
|
221
|
+
|
222
|
+
```yaml
|
223
|
+
# _config.yml
|
224
|
+
|
225
|
+
defaults:
|
226
|
+
- scope:
|
227
|
+
path: ""
|
228
|
+
type: contributors
|
229
|
+
values:
|
230
|
+
layout: contributors-page
|
231
|
+
```
|
232
|
+
|
233
|
+
This layout should include the page content, and then iterate through the contributor's posts, which are all accessible to the layout and the page as `{{ page.posts }}`. Here's a simple example.
|
234
|
+
|
235
|
+
```html
|
236
|
+
<!-- contributors-page.html -->
|
237
|
+
|
238
|
+
<h1>page.title</h1>
|
239
|
+
|
240
|
+
{{ content }}
|
241
|
+
|
242
|
+
{% for post in page.posts %}
|
243
|
+
<article>
|
244
|
+
<h1>{{ post.title }}</h1>
|
245
|
+
{{ post.content }}
|
246
|
+
</article>
|
247
|
+
{% endfor %}
|
248
|
+
```
|
249
|
+
|
250
|
+
Now you can create the contributor's pages. Each page can contain a picture, a bio and anything else you want. So long as you set the layout in the front matter to `contributors-page`, the pages will display all of the episodes that the contributor is involved in.
|
251
|
+
|
252
|
+
To indicate which contributors are involved in a particular episode, just add a list of contributors to the front matter of the corresponding post.
|
253
|
+
|
254
|
+
```yaml
|
255
|
+
contributors:
|
256
|
+
- Jonathan Archer
|
257
|
+
- Gabriel Lorca
|
258
|
+
- Christopher Pike
|
259
|
+
- James T Kirk
|
260
|
+
```
|
261
|
+
|
262
|
+
> Originally this feature was called _Guest pages_, and it's still possible to call this feature whatever you want. Just provide an replacement name in plural form to the `podcast` block in `_config.yml` as the `contributors_alias`, like this:
|
263
|
+
>
|
264
|
+
>```yaml
|
265
|
+
> # _config.yml
|
266
|
+
>
|
267
|
+
> podcast:
|
268
|
+
> contributors_alias: guests
|
269
|
+
> ```
|
270
|
+
>
|
271
|
+
> If you do this, you should also change the name of the collection and the default type in `_config.yml` as shown above.
|
272
|
+
|
273
|
+
### Tag pages
|
274
|
+
|
275
|
+
There are plenty of other implementations of tag pages for Jekyll blogs, and so this one just aims to be as simple as possible.
|
276
|
+
|
277
|
+
To specify an episode's tags, just list them in the post's front matter.
|
278
|
+
|
279
|
+
```yaml
|
280
|
+
tags:
|
281
|
+
- The Tenth Doctor
|
282
|
+
- Specials
|
283
|
+
- Christmas
|
284
|
+
```
|
285
|
+
|
286
|
+
That's it really. By default, the permalink of a tag page will be the slugified version of the tag's text.
|
287
|
+
|
288
|
+
If you want to specify permalinks for a tag, you can do that by creating a file in the data directory called `tag_permalinks.yaml` or `tag_permalinks.json`. It should be a hash, with the tag as the key and the permalink as the value.
|
289
|
+
|
290
|
+
```yaml
|
291
|
+
# tag_permalinks.yaml
|
292
|
+
|
293
|
+
"Star Trek: The Next Generation": /tng/
|
294
|
+
"Star Trek: Deep Space Nine": /ds9/
|
295
|
+
"Star Trek: Voyager": /voy/
|
296
|
+
```
|
297
|
+
|
298
|
+
To create a list of tag links attached to a post, you can use `jekyll-podcast`'s `tag_link` filter, like this.
|
299
|
+
|
300
|
+
```html
|
301
|
+
{% for tag in post.tags %}
|
302
|
+
{{ tag | tag_link }}
|
303
|
+
{% endfor %}
|
304
|
+
```
|
305
|
+
|
306
|
+
### The `pagetitle` tag
|
307
|
+
|
308
|
+
The `{% pagetitle %}` Liquid tag can be placed in the `<head>` of your HTML layouts, and it will render a `<title>` tag for your page. This tag will consist of the title of your page (as specified in the front matter), followed by an em-dash, followed by the title of your site (as specified in `_config.yml`). If your page has no title or if its title is the same as your site's title, the tag will just contain your site's title.
|
309
|
+
|
310
|
+
### `jekyll-podcast` in action
|
311
|
+
|
312
|
+
I've been using `jekyll-podcast` to create podcast websites since the middle of 2021, after hosting podcasts with WordPress for a number of years. It has allowed me to take more control of my podcast sites, and to spend my time writing HTML and Sass (and Ruby, I guess) instead of wrestling with WordPress plugins and PHP.
|
313
|
+
|
314
|
+
If you would like to see some podcasts powered by `jekyll-podcast`, here's a list of the podcasts I'm currently running.
|
315
|
+
|
316
|
+
- [Flight Through Entirety](https://flightthroughentirety.com), a _Doctor Who_ podcast flying through the entirety of the show's 60-year history.
|
317
|
+
- [Bondfinger](https://bondfinger.com), a James Bond commentary podcast that has run out of James Bond films and now spends its time drinking and watching terrible TV shows from the 1960s mostly.
|
318
|
+
- [Jodie into Terror](https://jodieintoterror.com), a _Doctor Who_ flashcast in which we give our (intermittently) enthusiastic hot takes on the most recent era of _Doctor Who_ mere days after each episode's first broadcast in the UK.
|
319
|
+
- [Untitled Star Trek Project](https://untitledstartrekproject.com), a _Star Trek_ commentary podcast in which two friends watch _Star Trek_ episodes from any series, chosen (nearly) at random by [a page on the podcast website](https://untitledstartrekproject.com/randomiser).
|
320
|
+
|
321
|
+
<!--
|
322
|
+
## Development
|
323
|
+
|
324
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
325
|
+
|
326
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
327
|
+
|
328
|
+
## Contributing
|
329
|
+
|
330
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/furius95/jekyll-podcast>.
|
331
|
+
|
332
|
+
-->
|
333
|
+
## License
|
334
|
+
|
335
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/jekyll/podcast/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'jekyll-podcast'
|
7
|
+
spec.version = Jekyll::Podcast::VERSION
|
8
|
+
spec.authors = ['Nathan Bottomley']
|
9
|
+
spec.email = ['nathan.bottomley@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'A Jekyll plugin that provides some useful features for podcasting websites.'
|
12
|
+
spec.homepage = 'https://github.com/furius95/jekyll-podcast'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
spec.required_ruby_version = '>= 3.0.0'
|
15
|
+
|
16
|
+
# spec.metadata['allowed_push_host'] = 'http://mygemserver.com'
|
17
|
+
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
20
|
+
# spec.metadata['changelog_uri'] = 'TODO: Put your gem's CHANGELOG.md URL here.'
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
# spec.bindir = 'exe'
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ['lib']
|
30
|
+
|
31
|
+
spec.add_runtime_dependency 'jekyll', '~> 4.2'
|
32
|
+
spec.add_runtime_dependency 'json-schema', '~> 2.6'
|
33
|
+
spec.add_runtime_dependency 'mp3info', '~> 0.8.5'
|
34
|
+
|
35
|
+
spec.add_development_dependency 'nokogiri', '~> 1.13'
|
36
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Podcast
|
5
|
+
module ContributorPageGenerator
|
6
|
+
# Adds posts to each contributor page on the site
|
7
|
+
class Generator < Jekyll::Generator
|
8
|
+
def generate(site)
|
9
|
+
@site = site
|
10
|
+
collect_contributor_posts
|
11
|
+
@site.documents.each do |doc|
|
12
|
+
next unless doc.type == contributors_alias.to_sym
|
13
|
+
|
14
|
+
doc.data['posts'] = @contributor_posts[doc.data['title']]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def collect_contributor_posts
|
21
|
+
@contributor_posts = {}
|
22
|
+
@site.posts.docs.each do |post|
|
23
|
+
next unless post.data[contributors_alias]
|
24
|
+
|
25
|
+
post.data[contributors_alias].each do |contributor|
|
26
|
+
@contributor_posts[contributor] ||= []
|
27
|
+
@contributor_posts[contributor].push(post)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def contributors_alias
|
33
|
+
@site.config['podcast']['contributors_alias'] || 'contributors'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
# Define duration method in Jekyll::Podcast to convert from seconds to string for feed
|
5
|
+
module Podcast
|
6
|
+
# Class responsible for setting episode data on a post's page
|
7
|
+
class EpisodeData
|
8
|
+
def initialize(page, payload)
|
9
|
+
@site = page.site
|
10
|
+
@page = payload['page']
|
11
|
+
@file_path = File.join(Jekyll::Podcast::Utils.episodes_dir(@site), @page['podcast']['file'])
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_episode_data
|
15
|
+
@page['podcast'].merge!({ 'size' => size,
|
16
|
+
'size_in_megabytes' => size_in_megabytes,
|
17
|
+
'duration' => duration(seconds),
|
18
|
+
'guid' => guid })
|
19
|
+
end
|
20
|
+
|
21
|
+
def size
|
22
|
+
if File.exist? @file_path
|
23
|
+
File.size(@file_path)
|
24
|
+
else
|
25
|
+
0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def size_in_megabytes
|
30
|
+
"#{(size / 1_000_000.0).round(1)} MB"
|
31
|
+
end
|
32
|
+
|
33
|
+
def seconds
|
34
|
+
if File.exist? @file_path
|
35
|
+
Mp3Info.open(@file_path, &:length)
|
36
|
+
else
|
37
|
+
0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def duration(seconds)
|
42
|
+
format('%<hours>d:%<minutes>02d:%<seconds>02d',
|
43
|
+
Jekyll::Podcast::Utils.duration(seconds))
|
44
|
+
end
|
45
|
+
|
46
|
+
def guid
|
47
|
+
@page['podcast']['guid'] || "#{@site.config['url']}#{@page['url']}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Jekyll::Hooks.register :posts, :pre_render, priority: 'high' do |page, payload|
|
54
|
+
Jekyll::Podcast::EpisodeData.new(page, payload).add_episode_data
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
4
|
+
required_keys = %w[title subtitle author description language summary owner email explicit category]
|
5
|
+
missing_keys = required_keys.reject { |x| site.config['podcast'].key?(x) }
|
6
|
+
Jekyll.logger.warn "Podcast config is missing keys #{missing_keys}" unless missing_keys.empty?
|
7
|
+
end
|
8
|
+
|
9
|
+
module Jekyll
|
10
|
+
module Podcast
|
11
|
+
# Class representing feed page
|
12
|
+
class FeedPage < Jekyll::Page
|
13
|
+
def read_yaml(*)
|
14
|
+
@data ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generator for podcast feed
|
19
|
+
class FeedGenerator < Jekyll::Generator
|
20
|
+
def generate(site)
|
21
|
+
@site = site
|
22
|
+
site.pages << new_feed_page
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_feed_page
|
26
|
+
feed_page = FeedPage.new(@site, __dir__, '', 'feed/podcast')
|
27
|
+
template_path = File.expand_path('podcast.xml', __dir__)
|
28
|
+
feed_page.content = File.read(template_path)
|
29
|
+
feed_page.data['layout'] = nil
|
30
|
+
feed_page
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Podcast
|
5
|
+
# Liquid tag to determine the existence of a file
|
6
|
+
# Path name should be the root of the directory, without an initial slash
|
7
|
+
class FileExistsTag < Liquid::Tag
|
8
|
+
def initialize(tag_name, path, tokens)
|
9
|
+
super
|
10
|
+
@path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(context)
|
14
|
+
# Pipe parameter through Liquid to make additional replacements possible
|
15
|
+
url = Liquid::Template.parse(@path).render context
|
16
|
+
|
17
|
+
# Add the site source, so that it also works with a custom one
|
18
|
+
site_source = context.registers[:site].config['source']
|
19
|
+
file_path = "#{site_source}/#{url}"
|
20
|
+
|
21
|
+
# Check if file exists (returns true or false)
|
22
|
+
File.exist?(file_path.strip!).to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Liquid::Template.register_tag('file_exists', Jekyll::Podcast::FileExistsTag)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Jekyll
|
6
|
+
module Podcast
|
7
|
+
# Define tag filters for podcasting
|
8
|
+
module TagFilters
|
9
|
+
def without_index_html(input)
|
10
|
+
input.delete_suffix('index.html')
|
11
|
+
end
|
12
|
+
|
13
|
+
def tag_permalink(input)
|
14
|
+
site.data['tag_permalinks'][input]
|
15
|
+
end
|
16
|
+
|
17
|
+
def episode_url(input)
|
18
|
+
input = ERB::Util.url_encode(input)
|
19
|
+
File.join(url_prefix, input)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def url_prefix
|
25
|
+
if tracking_prefix && remote_episode_host
|
26
|
+
url_prefix_with_tracking_prefix_and_remote_episode_host
|
27
|
+
elsif tracking_prefix
|
28
|
+
url_prefix_with_tracking_prefix_and_no_remote_episode_host
|
29
|
+
elsif remote_episode_host
|
30
|
+
url_prefix_with_no_tracking_prefix_and_remote_episode_host
|
31
|
+
else
|
32
|
+
File.join(site.config['url'], 'assets', 'episodes')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def url_prefix_with_tracking_prefix_and_remote_episode_host
|
37
|
+
File.join(tracking_prefix, remote_episode_host.sub(%r{^https?://}, ''))
|
38
|
+
end
|
39
|
+
|
40
|
+
def url_prefix_with_tracking_prefix_and_no_remote_episode_host
|
41
|
+
File.join(tracking_prefix, 'assets', 'episodes')
|
42
|
+
end
|
43
|
+
|
44
|
+
def url_prefix_with_no_tracking_prefix_and_remote_episode_host
|
45
|
+
remote_episode_host
|
46
|
+
end
|
47
|
+
|
48
|
+
def tracking_prefix
|
49
|
+
@context.registers[:site].config['podcast']['tracking_prefix']
|
50
|
+
end
|
51
|
+
|
52
|
+
def remote_episode_host
|
53
|
+
@context.registers[:site].config['podcast']['remote_episode_host']
|
54
|
+
end
|
55
|
+
|
56
|
+
def site
|
57
|
+
@context.registers[:site]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Liquid::Template.register_filter(Jekyll::Podcast::TagFilters)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Podcast
|
5
|
+
# Liquid tag for generating a page title
|
6
|
+
class PageTitleTag < Liquid::Tag
|
7
|
+
def render(context)
|
8
|
+
site_title = context.registers[:site].config['title']
|
9
|
+
page_title = context.registers[:page]['title']
|
10
|
+
|
11
|
+
if page_title.nil? || page_title.empty? || page_title == site_title
|
12
|
+
"<title>#{site_title}</title>"
|
13
|
+
else
|
14
|
+
"<title>#{page_title} — #{site_title}</title>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Liquid::Template.register_tag('pagetitle', Jekyll::Podcast::PageTitleTag)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<rss version="2.0"
|
3
|
+
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
|
4
|
+
xmlns:atom="http://www.w3.org/2005/Atom"
|
5
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
6
|
+
xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
7
|
+
<channel>
|
8
|
+
<title>{{ site.podcast.title }}</title>
|
9
|
+
<itunes:subtitle>{{ site.podcast.subtitle }}</itunes:subtitle>
|
10
|
+
<link>{{ site.url }}</link>
|
11
|
+
<atom:link href="{{ site.url }}{{ page.url }}" rel="self" type="application/rss+xml" />
|
12
|
+
<dc:creator><![CDATA[{{ site.podcast.author }}]]></dc:creator>
|
13
|
+
<description>{{ site.podcast.description }}</description>
|
14
|
+
<lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>
|
15
|
+
<language>{{ site.podcast.language }}</language>
|
16
|
+
<copyright>© {{ site.time | date: "%Y" }} {{ site.podcast.author }}</copyright>
|
17
|
+
<itunes:author>{{ site.podcast.author }}</itunes:author>
|
18
|
+
<itunes:type>episodic</itunes:type>
|
19
|
+
<itunes:summary>{{ site.podcast.summary }}</itunes:summary>
|
20
|
+
<itunes:owner>
|
21
|
+
<itunes:name>{{ site.podcast.owner }}</itunes:name>
|
22
|
+
<itunes:email>{{ site.podcast.email }}</itunes:email>
|
23
|
+
</itunes:owner>
|
24
|
+
<itunes:explicit>{{ site.podcast.explicit }}</itunes:explicit>
|
25
|
+
{% if site.podcast.subcategory -%}
|
26
|
+
<itunes:category text="{{ site.podcast.category }}">
|
27
|
+
<itunes:category text="{{ site.podcast.subcategory }}"/>
|
28
|
+
</itunes:category>
|
29
|
+
{%- else -%}
|
30
|
+
<itunes:category text="{{ site.podcast.category }}" />
|
31
|
+
{%- endif %}
|
32
|
+
<itunes:image href="{{ site.url }}/assets/images/podcast-logo.jpeg" />
|
33
|
+
{% if site.podcast.complete -%}
|
34
|
+
<itunes:complete>Yes</itunes:complete>
|
35
|
+
{%- endif %}
|
36
|
+
{% if site.podcast.new-feed-url -%}
|
37
|
+
<generator>Jekyll v{{ jekyll.version }}</generator>
|
38
|
+
{% for post in site.posts -%}
|
39
|
+
<item>
|
40
|
+
<title>{{ post.title }}</title>
|
41
|
+
<link>{{ site.url }}{{ post.url }}</link>
|
42
|
+
<pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
|
43
|
+
<guid isPermaLink="false">{{ post.podcast.guid }}</guid>
|
44
|
+
<dc:creator><![CDATA[{{ site.podcast.author }}]]></dc:creator>
|
45
|
+
{%- capture post_feed_content_exists -%}
|
46
|
+
{%- file_exists _includes/post-feed-content.html -%}
|
47
|
+
{%- endcapture %}
|
48
|
+
{%- capture content %}
|
49
|
+
{%- if post_feed_content_exists == "true" -%}
|
50
|
+
{%- include post-feed-content.html post=post -%}
|
51
|
+
{%- else -%}
|
52
|
+
{{ post.content }}
|
53
|
+
{%- endif -%}
|
54
|
+
{%- endcapture -%}
|
55
|
+
<description><![CDATA[{{ content }}]]></description>
|
56
|
+
<content:encoded><![CDATA[{{ content }}]]></content:encoded>
|
57
|
+
<itunes:subtitle><![CDATA[{{ post.excerpt | strip_html | truncate: 250 }}]]></itunes:subtitle>
|
58
|
+
<itunes:keywords>{{ post.tags | join: ","}}</itunes:keywords>
|
59
|
+
<itunes:episodeType>full</itunes:episodeType>
|
60
|
+
<itunes:title>{{ post.title }}</itunes:title>
|
61
|
+
<itunes:episode>{{ post.podcast.episode }}</itunes:episode>
|
62
|
+
<enclosure url="{{ post.podcast.file | episode_url }}" length="{{ post.podcast.size }}"
|
63
|
+
type="audio/mpeg" />
|
64
|
+
<itunes:summary><![CDATA[{{ post.excerpt | strip_html }}]]></itunes:summary>
|
65
|
+
<itunes:block>no</itunes:block>
|
66
|
+
<itunes:duration>{{ post.podcast.duration }}</itunes:duration>
|
67
|
+
<itunes:author>{{ site.podcast.author }}</itunes:author>
|
68
|
+
</item>
|
69
|
+
{% endfor %}
|
70
|
+
</channel>
|
71
|
+
</rss>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Podcast
|
5
|
+
# Calculate the total count and duration of all podcast episodes
|
6
|
+
module PodcastData
|
7
|
+
class << self
|
8
|
+
def episodes_dir
|
9
|
+
Jekyll::Podcast::Utils.episodes_dir(@site)
|
10
|
+
end
|
11
|
+
|
12
|
+
def episodes
|
13
|
+
Dir.children(episodes_dir).select { |x| x.end_with?('.mp3') }
|
14
|
+
end
|
15
|
+
|
16
|
+
def total_duration_in_seconds
|
17
|
+
episodes.sum do |mp3|
|
18
|
+
Mp3Info.open(File.join(episodes_dir, mp3), &:length)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def total_size_in_megabytes
|
23
|
+
size_in_bytes = episodes.sum do |mp3|
|
24
|
+
File.size(File.join(episodes_dir, mp3))
|
25
|
+
end
|
26
|
+
"#{(size_in_bytes / 1_000_000.0).round(1)} MB"
|
27
|
+
end
|
28
|
+
|
29
|
+
def podcast_data
|
30
|
+
result = Jekyll::Podcast::Utils.duration(total_duration_in_seconds)
|
31
|
+
result[:count] = episodes.length
|
32
|
+
result[:size] = total_size_in_megabytes
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
def podcast_data_log_entry(site)
|
37
|
+
@site = site
|
38
|
+
if Dir.exist?(episodes_dir)
|
39
|
+
format(
|
40
|
+
'%<count>d episodes; %<size>s;' \
|
41
|
+
'%<days>d d %<hours>d h %<minutes>d min %<seconds>0.3f s',
|
42
|
+
podcast_data
|
43
|
+
)
|
44
|
+
else
|
45
|
+
"No episodes directory found at #{episodes_dir}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Jekyll::Hooks.register :site, :post_write do |site|
|
54
|
+
Jekyll.logger.info Jekyll::Podcast::PodcastData.podcast_data_log_entry(site).yellow
|
55
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Podcast
|
5
|
+
# creates a filter to turn tag names into links
|
6
|
+
module TagLinkFilter
|
7
|
+
def tag_link(input)
|
8
|
+
tag_href = Jekyll::Podcast::TagPageGenerator.permalink(input, @context.registers[:site])
|
9
|
+
"<a class='tag' href='#{tag_href}'>#{input}</a>"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Liquid::Template.register_filter(Jekyll::Podcast::TagLinkFilter)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Podcast
|
5
|
+
# Generator to create tag pages for each tag on the site
|
6
|
+
module TagPageGenerator
|
7
|
+
# The generator itself
|
8
|
+
class Generator < Jekyll::Generator
|
9
|
+
def generate(site)
|
10
|
+
site.tags.each do |tag, _|
|
11
|
+
site.pages << TagPage.new(site, tag)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Represents a tag page; includes pagination information
|
17
|
+
class TagPage < Jekyll::Page
|
18
|
+
def initialize(site, tag)
|
19
|
+
super(site, site.baseurl, '/', 'index.html')
|
20
|
+
@tag = tag
|
21
|
+
@data ||= {}
|
22
|
+
@data['permalink'] = Jekyll::Podcast::TagPageGenerator.permalink(@tag, site)
|
23
|
+
@data['layout'] = 'tag-page'
|
24
|
+
@data['title'] = tag
|
25
|
+
@data['pagination'] = pagination(site)
|
26
|
+
end
|
27
|
+
|
28
|
+
def pagination(site)
|
29
|
+
pagination_config = site.config.dig('tag_pages', 'pagination') || {}
|
30
|
+
{
|
31
|
+
'enabled' => true,
|
32
|
+
'tag' => @tag,
|
33
|
+
'sort_reverse' => false
|
34
|
+
}.merge(pagination_config)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.permalink(tag, site)
|
39
|
+
if site.data['tag_permalinks'] && site.data['tag_permalinks'][tag]
|
40
|
+
site.data['tag_permalinks'][tag]
|
41
|
+
else
|
42
|
+
"/#{Jekyll::Utils.slugify(tag)}/"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Podcast
|
5
|
+
# Utility functions used in jekyll-podcast
|
6
|
+
module Utils
|
7
|
+
class << self
|
8
|
+
def duration(seconds)
|
9
|
+
mm, ss = seconds.divmod(60)
|
10
|
+
hh, mm = mm.divmod(60)
|
11
|
+
dd, hh = hh.divmod(24)
|
12
|
+
{
|
13
|
+
days: dd,
|
14
|
+
hours: hh,
|
15
|
+
minutes: mm,
|
16
|
+
seconds: ss
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def episodes_dir(site)
|
21
|
+
if site.config['podcast']['remote_episode_host']
|
22
|
+
File.join(site.source, '_episodes')
|
23
|
+
else
|
24
|
+
File.join(site.source, 'assets/episodes')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'yaml'
|
5
|
+
require 'json'
|
6
|
+
require 'json-schema'
|
7
|
+
|
8
|
+
module Jekyll
|
9
|
+
module Podcast
|
10
|
+
# Validate the frontmatter of each document using schemas in the _schemas directory
|
11
|
+
module ValidateYAMLFrontmatter
|
12
|
+
class << self
|
13
|
+
date_proc = lambda { |value|
|
14
|
+
begin
|
15
|
+
Date.iso8601(value)
|
16
|
+
rescue ArgumentError
|
17
|
+
raise JSON::Schema::CustomFormatError, 'must be in ISO-8601 format: CCYY-MM-DD'
|
18
|
+
end
|
19
|
+
}
|
20
|
+
JSON::Validator.register_format_validator('full-date', date_proc)
|
21
|
+
|
22
|
+
def print_validation_complete_message(error_count)
|
23
|
+
if error_count.zero?
|
24
|
+
Jekyll.logger.info 'YAML frontmatter validated successfully'.yellow
|
25
|
+
else
|
26
|
+
Jekyll.logger.info "YAML frontmatter validation failed with #{error_count} errors".yellow
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def print_document_validation_failed_message(document, errors)
|
31
|
+
Jekyll.logger.info "Validation failed for #{document.path}:".yellow
|
32
|
+
Jekyll.logger.info(errors.map { |x| "- #{x}" }.join("\n").yellow)
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_document(document)
|
36
|
+
return [] unless File.exist? "_schemas/#{document.collection.label}.json"
|
37
|
+
|
38
|
+
document_frontmatter = YAML.safe_load_file(document.path, permitted_classes: [Date]).to_json
|
39
|
+
JSON::Validator.fully_validate("_schemas/#{document.collection.label}.json", document_frontmatter)
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate_site(site)
|
43
|
+
Jekyll.logger.info 'Validating YAML frontmatter'.yellow
|
44
|
+
error_count = 0
|
45
|
+
site.documents.each do |document|
|
46
|
+
result = validate_document(document)
|
47
|
+
next if result.empty?
|
48
|
+
|
49
|
+
error_count += result.length
|
50
|
+
print_document_validation_failed_message(document, result)
|
51
|
+
end
|
52
|
+
print_validation_complete_message(error_count)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Jekyll::Hooks.register :site, :pre_render do |site, _payload|
|
60
|
+
Jekyll::Podcast::ValidateYAMLFrontmatter.validate_site(site)
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jekyll'
|
4
|
+
require 'mp3info'
|
5
|
+
require 'json-schema'
|
6
|
+
|
7
|
+
require 'jekyll/podcast/version'
|
8
|
+
|
9
|
+
module Jekyll
|
10
|
+
module Podcast
|
11
|
+
class Error < StandardError; end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'jekyll/podcast/utils'
|
16
|
+
require 'jekyll/podcast/episode_data'
|
17
|
+
require 'jekyll/podcast/podcast_episode_drop'
|
18
|
+
require 'jekyll/podcast/liquid_tag_filters'
|
19
|
+
require 'jekyll/podcast/file_exists'
|
20
|
+
require 'jekyll/podcast/feed_generator'
|
21
|
+
require 'jekyll/podcast/tag_page_generator'
|
22
|
+
require 'jekyll/podcast/tag_link_filter'
|
23
|
+
require 'jekyll/podcast/contributor_page_generator'
|
24
|
+
require 'jekyll/podcast/podcast_data'
|
25
|
+
require 'jekyll/podcast/validate_yaml_frontmatter'
|
26
|
+
require 'jekyll/podcast/page_title_liquid_tag'
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jekyll-podcast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Bottomley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-09-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jekyll
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json-schema
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mp3info
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.8.5
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.8.5
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: nokogiri
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.13'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.13'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- nathan.bottomley@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- ".rubocop.yml"
|
79
|
+
- CHANGELOG.md
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- jekyll-podcast.gemspec
|
86
|
+
- lib/jekyll/podcast.rb
|
87
|
+
- lib/jekyll/podcast/contributor_page_generator.rb
|
88
|
+
- lib/jekyll/podcast/episode_data.rb
|
89
|
+
- lib/jekyll/podcast/feed_generator.rb
|
90
|
+
- lib/jekyll/podcast/file_exists.rb
|
91
|
+
- lib/jekyll/podcast/liquid_tag_filters.rb
|
92
|
+
- lib/jekyll/podcast/page_title_liquid_tag.rb
|
93
|
+
- lib/jekyll/podcast/podcast.xml
|
94
|
+
- lib/jekyll/podcast/podcast_data.rb
|
95
|
+
- lib/jekyll/podcast/podcast_episode_drop.rb
|
96
|
+
- lib/jekyll/podcast/tag_link_filter.rb
|
97
|
+
- lib/jekyll/podcast/tag_page_generator.rb
|
98
|
+
- lib/jekyll/podcast/utils.rb
|
99
|
+
- lib/jekyll/podcast/validate_yaml_frontmatter.rb
|
100
|
+
- lib/jekyll/podcast/version.rb
|
101
|
+
homepage: https://github.com/furius95/jekyll-podcast
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata:
|
105
|
+
homepage_uri: https://github.com/furius95/jekyll-podcast
|
106
|
+
source_code_uri: https://github.com/furius95/jekyll-podcast
|
107
|
+
rubygems_mfa_required: 'true'
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 3.0.0
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubygems_version: 3.3.7
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: A Jekyll plugin that provides some useful features for podcasting websites.
|
127
|
+
test_files: []
|