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