podcast-dl 11.4.0 → 11.4.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/LICENSE +21 -21
- package/README.md +146 -146
- package/bin/archive.js +39 -39
- package/bin/async.js +36 -13
- package/bin/bin.js +2 -7
- package/bin/commander.js +199 -199
- package/bin/exec.js +30 -30
- package/bin/ffmpeg.js +105 -105
- package/bin/items.js +247 -247
- package/bin/logger.js +84 -84
- package/bin/meta.js +66 -66
- package/bin/naming.js +136 -136
- package/bin/util.js +61 -0
- package/bin/validate.js +39 -39
- package/package.json +62 -62
package/bin/meta.js
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import { getIsInArchive, writeToArchive } from "./archive.js";
|
|
3
|
-
import { logMessage } from "./logger.js";
|
|
4
|
-
import { getPublicObject } from "./util.js";
|
|
5
|
-
|
|
6
|
-
export const writeFeedMeta = ({ outputPath, feed, key, archive, override }) => {
|
|
7
|
-
if (key && archive && getIsInArchive({ key, archive })) {
|
|
8
|
-
logMessage("Feed metadata exists in archive. Skipping...");
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
const output = getPublicObject(feed, ["items"]);
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
if (override || !fs.existsSync(outputPath)) {
|
|
15
|
-
fs.writeFileSync(outputPath, JSON.stringify(output, null, 4));
|
|
16
|
-
} else {
|
|
17
|
-
logMessage("Feed metadata exists locally. Skipping...");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
21
|
-
try {
|
|
22
|
-
writeToArchive({ key, archive });
|
|
23
|
-
} catch (error) {
|
|
24
|
-
throw new Error(`Error writing to archive: ${error.toString()}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
} catch (error) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`Unable to save metadata file for feed: ${error.toString()}`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const writeItemMeta = ({
|
|
35
|
-
marker,
|
|
36
|
-
outputPath,
|
|
37
|
-
item,
|
|
38
|
-
key,
|
|
39
|
-
archive,
|
|
40
|
-
override,
|
|
41
|
-
}) => {
|
|
42
|
-
if (key && archive && getIsInArchive({ key, archive })) {
|
|
43
|
-
logMessage(`${marker} | Episode metadata exists in archive. Skipping...`);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const output = getPublicObject(item);
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
if (override || !fs.existsSync(outputPath)) {
|
|
51
|
-
fs.writeFileSync(outputPath, JSON.stringify(output, null, 4));
|
|
52
|
-
} else {
|
|
53
|
-
logMessage(`${marker} | Episode metadata exists locally. Skipping...`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
57
|
-
try {
|
|
58
|
-
writeToArchive({ key, archive });
|
|
59
|
-
} catch (error) {
|
|
60
|
-
throw new Error("Error writing to archive", error);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
throw new Error("Unable to save meta file for episode", error);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getIsInArchive, writeToArchive } from "./archive.js";
|
|
3
|
+
import { logMessage } from "./logger.js";
|
|
4
|
+
import { getPublicObject } from "./util.js";
|
|
5
|
+
|
|
6
|
+
export const writeFeedMeta = ({ outputPath, feed, key, archive, override }) => {
|
|
7
|
+
if (key && archive && getIsInArchive({ key, archive })) {
|
|
8
|
+
logMessage("Feed metadata exists in archive. Skipping...");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const output = getPublicObject(feed, ["items"]);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
if (override || !fs.existsSync(outputPath)) {
|
|
15
|
+
fs.writeFileSync(outputPath, JSON.stringify(output, null, 4));
|
|
16
|
+
} else {
|
|
17
|
+
logMessage("Feed metadata exists locally. Skipping...");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
21
|
+
try {
|
|
22
|
+
writeToArchive({ key, archive });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new Error(`Error writing to archive: ${error.toString()}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Unable to save metadata file for feed: ${error.toString()}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const writeItemMeta = ({
|
|
35
|
+
marker,
|
|
36
|
+
outputPath,
|
|
37
|
+
item,
|
|
38
|
+
key,
|
|
39
|
+
archive,
|
|
40
|
+
override,
|
|
41
|
+
}) => {
|
|
42
|
+
if (key && archive && getIsInArchive({ key, archive })) {
|
|
43
|
+
logMessage(`${marker} | Episode metadata exists in archive. Skipping...`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const output = getPublicObject(item);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
if (override || !fs.existsSync(outputPath)) {
|
|
51
|
+
fs.writeFileSync(outputPath, JSON.stringify(output, null, 4));
|
|
52
|
+
} else {
|
|
53
|
+
logMessage(`${marker} | Episode metadata exists locally. Skipping...`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (key && archive && !getIsInArchive({ key, archive })) {
|
|
57
|
+
try {
|
|
58
|
+
writeToArchive({ key, archive });
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error("Error writing to archive", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error("Unable to save meta file for episode", error);
|
|
65
|
+
}
|
|
66
|
+
};
|
package/bin/naming.js
CHANGED
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
import dayjs from "dayjs";
|
|
2
|
-
import filenamify from "filenamify";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
const INVALID_CHAR_REPLACE = "_";
|
|
6
|
-
|
|
7
|
-
const FILTER_FUNCTIONS = {
|
|
8
|
-
strip: (val) => val.replace(/\s+/g, ""),
|
|
9
|
-
strip_special: (val) => val.replace(/[^a-zA-Z0-9\s]/g, ""),
|
|
10
|
-
underscore: (val) => val.replace(/\s+/g, "_"),
|
|
11
|
-
dash: (val) => val.replace(/\s+/g, "-"),
|
|
12
|
-
camelcase: (val) =>
|
|
13
|
-
val
|
|
14
|
-
.split(/\s+/)
|
|
15
|
-
.map((w) =>
|
|
16
|
-
w ? w.charAt(0).toUpperCase() + w.slice(1).toLowerCase() : ""
|
|
17
|
-
)
|
|
18
|
-
.join(""),
|
|
19
|
-
lowercase: (val) => val.toLowerCase(),
|
|
20
|
-
uppercase: (val) => val.toUpperCase(),
|
|
21
|
-
trim: (val) => val.trim(),
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const applyFilters = (value, filterStr) => {
|
|
25
|
-
if (!filterStr) {
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const filters = filterStr.slice(1).split("|");
|
|
30
|
-
return filters.reduce((val, filter) => {
|
|
31
|
-
const filterFn = FILTER_FUNCTIONS[filter];
|
|
32
|
-
return filterFn ? filterFn(val) : val;
|
|
33
|
-
}, value);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const MAX_LENGTH_FILENAME = process.env.MAX_LENGTH_FILENAME
|
|
37
|
-
? parseInt(process.env.MAX_LENGTH_FILENAME)
|
|
38
|
-
: 255;
|
|
39
|
-
|
|
40
|
-
export const getSafeName = (name, maxLength = MAX_LENGTH_FILENAME) => {
|
|
41
|
-
return filenamify(name, {
|
|
42
|
-
replacement: INVALID_CHAR_REPLACE,
|
|
43
|
-
maxLength,
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const getSimpleFilename = (name, ext = "") => {
|
|
48
|
-
return `${getSafeName(name, MAX_LENGTH_FILENAME - (ext?.length ?? 0))}${ext}`;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export const getItemFilename = ({
|
|
52
|
-
item,
|
|
53
|
-
ext,
|
|
54
|
-
url,
|
|
55
|
-
feed,
|
|
56
|
-
template,
|
|
57
|
-
width,
|
|
58
|
-
customTemplateOptions = [],
|
|
59
|
-
offset = 0,
|
|
60
|
-
}) => {
|
|
61
|
-
const episodeNum = feed.items.length - item._originalIndex + offset;
|
|
62
|
-
const title = item.title || "";
|
|
63
|
-
|
|
64
|
-
const releaseYear = item.pubDate
|
|
65
|
-
? dayjs(new Date(item.pubDate)).format("YYYY")
|
|
66
|
-
: null;
|
|
67
|
-
|
|
68
|
-
const releaseMonth = item.pubDate
|
|
69
|
-
? dayjs(new Date(item.pubDate)).format("MM")
|
|
70
|
-
: null;
|
|
71
|
-
|
|
72
|
-
const releaseDay = item.pubDate
|
|
73
|
-
? dayjs(new Date(item.pubDate)).format("DD")
|
|
74
|
-
: null;
|
|
75
|
-
|
|
76
|
-
const releaseDate = item.pubDate
|
|
77
|
-
? dayjs(new Date(item.pubDate)).format("YYYYMMDD")
|
|
78
|
-
: null;
|
|
79
|
-
|
|
80
|
-
const customReplacementTuples = customTemplateOptions.map((option, i) => {
|
|
81
|
-
const matchRegex = new RegExp(option);
|
|
82
|
-
const match = title.match(matchRegex);
|
|
83
|
-
|
|
84
|
-
return match && match[0] ? [`custom_${i}`, match[0]] : [`custom_${i}`, ""];
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const templateReplacementsTuples = [
|
|
88
|
-
["title", title],
|
|
89
|
-
["release_date", releaseDate || ""],
|
|
90
|
-
["release_year", releaseYear || ""],
|
|
91
|
-
["release_month", releaseMonth || ""],
|
|
92
|
-
["release_day", releaseDay || ""],
|
|
93
|
-
["episode_num", `${episodeNum}`.padStart(width, "0")],
|
|
94
|
-
["url", url],
|
|
95
|
-
["podcast_title", feed.title || ""],
|
|
96
|
-
["podcast_link", feed.link || ""],
|
|
97
|
-
["duration", item.itunes?.duration || ""],
|
|
98
|
-
["guid", item.guid],
|
|
99
|
-
...customReplacementTuples,
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
const replacementsMap = Object.fromEntries(templateReplacementsTuples);
|
|
103
|
-
const templateSegments = template.trim().split(path.sep);
|
|
104
|
-
const nameSegments = templateSegments.map((segment) => {
|
|
105
|
-
const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
|
|
106
|
-
const name = segment.replace(replaceRegex, (match, varName, filterStr) => {
|
|
107
|
-
const replacement = replacementsMap[varName] || "";
|
|
108
|
-
return applyFilters(replacement, filterStr);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return getSimpleFilename(name);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
nameSegments[nameSegments.length - 1] = getSimpleFilename(
|
|
115
|
-
nameSegments[nameSegments.length - 1],
|
|
116
|
-
ext
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
return nameSegments.join(path.sep);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export const getFolderName = ({ feed, template }) => {
|
|
123
|
-
const replacementsMap = {
|
|
124
|
-
podcast_title: feed.title || "",
|
|
125
|
-
podcast_link: feed.link || "",
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
|
|
129
|
-
const name = template.replace(replaceRegex, (_, varName, filterStr) => {
|
|
130
|
-
const replacement = replacementsMap[varName] || "";
|
|
131
|
-
const filtered = applyFilters(replacement, filterStr);
|
|
132
|
-
return getSafeName(filtered);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return name;
|
|
136
|
-
};
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import filenamify from "filenamify";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
const INVALID_CHAR_REPLACE = "_";
|
|
6
|
+
|
|
7
|
+
const FILTER_FUNCTIONS = {
|
|
8
|
+
strip: (val) => val.replace(/\s+/g, ""),
|
|
9
|
+
strip_special: (val) => val.replace(/[^a-zA-Z0-9\s]/g, ""),
|
|
10
|
+
underscore: (val) => val.replace(/\s+/g, "_"),
|
|
11
|
+
dash: (val) => val.replace(/\s+/g, "-"),
|
|
12
|
+
camelcase: (val) =>
|
|
13
|
+
val
|
|
14
|
+
.split(/\s+/)
|
|
15
|
+
.map((w) =>
|
|
16
|
+
w ? w.charAt(0).toUpperCase() + w.slice(1).toLowerCase() : ""
|
|
17
|
+
)
|
|
18
|
+
.join(""),
|
|
19
|
+
lowercase: (val) => val.toLowerCase(),
|
|
20
|
+
uppercase: (val) => val.toUpperCase(),
|
|
21
|
+
trim: (val) => val.trim(),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const applyFilters = (value, filterStr) => {
|
|
25
|
+
if (!filterStr) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const filters = filterStr.slice(1).split("|");
|
|
30
|
+
return filters.reduce((val, filter) => {
|
|
31
|
+
const filterFn = FILTER_FUNCTIONS[filter];
|
|
32
|
+
return filterFn ? filterFn(val) : val;
|
|
33
|
+
}, value);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const MAX_LENGTH_FILENAME = process.env.MAX_LENGTH_FILENAME
|
|
37
|
+
? parseInt(process.env.MAX_LENGTH_FILENAME)
|
|
38
|
+
: 255;
|
|
39
|
+
|
|
40
|
+
export const getSafeName = (name, maxLength = MAX_LENGTH_FILENAME) => {
|
|
41
|
+
return filenamify(name, {
|
|
42
|
+
replacement: INVALID_CHAR_REPLACE,
|
|
43
|
+
maxLength,
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const getSimpleFilename = (name, ext = "") => {
|
|
48
|
+
return `${getSafeName(name, MAX_LENGTH_FILENAME - (ext?.length ?? 0))}${ext}`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const getItemFilename = ({
|
|
52
|
+
item,
|
|
53
|
+
ext,
|
|
54
|
+
url,
|
|
55
|
+
feed,
|
|
56
|
+
template,
|
|
57
|
+
width,
|
|
58
|
+
customTemplateOptions = [],
|
|
59
|
+
offset = 0,
|
|
60
|
+
}) => {
|
|
61
|
+
const episodeNum = feed.items.length - item._originalIndex + offset;
|
|
62
|
+
const title = item.title || "";
|
|
63
|
+
|
|
64
|
+
const releaseYear = item.pubDate
|
|
65
|
+
? dayjs(new Date(item.pubDate)).format("YYYY")
|
|
66
|
+
: null;
|
|
67
|
+
|
|
68
|
+
const releaseMonth = item.pubDate
|
|
69
|
+
? dayjs(new Date(item.pubDate)).format("MM")
|
|
70
|
+
: null;
|
|
71
|
+
|
|
72
|
+
const releaseDay = item.pubDate
|
|
73
|
+
? dayjs(new Date(item.pubDate)).format("DD")
|
|
74
|
+
: null;
|
|
75
|
+
|
|
76
|
+
const releaseDate = item.pubDate
|
|
77
|
+
? dayjs(new Date(item.pubDate)).format("YYYYMMDD")
|
|
78
|
+
: null;
|
|
79
|
+
|
|
80
|
+
const customReplacementTuples = customTemplateOptions.map((option, i) => {
|
|
81
|
+
const matchRegex = new RegExp(option);
|
|
82
|
+
const match = title.match(matchRegex);
|
|
83
|
+
|
|
84
|
+
return match && match[0] ? [`custom_${i}`, match[0]] : [`custom_${i}`, ""];
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const templateReplacementsTuples = [
|
|
88
|
+
["title", title],
|
|
89
|
+
["release_date", releaseDate || ""],
|
|
90
|
+
["release_year", releaseYear || ""],
|
|
91
|
+
["release_month", releaseMonth || ""],
|
|
92
|
+
["release_day", releaseDay || ""],
|
|
93
|
+
["episode_num", `${episodeNum}`.padStart(width, "0")],
|
|
94
|
+
["url", url],
|
|
95
|
+
["podcast_title", feed.title || ""],
|
|
96
|
+
["podcast_link", feed.link || ""],
|
|
97
|
+
["duration", item.itunes?.duration || ""],
|
|
98
|
+
["guid", item.guid],
|
|
99
|
+
...customReplacementTuples,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const replacementsMap = Object.fromEntries(templateReplacementsTuples);
|
|
103
|
+
const templateSegments = template.trim().split(path.sep);
|
|
104
|
+
const nameSegments = templateSegments.map((segment) => {
|
|
105
|
+
const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
|
|
106
|
+
const name = segment.replace(replaceRegex, (match, varName, filterStr) => {
|
|
107
|
+
const replacement = replacementsMap[varName] || "";
|
|
108
|
+
return applyFilters(replacement, filterStr);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return getSimpleFilename(name);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
nameSegments[nameSegments.length - 1] = getSimpleFilename(
|
|
115
|
+
nameSegments[nameSegments.length - 1],
|
|
116
|
+
ext
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return nameSegments.join(path.sep);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const getFolderName = ({ feed, template }) => {
|
|
123
|
+
const replacementsMap = {
|
|
124
|
+
podcast_title: feed.title || "",
|
|
125
|
+
podcast_link: feed.link || "",
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
|
|
129
|
+
const name = template.replace(replaceRegex, (_, varName, filterStr) => {
|
|
130
|
+
const replacement = replacementsMap[varName] || "";
|
|
131
|
+
const filtered = applyFilters(replacement, filterStr);
|
|
132
|
+
return getSafeName(filtered);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return name;
|
|
136
|
+
};
|
package/bin/util.js
CHANGED
|
@@ -160,6 +160,67 @@ export const AUDIO_TYPES_TO_EXTS = {
|
|
|
160
160
|
"video/x-m4v": ".m4v",
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
+
export const IMAGE_TYPES_TO_EXTS = {
|
|
164
|
+
"image/jpeg": ".jpg",
|
|
165
|
+
"image/jpg": ".jpg",
|
|
166
|
+
"image/png": ".png",
|
|
167
|
+
"image/gif": ".gif",
|
|
168
|
+
"image/webp": ".webp",
|
|
169
|
+
"image/bmp": ".bmp",
|
|
170
|
+
"image/tiff": ".tiff",
|
|
171
|
+
"image/avif": ".avif",
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const TRANSCRIPT_TYPES_TO_EXTS = {
|
|
175
|
+
"application/json": ".json",
|
|
176
|
+
"application/srt": ".srt",
|
|
177
|
+
"application/ttml+xml": ".ttml",
|
|
178
|
+
"application/x-subrip": ".srt",
|
|
179
|
+
"text/html": ".html",
|
|
180
|
+
"text/plain": ".txt",
|
|
181
|
+
"text/srt": ".srt",
|
|
182
|
+
"text/vtt": ".vtt",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const MIME_TO_EXT = {
|
|
186
|
+
...AUDIO_TYPES_TO_EXTS,
|
|
187
|
+
...IMAGE_TYPES_TO_EXTS,
|
|
188
|
+
...TRANSCRIPT_TYPES_TO_EXTS,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const getExtFromMime = (mime) => MIME_TO_EXT[mime] || null;
|
|
192
|
+
|
|
193
|
+
export const correctExtensionFromMime = ({
|
|
194
|
+
outputPath,
|
|
195
|
+
key,
|
|
196
|
+
contentType,
|
|
197
|
+
onCorrect,
|
|
198
|
+
}) => {
|
|
199
|
+
const mimeType = contentType?.split(";")[0];
|
|
200
|
+
const mimeExt = mimeType ? getExtFromMime(mimeType) : null;
|
|
201
|
+
|
|
202
|
+
if (!mimeExt) {
|
|
203
|
+
return { outputPath, key };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const currentExt = path.extname(outputPath);
|
|
207
|
+
if (mimeExt === currentExt) {
|
|
208
|
+
return { outputPath, key };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const basePath = currentExt
|
|
212
|
+
? outputPath.slice(0, -currentExt.length)
|
|
213
|
+
: outputPath;
|
|
214
|
+
const baseKey = key && currentExt ? key.slice(0, -currentExt.length) : key;
|
|
215
|
+
|
|
216
|
+
onCorrect?.(currentExt || "(none)", mimeExt);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
outputPath: basePath + mimeExt,
|
|
220
|
+
key: baseKey ? baseKey + mimeExt : null,
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
|
|
163
224
|
export const VALID_AUDIO_EXTS = [
|
|
164
225
|
...new Set(Object.values(AUDIO_TYPES_TO_EXTS)),
|
|
165
226
|
];
|
package/bin/validate.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { sync as commandExistsSync } from "command-exists";
|
|
2
|
-
import { logErrorAndExit } from "./logger.js";
|
|
3
|
-
|
|
4
|
-
export const createParseNumber = ({ min, max, name, required = true }) => {
|
|
5
|
-
return (value) => {
|
|
6
|
-
if (!value && !required) {
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
let number = parseInt(value);
|
|
12
|
-
if (isNaN(number)) {
|
|
13
|
-
logErrorAndExit(`${name} must be a number`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (typeof min !== undefined && number < min) {
|
|
17
|
-
logErrorAndExit(`${name} must be >= ${min}`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (typeof max !== undefined && number > max) {
|
|
21
|
-
logErrorAndExit(
|
|
22
|
-
`${name} must be <= ${
|
|
23
|
-
max === Number.MAX_SAFE_INTEGER ? "Number.MAX_SAFE_INTEGER" : max
|
|
24
|
-
}`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return number;
|
|
29
|
-
} catch (error) {
|
|
30
|
-
logErrorAndExit(`Unable to parse ${name}`);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const hasFfmpeg = () => {
|
|
36
|
-
if (!commandExistsSync("ffmpeg")) {
|
|
37
|
-
logErrorAndExit('option specified requires "ffmpeg" be available');
|
|
38
|
-
}
|
|
39
|
-
};
|
|
1
|
+
import { sync as commandExistsSync } from "command-exists";
|
|
2
|
+
import { logErrorAndExit } from "./logger.js";
|
|
3
|
+
|
|
4
|
+
export const createParseNumber = ({ min, max, name, required = true }) => {
|
|
5
|
+
return (value) => {
|
|
6
|
+
if (!value && !required) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
let number = parseInt(value);
|
|
12
|
+
if (isNaN(number)) {
|
|
13
|
+
logErrorAndExit(`${name} must be a number`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (typeof min !== undefined && number < min) {
|
|
17
|
+
logErrorAndExit(`${name} must be >= ${min}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof max !== undefined && number > max) {
|
|
21
|
+
logErrorAndExit(
|
|
22
|
+
`${name} must be <= ${
|
|
23
|
+
max === Number.MAX_SAFE_INTEGER ? "Number.MAX_SAFE_INTEGER" : max
|
|
24
|
+
}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return number;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logErrorAndExit(`Unable to parse ${name}`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const hasFfmpeg = () => {
|
|
36
|
+
if (!commandExistsSync("ffmpeg")) {
|
|
37
|
+
logErrorAndExit('option specified requires "ffmpeg" be available');
|
|
38
|
+
}
|
|
39
|
+
};
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "podcast-dl",
|
|
3
|
-
"version": "11.4.
|
|
4
|
-
"description": "A CLI for downloading podcasts.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": "./bin/bin.js",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "rimraf ./binaries && rimraf ./dist && node build.cjs",
|
|
9
|
-
"lint": "eslint ./bin"
|
|
10
|
-
},
|
|
11
|
-
"lint-staged": {
|
|
12
|
-
"*.{js,json,md}": [
|
|
13
|
-
"prettier --write"
|
|
14
|
-
]
|
|
15
|
-
},
|
|
16
|
-
"husky": {
|
|
17
|
-
"hooks": {
|
|
18
|
-
"pre-commit": "lint-staged",
|
|
19
|
-
"pre-push": "npm run lint"
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
"podcast",
|
|
24
|
-
"podcasts",
|
|
25
|
-
"downloader",
|
|
26
|
-
"cli"
|
|
27
|
-
],
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=18.17.0"
|
|
30
|
-
},
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "https://github.com/lightpohl/podcast-dl.git"
|
|
34
|
-
},
|
|
35
|
-
"files": [
|
|
36
|
-
"bin"
|
|
37
|
-
],
|
|
38
|
-
"author": "Joshua Pohl",
|
|
39
|
-
"license": "MIT",
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@yao-pkg/pkg": "^6.6.0",
|
|
42
|
-
"eslint": "^6.8.0",
|
|
43
|
-
"eslint-config-prettier": "^6.11.0",
|
|
44
|
-
"husky": "^4.2.5",
|
|
45
|
-
"lint-staged": "^10.1.7",
|
|
46
|
-
"prettier": "2.3.2",
|
|
47
|
-
"rimraf": "^3.0.2",
|
|
48
|
-
"webpack": "^5.75.0"
|
|
49
|
-
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"command-exists": "^1.2.9",
|
|
52
|
-
"commander": "^12.1.0",
|
|
53
|
-
"dayjs": "^1.8.25",
|
|
54
|
-
"filenamify": "^6.0.0",
|
|
55
|
-
"global-agent": "^3.0.0",
|
|
56
|
-
"got": "^11.0.2",
|
|
57
|
-
"p-limit": "^4.0.0",
|
|
58
|
-
"pluralize": "^8.0.0",
|
|
59
|
-
"rss-parser": "^3.12.0",
|
|
60
|
-
"throttle-debounce": "^3.0.1"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "podcast-dl",
|
|
3
|
+
"version": "11.4.1",
|
|
4
|
+
"description": "A CLI for downloading podcasts.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": "./bin/bin.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "rimraf ./binaries && rimraf ./dist && node build.cjs",
|
|
9
|
+
"lint": "eslint ./bin"
|
|
10
|
+
},
|
|
11
|
+
"lint-staged": {
|
|
12
|
+
"*.{js,json,md}": [
|
|
13
|
+
"prettier --write"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"husky": {
|
|
17
|
+
"hooks": {
|
|
18
|
+
"pre-commit": "lint-staged",
|
|
19
|
+
"pre-push": "npm run lint"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"podcast",
|
|
24
|
+
"podcasts",
|
|
25
|
+
"downloader",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.17.0"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/lightpohl/podcast-dl.git"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"bin"
|
|
37
|
+
],
|
|
38
|
+
"author": "Joshua Pohl",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@yao-pkg/pkg": "^6.6.0",
|
|
42
|
+
"eslint": "^6.8.0",
|
|
43
|
+
"eslint-config-prettier": "^6.11.0",
|
|
44
|
+
"husky": "^4.2.5",
|
|
45
|
+
"lint-staged": "^10.1.7",
|
|
46
|
+
"prettier": "2.3.2",
|
|
47
|
+
"rimraf": "^3.0.2",
|
|
48
|
+
"webpack": "^5.75.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"command-exists": "^1.2.9",
|
|
52
|
+
"commander": "^12.1.0",
|
|
53
|
+
"dayjs": "^1.8.25",
|
|
54
|
+
"filenamify": "^6.0.0",
|
|
55
|
+
"global-agent": "^3.0.0",
|
|
56
|
+
"got": "^11.0.2",
|
|
57
|
+
"p-limit": "^4.0.0",
|
|
58
|
+
"pluralize": "^8.0.0",
|
|
59
|
+
"rss-parser": "^3.12.0",
|
|
60
|
+
"throttle-debounce": "^3.0.1"
|
|
61
|
+
}
|
|
62
|
+
}
|