podcast-dl 9.0.3 → 9.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 CHANGED
@@ -1,102 +1,103 @@
1
- # podcast-dl
2
-
3
- A CLI for downloading podcasts with a focus on archiving.
4
-
5
- ## How to Use
6
-
7
- ### npx
8
-
9
- **[Node Required](https://nodejs.org/en/)**
10
-
11
- `npx podcast-dl --url <PODCAST_RSS_URL>`
12
-
13
- ### Binaries
14
-
15
- [Visit the releases page](https://github.com/lightpohl/podcast-dl/releases) and download the latest binary for your system.
16
-
17
- `podcast-dl --url <PODCAST_RSS_URL>`
18
-
19
- ### [More Examples](./docs/examples.md)
20
-
21
- ## Options
22
-
23
- Type values surrounded in square brackets (`[]`) can be used as used as boolean options (no argument required).
24
-
25
- | Option | Type | Required | Description |
26
- | ------------------------ | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
- | --url | String | true | URL to podcast RSS feed. |
28
- | --out-dir | String | false | Specify output directory for episodes and metadata. Defaults to "./{{podcast_title}}". See "Template Options" for more details. |
29
- | --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is 1. |
30
- | --attempts | Number | false | Sets the number of download attempts per individual file. Default is 3. |
31
- | --archive | [String] | false | Download or write out items not listed in archive file. Generates archive file at path if not found. Defaults to "./{{podcast_title}}/archive.json" when used as a boolean option. See "Template Options" for more details. |
32
- | --episode-template | String | false | Template for generating episode related filenames. See "Template Options" for details. |
33
- | --include-meta | | false | Write out podcast metadata to JSON. |
34
- | --include-episode-meta | | false | Write out individual episode metadata to JSON. |
35
- | --include-episode-images | | false | Download found episode images. |
36
- | --offset | Number | false | Offset starting download position. Default is 0. |
37
- | --limit | Number | false | Max number of episodes to download. Downloads all by default. |
38
- | --after | String | false | Only download episodes after this date (i.e. MM/DD/YYY, inclusive). |
39
- | --before | String | false | Only download episodes before this date (i.e. MM/DD/YYY, inclusive) |
40
- | --episode-regex | String | false | Match episode title against provided regex before starting download. |
41
- | --episode-digits | Number | false | Minimum number of digits to use for episode numbering (e.g. 3 would generate "001" instead of "1"). Default is 0. |
42
- | --episode-source-order | String | false | Attempted order to extract episode audio URL from RSS feed. Default is "enclosure,link". |
43
- | --add-mp3-metadata | | false | Attempts to add a base level of MP3 metadata to each episode. Recommended only in cases where the original metadata is of poor quality. (**ffmpeg required**) |
44
- | --adjust-bitrate | String (e.g. "48k") | false | Attempts to adjust bitrate of MP3s. (**ffmpeg required**) |
45
- | --mono | | false | Attempts to force MP3s into mono. (**ffmpeg required**) |
46
- | --override | | false | Override local files on collision. |
47
- | --reverse | | false | Reverse download direction and start at last RSS item. |
48
- | --info | | false | Print retrieved podcast info instead of downloading. |
49
- | --list | [String] | false | Print episode list instead of downloading. Defaults to "table" when used as a boolean option. "json" is also supported. |
50
- | --exec | String | false | Execute a command after each episode is downloaded. See "Template Options" for more details. |
51
- | --parser-config | String | false | Path to JSON file that will be parsed and used to override the default config passed to [rss-parser](https://github.com/rbren/rss-parser#xml-options). |
52
- | --proxy | | false | Enable proxy support. Specify environment variables listed by [global-agent](https://github.com/gajus/global-agent#environment-variables). |
53
- | --version | | false | Output the version number. |
54
- | --help | | false | Output usage information. |
55
-
56
- ## Archive
57
-
58
- - If passed the `--archive [path]` option, `podcast-dl` will generate/use a JSON archive at the provided path.
59
- - Before downloading an episode or writing out metadata, it'll check if the item was saved previously and abort the save if found.
60
-
61
- ## Template Options
62
-
63
- Options that support templates allow users to specify a template for the generated filename(s) or option. The provided template will replace all matched keywords with the related data described below. Each keyword must be wrapped in two braces like so:
64
-
65
- `--out-dir "./{{podcast_title}}"`
66
-
67
- `--episode-template "{{release_date}}-{{title}}"`
68
-
69
- ### `--out-dir` & `--archive`
70
-
71
- - `podcast_title`: Title of the podcast feed.
72
- - `podcast_link`: `link` value provided for the podcast feed. Typically the homepage URL.
73
-
74
- ### `--episode-template`
75
-
76
- - `title`: The title of the episode.
77
- - `release_date`: The release date of the episode in `YYYYMMDD` format.
78
- - `episode_num`: The location number of where the episodes appears in the feed.
79
- - `url`: URL of episode audio file.
80
- - `duration`: Provided `mm:ss` duration (if found).
81
- - `podcast_title`: Title of the podcast feed.
82
- - `podcast_link`: `link` value provided for the podcast feed. Typically the homepage URL.
83
- - `guid`: The GUID of the episode.
84
-
85
- ### `--exec`
86
-
87
- - `episode_path`: The path to the downloaded episode.
88
- - `episode_path_base`: The path to the folder of the downloaded episode.
89
- - `episode_filename`: The filename of the episode.
90
- - `episode_filename_base`: The filename of the episode without its extension.
91
-
92
- ## Log Levels
93
-
94
- By default, all logs and errors are outputted to the console. The amount of logs can be controlled using the environment variable `LOG_LEVEL` with the following options:
95
-
96
- - `static`: All logs and errors are outputted to the console, but disables any animations.
97
- - `quiet`: Only important info and non-critical errors will be logged (e.g. episode download started).
98
- - `silent`: Only critical error messages will be be logged.
99
-
100
- ## OS Filename Limits
101
-
102
- By default, the max length of a generated filename is `255`. If your OS has different limitations, or if you're running into issues with non-standard feeds, you can adjust the limit via the environment variable `MAX_LENGTH_FILENAME`.
1
+ # podcast-dl
2
+
3
+ A CLI for downloading podcasts with a focus on archiving.
4
+
5
+ ## How to Use
6
+
7
+ ### npx
8
+
9
+ **[Node Required](https://nodejs.org/en/)**
10
+
11
+ `npx podcast-dl --url <PODCAST_RSS_URL>`
12
+
13
+ ### Binaries
14
+
15
+ [Visit the releases page](https://github.com/lightpohl/podcast-dl/releases) and download the latest binary for your system.
16
+
17
+ `podcast-dl --url <PODCAST_RSS_URL>`
18
+
19
+ ### [More Examples](./docs/examples.md)
20
+
21
+ ## Options
22
+
23
+ Type values surrounded in square brackets (`[]`) can be used as used as boolean options (no argument required).
24
+
25
+ | Option | Type | Required | Description |
26
+ | ------------------------ | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | --url | String | true | URL to podcast RSS feed. |
28
+ | --out-dir | String | false | Specify output directory for episodes and metadata. Defaults to "./{{podcast_title}}". See "Template Options" for more details. |
29
+ | --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is 1. |
30
+ | --attempts | Number | false | Sets the number of download attempts per individual file. Default is 3. |
31
+ | --archive | [String] | false | Download or write out items not listed in archive file. Generates archive file at path if not found. Defaults to "./{{podcast_title}}/archive.json" when used as a boolean option. See "Template Options" for more details. |
32
+ | --episode-template | String | false | Template for generating episode related filenames. See "Template Options" for details. |
33
+ | --include-meta | | false | Write out podcast metadata to JSON. |
34
+ | --include-episode-meta | | false | Write out individual episode metadata to JSON. |
35
+ | --include-episode-images | | false | Download found episode images. |
36
+ | --offset | Number | false | Offset starting download position. Default is 0. |
37
+ | --limit | Number | false | Max number of episodes to download. Downloads all by default. |
38
+ | --after | String | false | Only download episodes after this date (i.e. MM/DD/YYY, inclusive). |
39
+ | --before | String | false | Only download episodes before this date (i.e. MM/DD/YYY, inclusive) |
40
+ | --episode-regex | String | false | Match episode title against provided regex before starting download. |
41
+ | --episode-digits | Number | false | Minimum number of digits to use for episode numbering (e.g. 3 would generate "001" instead of "1"). Default is 0. |
42
+ | --episode-source-order | String | false | Attempted order to extract episode audio URL from RSS feed. Default is "enclosure,link". |
43
+ | --add-mp3-metadata | | false | Attempts to add a base level of episode metadata to each episode. Recommended only in cases where the original metadata is of poor quality. (**ffmpeg required**) |
44
+ | --adjust-bitrate | String (e.g. "48k") | false | Attempts to adjust bitrate of episodes. (**ffmpeg required**) |
45
+ | --mono | | false | Attempts to force episodes into mono. (**ffmpeg required**) |
46
+ | --override | | false | Override local files on collision. |
47
+ | --always-postprocess | | false | Always run additional tasks on the file regardless if the file already exists. This includes --add-mp3-metadata, --adjust-bitrate, --mono, and --exec. |
48
+ | --reverse | | false | Reverse download direction and start at last RSS item. |
49
+ | --info | | false | Print retrieved podcast info instead of downloading. |
50
+ | --list | [String] | false | Print episode list instead of downloading. Defaults to "table" when used as a boolean option. "json" is also supported. |
51
+ | --exec | String | false | Execute a command after each episode is downloaded. See "Template Options" for more details. |
52
+ | --parser-config | String | false | Path to JSON file that will be parsed and used to override the default config passed to [rss-parser](https://github.com/rbren/rss-parser#xml-options). |
53
+ | --proxy | | false | Enable proxy support. Specify environment variables listed by [global-agent](https://github.com/gajus/global-agent#environment-variables). |
54
+ | --version | | false | Output the version number. |
55
+ | --help | | false | Output usage information. |
56
+
57
+ ## Archive
58
+
59
+ - If passed the `--archive [path]` option, `podcast-dl` will generate/use a JSON archive at the provided path.
60
+ - Before downloading an episode or writing out metadata, it'll check if the item was saved previously and abort the save if found.
61
+
62
+ ## Template Options
63
+
64
+ Options that support templates allow users to specify a template for the generated filename(s) or option. The provided template will replace all matched keywords with the related data described below. Each keyword must be wrapped in two braces like so:
65
+
66
+ `--out-dir "./{{podcast_title}}"`
67
+
68
+ `--episode-template "{{release_date}}-{{title}}"`
69
+
70
+ ### `--out-dir` & `--archive`
71
+
72
+ - `podcast_title`: Title of the podcast feed.
73
+ - `podcast_link`: `link` value provided for the podcast feed. Typically the homepage URL.
74
+
75
+ ### `--episode-template`
76
+
77
+ - `title`: The title of the episode.
78
+ - `release_date`: The release date of the episode in `YYYYMMDD` format.
79
+ - `episode_num`: The location number of where the episodes appears in the feed.
80
+ - `url`: URL of episode audio file.
81
+ - `duration`: Provided `mm:ss` duration (if found).
82
+ - `podcast_title`: Title of the podcast feed.
83
+ - `podcast_link`: `link` value provided for the podcast feed. Typically the homepage URL.
84
+ - `guid`: The GUID of the episode.
85
+
86
+ ### `--exec`
87
+
88
+ - `episode_path`: The path to the downloaded episode.
89
+ - `episode_path_base`: The path to the folder of the downloaded episode.
90
+ - `episode_filename`: The filename of the episode.
91
+ - `episode_filename_base`: The filename of the episode without its extension.
92
+
93
+ ## Log Levels
94
+
95
+ By default, all logs and errors are outputted to the console. The amount of logs can be controlled using the environment variable `LOG_LEVEL` with the following options:
96
+
97
+ - `static`: All logs and errors are outputted to the console, but disables any animations.
98
+ - `quiet`: Only important info and non-critical errors will be logged (e.g. episode download started).
99
+ - `silent`: Only critical error messages will be be logged.
100
+
101
+ ## OS Filename Limits
102
+
103
+ By default, the max length of a generated filename is `255`. If your OS has different limitations, or if you're running into issues with non-standard feeds, you can adjust the limit via the environment variable `MAX_LENGTH_FILENAME`.
package/bin/async.js CHANGED
@@ -36,6 +36,7 @@ const download = async (options) => {
36
36
  key,
37
37
  archive,
38
38
  override,
39
+ alwaysPostprocess,
39
40
  onAfterDownload,
40
41
  attempt = 1,
41
42
  maxAttempts = 3,
@@ -44,6 +45,11 @@ const download = async (options) => {
44
45
  const logMessage = getLogMessageWithMarker(marker);
45
46
  if (!override && fs.existsSync(outputPath)) {
46
47
  logMessage("Download exists locally. Skipping...");
48
+
49
+ if (onAfterDownload && alwaysPostprocess) {
50
+ await onAfterDownload();
51
+ }
52
+
47
53
  return;
48
54
  }
49
55
 
@@ -159,6 +165,7 @@ let downloadItemsAsync = async ({
159
165
  includeEpisodeMeta,
160
166
  mono,
161
167
  override,
168
+ alwaysPostprocess,
162
169
  targetItems,
163
170
  threads = 1,
164
171
  }) => {
@@ -193,6 +200,7 @@ let downloadItemsAsync = async ({
193
200
  await download({
194
201
  archive,
195
202
  override,
203
+ alwaysPostprocess,
196
204
  marker,
197
205
  key: getArchiveKey({
198
206
  prefix: archiveUrl,
@@ -216,6 +224,7 @@ let downloadItemsAsync = async ({
216
224
  itemIndex: item._originalIndex,
217
225
  outputPath: outputPodcastPath,
218
226
  addMp3Metadata: addMp3MetadataFlag,
227
+ ext: audioFileExt,
219
228
  });
220
229
  }
221
230
 
package/bin/bin.js CHANGED
@@ -45,6 +45,7 @@ const {
45
45
  after,
46
46
  before,
47
47
  override,
48
+ alwaysPostprocess,
48
49
  reverse,
49
50
  info,
50
51
  list,
@@ -225,6 +226,7 @@ const main = async () => {
225
226
  includeEpisodeMeta,
226
227
  mono,
227
228
  override,
229
+ alwaysPostprocess,
228
230
  targetItems,
229
231
  threads,
230
232
  });
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.3")
7
+ .version("9.1.0")
8
8
  .option("--url <string>", "url to podcast rss feed")
9
9
  .option(
10
10
  "--out-dir <path>",
@@ -74,20 +74,24 @@ export const setupCommander = (commander, argv) => {
74
74
  )
75
75
  .option(
76
76
  "--add-mp3-metadata",
77
- "attempts to add a base level of metadata to .mp3 files using ffmpeg",
77
+ "attempts to add a base level of metadata to episode files using ffmpeg",
78
78
  hasFfmpeg
79
79
  )
80
80
  .option(
81
81
  "--adjust-bitrate <string>",
82
- "attempts to adjust bitrate of .mp3 files using ffmpeg",
82
+ "attempts to adjust bitrate of episode files using ffmpeg",
83
83
  hasFfmpeg
84
84
  )
85
85
  .option(
86
86
  "--mono",
87
- "attempts to force .mp3 files into mono using ffmpeg",
87
+ "attempts to force episode files into mono using ffmpeg",
88
88
  hasFfmpeg
89
89
  )
90
90
  .option("--override", "override local files on collision")
91
+ .option(
92
+ "--always-postprocess",
93
+ "always run additional tasks on the file regardless if the file already exists"
94
+ )
91
95
  .option("--reverse", "download episodes in reverse order")
92
96
  .option("--info", "print retrieved podcast info instead of downloading")
93
97
  .option(
package/bin/util.js CHANGED
@@ -348,9 +348,13 @@ const AUDIO_TYPES_TO_EXTS = {
348
348
  "audio/ogg": ".ogg",
349
349
  "audio/vorbis": ".ogg",
350
350
  "audio/mp4": ".m4a",
351
+ "audio/x-m4a": ".m4a",
351
352
  "audio/wav": ".wav",
352
353
  "audio/x-wav": ".wav",
353
354
  "audio/aac": ".aac",
355
+ "video/mp4": ".mp4",
356
+ "video/quicktime": ".mov",
357
+ "video/x-m4v": ".m4v",
354
358
  };
355
359
 
356
360
  const VALID_AUDIO_EXTS = [...new Set(Object.values(AUDIO_TYPES_TO_EXTS))];
@@ -447,15 +451,12 @@ const runFfmpeg = async ({
447
451
  bitrate,
448
452
  mono,
449
453
  addMp3Metadata,
454
+ ext,
450
455
  }) => {
451
456
  if (!fs.existsSync(outputPath)) {
452
457
  return;
453
458
  }
454
459
 
455
- if (!outputPath.endsWith(".mp3")) {
456
- throw new Error("Not an .mp3 file. Unable to run ffmpeg.");
457
- }
458
-
459
460
  let command = `ffmpeg -loglevel quiet -i "${outputPath}"`;
460
461
 
461
462
  if (bitrate) {
@@ -504,7 +505,7 @@ const runFfmpeg = async ({
504
505
  command += ` -map_metadata 0 ${metadataString} -codec copy`;
505
506
  }
506
507
 
507
- const tmpMp3Path = `${outputPath}.tmp.mp3`;
508
+ const tmpMp3Path = `${outputPath}.tmp${ext}`;
508
509
  command += ` "${tmpMp3Path}"`;
509
510
  logMessage("Running command: " + command, LOG_LEVELS.debug);
510
511
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podcast-dl",
3
- "version": "9.0.3",
3
+ "version": "9.1.0",
4
4
  "description": "A CLI for downloading podcasts.",
5
5
  "type": "module",
6
6
  "bin": "./bin/bin.js",