eleventy-plugin-podcaster 2.0.0-alpha.3 → 2.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eleventy.config.js +7 -0
- package/package.json +1 -2
- package/src/calculateEpisodeFilename.js +6 -4
- package/src/calculateEpisodeSizeAndDuration.js +10 -7
- package/src/episodeData.js +5 -3
- package/src/excerpts.js +2 -1
- package/src/isEpisodePost.js +10 -0
- package/src/podcastFeed.js +4 -3
- package/src/podcastFeed.njk +2 -2
package/eleventy.config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
1
3
|
import podcastFeed from './src/podcastFeed.js'
|
|
2
4
|
import podcastData from './src/podcastData.js'
|
|
3
5
|
import episodeData from './src/episodeData.js'
|
|
@@ -10,6 +12,11 @@ import drafts from './src/drafts.js'
|
|
|
10
12
|
import pageTitle from './src/pageTitle.js'
|
|
11
13
|
|
|
12
14
|
export default function (eleventyConfig, options = {}) {
|
|
15
|
+
const episodePostsDirectory = options.episodePostsDirectory ?? 'episode-posts'
|
|
16
|
+
options.episodePostsDirectory = path.join(eleventyConfig.directories.input, episodePostsDirectory)
|
|
17
|
+
const episodeFilesDirectory = options.episodeFilesDirectory ?? 'episode-files'
|
|
18
|
+
options.episodeFilesDirectory = path.join(eleventyConfig.directories.input, episodeFilesDirectory)
|
|
19
|
+
|
|
13
20
|
eleventyConfig.addPlugin(podcastFeed, options)
|
|
14
21
|
eleventyConfig.addPlugin(podcastData, options)
|
|
15
22
|
eleventyConfig.addPlugin(episodeData, options)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eleventy-plugin-podcaster",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.4",
|
|
4
4
|
"description": "An Eleventy plugin that allows you to create a podcast and its accompanying website",
|
|
5
5
|
"main": "eleventy.config.js",
|
|
6
6
|
"exports": {
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
"@11ty/eleventy-plugin-rss": "^2.0.1",
|
|
33
33
|
"@aws-sdk/client-s3": "^3.862.0",
|
|
34
34
|
"@tsmx/human-readable": "^2.0.3",
|
|
35
|
-
"chalk": "^5.3.0",
|
|
36
35
|
"dom-serializer": "^2.0.0",
|
|
37
36
|
"htmlparser2": "^9.1.0",
|
|
38
37
|
"luxon": "^3.4.4",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import isEpisodePost from './isEpisodePost.js'
|
|
2
|
+
|
|
1
3
|
function findMatchingFilename (episodeData, thisEpisode) {
|
|
2
4
|
const filenameSeasonAndEpisodePattern =
|
|
3
|
-
/^.*?\b[sS](?<seasonNumber>\d+)\s*[eE](?<episodeNumber>\d+)\b.*\.mp3$/
|
|
4
|
-
const filenameEpisodePattern = /^.*?\b(?<episodeNumber>\d+)\b.*\.mp3$/
|
|
5
|
+
/^.*?\b[sS](?<seasonNumber>\d+)\s*[eE](?<episodeNumber>\d+)\b.*\.(mp3|m4a)$/
|
|
6
|
+
const filenameEpisodePattern = /^.*?\b(?<episodeNumber>\d+)\b.*\.(mp3|m4a)$/
|
|
5
7
|
const { seasonNumber, episodeNumber } = thisEpisode
|
|
6
8
|
|
|
7
9
|
for (const file of Object.keys(episodeData)) {
|
|
@@ -27,11 +29,11 @@ function findMatchingFilename (episodeData, thisEpisode) {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
export default function (eleventyConfig,
|
|
32
|
+
export default function (eleventyConfig, options) {
|
|
31
33
|
eleventyConfig.addGlobalData('eleventyComputed.episode.filename', () => {
|
|
32
34
|
return data => {
|
|
33
35
|
if (data.episode.filename) return data.episode.filename
|
|
34
|
-
if (!data
|
|
36
|
+
if (!isEpisodePost(data, options)) return
|
|
35
37
|
|
|
36
38
|
return findMatchingFilename(data.episodeData, data.episode)
|
|
37
39
|
}
|
|
@@ -6,7 +6,10 @@ import { Writable } from 'node:stream'
|
|
|
6
6
|
import { S3Client, ListObjectsCommand, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
|
|
7
7
|
import { parseFile as parseFileMetadata, parseBuffer as parseBufferMetadata } from 'music-metadata'
|
|
8
8
|
import hr from '@tsmx/human-readable'
|
|
9
|
-
import
|
|
9
|
+
import isEpisodePost from './isEpisodePost.js'
|
|
10
|
+
|
|
11
|
+
const isAudioFile = episodeFilename => episodeFilename.endsWith('.mp3') ||
|
|
12
|
+
episodeFilename.endsWith('.m4a')
|
|
10
13
|
|
|
11
14
|
const convertSecondsToReadableDuration = seconds =>
|
|
12
15
|
Duration.fromMillis(seconds * 1000)
|
|
@@ -35,7 +38,7 @@ async function readEpisodeDataLocally (episodeFilesDirectory) {
|
|
|
35
38
|
const episodes = await readdir(episodeFilesDirectory)
|
|
36
39
|
const episodeData = {}
|
|
37
40
|
for (const episode of episodes) {
|
|
38
|
-
if (!episode
|
|
41
|
+
if (!isAudioFile(episode)) continue
|
|
39
42
|
|
|
40
43
|
const episodePath = path.join(episodeFilesDirectory, episode)
|
|
41
44
|
const episodeSize = (await stat(episodePath)).size
|
|
@@ -59,7 +62,7 @@ function calculatePodcastData (episodeData) {
|
|
|
59
62
|
|
|
60
63
|
function reportPodcastData (podcastData) {
|
|
61
64
|
const { numberOfEpisodes, totalSize, totalDuration } = podcastData
|
|
62
|
-
console.log(
|
|
65
|
+
console.log(`\u001b[33m${numberOfEpisodes} episodes; ${hr.fromBytes(totalSize)}; ${convertSecondsToReadableDuration(totalDuration)}.\u001b[0m`)
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
async function writePodcastDataLocally (episodeData, podcastData, directories) {
|
|
@@ -130,7 +133,7 @@ async function updateEpisodeDataFromS3Bucket (s3Client, s3Bucket) {
|
|
|
130
133
|
const list = await s3Client.send(new ListObjectsCommand({ Bucket: s3Bucket }))
|
|
131
134
|
const result = { ...storedEpisodeData.episodeData }
|
|
132
135
|
for (const item of list.Contents ?? []) {
|
|
133
|
-
if (!item.Key
|
|
136
|
+
if (!isAudioFile(item.Key)) continue
|
|
134
137
|
|
|
135
138
|
const { Key: filename, Size: size, LastModified: lastModified } = item
|
|
136
139
|
|
|
@@ -163,7 +166,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
163
166
|
if (!firstRun || process.env.SKIP_EPISODE_CALCULATIONS === 'true') return
|
|
164
167
|
firstRun = false
|
|
165
168
|
|
|
166
|
-
const episodeFilesDirectory =
|
|
169
|
+
const episodeFilesDirectory = options.episodeFilesDirectory
|
|
167
170
|
let episodeData
|
|
168
171
|
if (existsSync(episodeFilesDirectory)) {
|
|
169
172
|
episodeData = await readEpisodeDataLocally(episodeFilesDirectory)
|
|
@@ -183,7 +186,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
183
186
|
eleventyConfig.addGlobalData('eleventyComputed.episode.size', () => {
|
|
184
187
|
return data => {
|
|
185
188
|
if (data.episode.size) return data.episode.size
|
|
186
|
-
if (data
|
|
189
|
+
if (isEpisodePost(data, options) && data.episodeData) {
|
|
187
190
|
return data.episodeData[data.episode.filename]?.size
|
|
188
191
|
}
|
|
189
192
|
}
|
|
@@ -196,7 +199,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
196
199
|
return convertedReadableDuration ?? data.episode.duration
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
if (data
|
|
202
|
+
if (isEpisodePost(data, options) && data.episodeData) {
|
|
200
203
|
return data.episodeData[data.episode.filename]?.duration
|
|
201
204
|
}
|
|
202
205
|
}
|
package/src/episodeData.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import isEpisodePost from './isEpisodePost.js'
|
|
2
|
+
|
|
1
3
|
export default function (eleventyConfig, options = {}) {
|
|
2
4
|
const postFilenameSeasonAndEpisodePattern =
|
|
3
5
|
/^[sS](?<seasonNumber>\d+)[eE](?<episodeNumber>\d+)/i
|
|
@@ -7,7 +9,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
7
9
|
return data => {
|
|
8
10
|
if (data.episode?.seasonNumber) return data.episode.seasonNumber
|
|
9
11
|
|
|
10
|
-
if (!data
|
|
12
|
+
if (!isEpisodePost(data, options)) return
|
|
11
13
|
|
|
12
14
|
const seasonAndEpisodeMatch = data.page.fileSlug.match(postFilenameSeasonAndEpisodePattern)
|
|
13
15
|
if (seasonAndEpisodeMatch) {
|
|
@@ -20,7 +22,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
20
22
|
return data => {
|
|
21
23
|
if (data.episode?.episodeNumber) return data.episode.episodeNumber
|
|
22
24
|
|
|
23
|
-
if (!data
|
|
25
|
+
if (!isEpisodePost(data, options)) return
|
|
24
26
|
|
|
25
27
|
const seasonAndEpisodeMatch = data.page.fileSlug.match(postFilenameSeasonAndEpisodePattern)
|
|
26
28
|
if (seasonAndEpisodeMatch) {
|
|
@@ -49,7 +51,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
49
51
|
|
|
50
52
|
eleventyConfig.addGlobalData('eleventyComputed.episode.url', () => {
|
|
51
53
|
return data => {
|
|
52
|
-
if (!data
|
|
54
|
+
if (!isEpisodePost(data, options)) return
|
|
53
55
|
|
|
54
56
|
const episodeUrlBase = data.podcast.episodeUrlBase
|
|
55
57
|
const filename = data.episode.filename
|
package/src/excerpts.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as htmlparser2 from 'htmlparser2'
|
|
2
2
|
import render from 'dom-serializer'
|
|
3
3
|
import markdownIt from 'markdown-it'
|
|
4
|
+
import isEpisodePost from './isEpisodePost.js'
|
|
4
5
|
|
|
5
6
|
export default function (eleventyConfig, options = {}) {
|
|
6
7
|
eleventyConfig.addGlobalData('eleventyComputed.excerpt', () => {
|
|
7
8
|
return (data) => {
|
|
8
|
-
if (!data
|
|
9
|
+
if (!isEpisodePost(data, options)) return
|
|
9
10
|
|
|
10
11
|
const md = markdownIt({
|
|
11
12
|
html: true,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
export default function isEpisodePost (data, options) {
|
|
4
|
+
if (data.page?.inputPath) {
|
|
5
|
+
const importPath = path.normalize(data.page.inputPath)
|
|
6
|
+
return importPath.startsWith(options.episodePostsDirectory)
|
|
7
|
+
} else {
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/podcastFeed.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import rssPlugin from '@11ty/eleventy-plugin-rss'
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import isEpisodePost from './isEpisodePost.js'
|
|
4
5
|
|
|
5
6
|
export default function (eleventyConfig, options = {}) {
|
|
6
7
|
if (!('addTemplate' in eleventyConfig)) {
|
|
@@ -12,7 +13,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
12
13
|
eleventyConfig.addTemplate('feed.njk', readFileSync(podcastFeedPath), {
|
|
13
14
|
eleventyExcludeFromCollections: true,
|
|
14
15
|
eleventyImport: {
|
|
15
|
-
collections: ['
|
|
16
|
+
collections: ['episodePost']
|
|
16
17
|
}
|
|
17
18
|
})
|
|
18
19
|
|
|
@@ -22,7 +23,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
22
23
|
}
|
|
23
24
|
})
|
|
24
25
|
|
|
25
|
-
eleventyConfig.addCollection('
|
|
26
|
-
return collectionApi.
|
|
26
|
+
eleventyConfig.addCollection('episodePost', (collectionApi) => {
|
|
27
|
+
return collectionApi.getAll().filter(item => isEpisodePost(item.data, options))
|
|
27
28
|
})
|
|
28
29
|
}
|
package/src/podcastFeed.njk
CHANGED
|
@@ -17,7 +17,7 @@ eleventyAllowMissingExtension: true
|
|
|
17
17
|
<description>{{ podcast.description }}</description>
|
|
18
18
|
<language>{{ podcast.language }}</language>
|
|
19
19
|
<copyright>{{ podcast.copyrightNotice }}</copyright>
|
|
20
|
-
<pubDate>{{ collections.
|
|
20
|
+
<pubDate>{{ collections.episodePost | getNewestCollectionItemDate | dateToRfc3339 }}</pubDate>
|
|
21
21
|
<lastBuildDate>{{ podcast.feedLastBuildDate }}</lastBuildDate>
|
|
22
22
|
<itunes:image href="{{ podcast.imagePath | htmlBaseUrl(siteUrl) }}"></itunes:image>
|
|
23
23
|
{%- if podcast.subcategory %}
|
|
@@ -47,7 +47,7 @@ eleventyAllowMissingExtension: true
|
|
|
47
47
|
</itunes:owner>
|
|
48
48
|
{%- endif %}
|
|
49
49
|
|
|
50
|
-
{% for post in collections.
|
|
50
|
+
{% for post in collections.episodePost | reverse %}
|
|
51
51
|
<item>
|
|
52
52
|
<title>{{ post.data.episode.title or post.data.title }}</title>
|
|
53
53
|
{% if post.data.episode.itunesTitle %}
|