podcast-dl 9.0.1 → 9.0.3
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/LICENSE +21 -21
- package/README.md +102 -97
- package/bin/async.js +304 -306
- package/bin/bin.js +259 -246
- package/bin/commander.js +1 -1
- package/bin/logger.js +92 -92
- package/bin/naming.js +85 -73
- package/bin/util.js +564 -561
- package/bin/validate.js +43 -43
- package/package.json +62 -62
package/bin/bin.js
CHANGED
|
@@ -1,246 +1,259 @@
|
|
|
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,
|
|
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
|
|
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
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
logMessage("\nDownloading podcast image...");
|
|
136
|
-
await download({
|
|
137
|
-
archive,
|
|
138
|
-
override,
|
|
139
|
-
marker: podcastImageUrl,
|
|
140
|
-
key: getArchiveKey({
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
feed,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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();
|
package/bin/commander.js
CHANGED