podcast-dl 9.0.3 → 9.2.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/bin/bin.js CHANGED
@@ -1,259 +1,261 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "fs";
4
- import _path from "path";
5
- import commander from "commander";
6
- import pluralize from "pluralize";
7
- import { bootstrap as bootstrapProxy } from "global-agent";
8
-
9
- import { setupCommander } from "./commander.js";
10
- import { download } from "./async.js";
11
- import {
12
- getArchiveKey,
13
- getFeed,
14
- getImageUrl,
15
- getItemsToDownload,
16
- getUrlExt,
17
- logFeedInfo,
18
- logItemsList,
19
- writeFeedMeta,
20
- } from "./util.js";
21
- import {
22
- ERROR_STATUSES,
23
- LOG_LEVELS,
24
- logMessage,
25
- logError,
26
- logErrorAndExit,
27
- } from "./logger.js";
28
- import { getFolderName, getSimpleFilename } from "./naming.js";
29
- import { downloadItemsAsync } from "./async.js";
30
-
31
- setupCommander(commander, process.argv);
32
-
33
- const {
34
- url,
35
- outDir,
36
- episodeTemplate,
37
- episodeDigits,
38
- episodeSourceOrder,
39
- includeMeta,
40
- includeEpisodeMeta,
41
- includeEpisodeImages,
42
- offset,
43
- limit,
44
- episodeRegex,
45
- after,
46
- before,
47
- override,
48
- reverse,
49
- info,
50
- list,
51
- exec,
52
- mono,
53
- threads,
54
- attempts,
55
- parserConfig,
56
- proxy,
57
- addMp3Metadata: addMp3MetadataFlag,
58
- adjustBitrate: bitrate,
59
- } = commander;
60
-
61
- let { archive } = commander;
62
-
63
- const main = async () => {
64
- if (!url) {
65
- logErrorAndExit("No URL provided");
66
- }
67
-
68
- if (proxy) {
69
- bootstrapProxy();
70
- }
71
-
72
- const { hostname, pathname } = new URL(url);
73
- const archiveUrl = `${hostname}${pathname}`;
74
- const feed = await getFeed(url, parserConfig);
75
- const basePath = _path.resolve(
76
- process.cwd(),
77
- getFolderName({ feed, template: outDir })
78
- );
79
-
80
- if (info) {
81
- logFeedInfo(feed);
82
- }
83
-
84
- if (list) {
85
- if (feed?.items?.length) {
86
- const listFormat = typeof list === "boolean" ? "table" : list;
87
- logItemsList({
88
- type: listFormat,
89
- feed,
90
- limit,
91
- offset,
92
- reverse,
93
- after,
94
- before,
95
- episodeRegex,
96
- });
97
- } else {
98
- logErrorAndExit("No episodes found to list");
99
- }
100
- }
101
-
102
- if (info || list) {
103
- process.exit(0);
104
- }
105
-
106
- logFeedInfo(feed);
107
-
108
- if (!fs.existsSync(basePath)) {
109
- logMessage(`${basePath} does not exist. Creating...`, LOG_LEVELS.important);
110
- fs.mkdirSync(basePath, { recursive: true });
111
- }
112
-
113
- if (archive) {
114
- archive =
115
- typeof archive === "boolean"
116
- ? "./{{podcast_title}}/archive.json"
117
- : archive;
118
- archive = getFolderName({ feed, template: archive });
119
- }
120
-
121
- if (includeMeta) {
122
- const podcastImageUrl = getImageUrl(feed);
123
-
124
- if (podcastImageUrl) {
125
- const podcastImageFileExt = getUrlExt(podcastImageUrl);
126
- const outputImagePath = _path.resolve(
127
- basePath,
128
- getSimpleFilename(
129
- feed.title ? feed.title : "image",
130
- feed.title ? `.image${podcastImageFileExt}` : podcastImageFileExt
131
- )
132
- );
133
-
134
- try {
135
- logMessage("\nDownloading podcast image...");
136
- await download({
137
- archive,
138
- override,
139
- marker: podcastImageUrl,
140
- key: getArchiveKey({
141
- prefix: archiveUrl,
142
- name: `${
143
- feed.title ? `${feed.title}.image` : "image"
144
- }${podcastImageFileExt}`,
145
- }),
146
- outputPath: outputImagePath,
147
- url: podcastImageUrl,
148
- maxAttempts: attempts,
149
- });
150
- } catch (error) {
151
- logError("Unable to download podcast image", error);
152
- }
153
- }
154
-
155
- const outputMetaPath = _path.resolve(
156
- basePath,
157
- getSimpleFilename(
158
- feed.title ? feed.title : "meta",
159
- feed.title ? ".meta.json" : ".json"
160
- )
161
- );
162
-
163
- try {
164
- logMessage("\nSaving podcast metadata...");
165
- writeFeedMeta({
166
- archive,
167
- override,
168
- feed,
169
- key: getArchiveKey({
170
- prefix: archiveUrl,
171
- name: `${feed.title ? `${feed.title}.meta` : "meta"}.json`,
172
- }),
173
- outputPath: outputMetaPath,
174
- });
175
- } catch (error) {
176
- logError("Unable to save podcast metadata", error);
177
- }
178
- }
179
-
180
- if (!feed.items || feed.items.length === 0) {
181
- logErrorAndExit("No episodes found to download");
182
- }
183
-
184
- if (offset >= feed.items.length) {
185
- logErrorAndExit("--offset too large. No episodes to download.");
186
- }
187
-
188
- const targetItems = getItemsToDownload({
189
- archive,
190
- archiveUrl,
191
- basePath,
192
- feed,
193
- limit,
194
- offset,
195
- reverse,
196
- after,
197
- before,
198
- episodeDigits,
199
- episodeRegex,
200
- episodeSourceOrder,
201
- episodeTemplate,
202
- includeEpisodeImages,
203
- });
204
-
205
- if (!targetItems.length) {
206
- logErrorAndExit("No episodes found with provided criteria to download");
207
- }
208
-
209
- logMessage(
210
- `\nStarting download of ${pluralize("episode", targetItems.length, true)}\n`
211
- );
212
-
213
- const { numEpisodesDownloaded, hasErrors } = await downloadItemsAsync({
214
- addMp3MetadataFlag,
215
- archive,
216
- archiveUrl,
217
- attempts,
218
- basePath,
219
- bitrate,
220
- episodeTemplate,
221
- episodeDigits,
222
- episodeSourceOrder,
223
- exec,
224
- feed,
225
- includeEpisodeMeta,
226
- mono,
227
- override,
228
- targetItems,
229
- threads,
230
- });
231
-
232
- if (hasErrors && numEpisodesDownloaded !== targetItems.length) {
233
- logMessage(
234
- `\n${numEpisodesDownloaded} of ${pluralize(
235
- "episode",
236
- targetItems.length,
237
- true
238
- )} downloaded\n`
239
- );
240
- } else if (numEpisodesDownloaded > 0) {
241
- logMessage(
242
- `\nSuccessfully downloaded ${pluralize(
243
- "episode",
244
- numEpisodesDownloaded,
245
- true
246
- )}\n`
247
- );
248
- }
249
-
250
- if (numEpisodesDownloaded === 0) {
251
- process.exit(ERROR_STATUSES.nothingDownloaded);
252
- }
253
-
254
- if (hasErrors) {
255
- process.exit(ERROR_STATUSES.completedWithErrors);
256
- }
257
- };
258
-
259
- main();
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs";
4
+ import _path from "path";
5
+ import commander from "commander";
6
+ import pluralize from "pluralize";
7
+ import { bootstrap as bootstrapProxy } from "global-agent";
8
+
9
+ import { setupCommander } from "./commander.js";
10
+ import { download } from "./async.js";
11
+ import {
12
+ getArchiveKey,
13
+ getFeed,
14
+ getImageUrl,
15
+ getItemsToDownload,
16
+ getUrlExt,
17
+ logFeedInfo,
18
+ logItemsList,
19
+ writeFeedMeta,
20
+ } from "./util.js";
21
+ import {
22
+ ERROR_STATUSES,
23
+ LOG_LEVELS,
24
+ logMessage,
25
+ logError,
26
+ logErrorAndExit,
27
+ } from "./logger.js";
28
+ import { getFolderName, getSimpleFilename } from "./naming.js";
29
+ import { downloadItemsAsync } from "./async.js";
30
+
31
+ setupCommander(commander, process.argv);
32
+
33
+ const {
34
+ url,
35
+ outDir,
36
+ episodeTemplate,
37
+ episodeDigits,
38
+ episodeSourceOrder,
39
+ includeMeta,
40
+ includeEpisodeMeta,
41
+ includeEpisodeImages,
42
+ offset,
43
+ limit,
44
+ episodeRegex,
45
+ after,
46
+ before,
47
+ override,
48
+ alwaysPostprocess,
49
+ reverse,
50
+ info,
51
+ list,
52
+ exec,
53
+ mono,
54
+ threads,
55
+ attempts,
56
+ parserConfig,
57
+ proxy,
58
+ addMp3Metadata: addMp3MetadataFlag,
59
+ adjustBitrate: bitrate,
60
+ } = commander;
61
+
62
+ let { archive } = commander;
63
+
64
+ const main = async () => {
65
+ if (!url) {
66
+ logErrorAndExit("No URL provided");
67
+ }
68
+
69
+ if (proxy) {
70
+ bootstrapProxy();
71
+ }
72
+
73
+ const { hostname, pathname } = new URL(url);
74
+ const archiveUrl = `${hostname}${pathname}`;
75
+ const feed = await getFeed(url, parserConfig);
76
+ const basePath = _path.resolve(
77
+ process.cwd(),
78
+ getFolderName({ feed, template: outDir })
79
+ );
80
+
81
+ if (info) {
82
+ logFeedInfo(feed);
83
+ }
84
+
85
+ if (list) {
86
+ if (feed?.items?.length) {
87
+ const listFormat = typeof list === "boolean" ? "table" : list;
88
+ logItemsList({
89
+ type: listFormat,
90
+ feed,
91
+ limit,
92
+ offset,
93
+ reverse,
94
+ after,
95
+ before,
96
+ episodeRegex,
97
+ });
98
+ } else {
99
+ logErrorAndExit("No episodes found to list");
100
+ }
101
+ }
102
+
103
+ if (info || list) {
104
+ process.exit(0);
105
+ }
106
+
107
+ logFeedInfo(feed);
108
+
109
+ if (!fs.existsSync(basePath)) {
110
+ logMessage(`${basePath} does not exist. Creating...`, LOG_LEVELS.important);
111
+ fs.mkdirSync(basePath, { recursive: true });
112
+ }
113
+
114
+ if (archive) {
115
+ archive =
116
+ typeof archive === "boolean"
117
+ ? "./{{podcast_title}}/archive.json"
118
+ : archive;
119
+ archive = getFolderName({ feed, template: archive });
120
+ }
121
+
122
+ if (includeMeta) {
123
+ const podcastImageUrl = getImageUrl(feed);
124
+
125
+ if (podcastImageUrl) {
126
+ const podcastImageFileExt = getUrlExt(podcastImageUrl);
127
+ const outputImagePath = _path.resolve(
128
+ basePath,
129
+ getSimpleFilename(
130
+ feed.title ? feed.title : "image",
131
+ feed.title ? `.image${podcastImageFileExt}` : podcastImageFileExt
132
+ )
133
+ );
134
+
135
+ try {
136
+ logMessage("\nDownloading podcast image...");
137
+ await download({
138
+ archive,
139
+ override,
140
+ marker: podcastImageUrl,
141
+ key: getArchiveKey({
142
+ prefix: archiveUrl,
143
+ name: `${
144
+ feed.title ? `${feed.title}.image` : "image"
145
+ }${podcastImageFileExt}`,
146
+ }),
147
+ outputPath: outputImagePath,
148
+ url: podcastImageUrl,
149
+ maxAttempts: attempts,
150
+ });
151
+ } catch (error) {
152
+ logError("Unable to download podcast image", error);
153
+ }
154
+ }
155
+
156
+ const outputMetaPath = _path.resolve(
157
+ basePath,
158
+ getSimpleFilename(
159
+ feed.title ? feed.title : "meta",
160
+ feed.title ? ".meta.json" : ".json"
161
+ )
162
+ );
163
+
164
+ try {
165
+ logMessage("\nSaving podcast metadata...");
166
+ writeFeedMeta({
167
+ archive,
168
+ override,
169
+ feed,
170
+ key: getArchiveKey({
171
+ prefix: archiveUrl,
172
+ name: `${feed.title ? `${feed.title}.meta` : "meta"}.json`,
173
+ }),
174
+ outputPath: outputMetaPath,
175
+ });
176
+ } catch (error) {
177
+ logError("Unable to save podcast metadata", error);
178
+ }
179
+ }
180
+
181
+ if (!feed.items || feed.items.length === 0) {
182
+ logErrorAndExit("No episodes found to download");
183
+ }
184
+
185
+ if (offset >= feed.items.length) {
186
+ logErrorAndExit("--offset too large. No episodes to download.");
187
+ }
188
+
189
+ const targetItems = getItemsToDownload({
190
+ archive,
191
+ archiveUrl,
192
+ basePath,
193
+ feed,
194
+ limit,
195
+ offset,
196
+ reverse,
197
+ after,
198
+ before,
199
+ episodeDigits,
200
+ episodeRegex,
201
+ episodeSourceOrder,
202
+ episodeTemplate,
203
+ includeEpisodeImages,
204
+ });
205
+
206
+ if (!targetItems.length) {
207
+ logErrorAndExit("No episodes found with provided criteria to download");
208
+ }
209
+
210
+ logMessage(
211
+ `\nStarting download of ${pluralize("episode", targetItems.length, true)}\n`
212
+ );
213
+
214
+ const { numEpisodesDownloaded, hasErrors } = await downloadItemsAsync({
215
+ addMp3MetadataFlag,
216
+ archive,
217
+ archiveUrl,
218
+ attempts,
219
+ basePath,
220
+ bitrate,
221
+ episodeTemplate,
222
+ episodeDigits,
223
+ episodeSourceOrder,
224
+ exec,
225
+ feed,
226
+ includeEpisodeMeta,
227
+ mono,
228
+ override,
229
+ alwaysPostprocess,
230
+ targetItems,
231
+ threads,
232
+ });
233
+
234
+ if (hasErrors && numEpisodesDownloaded !== targetItems.length) {
235
+ logMessage(
236
+ `\n${numEpisodesDownloaded} of ${pluralize(
237
+ "episode",
238
+ targetItems.length,
239
+ true
240
+ )} downloaded\n`
241
+ );
242
+ } else if (numEpisodesDownloaded > 0) {
243
+ logMessage(
244
+ `\nSuccessfully downloaded ${pluralize(
245
+ "episode",
246
+ numEpisodesDownloaded,
247
+ true
248
+ )}\n`
249
+ );
250
+ }
251
+
252
+ if (numEpisodesDownloaded === 0) {
253
+ process.exit(ERROR_STATUSES.nothingDownloaded);
254
+ }
255
+
256
+ if (hasErrors) {
257
+ process.exit(ERROR_STATUSES.completedWithErrors);
258
+ }
259
+ };
260
+
261
+ main();
package/bin/commander.js CHANGED
@@ -4,7 +4,7 @@ import { logErrorAndExit } from "./logger.js";
4
4
 
5
5
  export const setupCommander = (commander, argv) => {
6
6
  commander
7
- .version("9.0.3")
7
+ .version("9.2.0")
8
8
  .option("--url <string>", "url to podcast rss feed")
9
9
  .option(
10
10
  "--out-dir <path>",
@@ -74,20 +74,24 @@ export const setupCommander = (commander, argv) => {
74
74
  )
75
75
  .option(
76
76
  "--add-mp3-metadata",
77
- "attempts to add a base level of metadata to .mp3 files using ffmpeg",
77
+ "attempts to add a base level of metadata to episode files using ffmpeg",
78
78
  hasFfmpeg
79
79
  )
80
80
  .option(
81
81
  "--adjust-bitrate <string>",
82
- "attempts to adjust bitrate of .mp3 files using ffmpeg",
82
+ "attempts to adjust bitrate of episode files using ffmpeg",
83
83
  hasFfmpeg
84
84
  )
85
85
  .option(
86
86
  "--mono",
87
- "attempts to force .mp3 files into mono using ffmpeg",
87
+ "attempts to force episode files into mono using ffmpeg",
88
88
  hasFfmpeg
89
89
  )
90
90
  .option("--override", "override local files on collision")
91
+ .option(
92
+ "--always-postprocess",
93
+ "always run additional tasks on the file regardless if the file already exists"
94
+ )
91
95
  .option("--reverse", "download episodes in reverse order")
92
96
  .option("--info", "print retrieved podcast info instead of downloading")
93
97
  .option(