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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # podcast-dl
2
2
 
3
- ## A CLI for downloading podcasts with a focus on archiving.
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(outputPath)) {
68
- fs.unlinkSync(outputPath);
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(outputPath)
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(outputPath).size;
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
- if (expectedSize && !isNaN(expectedSize) && expectedSize !== fileSize) {
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
- if (!savedArchive.includes(episodeImageArchiveKey)) {
215
- const episodeImageName = getFilename({
216
- item,
217
- feed,
218
- url: episodeAudioUrl,
219
- ext: episodeImageFileExt,
220
- template: episodeTemplate,
221
- });
222
-
223
- const outputImagePath = path.resolve(basePath, episodeImageName);
224
- item._extra_downloads.push({
225
- url: episodeImageUrl,
226
- outputPath: outputImagePath,
227
- key: episodeImageArchiveKey,
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 write...");
296
+ logMessage("Feed metadata exists in archive. Skipping...");
295
297
  return;
296
298
  }
297
299
 
298
- const title = feed.title || null;
299
- const description = feed.description || null;
300
- const link = feed.link || null;
301
- const feedUrl = feed.feedUrl || null;
302
- const managingEditor = feed.managingEditor || null;
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 write...");
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 title = item.title || null;
354
- const descriptionText = item.contentSnippet || null;
355
- const pubDate = item.pubDate || null;
356
- const creator = item.creator || null;
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podcast-dl",
3
- "version": "7.1.0",
3
+ "version": "7.3.1",
4
4
  "description": "A CLI for downloading podcasts.",
5
5
  "type": "module",
6
6
  "bin": "./bin/bin.js",