podcast-dl 11.7.3 → 11.7.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/bin/archive.js +2 -7
- package/bin/async.js +20 -50
- package/bin/bin.js +10 -35
- package/bin/commander.js +51 -101
- package/bin/commander.test.js +170 -0
- package/bin/exec.js +2 -8
- package/bin/ffmpeg.js +15 -15
- package/bin/items.js +11 -22
- package/bin/logger.js +2 -5
- package/bin/logger.test.js +149 -0
- package/bin/meta.js +4 -21
- package/bin/naming.js +6 -11
- package/bin/naming.test.js +106 -0
- package/bin/util.js +8 -22
- package/bin/util.test.js +9 -14
- package/bin/validate.js +1 -3
- package/bin/validate.test.js +131 -0
- package/lib/rss-parser/fields.js +1 -3
- package/lib/rss-parser/parser.js +10 -35
- package/lib/rss-parser/utils.js +2 -2
- package/package.json +12 -12
package/bin/archive.js
CHANGED
|
@@ -43,10 +43,7 @@ export const writeToArchive = ({ archiveKeys, archive }) => {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
if (data.dirty) {
|
|
46
|
-
fs.writeFileSync(
|
|
47
|
-
path.resolve(cwd, archive),
|
|
48
|
-
JSON.stringify([...data.entries], null, 4)
|
|
49
|
-
);
|
|
46
|
+
fs.writeFileSync(path.resolve(cwd, archive), JSON.stringify([...data.entries], null, 4));
|
|
50
47
|
data.dirty = false;
|
|
51
48
|
}
|
|
52
49
|
};
|
|
@@ -57,9 +54,7 @@ export const getIsInArchive = ({ archiveKeys, archive }) => {
|
|
|
57
54
|
};
|
|
58
55
|
|
|
59
56
|
export const getArchiveFilename = ({ pubDate, name, ext }) => {
|
|
60
|
-
const formattedPubDate = pubDate
|
|
61
|
-
? dayjs(new Date(pubDate)).format("YYYYMMDD")
|
|
62
|
-
: null;
|
|
57
|
+
const formattedPubDate = pubDate ? dayjs(new Date(pubDate)).format("YYYYMMDD") : null;
|
|
63
58
|
|
|
64
59
|
const baseName = formattedPubDate ? `${formattedPubDate}-${name}` : name;
|
|
65
60
|
|
package/bin/async.js
CHANGED
|
@@ -5,12 +5,7 @@ import _path from "path";
|
|
|
5
5
|
import stream from "stream";
|
|
6
6
|
import { throttle } from "throttle-debounce";
|
|
7
7
|
import { promisify } from "util";
|
|
8
|
-
import {
|
|
9
|
-
getArchiveFilename,
|
|
10
|
-
getArchiveKeys,
|
|
11
|
-
getIsInArchive,
|
|
12
|
-
writeToArchive,
|
|
13
|
-
} from "./archive.js";
|
|
8
|
+
import { getArchiveFilename, getArchiveKeys, getIsInArchive, writeToArchive } from "./archive.js";
|
|
14
9
|
import { runExec } from "./exec.js";
|
|
15
10
|
import { runFfmpeg } from "./ffmpeg.js";
|
|
16
11
|
import {
|
|
@@ -61,11 +56,7 @@ export const download = async (options) => {
|
|
|
61
56
|
return outputPath;
|
|
62
57
|
}
|
|
63
58
|
|
|
64
|
-
if (
|
|
65
|
-
archive &&
|
|
66
|
-
archiveKeys.length &&
|
|
67
|
-
getIsInArchive({ archiveKeys, archive })
|
|
68
|
-
) {
|
|
59
|
+
if (archive && archiveKeys.length && getIsInArchive({ archiveKeys, archive })) {
|
|
69
60
|
logMessage("Download exists in archive. Skipping...");
|
|
70
61
|
return null;
|
|
71
62
|
}
|
|
@@ -98,23 +89,17 @@ export const download = async (options) => {
|
|
|
98
89
|
|
|
99
90
|
logMessage(
|
|
100
91
|
`Starting download${
|
|
101
|
-
expectedSize
|
|
102
|
-
|
|
103
|
-
: "..."
|
|
104
|
-
}`
|
|
92
|
+
expectedSize ? ` of ${(expectedSize / BYTES_IN_MB).toFixed(2)} MB...` : "..."
|
|
93
|
+
}`,
|
|
105
94
|
);
|
|
106
95
|
|
|
107
96
|
try {
|
|
108
97
|
const onDownloadProgress = throttle(3000, (progress) => {
|
|
109
|
-
if (
|
|
110
|
-
getShouldOutputProgressIndicator() &&
|
|
111
|
-
progress.transferred > 0 &&
|
|
112
|
-
progress.percent < 1
|
|
113
|
-
) {
|
|
98
|
+
if (getShouldOutputProgressIndicator() && progress.transferred > 0 && progress.percent < 1) {
|
|
114
99
|
logMessage(
|
|
115
|
-
`${(progress.percent * 100).toFixed(0)}% of ${(
|
|
116
|
-
|
|
117
|
-
)
|
|
100
|
+
`${(progress.percent * 100).toFixed(0)}% of ${(progress.total / BYTES_IN_MB).toFixed(
|
|
101
|
+
2,
|
|
102
|
+
)} MB...`,
|
|
118
103
|
);
|
|
119
104
|
}
|
|
120
105
|
});
|
|
@@ -123,7 +108,7 @@ export const download = async (options) => {
|
|
|
123
108
|
got
|
|
124
109
|
.stream(url, { headers: { "user-agent": userAgent } })
|
|
125
110
|
.on("downloadProgress", onDownloadProgress),
|
|
126
|
-
fs.createWriteStream(tempOutputPath)
|
|
111
|
+
fs.createWriteStream(tempOutputPath),
|
|
127
112
|
);
|
|
128
113
|
} catch (error) {
|
|
129
114
|
removeFile();
|
|
@@ -145,10 +130,7 @@ export const download = async (options) => {
|
|
|
145
130
|
if (fileSize === 0) {
|
|
146
131
|
removeFile();
|
|
147
132
|
|
|
148
|
-
logMessage(
|
|
149
|
-
"Unable to write to file. Suggestion: verify permissions",
|
|
150
|
-
LOG_LEVELS.important
|
|
151
|
-
);
|
|
133
|
+
logMessage("Unable to write to file. Suggestion: verify permissions", LOG_LEVELS.important);
|
|
152
134
|
|
|
153
135
|
return null;
|
|
154
136
|
}
|
|
@@ -159,10 +141,7 @@ export const download = async (options) => {
|
|
|
159
141
|
outputPath,
|
|
160
142
|
contentType: headResponse?.headers?.["content-type"],
|
|
161
143
|
onCorrect: (from, to) =>
|
|
162
|
-
logMessage(
|
|
163
|
-
`Correcting extension: ${from} --> ${to}`,
|
|
164
|
-
LOG_LEVELS.important
|
|
165
|
-
),
|
|
144
|
+
logMessage(`Correcting extension: ${from} --> ${to}`, LOG_LEVELS.important),
|
|
166
145
|
});
|
|
167
146
|
|
|
168
147
|
fs.renameSync(tempOutputPath, finalOutputPath);
|
|
@@ -217,8 +196,10 @@ export const downloadItemsAsync = async ({
|
|
|
217
196
|
const threadIndex = index % threads;
|
|
218
197
|
const marker = threads > 1 ? `[${threadIndex}] ${item.title}` : item.title;
|
|
219
198
|
const logMessage = getLogMessageWithMarker(marker);
|
|
220
|
-
const { url: episodeAudioUrl, ext: audioFileExt } =
|
|
221
|
-
|
|
199
|
+
const { url: episodeAudioUrl, ext: audioFileExt } = getEpisodeAudioUrlAndExt(
|
|
200
|
+
item,
|
|
201
|
+
episodeSourceOrder,
|
|
202
|
+
);
|
|
222
203
|
|
|
223
204
|
if (!episodeAudioUrl) {
|
|
224
205
|
hasErrors = true;
|
|
@@ -273,9 +254,7 @@ export const downloadItemsAsync = async ({
|
|
|
273
254
|
} catch (error) {
|
|
274
255
|
hasErrors = true;
|
|
275
256
|
logError(
|
|
276
|
-
`${marker} | Error downloading ${
|
|
277
|
-
item._episodeImage.url
|
|
278
|
-
}: ${error.toString()}`
|
|
257
|
+
`${marker} | Error downloading ${item._episodeImage.url}: ${error.toString()}`,
|
|
279
258
|
);
|
|
280
259
|
}
|
|
281
260
|
}
|
|
@@ -300,9 +279,7 @@ export const downloadItemsAsync = async ({
|
|
|
300
279
|
} catch (error) {
|
|
301
280
|
hasErrors = true;
|
|
302
281
|
logError(
|
|
303
|
-
`${marker} | Error downloading ${
|
|
304
|
-
item._episodeTranscript.url
|
|
305
|
-
}: ${error.toString()}`
|
|
282
|
+
`${marker} | Error downloading ${item._episodeTranscript.url}: ${error.toString()}`,
|
|
306
283
|
);
|
|
307
284
|
}
|
|
308
285
|
}
|
|
@@ -316,9 +293,7 @@ export const downloadItemsAsync = async ({
|
|
|
316
293
|
audioFormat,
|
|
317
294
|
bitrate,
|
|
318
295
|
embedMetadata: embedMetadataFlag,
|
|
319
|
-
episodeImageOutputPath: hasEpisodeImage
|
|
320
|
-
? item._episodeImage.outputPath
|
|
321
|
-
: undefined,
|
|
296
|
+
episodeImageOutputPath: hasEpisodeImage ? item._episodeImage.outputPath : undefined,
|
|
322
297
|
ext: audioFileExt,
|
|
323
298
|
feed,
|
|
324
299
|
item,
|
|
@@ -355,10 +330,7 @@ export const downloadItemsAsync = async ({
|
|
|
355
330
|
width: episodeDigits,
|
|
356
331
|
offset: episodeNumOffset,
|
|
357
332
|
});
|
|
358
|
-
const outputEpisodeMetaPath = _path.resolve(
|
|
359
|
-
basePath,
|
|
360
|
-
episodeMetaName
|
|
361
|
-
);
|
|
333
|
+
const outputEpisodeMetaPath = _path.resolve(basePath, episodeMetaName);
|
|
362
334
|
|
|
363
335
|
try {
|
|
364
336
|
logMessage("Saving episode metadata...");
|
|
@@ -393,9 +365,7 @@ export const downloadItemsAsync = async ({
|
|
|
393
365
|
}
|
|
394
366
|
};
|
|
395
367
|
|
|
396
|
-
const itemPromises = targetItems.map((item, index) =>
|
|
397
|
-
limit(() => downloadItem(item, index))
|
|
398
|
-
);
|
|
368
|
+
const itemPromises = targetItems.map((item, index) => limit(() => downloadItem(item, index)));
|
|
399
369
|
|
|
400
370
|
await Promise.all(itemPromises);
|
|
401
371
|
|
package/bin/bin.js
CHANGED
|
@@ -9,13 +9,7 @@ import { getArchiveKey } from "./archive.js";
|
|
|
9
9
|
import { download, downloadItemsAsync } from "./async.js";
|
|
10
10
|
import { setupCommander } from "./commander.js";
|
|
11
11
|
import { getItemsToDownload, logItemsList } from "./items.js";
|
|
12
|
-
import {
|
|
13
|
-
ERROR_STATUSES,
|
|
14
|
-
LOG_LEVELS,
|
|
15
|
-
logError,
|
|
16
|
-
logErrorAndExit,
|
|
17
|
-
logMessage,
|
|
18
|
-
} from "./logger.js";
|
|
12
|
+
import { ERROR_STATUSES, LOG_LEVELS, logError, logErrorAndExit, logMessage } from "./logger.js";
|
|
19
13
|
import { writeFeedMeta } from "./meta.js";
|
|
20
14
|
import { getFolderName, getSimpleFilename } from "./naming.js";
|
|
21
15
|
import {
|
|
@@ -87,9 +81,7 @@ const main = async () => {
|
|
|
87
81
|
bootstrapProxy();
|
|
88
82
|
}
|
|
89
83
|
|
|
90
|
-
const feed = url
|
|
91
|
-
? await getUrlFeed(url, parserConfig)
|
|
92
|
-
: await getFileFeed(file, parserConfig);
|
|
84
|
+
const feed = url ? await getUrlFeed(url, parserConfig) : await getFileFeed(file, parserConfig);
|
|
93
85
|
|
|
94
86
|
const archivePrefix = (() => {
|
|
95
87
|
if (feed.feedUrl || url) {
|
|
@@ -101,10 +93,7 @@ const main = async () => {
|
|
|
101
93
|
return feed.title || file;
|
|
102
94
|
})();
|
|
103
95
|
|
|
104
|
-
const basePath = _path.resolve(
|
|
105
|
-
cwd,
|
|
106
|
-
getFolderName({ feed, template: outDir })
|
|
107
|
-
);
|
|
96
|
+
const basePath = _path.resolve(cwd, getFolderName({ feed, template: outDir }));
|
|
108
97
|
|
|
109
98
|
if (info) {
|
|
110
99
|
logFeedInfo(feed);
|
|
@@ -142,10 +131,7 @@ const main = async () => {
|
|
|
142
131
|
}
|
|
143
132
|
|
|
144
133
|
if (archive) {
|
|
145
|
-
archive =
|
|
146
|
-
typeof archive === "boolean"
|
|
147
|
-
? "./{{podcast_title}}/archive.json"
|
|
148
|
-
: archive;
|
|
134
|
+
archive = typeof archive === "boolean" ? "./{{podcast_title}}/archive.json" : archive;
|
|
149
135
|
archive = getFolderName({ feed, template: archive });
|
|
150
136
|
}
|
|
151
137
|
|
|
@@ -156,7 +142,7 @@ const main = async () => {
|
|
|
156
142
|
const podcastImageFileExt = getUrlExt(podcastImageUrl);
|
|
157
143
|
const outputImagePath = _path.resolve(
|
|
158
144
|
basePath,
|
|
159
|
-
getSimpleFilename(feed.title || "image", podcastImageFileExt)
|
|
145
|
+
getSimpleFilename(feed.title || "image", podcastImageFileExt),
|
|
160
146
|
);
|
|
161
147
|
|
|
162
148
|
try {
|
|
@@ -184,10 +170,7 @@ const main = async () => {
|
|
|
184
170
|
|
|
185
171
|
const outputMetaPath = _path.resolve(
|
|
186
172
|
basePath,
|
|
187
|
-
getSimpleFilename(
|
|
188
|
-
feed.title ? feed.title : "meta",
|
|
189
|
-
feed.title ? ".meta.json" : ".json"
|
|
190
|
-
)
|
|
173
|
+
getSimpleFilename(feed.title ? feed.title : "meta", feed.title ? ".meta.json" : ".json"),
|
|
191
174
|
);
|
|
192
175
|
|
|
193
176
|
try {
|
|
@@ -243,9 +226,7 @@ const main = async () => {
|
|
|
243
226
|
logErrorAndExit("No episodes found with provided criteria to download");
|
|
244
227
|
}
|
|
245
228
|
|
|
246
|
-
logMessage(
|
|
247
|
-
`\nStarting download of ${pluralize("episode", targetItems.length, true)}\n`
|
|
248
|
-
);
|
|
229
|
+
logMessage(`\nStarting download of ${pluralize("episode", targetItems.length, true)}\n`);
|
|
249
230
|
|
|
250
231
|
const { numEpisodesDownloaded, hasErrors } = await downloadItemsAsync({
|
|
251
232
|
archive,
|
|
@@ -278,17 +259,11 @@ const main = async () => {
|
|
|
278
259
|
`\n${numEpisodesDownloaded} of ${pluralize(
|
|
279
260
|
"episode",
|
|
280
261
|
targetItems.length,
|
|
281
|
-
true
|
|
282
|
-
)} downloaded\n
|
|
262
|
+
true,
|
|
263
|
+
)} downloaded\n`,
|
|
283
264
|
);
|
|
284
265
|
} else if (numEpisodesDownloaded > 0) {
|
|
285
|
-
logMessage(
|
|
286
|
-
`\nSuccessfully downloaded ${pluralize(
|
|
287
|
-
"episode",
|
|
288
|
-
numEpisodesDownloaded,
|
|
289
|
-
true
|
|
290
|
-
)}\n`
|
|
291
|
-
);
|
|
266
|
+
logMessage(`\nSuccessfully downloaded ${pluralize("episode", numEpisodesDownloaded, true)}\n`);
|
|
292
267
|
}
|
|
293
268
|
|
|
294
269
|
if (numEpisodesDownloaded === 0) {
|
package/bin/commander.js
CHANGED
|
@@ -7,29 +7,22 @@ export const setupCommander = (program) => {
|
|
|
7
7
|
program
|
|
8
8
|
.option("--url <string>", "url to podcast rss feed")
|
|
9
9
|
.option("--file <path>", "local path to podcast rss feed")
|
|
10
|
-
.option(
|
|
11
|
-
|
|
12
|
-
"specify output directory",
|
|
13
|
-
"./{{podcast_title}}"
|
|
14
|
-
)
|
|
15
|
-
.option(
|
|
16
|
-
"--archive [path]",
|
|
17
|
-
"download or write only items not listed in archive file"
|
|
18
|
-
)
|
|
10
|
+
.option("--out-dir <path>", "specify output directory", "./{{podcast_title}}")
|
|
11
|
+
.option("--archive [path]", "download or write only items not listed in archive file")
|
|
19
12
|
.option(
|
|
20
13
|
"--episode-template <string>",
|
|
21
14
|
"template for generating episode related filenames",
|
|
22
|
-
"{{release_date}}-{{title}}"
|
|
15
|
+
"{{release_date}}-{{title}}",
|
|
23
16
|
)
|
|
24
17
|
.option(
|
|
25
18
|
"--episode-custom-template-options <patterns...>",
|
|
26
|
-
"create custom options for the episode template"
|
|
19
|
+
"create custom options for the episode template",
|
|
27
20
|
)
|
|
28
21
|
.option(
|
|
29
22
|
"--episode-digits <number>",
|
|
30
23
|
"minimum number of digits to use for episode numbering (leading zeros)",
|
|
31
24
|
createParseNumber({ min: 0, name: "--episode-digits" }),
|
|
32
|
-
1
|
|
25
|
+
1,
|
|
33
26
|
)
|
|
34
27
|
.option(
|
|
35
28
|
"--episode-num-offset <number>",
|
|
@@ -39,7 +32,7 @@ export const setupCommander = (program) => {
|
|
|
39
32
|
max: Number.MAX_SAFE_INTEGER,
|
|
40
33
|
name: "--episode-num-offset",
|
|
41
34
|
}),
|
|
42
|
-
0
|
|
35
|
+
0,
|
|
43
36
|
)
|
|
44
37
|
.option(
|
|
45
38
|
"--episode-source-order <string>",
|
|
@@ -49,24 +42,16 @@ export const setupCommander = (program) => {
|
|
|
49
42
|
const isValid = parsed.every((type) => !!AUDIO_ORDER_TYPES[type]);
|
|
50
43
|
|
|
51
44
|
if (!isValid) {
|
|
52
|
-
logErrorAndExit(
|
|
53
|
-
`Invalid type found in --episode-source-order: ${value}\n`
|
|
54
|
-
);
|
|
45
|
+
logErrorAndExit(`Invalid type found in --episode-source-order: ${value}\n`);
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
return parsed;
|
|
58
49
|
},
|
|
59
|
-
[AUDIO_ORDER_TYPES.enclosure, AUDIO_ORDER_TYPES.link]
|
|
60
|
-
)
|
|
61
|
-
.option("--include-meta", "write out podcast metadata to json")
|
|
62
|
-
.option(
|
|
63
|
-
"--include-episode-meta",
|
|
64
|
-
"write out individual episode metadata to json"
|
|
65
|
-
)
|
|
66
|
-
.option(
|
|
67
|
-
"--include-episode-transcripts",
|
|
68
|
-
"download found episode transcripts"
|
|
50
|
+
[AUDIO_ORDER_TYPES.enclosure, AUDIO_ORDER_TYPES.link],
|
|
69
51
|
)
|
|
52
|
+
.option("--include-meta", "write out podcast metadata to json", false)
|
|
53
|
+
.option("--include-episode-meta", "write out individual episode metadata to json", false)
|
|
54
|
+
.option("--include-episode-transcripts", "download found episode transcripts", false)
|
|
70
55
|
.option(
|
|
71
56
|
"--episode-transcript-types <string>",
|
|
72
57
|
"list of allowed transcript types in preferred order",
|
|
@@ -75,9 +60,7 @@ export const setupCommander = (program) => {
|
|
|
75
60
|
const isValid = parsed.every((type) => !!TRANSCRIPT_TYPES[type]);
|
|
76
61
|
|
|
77
62
|
if (!isValid) {
|
|
78
|
-
logErrorAndExit(
|
|
79
|
-
`Invalid type found in --transcript-types: ${value}\n`
|
|
80
|
-
);
|
|
63
|
+
logErrorAndExit(`Invalid type found in --transcript-types: ${value}\n`);
|
|
81
64
|
}
|
|
82
65
|
|
|
83
66
|
return parsed;
|
|
@@ -90,98 +73,68 @@ export const setupCommander = (program) => {
|
|
|
90
73
|
TRANSCRIPT_TYPES["text/vtt"],
|
|
91
74
|
TRANSCRIPT_TYPES["text/html"],
|
|
92
75
|
TRANSCRIPT_TYPES["text/plain"],
|
|
93
|
-
]
|
|
76
|
+
],
|
|
94
77
|
)
|
|
95
|
-
.option("--include-episode-images", "download found episode images")
|
|
78
|
+
.option("--include-episode-images", "download found episode images", false)
|
|
96
79
|
.option(
|
|
97
80
|
"--offset <number>",
|
|
98
81
|
"offset episode to start downloading from (most recent = 0)",
|
|
99
82
|
createParseNumber({ min: 0, name: "--offset" }),
|
|
100
|
-
0
|
|
83
|
+
0,
|
|
101
84
|
)
|
|
102
85
|
.option(
|
|
103
86
|
"--limit <number>",
|
|
104
87
|
"max amount of episodes to download",
|
|
105
|
-
createParseNumber({ min: 1, name: "--limit",
|
|
106
|
-
)
|
|
107
|
-
.option(
|
|
108
|
-
"--episode-regex <string>",
|
|
109
|
-
"match episode title against regex before downloading"
|
|
110
|
-
)
|
|
111
|
-
.option(
|
|
112
|
-
"--episode-regex-exclude <string>",
|
|
113
|
-
"episode titles matching regex will be excluded"
|
|
88
|
+
createParseNumber({ min: 1, name: "--limit", required: false }),
|
|
114
89
|
)
|
|
90
|
+
.option("--episode-regex <string>", "match episode title against regex before downloading")
|
|
91
|
+
.option("--episode-regex-exclude <string>", "episode titles matching regex will be excluded")
|
|
115
92
|
.option(
|
|
116
93
|
"--season <number>",
|
|
117
94
|
"download only episodes from this season",
|
|
118
|
-
createParseNumber({ min: 0, name: "--season" })
|
|
119
|
-
)
|
|
120
|
-
.option(
|
|
121
|
-
"--after <string>",
|
|
122
|
-
"download episodes only after this date (inclusive)"
|
|
123
|
-
)
|
|
124
|
-
.option(
|
|
125
|
-
"--before <string>",
|
|
126
|
-
"download episodes only before this date (inclusive)"
|
|
127
|
-
)
|
|
128
|
-
.option(
|
|
129
|
-
"--embed-metadata",
|
|
130
|
-
"add metadata to episode files using ffmpeg",
|
|
131
|
-
hasFfmpeg
|
|
132
|
-
)
|
|
133
|
-
.option(
|
|
134
|
-
"--add-mp3-metadata",
|
|
135
|
-
"deprecated: use --embed-metadata instead",
|
|
136
|
-
hasFfmpeg
|
|
95
|
+
createParseNumber({ min: 0, name: "--season" }),
|
|
137
96
|
)
|
|
97
|
+
.option("--after <string>", "download episodes only after this date (inclusive)")
|
|
98
|
+
.option("--before <string>", "download episodes only before this date (inclusive)")
|
|
99
|
+
.option("--embed-metadata", "add metadata to episode files using ffmpeg", hasFfmpeg, false)
|
|
100
|
+
.option("--add-mp3-metadata", "deprecated: use --embed-metadata instead", hasFfmpeg, false)
|
|
138
101
|
.option(
|
|
139
102
|
"--audio-format <string>",
|
|
140
103
|
"convert audio to format (mp3, m4a, aac, opus, ogg, flac, wav)",
|
|
141
104
|
(value) => {
|
|
142
105
|
if (!AUDIO_FORMATS[value]) {
|
|
143
106
|
logErrorAndExit(
|
|
144
|
-
`Invalid audio format: ${value}\nSupported formats: ${Object.keys(
|
|
145
|
-
|
|
146
|
-
)
|
|
107
|
+
`Invalid audio format: ${value}\nSupported formats: ${Object.keys(AUDIO_FORMATS).join(
|
|
108
|
+
", ",
|
|
109
|
+
)}`,
|
|
147
110
|
);
|
|
148
111
|
}
|
|
149
112
|
|
|
150
113
|
return hasFfmpeg(value);
|
|
151
|
-
}
|
|
152
|
-
)
|
|
153
|
-
.option(
|
|
154
|
-
"--adjust-bitrate <string>",
|
|
155
|
-
"adjust bitrate of episode files using ffmpeg",
|
|
156
|
-
hasFfmpeg
|
|
114
|
+
},
|
|
157
115
|
)
|
|
158
|
-
.option("--
|
|
159
|
-
.option("--
|
|
116
|
+
.option("--adjust-bitrate <string>", "adjust bitrate of episode files using ffmpeg", hasFfmpeg)
|
|
117
|
+
.option("--mono", "force episode files into mono using ffmpeg", hasFfmpeg, false)
|
|
118
|
+
.option("--override", "override local files on collision", false)
|
|
160
119
|
.option(
|
|
161
120
|
"--always-postprocess",
|
|
162
|
-
"always run additional tasks on the file regardless of whether the file already exists"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.option("--
|
|
166
|
-
.option(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
)}`
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return value;
|
|
121
|
+
"always run additional tasks on the file regardless of whether the file already exists",
|
|
122
|
+
false,
|
|
123
|
+
)
|
|
124
|
+
.option("--reverse", "download episodes in reverse order", false)
|
|
125
|
+
.option("--info", "print retrieved podcast info instead of downloading", false)
|
|
126
|
+
.option("--list [table|json]", "print episode info instead of downloading", (value) => {
|
|
127
|
+
if (!ITEM_LIST_FORMATS.includes(value)) {
|
|
128
|
+
logErrorAndExit(
|
|
129
|
+
`${value} is an invalid format for --list\nUse one of the following: ${ITEM_LIST_FORMATS.join(
|
|
130
|
+
", ",
|
|
131
|
+
)}`,
|
|
132
|
+
);
|
|
179
133
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
134
|
+
|
|
135
|
+
return value;
|
|
136
|
+
})
|
|
137
|
+
.option("--exec <string>", "execute a command after each episode is downloaded")
|
|
185
138
|
.option(
|
|
186
139
|
"--threads <number>",
|
|
187
140
|
"the number of downloads that can happen concurrently",
|
|
@@ -190,7 +143,7 @@ export const setupCommander = (program) => {
|
|
|
190
143
|
max: Number.MAX_SAFE_INTEGER,
|
|
191
144
|
name: "threads",
|
|
192
145
|
}),
|
|
193
|
-
1
|
|
146
|
+
1,
|
|
194
147
|
)
|
|
195
148
|
.option(
|
|
196
149
|
"--attempts <number>",
|
|
@@ -200,15 +153,12 @@ export const setupCommander = (program) => {
|
|
|
200
153
|
max: Number.MAX_SAFE_INTEGER,
|
|
201
154
|
name: "attempts",
|
|
202
155
|
}),
|
|
203
|
-
3
|
|
204
|
-
)
|
|
205
|
-
.option(
|
|
206
|
-
"--parser-config <string>",
|
|
207
|
-
"path to JSON config to override RSS parser"
|
|
156
|
+
3,
|
|
208
157
|
)
|
|
209
|
-
.option("--
|
|
158
|
+
.option("--parser-config <string>", "path to JSON config to override RSS parser")
|
|
159
|
+
.option("--proxy", "enable proxy support via global-agent", false)
|
|
210
160
|
.option("--user-agent <string>", "specify custom user agent string")
|
|
211
|
-
.option("--trust-ext", "trust file extension, skip MIME-based correction");
|
|
161
|
+
.option("--trust-ext", "trust file extension, skip MIME-based correction", false);
|
|
212
162
|
|
|
213
163
|
program.parse();
|
|
214
164
|
|