podcast-dl 6.1.0 → 7.1.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/README.md +26 -34
- package/bin/async.js +294 -0
- package/bin/bin.js +85 -190
- package/bin/logger.js +14 -2
- package/bin/naming.js +3 -8
- package/bin/util.js +188 -230
- package/bin/validate.js +9 -3
- package/package.json +8 -5
package/bin/util.js
CHANGED
|
@@ -1,26 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logError,
|
|
15
|
-
logErrorAndExit,
|
|
16
|
-
LOG_LEVELS,
|
|
17
|
-
} = require("./logger");
|
|
18
|
-
|
|
19
|
-
const pipeline = promisify(stream.pipeline);
|
|
1
|
+
import rssParser from "rss-parser";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import dayjs from "dayjs";
|
|
5
|
+
import got from "got";
|
|
6
|
+
import util from "util";
|
|
7
|
+
import { exec } from "child_process";
|
|
8
|
+
|
|
9
|
+
import { logErrorAndExit, logMessage } from "./logger.js";
|
|
10
|
+
import { getArchiveFilename, getFilename } from "./naming.js";
|
|
11
|
+
|
|
12
|
+
const execWithPromise = util.promisify(exec);
|
|
13
|
+
|
|
20
14
|
const parser = new rssParser({
|
|
21
15
|
defaultRSS: 2.0,
|
|
22
16
|
});
|
|
23
17
|
|
|
18
|
+
const getArchiveKey = ({ prefix, name }) => {
|
|
19
|
+
return `${prefix}-${name}`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getArchive = (archive) => {
|
|
23
|
+
const archivePath = path.resolve(process.cwd(), archive);
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(archivePath)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return JSON.parse(fs.readFileSync(archivePath));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const writeToArchive = ({ key, archive }) => {
|
|
33
|
+
const archivePath = path.resolve(process.cwd(), archive);
|
|
34
|
+
const archiveResult = getArchive(archive);
|
|
35
|
+
|
|
36
|
+
if (!archiveResult.includes(key)) {
|
|
37
|
+
archiveResult.push(key);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fs.writeFileSync(archivePath, JSON.stringify(archiveResult, null, 4));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getIsInArchive = ({ key, archive }) => {
|
|
44
|
+
const archiveResult = getArchive(archive);
|
|
45
|
+
return archiveResult.includes(key);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const getPossibleUrlEmbeds = (url, maxAmount = 5) => {
|
|
49
|
+
const fullUrl = new URL(url);
|
|
50
|
+
const possibleStartIndexes = [];
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < fullUrl.pathname.length; i++) {
|
|
53
|
+
if (fullUrl.pathname[i] === "/") {
|
|
54
|
+
possibleStartIndexes.push(i);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const possibleEmbedChoices = possibleStartIndexes.map((startIndex) => {
|
|
59
|
+
let possibleEmbed = fullUrl.pathname.slice(startIndex + 1);
|
|
60
|
+
|
|
61
|
+
if (!possibleEmbed.startsWith("http")) {
|
|
62
|
+
possibleEmbed = `https://${possibleEmbed}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return decodeURIComponent(possibleEmbed);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return possibleEmbedChoices
|
|
69
|
+
.slice(Math.max(possibleEmbedChoices.length - maxAmount, 0))
|
|
70
|
+
.reverse();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getUrlEmbed = async (url) => {
|
|
74
|
+
const possibleUrlEmbeds = getPossibleUrlEmbeds(url);
|
|
75
|
+
for (const possibleUrl of possibleUrlEmbeds) {
|
|
76
|
+
try {
|
|
77
|
+
const embeddedUrl = new URL(possibleUrl);
|
|
78
|
+
await got(embeddedUrl.href, {
|
|
79
|
+
timeout: 3000,
|
|
80
|
+
method: "HEAD",
|
|
81
|
+
responseType: "json",
|
|
82
|
+
headers: {
|
|
83
|
+
accept: "*/*",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return embeddedUrl;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// do nothing
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
|
|
24
96
|
const getLoopControls = ({ limit, offset, length, reverse }) => {
|
|
25
97
|
if (reverse) {
|
|
26
98
|
const startIndex = length - 1 - offset;
|
|
@@ -48,6 +120,9 @@ const getLoopControls = ({ limit, offset, length, reverse }) => {
|
|
|
48
120
|
};
|
|
49
121
|
|
|
50
122
|
const getItemsToDownload = ({
|
|
123
|
+
archive,
|
|
124
|
+
archiveUrl,
|
|
125
|
+
basePath,
|
|
51
126
|
feed,
|
|
52
127
|
limit,
|
|
53
128
|
offset,
|
|
@@ -55,6 +130,8 @@ const getItemsToDownload = ({
|
|
|
55
130
|
before,
|
|
56
131
|
after,
|
|
57
132
|
episodeRegex,
|
|
133
|
+
episodeTemplate,
|
|
134
|
+
includeEpisodeImages,
|
|
58
135
|
}) => {
|
|
59
136
|
const { startIndex, limitCheck, next } = getLoopControls({
|
|
60
137
|
limit,
|
|
@@ -66,6 +143,8 @@ const getItemsToDownload = ({
|
|
|
66
143
|
let i = startIndex;
|
|
67
144
|
const items = [];
|
|
68
145
|
|
|
146
|
+
const savedArchive = archive ? getArchive(archive) : [];
|
|
147
|
+
|
|
69
148
|
while (limitCheck(i)) {
|
|
70
149
|
const { title, pubDate } = feed.items[i];
|
|
71
150
|
const pubDateDay = dayjs(new Date(pubDate));
|
|
@@ -98,9 +177,59 @@ const getItemsToDownload = ({
|
|
|
98
177
|
}
|
|
99
178
|
}
|
|
100
179
|
|
|
180
|
+
const { url: episodeAudioUrl, ext: audioFileExt } =
|
|
181
|
+
getEpisodeAudioUrlAndExt(feed.items[i]);
|
|
182
|
+
const key = getArchiveKey({
|
|
183
|
+
prefix: archiveUrl,
|
|
184
|
+
name: getArchiveFilename({
|
|
185
|
+
pubDate,
|
|
186
|
+
name: title,
|
|
187
|
+
ext: audioFileExt,
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (key && savedArchive.includes(key)) {
|
|
192
|
+
isValid = false;
|
|
193
|
+
}
|
|
194
|
+
|
|
101
195
|
if (isValid) {
|
|
102
196
|
const item = feed.items[i];
|
|
103
197
|
item._originalIndex = i;
|
|
198
|
+
item._extra_downloads = [];
|
|
199
|
+
|
|
200
|
+
if (includeEpisodeImages) {
|
|
201
|
+
const episodeImageUrl = getImageUrl(item);
|
|
202
|
+
|
|
203
|
+
if (episodeImageUrl) {
|
|
204
|
+
const episodeImageFileExt = getUrlExt(episodeImageUrl);
|
|
205
|
+
const episodeImageArchiveKey = getArchiveKey({
|
|
206
|
+
prefix: archiveUrl,
|
|
207
|
+
name: getArchiveFilename({
|
|
208
|
+
pubDate,
|
|
209
|
+
name: title,
|
|
210
|
+
ext: episodeImageFileExt,
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
|
|
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
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
104
233
|
items.push(item);
|
|
105
234
|
}
|
|
106
235
|
|
|
@@ -111,9 +240,9 @@ const getItemsToDownload = ({
|
|
|
111
240
|
};
|
|
112
241
|
|
|
113
242
|
const logFeedInfo = (feed) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
243
|
+
logMessage(feed.title);
|
|
244
|
+
logMessage(feed.description);
|
|
245
|
+
logMessage();
|
|
117
246
|
};
|
|
118
247
|
|
|
119
248
|
const ITEM_LIST_FORMATS = {
|
|
@@ -148,6 +277,7 @@ const logItemsList = ({
|
|
|
148
277
|
pubDate: item.pubDate,
|
|
149
278
|
};
|
|
150
279
|
});
|
|
280
|
+
|
|
151
281
|
if (!tableData.length) {
|
|
152
282
|
logErrorAndExit("No episodes found with provided criteria to list");
|
|
153
283
|
}
|
|
@@ -159,46 +289,9 @@ const logItemsList = ({
|
|
|
159
289
|
}
|
|
160
290
|
};
|
|
161
291
|
|
|
162
|
-
const logItemInfo = (item, logLevel) => {
|
|
163
|
-
const { title, pubDate } = item;
|
|
164
|
-
|
|
165
|
-
logMessage(`Title: ${title}`, logLevel);
|
|
166
|
-
logMessage(`Publish Date: ${pubDate}`, logLevel);
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const getArchiveKey = ({ prefix, name }) => {
|
|
170
|
-
return `${prefix}-${name}`;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const writeToArchive = ({ key, archive }) => {
|
|
174
|
-
let archiveResult = [];
|
|
175
|
-
const archivePath = path.resolve(process.cwd(), archive);
|
|
176
|
-
|
|
177
|
-
if (fs.existsSync(archivePath)) {
|
|
178
|
-
archiveResult = JSON.parse(fs.readFileSync(archivePath));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!archiveResult.includes(key)) {
|
|
182
|
-
archiveResult.push(key);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
fs.writeFileSync(archivePath, JSON.stringify(archiveResult, null, 4));
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const getIsInArchive = ({ key, archive }) => {
|
|
189
|
-
const archivePath = path.resolve(process.cwd(), archive);
|
|
190
|
-
|
|
191
|
-
if (!fs.existsSync(archivePath)) {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const archiveResult = JSON.parse(fs.readFileSync(archivePath));
|
|
196
|
-
return archiveResult.includes(key);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
292
|
const writeFeedMeta = ({ outputPath, feed, key, archive, override }) => {
|
|
200
293
|
if (key && archive && getIsInArchive({ key, archive })) {
|
|
201
|
-
logMessage("Feed metadata exists in archive. Skipping write");
|
|
294
|
+
logMessage("Feed metadata exists in archive. Skipping write...");
|
|
202
295
|
return;
|
|
203
296
|
}
|
|
204
297
|
|
|
@@ -225,24 +318,35 @@ const writeFeedMeta = ({ outputPath, feed, key, archive, override }) => {
|
|
|
225
318
|
)
|
|
226
319
|
);
|
|
227
320
|
} else {
|
|
228
|
-
logMessage("Feed metadata exists locally. Skipping write");
|
|
321
|
+
logMessage("Feed metadata exists locally. Skipping write...");
|
|
229
322
|
}
|
|
230
323
|
|
|
231
324
|
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
232
325
|
try {
|
|
233
326
|
writeToArchive({ key, archive });
|
|
234
327
|
} catch (error) {
|
|
235
|
-
|
|
328
|
+
throw new Error(`Error writing to archive: ${error.toString()}`);
|
|
236
329
|
}
|
|
237
330
|
}
|
|
238
331
|
} catch (error) {
|
|
239
|
-
|
|
332
|
+
throw new Error(
|
|
333
|
+
`Unable to save metadata file for feed: ${error.toString()}`
|
|
334
|
+
);
|
|
240
335
|
}
|
|
241
336
|
};
|
|
242
337
|
|
|
243
|
-
const writeItemMeta = ({
|
|
338
|
+
const writeItemMeta = ({
|
|
339
|
+
marker,
|
|
340
|
+
outputPath,
|
|
341
|
+
item,
|
|
342
|
+
key,
|
|
343
|
+
archive,
|
|
344
|
+
override,
|
|
345
|
+
}) => {
|
|
244
346
|
if (key && archive && getIsInArchive({ key, archive })) {
|
|
245
|
-
logMessage(
|
|
347
|
+
logMessage(
|
|
348
|
+
`${marker} | Episode metadata exists in archive. Skipping write...`
|
|
349
|
+
);
|
|
246
350
|
return;
|
|
247
351
|
}
|
|
248
352
|
|
|
@@ -267,23 +371,25 @@ const writeItemMeta = ({ outputPath, item, key, archive, override }) => {
|
|
|
267
371
|
)
|
|
268
372
|
);
|
|
269
373
|
} else {
|
|
270
|
-
logMessage(
|
|
374
|
+
logMessage(
|
|
375
|
+
`${marker} | Episode metadata exists locally. Skipping write...`
|
|
376
|
+
);
|
|
271
377
|
}
|
|
272
378
|
|
|
273
379
|
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
274
380
|
try {
|
|
275
381
|
writeToArchive({ key, archive });
|
|
276
382
|
} catch (error) {
|
|
277
|
-
|
|
383
|
+
throw new Error("Error writing to archive", error);
|
|
278
384
|
}
|
|
279
385
|
}
|
|
280
386
|
} catch (error) {
|
|
281
|
-
|
|
387
|
+
throw new Error("Unable to save meta file for episode", error);
|
|
282
388
|
}
|
|
283
389
|
};
|
|
284
390
|
|
|
285
391
|
const getUrlExt = (url) => {
|
|
286
|
-
const { pathname } =
|
|
392
|
+
const { pathname } = new URL(url);
|
|
287
393
|
|
|
288
394
|
if (!pathname) {
|
|
289
395
|
return "";
|
|
@@ -349,149 +455,8 @@ const getImageUrl = ({ image, itunes }) => {
|
|
|
349
455
|
return null;
|
|
350
456
|
};
|
|
351
457
|
|
|
352
|
-
const BYTES_IN_MB = 1000000;
|
|
353
|
-
const printProgress = ({ percent, total, transferred }) => {
|
|
354
|
-
if (!getShouldOutputProgressIndicator()) {
|
|
355
|
-
/*
|
|
356
|
-
Non-TTY environments do not have access to `stdout.clearLine` and
|
|
357
|
-
`stdout.cursorTo`. Skip download progress logging in these environments.
|
|
358
|
-
*/
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
let line = "downloading...";
|
|
363
|
-
const percentRounded = (percent * 100).toFixed(2);
|
|
364
|
-
|
|
365
|
-
if (transferred > 0) {
|
|
366
|
-
/*
|
|
367
|
-
* Got has a bug where it'll set percent to 1 when the download first starts.
|
|
368
|
-
* Ignore percent until transfer has started.
|
|
369
|
-
*/
|
|
370
|
-
line += ` ${percentRounded}%`;
|
|
371
|
-
|
|
372
|
-
if (total) {
|
|
373
|
-
const totalMBs = total / BYTES_IN_MB;
|
|
374
|
-
const roundedTotalMbs = totalMBs.toFixed(2);
|
|
375
|
-
line += ` of ${roundedTotalMbs} MB`;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
process.stdout.clearLine();
|
|
380
|
-
process.stdout.cursorTo(0);
|
|
381
|
-
process.stdout.write(line);
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
const download = async ({
|
|
385
|
-
url,
|
|
386
|
-
outputPath,
|
|
387
|
-
key,
|
|
388
|
-
archive,
|
|
389
|
-
override,
|
|
390
|
-
onSkip,
|
|
391
|
-
onBeforeDownload,
|
|
392
|
-
onAfterDownload,
|
|
393
|
-
}) => {
|
|
394
|
-
if (key && archive && getIsInArchive({ key, archive })) {
|
|
395
|
-
if (onSkip) {
|
|
396
|
-
onSkip();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
logMessage("Download exists in archive. Skipping");
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (!override && fs.existsSync(outputPath)) {
|
|
404
|
-
if (onSkip) {
|
|
405
|
-
onSkip();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
logMessage("Download exists locally. Skipping");
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (onBeforeDownload) {
|
|
413
|
-
onBeforeDownload();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const headResponse = await got(url, {
|
|
417
|
-
timeout: 5000,
|
|
418
|
-
method: "HEAD",
|
|
419
|
-
responseType: "json",
|
|
420
|
-
headers: {
|
|
421
|
-
accept: "*/*",
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
const removeFile = () => {
|
|
426
|
-
if (fs.existsSync(outputPath)) {
|
|
427
|
-
fs.unlinkSync(outputPath);
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
const expectedSize =
|
|
432
|
-
headResponse &&
|
|
433
|
-
headResponse.headers &&
|
|
434
|
-
headResponse.headers["content-length"]
|
|
435
|
-
? parseInt(headResponse.headers["content-length"])
|
|
436
|
-
: 0;
|
|
437
|
-
|
|
438
|
-
if (!getShouldOutputProgressIndicator()) {
|
|
439
|
-
logMessage(
|
|
440
|
-
`Starting download${
|
|
441
|
-
expectedSize
|
|
442
|
-
? ` of ${(expectedSize / BYTES_IN_MB).toFixed(2)} MB`
|
|
443
|
-
: "..."
|
|
444
|
-
}`
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
try {
|
|
449
|
-
await pipeline(
|
|
450
|
-
got.stream(url).on("downloadProgress", (progress) => {
|
|
451
|
-
printProgress(progress);
|
|
452
|
-
}),
|
|
453
|
-
fs.createWriteStream(outputPath)
|
|
454
|
-
);
|
|
455
|
-
} catch (error) {
|
|
456
|
-
removeFile();
|
|
457
|
-
|
|
458
|
-
throw error;
|
|
459
|
-
} finally {
|
|
460
|
-
if (getShouldOutputProgressIndicator()) {
|
|
461
|
-
console.log();
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const fileSize = fs.statSync(outputPath).size;
|
|
466
|
-
|
|
467
|
-
if (fileSize === 0) {
|
|
468
|
-
removeFile();
|
|
469
|
-
throw new Error("Unable to write to file. Suggestion: verify permissions");
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (expectedSize && !isNaN(expectedSize) && expectedSize !== fileSize) {
|
|
473
|
-
logMessage(
|
|
474
|
-
"File size differs from expected content length. Suggestion: verify file works as expected",
|
|
475
|
-
LOG_LEVELS.important
|
|
476
|
-
);
|
|
477
|
-
logMessage(outputPath, LOG_LEVELS.important);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (onAfterDownload) {
|
|
481
|
-
onAfterDownload();
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
485
|
-
try {
|
|
486
|
-
writeToArchive({ key, archive });
|
|
487
|
-
} catch (error) {
|
|
488
|
-
logError("Error writing to archive", error);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
|
|
493
458
|
const getFeed = async (url) => {
|
|
494
|
-
const { href } =
|
|
459
|
+
const { href } = new URL(url);
|
|
495
460
|
|
|
496
461
|
let feed;
|
|
497
462
|
try {
|
|
@@ -503,7 +468,7 @@ const getFeed = async (url) => {
|
|
|
503
468
|
return feed;
|
|
504
469
|
};
|
|
505
470
|
|
|
506
|
-
const runFfmpeg = ({
|
|
471
|
+
const runFfmpeg = async ({
|
|
507
472
|
feed,
|
|
508
473
|
item,
|
|
509
474
|
itemIndex,
|
|
@@ -517,8 +482,7 @@ const runFfmpeg = ({
|
|
|
517
482
|
}
|
|
518
483
|
|
|
519
484
|
if (!outputPath.endsWith(".mp3")) {
|
|
520
|
-
|
|
521
|
-
return;
|
|
485
|
+
throw new Error("Not an .mp3 file. Unable to run ffmpeg.");
|
|
522
486
|
}
|
|
523
487
|
|
|
524
488
|
let command = `ffmpeg -loglevel quiet -i "${outputPath}"`;
|
|
@@ -570,26 +534,21 @@ const runFfmpeg = ({
|
|
|
570
534
|
const tmpMp3Path = `${outputPath}.tmp.mp3`;
|
|
571
535
|
command += ` "${tmpMp3Path}"`;
|
|
572
536
|
|
|
573
|
-
logMessage("Running ffmpeg...");
|
|
574
|
-
|
|
575
537
|
try {
|
|
576
|
-
|
|
538
|
+
await execWithPromise(command, { stdio: "ignore" });
|
|
577
539
|
} catch (error) {
|
|
578
|
-
logError("Error running ffmpeg", error);
|
|
579
|
-
|
|
580
540
|
if (fs.existsSync(tmpMp3Path)) {
|
|
581
|
-
logMessage("Cleaning up temporary file...");
|
|
582
541
|
fs.unlinkSync(tmpMp3Path);
|
|
583
542
|
}
|
|
584
543
|
|
|
585
|
-
|
|
544
|
+
throw error;
|
|
586
545
|
}
|
|
587
546
|
|
|
588
547
|
fs.unlinkSync(outputPath);
|
|
589
548
|
fs.renameSync(tmpMp3Path, outputPath);
|
|
590
549
|
};
|
|
591
550
|
|
|
592
|
-
const runExec = ({ exec, outputPodcastPath, episodeFilename }) => {
|
|
551
|
+
const runExec = async ({ exec, outputPodcastPath, episodeFilename }) => {
|
|
593
552
|
const filenameBase = episodeFilename.substring(
|
|
594
553
|
0,
|
|
595
554
|
episodeFilename.lastIndexOf(".")
|
|
@@ -597,23 +556,22 @@ const runExec = ({ exec, outputPodcastPath, episodeFilename }) => {
|
|
|
597
556
|
const execCmd = exec
|
|
598
557
|
.replace(/{}/g, `"${outputPodcastPath}"`)
|
|
599
558
|
.replace(/{filenameBase}/g, `"${filenameBase}"`);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
} catch (error) {
|
|
603
|
-
logError(`--exec process error: exit code ${error.status}`, error);
|
|
604
|
-
}
|
|
559
|
+
|
|
560
|
+
await execWithPromise(execCmd, { stdio: "ignore" });
|
|
605
561
|
};
|
|
606
562
|
|
|
607
|
-
|
|
608
|
-
|
|
563
|
+
export {
|
|
564
|
+
getArchive,
|
|
565
|
+
getIsInArchive,
|
|
609
566
|
getArchiveKey,
|
|
567
|
+
writeToArchive,
|
|
610
568
|
getEpisodeAudioUrlAndExt,
|
|
611
569
|
getFeed,
|
|
612
570
|
getImageUrl,
|
|
613
571
|
getItemsToDownload,
|
|
614
572
|
getUrlExt,
|
|
573
|
+
getUrlEmbed,
|
|
615
574
|
logFeedInfo,
|
|
616
|
-
logItemInfo,
|
|
617
575
|
ITEM_LIST_FORMATS,
|
|
618
576
|
logItemsList,
|
|
619
577
|
writeFeedMeta,
|
package/bin/validate.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { sync as commandExistsSync } from "command-exists";
|
|
2
|
+
|
|
3
|
+
import { logErrorAndExit } from "./logger.js";
|
|
2
4
|
|
|
3
5
|
const createParseNumber = ({ min, name, required = true }) => {
|
|
4
6
|
return (value) => {
|
|
@@ -24,6 +26,10 @@ const createParseNumber = ({ min, name, required = true }) => {
|
|
|
24
26
|
};
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
const hasFfmpeg = () => {
|
|
30
|
+
if (!commandExistsSync("ffmpeg")) {
|
|
31
|
+
logErrorAndExit('option specified requires "ffmpeg" be available');
|
|
32
|
+
}
|
|
29
33
|
};
|
|
34
|
+
|
|
35
|
+
export { createParseNumber, hasFfmpeg };
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "podcast-dl",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "A CLI for downloading podcasts.",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"bin": "./bin/bin.js",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"build": "rimraf ./binaries && npm run pkg",
|
|
8
8
|
"lint": "eslint ./bin",
|
|
9
|
-
"pkg": "npx pkg ./ --targets node16-linux-x64,node16-win-x64,node16-macos-x64 --out-path ./binaries",
|
|
10
9
|
"release": "standard-version"
|
|
11
10
|
},
|
|
12
11
|
"lint-staged": {
|
|
@@ -27,7 +26,7 @@
|
|
|
27
26
|
"cli"
|
|
28
27
|
],
|
|
29
28
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
29
|
+
"node": ">=14.17.6"
|
|
31
30
|
},
|
|
32
31
|
"repository": {
|
|
33
32
|
"type": "git",
|
|
@@ -48,10 +47,14 @@
|
|
|
48
47
|
"standard-version": "^9.0.0"
|
|
49
48
|
},
|
|
50
49
|
"dependencies": {
|
|
50
|
+
"command-exists": "^1.2.9",
|
|
51
51
|
"commander": "^5.1.0",
|
|
52
52
|
"dayjs": "^1.8.25",
|
|
53
53
|
"filenamify": "^4.1.0",
|
|
54
54
|
"got": "^11.0.2",
|
|
55
|
-
"
|
|
55
|
+
"p-limit": "^4.0.0",
|
|
56
|
+
"pluralize": "^8.0.0",
|
|
57
|
+
"rss-parser": "^3.7.6",
|
|
58
|
+
"throttle-debounce": "^3.0.1"
|
|
56
59
|
}
|
|
57
60
|
}
|