podcast-dl 11.3.0 → 11.4.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
@@ -22,46 +22,46 @@ A humble CLI for downloading and archiving podcasts.
22
22
 
23
23
  Either `--url` or `--file` must be provided.
24
24
 
25
- Type values surrounded in square brackets (`[]`) can be used as used as boolean options (no argument required).
26
-
27
- | Option | Type | Required | Description |
28
- | --------------------------------- | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29
- | --url | String | true\* | URL to podcast RSS feed. |
30
- | --file | String | true\* | Path to local RSS file. |
31
- | --out-dir | String | false | Specify output directory for episodes and metadata. Defaults to "./{{podcast_title}}". See "Template Options" for more details. |
32
- | --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is 1. |
33
- | --attempts | Number | false | Sets the number of download attempts per individual file. Default is 3. |
34
- | --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. |
35
- | --episode-template | String | false | Template for generating episode related filenames. See "Template Options" for details. |
36
- | --episode-custom-template-options | <String...> | false | Provide custom options for the episode template. See "Template Options" for details. |
37
- | --include-meta | | false | Write out podcast metadata to JSON. |
38
- | --include-episode-meta | | false | Write out individual episode metadata **to** JSON. |
39
- | --include-episode-images | | false | Download found episode images. |
40
- | --include-episode-transcripts | | false | Download found episode transcripts. |
41
- | --offset | Number | false | Offset starting download position. Default is 0. |
42
- | --limit | Number | false | Max number of episodes to download. Downloads all by default. |
43
- | --after | String | false | Only download episodes after this date (i.e. MM/DD/YYY, inclusive). |
44
- | --before | String | false | Only download episodes before this date (i.e. MM/DD/YYY, inclusive) |
45
- | --episode-regex | String | false | Match episode title against provided regex before starting download. |
46
- | --episode-regex-exclude | String | false | Matched episode titles against provided regex will be excluded. |
47
- | --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. |
48
- | --episode-num-offset | Number | false | Offset the acquired episode number. Default is 0. |
49
- | --episode-source-order | String | false | Attempted order to extract episode audio URL from RSS feed. Default is "enclosure,link". |
50
- | --episode-transcript-types | String | false | List of allowed transcript types in preferred order. Default is "application/json,application/x-subrip,application/srr,application/srt,text/vtt,text/html,text/plain". |
51
- | --season | Number | false | Only download episodes from this season. Note: this will only work if the RSS feed includes the itunes:season tag on episodes. |
52
- | --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**) |
53
- | --adjust-bitrate | String (e.g. "48k") | false | Attempts to adjust bitrate of episodes. (**ffmpeg required**) |
54
- | --mono | | false | Attempts to force episodes into mono. (**ffmpeg required**) |
55
- | --override | | false | Override local files on collision. |
56
- | --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. |
57
- | --reverse | | false | Reverse download direction and start at last RSS item. |
58
- | --info | | false | Print retrieved podcast info instead of downloading. |
59
- | --list | [String] | false | Print episode list instead of downloading. Defaults to "table" when used as a boolean option. "json" is also supported. |
60
- | --exec | String | false | Execute a command after each episode is downloaded. See "Template Options" for more details. |
61
- | --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). |
62
- | --user-agent | String | false | Specify custom user agent string for HTTP requests. Defaults to a Chrome user agent if not specified. |
63
- | --proxy | | false | Enable proxy support. Specify environment variables listed by [global-agent](https://github.com/gajus/global-agent#environment-variables). |
64
- | --help | | false | Output usage information. |
25
+ Type values surrounded in square brackets (`[]`) can be used as boolean options (no argument required).
26
+
27
+ | Option | Type | Required | Description |
28
+ | --------------------------------- | ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29
+ | --url | String | true\* | URL to podcast RSS feed. |
30
+ | --file | String | true\* | Path to local RSS file. |
31
+ | --out-dir | String | false | Specify output directory for episodes and metadata. Defaults to `"./{{podcast_title}}"`. See "Template Options" for more details. |
32
+ | --threads | Number | false | Determines the number of downloads that will happen concurrently. Default is `1`. |
33
+ | --attempts | Number | false | Sets the number of download attempts per individual file. Default is `3`. |
34
+ | --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. |
35
+ | --episode-template | String | false | Template for generating episode related filenames. See "Template Options" for details. |
36
+ | --episode-custom-template-options | <String...> | false | Provide custom options for the episode template. See "Template Options" for details. |
37
+ | --include-meta | | false | Write out podcast metadata to JSON. |
38
+ | --include-episode-meta | | false | Write out individual episode metadata to JSON. |
39
+ | --include-episode-images | | false | Download found episode images. |
40
+ | --include-episode-transcripts | | false | Download found episode transcripts. |
41
+ | --offset | Number | false | Offset starting download position. Default is `0`. |
42
+ | --limit | Number | false | Max number of episodes to download. Downloads all by default. |
43
+ | --after | String | false | Only download episodes after this date (i.e. MM/DD/YYYY, inclusive). |
44
+ | --before | String | false | Only download episodes before this date (i.e. MM/DD/YYYY, inclusive). |
45
+ | --episode-regex | String | false | Match episode title against provided regex before starting download. |
46
+ | --episode-regex-exclude | String | false | Episode titles matching provided regex will be excluded. |
47
+ | --episode-digits | Number | false | Minimum number of digits to use for episode numbering (e.g. 3 would generate "001" instead of "1"). Default is `1`. |
48
+ | --episode-num-offset | Number | false | Offset the acquired episode number. Default is `0`. |
49
+ | --episode-source-order | String | false | Attempted order to extract episode audio URL from RSS feed. Default is `"enclosure,link"`. |
50
+ | --episode-transcript-types | String | false | List of allowed transcript types in preferred order. Default is "application/json,application/x-subrip,application/srr,application/srt,text/vtt,text/html,text/plain". |
51
+ | --season | Number | false | Only download episodes from specified season. Note: this will only work if the RSS feed includes the `itunes:season` tag on episodes. |
52
+ | --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**) |
53
+ | --adjust-bitrate | String (e.g. "48k") | false | Attempts to adjust bitrate of episodes. (**ffmpeg required**) |
54
+ | --mono | | false | Attempts to force episodes into mono. (**ffmpeg required**) |
55
+ | --override | | false | Override local files on collision. |
56
+ | --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`. |
57
+ | --reverse | | false | Reverse download direction and start at last RSS item. |
58
+ | --info | | false | Print retrieved podcast info instead of downloading. |
59
+ | --list | [String] | false | Print episode list instead of downloading. Defaults to `"table"` when used as a boolean option. `"json"` is also supported. |
60
+ | --exec | String | false | Execute a command after each episode is downloaded. See "Template Options" for more details. |
61
+ | --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). |
62
+ | --user-agent | String | false | Specify custom user agent string for HTTP requests. Defaults to a Chrome user agent if not specified. |
63
+ | --proxy | | false | Enable proxy support. Specify environment variables listed by [global-agent](https://github.com/gajus/global-agent#environment-variables). |
64
+ | --help | | false | Output usage information. |
65
65
 
66
66
  ## Archive
67
67
 
@@ -88,7 +88,7 @@ Options that support templates allow users to specify a template for the generat
88
88
  - `release_year`: The release year (`YYYY`) of the episode.
89
89
  - `release_month`: The release month (`MM`) of the episode.
90
90
  - `release_day`: The release day (`DD`) of the episode.
91
- - `episode_num`: The location number of where the episodes appears in the feed.
91
+ - `episode_num`: The position number of where the episode appears in the feed.
92
92
  - `url`: URL of episode audio file.
93
93
  - `duration`: Provided `mm:ss` duration (if found).
94
94
  - `podcast_title`: Title of the podcast feed.
@@ -109,13 +109,37 @@ If no match is found, the `custom_<n>` keyword will be replaced with an empty st
109
109
  - `episode_filename_base`: The filename of the episode without its extension.
110
110
  - `url`: URL of episode audio file.
111
111
 
112
+ ### Template Filters
113
+
114
+ Template variables can be transformed using filters. Filters are applied using the pipe (`|`) character and can be chained:
115
+
116
+ `--episode-template "{{podcast_title|underscore}}-{{title|strip_special|camelcase}}"`
117
+
118
+ For example, given `title` = "Serial- S01 E01: The Alibi":
119
+
120
+ - `{{title|strip_special|underscore}}` produces `Serial S01 E01 The Alibi` then `Serial_S01_E01_The_Alibi`
121
+ - `{{title|strip_special|camelcase}}` produces `SerialS01E01TheAlibi`
122
+
123
+ #### Available Filters
124
+
125
+ | Filter | Description | Input | Output |
126
+ | --------------- | --------------------------------------------- | ------------- | ----------- |
127
+ | `strip` | Remove all whitespace | `"foo bar"` | `"foobar"` |
128
+ | `strip_special` | Remove non-alphanumeric chars (except spaces) | `"S01: E01!"` | `"S01 E01"` |
129
+ | `underscore` | Replace whitespace with underscores | `"foo bar"` | `"foo_bar"` |
130
+ | `dash` | Replace whitespace with dashes | `"foo bar"` | `"foo-bar"` |
131
+ | `camelcase` | Convert to UpperCamelCase | `"foo bar"` | `"FooBar"` |
132
+ | `lowercase` | Convert to lowercase | `"FOO Bar"` | `"foo bar"` |
133
+ | `uppercase` | Convert to UPPERCASE | `"foo bar"` | `"FOO BAR"` |
134
+ | `trim` | Remove leading/trailing whitespace | `" foo "` | `"foo"` |
135
+
112
136
  ## Log Levels
113
137
 
114
138
  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:
115
139
 
116
140
  - `static`: All logs and errors are outputted to the console, but disables any animations.
117
141
  - `quiet`: Only important info and non-critical errors will be logged (e.g. episode download started).
118
- - `silent`: Only critical error messages will be be logged.
142
+ - `silent`: Only critical error messages will be logged.
119
143
 
120
144
  ## OS Filename Limits
121
145
 
package/bin/commander.js CHANGED
@@ -110,7 +110,7 @@ export const setupCommander = (program) => {
110
110
  )
111
111
  .option(
112
112
  "--episode-regex-exclude <string>",
113
- "matched episode titles against regex will be excluded"
113
+ "episode titles matching regex will be excluded"
114
114
  )
115
115
  .option(
116
116
  "--season <number>",
package/bin/naming.js CHANGED
@@ -3,6 +3,36 @@ import filenamify from "filenamify";
3
3
  import path from "path";
4
4
 
5
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
+
6
36
  const MAX_LENGTH_FILENAME = process.env.MAX_LENGTH_FILENAME
7
37
  ? parseInt(process.env.MAX_LENGTH_FILENAME)
8
38
  : 255;
@@ -69,16 +99,13 @@ export const getItemFilename = ({
69
99
  ...customReplacementTuples,
70
100
  ];
71
101
 
102
+ const replacementsMap = Object.fromEntries(templateReplacementsTuples);
72
103
  const templateSegments = template.trim().split(path.sep);
73
104
  const nameSegments = templateSegments.map((segment) => {
74
- let name = segment;
75
- templateReplacementsTuples.forEach((replacementTuple) => {
76
- const [matcher, replacement] = replacementTuple;
77
- const replaceRegex = new RegExp(`{{${matcher}}}`, "g");
78
-
79
- name = replacement
80
- ? name.replace(replaceRegex, replacement)
81
- : name.replace(replaceRegex, "");
105
+ const replaceRegex = /{{(\w+)(\|[^}]+)?}}/g;
106
+ const name = segment.replace(replaceRegex, (match, varName, filterStr) => {
107
+ const replacement = replacementsMap[varName] || "";
108
+ return applyFilters(replacement, filterStr);
82
109
  });
83
110
 
84
111
  return getSimpleFilename(name);
@@ -93,19 +120,16 @@ export const getItemFilename = ({
93
120
  };
94
121
 
95
122
  export const getFolderName = ({ feed, template }) => {
96
- const templateReplacementsTuples = [
97
- ["podcast_title", feed.title || ""],
98
- ["podcast_link", feed.link || ""],
99
- ];
100
-
101
- let name = template;
102
- templateReplacementsTuples.forEach((replacementTuple) => {
103
- const [matcher, replacement] = replacementTuple;
104
- const replaceRegex = new RegExp(`{{${matcher}}}`, "g");
105
-
106
- name = replacement
107
- ? name.replace(replaceRegex, getSafeName(replacement))
108
- : name.replace(replaceRegex, "");
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);
109
133
  });
110
134
 
111
135
  return name;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podcast-dl",
3
- "version": "11.3.0",
3
+ "version": "11.4.0",
4
4
  "description": "A CLI for downloading podcasts.",
5
5
  "type": "module",
6
6
  "bin": "./bin/bin.js",