-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathREADME.htm
More file actions
440 lines (403 loc) · 61 KB
/
README.htm
File metadata and controls
440 lines (403 loc) · 61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
code {
display: inline;
}
html {
font-size: 14px;
font-family: verdana;
padding: 5px;
}
ul {
list-style-type: none;
padding-left: 4em;
text-indent: -2em;
}
.block {
display: block;
margin-left: 2em;
padding-left: 2em;
text-indent: -2em;
}
ul.bullets {
list-style-type: disc;
padding-left: 3em;
text-indent: unset;
}
ul ul.bullets {list-style: circle;}
</style>
<title>README.htm</title>
</head>
<body>
<h1>SoundsDownloadScript.ps1 + genRSS.ps1</h1>
<p><i>*Updated: 18-Nov-2024 22:21 GMT</i></p>
<p>These instructions are very crude and messy. Sorry for the windbag word vomit. I hope they help, though. If someone wants to convert this file into a markdown README.md for github, be my guest. The scripts are a little tedious to set up the first time, but once you get them working they're pretty reliable and easy to maintain.</p>
<p>I recommend you check the <a href="https://github.com/endkb/SoundsDownloadScript">repo on github</a> for the <a href="https://raw.githack.com/endkb/SoundsDownloadScript/main/README.htm">latest version of this README</a> before continuing.</p>
<p>If you find a bug in the scripts or something is incorrect or incomplete in the documentation, feel free to <a href="https://github.com/endkb/SoundsDownloadScript/issues">open an issue on github</a>.</p>
<h3>Table of Contents</h3>
<ul class="bullets">
<li><a href="#About">About</a></li>
<li><a href="#GettingStarted">Getting Started</a></li>
<li><a href="#HowItWorks">How it Works</a></li>
<li><a href="#Installation">Installation</a>
<ul class="bullets">
<li><a href="#Installation_1">SoundsDownloadScript.ps1</a></li>
<li><a href="#Installation_2">genRSS.ps1</a></li>
</ul>
</li>
<li><a href="#HowToRun">How to Run</a>
<ul class="bullets">
<li><a href="#HowToRun_1">SoundsDownloadScript.ps1</a></li>
<li><a href="#HowToRun_2">genRSS.ps1</a></li>
<li><a href="#HowToRun_3">Windows Task Scheduler Examples</a></li>
</ul>
</li>
<li><a href="#Documentation">Documentation</a>
<li><a href="#Support">Support</a>
<li><a href="#MITLicense">MIT License</a>
</ul>
<br>
<h2 id="About">ABOUT:</h2>
<p>SoundsDownloadScript.ps1 and genRSS.ps1 are Powershell scripts that can work together to download episodes from the BBC Sounds website and then publish a podcast feed.</p>
<p>SoundsDownloadScript.ps1 can work without genRSS.ps1 if you just want to download the audio files, but genRSS.ps1 won't really work with audio files tagged with other tools because they won't be tagged properly to build a podcast feed (<a href="#ID3Tags">see this note</a>).</p>
<br>
<h2 id="GettingStarted">GETTING STARTED:</h2>
<h3>Package (<a href="https://github.com/endkb/SoundsDownloadScript/releases/latest">latest from github</a>)</h3>
<ul>
<li>genRSS.ps1</li>
<li>ProfileTemplate (for use with genRSS.ps1)</li>
<li>README.htm (this file)</li>
<li>SoundsDownloadScript.ps1</li>
</ul>
<h3>Prerequisites</h3>
<ul>
<li><a href="https://www.gyan.dev/ffmpeg/builds/">ffmpeg</a> (Make sure the package you choose comes with ffprobe.)</li>
<li><a href="https://kid3.kde.org/#download">kid3</a></li>
<li><a href="https://github.com/PowerShell/PowerShell">Powershell</a> (Scripts were developed and tested on v7.0.3 for Windows.)</li>
<li><a href="https://github.com/yt-dlp/yt-dlp/releases">yt-dlp</a></li>
</ul>
<h3>Optional</h3>
<ul>
<li><a href="https://community.openvpn.net/openvpn/wiki/Downloads">OpenVPN Community Edition</a> (If you want to download higher quality audio from outside the UK. You must have a VPN provider with UK servers.)</li>
<li><a href="https://rclone.org/downloads/">rclone</a> (If you want to upload files somewhere like S3 buckets, FTP, or archive.org.)</li>
</ul>
<p>I believe there are Linux versions for all of these packages, but I've only ever used this on Windows. The script may work on Powershell for Linux, but it will probably take a lot of tweaking. I'm sure another language that's more appropriate. If you're up for the challenge, feel free to use the script logic as a guide and go for it!</p>
<br>
<h2 id="HowItWorks">HOW IT WORKS:</h2>
<p>When called, SoundsDownloadScript.ps1 checks the program page for the BBC program you are requesting. It gets the name of the most recent episode and checks whether it has downloaded it already. If it hasn't already been downloaded, it calls yt-dlp to download it and then gets the meta data and cover art from the episode's BBC Sounds page. The script calls kid3 to set id3 tags on the audio file. If configured, the script will then clean up old episodes it has downloaded. After that, it can upload the file to a remote location using rclone. If the episode has already been downloaded, the script exits with no action.</p>
<p>genRSS.ps1 uses profile config files to create an RSS file. It scans your download directory of the program and uses kid3 to pull the id3 tags of each episode. It checks the date and time of the most recent file and then checks the date and time of the RSS file to decide whether it needs to update the RSS. If needed, it uses the tags to build an RSS file for a podcast feed. It does not append, it rewrites the whole file each time. It uses rclone to upload the RSS file to a remote location, if configured. If accessible, the url of the RSS feed can be put into a podcast app to be subscribed to.</p>
<br>
<h2 id="Installation">INSTALLATION:</h2>
<h3 id="Installation_1">SoundsDownloadScript.ps1</h3>
<ol>
<li>Copy SoundsDownloadScript.ps1 to a directory.</li>
<li>Unpack ffmpeg to a directory (recommend a subdirectory inside the directory SoundsDownloadScript.ps1 is in).</li>
<li>Unpack kid3 to a directory (recommend a subdirectory inside the directory SoundsDownloadScript.ps1 is in).</li>
<li>Copy yt-dlp.exe to a directory (recommend inside the directory SoundsDownloadScript.ps1 is in).</li>
<li>If using rclone, unpack rclone to a directory (recommend a subdirectory inside the directory SoundsDownloadScript.ps1 is in).</li>
<li>If using OpenVPN, install it using the default options (should be everything except OpenSSL Utilities). Information and support can be found on <a href="https://openvpn.net/community-resources/installing-openvpn/">OpenVPN's website</a>. Note: This must be the Community edition, not OpenVPN Connect.</li>
<li>Edit SoundsDownloadScript.ps1 before using and set the following variables:
<ul>
<li><code>$DefaultTrackNoFormat</code>: [<i>String</i>] DateTime formatted string (<a href="https://www.sharepointdiary.com/2021/11/date-format-in-powershell.html">see this guide for DateTime formatting help</a>) to set the track number if the <code>-TrackNoFormat</code> parameter is not set. Setting this to <code>'c'</code> will count up the track number from the last episode, <code>'c(r)'</code> does the same but searches recursive directories. Note: The <code>'c'</code> option will call kid3 on each track in the directory to determine the next track number. This can be slow as the directory fills up with more and more audio files. If you're not using the <code>-Archive</code> parameter, consider using a DateTime format instead. Also, <code>o</code> can be included in a DateTime as a one digit year, and <code>jjj</code> can be included in a DateTime as a Julian date.</li>
<li><code>$DefaultTitleFormat</code>: [<i>Format string</i>] Default format string to set episode title to if <code>-TitleFormat</code> is not set. The string may contain the following variables in curly brackets:
<ul>
<li><code>{0}</code> = The BBC's primary title. This is usually the show's title.</li>
<li><code>{1}</code> = The BBC's secondary title. This is usually the title of the episode or the series number.</li>
<li><code>{2}</code> = The BBC's tertiary title, usually an episode subtitle or episode number in the series. It's often blank.</li>
<li><code>{3}</code> = The release date and/or time in UTC. A DateTime format should follow. An example would be (<code>3:</code>[DateTimeFormat]} ex: <code>{3:HH:mm}</code></li>
<li><code>{4}</code> = The release date and/or time in UK time. See above.</li>
</ul>
</li>
<li><code>$DefaultBitrate</code>: [<i>Number of kilobits</i>] Specify the bitrate stream that yt-dlp should download if <code>-Bitrate</code> is not set in the command line. Set to <code>0</code> to have yt-dlp download the highest bitrate available. It must be the number of kilobits per second (kbps), and only the number. The higher the bitrate, the higher the audio the quality and the bigger the file size. Available bitrates are generally <code>48</code>, <code>96</code>, <code>128</code>, and <code>320</code>. If the specified bitrate is not available, yt-dlp will fail to download the program and throw an error that says <code>Requested format is not available</code>. You can view the bitrates that are available for a particular stream by running:
<code class="block">yt-dlp.exe --list-formats [URL...]</code>
The BBC only makes its content available worldwide in <code>48</code> and <code>96</code> kbps. If you are outside the UK, and want to download a higher bitrate, you will need to combine this option with <code>-VPNConfig</code> and have a VPN provider with UK servers that can access those streams.
</li>
<br>
<li><code>$GenreTag</code>: [<i>$true,$false</i>] Will add the genre(s) to the metadata. If set to $true, the script will pull the genre from the 'Similar programmes' section at the bottom of the program page. The BBC's genres do not comply with the <itunes:category> tag in podcast feeds. It will not affect any genre information in genRSS.ps1. Some media tools are weird about displaying multiple values in m4a tags, but the values are there.</li>
<br>
<li><code>$DumpDirectory</code>: [<i>Directory path</i>] Directory to save the stream files and artwork to while working on it. To use the win temp dir, use <code>$env:TEMP</code>.<br><br></li>
<li><code>$VPNAdapter</code>: [<i>String</i>] Name of the adapter used by OpenVPN. Run <code>Get-NetAdapter</code> in Powershell to get your list of adapter names. You'll want the Name, not the InterfaceDescription. Mine is 'OpenVPN TAP-Windows6'. The script will use this to determine when the VPN is connected and disconnected to continue on. Only needed if using VPN.</li>
<li><code>$VPNTimeout</code>: [<i>Number of seconds</i>] Number of seconds to wait before giving up on VPN if it doesn't connect. Remember that while it's waiting, the script will pause and could tie up other instances that are also waiting to run, so set it for a reasonable number of seconds. Only needed if using VPN.</li>
<br>
<li><code>$ScriptInstanceControl</code>: [<i>$true,$false</i>] This controls the instances of the script that can download at a time. If ANY of your instances are using VPN, you should set this to <code>$true</code>. If it's enabled, it works like this: If an instance is not configured to use VPN, other instances that are also not using the VPN can download at the same time. If an instance that needs VPN wants to download, it must wait until all other instances are done instances are done. If another script is downloading, the current one will wait. If multiple scripts are connecting and disconnecting the VPN, it will screw up downloads. You really only need this if you're using VPN.</li>
<li><code>$LockFileDirectory</code>: [<i>Directory path</i>] Directory to save lock files for <code>$ScriptInstanceControl</code>. Specify a non-environment dir (like not the user's temp dir) if the script is running under different user accounts. The paths will need to be accessible by all accounts and have read and write permissions for it to work properly. Only needed if using <code>$ScriptInstanceControl</code>.</li>
<li><code>$LockFileMaxDuration</code>: [<i>Number of seconds</i>] This is the maximum age in seconds before lock files are deleted. This keeps script from getting hung up by orphaned lock files. It's rare, but it can happen if the script is interrupted during a download. To disable (not recommended), set to <code>0</code>. Only needed if using <code>$ScriptInstanceControl</code>.<br><br></li>
<li><code>$ytdlpUpdate</code>: [<i>$true,$false</i>] Set to <code>$true</code> to update yt-dlp before the script runs.</li>
<li><code>$rcloneUpdate</code>: [<i>$true,$false</i>] Set to <code>$true</code> to update rclone to the latest stable version before uploading files.<br><br></li>
<li><code>$Logging</code>: [<i>$true,$false,$Logging</i>] Can be used to force log files on or off for all instances. If <code>$false</code>, then logging will be turned off regardless of whether <code>-Debug</code> is specified in the command line. Console output and script variables are saved to a log file, and rclone and OpenVPN logs are saved to separate files. Logs will be saved in the <code>$LogDirectory</code> specified below.
<ul>
<li><code>$Logging = $true</code> = Save logs for all downloads</li>
<li><code>$Logging = $false</code> = Don't save logs for any downloads</li>
<li><code>$Logging > $null</code> = Use whatever is set in the command line parameter per instance (<code>$Logging = $Logging</code> will also have the same effect)</li>
</ul>
</li>
<li><code>$Printjson</code>: [<i>$true,$false</i>] Prints the raw json data from <code>$jsonResult</code> to the console. It will also be saved to the log if <code>$Logging</code> is enabled. The text can be copied and pasted into an <a href="https://codebeautify.org/jsonviewer">online json viewer</a> for better readability. Use this for troubleshooting things like parsing issues, special character issues, or finding a reference point to retrieve a json value (see <a href="#TitleFormat">TitleFormat</a> under the Documentation section). The Console+Vars log files will be significantly larger if this setting is left on.</li>
<li><code>$LogDirectory</code>: [<i>Directory path</i>] This is the directory to move logs to when the <code>-Logging</code> switch is present or when <code>$Logging</code> is set to <code>$true</code>. You'll want to check this directory once in a while because the logs can get unwieldy.</li>
<li id="LogFileNameFormat_1"><code>$LogFileNameFormat</code>: [<i>Format string</i>] This is a format string to set the file name of the log files (ex: <code>$LogFileNameFormat = "{0}-{1}-{2}-{3}.log"</code>). The string may contain the following variables in curly brackets:
<ul>
<li><code>{0}</code> = ShortTitle</li>
<li><code>{1}</code> = Hash of task scheduler GUID</li>
<li><code>{2}</code> = PID of the script that is running</li>
<li><code>{3}</code> = The type of log (Console+Vars, rclone, vpn)</li>
<li><code>{4}</code> = Current date and time (must only include <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file">legal file name chars</a>; <code>{4:yyyyMMdd_HHmmss}</code> is a good format)</li>
</ul>
<br></li>
<li><code>$ffmpegExe</code>: [<i>File path</i>] Path to ffmpeg.exe. You can also use the Get-ChildItem cmdlet:
<code class="block">(Get-ChildItem -Path $PSScriptRoot -Filter "ffmpeg.exe" -Recurse | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1 | % {$_.FullName })</code>
to recursively search for the most recent executable in the directory.</li>
<li><code>$ffprobeExe</code>: [<i>File path</i>] Path or Get-ChildItem cmdlet to <code>ffprobe.exe</code>.</li>
<li><code>$kid3Exe</code>: [<i>File path</i>] Path or Get-ChildItem cmdlet to <code>kid3-cli.exe</code>.</li>
<li><code>$rcloneExe</code>: [<i>File path</i>] Path or Get-ChildItem cmdlet to <code>rclone.exe</code> (you can comment this line out with <code>#</code> if not using).</li>
<li><code>$vpnExe</code>: [<i>File path</i>] Path to openvpn.exe. Since OpenVPN is usually installed into the Program Files directory, you can use the Get-ChildItem cmdlet to <code>openvpn.exe</code>.
<code class="block">(Get-ChildItem -Path $env:Programfiles -Filter 'openvpn.exe' -Recurse -ErrorAction SilentlyContinue | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1 | % { $_.FullName })</code>
This must be the command line executable, not the gui. You can comment this line out with <code>#</code> if not using a VPN.</li>
<li><code>$ytdlpExe</code>: [<i>File path</i>] Path or Get-ChildItem cmdlet to <code>yt-dlp.exe</code>.<br><br></li>
<li><code>$SortArticles</code>: [<i>Delimited string</i>] This isn't used much. This is a string of definite articles in various languages separated by a pipe (|). The script will strip these to fill tags specifically used for sorting. For example 'The Beatles' sort tags will become 'Beatles' and will get sorted in the Bs instead of the Ts. You can add your own for other languages, if needed. The caret (^) denotes the beginning of the string. Without it, it will also strip characters from the middle. Put a space after the definite article if it's its own word. For contractions like l', don't put a space. The defaults should be fine for most people, especially since this isn't music.<br></li>
</ul>
<li>If using rclone to upload the files to a remote location, configure it by setting up remotes: <code>rclone.exe config</code><br>
<ul>
<li><a href="https://rclone.org/install/">See the rclone installation instructions here</a></li>
<li><a href="https://rclone.org/docs/">Detailed instructions for configuring specific remotes are here</a></li>
</ul>
You'll need to know where the rclone configuration file is saved. You can run <code>rclone.exe config file</code> to get that location. On Windows, it's usually stored in the user's AppData folder. You'll probably want to move or copy it to the same directory that rclone is in, especially if you're going to run the script as a different user. When running the scripts, you'll need to specify the location by setting the <code>$rcloneConfig</code> even if it's in the default location. You can have multiple remotes in the same config file, or you can have a different config file for each remote. Either option works.
</li>
<li>If using OpenVPN, you'll need to download or create .ovpn config files. This will be different for each VPN provider. I use one called <a href="https://www.privateinternetaccess.com/">Private Internet Access</a> (PIA), which offers multiple UK servers and makes pre-made .ovpn files available for download. Other providers are available. In the OpenVPN config directory, you'll also need to create a text file with your VPN username in the first line and your password in the second line. Then, in each .ovpn file you'll need to add:
<br>
<code class="block">auth-user-pass "C:\\Program Files\\OpenVPN\\config\\[YourPasswordFile].txt"</code>
This will let OpenVPN connect without having to enter the credentials every time. Note the double back-slashes (<code>\\</code>) in the path.</li>
<li>If using rclone, edit SoundsDownloadScript.ps1 and configure the script blocks to format the <code>rclone.exe</code> command line parameters to your needs. Script blocks must start with <code>$remote_</code> and must be named the same as the specified remote config in order to be run. For example, if you have an rclone remote called <code>poop</code>, you should have a script block named <code>$remote_poop</code>. This is how the script will know which rclone command to use. You will need to be a little familiar with Powershell. Something like:
<code class="block">$remote_poop = {<br>
& $rcloneExe sync $SaveDir $rcloneSyncDir --create-empty-src-dirs --progress --config $rcloneConfig -v $rcloneLoggingArgs<br>
}</code>
Your rclone commands should include all of the following parameters and variables:
<ul>
<li><code>$rcloneExe</code> is the path to rclone.exe</li>
<li><code>$SaveDir</code> is the local directory (for syncing) or <code>$MediaFile</code> is the downloaded media file (for copying)</li>
<li><code>$rcloneSyncDir</code> is remote:path</li>
<li><code>--config $RemoteConfig</code> is the location of the rclone configuration file</li>
<li><code>-v $rcloneLoggingArgs</code> is the location to save the log file if enabled</li>
</ul>
Different providers will need different rclone commands and options. Sometimes you'll need to include additional things like headers, depending on the remote. <a href="https://rclone.org/docs/">Read the rclone documentation for the remote you're using</a> and be ready to troubleshoot. The <a href="https://forum.rclone.org">rclone support forums</a> are very helpful.
If you are using <code>rclone.exe copy</code> or <code>copyto</code>, then you will need to call the <code>Confirm-DownloadedMediaFile</code> function at the top of your script block like below:
<code class="block">$remote_poop2 = {<br>
Confirm-DownloadedMediaFile<br>
& $rcloneExe copyto $MediaFile $rcloneSyncDir --check-first --metadata --config $rcloneConfig --progress -v --dump headers $rcloneLoggingArgs<br>
}</code>
Calling <code>Confirm-DownloadedMediaFile</code> will exit the script block if there was no file downloaded. Otherwise if rclone runs and tries to copy a file that doesn't exist, it will throw a fit. It is not needed when using <code>sync</code>. Note: the way the script is set up, rclone will not delete files from a remote unless the <code>sync</code> option is used.
</ol>
<h3 id="Installation_2">genRSS.ps1 (if using)</h3>
<p>Note: If if you're using the files from a <a href="https://github.com/endkb/SoundsDownloadScript/releases">release</a>, the animal name in the first line of genRSS should match the animal name in the first line of SoundsDownloadScript. This will ensure compatibility (the big issue is metatags in the media files). The main branch on github will not have animal name indicators. If you're updating from the main branch, you should update both files at the same time to avoid issues.</p>
<ol>
<li>Copy genRSS.ps1 to a directory (recommend inside the directory SoundsDownloadScript.ps1 is in).</li>
<li>Edit genRSS.ps1 before use by setting the following variables:
<ul>
<li><code>$kid3Exe</code>: [<i>File path</i>] Path to kid3-cli.exe. You can also use the Get-ChildItem cmdlet:
<code class="block">(Get-ChildItem -Path $PSScriptRoot -Filter "kid3-cli.exe" -Recurse | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1 | % {$_.FullName })</code>
to recursively search for the most recent executable in the directory.</li>
<li><code>$rcloneExe</code>: [<i>File path</i>] Path or Get-ChildItem cmdlet to rclone.exe (you can comment this line out with <code>#</code> if not using).</li>
</ul>
<li>Copy the ProfileTemplate file to the directory that genRSS.ps1 is in. You should rename it to something descriptive.</li>
<li>Edit the options in the copied file. Paths with a backslash (<code>\</code>) need to be escaped by using two (<code>\\</code>). Values may be surrounded by single, double, or no quotes.
<ul>
<li><code>MediaDirectory</code>: [<i>Directory path</i>] The directory with your downloaded SoundsDownloadScript files to scan.</li>
<li><code>Recursive</code>: [<i>yes,no</i>] Search subdirectories of <code>MediaDirectory</code>.</li>
<li><code>MediaExtension</code>: [<i>String</i>] File extensions to search for without the leading period, separated by a comma with no space. Default is <code>m4a,mp3</code>.<br><br></li>
<li><code>Directory</code>: [<i>Directory path</i>] Local directory to save the RSS file.</li>
<li><code>RSSFileName</code>: [<i>File name</i>] Name of the RSS file to save locally. This is the file that will get uploaded.<br><br></li>
<li><code>CheckMediaDirectoryHash</code>: [<i>'contents','filenames','no'</i>] Uses a hash to determine whether the media files have changed and to republish the RSS. If <code>CheckMediaDirectoryHash</code> is set to <code>contents</code> or <code>filenames</code> then each time the script is called, it will compute the MD5 hash value of the MediaDirectory and save it into the RSS. When it is called again, it will pull the hash from the RSS and compare the values. The hash will be saved in <MediaDirectoryHash> as a child of the <rss> element outside of the <channel> element of the RSS. If <code>CheckMediaDirectoryHash</code> is <code>no</code> or is not set, genRSS compares the file with the latest LastWriteTimeUtc to the time the RSS was last generated (<lastBuildDate>) to know whether to update republish the RSS. Possible values are:</li>
<ul>
<li><code>contents</code> = Scans each file in the <code>MediaDirectory</code> and calculates a hash of the contents using ReadAllBytes and ComputeHash. This will also detect changes to metadata. Use this if you're using the <code>-RecheckMetadata</code> option in SoundsDownloadScript. This will be pretty slow if there are a lot of files or of the files are large.</li>
<li><code>filenames</code> = Computes the MD5 hash value of the <code>MediaDirectory</code> filenames only using MD5CryptoServiceProvider. This will detect file additions or deletions, but not changes to metadata.</li>
<li><code>no</code> = Don't take a hash. Use LastWriteTime of the latest file in <code>MediaDirectory</code> to determine whether the RSS needs to be updated. This is the fastest option, but it won't detect changes if only a file was deleted and not added. This is the default value if <code>CheckMediaDirectoryHash</code> is not set.</li>
</ul>
<li><code>CheckProfileHash</code>: [<i>yes,no</i>] Updates the RSS file if there are changes to the podcast profile. Normally, genRSS will only update the RSS file when there is a new episode. If <code>CheckProfileHash</code> is set to <code>yes</code> then the next time the script is called, it will put the MD5 hash value of the profile into the RSS using Get-FileHash. When it is called again, it will pull the hash from the RSS and compare the values. If they are different, then it knows that the profile was updated and the RSS needs to be regenerated and republished. The hash will be put into <ProfileHash> as a child of the <rss> element outside of the <channel> element in the RSS.<br><br></li>
<li><code>rcloneConfig</code>: [<i>File path</i>] Path to the rclone ini config file.</li>
<li><code>RemotePublishDirectory</code>: [<i>String</i>] This is the remote and directory that rclone should upload the RSS file to. It should be in the form of 'Remote:Directory'. Remote is the name of the appropriate config found in the config file specified in <code>-rcloneConfig</code>. For services that use the S3 API, use Remote:BucketName\Directory.</li>
<li><code>RemoteRSSFileName</code>: [<i>File name</i>] Name of the RSS file to publish remotely.<br><br></li>
<li><code>PodcastFeedURL</code>: [<i>URL</i>] The publicly accessible URL of the RSS feed. This populates <atom:link>, which the PSP-1 requires. If it is empty or missing, the element will be omitted from the RSS file for backwards compatibility with older profile templates.</li>
<li><code>PodcastTitle</code>: [<i>String</i>] The title of the podcast. Just use the program title.</li>
<li><code>PodcastDescription</code>: [<i>String</i>] Populates the <description> and <itunes:summary> tags at the <channel> (podcast) level. Value will be marked as CDATA and can technically contain html tags, but many podcast apps only support plain text in the description field. You can insert a new line with <code>\n</code>. See the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlwritersettings.newlinehandling?view=net-8.0">XmlWriterSettings.NewLineHandling Property page</a> for more information on handling new lines with XmlWriter.</li>
<li><code>PodcastAuthor</code>: [<i>String</i>] It makes sense to use the name of the BBC station here.</li>
<li><code>OwnerName</code>: [<i>String</i>] You. I don't want my name out there, so I just put my domain here. This populates the <itunes:owner> element. If left empty, this will be omitted from the RSS.</li>
<li><code>OwnerEmail</code>: [<i>E-mail address</i>] An e-mail address. This populates the <itunes:email> element. If left empty, this will be omitted from the RSS.</li>
<li><code>PodcastURL</code>: [<i>URL</i>] Populates the <link> tag at the <channel> (podcast) level. It's supposed to be website or web page associated with a podcast. I set it to the BBC program page of the show.</li>
<li><code>PodcastLanguage</code>: [<i>ISO 639 language code</i>] This should be a <a href="https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes">two letter code</a>, optionally with a <a href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">country code</a> (ex: <code>en-US</code>).</li>
<li><code>PodcastCopyright</code>: [<i>String</i>] I put the BBC's info here (ex: <code>(C) BBC 2024</code>) since I don't own the copyright for the media. If this is empty, the element will be omitted from the RSS.</li>
<li><code>Category</code>: [<i>String array</i>] Specify one or more categories to define the podcast. Categories may be separated by a comma with no space in between. Subcategories can be specified with a <code>></code> angle bracket. The first value will be the category. An unlimited number of subcategories can be specified. Take the example:
<code class="block">Category=News,Science>Chemistry>Physics</code>
News and Science will be categories, and Chemistry and Physics will be subcategories under Science. Recommend limiting them to <a href="https://podcasters.apple.com/support/1691-apple-podcasts-categories">Apple Podcasts categories</a>.</li>
<li><code>PodcastImage</code>: [<i>URL</i>] The <channel> (podcast) level cover art that will display in podcast apps. You could host the image wherever you want, but I just point it to the image on the BBC's site. To find the URL, open the program page and view the page source. Search for .jpg and you should find the cover image easily. The URL will look like this: <a href="https://ichef.bbci.co.uk/images/ic/128x128/p0c5ydny.jpg">https://ichef.bbci.co.uk/images/ic/128x128/p0c5ydny.jpg</a>. You should copy the link you find and plug in different size dimensions and choose the largest one. Sizes that seem to work are: 128x128, 512x512, 1048x1048, 1792x1792, 1920x1920, and 3000x3000. It should be a square of course.</li>
<li><code>Block</code>: [<i>yes,no,String array</i>] Populates the <itunes:block> and <podcast:block> elements. If this is set to <code>yes</code>, podcast indexes like Podcast Addict should not publish the feed in their directories, sort of like noindex. You might want to use this for testing when you first create a podcast feed before it's ready to be published. Note: <code>yes</code> and <code>no</code> can be complete values. The <podcast:block> element is included with the PSP-1 standard and can also be made granular by adding specific platform IDs (<a href="https://github.com/Podcastindex-org/podcast-namespace/blob/main/serviceslugs.txt">here's a list of IDs</a>) into an array separated by commas with no spaces. See the two examples below:
<code class="block">Block=yes,podcastaddict:no,podcastindex:no</code>
<code class="block">Block=no,amazon:yes,audible:yes,itunes:yes</code>
In the first example, all platforms would be blocked except Podcast Addict and The Podcast Index (works like a whitelist). In the second example, all platforms would be allowed except Amazon, Audible, and iTunes (works like a blacklist). Unfortunately, <podcast:block> is not supported by very many platorms yet. If <code>Block</code> is absent, <code>no</code> is assumed.<br><br></li>
<li><code>MediaRootURL</code>: [<i>URL</i>] This should be the publicly accessible URL path that podcast apps can download the episodes. It should normally be an HTTP directory and then the script will add the file name to the end. I <i>think</i> it could also be a file hosted locally on a windows or smb share by using <code>file://</code> instead, but I haven't tested it.</li>
<li><code>RerunLabel</code>: [<i>String</i>] A label to prepend to episode titles when the episode is a rerun according to the criteria in <code>RerunFiles</code> or <code>RerunTitles</code>. Include a delimiting character like a colon or dash if you'd like. A space is NOT automatically included. To include a space between the label and the title, surround the value in quotes or double quotes (<code>"Repeat: "</code>).</li>
<li><code>AutoDetectReruns</code>: [<i>yes,no,Number of days greater than 0</i>] Specify the number of days after the original air date that an episode should be considered a rerun. In other words, try to determine whether the episode is a rerun by comparing the original date (Original Date or TDOR) to the most recent release date (Release Date or TOAL) of the episode. If it's over the number of days specified, the script marks it as a rerun. If the value is <code>yes</code> then a default of 90 days is used. If the value is <code>no</code> or is not present, the feature is disabled.</li>
<li><code>RerunFiles</code>: [<i>String</i>] Searches the episode file names for this text to decide whether it's a rerun. Separate entries by a comma with no space. I recommend using the BBC program ID.</li>
<li><code>RerunTitles</code>: [<i>String</i>] Searches the episode titles for this text to decide whether it's a rerun. Separate entries by a comma and without a space. I suggest putting the Series numbers in here (Ex: <code>Series 21,Series 22</code>).</li>
<li><code>SkipFiles</code>: [<i>String</i>] Searches the episode file names for this text to decide whether to skip the file. Separate entries by a comma. I recommend using the BBC program ID. These episodes will not be included in the RSS.</li>
<li><code>SkipTitles</code>: [<i>String</i>] Searches the episode titles for this text to decide whether to skip the file. Separate entries by a comma. These episodes will not be included in the RSS.</li>
<li><code>[Program ID]=Desired Title</code>: You can force it to use a custom title on specific episodes. One line for each episode. genRSS will use that title for the episode instead of pulling it from the metadata. Examples:
<code class="block">m001ts8t=Seasonal Trimmings</code>
<code class="block">m002tc9v=Year in Review</code>
<br></li>
<li><code>Logging</code>: [<i>yes,no</i>] Outputs the console and variables to a text file in the <code>LogDirectory</code>. This can also be set by using the <code>-Logging</code> command line parameter. Finally, you can set it globally by adding <code>$Logging = $true</code> or <code>$Logging = $false</code> to the configurable options in the script.</li>
<li><code>LogDirectory</code>: [<i>Directory path</i>] The directory to save log files if <code>Logging</code> is enabled. It also can be set with a command line parameter (<code>-LogDirectory</code>) or globally as a configurable option in the script (<code>$LogDirectory</code>).</li>
<li id="LogFileNameFormat_2"><code>LogFileNameFormat</code>: [<i>Format string</i>] A format string to set the name of the log files (ex: <code>LogFileNameFormat = "{0}-{1}-{2}-genRSS_{3}.log"</code>). The string may contain the following variables in curly brackets:
<ul>
<li><code>{0}</code> = Profile name</li>
<li><code>{1}</code> = Hash of task scheduler GUID</li>
<li><code>{2}</code> = PID of the script that is running</li>
<li><code>{3}</code> = The type of log (Console+Vars, rclone)</li>
<li><code>{4}</code> = Current date and time (must only include <a href="https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file">legal file name chars</a>; <code>{4:yyyyMMdd_HHmmss}</code> is a good format)</li>
</ul>
This can also be given as a command line parameter (<code>-LogFileNameFormat</code>) or globally as a configurable option in the script (<code>$LogFileNameFormat</code>), instead.
</ul>
<li>Profit! (actually, don't profit because I want this project to stay off the BBC's radar)</li>
</ol>
<br>
<h2 id="HowToRun">HOW TO RUN:</h2>
<p>The scripts can be called manually from a Powershell console, but I use the Windows Task Scheduler unless I'm doing testing or downloading a one-off episode. Each show has its own task. For most weekly shows, I have it check for new episodes twice a day. Sometimes shows will do out-of-cycle specials around the holidays and this will catch those. For daily shows, I have it check around the time the show is done airing on the BBC schedule and then keep checking every 20 to 30 minutes. Caution: if you set the frequency to run too often, I've noticed that it can get hung up if the same task is still running while it keeps trying. Basically, make sure you allow enough of an interval to let the script finish downloading and uploading an audio file before setting it to retry. I blame Task Scheduler, but that's the way it is.</p>
<h3 id="HowToRun_1">SoundsDownloadScript.ps1</h3>
<p><i>Command line parameters:</i></p>
<ul>
<li><code>-ProgramURL</code>: [<i>URL</i>] This is the bbc.co.uk/programmes URL of the show to download the latest ep. Sometimes https://www.bbc.co.uk/programmes/[Program ID]/episodes/player works better, especially if the program releases special features. You can get that link by clicking on the program page and selecting available episodes. You can also use the https://bbc.co.uk/sounds/play/[Program ID] link if you want to download only a specific episode.</li>
<li><code>-SaveDir</code>: [<i>Directory path</i>] The local directory to move the finished audio file to. The directory will be created if it does not exist.</li>
<li><code>-ShortTitle</code>: [<i>String</i>] A short reference for the filename. It shouldn't have spaces (Ex: <code>TheNowShow</code>).</li>
<li><code>-TrackNoFormat</code>: [<i>String</i>] Set track number format. It can be a DateTime string (<a href="https://www.sharepointdiary.com/2021/11/date-format-in-powershell.html">see this guide for help formatting DateTime</a>). Additionally, <code>o</code> can be included in a DateTime as a one digit year, <code>jjj</code> can be included in a DateTime as a Julian date. There are a few other options: <code>'c'</code> counts up from the track number of the most recent file in the save directory. <code>'c(r)'</code> does the same but searches recursive directories. Note: The <code>'c'</code> option will call kid3 on each track in the directory to determine the next track number. This can be slow as the directory fills up with more and more audio files. If you're not using the <code>-Archive</code> parameter, consider using a DateTime format instead. If this is omitted, it will use the <code>$DefaultTrackNoFormat</code>.</li>
<li><code>-TitleFormat</code>: [<i>Format string</i>] Set the format of the Title tag. It should be a string. There are several variables that can be used, surrounded by curly brackets:
<ul>
<li><code>{0}</code> = The BBC's primary title. This is usually the show's title.</li>
<li><code>{1}</code> = The BBC's secondary title. This is usually the title of the episode or the series number.</li>
<li><code>{2}</code> = The BBC's tertiary title, usually an episode subtitle or episode number in the series. It's often blank.</li>
<li><code>{3}</code> = The release date and/or time in UTC. A DateTime format should follow. An example would be (3:[DateTimeFormat]} ex: <code>{3:HH:mm}</code></li>
<li><code>{4}</code> = The release date and/or time in UK time. See above.</li>
</ul>
An often useful format for serialized shows is <code>'{1} - {2}'</code> which will usually set the series and episode numbers: Series 15 - Episode 4.
</li>
<li><code>-UseOrigRelease</code>: [<i>Switch</i>] Sets the release date of the episode to the original date the episode aired. Without this parameter, it uses the most recent availability date. When this parameter is set, the RELEASEDATE/TDRL and ORIGINALDATE/TDOR tags will have the same value. This will affect the file name and also the <pubDate> tag in genRSS.</li>
<li><code>-Bitrate</code>: [<i>Number of kilobits</i>] Specify the bitrate stream that yt-dlp should download. Set to <code>0</code> to have yt-dlp download the highest bitrate available. It must be the number of kilobits per second (kbps), and only the number. The higher the bitrate, the higher the audio the quality and the bigger the file size. Available bitrates are generally <code>48</code>, <code>96</code>, <code>128</code>, and <code>320</code>. If the specified bitrate is not available, yt-dlp will fail to download the program and will throw an error that says <code>Requested format is not available</code>. You can view the bitrates that are available for a particular stream by running:
<code class="block">yt-dlp.exe --list-formats [URL]</code>
The BBC only makes its content available worldwide in <code>48</code> and <code>96</code> kbps. If you are outside the UK, and want to download a higher bitrate, you will need to combine this option with <code>-VPNConfig</code> and have a VPN provider with UK servers that can access those streams.
</li>
<li><code>-mp3</code>: [<i>Switch</i>] Transcode the audio file to mp3 using ffmpeg after downloading. The default is file type is m4a. Uses libmp3lame codec and mirrors the bitrate of the m4a file. Note: I don't do as much testing on the mp3 option (e.g., m4a and mp3 use different tags). If you find a bug with it, <a href="https://github.com/endkb/SoundsDownloadScript/issues">open an issue</a> in the repo.</li>
<li><code>-Archive</code>: [<i>Number</i>] The number of episodes to keep. Set to <code>0</code> to disable and keep all episodes. After the script downloads the latest one, it will delete excess ones. It searches for episodes using the ShortTitle. If multiple shows are saved in the same folder for some reason, the other shows will not be deleted.</li>
<li><code>-Days</code>: [<i>Switch</i>] Bases the <code>-Archive</code> parameter on the number of days to keep instead of the number of episodes. This option reads the date from each filename (which is set from the episode's GMT release date in the metadata). If Archive is <code>0</code> or is not set, this parameter has no effect.</li>
<li><code>-RecheckMetadata</code>: [<i>Switch</i>] Refreshes the metadata of all media files to pull any changes from the BBC. It searches recursively for files matching the <code>-ShortTitle</code> and fetches the latest metadata from the episode page on the BBC's website. If there are discrepancies, it updates the media file’s title and comment. I would only use it with the <code>-Archive</code> option because it can be slow if you have a lot of files since it has to pull the web page of each file individually. Use case: Sometimes the BBC doesn't set the real title or description of a show until after it has already been published to Sounds. If you're also using genRSS to build a podcast feed, you'll want to specify <code>CheckMediaDirectoryHash=contents</code> in the podcast profile so that genRSS detects the changes and updates the feed with the new metadata.</li>
<li><code>-VPNConfig</code>: [<i>String array of file paths</i>] If using a VPN, this is the path to the OpenVPN .ovpn config file. It can also be an array of file locations separated by a comma. Since the BBC tries to block VPNs in a cat and mouse game, the script will run through each config in order until it finds one that can download the episode. If you're using VPN and running the script from the task scheduler, I would set the task to run more often so it gets more chances to work. I use PIA for VPN. Also be sure create and set an auth-user-pass file if using. See OpenVPN support for that.</li>
<li><code>-rcloneConfig</code>: [<i>File path</i>] If using rclone to upload the episode somewhere, this is the path to the rclone config file. You'll need to use:
<code class="block">rclone.exe config create</code>
to create a config. You can copy the config text to another file and specify it here.</li>
<li><code>-rcloneSyncDir</code>: [<i>String array of file paths</i>] This is the remote and directory that rclone should upload to. It should be in the form of 'Remote:Directory'. Remote is the name of the appropriate config found in the config file specified in rcloneConfig. The value can be an array separated by comma if you want to put it to multiple locations. For S3 API services, use <code>Remote:BucketName\Directory</code>.</li>
<li><code>-DotSrcConfig</code>: [<i>File path</i>] The path to an external script file (.ps1) that contains configuration options can be specified here. At a minimum, the file must contain values for <code>$DumpDirectory</code>, <code>$ffmpegExe</code>, <code>$ffprobeExe</code>, <code>$kid3Exe</code>, and <code>$ytdlpExe</code>. The best thing to do would be to simply copy the entire 'inline' configuration options section from SoundsDownloadScript.ps1 and paste it into a new .ps1. The script will be called using dot sourcing. It will override any settings that are also set in the inline configuration options. This was implemented to make it easier to upgrade SoundsDownloadScript without having to transcribe the settings to a new file each time. The dot sourcing method was selected over 'real' config files (ini, xml, json, etc.) to account for the multi-line remote script blocks which would have been hard to reliably implement using the other ways. It's not a great option, but it's there and it works. More information on dot sourcing can be found at <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scripts?view=powershell-7.4#script-scope-and-dot-sourcing">this Microsoft Learn page</a>.</li>
<li><code>-Logging</code>: [<i>Switch</i>] Output the console and variables to text files in the specified <code>-LogDirectory</code>. If rclone and OpenVPN are used, it will create separate log files for those in the same directory with the same name structure.</li>
<li><code>-LogDirectory</code>: [<i>Directory path</i>] The directory to save log files if <code>-Logging</code> is enabled.</li>
<li><code>-LogFileNameFormat</code>: [<i>Format string</i>] A format string to set the name of the log files (ex: <code>-LogFileNameFormat "{0}-{1}-{2}-{3}.log"</code>). <a href="#LogFileNameFormat_1">Available format variables can be found above</a>.</li>
<li><code>-NoDL</code>: [<i>Switch</i>] Use this option to skip downloading the episode. It's useful to see the metadata to set the TitleFormat and troubleshoot escape character and encoding problems.</li>
<li><code>-Force</code>: [<i>Switch</i>] Download the episode even if it's already downloaded. It will append an underscore number to the end so as not to overwrite an existing file. This can be useful for testing.</li>
</ul>
<p><i>TerminatingError(Invoke-WebRequest):</i></p>
<p>Sometimes the BBC updates the program page before it releases the episode on Sounds. This will cause SoundsDownloadScript.ps1 to try to download the episode that isn't available yet. In the Console+Vars log file, you'll see:</p>
<p><code class="block">PS>TerminatingError(Invoke-WebRequest):"<br>
<br>
<br>
BBC Sounds</code></p>
<p>followed by a lot of html and the message <code>Sorry, the page you are looking for cannot be found!</code>. Don't freak out if this happens. Just wait and run the script again later after the episode has been fully released.</p>
<h3 id="HowToRun_2">genRSS.ps1 (if using)</h3>
<p><i>Command line parameters:</i></p>
<ul>
<li><code>-Profile</code>: [<i>File path</i>] The path to the profile config file to use.</li>
<li><code>-Test</code>: [<i>File path</i>] A local file name to generate a test RSS file to. This option will not upload anything remotely. It is useful for testing without messing up a public RSS feed.</li>
<li><code>-Force</code>: [<i>Switch</i>] Force the script to update the RSS file, even if it doesn't need to be. This is useful for testing or pushing changes.</li>
<li><code>-Logging</code>: [<i>Switch</i>] Output the console and variables to a text file in the <code>$LogDirectory</code>.</li>
<li><code>-LogDirectory</code>: [<i>Directory path</i>] The directory to save log files if <code>-Logging</code> is enabled.</li>
<li><code>-LogFileNameFormat</code>: [<i>Format string</i>] A format string to set the name of the log files (ex: <code>-LogFileNameFormat "{0}-{1}-{2}-genRSS_{3}.log"</code>). <a href="#LogFileNameFormat_2">See the available variables above</a>.</li>
</ul>
<h3 id="HowToRun_3">Windows Task Scheduler Examples</h3>
<p>If you're using Windows Task Scheduler, here are some examples of how to format the actions:</p>
<i>Basic download:</i>
<ul><li><b>Program:</b> <code>"C:\Program Files\PowerShell\7\pwsh.exe"</code> <b>Arguments:</b> <code>-Command "& 'C:\Program Files\VideoLAN\VLC\SoundsDownloadScript.ps1' -ProgramURL https://www.bbc.co.uk/programmes/b006r9yq -SaveDir 'F:\Audio\The News Quiz' -ShortTitle TheNewsQuiz -TitleFormat '{1} - {2}' -Archive 0 -Logging -LogDirectory 'C:\Logs'"</code></li></ul>
<br>
<i>Uploading to CloudFlare R2 and generating an RSS:</i>
<ul>
<li><b>Program:</b> <code>"C:\Program Files\PowerShell\7\pwsh.exe"</code> <b>Arguments:</b> <code>-Command "& 'C:\Program Files\VideoLAN\VLC\SoundsDownloadScript.ps1' -ProgramURL https://www.bbc.co.uk/programmes/b006qfvv -SaveDir 'F:\Audio\Shipping Forecast' -ShortTitle ShippingForecast -TitleFormat '{1} {4:HH:mm}' -rcloneConfig 'C:\Program Files\VideoLAN\VLC\rclone\rclone.conf' -rcloneSyncDir 'bbcsoundsrss_r2:bbcsoundsrss\ShippingForecast\media' -Archive 7 -Days -Logging"</code></li>
<li><b>Program:</b> <code>"C:\Program Files\PowerShell\7\pwsh.exe"</code> <b>Arguments:</b> <code>-Command "& 'C:\Program Files\VideoLAN\VLC\genRSS.ps1' -Profile 'E:\FeedProfiles\ShippingForecast' -Logging -LogDirectory 'C:\Logs'"</code></li>
</ul>
<br>
<i>Using VPN and uploading to archive.org:</i>
<ul><li><b>Program:</b> <code>"C:\Program Files\PowerShell\7\pwsh.exe"</code> <b>Arguments:</b> <code>-Command "& 'C:\Program Files\VideoLAN\VLC\SoundsDownloadScript.ps1' -ProgramURL https://www.bbc.co.uk/programmes/b0100rp6 -SaveDir 'F:\Audio\Radcliffe and Maconie' -ShortTitle RadMac -VPNConfig 'C:\Program Files\OpenVPN\config\uk_streaming.ovpn,C:\Program Files\OpenVPN\config\uk_southampton.ovpn,C:\Program Files\OpenVPN\config\uk_manchester.ovpn,C:\Program Files\OpenVPN\config\uk_london.ovpn' -rcloneConfig 'C:\Program Files\VideoLAN\VLC\rclone\rclone.conf' -rcloneSyncDir 'internetarchive_config:' -Archive 0"</code></li></ul>
<br>
<h2 id="Documentation">DOCUMENTATION:</h2>
<p>Feel free to mess around in the code if you're comfortable with Powershell.</p>
<h3>SoundsDownloadService.ps1</h3>
<p>I tried to make SoundsDownloadScript.ps1 fairly modular and include comments to help, but there are a few things to keep in mind:</p>
<i>Audio file name:</i>
<p>The naming format for finished audio file is [ShortTitle]-[YYYYMMDD]-[Program ID]. If you decide to change that format, you could break some things unintentionally. For example, the script specifically looks for the 8-character Program ID in the file name to decide whether it's already been downloaded. genRSS.ps1 also relies on it for certain options. The script also uses a regex pattern to match files to delete if the <code>-Archive</code> parameter is set. Just be sure you update all of those things if you make changes to the file name format.</p>
<i>RCLONE script blocks:</i>
<p>If rclone is enabled, the script will take a hash of the <code>-SaveDir</code> and save it to a user-level environment variable with the same name as the SaveDir. This will track folder changes between instances. It will then take a hash of the SaveDir after running and compare the two hashes to decide whether to run the through the script blocks to run rclone.</p>
<p>You can test whether the environment variable is working properly by checking the <code>$SaveDirHashIsFromEnvVar</code> variable in the Console+Vars log. If it's <code>$true</code> then the hash was successfully pulled from the environment variable. If it's <code>$false</code>, then it couldn't find the variable and it generated the hash on the fly.</p>
<i>Tags with special characters:</i>
<p>Certain special characters have to be escaped with a backslash (<code>\</code>) in kid3 or kid3 will choke. I don't think a complete list exists. SoundsDownloadService escapes single quotes, double quotes, and pipes in a function called Format-kid3CommandString. If you find another character that needs to be escaped, you can add it to the <code>Return</code> line using the <code>replace</code> method.
<code class="block">
Function Format-kid3CommandString ($StringToFormat) {<br>
Return $StringToFormat.replace("'","\'").replace("\`"","`"").replace("`"","\`"").replace("|","\|")<br>
}</code>
Note: You may also need to escape the special characters in Powershell for it to pass them correctly, especially quotes. Use a backtick (<code>`</code>) for that.
</p>
<i id="TitleFormat">TitleFormat:</i>
<p>SoundsDownloadScript uses the format operator (<code>-f</code>) to let you build dynamic title formats. The available options are stored in an array called <code>$TitleFormatArray</code>. The first one starts at <code>{0}</code>.
<code class="block">$TitleFormatArray = $TitleTable.'primary', $TitleTable.'secondary', $TitleTable.'tertiary', $ReleaseDate.ToUniversalTime(), [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($ReleaseDate, 'GMT Standard Time')</code></p>
<p>You can add more variables to the array to suit your title needs, but you should append them to the end so that they become <code>{5}</code>, <code>{6}</code>, etc. Otherwise it will scoot the numbering down the line and potentially screw up your previous settings. You can use this to format the dates a different way, or add any other values from the json metadata. See <a href="https://ss64.com/ps/syntax-f-operator.html">this page</a> and/or <a href="https://arcanecode.com/2021/07/19/fun-with-powershell-string-formatting/">this page</a> for more information on the format operator.</p>
<p>To add other values, I recommend you set <code>$Printjson</code> to <code>$true</code> to view the data and paste it into an <a href="https://codebeautify.org/jsonviewer">online json viewer</a> to help you find a reference point to the value you're looking for. The script parses the metadata from json format into the <code>$jsonData</code> variable. Any of these fields can be used. Recalling a json value with powershell will look something like these examples:
<ul>
<li>The network station name (e.g. Radio 1): <code>$jsonData.modules.data[0].data.network.short_title</code></li>
<li>The episode duration: <code>$jsonData.modules.data[0].data.duration.label</code></li>
</ul>
Once you have the reference point, add it to the end of <code>$TitleFormatArray</code>.</p>
<h3>genRSS.ps1</h3>
<p>I heavily modified <a href="https://gist.github.com/arebee/a7a77044c77443effaeddbe3730af4ad">this script</a> to create genRSS.ps1. I don't really understand the XML writer part of it, only that it just seems to work. The whole script is messy and very inefficient. Be sure to test THOROUGHLY before putting changes to genRSS into production because you can screw up your subscriber's feeds. Good luck!</p>
<i>PSP-1: The Podcast RSS Standard:</i>
<p>I am trying to make the RSS output to be <a href="https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification?tab=readme-ov-file#channel-itunes-explicit">PSP-1 compliant</a> as much as I can. If a required element is missing or something isn't implemented according to the standard, open an issue on github.</p>
<i>Global logging:</i>
<p>The logging variable can be set in each profile template or through the command line with the <code>-Logging</code> switch. It can also be enabled or disabled globally by adding <code>$Logging = $true</code> or <code>$Logging = $false</code> to the configuration options section in genRSS.ps1. To force logging on for all instances, set to <code>$true</code>. To disable it on all instances, set to <code>$false</code>. If set to <code>$false</code>, it will override any <code>Logging=yes</code> entries in the profile templates and <code>-Debug</code> command line parameters. It effectively forces no genRSS logging. To not force logging on or off and restore the option back to the profiles or command line, just remove or comment out the <code>$Logging</code> variable.</p>
<p>With the way the script is laid out, I couldn't figure out how to gracefully determine if the <code>$Logging</code> switch was explicitly set to <code>$false</code> in the script. I got stubborn and chose to use Select-String to test a regex pattern:
<code class="block">Select-String -Path $PSCommandPath -Pattern '^[\s*]*(\$Logging)[\s*]*=[\s*]*(\$false)(\s*$|\s*#.*)'</code>
Basically it searches the script for a <code>$Logging = $false</code> string. It works, but it's not efficient, goes against how powershell should work, and I don't like it. I know there's a better way. If you know what it is, open an issue. I'm all ears.</p>
<h3 id="ID3Tags">MP4/ID3 tags</h3>
<p>Having correct MP4/ID3 tags is crucial for both scripts to "work" together. SoundsDownloadScript.ps1 sets way more tags than this, but these are the important ones for genRSS.ps1 to work properly:</p>
<ul>
<li>Title: The title of the episode <title></li>
<li>Album: The name of the show </li>
<li>Artist: The station or service the episode aired on (e.g. Radio 1)</li>
<li>albumart: The URL to the .jpg cover art <media:content></li>
<li>Comment: The episode description and track list <description></li>
<li>RELEASEDATE/TDRL: The date the program was released on BBC Sounds <pubDate></li>
<li>ORIGINALALBUM/TDOR: The date the program originally aired - used for AutoDetectReruns</li>
<li>WEBSITE/User-defined URL: The bbc.co.uk/programmes episode page <guid>, <link></li>
<li>AudioSourceURL: The URL to the BBC Sounds episode page</li>
</ul>
<p>SoundsDownloadScript.ps1 uses kid3 to set tags. If you want to change them or add additional ones, <a href="https://kid3.sourceforge.io/kid3_en.html#frame-list">The Kid3 Handbook</a> has a breakout of what tags it supports. genRSS.ps1 also uses kid3 to read tags. kid3 spits out the tags in json format and then the script parses it. If you're having trouble, you can run<br>
<code class="block">.\kid3-cli.exe -c '{\"method\":\"get\"}' '[FILE]'</code>
to see exactly what kid3 is reading from the audio file and passing to the script. Sometimes special characters can trip it up and it might be necessary to add to the <code>$StringToFormat</code> variable in the Format-kid3CommandString function. It will probably take some troubleshooting. Turn on the logging to help.</p>
<br>
<h2 id="Support">SUPPORT:</h2>
<p>For help with SoundsDownloadScript.ps1 or genRSS.ps1:</p>
<ul class="bullets">
<li>To report a bug or request a feature: <a href="https://github.com/endkb/SoundsDownloadScript/issues">Open an issue on github</a></li>
<li>To ask a question: Drop a message to <a href="mailto:endkb@proton.me">endkb<!-- -->@<!-- -->proton.me</a> or on reddit at <a href="https://reddit.com/u/endkb">u/endkb</a></li>
</ul>
<br>
<h2 id="MITLicense">MIT LICENSE:</h2>
<p>Copyright © 2024 endkb (https://github.com/endkb)</p>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>
<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
<br>
</body>
</html>