podcast-dl 9.0.0 → 9.0.2
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 +1 -1
- package/bin/async.js +8 -10
- package/bin/bin.js +24 -10
- package/bin/commander.js +2 -2
- package/bin/naming.js +19 -8
- package/bin/util.js +12 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ Options that support templates allow users to specify a template for the generat
|
|
|
85
85
|
|
|
86
86
|
- `episode_path`: The path to the downloaded episode.
|
|
87
87
|
- `episode_path_base`: The path to the folder of the downloaded episode.
|
|
88
|
-
- `
|
|
88
|
+
- `episode_filename`: The filename of the episode.
|
|
89
89
|
- `episode_filename_base`: The filename of the episode without its extension.
|
|
90
90
|
|
|
91
91
|
## Log Levels
|
package/bin/async.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
getLogMessageWithMarker,
|
|
13
13
|
getShouldOutputProgressIndicator,
|
|
14
14
|
} from "./logger.js";
|
|
15
|
-
import { getArchiveFilename,
|
|
15
|
+
import { getArchiveFilename, getItemFilename } from "./naming.js";
|
|
16
16
|
import {
|
|
17
17
|
getEpisodeAudioUrlAndExt,
|
|
18
18
|
getArchiveKey,
|
|
@@ -68,12 +68,9 @@ const download = async (options) => {
|
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
const expectedSize =
|
|
72
|
-
headResponse
|
|
73
|
-
|
|
74
|
-
headResponse.headers["content-length"]
|
|
75
|
-
? parseInt(headResponse.headers["content-length"])
|
|
76
|
-
: 0;
|
|
71
|
+
const expectedSize = headResponse?.headers?.["content-length"]
|
|
72
|
+
? parseInt(headResponse.headers["content-length"])
|
|
73
|
+
: 0;
|
|
77
74
|
|
|
78
75
|
logMessage(
|
|
79
76
|
`Starting download${
|
|
@@ -156,6 +153,7 @@ let downloadItemsAsync = async ({
|
|
|
156
153
|
bitrate,
|
|
157
154
|
episodeTemplate,
|
|
158
155
|
episodeDigits,
|
|
156
|
+
episodeSourceOrder,
|
|
159
157
|
exec,
|
|
160
158
|
feed,
|
|
161
159
|
includeEpisodeMeta,
|
|
@@ -173,7 +171,7 @@ let downloadItemsAsync = async ({
|
|
|
173
171
|
const marker = threads > 1 ? `[${threadIndex}] ${item.title}` : item.title;
|
|
174
172
|
const logMessage = getLogMessageWithMarker(marker);
|
|
175
173
|
const { url: episodeAudioUrl, ext: audioFileExt } =
|
|
176
|
-
getEpisodeAudioUrlAndExt(item);
|
|
174
|
+
getEpisodeAudioUrlAndExt(item, episodeSourceOrder);
|
|
177
175
|
|
|
178
176
|
if (!episodeAudioUrl) {
|
|
179
177
|
hasErrors = true;
|
|
@@ -181,7 +179,7 @@ let downloadItemsAsync = async ({
|
|
|
181
179
|
return;
|
|
182
180
|
}
|
|
183
181
|
|
|
184
|
-
const episodeFilename =
|
|
182
|
+
const episodeFilename = getItemFilename({
|
|
185
183
|
item,
|
|
186
184
|
feed,
|
|
187
185
|
url: episodeAudioUrl,
|
|
@@ -259,7 +257,7 @@ let downloadItemsAsync = async ({
|
|
|
259
257
|
|
|
260
258
|
if (includeEpisodeMeta) {
|
|
261
259
|
const episodeMetaExt = ".meta.json";
|
|
262
|
-
const episodeMetaName =
|
|
260
|
+
const episodeMetaName = getItemFilename({
|
|
263
261
|
item,
|
|
264
262
|
feed,
|
|
265
263
|
url: episodeAudioUrl,
|
package/bin/bin.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
logError,
|
|
26
26
|
logErrorAndExit,
|
|
27
27
|
} from "./logger.js";
|
|
28
|
-
import { getFolderName,
|
|
28
|
+
import { getFolderName, getSimpleFilename } from "./naming.js";
|
|
29
29
|
import { downloadItemsAsync } from "./async.js";
|
|
30
30
|
|
|
31
31
|
setupCommander(commander, process.argv);
|
|
@@ -82,7 +82,7 @@ const main = async () => {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
if (list) {
|
|
85
|
-
if (feed
|
|
85
|
+
if (feed?.items?.length) {
|
|
86
86
|
const listFormat = typeof list === "boolean" ? "table" : list;
|
|
87
87
|
logItemsList({
|
|
88
88
|
type: listFormat,
|
|
@@ -123,12 +123,12 @@ const main = async () => {
|
|
|
123
123
|
|
|
124
124
|
if (podcastImageUrl) {
|
|
125
125
|
const podcastImageFileExt = getUrlExt(podcastImageUrl);
|
|
126
|
-
const podcastImageName = `${
|
|
127
|
-
feed.title ? `${feed.title}.image` : "image"
|
|
128
|
-
}${podcastImageFileExt}`;
|
|
129
126
|
const outputImagePath = _path.resolve(
|
|
130
127
|
basePath,
|
|
131
|
-
|
|
128
|
+
getSimpleFilename(
|
|
129
|
+
feed.title ? feed.title : "image",
|
|
130
|
+
feed.title ? `.image${podcastImageFileExt}` : podcastImageFileExt
|
|
131
|
+
)
|
|
132
132
|
);
|
|
133
133
|
|
|
134
134
|
try {
|
|
@@ -137,7 +137,12 @@ const main = async () => {
|
|
|
137
137
|
archive,
|
|
138
138
|
override,
|
|
139
139
|
marker: podcastImageUrl,
|
|
140
|
-
key: getArchiveKey({
|
|
140
|
+
key: getArchiveKey({
|
|
141
|
+
prefix: archiveUrl,
|
|
142
|
+
name: `${
|
|
143
|
+
feed.title ? `${feed.title}.image` : "image"
|
|
144
|
+
}${podcastImageFileExt}`,
|
|
145
|
+
}),
|
|
141
146
|
outputPath: outputImagePath,
|
|
142
147
|
url: podcastImageUrl,
|
|
143
148
|
maxAttempts: attempts,
|
|
@@ -147,8 +152,13 @@ const main = async () => {
|
|
|
147
152
|
}
|
|
148
153
|
}
|
|
149
154
|
|
|
150
|
-
const
|
|
151
|
-
|
|
155
|
+
const outputMetaPath = _path.resolve(
|
|
156
|
+
basePath,
|
|
157
|
+
getSimpleFilename(
|
|
158
|
+
feed.title ? feed.title : "meta",
|
|
159
|
+
feed.title ? ".meta.json" : ".json"
|
|
160
|
+
)
|
|
161
|
+
);
|
|
152
162
|
|
|
153
163
|
try {
|
|
154
164
|
logMessage("\nSaving podcast metadata...");
|
|
@@ -156,7 +166,10 @@ const main = async () => {
|
|
|
156
166
|
archive,
|
|
157
167
|
override,
|
|
158
168
|
feed,
|
|
159
|
-
key: getArchiveKey({
|
|
169
|
+
key: getArchiveKey({
|
|
170
|
+
prefix: archiveUrl,
|
|
171
|
+
name: `${feed.title ? `${feed.title}.meta` : "meta"}.json`,
|
|
172
|
+
}),
|
|
160
173
|
outputPath: outputMetaPath,
|
|
161
174
|
});
|
|
162
175
|
} catch (error) {
|
|
@@ -206,6 +219,7 @@ const main = async () => {
|
|
|
206
219
|
bitrate,
|
|
207
220
|
episodeTemplate,
|
|
208
221
|
episodeDigits,
|
|
222
|
+
episodeSourceOrder,
|
|
209
223
|
exec,
|
|
210
224
|
feed,
|
|
211
225
|
includeEpisodeMeta,
|
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.
|
|
7
|
+
.version("9.0.2")
|
|
8
8
|
.option("--url <string>", "url to podcast rss feed")
|
|
9
9
|
.option(
|
|
10
10
|
"--out-dir <path>",
|
|
@@ -41,7 +41,7 @@ export const setupCommander = (commander, argv) => {
|
|
|
41
41
|
|
|
42
42
|
return parsed;
|
|
43
43
|
},
|
|
44
|
-
|
|
44
|
+
[AUDIO_ORDER_TYPES.enclosure, AUDIO_ORDER_TYPES.link]
|
|
45
45
|
)
|
|
46
46
|
.option("--include-meta", "write out podcast metadata to json")
|
|
47
47
|
.option(
|
package/bin/naming.js
CHANGED
|
@@ -2,16 +2,22 @@ import filenamify from "filenamify";
|
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
3
|
|
|
4
4
|
const INVALID_CHAR_REPLACE = "_";
|
|
5
|
-
const MAX_LENGTH_FILENAME =
|
|
5
|
+
const MAX_LENGTH_FILENAME = process.env.MAX_LENGTH_FILENAME
|
|
6
|
+
? parseInt(process.env.MAX_LENGTH_FILENAME)
|
|
7
|
+
: 255;
|
|
6
8
|
|
|
7
|
-
const getSafeName = (name) => {
|
|
9
|
+
const getSafeName = (name, maxLength = MAX_LENGTH_FILENAME) => {
|
|
8
10
|
return filenamify(name, {
|
|
9
11
|
replacement: INVALID_CHAR_REPLACE,
|
|
10
|
-
maxLength
|
|
12
|
+
maxLength,
|
|
11
13
|
});
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const getSimpleFilename = (name, ext = "") => {
|
|
17
|
+
return `${getSafeName(name, MAX_LENGTH_FILENAME - (ext?.length ?? 0))}${ext}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getItemFilename = ({ item, ext, url, feed, template, width }) => {
|
|
15
21
|
const episodeNum = feed.items.length - item._originalIndex;
|
|
16
22
|
const formattedPubDate = item.pubDate
|
|
17
23
|
? dayjs(new Date(item.pubDate)).format("YYYYMMDD")
|
|
@@ -24,7 +30,7 @@ const getFilename = ({ item, ext, url, feed, template, width }) => {
|
|
|
24
30
|
["url", url],
|
|
25
31
|
["podcast_title", feed.title || ""],
|
|
26
32
|
["podcast_link", feed.link || ""],
|
|
27
|
-
["duration",
|
|
33
|
+
["duration", item.itunes?.duration || ""],
|
|
28
34
|
];
|
|
29
35
|
|
|
30
36
|
let name = template;
|
|
@@ -37,8 +43,7 @@ const getFilename = ({ item, ext, url, feed, template, width }) => {
|
|
|
37
43
|
: name.replace(replaceRegex, "");
|
|
38
44
|
});
|
|
39
45
|
|
|
40
|
-
name
|
|
41
|
-
return getSafeName(name);
|
|
46
|
+
return getSimpleFilename(name, ext);
|
|
42
47
|
};
|
|
43
48
|
|
|
44
49
|
const getFolderName = ({ feed, template }) => {
|
|
@@ -70,4 +75,10 @@ const getArchiveFilename = ({ pubDate, name, ext }) => {
|
|
|
70
75
|
return `${baseName}${ext}`;
|
|
71
76
|
};
|
|
72
77
|
|
|
73
|
-
export {
|
|
78
|
+
export {
|
|
79
|
+
getArchiveFilename,
|
|
80
|
+
getFolderName,
|
|
81
|
+
getItemFilename,
|
|
82
|
+
getSafeName,
|
|
83
|
+
getSimpleFilename,
|
|
84
|
+
};
|
package/bin/util.js
CHANGED
|
@@ -6,7 +6,7 @@ import util from "util";
|
|
|
6
6
|
import { exec } from "child_process";
|
|
7
7
|
|
|
8
8
|
import { logErrorAndExit, logMessage } from "./logger.js";
|
|
9
|
-
import { getArchiveFilename,
|
|
9
|
+
import { getArchiveFilename, getItemFilename } from "./naming.js";
|
|
10
10
|
|
|
11
11
|
const execWithPromise = util.promisify(exec);
|
|
12
12
|
|
|
@@ -185,7 +185,7 @@ const getItemsToDownload = ({
|
|
|
185
185
|
}),
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
const episodeImageName =
|
|
188
|
+
const episodeImageName = getItemFilename({
|
|
189
189
|
item,
|
|
190
190
|
feed,
|
|
191
191
|
url: episodeAudioUrl,
|
|
@@ -377,7 +377,7 @@ const AUDIO_ORDER_TYPES = {
|
|
|
377
377
|
|
|
378
378
|
const getEpisodeAudioUrlAndExt = (
|
|
379
379
|
{ enclosure, link },
|
|
380
|
-
order = [AUDIO_ORDER_TYPES.
|
|
380
|
+
order = [AUDIO_ORDER_TYPES.enclosure, AUDIO_ORDER_TYPES.link]
|
|
381
381
|
) => {
|
|
382
382
|
for (const source of order) {
|
|
383
383
|
if (source === AUDIO_ORDER_TYPES.link && link && getIsAudioUrl(link)) {
|
|
@@ -399,15 +399,15 @@ const getEpisodeAudioUrlAndExt = (
|
|
|
399
399
|
};
|
|
400
400
|
|
|
401
401
|
const getImageUrl = ({ image, itunes }) => {
|
|
402
|
-
if (image
|
|
402
|
+
if (image?.url) {
|
|
403
403
|
return image.url;
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
if (image
|
|
406
|
+
if (image?.link) {
|
|
407
407
|
return image.link;
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
if (itunes
|
|
410
|
+
if (itunes?.image) {
|
|
411
411
|
return itunes.image;
|
|
412
412
|
}
|
|
413
413
|
|
|
@@ -469,14 +469,12 @@ const runFfmpeg = async ({
|
|
|
469
469
|
if (addMp3Metadata) {
|
|
470
470
|
const album = feed.title || "";
|
|
471
471
|
const title = item.title || "";
|
|
472
|
-
const artist =
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
? item.itunes.episode
|
|
479
|
-
: `${feed.items.length - itemIndex}`;
|
|
472
|
+
const artist = item?.itunes?.author
|
|
473
|
+
? item.itunes.author
|
|
474
|
+
: item.author || "";
|
|
475
|
+
const track = item?.itunes?.episode
|
|
476
|
+
? item.itunes.episode
|
|
477
|
+
: `${feed.items.length - itemIndex}`;
|
|
480
478
|
const date = item.pubDate
|
|
481
479
|
? dayjs(new Date(item.pubDate)).format("YYYY-MM-DD")
|
|
482
480
|
: "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "podcast-dl",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.2",
|
|
4
4
|
"description": "A CLI for downloading podcasts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./bin/bin.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"command-exists": "^1.2.9",
|
|
52
52
|
"commander": "^5.1.0",
|
|
53
53
|
"dayjs": "^1.8.25",
|
|
54
|
-
"filenamify": "^
|
|
54
|
+
"filenamify": "^6.0.0",
|
|
55
55
|
"global-agent": "^3.0.0",
|
|
56
56
|
"got": "^11.0.2",
|
|
57
57
|
"p-limit": "^4.0.0",
|