podcast-dl 7.1.0 → 7.3.1
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 +4 -2
- package/bin/async.js +13 -11
- package/bin/bin.js +18 -0
- package/bin/util.js +39 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# podcast-dl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A CLI for downloading podcasts with a focus on archiving.
|
|
4
4
|
|
|
5
5
|
## How to Use
|
|
6
6
|
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
`npx podcast-dl --url <PODCAST_RSS_URL>`
|
|
12
12
|
|
|
13
|
+
### [More Examples](./docs/examples.md)
|
|
14
|
+
|
|
13
15
|
## Options
|
|
14
16
|
|
|
15
17
|
Type values surrounded in square brackets (`[]`) can be used as used as boolean options (no argument required).
|
|
@@ -18,6 +20,7 @@ Type values surrounded in square brackets (`[]`) can be used as used as boolean
|
|
|
18
20
|
| ------------------------ | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
21
|
| --url | String | true | URL to podcast RSS feed. |
|
|
20
22
|
| --out-dir | String | false | Specify output directory for episodes and metadata. Defaults to "./{{podcast_title}}". See "Templating" for more details. |
|
|
23
|
+
| --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is 1. |
|
|
21
24
|
| --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 "Templating" for more details. |
|
|
22
25
|
| --episode-template | String | false | Template for generating episode related filenames. See "Templating" for details. |
|
|
23
26
|
| --include-meta | | false | Write out podcast metadata to JSON. |
|
|
@@ -36,7 +39,6 @@ Type values surrounded in square brackets (`[]`) can be used as used as boolean
|
|
|
36
39
|
| --info | | false | Print retrieved podcast info instead of downloading. |
|
|
37
40
|
| --list | [String] | false | Print episode list instead of downloading. Defaults to "table" when used as a boolean option. "json" is also supported. |
|
|
38
41
|
| --exec | String | false | Execute a command after each episode is downloaded. |
|
|
39
|
-
| --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is 1. |
|
|
40
42
|
| --filter-url-tacking | | false | Attempts to extract the direct download link of an episode if detected (**experimental**). |
|
|
41
43
|
| --version | | false | Output the version number. |
|
|
42
44
|
| --help | | false | Output usage information. |
|
package/bin/async.js
CHANGED
|
@@ -16,11 +16,13 @@ import { getArchiveFilename, getFilename } from "./naming.js";
|
|
|
16
16
|
import {
|
|
17
17
|
getEpisodeAudioUrlAndExt,
|
|
18
18
|
getArchiveKey,
|
|
19
|
+
getTempPath,
|
|
19
20
|
runFfmpeg,
|
|
20
21
|
runExec,
|
|
21
22
|
writeItemMeta,
|
|
22
23
|
writeToArchive,
|
|
23
24
|
getUrlEmbed,
|
|
25
|
+
getIsInArchive,
|
|
24
26
|
} from "./util.js";
|
|
25
27
|
|
|
26
28
|
const pipeline = promisify(stream.pipeline);
|
|
@@ -43,6 +45,11 @@ const download = async ({
|
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
if (key && archive && getIsInArchive({ key, archive })) {
|
|
49
|
+
logMessage("Download exists in archive. Skipping...");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
let embeddedUrl = null;
|
|
47
54
|
if (filterUrlTracking) {
|
|
48
55
|
logMessage("Attempting to find embedded URL...");
|
|
@@ -63,9 +70,10 @@ const download = async ({
|
|
|
63
70
|
},
|
|
64
71
|
});
|
|
65
72
|
|
|
73
|
+
const tempOutputPath = getTempPath(outputPath);
|
|
66
74
|
const removeFile = () => {
|
|
67
|
-
if (fs.existsSync(
|
|
68
|
-
fs.unlinkSync(
|
|
75
|
+
if (fs.existsSync(tempOutputPath)) {
|
|
76
|
+
fs.unlinkSync(tempOutputPath);
|
|
69
77
|
}
|
|
70
78
|
};
|
|
71
79
|
|
|
@@ -101,14 +109,14 @@ const download = async ({
|
|
|
101
109
|
|
|
102
110
|
await pipeline(
|
|
103
111
|
got.stream(finalUrl).on("downloadProgress", onDownloadProgress),
|
|
104
|
-
fs.createWriteStream(
|
|
112
|
+
fs.createWriteStream(tempOutputPath)
|
|
105
113
|
);
|
|
106
114
|
} catch (error) {
|
|
107
115
|
removeFile();
|
|
108
116
|
throw error;
|
|
109
117
|
}
|
|
110
118
|
|
|
111
|
-
const fileSize = fs.statSync(
|
|
119
|
+
const fileSize = fs.statSync(tempOutputPath).size;
|
|
112
120
|
|
|
113
121
|
if (fileSize === 0) {
|
|
114
122
|
removeFile();
|
|
@@ -121,13 +129,7 @@ const download = async ({
|
|
|
121
129
|
return;
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
logMessage(
|
|
126
|
-
"File size differs from expected content length. Suggestion: verify file works as expected",
|
|
127
|
-
LOG_LEVELS.important
|
|
128
|
-
);
|
|
129
|
-
logMessage(`${outputPath}`, LOG_LEVELS.important);
|
|
130
|
-
}
|
|
132
|
+
fs.renameSync(tempOutputPath, outputPath);
|
|
131
133
|
|
|
132
134
|
logMessage("Download complete!");
|
|
133
135
|
|
package/bin/bin.js
CHANGED
|
@@ -293,6 +293,24 @@ const main = async () => {
|
|
|
293
293
|
filterUrlTracking,
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
if (hasErrors && numEpisodesDownloaded !== targetItems.length) {
|
|
297
|
+
logMessage(
|
|
298
|
+
`\n${numEpisodesDownloaded} of ${pluralize(
|
|
299
|
+
"episode",
|
|
300
|
+
targetItems.length,
|
|
301
|
+
true
|
|
302
|
+
)} downloaded\n`
|
|
303
|
+
);
|
|
304
|
+
} else if (numEpisodesDownloaded > 0) {
|
|
305
|
+
logMessage(
|
|
306
|
+
`\nSuccessfully downloaded ${pluralize(
|
|
307
|
+
"episode",
|
|
308
|
+
numEpisodesDownloaded,
|
|
309
|
+
true
|
|
310
|
+
)}\n`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
296
314
|
if (numEpisodesDownloaded === 0) {
|
|
297
315
|
process.exit(ERROR_STATUSES.nothingDownloaded);
|
|
298
316
|
}
|
package/bin/util.js
CHANGED
|
@@ -15,6 +15,10 @@ const parser = new rssParser({
|
|
|
15
15
|
defaultRSS: 2.0,
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
const getTempPath = (path) => {
|
|
19
|
+
return `${path}.tmp`;
|
|
20
|
+
};
|
|
21
|
+
|
|
18
22
|
const getArchiveKey = ({ prefix, name }) => {
|
|
19
23
|
return `${prefix}-${name}`;
|
|
20
24
|
};
|
|
@@ -211,22 +215,20 @@ const getItemsToDownload = ({
|
|
|
211
215
|
}),
|
|
212
216
|
});
|
|
213
217
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
});
|
|
229
|
-
}
|
|
218
|
+
const episodeImageName = getFilename({
|
|
219
|
+
item,
|
|
220
|
+
feed,
|
|
221
|
+
url: episodeAudioUrl,
|
|
222
|
+
ext: episodeImageFileExt,
|
|
223
|
+
template: episodeTemplate,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const outputImagePath = path.resolve(basePath, episodeImageName);
|
|
227
|
+
item._extra_downloads.push({
|
|
228
|
+
url: episodeImageUrl,
|
|
229
|
+
outputPath: outputImagePath,
|
|
230
|
+
key: episodeImageArchiveKey,
|
|
231
|
+
});
|
|
230
232
|
}
|
|
231
233
|
}
|
|
232
234
|
|
|
@@ -291,34 +293,24 @@ const logItemsList = ({
|
|
|
291
293
|
|
|
292
294
|
const writeFeedMeta = ({ outputPath, feed, key, archive, override }) => {
|
|
293
295
|
if (key && archive && getIsInArchive({ key, archive })) {
|
|
294
|
-
logMessage("Feed metadata exists in archive. Skipping
|
|
296
|
+
logMessage("Feed metadata exists in archive. Skipping...");
|
|
295
297
|
return;
|
|
296
298
|
}
|
|
297
299
|
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
const output = {};
|
|
301
|
+
["title", "description", "link", "feedUrl", "managingEditor"].forEach(
|
|
302
|
+
(key) => {
|
|
303
|
+
if (feed[key]) {
|
|
304
|
+
output[key] = feed[key];
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
);
|
|
303
308
|
|
|
304
309
|
try {
|
|
305
310
|
if (override || !fs.existsSync(outputPath)) {
|
|
306
|
-
fs.writeFileSync(
|
|
307
|
-
outputPath,
|
|
308
|
-
JSON.stringify(
|
|
309
|
-
{
|
|
310
|
-
title,
|
|
311
|
-
description,
|
|
312
|
-
link,
|
|
313
|
-
feedUrl,
|
|
314
|
-
managingEditor,
|
|
315
|
-
},
|
|
316
|
-
null,
|
|
317
|
-
4
|
|
318
|
-
)
|
|
319
|
-
);
|
|
311
|
+
fs.writeFileSync(outputPath, JSON.stringify(output, null, 4));
|
|
320
312
|
} else {
|
|
321
|
-
logMessage("Feed metadata exists locally. Skipping
|
|
313
|
+
logMessage("Feed metadata exists locally. Skipping...");
|
|
322
314
|
}
|
|
323
315
|
|
|
324
316
|
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
@@ -344,36 +336,22 @@ const writeItemMeta = ({
|
|
|
344
336
|
override,
|
|
345
337
|
}) => {
|
|
346
338
|
if (key && archive && getIsInArchive({ key, archive })) {
|
|
347
|
-
logMessage(
|
|
348
|
-
`${marker} | Episode metadata exists in archive. Skipping write...`
|
|
349
|
-
);
|
|
339
|
+
logMessage(`${marker} | Episode metadata exists in archive. Skipping...`);
|
|
350
340
|
return;
|
|
351
341
|
}
|
|
352
342
|
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
343
|
+
const output = {};
|
|
344
|
+
["title", "contentSnippet", "pubDate", "creator"].forEach((key) => {
|
|
345
|
+
if (item[key]) {
|
|
346
|
+
output[key] = item[key];
|
|
347
|
+
}
|
|
348
|
+
});
|
|
357
349
|
|
|
358
350
|
try {
|
|
359
351
|
if (override || !fs.existsSync(outputPath)) {
|
|
360
|
-
fs.writeFileSync(
|
|
361
|
-
outputPath,
|
|
362
|
-
JSON.stringify(
|
|
363
|
-
{
|
|
364
|
-
title,
|
|
365
|
-
pubDate,
|
|
366
|
-
creator,
|
|
367
|
-
descriptionText,
|
|
368
|
-
},
|
|
369
|
-
null,
|
|
370
|
-
4
|
|
371
|
-
)
|
|
372
|
-
);
|
|
352
|
+
fs.writeFileSync(outputPath, JSON.stringify(output, null, 4));
|
|
373
353
|
} else {
|
|
374
|
-
logMessage(
|
|
375
|
-
`${marker} | Episode metadata exists locally. Skipping write...`
|
|
376
|
-
);
|
|
354
|
+
logMessage(`${marker} | Episode metadata exists locally. Skipping...`);
|
|
377
355
|
}
|
|
378
356
|
|
|
379
357
|
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
@@ -569,6 +547,7 @@ export {
|
|
|
569
547
|
getFeed,
|
|
570
548
|
getImageUrl,
|
|
571
549
|
getItemsToDownload,
|
|
550
|
+
getTempPath,
|
|
572
551
|
getUrlExt,
|
|
573
552
|
getUrlEmbed,
|
|
574
553
|
logFeedInfo,
|