eleventy-plugin-podcaster 2.0.0-alpha.3 → 2.0.0-alpha.5
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/.prettierrc +11 -0
- package/README.md +7 -4
- package/eleventy.config.js +9 -0
- package/package.json +1 -2
- package/src/calculateEpisodeFilename.js +6 -4
- package/src/calculateEpisodeSizeAndDuration.js +13 -31
- package/src/chapters.js +13 -0
- package/src/chapters.njk +9 -0
- package/src/episodeData.js +5 -3
- package/src/excerpts.js +2 -1
- package/src/isEpisodePost.js +10 -0
- package/src/podcastFeed.js +27 -3
- package/src/podcastFeed.njk +6 -2
- package/src/readableDuration.js +26 -0
package/.prettierrc
ADDED
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# eleventy-plugin-podcaster 🕚⚡️🎈🐀🎤📲
|
|
2
2
|
|
|
3
|
-
`eleventy-plugin-podcaster` — or **Podcaster**, as we will call it from now on — is an Eleventy plugin which lets you create a podcast and its accompanying website.
|
|
3
|
+
`eleventy-plugin-podcaster` — or **Podcaster**, as we will call it from now on — is an Eleventy plugin which lets you create a podcast and its accompanying website.
|
|
4
|
+
|
|
5
|
+
- **Podcaster** creates the podcast feed that you submit to Apple Podcasts, Spotify or any other podcast directory.
|
|
6
|
+
- **Podcaster** also provides information about your podcast to your Eleventy templates. This means that you can describe the podcast and its episodes on your podcast website, creating pages for individual episodes, guests, topics, seasons or anything else at all.
|
|
4
7
|
|
|
5
8
|
Plenty of services exist that will host your podcast online — [Spotify][], [Acast][], [Podbean][], [Buzzsprout][], [Blubrry][]. But none of these will allow you to own your podcast's presence on the web, and none of them will give you the freedom to create a site that presents your podcast in a way that reflects its premise, tone and style.
|
|
6
9
|
|
|
@@ -44,9 +47,9 @@ Detailed and specific information about how to install and use **Podcaster** can
|
|
|
44
47
|
|
|
45
48
|
1. **Podcaster** is an Eleventy plugin. Create an Eleventy site and install the `eleventy-plugin-podcaster` plugin in the usual way.
|
|
46
49
|
2. In the data directory, create a `podcast.json` file. This will contain information about your podcast and its site — at the very least, its title, the URL of the site, a description, its language, and its category.
|
|
47
|
-
3. In the input directory, create
|
|
48
|
-
4. In the input directory, create
|
|
49
|
-
5. Create pages on your site for each of your episodes using the posts in the `
|
|
50
|
+
3. In the input directory, create an `episode-files` directory and put your podcast MP3s in there.
|
|
51
|
+
4. In the input directory, create an `episode-posts` directory. Here you will create a post for each episode, which will include information about the episode in its filename and front matter and which whill contain the episode description or show notes.
|
|
52
|
+
5. Create pages on your site for each of your episodes using the posts in the `episode-posts` directory. And you can also create index pages, topic pages, guest pages or anything you like — all using the information you have supplied to **Podcaster** in your `podcast.json` file and your episode posts.
|
|
50
53
|
6. Find a host for your site, and a CDN for your podcast episodes.
|
|
51
54
|
|
|
52
55
|
> [!WARNING]
|
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'
|
|
@@ -5,11 +7,17 @@ import calculateEpisodeSizeAndDuration from './src/calculateEpisodeSizeAndDurati
|
|
|
5
7
|
import calculateEpisodeFilename from './src/calculateEpisodeFilename.js'
|
|
6
8
|
|
|
7
9
|
import readableFilters from './src/readableFilters.js'
|
|
10
|
+
import chapters from './src/chapters.js'
|
|
8
11
|
import excerpts from './src/excerpts.js'
|
|
9
12
|
import drafts from './src/drafts.js'
|
|
10
13
|
import pageTitle from './src/pageTitle.js'
|
|
11
14
|
|
|
12
15
|
export default function (eleventyConfig, options = {}) {
|
|
16
|
+
const episodePostsDirectory = options.episodePostsDirectory ?? 'episode-posts'
|
|
17
|
+
options.episodePostsDirectory = path.join(eleventyConfig.directories.input, episodePostsDirectory)
|
|
18
|
+
const episodeFilesDirectory = options.episodeFilesDirectory ?? 'episode-files'
|
|
19
|
+
options.episodeFilesDirectory = path.join(eleventyConfig.directories.input, episodeFilesDirectory)
|
|
20
|
+
|
|
13
21
|
eleventyConfig.addPlugin(podcastFeed, options)
|
|
14
22
|
eleventyConfig.addPlugin(podcastData, options)
|
|
15
23
|
eleventyConfig.addPlugin(episodeData, options)
|
|
@@ -19,6 +27,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
19
27
|
// Filters
|
|
20
28
|
|
|
21
29
|
eleventyConfig.addPlugin(readableFilters, options)
|
|
30
|
+
eleventyConfig.addPlugin(chapters, options)
|
|
22
31
|
|
|
23
32
|
// Optional features
|
|
24
33
|
|
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.5",
|
|
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
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DateTime } from 'luxon'
|
|
2
|
+
import readableDuration from './readableDuration.js'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { existsSync } from 'node:fs'
|
|
4
5
|
import { readdir, stat, writeFile } from 'node:fs/promises'
|
|
@@ -6,36 +7,16 @@ import { Writable } from 'node:stream'
|
|
|
6
7
|
import { S3Client, ListObjectsCommand, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
|
|
7
8
|
import { parseFile as parseFileMetadata, parseBuffer as parseBufferMetadata } from 'music-metadata'
|
|
8
9
|
import hr from '@tsmx/human-readable'
|
|
9
|
-
import
|
|
10
|
+
import isEpisodePost from './isEpisodePost.js'
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
.shiftTo('days', 'hours', 'minutes', 'seconds')
|
|
14
|
-
.toHuman()
|
|
15
|
-
|
|
16
|
-
const convertReadableDurationToSeconds = duration => {
|
|
17
|
-
const durationPattern = /^(?:(?<hours>\d+):)?(?<minutes>\d{1,2}):(?<seconds>\d{2}(?:\.\d+)?)$/
|
|
18
|
-
|
|
19
|
-
let match
|
|
20
|
-
if (duration?.match) {
|
|
21
|
-
match = duration.match(durationPattern)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (match) {
|
|
25
|
-
const hours = isNaN(parseInt(match.groups.hours))
|
|
26
|
-
? 0
|
|
27
|
-
: parseInt(match.groups.hours)
|
|
28
|
-
const minutes = parseInt(match.groups.minutes)
|
|
29
|
-
const seconds = parseFloat(match.groups.seconds)
|
|
30
|
-
return hours * 3600 + minutes * 60 + seconds
|
|
31
|
-
}
|
|
32
|
-
}
|
|
12
|
+
const isAudioFile = episodeFilename => episodeFilename.endsWith('.mp3') ||
|
|
13
|
+
episodeFilename.endsWith('.m4a')
|
|
33
14
|
|
|
34
15
|
async function readEpisodeDataLocally (episodeFilesDirectory) {
|
|
35
16
|
const episodes = await readdir(episodeFilesDirectory)
|
|
36
17
|
const episodeData = {}
|
|
37
18
|
for (const episode of episodes) {
|
|
38
|
-
if (!episode
|
|
19
|
+
if (!isAudioFile(episode)) continue
|
|
39
20
|
|
|
40
21
|
const episodePath = path.join(episodeFilesDirectory, episode)
|
|
41
22
|
const episodeSize = (await stat(episodePath)).size
|
|
@@ -59,7 +40,7 @@ function calculatePodcastData (episodeData) {
|
|
|
59
40
|
|
|
60
41
|
function reportPodcastData (podcastData) {
|
|
61
42
|
const { numberOfEpisodes, totalSize, totalDuration } = podcastData
|
|
62
|
-
console.log(
|
|
43
|
+
console.log(`\u001b[33m${numberOfEpisodes} episodes; ${hr.fromBytes(totalSize)}; ${readableDuration.convertFromSeconds(totalDuration)}.\u001b[0m`)
|
|
63
44
|
}
|
|
64
45
|
|
|
65
46
|
async function writePodcastDataLocally (episodeData, podcastData, directories) {
|
|
@@ -130,7 +111,7 @@ async function updateEpisodeDataFromS3Bucket (s3Client, s3Bucket) {
|
|
|
130
111
|
const list = await s3Client.send(new ListObjectsCommand({ Bucket: s3Bucket }))
|
|
131
112
|
const result = { ...storedEpisodeData.episodeData }
|
|
132
113
|
for (const item of list.Contents ?? []) {
|
|
133
|
-
if (!item.Key
|
|
114
|
+
if (!isAudioFile(item.Key)) continue
|
|
134
115
|
|
|
135
116
|
const { Key: filename, Size: size, LastModified: lastModified } = item
|
|
136
117
|
|
|
@@ -161,9 +142,10 @@ export default function (eleventyConfig, options = {}) {
|
|
|
161
142
|
let firstRun = true
|
|
162
143
|
eleventyConfig.on('eleventy.before', async ({ directories }) => {
|
|
163
144
|
if (!firstRun || process.env.SKIP_EPISODE_CALCULATIONS === 'true') return
|
|
145
|
+
|
|
164
146
|
firstRun = false
|
|
165
147
|
|
|
166
|
-
const episodeFilesDirectory =
|
|
148
|
+
const episodeFilesDirectory = options.episodeFilesDirectory
|
|
167
149
|
let episodeData
|
|
168
150
|
if (existsSync(episodeFilesDirectory)) {
|
|
169
151
|
episodeData = await readEpisodeDataLocally(episodeFilesDirectory)
|
|
@@ -183,7 +165,7 @@ export default function (eleventyConfig, options = {}) {
|
|
|
183
165
|
eleventyConfig.addGlobalData('eleventyComputed.episode.size', () => {
|
|
184
166
|
return data => {
|
|
185
167
|
if (data.episode.size) return data.episode.size
|
|
186
|
-
if (data
|
|
168
|
+
if (isEpisodePost(data, options) && data.episodeData) {
|
|
187
169
|
return data.episodeData[data.episode.filename]?.size
|
|
188
170
|
}
|
|
189
171
|
}
|
|
@@ -192,11 +174,11 @@ export default function (eleventyConfig, options = {}) {
|
|
|
192
174
|
eleventyConfig.addGlobalData('eleventyComputed.episode.duration', () => {
|
|
193
175
|
return data => {
|
|
194
176
|
if (data.episode.duration) {
|
|
195
|
-
const convertedReadableDuration =
|
|
177
|
+
const convertedReadableDuration = readableDuration.convertToSeconds(data.episode.duration)
|
|
196
178
|
return convertedReadableDuration ?? data.episode.duration
|
|
197
179
|
}
|
|
198
180
|
|
|
199
|
-
if (data
|
|
181
|
+
if (isEpisodePost(data, options) && data.episodeData) {
|
|
200
182
|
return data.episodeData[data.episode.filename]?.duration
|
|
201
183
|
}
|
|
202
184
|
}
|
package/src/chapters.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import readableDuration from './readableDuration.js'
|
|
2
|
+
|
|
3
|
+
export default (eleventyConfig, options = {}) => {
|
|
4
|
+
eleventyConfig.addFilter('normalizeChaptersData', (data) => {
|
|
5
|
+
const result = data.map(x => {
|
|
6
|
+
if (x.startTime) {
|
|
7
|
+
x.startTime = readableDuration.convertToSeconds(x.startTime) ?? x.startTime
|
|
8
|
+
}
|
|
9
|
+
return x
|
|
10
|
+
})
|
|
11
|
+
return JSON.stringify(result, null, 2)
|
|
12
|
+
})
|
|
13
|
+
}
|
package/src/chapters.njk
ADDED
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)) {
|
|
@@ -8,11 +9,18 @@ export default function (eleventyConfig, options = {}) {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
const podcastFeedPath = path.join(import.meta.dirname, './podcastFeed.njk')
|
|
11
|
-
|
|
12
12
|
eleventyConfig.addTemplate('feed.njk', readFileSync(podcastFeedPath), {
|
|
13
13
|
eleventyExcludeFromCollections: true,
|
|
14
14
|
eleventyImport: {
|
|
15
|
-
collections: ['
|
|
15
|
+
collections: ['episodePost']
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const chaptersPath = path.join(import.meta.dirname, './chapters.njk')
|
|
20
|
+
eleventyConfig.addTemplate('chapters.njk', readFileSync(chaptersPath), {
|
|
21
|
+
eleventyExcludeFromCollections: true,
|
|
22
|
+
eleventyImport: {
|
|
23
|
+
collections: ['episodePostWithChapters']
|
|
16
24
|
}
|
|
17
25
|
})
|
|
18
26
|
|
|
@@ -22,7 +30,23 @@ export default function (eleventyConfig, options = {}) {
|
|
|
22
30
|
}
|
|
23
31
|
})
|
|
24
32
|
|
|
33
|
+
eleventyConfig.addCollection('episodePost', (collectionApi) => {
|
|
34
|
+
return collectionApi
|
|
35
|
+
.getAll()
|
|
36
|
+
.filter(item => isEpisodePost(item.data, options))
|
|
37
|
+
// sort order explicit: presence of template data files disrupts it otherwise
|
|
38
|
+
.sort((a, b) => a.date - b.date)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// included for backward compatibility, will be deprecated in 3.0
|
|
25
42
|
eleventyConfig.addCollection('podcastEpisode', (collectionApi) => {
|
|
26
|
-
return collectionApi
|
|
43
|
+
return collectionApi
|
|
44
|
+
.getAll()
|
|
45
|
+
.filter(item => isEpisodePost(item.data, options))
|
|
46
|
+
.sort((a, b) => a.date - b.date)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
eleventyConfig.addCollection('episodePostWithChapters', (collectionApi) => {
|
|
50
|
+
return collectionApi.getAll().filter(item => isEpisodePost(item.data, options) && item.data.episode?.chapters)
|
|
27
51
|
})
|
|
28
52
|
}
|
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 %}
|
|
@@ -60,6 +60,10 @@ eleventyAllowMissingExtension: true
|
|
|
60
60
|
<guid isPermaLink="true">{{ post.url | htmlBaseUrl(siteUrl) }}</guid>
|
|
61
61
|
{% endif -%}
|
|
62
62
|
<pubDate>{{ post.date | dateToRfc3339 }}</pubDate>
|
|
63
|
+
{%- if post.data.episode.chapters %}
|
|
64
|
+
<podcast:chapters url="{{ [post.url, 'chapters.json'] | join('/') | htmlBaseUrl(siteUrl) }}"
|
|
65
|
+
type="application/json+chapters" />
|
|
66
|
+
{% endif -%}
|
|
63
67
|
{% if podcast.episodeDescriptionTemplate %}
|
|
64
68
|
{%- set episodeDescription -%}
|
|
65
69
|
{% include podcast.episodeDescriptionTemplate %}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Duration } from 'luxon'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
convertFromSeconds (seconds) {
|
|
5
|
+
return Duration.fromMillis(seconds * 1000)
|
|
6
|
+
.shiftTo('days', 'hours', 'minutes', 'seconds')
|
|
7
|
+
.toHuman()
|
|
8
|
+
},
|
|
9
|
+
convertToSeconds (duration) {
|
|
10
|
+
const durationPattern = /^(?:(?<hours>\d+):)?(?<minutes>\d{1,2}):(?<seconds>\d{2}(?:\.\d+)?)$/
|
|
11
|
+
|
|
12
|
+
let match
|
|
13
|
+
if (duration?.match) {
|
|
14
|
+
match = duration.match(durationPattern)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (match) {
|
|
18
|
+
const hours = isNaN(parseInt(match.groups.hours))
|
|
19
|
+
? 0
|
|
20
|
+
: parseInt(match.groups.hours)
|
|
21
|
+
const minutes = parseInt(match.groups.minutes)
|
|
22
|
+
const seconds = parseFloat(match.groups.seconds)
|
|
23
|
+
return hours * 3600 + minutes * 60 + seconds
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|