podcast-dl 9.5.0 → 10.1.0
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/README.md +42 -34
- package/bin/async.js +4 -1
- package/bin/bin.js +7 -4
- package/bin/commander.js +11 -4
- package/bin/naming.js +18 -3
- package/bin/util.js +2 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,40 +24,41 @@ Either `--url` or `--file` must be provided.
|
|
|
24
24
|
|
|
25
25
|
Type values surrounded in square brackets (`[]`) can be used as used as boolean options (no argument required).
|
|
26
26
|
|
|
27
|
-
| Option
|
|
28
|
-
|
|
|
29
|
-
| --url
|
|
30
|
-
| --file
|
|
31
|
-
| --out-dir
|
|
32
|
-
| --threads
|
|
33
|
-
| --attempts
|
|
34
|
-
| --archive
|
|
35
|
-
| --episode-template
|
|
36
|
-
| --
|
|
37
|
-
| --include-
|
|
38
|
-
| --include-episode-
|
|
39
|
-
| --include-episode-
|
|
40
|
-
| --
|
|
41
|
-
| --
|
|
42
|
-
| --
|
|
43
|
-
| --
|
|
44
|
-
| --
|
|
45
|
-
| --episode-
|
|
46
|
-
| --episode-
|
|
47
|
-
| --episode-
|
|
48
|
-
| --episode-
|
|
49
|
-
| --
|
|
50
|
-
| --
|
|
51
|
-
| --
|
|
52
|
-
| --
|
|
53
|
-
| --
|
|
54
|
-
| --
|
|
55
|
-
| --
|
|
56
|
-
| --
|
|
57
|
-
| --
|
|
58
|
-
| --
|
|
59
|
-
| --
|
|
60
|
-
| --
|
|
27
|
+
| Option | Type | Required | Description |
|
|
28
|
+
| --------------------------------- | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| --url | String | true\* | URL to podcast RSS feed. |
|
|
30
|
+
| --file | String | true\* | Path to local RSS file. |
|
|
31
|
+
| --out-dir | String | false | Specify output directory for episodes and metadata. Defaults to "./{{podcast_title}}". See "Template Options" for more details. |
|
|
32
|
+
| --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is 1. |
|
|
33
|
+
| --attempts | Number | false | Sets the number of download attempts per individual file. Default is 3. |
|
|
34
|
+
| --archive | [String] | false | Download or write out items not listed in archive file. Generates archive file at path if not found. Defaults to "./{{podcast_title}}/archive.json" when used as a boolean option. See "Template Options" for more details. |
|
|
35
|
+
| --episode-template | String | false | Template for generating episode related filenames. See "Template Options" for details. |
|
|
36
|
+
| --episode-custom-template-options | <String...> | false | Provide custom options for the episode template. See "Template Options" for details. |
|
|
37
|
+
| --include-meta | | false | Write out podcast metadata to JSON. |
|
|
38
|
+
| --include-episode-meta | | false | Write out individual episode metadata **to** JSON. |
|
|
39
|
+
| --include-episode-images | | false | Download found episode images. |
|
|
40
|
+
| --include-episode-transcripts | | false | Download found episode transcripts. |
|
|
41
|
+
| --offset | Number | false | Offset starting download position. Default is 0. |
|
|
42
|
+
| --limit | Number | false | Max number of episodes to download. Downloads all by default. |
|
|
43
|
+
| --after | String | false | Only download episodes after this date (i.e. MM/DD/YYY, inclusive). |
|
|
44
|
+
| --before | String | false | Only download episodes before this date (i.e. MM/DD/YYY, inclusive) |
|
|
45
|
+
| --episode-regex | String | false | Match episode title against provided regex before starting download. |
|
|
46
|
+
| --episode-digits | Number | false | Minimum number of digits to use for episode numbering (e.g. 3 would generate "001" instead of "1"). Default is 0. |
|
|
47
|
+
| --episode-num-offset | Number | false | Offset the acquired episode number. Default is 0. |
|
|
48
|
+
| --episode-source-order | String | false | Attempted order to extract episode audio URL from RSS feed. Default is "enclosure,link". |
|
|
49
|
+
| --episode-transcript-types | String | false | List of allowed transcript types in preferred order. Default is "application/json,application/x-subrip,application/srr,application/srt,text/vtt,text/html,text/plain". |
|
|
50
|
+
| --add-mp3-metadata | | false | Attempts to add a base level of episode metadata to each episode. Recommended only in cases where the original metadata is of poor quality. (**ffmpeg required**) |
|
|
51
|
+
| --adjust-bitrate | String (e.g. "48k") | false | Attempts to adjust bitrate of episodes. (**ffmpeg required**) |
|
|
52
|
+
| --mono | | false | Attempts to force episodes into mono. (**ffmpeg required**) |
|
|
53
|
+
| --override | | false | Override local files on collision. |
|
|
54
|
+
| --always-postprocess | | false | Always run additional tasks on the file regardless if the file already exists. This includes --add-mp3-metadata, --adjust-bitrate, --mono, and --exec. |
|
|
55
|
+
| --reverse | | false | Reverse download direction and start at last RSS item. |
|
|
56
|
+
| --info | | false | Print retrieved podcast info instead of downloading. |
|
|
57
|
+
| --list | [String] | false | Print episode list instead of downloading. Defaults to "table" when used as a boolean option. "json" is also supported. |
|
|
58
|
+
| --exec | String | false | Execute a command after each episode is downloaded. See "Template Options" for more details. |
|
|
59
|
+
| --parser-config | String | false | Path to JSON file that will be parsed and used to override the default config passed to [rss-parser](https://github.com/rbren/rss-parser#xml-options). |
|
|
60
|
+
| --proxy | | false | Enable proxy support. Specify environment variables listed by [global-agent](https://github.com/gajus/global-agent#environment-variables). |
|
|
61
|
+
| --help | | false | Output usage information. |
|
|
61
62
|
|
|
62
63
|
## Archive
|
|
63
64
|
|
|
@@ -81,6 +82,7 @@ Options that support templates allow users to specify a template for the generat
|
|
|
81
82
|
|
|
82
83
|
- `title`: The title of the episode.
|
|
83
84
|
- `release_date`: The release date of the episode in `YYYYMMDD` format.
|
|
85
|
+
- `release_year`: The release year of the episode.
|
|
84
86
|
- `episode_num`: The location number of where the episodes appears in the feed.
|
|
85
87
|
- `url`: URL of episode audio file.
|
|
86
88
|
- `duration`: Provided `mm:ss` duration (if found).
|
|
@@ -88,6 +90,12 @@ Options that support templates allow users to specify a template for the generat
|
|
|
88
90
|
- `podcast_link`: `link` value provided for the podcast feed. Typically the homepage URL.
|
|
89
91
|
- `guid`: The GUID of the episode.
|
|
90
92
|
|
|
93
|
+
#### `--episode-custom-template-options`
|
|
94
|
+
|
|
95
|
+
Each matcher provided will be used to extract a value from the episode `title`. Access these values in the template using the `custom_<n>` keyword where `<n>` is the index of the matcher provided (starting from `0`).
|
|
96
|
+
|
|
97
|
+
If no match is found, the `custom_<n>` keyword will be replaced with an empty string.
|
|
98
|
+
|
|
91
99
|
### `--exec`
|
|
92
100
|
|
|
93
101
|
- `episode_path`: The path to the downloaded episode.
|
package/bin/async.js
CHANGED
|
@@ -70,7 +70,7 @@ const download = async (options) => {
|
|
|
70
70
|
},
|
|
71
71
|
});
|
|
72
72
|
} catch (error) {
|
|
73
|
-
// unable to
|
|
73
|
+
// unable to retrieve head response
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const tempOutputPath = getTempPath(outputPath);
|
|
@@ -164,6 +164,7 @@ const downloadItemsAsync = async ({
|
|
|
164
164
|
basePath,
|
|
165
165
|
bitrate,
|
|
166
166
|
episodeTemplate,
|
|
167
|
+
episodeCustomTemplateOptions,
|
|
167
168
|
episodeDigits,
|
|
168
169
|
episodeNumOffset,
|
|
169
170
|
episodeSourceOrder,
|
|
@@ -199,6 +200,7 @@ const downloadItemsAsync = async ({
|
|
|
199
200
|
url: episodeAudioUrl,
|
|
200
201
|
ext: audioFileExt,
|
|
201
202
|
template: episodeTemplate,
|
|
203
|
+
customTemplateOptions: episodeCustomTemplateOptions,
|
|
202
204
|
width: episodeDigits,
|
|
203
205
|
offset: episodeNumOffset,
|
|
204
206
|
});
|
|
@@ -283,6 +285,7 @@ const downloadItemsAsync = async ({
|
|
|
283
285
|
url: episodeAudioUrl,
|
|
284
286
|
ext: episodeMetaExt,
|
|
285
287
|
template: episodeTemplate,
|
|
288
|
+
customTemplateOptions: episodeCustomTemplateOptions,
|
|
286
289
|
width: episodeDigits,
|
|
287
290
|
offset: episodeNumOffset,
|
|
288
291
|
});
|
package/bin/bin.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import _path from "path";
|
|
5
|
-
import
|
|
5
|
+
import { program } from "commander";
|
|
6
6
|
import pluralize from "pluralize";
|
|
7
7
|
import { bootstrap as bootstrapProxy } from "global-agent";
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { getFolderName, getSimpleFilename } from "./naming.js";
|
|
30
30
|
import { downloadItemsAsync } from "./async.js";
|
|
31
31
|
|
|
32
|
-
setupCommander(
|
|
32
|
+
const opts = setupCommander(program);
|
|
33
33
|
|
|
34
34
|
const {
|
|
35
35
|
after,
|
|
@@ -41,6 +41,7 @@ const {
|
|
|
41
41
|
episodeRegex,
|
|
42
42
|
episodeSourceOrder,
|
|
43
43
|
episodeTemplate,
|
|
44
|
+
episodeCustomTemplateOptions,
|
|
44
45
|
episodeTranscriptTypes,
|
|
45
46
|
exec,
|
|
46
47
|
file,
|
|
@@ -62,9 +63,9 @@ const {
|
|
|
62
63
|
url,
|
|
63
64
|
addMp3Metadata: addMp3MetadataFlag,
|
|
64
65
|
adjustBitrate: bitrate,
|
|
65
|
-
} =
|
|
66
|
+
} = opts;
|
|
66
67
|
|
|
67
|
-
let { archive } =
|
|
68
|
+
let { archive } = opts;
|
|
68
69
|
|
|
69
70
|
const main = async () => {
|
|
70
71
|
if (!url && !file) {
|
|
@@ -213,6 +214,7 @@ const main = async () => {
|
|
|
213
214
|
episodeRegex,
|
|
214
215
|
episodeSourceOrder,
|
|
215
216
|
episodeTemplate,
|
|
217
|
+
episodeCustomTemplateOptions,
|
|
216
218
|
includeEpisodeImages,
|
|
217
219
|
includeEpisodeTranscripts,
|
|
218
220
|
episodeTranscriptTypes,
|
|
@@ -234,6 +236,7 @@ const main = async () => {
|
|
|
234
236
|
basePath,
|
|
235
237
|
bitrate,
|
|
236
238
|
episodeTemplate,
|
|
239
|
+
episodeCustomTemplateOptions,
|
|
237
240
|
episodeDigits,
|
|
238
241
|
episodeNumOffset,
|
|
239
242
|
episodeSourceOrder,
|
package/bin/commander.js
CHANGED
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
import { createParseNumber, hasFfmpeg } from "./validate.js";
|
|
7
7
|
import { logErrorAndExit } from "./logger.js";
|
|
8
8
|
|
|
9
|
-
export const setupCommander = (
|
|
10
|
-
|
|
9
|
+
export const setupCommander = (program) => {
|
|
10
|
+
program
|
|
11
11
|
.option("--url <string>", "url to podcast rss feed")
|
|
12
12
|
.option("--file <path>", "local path to podcast rss feed")
|
|
13
13
|
.option(
|
|
@@ -24,6 +24,10 @@ export const setupCommander = (commander, argv) => {
|
|
|
24
24
|
"template for generating episode related filenames",
|
|
25
25
|
"{{release_date}}-{{title}}"
|
|
26
26
|
)
|
|
27
|
+
.option(
|
|
28
|
+
"--episode-custom-template-options <patterns...>",
|
|
29
|
+
"create custom options for the episode template"
|
|
30
|
+
)
|
|
27
31
|
.option(
|
|
28
32
|
"--episode-digits <number>",
|
|
29
33
|
"minimum number of digits to use for episode numbering (leading zeros)",
|
|
@@ -180,6 +184,9 @@ export const setupCommander = (commander, argv) => {
|
|
|
180
184
|
"--parser-config <string>",
|
|
181
185
|
"path to JSON config to override RSS parser"
|
|
182
186
|
)
|
|
183
|
-
.option("--proxy", "enable proxy support via global-agent")
|
|
184
|
-
|
|
187
|
+
.option("--proxy", "enable proxy support via global-agent");
|
|
188
|
+
|
|
189
|
+
program.parse();
|
|
190
|
+
|
|
191
|
+
return program.opts();
|
|
185
192
|
};
|
package/bin/naming.js
CHANGED
|
@@ -25,22 +25,37 @@ const getItemFilename = ({
|
|
|
25
25
|
feed,
|
|
26
26
|
template,
|
|
27
27
|
width,
|
|
28
|
+
customTemplateOptions = [],
|
|
28
29
|
offset = 0,
|
|
29
30
|
}) => {
|
|
30
31
|
const episodeNum = feed.items.length - item._originalIndex + offset;
|
|
31
|
-
const
|
|
32
|
+
const title = item.title || "";
|
|
33
|
+
const releaseYear = item.pubDate
|
|
34
|
+
? dayjs(new Date(item.pubDate)).format("YYYY")
|
|
35
|
+
: null;
|
|
36
|
+
|
|
37
|
+
const releaseDate = item.pubDate
|
|
32
38
|
? dayjs(new Date(item.pubDate)).format("YYYYMMDD")
|
|
33
39
|
: null;
|
|
34
40
|
|
|
41
|
+
const customReplacementTuples = customTemplateOptions.map((option, i) => {
|
|
42
|
+
const matchRegex = new RegExp(option);
|
|
43
|
+
const match = title.match(matchRegex);
|
|
44
|
+
|
|
45
|
+
return match && match[0] ? [`custom_${i}`, match[0]] : [`custom_${i}`, ""];
|
|
46
|
+
});
|
|
47
|
+
|
|
35
48
|
const templateReplacementsTuples = [
|
|
36
|
-
["title",
|
|
37
|
-
["release_date",
|
|
49
|
+
["title", title],
|
|
50
|
+
["release_date", releaseDate || ""],
|
|
51
|
+
["release_year", releaseYear || ""],
|
|
38
52
|
["episode_num", `${episodeNum}`.padStart(width, "0")],
|
|
39
53
|
["url", url],
|
|
40
54
|
["podcast_title", feed.title || ""],
|
|
41
55
|
["podcast_link", feed.link || ""],
|
|
42
56
|
["duration", item.itunes?.duration || ""],
|
|
43
57
|
["guid", item.guid],
|
|
58
|
+
...customReplacementTuples,
|
|
44
59
|
];
|
|
45
60
|
|
|
46
61
|
const templateSegments = template.trim().split(path.sep);
|
package/bin/util.js
CHANGED
|
@@ -165,6 +165,7 @@ const getItemsToDownload = ({
|
|
|
165
165
|
episodeRegex,
|
|
166
166
|
episodeSourceOrder,
|
|
167
167
|
episodeTemplate,
|
|
168
|
+
episodeCustomTemplateOptions,
|
|
168
169
|
includeEpisodeImages,
|
|
169
170
|
includeEpisodeTranscripts,
|
|
170
171
|
episodeTranscriptTypes,
|
|
@@ -252,6 +253,7 @@ const getItemsToDownload = ({
|
|
|
252
253
|
url: episodeAudioUrl,
|
|
253
254
|
ext: episodeImageFileExt,
|
|
254
255
|
template: episodeTemplate,
|
|
256
|
+
customTemplateOptions: episodeCustomTemplateOptions,
|
|
255
257
|
width: episodeDigits,
|
|
256
258
|
offset: episodeNumOffset,
|
|
257
259
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "podcast-dl",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"description": "A CLI for downloading podcasts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./bin/bin.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"cli"
|
|
27
27
|
],
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=18.17.0"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"command-exists": "^1.2.9",
|
|
52
|
-
"commander": "^
|
|
52
|
+
"commander": "^12.1.0",
|
|
53
53
|
"dayjs": "^1.8.25",
|
|
54
54
|
"filenamify": "^6.0.0",
|
|
55
55
|
"global-agent": "^3.0.0",
|