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