Merge branch 'main' of repo.anhonesthost.net:HPR/hpr-tools

This commit is contained in:
Dave Morriss 2024-11-23 22:39:30 +00:00
commit 7e925621f4
31 changed files with 10294 additions and 0 deletions

14
feed_watcher/db_regen Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
#
# Make a new empty database using the schema file
#
BASEDIR="$HOME/HPR/feed_watcher"
DB="$BASEDIR/feedWatcher.db"
SCHEMA="$BASEDIR/feedWatcher_schema.sql"
if [[ -e $DB ]]; then
rm -f "$DB"
fi
sqlite3 "$DB" < "$SCHEMA"

2195
feed_watcher/feedWatcher Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
<database>
type = SQLite
file = feedWatcher.db
user =
password =
</database>

BIN
feed_watcher/feedWatcher.db Normal file

Binary file not shown.

View File

@ -0,0 +1,708 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
/* prefixed by https://autoprefixer.github.io (PostCSS: v7.0.26, autoprefixer: v9.7.3) */
html {
height: 100%;
background-color: black;
font-family: "FreeSans", sans-serif;
}
body {
background-color: white;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
-ms-flex-line-pack: center;
align-content: center;
padding: 0;
height: 100%;
max-width: 50%;
margin: 0 auto 0 auto;
}
body > * {
padding: 1em 1em;
}
main {
background-color: #efbe0032;
background-color: hsla(47.7,100%,46.9%, 0.2);
-webkit-box-flex: 1;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
overflow: auto;
}
main * {
opacity: 100%;
}
header, footer {
-webkit-box-flex: 0;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
background-color: #efbe00;
background-color: hsla(47.7,100%,46.9%, 1);
}
footer {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
padding-top: 1em;
padding-bottom: 1em;
}
header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-basis: 50%;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: start;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}
header > * {
margin: 0;
padding: 0;
min-height: 0;
}
header img {
max-height: 128px;
min-height: 128px;
}
header h1 {
padding: 0 0.5em 0 0.15em;
-webkit-box-flex: 2;
-ms-flex-positive: 2;
flex-grow: 2;
color: white;
font-size: 3rem;
text-shadow: -1px 1px 0 #000,
1px 1px 0 #000,
1px -1px 0 #000,
-1px -1px 0 #000;
}
header h1 span {
white-space: nowrap;
}
header aside {
border: solid 0.5em black;
padding: 1em;
background-color: #FCF2CD;
background-color: hsla(47.2, 88.7%, 89.6%, 1);
-ms-flex-negative: 1;
flex-shrink: 1;
}
dt {
font-weight: 600;
margin-top: 0.25em;
}
dd {
margin-top: 0.15em;
font-weight: 400;
}
@media only screen and (max-width: 1270px){
html {
border-left: solid 0.5em black;
border-right: solid 0.5em black;
}
body {
max-width:none;
}
header {
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
header h1 {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 67%;
}
header aside {
margin-top: 0.5em;
}
}
@media only screen and (max-width: 600px){
body > * {
padding: 0.5em;
}
header > * {
padding: 0;
}
header h1 {
padding-right: 0;
font-size: 2rem;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 70%;
}
header img {
max-height: 96px;
min-height: 96px;
}
header aside {
font-size: 0.95rem;
padding: 0.5em;
}
footer img {
max-height: 32px;
}
}
@media only screen and (max-height: 780px) and (orientation: landscape) {
html {
height:auto;
display:initial;
}
}
</style>
</head>
<body>
<header>
<img src="./logo.svg" alt="[logo]">
<h1><span>Free Culture</span> Podcasts</h1>
<aside>
The finest selection of Free Culture Podcasts spanning
the genres of Discussion, Drama, Education, Music, and beyond.
</aside>
</header>
<main>
<dl>
<dt><a href="https://www.adminadminpodcast.co.uk">Admin Admin Podcast</a> (<a href="http://feeds.feedburner.com/TheAdminAdminPodcast">feed</a>)</dt>
<dd>A Podcast about servers and Networking</dd>
<dt><a href="https://twit.tv/shows/all-about-android">All About Android (MP3)</a> (<a href="http://feeds.twit.tv/aaa.xml">feed</a>)</dt>
<dd>All About Android delivers everything you want to know about Android each week--the biggest news, freshest hardware, best apps and geekiest how-tos--with Android enthusiasts Jason Howell, Florence Ion, Ron Richards, and a variety of special guests along the way. Records live every Tuesday at 8:00pm Eastern / 5:00pm Pacific / 01:00 (Wed) UTC.</dd>
<dt><a href="http://aiit.se/radio/">All In IT Radio (ogg)</a> (<a href="http://feeds.aiit.se/allinit-radio-ogg">feed</a>)</dt>
<dd>Join us as we talk about everything related to Information Technology, and some other random stuff as well.</dd>
<dt><a href="http://www.amateurlogic.tv">AmateurLogic.TV</a> (<a href="http://amateurlogic.tv/transmitter/feeds/ipod.xml">feed</a>)</dt>
<dd>A monthly podcast covering Ham Radio equipment, events and personalities.</dd>
<dt><a href="https://leukemensen.nl/angrynerds/">Angry Nerds</a> (<a href="https://leukemensen.nl/angrynerds/feed.xml">feed</a>)</dt>
<dd>Heb je al gehoord over die laatste grote hack? Een panel van kritische deskundigen bespreken privacy- en security gerelateerde onderwerpen uit het nieuws in gewone mensentaal.</dd>
<dt><a href="https://cchits.net/daily">CCHits.net</a> (<a href="https://cchits.net/daily/rss">feed</a>)</dt>
<dd>CCHits.net is designed to provide a Chart for Creative Commons Music, in a way that is easily able to be integrated into other music shows that play Creative Commons Music. CCHits.net has a daily exposure podcast, playing one new track every day, a weekly podcast, playing the last week of tracks played on the podcast, plus the top rated three tracks from the previous week. There is also a monthly podcast which features the top rated tracks over the whole system.</dd>
<dt><a href="https://ccjam.otherside.network">CCJam</a> (<a href="https://ccjam.otherside.network/feed/podcast/">feed</a>)</dt>
<dd>Community music podcast &ndash; turn up the volume!!!</dd>
<dt><a href="http://www.castofwonders.org">Cast of Wonders</a> (<a href="http://www.castofwonders.org/feed/podcast/">feed</a>)</dt>
<dd>The Young Adult Speculative Fiction Podcast</dd>
<dt><a href="https://category5.tv/">Category5 Technology TV (MP3 Audio)</a> (<a href="https://rss.cat5.tv/audio.rss">feed</a>)</dt>
<dd>Weekly live digital TV show with Robbie Ferguson focused on topics of interest to tech minds. Ask your questions and get live answers. Recipient of 2014 and 2017 Top 100 Tech Podcasters award.</dd>
<dt><a href="https://destinationlinux.org/blog/">Destination Linux</a> (<a href="http://destinationlinux.org/feed/mp3/">feed</a>)</dt>
<dd>A podcast made by people who love running Linux.</dd>
<dt><a href="https://distrohoppersdigest.blogspot.com/">Distrohoppers&#39; Digest</a> (<a href="http://feeds.feedburner.com/blogspot/jejQf">feed</a>)</dt>
<dd>We are two Blokes who love Linux and trying out new stuff, we thought it would be interesting to share our experience of trying new Linux and BSD distributions and how we found it trying to live with them as our daily driver for up to a Month at a time, by recording a podcast about how we got on.</dd>
<dt><a href="https://edictzero.wordpress.com">Edict Zero &ndash; FIS</a> (<a href="https://edictzero.wordpress.com/feed/">feed</a>)</dt>
<dd>Home of the Science Fiction Audio Drama series</dd>
<dt><a href="http://wikipediapodden.se">English &ndash; Wikipediapodden</a> (<a href="http://wikipediapodden.se/tag/english/feed/">feed</a>)</dt>
<dd>En podcast om Wikipedia p&aring; svenska</dd>
<dt><a href="http://escapepod.org">Escape Pod</a> (<a href="http://escapepod.org/feed/">feed</a>)</dt>
<dd>The Original Science Fiction Podcast</dd>
<dt><a href="https://twit.tv/shows/floss-weekly">FLOSS Weekly (MP3)</a> (<a href="http://feeds.twit.tv/floss.xml">feed</a>)</dt>
<dd>We&#39;re not talking dentistry here; FLOSS all about Free Libre Open Source Software. Join host Randal Schwartz and his rotating panel of co-hosts every Wednesday as they talk with the most interesting and important people in the Open Source and Free Software community. Records live every Wednesday at 12:30pm Eastern / 9:30am Pacific / 17:30 UTC.</dd>
<dt><a href="http://faif.us/cast/">Free as in Freedom</a> (<a href="http://faif.us/feeds/cast-ogg/">feed</a>)</dt>
<dd>A bi-weekly discussion of legal, policy, and other issues in the open source and software freedom community (including occasional interviews) from Brooklyn, New York, USA. Presented by Karen Sandler and Bradley M. Kuhn.</dd>
<dt><a href="http://www.gnuworldorder.info">GNU World Order Linux Cast</a> (<a href="https://gnuworldorder.info/ogg.xml">feed</a>)</dt>
<dd>GNU, Linux, coffee, and subversion.</dd>
<dt><a href="https://geekspeak.org/">Geek Speak with Lyle Troxell</a> (<a href="https://geekspeak.org/episodes/rss.xml">feed</a>)</dt>
<dd>A weekly talk show about technology, science, and human creativity that excites, educates, and fosters curiosity. Discussions touch upon how technology affects society and how we react to that change. Hosts are passionate about explaining complex concepts in simple, easy to digest, chunks. We bridge the gaps between Geeks and the rest of humanity.</dd>
<dt><a href="http://frontrowcrew.com/geeknights/monday/">GeekNights Mondays: Science Technology Computing</a> (<a href="http://feeds.feedburner.com/GNSciTech">feed</a>)</dt>
<dd>GeekNights Mondays is the weekly sci/tech segment of GeekNights, featuring science, technology, computing, and more. We talk Linux, Windows, gadgets, you name it.</dd>
<dt><a href="https://goinglinux.com">Going Linux</a> (<a href="http://goinglinux.com/oggpodcast.xml">feed</a>)</dt>
<dd>Once you become aware that there is a dependable, secure, capable, and modern computer system that rivals all others in popularity and actual use, you will want to try the Linux operating system on your computer. Perhaps you&#39;ve been using a member of the Unix/Linux family - Linux, Android, ChromeOS, BSD or even OSX - for quite a while. If so, you are likely looking for new ways to optimize your technology for the way you work. Going Linux is for computer users who just want to use Linux to get things done. Are you new to Linux, upgrading from Windows to Linux, or just thinking about moving to Linux? This audio podcast provides you with practical, day-to-day advice on how to use Linux and its applications. Our goal is to help make the Linux experience easy for you.</dd>
<dt><a href="http://hackerpublicradio.org/about.php">Hacker Public Radio</a> (<a href="http://hackerpublicradio.org/hpr_ogg_rss.php">feed</a>)</dt>
<dd>Hacker Public Radio is an podcast that releases shows every weekday Monday through Friday. Our shows are produced by the community (you) and can be on any topic that are of interest to hackers and hobbyists.</dd>
<dt><a href="https://knightwise.com">Knightwise.com Audio Feed.</a> (<a href="http://feeds.feedburner.com/knightcastpodcast">feed</a>)</dt>
<dd>The cross platform podcast that makes technology work for you and not the other way around. The place to go for all geeks who slide between Mac, iOS, Android, Linux and Windows offering an essential mix of hacks, tips, howto&#39;s and tweaks spiced up with a dash of geek culture. Also check out our Mediafeed that has both our audio and video episodes.</dd>
<dt><a href="https://latenightlinux.com">Late Night Linux (Ogg)</a> (<a href="http://latenightlinux.com/feed/ogg">feed</a>)</dt>
<dd><em>No description</em></dd>
<dt><a href="https://librelounge.org">Libre Lounge</a> (<a href="https://librelounge.org/rss-feed.rss">feed</a>)</dt>
<dd>A casual podcast about user freedom</dd>
<dt><a href="http://podnutz.com">Linux For The Rest Of Us - Podnutz</a> (<a href="http://feeds.feedburner.com/linuxfortherestofus">feed</a>)</dt>
<dd>Linux For The Rest Of Us - Podnutz</dd>
<dt><a href="https://linuxlads.com/feed_ogg.rss">Linux Lads</a> (<a href="https://linuxlads.com/feed_ogg.rss">feed</a>)</dt>
<dd>Chat about Linux and all things connected</dd>
<dt><a href="http://setbit.org/lt.html">Linux Trivia Podcast</a> (<a href="http://setbit.org/lt-ogg.xml">feed</a>)</dt>
<dd>Verbal&#39;s Linux Trivia Podcast</dd>
<dt><a href="https://lhspodcast.info/category/podcast-ogg/">Linux in the Ham Shack (OGG Feed)</a> (<a href="https://lhspodcast.info/category/podcast-ogg/feed/">feed</a>)</dt>
<dd>Linux in the Ham Shack Podcast in OGG Format</dd>
<dt><a href="https://linuxgamecast.com">LinuxGameCast</a> (<a href="https://linuxgamecast.com/feed/">feed</a>)</dt>
<dd>Linux fueled mayhem &amp; madness with a side of news, reviews, and whatever the Hell-Elks&trade; we come up with</dd>
<dt><a href="http://linuxlugcast.com">Linuxlugcast</a> (<a href="http://feeds.feedburner.com/linuxlugcast-ogg">feed</a>)</dt>
<dd>Linuxlugcast</dd>
<dt><a href="https://makerscorner.tech">Makers Corner, with Nate and Yannick</a> (<a href="https://makerscorner.tech/feed/podcast/">feed</a>)</dt>
<dd>A tech oriented DIY podcast, from the Other Side Podcast Network</dd>
<dt><a href="http://downloads.cavalcadeaudio.com/stardrifter-novels/01-motherload/">Motherload</a> (<a href="http://downloads.cavalcadeaudio.com/stardrifter-novels/01-motherload/feed.xml">feed</a>)</dt>
<dd>A remote corner of a bleak system. A broken-down gunboat, stuck in space. An incompetent captain and a misfit crew. A pirate ship, a silent target, and a whole bunch of secrets. So how&#39;s YOUR day going?</dd>
<dt><a href="https://mintcast.org">OGG &ndash; mintCast</a> (<a href="https://mintcast.org/category/ogg/feed/">feed</a>)</dt>
<dd>Just another WordPress site</dd>
<dt><a href="http://openmetalcast.com">Open Metalcast</a> (<a href="http://feeds.feedburner.com/OpenMetalcast">feed</a>)</dt>
<dd>Music that will rip your face off and give a copy to your friends</dd>
<dt><a href="http://opensourcesecuritypodcast.com">Open Source Security Podcast</a> (<a href="https://opensourcesecuritypodcast.libsyn.com/rss">feed</a>)</dt>
<dd>A security podcast geared towards those looking to better understand security topics of the day. Hosted by Kurt Seifried and Josh Bressers covering a wide range of topics including IoT, application security, operational security, cloud, devops, and security news of the day. There is a special open source twist to the discussion often giving a unique perspective on any given topic.</dd>
<dt><a href="http://petecogle.co.uk/blog/category/pc-podcast/">PC Podcast. A Music Podcast with Pete Cogle, since January 2006</a> (<a href="http://feeds.feedburner.com/pcpodcast">feed</a>)</dt>
<dd>Guaranteed to widen your musical perspectives, Pete Cogle plays tracks from all over the world in all possible genres. Like it, or hate it, it&#39;s a change from the same old shit on clearchannel radio.</dd>
<dt><a href="http://podcastle.org">PodCastle</a> (<a href="http://podcastle.org/feed/">feed</a>)</dt>
<dd>The Fantasy Fiction Podcast</dd>
<dt><a href="https://craphound.com">Podcast &ndash; Cory Doctorow&#39;s craphound.com</a> (<a href="http://feeds.feedburner.com/doctorow_podcast">feed</a>)</dt>
<dd>Cory Doctorow&#39;s Literary Works</dd>
<dt><a href="http://pseudopod.org">PseudoPod</a> (<a href="http://pseudopod.org/feed/">feed</a>)</dt>
<dd>The Sound of Horror</dd>
<dt><a href="https://www.radiotux.de/">RadioTux</a> (<a href="http://radiotux.de/podcast/rss/radiotux-all.xml">feed</a>)</dt>
<dd>Linux, Open Source und Netzkultur</dd>
<dt><a href="http://ratholeradio.org">RatholeRadio.org (Ogg Version)</a> (<a href="http://feeds.feedburner.com/RatholeRadio-ogg">feed</a>)</dt>
<dd>Music, Waffling, Live Performances &amp; Laughs</dd>
<dt><a href="https://twit.tv/shows/security-now">Security Now (MP3)</a> (<a href="http://feeds.twit.tv/sn.xml">feed</a>)</dt>
<dd>Steve Gibson, the man who coined the term spyware and created the first anti-spyware program, creator of Spinrite and ShieldsUP, discusses the hot topics in security today with Leo Laporte. Records live every Tuesday at 4:30pm Eastern / 1:30pm Pacific / 21:30 UTC.</dd>
<dt><a href="http://www.skepticule.co.uk/">Skepticule</a> (<a href="http://www.skepticule.co.uk/feeds/posts/default?alt=rss">feed</a>)</dt>
<dd>Spanking the bottom of ignorance since 2009</dd>
<dt><a href="https://smlr.us">Sunday Morning Linux Review - OGG Feed</a> (<a href="http://feeds.feedburner.com/SundayMorningLinuxReview-OggFeed">feed</a>)</dt>
<dd>Sunday Morning Linux Review &ndash; OGG Feed for Freedom Lovers!</dd>
<dt><a href="https://teaearlgreyhot.org">Tea, Earl Grey, Hot !</a> (<a href="https://teaearlgreyhot.org/feed/podcast">feed</a>)</dt>
<dd>An unofficial Star Trek fan podcast from the Other Side Podcast Network</dd>
<dt><a href="https://www.thebinarytimes.net">The Binary Times Audiocast - ogg</a> (<a href="https://www.thebinarytimes.net/rss-ogg.xml">feed</a>)</dt>
<dd>Linux and open source tips, tricks and discussion. Free software, hardware and modern culture. The Binary Times Audiocast is created by Mark and Wayne, two chaps who just like using linux and open source software and want to spread the word. Linux is free and open source and it is an excellent choice of operating system for our ever changing times. This audiocast is released fortnightly.</dd>
<dt><a href="https://thebugcast.org">The Bugcast - Ogg Feed</a> (<a href="http://thebugcast.org/feed/ogg/">feed</a>)</dt>
<dd>Award-winning music and chat from South Yorkshire in the UK</dd>
<dt><a href="http://thecommandline.net/">The Command Line Podcast</a> (<a href="https://thecommandline.net/files/cmdln_mp3.xml">feed</a>)</dt>
<dd>A podcast by a self-described hacker, curmudgeon and hacktivist about the practice and profession of programming drawing on over a decade of professional experience and a lifetime spent hacking, the intersection of politics and society with technology and anything else clever, elegant or funny that catches my mind as a die hard technology geek.</dd>
<dt><a href="http://petecogle.co.uk/blog">The Dub Zone</a> (<a href="http://feeds.feedburner.com/TheDubZone">feed</a>)</dt>
<dd>An 8-track mix of the best dub reggae, every month. Curated by Pete Cogle, since May 2007.</dd>
<dt><a href="https://duffercast.org">The Duffercast in Ogg Vorbis</a> (<a href="http://duffercast.org/feed/podcast/">feed</a>)</dt>
<dd>Some auld duffers providing world wide wisdom</dd>
<dt><a href="https://fullcirclemagazine.org/">The Full Circle Weekly News</a> (<a href="https://fullcirclemagazine.org/feed/podcast">feed</a>)</dt>
<dd>From the independent magazine for the Ubuntu Linux community. The Full Circle Weekly News is a short podcast with just the news. No chit-chat. No time wasting. Just the latest FOSS/Linux/Ubuntu news.</dd>
<dt><a href="http://thejakattack.com/">The JaK Attack! Podcast</a> (<a href="http://feeds.feedburner.com/TheJakAttack">feed</a>)</dt>
<dd>Where Chic meets geek! The JaK Attack! is hoted by Jon Watson and Kelly Penguin Girl. We talk about tech, art, and we do it just a little too loud.</dd>
<dt><a href="http://www.jodcast.net/">The Jodcast (high bandwidth)</a> (<a href="http://www.jodcast.net/rss-high.xml">feed</a>)</dt>
<dd>Monthly astronomy news, interviews and questions. Created by astronomers.</dd>
<dt><a href="http://www.tllts.org">The Linux Link Tech Show Ogg-Vorbis Feed</a> (<a href="http://feeds.feedburner.com/TheLinuxLinkTechShowOgg-vorbisFeed">feed</a>)</dt>
<dd>The Linux Link Tech Show</dd>
<dt><a href="https://twit.tv/shows/this-week-in-computer-hardware">This Week in Computer Hardware (MP3)</a> (<a href="http://feeds.twit.tv/twich.xml">feed</a>)</dt>
<dd>If you obsess about the details inside computers then on This Week in Computer Hardware, you&#39;ll find out the latest in motherboards, CPUs, GPUs, storage, RAM, power supplies, input devices, and monitors. Hosts Patrick Norton of TekThing and Sebastian Peak of PC Perspective bring you the newest hardware, talk benchmarks, and even dive into the not-yet-released products on the horizon. Records live every Thursday at 3:30pm Eastern / 12:30pm Pacific / 20:30 UTC.</dd>
<dt><a href="https://twit.tv/shows/this-week-in-google">This Week in Google (MP3)</a> (<a href="http://feeds.twit.tv/twig.xml">feed</a>)</dt>
<dd>Leo Laporte, Jeff Jarvis, Stacey Higginbotham, Ant Pruitt, and their guests talk about the latest Google and cloud computing news. Records live every Wednesday at 4:00pm Eastern / 1:00pm Pacific / 21:00 UTC.</dd>
<dt><a href="http://www.asm.org/twim/">This Week in Microbiology</a> (<a href="http://feeds.feedburner.com/twim">feed</a>)</dt>
<dd>This Week in Microbiology is a podcast about unseen life on Earth hosted by Vincent Racaniello and friends. Following in the path of his successful shows &#39;This Week in Virology&#39; (TWiV) and &#39;This Week in Parasitism&#39; (TWiP), Racaniello and guests produce an informal yet informative conversation about microbes which is accessible to everyone, no matter what their science background.</dd>
<dt><a href="https://twit.tv/shows/this-week-in-tech">This Week in Tech (MP3)</a> (<a href="http://feeds.twit.tv/twit.xml">feed</a>)</dt>
<dd>Your first podcast of the week is the last word in tech. Join the top tech pundits in a roundtable discussion of the latest trends in high tech. Records live every Sunday at 5:15pm Eastern / 2:15pm Pacific / 22:15 UTC.</dd>
<dt><a href="https://tuxjam.otherside.network">TuxJam OGG</a> (<a href="https://tuxjam.otherside.network/?feed=podcast">feed</a>)</dt>
<dd>TuxJam is a family friendly show that blends Creative Commons music and Open Source goodness. This is an OGG feed.</dd>
<dt><a href="http://ubuntupodcast.org">Ubuntu Podcast</a> (<a href="http://ubuntupodcast.org/feed/">feed</a>)</dt>
<dd>Upbeat and family-friendly show including news, discussion, interviews and reviews from the Ubuntu, Linux and Open Source world.</dd>
<dt><a href="http://wikipediapodden.se/prenumerera/">Wikipediapodden</a> (<a href="http://wikipediapodden.se/feed/podcast/">feed</a>)</dt>
<dd>En podcast om Wikipedia p&aring; svenska</dd>
<dt><a href="https://urandom-podcast.info/">urandom podcast</a> (<a href="http://feeds.feedburner.com/urandom-podcast/ogg">feed</a>)</dt>
<dd>urandom: your unlimited source of medium quality randomness</dd>
</dl>
<p>and many <a href="https://gpodder.net/directory/">more</a> ...</p>
</main>
<footer>
<img src="./cc.svg" alt="[Creative Commons]">
<img src="./attribution.svg" alt="[Attribution]">
<img src="./share_alike.svg" alt="[Share Alike]">
<img src="./remix.svg" alt="[Remix]">
</footer>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,290 @@
# Free Culture Podcasts
### The finest selection of Free Culture Podcasts spanning the genres of Discussion, Drama, Education, Music, and beyond.
- **Admin Admin Podcast**
- Website: https://www.adminadminpodcast.co.uk
- Feed: http://feeds.feedburner.com/TheAdminAdminPodcast
- Licence:
- **All About Android (MP3)**
- Website: https://twit.tv/shows/all-about-android
- Feed: http://feeds.twit.tv/aaa.xml
- Licence: This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/
- **All In IT Radio (ogg)**
- Website: http://aiit.se/radio/
- Feed: http://feeds.aiit.se/allinit-radio-ogg
- Licence: Creative Commons Attribution-NoDerivs 3.0 Unported License
- **AmateurLogic.TV**
- Website: http://www.amateurlogic.tv
- Feed: http://amateurlogic.tv/transmitter/feeds/ipod.xml
- Licence: Creative Commons
- **Angry Nerds**
- Website: https://leukemensen.nl/angrynerds/
- Feed: https://leukemensen.nl/angrynerds/feed.xml
- Licence: leukemensen.nl
- **CCHits.net**
- Website: https://cchits.net/daily
- Feed: https://cchits.net/daily/rss
- Licence: The content created by this site is generated by a script which is licensed under the Affero General Public License version 3 (AGPL3). The generated content is released under a Creative Commons By-Attribution License.
- **CCJam**
- Website: https://ccjam.otherside.network
- Feed: https://ccjam.otherside.network/feed/podcast/
- Licence:
- **Cast of Wonders**
- Website: http://www.castofwonders.org
- Feed: http://www.castofwonders.org/feed/podcast/
- Licence: Copyright © 2011-2019
- **Category5 Technology TV (MP3 Audio)**
- Website: https://category5.tv/
- Feed: https://rss.cat5.tv/audio.rss
- Licence: This work is licensed under a Creative Commons License - Attribution-NonCommercial-ShareAlike - https://creativecommons.org/licenses/by-nc-sa/3.0/
- **Destination Linux**
- Website: https://destinationlinux.org/blog/
- Feed: http://destinationlinux.org/feed/mp3/
- Licence:
- **Distrohoppers' Digest**
- Website: https://distrohoppersdigest.blogspot.com/
- Feed: http://feeds.feedburner.com/blogspot/jejQf
- Licence:
- **Edict Zero FIS**
- Website: https://edictzero.wordpress.com
- Feed: https://edictzero.wordpress.com/feed/
- Licence:
- **Escape Pod**
- Website: http://escapepod.org
- Feed: http://escapepod.org/feed/
- Licence:
- **FLOSS Weekly (MP3)**
- Website: https://twit.tv/shows/floss-weekly
- Feed: http://feeds.twit.tv/floss.xml
- Licence: This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/
- **Free as in Freedom**
- Website: http://faif.us/cast/
- Feed: http://faif.us/feeds/cast-ogg/
- Licence: 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2018, 2019, Free as in Freedom. Licensed under a Creative Commons Attribution-Share Alike 3.0 USA License.
- **GNU World Order Linux Cast**
- Website: http://www.gnuworldorder.info
- Feed: https://gnuworldorder.info/ogg.xml
- Licence:
- **Geek Speak with Lyle Troxell**
- Website: https://geekspeak.org/
- Feed: https://geekspeak.org/episodes/rss.xml
- Licence: Creative Commons Attribution 3.0 United States License
- **GeekNights Mondays: Science Technology Computing**
- Website: http://frontrowcrew.com/geeknights/monday/
- Feed: http://feeds.feedburner.com/GNSciTech
- Licence: Creative Commons
- **Going Linux**
- Website: https://goinglinux.com
- Feed: http://goinglinux.com/oggpodcast.xml
- Licence: Creative Commons Attribution 4.0 International License.
- **Hacker Public Radio**
- Website: http://hackerpublicradio.org/about.php
- Feed: http://hackerpublicradio.org/hpr_ogg_rss.php
- Licence: Creative Commons Attribution-ShareAlike 3.0 License
- **Knightwise.com Audio Feed.**
- Website: https://knightwise.com
- Feed: http://feeds.feedburner.com/knightcastpodcast
- Licence: Creative commons apply ! Non commercial re-use is allowed.
- **Late Night Linux (Ogg)**
- Website: https://latenightlinux.com
- Feed: http://latenightlinux.com/feed/ogg
- Licence:
- **Libre Lounge**
- Website: https://librelounge.org
- Feed: https://librelounge.org/rss-feed.rss
- Licence: Licensed under CC BY-SA 4.0 International
- **Linux For The Rest Of Us - Podnutz**
- Website: http://podnutz.com
- Feed: http://feeds.feedburner.com/linuxfortherestofus
- Licence: Copyright 2017 Podnutz.com
- **Linux Lads**
- Website: https://linuxlads.com/feed_ogg.rss
- Feed: https://linuxlads.com/feed_ogg.rss
- Licence: Released under the Creative Commons Attribution-Share Alike 3.0 Unported Licence
- **Linux Trivia Podcast**
- Website: http://setbit.org/lt.html
- Feed: http://setbit.org/lt-ogg.xml
- Licence: Creative Commons License Some Rights Reserved
- **Linux in the Ham Shack (OGG Feed)**
- Website: https://lhspodcast.info/category/podcast-ogg/
- Feed: https://lhspodcast.info/category/podcast-ogg/feed/
- Licence: Attribution-NonCommercial-NoDerivatives 4.0 International
- **LinuxGameCast**
- Website: https://linuxgamecast.com
- Feed: https://linuxgamecast.com/feed/
- Licence:
- **Makers Corner, with Nate and Yannick**
- Website: https://makerscorner.tech
- Feed: https://makerscorner.tech/feed/podcast/
- Licence: Unless otherwise stated, this podcast is released under a Creative Commons, By Attribution, Share Alike license.
- **Motherload**
- Website: http://downloads.cavalcadeaudio.com/stardrifter-novels/01-motherload/
- Feed: http://downloads.cavalcadeaudio.com/stardrifter-novels/01-motherload/feed.xml
- Licence: Motherload (Stardrifter Book 01) written and read by David Collins-Rivera © 2012 David Collins-Rivera Creative Commons Attribution Share-Alike Unported 3.0 https://creativecommons.org/licenses/by-sa/3.0/
- **OGG mintCast**
- Website: https://mintcast.org
- Feed: https://mintcast.org/category/ogg/feed/
- Licence:
- **Open Metalcast**
- Website: http://openmetalcast.com
- Feed: http://feeds.feedburner.com/OpenMetalcast
- Licence: (c) 2010-2018 Craig Maloney. Some Rights Reserved.
- **PC Podcast. A Music Podcast with Pete Cogle, since January 2006**
- Website: http://petecogle.co.uk/blog/category/pc-podcast/
- Feed: http://feeds.feedburner.com/pcpodcast
- Licence: PCP
- **PodCastle**
- Website: http://podcastle.org
- Feed: http://podcastle.org/feed/
- Licence:
- **Podcast Cory Doctorow's craphound.com**
- Website: https://craphound.com
- Feed: http://feeds.feedburner.com/doctorow_podcast
- Licence: Creative Commons by-nc-sa http://creativecommons.org/licenses/by-nc-sa/2.5/
- **PseudoPod**
- Website: http://pseudopod.org
- Feed: http://pseudopod.org/feed/
- Licence:
- **RadioTux**
- Website: https://www.radiotux.de/
- Feed: http://radiotux.de/podcast/rss/radiotux-all.xml
- Licence: © Creative Commons - BY-SA 3.0 Unported
- **RatholeRadio.org (Ogg Version)**
- Website: http://ratholeradio.org
- Feed: http://feeds.feedburner.com/RatholeRadio-ogg
- Licence: Creative Commons BY-SA Licensed
- **Security Now (MP3)**
- Website: https://twit.tv/shows/security-now
- Feed: http://feeds.twit.tv/sn.xml
- Licence: This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/
- **Skepticule**
- Website: http://www.skepticule.co.uk/
- Feed: http://www.skepticule.co.uk/feeds/posts/default?alt=rss
- Licence: Creative Commons Attribution-NonCommercial-No Derivative Works 2.0 UK: England & Wales
- **Sunday Morning Linux Review - OGG Feed**
- Website: https://smlr.us
- Feed: http://feeds.feedburner.com/SundayMorningLinuxReview-OggFeed
- Licence: This content is published under the Attribution-Noncommercial-Share Alike 3.0 Unported license.
- **Tea, Earl Grey, Hot !**
- Website: https://teaearlgreyhot.org
- Feed: https://teaearlgreyhot.org/feed/podcast
- Licence: Unless otherwise stated, this podcast is released under a Creative Commons, By Attribution, Share Alike license.
- **The Binary Times Audiocast - ogg**
- Website: https://www.thebinarytimes.net
- Feed: https://www.thebinarytimes.net/rss-ogg.xml
- Licence: Unless otherwise stated, this podcast is released under a Creative Commons, By Attribution, Share Alike license.
- **The Bugcast - Ogg Feed**
- Website: https://thebugcast.org
- Feed: http://thebugcast.org/feed/ogg/
- Licence:
- **The Command Line Podcast**
- Website: http://thecommandline.net/
- Feed: https://thecommandline.net/files/cmdln_mp3.xml
- Licence: 2005-2014, http://creativecommons.org/licenses/by-sa/3.0/us
- **The Dub Zone**
- Website: http://petecogle.co.uk/blog
- Feed: http://feeds.feedburner.com/TheDubZone
- Licence: Creative Commons
- **The Duffercast in Ogg Vorbis**
- Website: https://duffercast.org
- Feed: http://duffercast.org/feed/podcast/
- Licence:
- **The Full Circle Weekly News**
- Website: https://fullcirclemagazine.org/
- Feed: https://fullcirclemagazine.org/feed/podcast
- Licence: © 2016 Full Circle Magazine
- **The JaK Attack! Podcast**
- Website: http://thejakattack.com/
- Feed: http://feeds.feedburner.com/TheJakAttack
- Licence: © 2018 The JaK Attack! Podcast
- **The Linux Link Tech Show Ogg-Vorbis Feed**
- Website: http://www.tllts.org
- Feed: http://feeds.feedburner.com/TheLinuxLinkTechShowOgg-vorbisFeed
- Licence: TLLTS is licensed under Creative Commons License for Non-Commercial Use
- **This Week in Computer Hardware (MP3)**
- Website: https://twit.tv/shows/this-week-in-computer-hardware
- Feed: http://feeds.twit.tv/twich.xml
- Licence: This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/
- **This Week in Google (MP3)**
- Website: https://twit.tv/shows/this-week-in-google
- Feed: http://feeds.twit.tv/twig.xml
- Licence: This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/
- **This Week in Microbiology**
- Website: http://www.asm.org/twim/
- Feed: http://feeds.feedburner.com/twim
- Licence: Creative Commons Attribution - Noncommercial
- **This Week in Tech (MP3)**
- Website: https://twit.tv/shows/this-week-in-tech
- Feed: http://feeds.twit.tv/twit.xml
- Licence: This work is licensed under a Creative Commons License - Attribution-NonCommercial-NoDerivatives 4.0 International - http://creativecommons.org/licenses/by-nc-nd/4.0/
- **TuxJam OGG**
- Website: https://tuxjam.otherside.network
- Feed: https://tuxjam.otherside.network/?feed=podcast
- Licence: Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
- **Ubuntu Podcast**
- Website: http://ubuntupodcast.org
- Feed: http://ubuntupodcast.org/feed/
- Licence:
- **urandom podcast**
- Website: https://urandom-podcast.info/
- Feed: http://feeds.feedburner.com/urandom-podcast/ogg
- Licence:

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.1">
<head>
<title>Free Culture Podcasts</title>
<dateCreated>2020-02-14 22:34:08</dateCreated>
<dateModified>2020-02-14 22:34:08</dateModified>
<ownerName></ownerName>
<ownerEmail></ownerEmail>
<expansionState></expansionState>
<vertScrollState></vertScrollState>
<windowTop></windowTop>
<windowLeft></windowLeft>
<windowBottom></windowBottom>
<windowRight></windowRight>
</head>
<body>
<outline description="A Podcast about servers and Networking" htmlUrl="https://www.adminadminpodcast.co.uk" text="Admin Admin Podcast" title="Admin Admin Podcast" xmlUrl="http://feeds.feedburner.com/TheAdminAdminPodcast" />
<outline description="All About Android delivers everything you want to know about Android each week--the biggest news, freshest hardware, best apps and geekiest how-tos--with Android enthusiasts Jason Howell, Florence Ion, Ron Richards, and a variety of special guests along the way. Records live every Tuesday at 8:00pm Eastern / 5:00pm Pacific / 01:00 (Wed) UTC." htmlUrl="https://twit.tv/shows/all-about-android" text="All About Android (MP3)" title="All About Android (MP3)" xmlUrl="http://feeds.twit.tv/aaa.xml" />
<outline description="Join us as we talk about everything related to Information Technology, and some other random stuff as well." htmlUrl="http://aiit.se/radio/" text="All In IT Radio (ogg)" title="All In IT Radio (ogg)" xmlUrl="http://feeds.aiit.se/allinit-radio-ogg" />
<outline description="A monthly podcast covering Ham Radio equipment, events and personalities." htmlUrl="http://www.amateurlogic.tv" text="AmateurLogic.TV" title="AmateurLogic.TV" xmlUrl="http://amateurlogic.tv/transmitter/feeds/ipod.xml" />
<outline description="Heb je al gehoord over die laatste grote hack? Een panel van kritische deskundigen bespreken privacy- en security gerelateerde onderwerpen uit het nieuws in gewone mensentaal." htmlUrl="https://leukemensen.nl/angrynerds/" text="Angry Nerds" title="Angry Nerds" xmlUrl="https://leukemensen.nl/angrynerds/feed.xml" />
<outline description="CCHits.net is designed to provide a Chart for Creative Commons Music, in a way that is easily able to be integrated into other music shows that play Creative Commons Music. CCHits.net has a daily exposure podcast, playing one new track every day, a weekly podcast, playing the last week of tracks played on the podcast, plus the top rated three tracks from the previous week. There is also a monthly podcast which features the top rated tracks over the whole system." htmlUrl="https://cchits.net/daily" text="CCHits.net" title="CCHits.net" xmlUrl="https://cchits.net/daily/rss" />
<outline description="Community music podcast turn up the volume!!!" htmlUrl="https://ccjam.otherside.network" text="CCJam" title="CCJam" xmlUrl="https://ccjam.otherside.network/feed/podcast/" />
<outline description="The Young Adult Speculative Fiction Podcast" htmlUrl="http://www.castofwonders.org" text="Cast of Wonders" title="Cast of Wonders" xmlUrl="http://www.castofwonders.org/feed/podcast/" />
<outline description="Weekly live digital TV show with Robbie Ferguson focused on topics of interest to tech minds. Ask your questions and get live answers. Recipient of 2014 and 2017 Top 100 Tech Podcasters award." htmlUrl="https://category5.tv/" text="Category5 Technology TV (MP3 Audio)" title="Category5 Technology TV (MP3 Audio)" xmlUrl="https://rss.cat5.tv/audio.rss" />
<outline description="A podcast made by people who love running Linux." htmlUrl="https://destinationlinux.org/blog/" text="Destination Linux" title="Destination Linux" xmlUrl="http://destinationlinux.org/feed/mp3/" />
<outline description="We are two Blokes who love Linux and trying out new stuff, we thought it would be interesting to share our experience of trying new Linux and BSD distributions and how we found it trying to live with them as our daily driver for up to a Month at a time, by recording a podcast about how we got on." htmlUrl="https://distrohoppersdigest.blogspot.com/" text="Distrohoppers' Digest" title="Distrohoppers' Digest" xmlUrl="http://feeds.feedburner.com/blogspot/jejQf" />
<outline description="Home of the Science Fiction Audio Drama series" htmlUrl="https://edictzero.wordpress.com" text="Edict Zero FIS" title="Edict Zero FIS" xmlUrl="https://edictzero.wordpress.com/feed/" />
<outline description="En podcast om Wikipedia på svenska" htmlUrl="http://wikipediapodden.se" text="English Wikipediapodden" title="English Wikipediapodden" xmlUrl="http://wikipediapodden.se/tag/english/feed/" />
<outline description="The Original Science Fiction Podcast" htmlUrl="http://escapepod.org" text="Escape Pod" title="Escape Pod" xmlUrl="http://escapepod.org/feed/" />
<outline description="We're not talking dentistry here; FLOSS all about Free Libre Open Source Software. Join host Randal Schwartz and his rotating panel of co-hosts every Wednesday as they talk with the most interesting and important people in the Open Source and Free Software community. Records live every Wednesday at 12:30pm Eastern / 9:30am Pacific / 17:30 UTC." htmlUrl="https://twit.tv/shows/floss-weekly" text="FLOSS Weekly (MP3)" title="FLOSS Weekly (MP3)" xmlUrl="http://feeds.twit.tv/floss.xml" />
<outline description="A bi-weekly discussion of legal, policy, and other issues in the open source and software freedom community (including occasional interviews) from Brooklyn, New York, USA. Presented by Karen Sandler and Bradley M. Kuhn." htmlUrl="http://faif.us/cast/" text="Free as in Freedom" title="Free as in Freedom" xmlUrl="http://faif.us/feeds/cast-ogg/" />
<outline description="GNU, Linux, coffee, and subversion." htmlUrl="http://www.gnuworldorder.info" text="GNU World Order Linux Cast" title="GNU World Order Linux Cast" xmlUrl="https://gnuworldorder.info/ogg.xml" />
<outline description="A weekly talk show about technology, science, and human creativity that excites, educates, and fosters curiosity. Discussions touch upon how technology affects society and how we react to that change. Hosts are passionate about explaining complex concepts in simple, easy to digest, chunks. We bridge the gaps between Geeks and the rest of humanity." htmlUrl="https://geekspeak.org/" text="Geek Speak with Lyle Troxell" title="Geek Speak with Lyle Troxell" xmlUrl="https://geekspeak.org/episodes/rss.xml" />
<outline description="GeekNights Mondays is the weekly sci/tech segment of GeekNights, featuring science, technology, computing, and more. We talk Linux, Windows, gadgets, you name it." htmlUrl="http://frontrowcrew.com/geeknights/monday/" text="GeekNights Mondays: Science Technology Computing" title="GeekNights Mondays: Science Technology Computing" xmlUrl="http://feeds.feedburner.com/GNSciTech" />
<outline description="Once you become aware that there is a dependable, secure, capable, and modern computer system that rivals all others in popularity and actual use, you will want to try the Linux operating system on your computer. Perhaps you've been using a member of the Unix/Linux family - Linux, Android, ChromeOS, BSD or even OSX - for quite a while. If so, you are likely looking for new ways to optimize your technology for the way you work. Going Linux is for computer users who just want to use Linux to get things done. Are you new to Linux, upgrading from Windows to Linux, or just thinking about moving to Linux? This audio podcast provides you with practical, day-to-day advice on how to use Linux and its applications. Our goal is to help make the Linux experience easy for you." htmlUrl="https://goinglinux.com" text="Going Linux" title="Going Linux" xmlUrl="http://goinglinux.com/oggpodcast.xml" />
<outline description="Hacker Public Radio is an podcast that releases shows every weekday Monday through Friday. Our shows are produced by the community (you) and can be on any topic that are of interest to hackers and hobbyists." htmlUrl="http://hackerpublicradio.org/about.php" text="Hacker Public Radio" title="Hacker Public Radio" xmlUrl="http://hackerpublicradio.org/hpr_ogg_rss.php" />
<outline description="The cross platform podcast that makes technology work for you and not the other way around. The place to go for all geeks who slide between Mac, iOS, Android, Linux and Windows offering an essential mix of hacks, tips, howto's and tweaks spiced up with a dash of geek culture. Also check out our Mediafeed that has both our audio and video episodes." htmlUrl="https://knightwise.com" text="Knightwise.com Audio Feed." title="Knightwise.com Audio Feed." xmlUrl="http://feeds.feedburner.com/knightcastpodcast" />
<outline description="" htmlUrl="https://latenightlinux.com" text="Late Night Linux (Ogg)" title="Late Night Linux (Ogg)" xmlUrl="http://latenightlinux.com/feed/ogg" />
<outline description="A casual podcast about user freedom" htmlUrl="https://librelounge.org" text="Libre Lounge" title="Libre Lounge" xmlUrl="https://librelounge.org/rss-feed.rss" />
<outline description="Linux For The Rest Of Us - Podnutz" htmlUrl="http://podnutz.com" text="Linux For The Rest Of Us - Podnutz" title="Linux For The Rest Of Us - Podnutz" xmlUrl="http://feeds.feedburner.com/linuxfortherestofus" />
<outline description="Chat about Linux and all things connected" htmlUrl="https://linuxlads.com/feed_ogg.rss" text="Linux Lads" title="Linux Lads" xmlUrl="https://linuxlads.com/feed_ogg.rss" />
<outline description="Verbal's Linux Trivia Podcast" htmlUrl="http://setbit.org/lt.html" text="Linux Trivia Podcast" title="Linux Trivia Podcast" xmlUrl="http://setbit.org/lt-ogg.xml" />
<outline description="Linux in the Ham Shack Podcast in OGG Format" htmlUrl="https://lhspodcast.info/category/podcast-ogg/" text="Linux in the Ham Shack (OGG Feed)" title="Linux in the Ham Shack (OGG Feed)" xmlUrl="https://lhspodcast.info/category/podcast-ogg/feed/" />
<outline description="Linux fueled mayhem &amp; madness with a side of news, reviews, and whatever the Hell-Elks™ we come up with" htmlUrl="https://linuxgamecast.com" text="LinuxGameCast" title="LinuxGameCast" xmlUrl="https://linuxgamecast.com/feed/" />
<outline description="Linuxlugcast" htmlUrl="http://linuxlugcast.com" text="Linuxlugcast" title="Linuxlugcast" xmlUrl="http://feeds.feedburner.com/linuxlugcast-ogg" />
<outline description="A tech oriented DIY podcast, from the Other Side Podcast Network" htmlUrl="https://makerscorner.tech" text="Makers Corner, with Nate and Yannick" title="Makers Corner, with Nate and Yannick" xmlUrl="https://makerscorner.tech/feed/podcast/" />
<outline description="A remote corner of a bleak system. A broken-down gunboat, stuck in space. An incompetent captain and a misfit crew. A pirate ship, a silent target, and a whole bunch of secrets. So how's YOUR day going?" htmlUrl="http://downloads.cavalcadeaudio.com/stardrifter-novels/01-motherload/" text="Motherload" title="Motherload" xmlUrl="http://downloads.cavalcadeaudio.com/stardrifter-novels/01-motherload/feed.xml" />
<outline description="Just another WordPress site" htmlUrl="https://mintcast.org" text="OGG mintCast" title="OGG mintCast" xmlUrl="https://mintcast.org/category/ogg/feed/" />
<outline description="Music that will rip your face off and give a copy to your friends" htmlUrl="http://openmetalcast.com" text="Open Metalcast" title="Open Metalcast" xmlUrl="http://feeds.feedburner.com/OpenMetalcast" />
<outline description="A security podcast geared towards those looking to better understand security topics of the day. Hosted by Kurt Seifried and Josh Bressers covering a wide range of topics including IoT, application security, operational security, cloud, devops, and security news of the day. There is a special open source twist to the discussion often giving a unique perspective on any given topic." htmlUrl="http://opensourcesecuritypodcast.com" text="Open Source Security Podcast" title="Open Source Security Podcast" xmlUrl="https://opensourcesecuritypodcast.libsyn.com/rss" />
<outline description="Guaranteed to widen your musical perspectives, Pete Cogle plays tracks from all over the world in all possible genres. Like it, or hate it, it's a change from the same old shit on clearchannel radio." htmlUrl="http://petecogle.co.uk/blog/category/pc-podcast/" text="PC Podcast. A Music Podcast with Pete Cogle, since January 2006" title="PC Podcast. A Music Podcast with Pete Cogle, since January 2006" xmlUrl="http://feeds.feedburner.com/pcpodcast" />
<outline description="The Fantasy Fiction Podcast" htmlUrl="http://podcastle.org" text="PodCastle" title="PodCastle" xmlUrl="http://podcastle.org/feed/" />
<outline description="Cory Doctorow's Literary Works" htmlUrl="https://craphound.com" text="Podcast Cory Doctorow's craphound.com" title="Podcast Cory Doctorow's craphound.com" xmlUrl="http://feeds.feedburner.com/doctorow_podcast" />
<outline description="The Sound of Horror" htmlUrl="http://pseudopod.org" text="PseudoPod" title="PseudoPod" xmlUrl="http://pseudopod.org/feed/" />
<outline description="Linux, Open Source und Netzkultur" htmlUrl="https://www.radiotux.de/" text="RadioTux" title="RadioTux" xmlUrl="http://radiotux.de/podcast/rss/radiotux-all.xml" />
<outline description="Music, Waffling, Live Performances &amp; Laughs" htmlUrl="http://ratholeradio.org" text="RatholeRadio.org (Ogg Version)" title="RatholeRadio.org (Ogg Version)" xmlUrl="http://feeds.feedburner.com/RatholeRadio-ogg" />
<outline description="Steve Gibson, the man who coined the term spyware and created the first anti-spyware program, creator of Spinrite and ShieldsUP, discusses the hot topics in security today with Leo Laporte. Records live every Tuesday at 4:30pm Eastern / 1:30pm Pacific / 21:30 UTC." htmlUrl="https://twit.tv/shows/security-now" text="Security Now (MP3)" title="Security Now (MP3)" xmlUrl="http://feeds.twit.tv/sn.xml" />
<outline description="Spanking the bottom of ignorance since 2009" htmlUrl="http://www.skepticule.co.uk/" text="Skepticule" title="Skepticule" xmlUrl="http://www.skepticule.co.uk/feeds/posts/default?alt=rss" />
<outline description="Sunday Morning Linux Review OGG Feed for Freedom Lovers!" htmlUrl="https://smlr.us" text="Sunday Morning Linux Review - OGG Feed" title="Sunday Morning Linux Review - OGG Feed" xmlUrl="http://feeds.feedburner.com/SundayMorningLinuxReview-OggFeed" />
<outline description="An unofficial Star Trek fan podcast from the Other Side Podcast Network" htmlUrl="https://teaearlgreyhot.org" text="Tea, Earl Grey, Hot !" title="Tea, Earl Grey, Hot !" xmlUrl="https://teaearlgreyhot.org/feed/podcast" />
<outline description="Linux and open source tips, tricks and discussion. Free software, hardware and modern culture. The Binary Times Audiocast is created by Mark and Wayne, two chaps who just like using linux and open source software and want to spread the word. Linux is free and open source and it is an excellent choice of operating system for our ever changing times. This audiocast is released fortnightly." htmlUrl="https://www.thebinarytimes.net" text="The Binary Times Audiocast - ogg" title="The Binary Times Audiocast - ogg" xmlUrl="https://www.thebinarytimes.net/rss-ogg.xml" />
<outline description="Award-winning music and chat from South Yorkshire in the UK" htmlUrl="https://thebugcast.org" text="The Bugcast - Ogg Feed" title="The Bugcast - Ogg Feed" xmlUrl="http://thebugcast.org/feed/ogg/" />
<outline description="A podcast by a self-described hacker, curmudgeon and hacktivist about the practice and profession of programming drawing on over a decade of professional experience and a lifetime spent hacking, the intersection of politics and society with technology and anything else clever, elegant or funny that catches my mind as a die hard technology geek." htmlUrl="http://thecommandline.net/" text="The Command Line Podcast" title="The Command Line Podcast" xmlUrl="https://thecommandline.net/files/cmdln_mp3.xml" />
<outline description="An 8-track mix of the best dub reggae, every month. Curated by Pete Cogle, since May 2007." htmlUrl="http://petecogle.co.uk/blog" text="The Dub Zone" title="The Dub Zone" xmlUrl="http://feeds.feedburner.com/TheDubZone" />
<outline description="Some auld duffers providing world wide wisdom" htmlUrl="https://duffercast.org" text="The Duffercast in Ogg Vorbis" title="The Duffercast in Ogg Vorbis" xmlUrl="http://duffercast.org/feed/podcast/" />
<outline description="From the independent magazine for the Ubuntu Linux community. The Full Circle Weekly News is a short podcast with just the news. No chit-chat. No time wasting. Just the latest FOSS/Linux/Ubuntu news." htmlUrl="https://fullcirclemagazine.org/" text="The Full Circle Weekly News" title="The Full Circle Weekly News" xmlUrl="https://fullcirclemagazine.org/feed/podcast" />
<outline description="Where Chic meets geek! The JaK Attack! is hoted by Jon Watson and Kelly Penguin Girl. We talk about tech, art, and we do it just a little too loud." htmlUrl="http://thejakattack.com/" text="The JaK Attack! Podcast" title="The JaK Attack! Podcast" xmlUrl="http://feeds.feedburner.com/TheJakAttack" />
<outline description="Monthly astronomy news, interviews and questions. Created by astronomers." htmlUrl="http://www.jodcast.net/" text="The Jodcast (high bandwidth)" title="The Jodcast (high bandwidth)" xmlUrl="http://www.jodcast.net/rss-high.xml" />
<outline description="The Linux Link Tech Show" htmlUrl="http://www.tllts.org" text="The Linux Link Tech Show Ogg-Vorbis Feed" title="The Linux Link Tech Show Ogg-Vorbis Feed" xmlUrl="http://feeds.feedburner.com/TheLinuxLinkTechShowOgg-vorbisFeed" />
<outline description="If you obsess about the details inside computers then on This Week in Computer Hardware, you'll find out the latest in motherboards, CPUs, GPUs, storage, RAM, power supplies, input devices, and monitors. Hosts Patrick Norton of TekThing and Sebastian Peak of PC Perspective bring you the newest hardware, talk benchmarks, and even dive into the not-yet-released products on the horizon. Records live every Thursday at 3:30pm Eastern / 12:30pm Pacific / 20:30 UTC." htmlUrl="https://twit.tv/shows/this-week-in-computer-hardware" text="This Week in Computer Hardware (MP3)" title="This Week in Computer Hardware (MP3)" xmlUrl="http://feeds.twit.tv/twich.xml" />
<outline description="Leo Laporte, Jeff Jarvis, Stacey Higginbotham, Ant Pruitt, and their guests talk about the latest Google and cloud computing news. Records live every Wednesday at 4:00pm Eastern / 1:00pm Pacific / 21:00 UTC." htmlUrl="https://twit.tv/shows/this-week-in-google" text="This Week in Google (MP3)" title="This Week in Google (MP3)" xmlUrl="http://feeds.twit.tv/twig.xml" />
<outline description="This Week in Microbiology is a podcast about unseen life on Earth hosted by Vincent Racaniello and friends. Following in the path of his successful shows 'This Week in Virology' (TWiV) and 'This Week in Parasitism' (TWiP), Racaniello and guests produce an informal yet informative conversation about microbes which is accessible to everyone, no matter what their science background." htmlUrl="http://www.asm.org/twim/" text="This Week in Microbiology" title="This Week in Microbiology" xmlUrl="http://feeds.feedburner.com/twim" />
<outline description="Your first podcast of the week is the last word in tech. Join the top tech pundits in a roundtable discussion of the latest trends in high tech. Records live every Sunday at 5:15pm Eastern / 2:15pm Pacific / 22:15 UTC." htmlUrl="https://twit.tv/shows/this-week-in-tech" text="This Week in Tech (MP3)" title="This Week in Tech (MP3)" xmlUrl="http://feeds.twit.tv/twit.xml" />
<outline description="TuxJam is a family friendly show that blends Creative Commons music and Open Source goodness. This is an OGG feed." htmlUrl="https://tuxjam.otherside.network" text="TuxJam OGG" title="TuxJam OGG" xmlUrl="https://tuxjam.otherside.network/?feed=podcast" />
<outline description="Upbeat and family-friendly show including news, discussion, interviews and reviews from the Ubuntu, Linux and Open Source world." htmlUrl="http://ubuntupodcast.org" text="Ubuntu Podcast" title="Ubuntu Podcast" xmlUrl="http://ubuntupodcast.org/feed/" />
<outline description="En podcast om Wikipedia på svenska" htmlUrl="http://wikipediapodden.se/prenumerera/" text="Wikipediapodden" title="Wikipediapodden" xmlUrl="http://wikipediapodden.se/feed/podcast/" />
<outline description="urandom: your unlimited source of medium quality randomness" htmlUrl="https://urandom-podcast.info/" text="urandom podcast" title="urandom podcast" xmlUrl="http://feeds.feedburner.com/urandom-podcast/ogg" />
</body>
</opml>

Binary file not shown.

View File

@ -0,0 +1 @@
feedWatcher_2.tpl

View File

@ -0,0 +1,50 @@
[%# feedWatcher_1.tpl 2020-01-23 -%]
[%# Generates very primitive standalone HTML %]
[% USE dumper(indent=0, pad="<br/>") %]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<meta name="author" content="Dave Morriss">
<title>Free Culture Podcasts</title>
<style type="text/css">code{white-space: pre;}</style>
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="stylesheet" href="http://hackerpublicradio.org/css/hpr.css">
</head>
<body id="home">
<div id="container" class="shadow">
[% IF feeds.size > 0 -%]
[% i = 0 -%]
<ul>
[% WHILE i < feeds.size -%]
[% IF feeds.$i.urls_title.length > 0 -%]
<li><a href="[% feeds.$i.urls_url %]">[% feeds.$i.urls_title %]</a><br/>
[% ELSE -%]
<li><a href="[% feeds.$i.urls_url %]">[% feeds.$i.urls_url %]</a><br/>
[% END -%]
<em>Description:</em> [% feeds.$i.urls_description %]<br/>
[% IF feeds.$i.urls_image.defined -%]
<em>Logo:</em> <img src="[% feeds.$i.urls_image %]" height="100" width="100" alt="Logo"><br/>
[% END -%]
<em>Copyright:</em> [% feeds.$i.urls_copyright %]<br/>
<em>Latest episode:</em> [% feeds.$i.latest_ep %]<br/>
<hr>
</li>
[% i = i + 1 -%]
[% END -%]
</ul>
[% END -%]
</div>
</body>
</html>
[%#
# vim: syntax=tt2:ts=8:sw=4:ai:et:tw=78:fo=tcrqn21
-%]

View File

@ -0,0 +1,226 @@
[% TAGS html %]
<!--# feedWatcher_2.tpl 2020-01-26 -->
<!--# Web page design courtesy of Roan Horning -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
/* prefixed by https://autoprefixer.github.io (PostCSS: v7.0.26, autoprefixer: v9.7.3) */
html {
height: 100%;
background-color: black;
font-family: "FreeSans", sans-serif;
}
body {
background-color: white;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
-ms-flex-line-pack: center;
align-content: center;
padding: 0;
height: 100%;
max-width: 50%;
margin: 0 auto 0 auto;
}
body > * {
padding: 1em 1em;
}
main {
background-color: #efbe0032;
background-color: hsla(47.7,100%,46.9%, 0.2);
-webkit-box-flex: 1;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
overflow: auto;
}
main * {
opacity: 100%;
}
header, footer {
-webkit-box-flex: 0;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
background-color: #efbe00;
background-color: hsla(47.7,100%,46.9%, 1);
}
footer {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
padding-top: 1em;
padding-bottom: 1em;
}
header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-basis: 50%;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: start;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}
header > * {
margin: 0;
padding: 0;
min-height: 0;
}
header img {
max-height: 128px;
min-height: 128px;
}
header h1 {
padding: 0 0.5em 0 0.15em;
-webkit-box-flex: 2;
-ms-flex-positive: 2;
flex-grow: 2;
color: white;
font-size: 3rem;
text-shadow: -1px 1px 0 #000,
1px 1px 0 #000,
1px -1px 0 #000,
-1px -1px 0 #000;
}
header h1 span {
white-space: nowrap;
}
header aside {
border: solid 0.5em black;
padding: 1em;
background-color: #FCF2CD;
background-color: hsla(47.2, 88.7%, 89.6%, 1);
-ms-flex-negative: 1;
flex-shrink: 1;
}
dt {
font-weight: 600;
margin-top: 0.25em;
}
dd {
margin-top: 0.15em;
font-weight: 400;
}
@media only screen and (max-width: 1270px){
html {
border-left: solid 0.5em black;
border-right: solid 0.5em black;
}
body {
max-width:none;
}
header {
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
header h1 {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 67%;
}
header aside {
margin-top: 0.5em;
}
}
@media only screen and (max-width: 600px){
body > * {
padding: 0.5em;
}
header > * {
padding: 0;
}
header h1 {
padding-right: 0;
font-size: 2rem;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 70%;
}
header img {
max-height: 96px;
min-height: 96px;
}
header aside {
font-size: 0.95rem;
padding: 0.5em;
}
footer img {
max-height: 32px;
}
}
@media only screen and (max-height: 780px) and (orientation: landscape) {
html {
height:auto;
display:initial;
}
}
</style>
</head>
<body>
<header>
<img src="./logo.svg" alt="[logo]">
<h1><span>Free Culture</span> Podcasts</h1>
<aside>
The finest selection of Free Culture Podcasts spanning
the genres of Discussion, Drama, Education, Music, and beyond.
</aside>
</header>
<main>
<!-- IF feeds.size > 0 -->
<!-- i = 0 -->
<dl>
<!-- WHILE i < feeds.size -->
<!-- IF feeds.$i.urls_title.length > 0 -->
<dt><a href="<!-- feeds.$i.urls_link -->"><!-- feeds.$i.urls_title FILTER html_entity --></a> (<a href="<!-- feeds.$i.urls_url -->">feed</a>)</dt>
<!-- ELSE -->
<dt><a href="<!-- feeds.$i.urls_link -->"><em>No title</em></a> (<a href="<!-- feeds.$i.urls_url -->">feed</a>)</dt>
<!-- END -->
<!-- IF feeds.$i.urls_description.length > 0 -->
<dd><!-- feeds.$i.urls_description FILTER html_entity --></dd>
<!-- ELSE -->
<dd><em>No description</em></dd>
<!-- END -->
<!-- i = i + 1 -->
<!-- END -->
</dl>
<!-- END -->
<p>and many <a href="https://gpodder.net/directory/">more</a> ...</p>
</main>
<footer>
<img src="./cc.svg" alt="[Creative Commons]">
<img src="./attribution.svg" alt="[Attribution]">
<img src="./share_alike.svg" alt="[Share Alike]">
<img src="./remix.svg" alt="[Remix]">
</footer>
</body>
</html>
<!--#
# vim: syntax=html:ts=8:sw=4:ai:et:tw=78:fo=tcrqn21
-->

View File

@ -0,0 +1,21 @@
[%# feedWatcher_3.tpl 2020-01-24 -%]
[%# Generates Markdown as a list with a sublist per feed -%]
# Free Culture Podcasts
### The finest selection of Free Culture Podcasts spanning the genres of Discussion, Drama, Education, Music, and beyond.
[% IF feeds.size > 0 -%]
[% i = 0 -%]
[% WHILE i < feeds.size -%]
- **[% feeds.$i.urls_title %]**
- Website: [% feeds.$i.urls_link %]
- Feed: [% feeds.$i.urls_url %]
- Licence: [% feeds.$i.urls_copyright %]
[% i = i + 1 -%]
[% END -%]
[% END -%]
[%#
# vim: syntax=tt2:ts=8:sw=4:ai:et:tw=78:fo=tcrqn21
-%]

View File

@ -0,0 +1,16 @@
[%# feedWatcher_4.tpl 2020-01-25 -%]
[%# Generates simple lines each containing title and description per feed -%]
[% i = 0 -%]
[% WHILE i < feeds.size -%]
[% IF feeds.$i.urls_description.length > 0 -%]
[% desc = feeds.$i.urls_description -%]
[% ELSE -%]
[% desc = "[No description]" -%]
[% END -%]
[% feeds.$i.urls_title %]: [% desc %]
[% i = i + 1 -%]
[% END -%]
[%#
# vim: syntax=tt2:ts=8:sw=4:ai:et:tw=78:fo=tcrqn21
-%]

View File

@ -0,0 +1,149 @@
--
-- feedWatcher_schema.sql - version 0.0.9
--
-- Renamed 'feedWatcher.sql' => `feedWatcher_schema.sql' on 2020-01-18
--
/*
* Table 'urls'
* ------------
*
* The main table containing host, http and feed information
*/
DROP TABLE IF EXISTS episodes;
CREATE TABLE urls (
id integer PRIMARY KEY,
url varchar(1024) NOT NULL,
dns text,
host_up boolean DEFAULT 0,
http_status varchar(80),
content_type varchar(40),
urltype varchar(40),
feedformat varchar(20),
title varchar(1024),
description text,
author varchar(80),
modified timestamp,
link varchar(1024),
image varchar(1024),
copyright varchar(80),
generator varchar(80),
language varchar(40),
last_update timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);
--
-- Ensure the 'url' column is unique
--
DROP INDEX IF EXISTS url_index;
CREATE UNIQUE INDEX url_index ON urls ( url );
--
-- Trigger to update the 'last_update' field every time the row changes
--
DROP TRIGGER IF EXISTS urls_last_updated;
CREATE TRIGGER urls_last_updated AFTER UPDATE ON urls
BEGIN
UPDATE urls SET last_update = datetime('now');
END;
/*
* Table 'episodes'
* ----------------
*
* Has a foreign key relationship with table 'urls'. If the matching row in
* 'urls' is deleted the deletion cascades to this table (ON DELETE CASCADE).
* If the primary key in 'urls' is updated the relationship prevents this
* change (ON UPDATE RESTRICT).
*/
DROP TABLE IF EXISTS episodes;
CREATE TABLE episodes (
id integer PRIMARY KEY,
urls_id integer NOT NULL
REFERENCES urls (id)
ON DELETE CASCADE
ON UPDATE RESTRICT,
link varchar(1024),
enclosure varchar(256),
title varchar(256),
author varchar(256),
category text,
source varchar(128),
ep_id varchar(256),
issued timestamp,
modified timestamp,
byte_length bigint,
mime_type varchar(64),
last_update timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);
--
-- Ensure the 'enclosure' column is unique (per feed)
--
DROP INDEX IF EXISTS enclosure_index;
CREATE UNIQUE INDEX enclosure_index ON episodes ( urls_id, enclosure );
--
-- Trigger to update the 'last_update' field every time the row changes
--
DROP TRIGGER IF EXISTS episodes_last_updated;
CREATE TRIGGER episodes_last_updated AFTER UPDATE ON episodes
BEGIN
UPDATE episodes SET last_update = datetime('now');
END;
--
-- View to simplify joining the two tables.
--
DROP VIEW IF EXISTS all_episodes;
CREATE VIEW all_episodes AS
SELECT
urls.id as urls_id,
urls.url as urls_url,
urls.dns as urls_dns,
urls.host_up as urls_host_up,
urls.http_status as urls_http_status,
urls.content_type as urls_content_type,
urls.urltype as urls_urltype,
urls.feedformat as urls_feedformat,
urls.title as urls_title,
urls.description as urls_description,
urls.author as urls_author,
urls.modified as urls_modified,
urls.link as urls_link,
urls.image as urls_image,
urls.copyright as urls_copyright,
urls.generator as urls_generator,
urls.language as urls_language,
urls.last_update as urls_last_update,
ep.id as ep_id,
ep.urls_id as ep_urls_id,
ep.link as ep_link,
ep.enclosure as ep_enclosure,
ep.title as ep_title,
ep.author as ep_author,
ep.category as ep_category,
ep.source as ep_source,
ep.ep_id as ep_ep_id,
ep.issued as ep_issued,
ep.modified as ep_modified,
ep.byte_length as ep_byte_length,
ep.mime_type as ep_mime_type,
ep.last_update as ep_last_update
FROM urls LEFT JOIN episodes ep ON urls.id = ep.urls_id;
-- vim: syntax=sql:ts=8:ai:tw=78:et:fo=tcrqn21:comments+=b\:--

30
workflow/duration.bash Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/ ]
if [ -f "${1}" ]
then
mediainfo --Output=XML --Full "${1}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | sed 's/0$//g'
exit 0
fi
find ./ -type f | grep -E -v '/sponsor-anhonesthost.com-hpr15.flac|/outro.flac|/intro.flac|/sponsor-archive.org.flac' | while read mediafile
do
if [ "$( file "${mediafile}" | grep -c audio )" == "0" ]
then
continue
fi
duration=$( mediainfo --full --Output=XML "${mediafile}" | xmlstarlet sel -T -t -m "_:MediaInfo/_:media/_:track[@type='Audio']/_:Duration[1]" -v "." -n - | sed 's/0$//g')
if [ "${duration}" != "" ]
then
echo "${mediafile}: ${duration}"
continue
fi
duration=$( /bin/date -ud "1970-01-01 $( ffprobe -i "${mediafile}" 2>&1| awk -F ': |, ' '/Duration:/ { print $2 }' )" +%s )
if [ "${duration}" != 0 ]
then
echo "${mediafile}: ${duration}"
continue
fi
done

641
workflow/fix_tags Executable file
View File

@ -0,0 +1,641 @@
#!/usr/bin/perl
#===============================================================================
#
# FILE: fix_tags
#
# USAGE: ./fix_tags [-help] [-album=ALBUMSTRING] [-artist=ARTISTSTRING]
# [-comment=COMMENTSTRING] [-genre=GENRESTRING]
# [-title=TITLESTRING] [-track=TRACKNUMBER] [-year=YEAR]
# [-filter TAGNAME=FILTERNAME] audio_file ...
#
# DESCRIPTION: A tool for reporting and altering tags in audio files of
# a variety of formats
#
# OPTIONS: ---
# REQUIREMENTS: ---
# BUGS: ---
# NOTES: ---
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
# LICENCE: Copyright (c) year 2011, 2012, 2013, 2014, Dave Morriss
# VERSION: 1.3.4
# CREATED: 2011-12-12 22:00:34
# REVISION: 2014-06-25 15:10:17
#
#===============================================================================
use Modern::Perl '2011';
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;
use File::stat;
use Date::Manip::Delta;
use Date::Manip::TZ;
use Audio::TagLib;
use TryCatch;
use HTML::Restrict;
#
# Version number (manually incremented)
#
our $VERSION = '1.3.4';
#
# Script name
#
( my $PROG = $0 ) =~ s|.*/||mx;
#
# Declarations
#
my ( $sb, $fyear, $ref, $tag, $aprop, $changed );
my ( %tags, @errors );
#
# The Audio::TagLib methods to call for each tag manipulated by the script.
# The number after the method name is 1 if the value being set is a string,
# and zero otherwise.
#
my %tagmethods = (
album => [ 'setAlbum', 1 ],
artist => [ 'setArtist', 1 ],
comment => [ 'setComment', 1 ],
genre => [ 'setGenre', 1 ],
title => [ 'setTitle', 1 ],
track => [ 'setTrack', 0 ],
year => [ 'setYear', 0 ],
);
#
# Internal routines to invoke to perform filtering tasks
#
my %filtermethods = (
clean => \&clean_string,
underscore => \&replace_underscores,
HTML => \&remove_HTML,
);
#
# Ensure STDOUT and STDERR are in UTF8 mode
#
binmode( STDOUT, ":encoding(utf8)" );
binmode( STDERR, ":encoding(utf8)" );
#
# Options and arguments
#
my ( %options, %filter );
Options( \%options, \%filter );
#
# Default help
#
pod2usage( -msg => "Version $VERSION\n", -exitval => 1 )
if ( $options{'help'} );
#
# Collect options
#
my $album = $options{album};
my $artist = $options{artist};
my $comment = $options{comment};
my $genre = $options{genre};
my $title = $options{title};
my $track = $options{track};
my $year = $options{year};
#
# Check the filter options
#
unless ( check_filters( \%filter, \@errors ) ) {
print STDERR join( "\n", @errors ), "\n";
exit(1);
}
my @files = @ARGV;
pod2usage(
-msg => "Missing arguments\n\nVersion $VERSION\n",
-exitval => 1
) unless @files;
foreach my $file (@files) {
unless ( -e $file ) {
warn "$file does not exist\n";
next;
}
#
# Report the file name
#
print "$file\n";
#
# If the file is empty report it and skip it
#
if ( -z $file ) {
warn "File $file is empty\n";
next;
}
$sb = stat($file);
$fyear = ( localtime( $sb->mtime ) )[5] + 1900;
#
# Catch errors if someone tries to use this tool on a file that
# Audio::TagLib doesn't know about
#
try {
$ref = Audio::TagLib::FileRef->new($file);
$tag = $ref->tag();
$aprop = $ref->audioProperties();
%tags = (
title => $tag->title()->toCString(),
artist => $tag->artist()->toCString(),
album => $tag->album()->toCString(),
comment => $tag->comment()->toCString(),
genre => $tag->genre()->toCString(),
year => $tag->year(),
track => $tag->track(),
length => sprintf( "%s (%d sec)",
interval( $aprop->length() ),
$aprop->length() ),
);
}
#
# We choked on something nasty
#
catch {
warn "File $file apparently does not contain tags\n";
next;
}
#
# Report current tags
#
for my $key ( sort( keys(%tags) ) ) {
printf "%-10s: %s\n", $key, $tags{$key};
}
$changed = 0;
#
# Change album, artist name, comment, genre, track number or year if
# requested
#
$changed
+= changeTag( $tag, 'album', $tags{album}, $album, 'setAlbum', 1 );
$changed
+= changeTag( $tag, 'artist', $tags{artist}, $artist, 'setArtist',
1 );
$changed
+= changeTag( $tag, 'comment', $tags{comment}, $comment,
'setComment', 1 );
$changed
+= changeTag( $tag, 'genre', $tags{genre}, $genre, 'setGenre', 1 );
$changed
+= changeTag( $tag, 'title', $tags{title}, $title, 'setTitle', 1 );
$changed
+= changeTag( $tag, 'track', $tags{track}, $track, 'setTrack', 0 );
$changed += changeTag( $tag, 'year', $tags{year}, $year, 'setYear', 0 );
#
# Do some filtering
#
$changed += apply_filters( $tag, \%tags, \%tagmethods, \%filter,
\%filtermethods );
#
# Update if there are changes
#
if ($changed) {
$ref->save();
}
}
continue {
print "\n";
}
exit;
#=== FUNCTION ================================================================
# NAME: changeTag
# PURPOSE: Changes a tag to a new value if appropriate
# PARAMETERS: $tag Tag object
# $tagname Name of tag
# $oldValue Current value of tag
# $newValue New value of tag or undefined
# $setFunc String containing the name of the 'set'
# function
# $isString True if the value being set is a string
# RETURNS: 1 if a change has been made, 0 otherwise
# DESCRIPTION: Checks that $newValue is defined (it can be an empty string)
# and that the new value differs from the old one, returning if
# not. The $isString value defaults to zero. Ensures that a null
# $newValue is replaced by a zero if the tag is numeric. Reports
# what change has been requested then makes the change.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub changeTag {
my ( $tag, $tagname, $oldValue, $newValue, $setFunc, $isString ) = @_;
return 0 unless defined($newValue);
return 0 if $oldValue eq $newValue;
$isString = 0 unless defined($isString);
$newValue = 0 if ( $newValue eq '' && !$isString );
print "Changing $tagname to '$newValue'\n";
$tag->$setFunc(
( $isString
? Audio::TagLib::String->new($newValue)
: $newValue
)
);
return 1;
}
#=== FUNCTION ================================================================
# NAME: check_filters
# PURPOSE: Check that the filter hash contains valid settings
# PARAMETERS: $filter Hashref containing the filter options
# $errors Arrayref to contain error messages
# RETURNS: 1 (true) if all is well, otherwise 0 (false)
# DESCRIPTION: The check returns true if there are no filters. It returns
# false if there are any unknown tag names or any unknown filter
# names.
# THROWS: No exceptions
# COMMENTS: The knowledge of what is a vlaid tag name or filter name is
# within this function, which is not ideal for maintenance.
# SEE ALSO: N/A
#===============================================================================
sub check_filters {
my ( $filters, $errors ) = @_;
my @filterable_tags = (qw{ album artist comment genre title });
my @valid_filters = (qw{clean underscore html});
my @unknown;
#
# Nothing is wrong if there are no filters
#
return 1 unless defined($filters);
#
# Are any tag names unknown?
#
my %filterable = map { $_ => 1 } @filterable_tags;
@unknown = grep { !defined( $filterable{$_} ) }
map { lc($_) } keys( %{$filters} );
if (@unknown) {
push( @{$errors}, "Error: Invalid filter(s)" )
unless defined($errors);
push( @{$errors}, "Unknown tag names: " . join( ", ", @unknown ) );
}
#
# Are any filter names unknown?
#
my %valid = map { $_ => 1 } @valid_filters;
my @names = map { @{ $filters->{$_} } } keys( %{$filters} );
@unknown = grep { !defined( $valid{$_} ) } map { lc($_) } @names;
if (@unknown) {
push( @{$errors}, "Error: Invalid filter(s)" )
unless defined($errors);
push( @{$errors}, "Unknown filter names: " . join( ", ", @unknown ) );
}
#
# All tests passed if no errors
#
return scalar( @{$errors} ) == 0;
}
#=== FUNCTION ================================================================
# NAME: apply_filters
# PURPOSE: Looks for requested filters and the tags they are to be
# applied to and performs the necessary filtering
# PARAMETERS: $tag Tag object
# $tags Hashref containing the converted tags from the
# current file
# $tagmethods Hashref containing the Audio::TagLib method
# names per tag
# $filter Hashref containing filter options
# $filtermethods Hashref containing filter names and the
# routines that handle them
# RETURNS: Number of changes made
# DESCRIPTION: The $filter hash contains tag names as keys (lower- or
# upper-case). The value is an array of filter names (lower- or
# upper-case). We loop through the tag names looking for filter
# names and applying the filters we find.
# THROWS: No exceptions
# COMMENTS: The sorting should be case-insensitive.
# SEE ALSO: N/A
#===============================================================================
sub apply_filters {
my ( $tag, $tags, $tagmethods, $filter, $filtermethods ) = @_;
my $lc_t;
my $newtag;
my $changes = 0;
#
# Loop through the tags we are to filter in sorted order
#
for my $t ( sort( keys( %{$filter} ) ) ) {
#
# We need a lowercase key to access the tag
#
$lc_t = lc($t);
#
# Loop through all available methods and apply them if requested
#
for my $f ( sort( keys( %{$filtermethods} ) ) ) {
if ( grep( /^$f$/i, @{ $filter->{$t} } ) ) {
$newtag = &{ $filtermethods->{$f} }( $tags->{$lc_t} );
$changes += changeTag( $tag, $lc_t, $tags->{$lc_t},
$newtag, @{ $tagmethods->{$lc_t} } );
}
}
}
return $changes;
}
#=== FUNCTION ================================================================
# NAME: interval
# PURPOSE: Convert a 'MM:SS' string into an acceptable PostgreSQL
# interval where the minutes portion may exceed 60.
# PARAMETERS:
# RETURNS: The interval string in the format 'HH:MM:SS'
# DESCRIPTION:
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub interval {
my ($time) = @_;
return undef unless $time;
my $date = new Date::Manip::Delta;
unless ( $date->parse($time) ) {
return $date->printf("%02hv:%02mv:%02sv");
}
else {
warn "Invalid time $time\n";
return undef;
}
}
#=== FUNCTION ================================================================
# NAME: clean_string
# PURPOSE: Clean a string of non-printables, newlines, multiple spaces
# PARAMETERS: $str The string to process
# RETURNS: The processed string
# DESCRIPTION: Removes leading and trailing spaces. Removes all non-printable
# characters. Removes all CR/LF sequences. Replaces multiple
# spaces with a single space.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub clean_string {
my ($str) = @_;
$str =~ s/(^\s+|\s+$)//g;
$str =~ tr/[[:graph:]]//c;
$str =~ tr/\x0A\x0D/ /s;
$str =~ tr/ \t/ /s;
return $str;
}
#=== FUNCTION ================================================================
# NAME: replace_underscores
# PURPOSE: Replaces underscores in a string by spaces
# PARAMETERS: $str The string to process
# RETURNS: The processed string
# DESCRIPTION:
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub replace_underscores {
my ($str) = @_;
$str =~ s/_/ /g;
return $str;
}
#=== FUNCTION ================================================================
# NAME: remove_HTML
# PURPOSE: Clean a string of HTML tags
# PARAMETERS: $str The string to process
# RETURNS: The processed string
# DESCRIPTION:
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub remove_HTML {
my ($str) = @_;
my $hr = HTML::Restrict->new();
my $processed = $hr->process($str);
return $processed;
}
#=== FUNCTION ================================================================
# NAME: Options
# PURPOSE: Processes command-line options
# PARAMETERS: $optref Hash reference to hold the options
# RETURNS: Undef
# DESCRIPTION:
# THROWS: no exceptions
# COMMENTS: none
# SEE ALSO: n/a
#===============================================================================
sub Options {
my ( $optref, $filter ) = @_;
my @options = (
"help", "album:s", "artist:s", "comment:s",
"genre:s", "title:s", "track:s", "year:s",
);
#
# Implement '-filter=TAGNAME=FILTERNAME' (from the Getopt::Long manpage)
#
my %opthash
= ( "filter=s%" => sub { push( @{ $filter->{ $_[1] } }, $_[2] ) }, );
if ( !GetOptions( $optref, @options, %opthash ) ) {
pod2usage( -msg => "Version $VERSION\n", -exitval => 1 );
}
return;
}
__END__
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Application Documentation
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#{{{
=head1 NAME
fix_tags - manipulate ID3 tags
=head1 VERSION
This documentation refers to I<fix_tags> version 1.3.4
=head1 USAGE
fix_tags [ -help ] [-album=ALBUMSTRING] [-artist=ARTISTSTRING]
[-comment=COMMENTSTRING] [-genre=GENRESTRING] [-title=TITLESTRING]
[-track=TRACKNUMBER] [-year=YEAR] [-filter TAGNAME=FILTERNAME] audio_file ...
=head1 OPTIONS
=over 8
=item B<-help>
Prints a brief help message describing the usage of the program, and then exits.
=item B<-album=ALBUMSTRING>
Sets the album tag to the string defined by the option. Use B<-album=> to
clear the tag.
=item B<-artist=ARTISTSTRING>
Sets the artist tag to the string defined by the option. Use B<-artist=> to
clear the tag.
=item B<-comment=COMMENTSTRING>
Sets the comment tag to the string defined by the option. Use B<-comment=> to
clear the tag.
=item B<-genre=GENRESTRING>
Sets the genre tag to the string defined by the option. Use B<-genre=> to
clear the tag.
=item B<-title=TITLESTRING>
Sets the title tag to the string defined by the option. Use B<-title=> to
clear the tag.
=item B<-track=TRACKNUMBER>
Sets the track tag to the number defined by the option. Use B<-track=> to
set the tag to zero.
=item B<-year=YEAR>
Sets the year tag to the number defined by the option. Use B<-year=> to
set the tag to zero.
=item B<-filter TAGNAME=FILTERNAME> or B<-filter=TAGNAME=FILTERNAME>
This option provides an interface to the filtering capability of the script.
Here B<TAGNAME> denotes the full name of one of the text tags (album, artist,
comment, genre or title - not track or year), and B<FILTERNAME> is one of the
built-in filters:
=over 4
=item B<clean>
The tag string has leading and trailing spaces removed. Multiple internal
spaces and tabs are replaced by a single space. CR/LF sequences are removed,
as are all non-graphic characters.
=item B<underscore>
All underscores in the tag string are replaced by spaces. No space compression
takes place.
=item B<HTML>
The tag string is fed through the I<HTML::Restrict> module and all HTML tags
are removed.
=back
Neither the B<TAGNAME> nor the B<FILTERNAME> parts of the option may be
abbreviated, but neither is case-sensitive.
Multiple filters may be specified by repeating the complete option sequence.
For example:
fix_tags -filter comment=clean -fil comment=underscore FILE
The script processes the tags specified in the B<-filter> option in alphabetic
order, and for a given tag it also processes the filters in alphabetic order.
=back
=head1 DESCRIPTION
This script manipulates ID3 tags (or their equivalents) in FLAC, MP3, OGG, SPX
and WAV files.
=head1 DEPENDENCIES
Audio::TagLib
Data::Dumper
Date::Manip::Delta
Date::Manip::TZ
File::stat
Getopt::Long
HTML::Restrict
Pod::Usage
TryCatch
=head1 BUGS AND LIMITATIONS
There are no known bugs in this module.
Please report problems to Dave Morriss (Dave.Morriss@gmail.com)
Patches are welcome.
=head1 AUTHOR
Dave Morriss (Dave.Morriss@gmail.com) 2011, 2012, 2013, 2014
=head1 LICENCE AND COPYRIGHT
Copyright (c) Dave Morriss (Dave.Morriss@gmail.com). All rights reserved.
This program is free software. You can redistribute it and/or modify it under
the same terms as perl itself.
=cut
#}}}
# [zo to open fold, zc to close]
# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker

BIN
workflow/hpr-logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

268
workflow/hprid Executable file
View File

@ -0,0 +1,268 @@
#!/bin/bash
# hprid
################################################################################
#
# script to prepare audio files for HPR shows
#
# input: mp3 or ogg file
# result: mp3, ogg in 44100 Hz, spx files 16000Hz with intro and outro
# provides 3 interactive checks for audio quality, intro and outro
#
################################################################################
# This file is part of the HPR Tool set
#
# HPR Tool set is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# HPR Tool set is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HPR Tool set. If not, see <http://www.gnu.org/licenses/>.
# http://www.gnu.org/licenses/agpl-3.0.html
# #########################################################################
################################################################################
#
# PREREQUISITS
# - current folder has to be writable
# - there should NOT be a temp.ogg or temp.mp3 file
# - intro.mp3 and outro.mp3 have to be present
# - IMPORTANT: sox compiled with mp3 support
# see http://a0u.xanga.com/700438974/howto-soc-installation
#
#
# IMPORTANT
# Backup the files before feeding them to this script, no guarantees here
# Handling of .wav not yet tested, but it should work
#
# code.cruncher, May 2011
#
################################################################################
################################################################################
#
# TODO
#
# test handling of wav files
# add play final files or open them in specific player(s)
# add handling of ID3 tags
# create html interface for standardized info gathering
#
################################################################################
#============================================================
# Check input
usage="usage: $(basename $0 ) [ -i ] [ -o ] <fname>, -i to add intro and -o outro, fname is a file with audio for HPR"
CHANNELS="1"
ADDINTRO="n"
ADDOUTRO="n"
while getopts "io" opt; do
case $opt in
i )
ADDINTRO="y"
;;
o )
ADDOUTRO="y"
;;
esac
done
shift $(($OPTIND - 1))
# if not ${mediafile} return usage
if [ $# -lt 1 ]; then
echo $usage
exit 1
fi
mediafile=${1}
# test if file exists
if [ ! -f "intro.flac" ]; then
echo "sorry, file \"intro.flac\" does not exist"
echo "To download it run the command:"
echo " wget http://hackerpublicradio.org/media/theme-music/intro.flac"
exit 1
fi
if [ ! -f "outro.flac" ]; then
echo "sorry, file \"outro.flac\" does not exist"
echo "To download it run the command:"
echo " wget http://hackerpublicradio.org/media/theme-music/outro.flac"
exit 1
fi
for mediafile in "$@"
do
echo $var
if [ ! -f "${mediafile}" ]; then
echo "sorry, file \"${mediafile}\" does not exist"
continue
fi
# test if file exists
if [ ! -r "${mediafile}" ]; then
echo "sorry, file \"${mediafile}\" is not readable"
continue
fi
if [ $(ffprobe "${mediafile}" 2>&1 | grep "Audio:" | wc -l ) -eq 0 ]; then
echo "sorry, file \"${mediafile}\" has no audio track"
continue
fi
# extract file name and extension
fname=${mediafile%.*}
ext=${mediafile/*./}
#Make a backup
# mediafilebackup=${mediafile}_$(md5sum ${mediafile} | cut -c -32 )_orig.${ext}
# cp -v ${mediafile} ${mediafilebackup}
#
# if [[ ! -e ${mediafilebackup} ]]; then
# echo "Backup not made: ${mediafilebackup}"
# exit
# fi
# check audio quality
dur=7 # playtime of sample in seconds
go=1 # variable to repeat playing of sample
from=180 # start sample at 3 minutes in
#============================================================
# Question Time
# # # while [ $go -ne 0 ]
# # # do
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "1/4 AUDIO TEST: check audio quality: ... playing $dur seconds ..."
# # # play "${mediafile}" trim $from $dur
# # # ((from+=180)) # next sample will be 3 minutes later
# # # read -s -n1 -p "sound quality ok?[y,n] ... or play another sample[a] ... [y,n,a]"
# # # echo
# # # case "$REPLY" in
# # # n) echo "aborting ... get better quality sound file ... good bye!"; exit 0;;
# # # y) go=0;;
# # # esac
# # # done
# # #
# # # # Check for intro
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "2/4 ADDINTRO TEST: Is the intro playing? "
# # # play "${mediafile}" trim 1 5 # play 5 seconds at beginning of file
# # # read -s -n1 -p "Is there a intro? [y, n]" -i "n"; echo
# # # if [ "$REPLY" = 'y' ]; then
# # # echo "Will add the intro"
# # # ADDINTRO="y"
# # # fi
# # #
# # # # Check for outro
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "3/4 ADDOUTRO TEST: Is the outro playing? "
# # # len=$(eval "soxi -D \"${mediafile}\"")
# # # len=$(echo "scale=0; $len - 50" | bc)
# # # play "${mediafile}" trim $len 5
# # # read -s -n1 -p "Is there a outro ? [y, n]" -i "n"; echo
# # # if [ "$REPLY" = 'y' ]; then
# # # echo "Will add the outro"
# # # ADDOUTRO="y"
# # # fi
# # #
# # #
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "4/4 STEREO TEST: Should this be mono or stereo [m,s] ? "
# # # CHANNELS="1"
# # # read -s -n1 -p "intro ok? [m, s]" -i "m" ; echo
# # # if [ "$REPLY" = 's' ]; then
# # # echo "Will convert to stereo"
# # # CHANNELS="2"
# # # fi
#============================================================
# Preprocess the source file
echo "Convert from ${mediafile} to known wav format ${fname}_tmp.wav"
ffmpeg -i ${mediafile} -ar 44100 -ac $CHANNELS ${fname}_tmp.wav > ${fname}_tmp.log 2>&1
# echo "Normalising the audio"
# normalize -a 0.5 ${fname}_tmp.wav >> ${fname}_tmp.log 2>&1
# TODO Compressor !
# TODO add a little speed up
# TODO little overlap in fade in of intro
# echo "Truncating the silence"
# sox ${fname}_tmp.wav ${fname}_sox.wav silence -l 1 0.1 1.6% -1 0.6 1.6% >> ${fname}_tmp.log 2>&1
cp -v ${fname}_tmp.wav ${fname}_sox.wav
echo "Add the intro if it is missing and add it to the temp pcm file"
if [ "$ADDINTRO" = 'y' ]; then
ffmpeg -i intro.flac -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - > ${fname}_tmp.pcm 2>> ${fname}_tmp.log
fi
echo "convert the uploaded episode and add it to the temp pcm file"
ffmpeg -i ${fname}_sox.wav -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp.pcm 2>> ${fname}_tmp.log
echo "Add the outro if it is missing and add it to the temp pcm file"
if [ "$ADDOUTRO" = 'y' ]; then
ffmpeg -i outro.flac -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp.pcm 2>> ${fname}_tmp.log
fi
echo "Convert the pcm file to a know wav format"
ffmpeg -f s16le -ar 44100 -ac 1 -acodec pcm_s16le -i ${fname}_tmp.pcm ${fname}_mez.wav 2>> ${fname}_tmp.log
# echo "Get an image of the converted audio"
# sox ${fname}_mez.wav -n spectrogram -x 800 -y 100 -o ${fname}_mez.png
echo "--------------------------------------------------------------------------------"
echo "File information"
ffprobe ${fname}_mez.wav 2>&1 | grep Audio:
mediainfo ${fname}_mez.wav
echo "--------------------------------------------------------------------------------"
# display ${fname}_mez.png &
# read -s -n1 -p "Spectrogram check: Everything look ok [y,n] ? " -i "y" ; echo
# if [ "$REPLY" = 'n' ]; then
# echo "Something went w rong. Aborting."
# exit
# fi
# echo "--------------------------------------------------------------------------------"
# vlc ${fname}_mez.wav >> ${fname}_tmp.log 2>&1 &
# read -s -n1 -p "VLC check: Everything look ok [y,n] ? " -i "y" ; echo
# if [ "$REPLY" = 'n' ]; then
# echo "Something went wrong. Aborting."
# exit
# fi
echo "Remove temp files"
rm -v ${fname}_tmp.wav ${fname}_sox.wav ${fname}_tmp.pcm ${fname}_tmp.log
echo "Convert to mp3" # TODO and add tags"
sox -S ${fname}_mez.wav ${fname}_mez.mp3
echo "Convert to ogg" # TODO and add tags"
sox -S ${fname}_mez.wav ${fname}_mez.ogg
echo "Convert to spx" # TODO and add tags"
sox -S ${fname}_mez.wav -c 1 -r 16000 -t wav - | speexenc - ${fname}_mez.spx
echo "Changing the file dates to the time of upload"
touch -r ${mediafile} ${fname}*
done

268
workflow/hprid.sh Executable file
View File

@ -0,0 +1,268 @@
#!/bin/bash
# hprid
################################################################################
#
# script to prepare audio files for HPR shows
#
# input: mp3 or ogg file
# result: mp3, ogg in 44100 Hz, spx files 16000Hz with intro and outro
# provides 3 interactive checks for audio quality, intro and outro
#
################################################################################
# This file is part of the HPR Tool set
#
# HPR Tool set is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# HPR Tool set is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HPR Tool set. If not, see <http://www.gnu.org/licenses/>.
# http://www.gnu.org/licenses/agpl-3.0.html
# #########################################################################
################################################################################
#
# PREREQUISITS
# - current folder has to be writable
# - there should NOT be a temp.ogg or temp.mp3 file
# - intro.mp3 and outro.mp3 have to be present
# - IMPORTANT: sox compiled with mp3 support
# see http://a0u.xanga.com/700438974/howto-soc-installation
#
#
# IMPORTANT
# Backup the files before feeding them to this script, no guarantees here
# Handling of .wav not yet tested, but it should work
#
# code.cruncher, May 2011
#
################################################################################
################################################################################
#
# TODO
#
# test handling of wav files
# add play final files or open them in specific player(s)
# add handling of ID3 tags
# create html interface for standardized info gathering
#
################################################################################
#============================================================
# Check input
usage="usage: $(basename $0 ) [ -i ] [ -o ] <fname>, -i to add intro and -o outro, fname is a file with audio for HPR"
CHANNELS="1"
ADDINTRO="n"
ADDOUTRO="n"
while getopts "io" opt; do
case $opt in
i )
ADDINTRO="y"
;;
o )
ADDOUTRO="y"
;;
esac
done
shift $(($OPTIND - 1))
# if not ${mediafile} return usage
if [ $# -lt 1 ]; then
echo $usage
exit 1
fi
mediafile=${1}
# test if file exists
if [ ! -f "intro.flac" ]; then
echo "sorry, file \"intro.flac\" does not exist"
echo "To download it run the command:"
echo " wget http://hackerpublicradio.org/media/theme-music/intro.flac"
exit 1
fi
if [ ! -f "outro.flac" ]; then
echo "sorry, file \"outro.flac\" does not exist"
echo "To download it run the command:"
echo " wget http://hackerpublicradio.org/media/theme-music/outro.flac"
exit 1
fi
for mediafile in "$@"
do
echo $var
if [ ! -f "${mediafile}" ]; then
echo "sorry, file \"${mediafile}\" does not exist"
continue
fi
# test if file exists
if [ ! -r "${mediafile}" ]; then
echo "sorry, file \"${mediafile}\" is not readable"
continue
fi
if [ $(ffprobe "${mediafile}" 2>&1 | grep "Audio:" | wc -l ) -eq 0 ]; then
echo "sorry, file \"${mediafile}\" has no audio track"
continue
fi
# extract file name and extension
fname=${mediafile%.*}
ext=${mediafile/*./}
#Make a backup
# mediafilebackup=${mediafile}_$(md5sum ${mediafile} | cut -c -32 )_orig.${ext}
# cp -v ${mediafile} ${mediafilebackup}
#
# if [[ ! -e ${mediafilebackup} ]]; then
# echo "Backup not made: ${mediafilebackup}"
# exit
# fi
# check audio quality
dur=7 # playtime of sample in seconds
go=1 # variable to repeat playing of sample
from=180 # start sample at 3 minutes in
#============================================================
# Question Time
# # # while [ $go -ne 0 ]
# # # do
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "1/4 AUDIO TEST: check audio quality: ... playing $dur seconds ..."
# # # play "${mediafile}" trim $from $dur
# # # ((from+=180)) # next sample will be 3 minutes later
# # # read -s -n1 -p "sound quality ok?[y,n] ... or play another sample[a] ... [y,n,a]"
# # # echo
# # # case "$REPLY" in
# # # n) echo "aborting ... get better quality sound file ... good bye!"; exit 0;;
# # # y) go=0;;
# # # esac
# # # done
# # #
# # # # Check for intro
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "2/4 ADDINTRO TEST: Is the intro playing? "
# # # play "${mediafile}" trim 1 5 # play 5 seconds at beginning of file
# # # read -s -n1 -p "Is there a intro? [y, n]" -i "n"; echo
# # # if [ "$REPLY" = 'y' ]; then
# # # echo "Will add the intro"
# # # ADDINTRO="y"
# # # fi
# # #
# # # # Check for outro
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "3/4 ADDOUTRO TEST: Is the outro playing? "
# # # len=$(eval "soxi -D \"${mediafile}\"")
# # # len=$(echo "scale=0; $len - 50" | bc)
# # # play "${mediafile}" trim $len 5
# # # read -s -n1 -p "Is there a outro ? [y, n]" -i "n"; echo
# # # if [ "$REPLY" = 'y' ]; then
# # # echo "Will add the outro"
# # # ADDOUTRO="y"
# # # fi
# # #
# # #
# # # echo
# # # echo "--------------------------------------------------------------------------------"
# # # echo "4/4 STEREO TEST: Should this be mono or stereo [m,s] ? "
# # # CHANNELS="1"
# # # read -s -n1 -p "intro ok? [m, s]" -i "m" ; echo
# # # if [ "$REPLY" = 's' ]; then
# # # echo "Will convert to stereo"
# # # CHANNELS="2"
# # # fi
#============================================================
# Preprocess the source file
echo "Convert from ${mediafile} to known wav format ${fname}_tmp.wav"
ffmpeg -i ${mediafile} -ar 44100 -ac $CHANNELS ${fname}_tmp.wav > ${fname}_tmp.log 2>&1
# echo "Normalising the audio"
# normalize -a 0.5 ${fname}_tmp.wav >> ${fname}_tmp.log 2>&1
# TODO Compressor !
# TODO add a little speed up
# TODO little overlap in fade in of intro
# echo "Truncating the silence"
# sox ${fname}_tmp.wav ${fname}_sox.wav silence -l 1 0.1 1.6% -1 0.6 1.6% >> ${fname}_tmp.log 2>&1
cp -v ${fname}_tmp.wav ${fname}_sox.wav
echo "Add the intro if it is missing and add it to the temp pcm file"
if [ "$ADDINTRO" = 'y' ]; then
ffmpeg -i intro.flac -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - > ${fname}_tmp.pcm 2>> ${fname}_tmp.log
fi
echo "convert the uploaded episode and add it to the temp pcm file"
ffmpeg -i ${fname}_sox.wav -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp.pcm 2>> ${fname}_tmp.log
echo "Add the outro if it is missing and add it to the temp pcm file"
if [ "$ADDOUTRO" = 'y' ]; then
ffmpeg -i outro.flac -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp.pcm 2>> ${fname}_tmp.log
fi
echo "Convert the pcm file to a know wav format"
ffmpeg -f s16le -ar 44100 -ac 1 -acodec pcm_s16le -i ${fname}_tmp.pcm ${fname}_mez.wav 2>> ${fname}_tmp.log
# echo "Get an image of the converted audio"
# sox ${fname}_mez.wav -n spectrogram -x 800 -y 100 -o ${fname}_mez.png
echo "--------------------------------------------------------------------------------"
echo "File information"
ffprobe ${fname}_mez.wav 2>&1 | grep Audio:
mediainfo ${fname}_mez.wav
echo "--------------------------------------------------------------------------------"
# display ${fname}_mez.png &
# read -s -n1 -p "Spectrogram check: Everything look ok [y,n] ? " -i "y" ; echo
# if [ "$REPLY" = 'n' ]; then
# echo "Something went w rong. Aborting."
# exit
# fi
# echo "--------------------------------------------------------------------------------"
# vlc ${fname}_mez.wav >> ${fname}_tmp.log 2>&1 &
# read -s -n1 -p "VLC check: Everything look ok [y,n] ? " -i "y" ; echo
# if [ "$REPLY" = 'n' ]; then
# echo "Something went wrong. Aborting."
# exit
# fi
echo "Remove temp files"
rm -v ${fname}_tmp.wav ${fname}_sox.wav ${fname}_tmp.pcm ${fname}_tmp.log
echo "Convert to mp3" # TODO and add tags"
sox -S ${fname}_mez.wav ${fname}_mez.mp3
echo "Convert to ogg" # TODO and add tags"
sox -S ${fname}_mez.wav ${fname}_mez.ogg
echo "Convert to spx" # TODO and add tags"
sox -S ${fname}_mez.wav -c 1 -r 16000 -t wav - | speexenc - ${fname}_mez.spx
echo "Changing the file dates to the time of upload"
touch -r ${mediafile} ${fname}*
done

View File

@ -0,0 +1,422 @@
#!/bin/bash
# Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/
#============================================================
# Check input
usage="usage: $(basename $0 ) [ -i ] [ -o ] <fname>, -i to add intro and -o outro, fname is a file with audio for HPR"
# Argument = -t test -r server -p password -v
echoerr()
{
echo "$@" 1>&2;
}
usage()
{
cat << EOF
usage: $0 [options] {media file to encode} {episode number}
OPTIONS:
-h Show this message
-p add a promo
-i add the intro
-s Automatically generate the summary
-o add the outro
-b add the intro and outro ( and summary default)
-2 encode to 2 channels
-n no audio normalization
-x eXclude sponsor mention
EOF
}
TEMP_DIR="/var/tmp/"
CHANNELS="1"
FIXAUDIO="1"
ADDSUMMARY="n"
ADDINTRO="n"
ADDPROMO="n"
ADDOUTRO="n"
ADDSPONSOR="y"
ARTIST="EMPTY"
TITLE="EMPTY"
YEAR="EMPTY"
SLOT="EMPTY"
basedir="/var/IA"
intro="${basedir}/intro.flac"
promo="${basedir}/promo.flac"
outro="${basedir}/outro.flac"
anhonesthost="${basedir}/sponsor-anhonesthost.com-hpr15.flac"
internetarchive="${basedir}/sponsor-archive.org.flac"
while getopts "hipsob2nx" OPTION
do
case $OPTION in
h)
usage
exit 1
;;
s)
ADDSUMMARY="y"
;;
i)
ADDINTRO="y"
;;
p)
ADDPROMO="y"
;;
o)
ADDOUTRO="y"
;;
b)
ADDINTRO="y"
ADDOUTRO="y"
;;
2)
CHANNELS="2"
;;
n)
FIXAUDIO="0"
;;
x)
ADDSPONSOR="n"
;;
?)
usage
exit
;;
esac
done
shift $(($OPTIND - 1))
if [ "$#" -ne 2 ]; then
echoerr "Please enter the source file and episode number"
exit
fi
mediafile=${1}
ep_num=${2}
ep_num=$(echo $ep_num | sed 's/hpr//g')
re='^[0-9]+$'
if ! [[ $ep_num =~ $re ]] ; then
echoerr "error: episode \"${ep_num}\" is not a number"
exit 1
fi
if [ ! -f "${anhonesthost}" ]; then
echoerr "sorry, file \"${anhonesthost}\" does not exist"
exit 1
fi
if [ ! -f "${internetarchive}" ]; then
echoerr "sorry, file \"${internetarchive}\" does not exist"
exit 1
fi
if [ ! -f "${intro}" ]; then
echoerr "sorry, file \"intro.flac\" does not exist"
exit 1
fi
if [ "$ADDPROMO" = 'y' ]; then
if [ ! -f "${promo}" ]; then
echoerr "sorry, file \"promo.flac\" does not exist"
exit 1
fi
fi
if [ ! -f "${outro}" ]; then
echoerr "sorry, file \"outro.flac\" does not exist"
exit 1
fi
if [ ! -f "${mediafile}" ]; then
echoerr "sorry, media file \"${mediafile}\" does not exist"
exit
fi
if [ ! -r "${mediafile}" ]; then
echoerr "sorry, media file \"${mediafile}\" is not readable"
exit
fi
if [ $(ffprobe "${mediafile}" 2>&1 | grep "Audio:" | wc -l ) -eq 0 ]; then
echoerr "sorry, media file \"${mediafile}\" has no audio track"
exit
fi
# extract file name and extension
media_dir=$(dirname ${mediafile})
fname=${mediafile%.*}
ext=${mediafile/*./}
if [ "$ADDSUMMARY" = 'n' ]; then
if [ ! -e "${media_dir}/summary.wav" ]
then
echoerr "ERROR: Can not find the summary file \"${media_dir}/summary.wav\""
wget -O- --timeout=10 --tries=1 --quiet http://hackerpublicradio.org/say.php?id=${ep_num} | grep HPR_summary
exit 1
fi
fi
if [[ -e hpr${ep_num}.wav ]] || [[ -e hpr${ep_num}.mp3 ]] || [[ -e hpr${ep_num}.ogg ]] || [[ -e hpr${ep_num}.spx ]] || [[ -e hpr${ep_num}_summary.wav ]] || [[ -e ${fname}_mez_norm.wav ]] || [[ -e ${fname}_mez.wav ]] || [[ -e ${fname}_sox_norm.wav ]] || [[ -e ${fname}_sox.wav ]] || [[ -e ${fname}_tmp_hh.pcm ]] || [[ -e ${fname}_tmp_ia.pcm ]] || [[ -e ${fname}_tmp.log ]]
then
echoerr "Files for this episode already exist."
ls -1 hpr${ep_num}* ${fname}_* 2>/dev/null
exit 1
fi
echo "--------------------------------------------------------------------------------"
echo "Geting metadata for hpr${ep_num}"
while read -r line
do
field=$(echo $line | awk -F ':' '{print $1}')
case $field in
"HPR_summary")
HPR_summary=$(echo $line | grep "HPR_summary: " | cut -c 14- )
continue
;;
"HPR_album")
HPR_album=$(echo $line | grep "HPR_album: " | cut -c 12- )
continue
;;
"HPR_artist")
HPR_artist=$(echo $line | grep "HPR_artist: " | cut -c 13- )
continue
;;
"HPR_comment")
HPR_comment=$(echo $line | grep "HPR_comment: " | cut -c 14- )
continue
;;
"HPR_genre")
HPR_genre=$(echo $line | grep "HPR_genre: " | cut -c 12- )
continue
;;
"HPR_title")
HPR_title=$(echo $line | grep "HPR_title: " | cut -c 12- )
continue
;;
"HPR_track")
HPR_track=$(echo $line | grep "HPR_track: " | cut -c 12- )
continue
;;
"HPR_year")
HPR_year=$(echo $line | grep "HPR_year: " | cut -c 11- )
continue
;;
"HPR_duration")
HPR_duration=$(echo $line | grep "HPR_duration: " | cut -c 15- )
continue
;;
"HPR_explicit")
HPR_explicit=$(echo $line | grep "HPR_explicit: " | cut -c 15- )
continue
;;
"HPR_license")
HPR_license=$(echo $line | grep "HPR_license: " | cut -c 14- )
continue
;;
esac
done < <( wget --timeout=10 --tries=1 --quiet http://hackerpublicradio.org/say.php?id=${ep_num} -O - )
if [[ -z "$ADDINTRO" || -z "$ADDPROMO" || -z "$ADDSUMMARY" || -z "$ADDOUTRO" || -z "$HPR_album" || -z "$HPR_artist" || -z "$HPR_comment" || -z "$HPR_genre" || -z "$HPR_title" || -z "$HPR_track" || -z "$HPR_year" || -z "$HPR_summary" || -z "$HPR_duration" || -z "$HPR_explicit" || -z "$HPR_license" ]]
then
echoerr "Could not find information on ${ep_num}. Has the show been posted ?"
exit;
fi
echo "--------------------------------------------------------------------------------"
echo "Add intro : $ADDINTRO"
echo "Add promo : $ADDPROMO"
echo "Add Summary : $ADDSUMMARY"
echo "Add outro : $ADDOUTRO"
echo "album : $HPR_album"
echo "artist : $HPR_artist"
echo "comment : $HPR_comment"
echo "genre : $HPR_genre"
echo "title : $HPR_title"
echo "track : $HPR_track"
echo "year : $HPR_year"
echo "summary : $HPR_summary"
echo "duration : $HPR_duration"
echo "explicit : $HPR_explicit"
echo "license : $HPR_license"
if [[ $HPR_duration == "0" ]]
then
echoerr "The duration is set to 0. Please update the show with the correct time."
exit;
fi
#============================================================
# Preproc`s the source file
echo "--------------------------------------------------------------------------------"
echo "Prepare mezzanine file"
ffmpeg -i ${mediafile} -ar 44100 -ac $CHANNELS ${fname}_sox.wav > ${fname}_tmp.log 2>&1
if [ "$FIXAUDIO" = "1" ];then
echo "normalizing audio"
sox --temp "${TEMP_DIR}" --norm ${fname}_sox.wav ${fname}_sox_norm.wav
mv -v ${fname}_sox_norm.wav ${fname}_sox.wav >> ${fname}_tmp.log 2>&1
# normalize -a 0.5 ${fname}_sox.wav >> ${fname}_tmp.log 2>&1
fi
echo "--------------------------------------------------------------------------------"
echo "Add HPR Branding"
if [ "$ADDSUMMARY" = 'y' ]; then
echo "Creating the summary"
#echo "$HPR_summary" - | espeak -w hpr${ep_num}_summary.wav
echo "$HPR_summary" - | text2wave - -o hpr${ep_num}_summary.wav #festival --tts
#echo "${HPR_summary}" | gtts-cli - --output hpr${ep_num}_summary.mp3
#ffmpeg -i hpr${ep_num}_summary.mp3 hpr${ep_num}_summary.wav
#rm -v hpr${ep_num}_summary.mp3
else
echo "Copying the supplied summary"
if [ ! -e "${media_dir}/summary.wav" ]
then
echoerr "ERROR: Can not find the summary file \"${media_dir}/summary.wav\""
exit 1
fi
cp -v "${media_dir}/summary.wav" hpr${ep_num}_summary.wav
fi
ffmpeg -i hpr${ep_num}_summary.wav -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_hh.pcm 2>> ${fname}_tmp.log
ffmpeg -i hpr${ep_num}_summary.wav -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_ia.pcm 2>> ${fname}_tmp.log
rm hpr${ep_num}_summary.wav
if [ "$ADDSPONSOR" = 'y' ]; then
echo "Adding the sponsor"
ffmpeg -i "$anhonesthost" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_hh.pcm 2>> ${fname}_tmp.log
ffmpeg -i "$internetarchive" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_ia.pcm 2>> ${fname}_tmp.log
else
echo "NOT Adding the sponsor"
fi
if [ "$ADDPROMO" = 'y' ]; then
echo "Adding the promo"
ffmpeg -i "$promo" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_hh.pcm 2>> ${fname}_tmp.log
ffmpeg -i "$promo" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_ia.pcm 2>> ${fname}_tmp.log
fi
if [ "$ADDINTRO" = 'y' ]; then
echo "Adding the intro"
ffmpeg -i "$intro" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_hh.pcm 2>> ${fname}_tmp.log
ffmpeg -i "$intro" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_ia.pcm 2>> ${fname}_tmp.log
fi
echo "convert the uploaded episode and add it to the temp pcm file"
ffmpeg -i ${fname}_sox.wav -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_hh.pcm 2>> ${fname}_tmp.log
ffmpeg -i ${fname}_sox.wav -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_ia.pcm 2>> ${fname}_tmp.log
if [ "$ADDOUTRO" = 'y' ]; then
echo "Adding the outro"
ffmpeg -i "$outro" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_hh.pcm 2>> ${fname}_tmp.log
ffmpeg -i "$outro" -ar 44100 -ac $CHANNELS -acodec pcm_s16le -f s16le - >> ${fname}_tmp_ia.pcm 2>> ${fname}_tmp.log
fi
echo "Convert the pcm file to a know wav format"
ffmpeg -f s16le -ar 44100 -ac $CHANNELS -acodec pcm_s16le -i ${fname}_tmp_hh.pcm ${fname}_mez.wav 2>> ${fname}_tmp.log
ffmpeg -f s16le -ar 44100 -ac $CHANNELS -acodec pcm_s16le -i ${fname}_tmp_ia.pcm hpr${ep_num}.wav 2>> ${fname}_tmp.log
echo "Normalizing the wav files"
sox --temp "${TEMP_DIR}" --norm ${fname}_mez.wav ${fname}_mez_norm.wav
mv -v ${fname}_mez_norm.wav ${fname}_mez.wav >> ${fname}_tmp.log 2>&1
# normalize -a 0.5 ${fname}_mez.wav >> ${fname}_tmp.log 2>&1
sox --temp "${TEMP_DIR}" --norm hpr${ep_num}.wav hpr${ep_num}_norm.wav
mv -v hpr${ep_num}_norm.wav /var/IA/uploads/hpr${ep_num}.wav >> ${fname}_tmp.log 2>&1
# normalize -a 0.5 hpr${ep_num}.wav >> ${fname}_tmp.log 2>&1
echo "--------------------------------------------------------------------------------"
echo "File information"
ffprobe ${fname}_mez.wav 2>&1 | grep Audio:
mediainfo ${fname}_mez.wav
echo "--------------------------------------------------------------------------------"
echo "Convert to opus for IA"
opusenc hpr${ep_num}.wav /var/IA/uploads/hpr${ep_num}.opus
echo "--------------------------------------------------------------------------------"
echo "Convert to flac for IA"
sox --temp "${TEMP_DIR}" -S hpr${ep_num}.wav /var/IA/uploads/hpr${ep_num}.flac
echo "--------------------------------------------------------------------------------"
echo "Convert to mp3 for HPR"
sox --temp "${TEMP_DIR}" -S ${fname}_mez.wav hpr${ep_num}.mp3
echo "Convert to mp3 for IA"
sox --temp "${TEMP_DIR}" -S hpr${ep_num}.wav /var/IA/uploads/hpr${ep_num}.mp3
echo "--------------------------------------------------------------------------------"
echo "Convert to ogg for HPR"
sox --temp "${TEMP_DIR}" -S ${fname}_mez.wav hpr${ep_num}.ogg
echo "Convert to ogg for IA"
sox --temp "${TEMP_DIR}" -S hpr${ep_num}.wav /var/IA/uploads/hpr${ep_num}.ogg
echo "--------------------------------------------------------------------------------"
echo "Convert to spx for HPR"
sox --temp "${TEMP_DIR}" -S ${fname}_mez.wav -c 1 -r 16000 -t wav - | speexenc - hpr${ep_num}.spx
echo "Convert to spx for IA"
sox --temp "${TEMP_DIR}" -S hpr${ep_num}.wav -c 1 -r 16000 -t wav - | speexenc - /var/IA/uploads/hpr${ep_num}.spx
if [[ ! -s /var/IA/uploads/hpr${ep_num}.wav ]] || [[ ! -s /var/IA/uploads/hpr${ep_num}.mp3 ]] || [[ ! -s /var/IA/uploads/hpr${ep_num}.ogg ]] || [[ ! -s /var/IA/uploads/hpr${ep_num}.spx ]] || [[ ! -s hpr${ep_num}.mp3 ]] || [[ ! -s hpr${ep_num}.ogg ]] || [[ ! -s hpr${ep_num}.spx ]]
then
echoerr "ERROR: Something went wrong encoding the files"
exit 1
fi
fix_tags -album="$HPR_album" -artist="$HPR_artist" -comment="${HPR_comment} The license is ${HPR_license}" -genre="$HPR_genre" -title="$HPR_title" -track="$HPR_track" -year="$HPR_year" hpr${ep_num}* 2>> ${fname}_tmp.log 1>&2
fix_tags -album="$HPR_album" -artist="$HPR_artist" -comment="$HPR_comment" -genre="$HPR_genre" -title="$HPR_title" -track="$HPR_track" -year="$HPR_year" /var/IA/uploads/hpr${ep_num}* 2>> ${fname}_tmp.log 1>&2
fix_tags hpr${ep_num}*
fix_tags /var/IA/uploads/hpr${ep_num}*
#echo "Changing the file dates to the time of upload"
touch -r ${mediafile} hpr${ep_num}*
touch -r ${mediafile} /var/IA/uploads/hpr${ep_num}*
rsync -ave ssh --partial --progress --ignore-existing hpr${ep_num}.mp3 hpr${ep_num}.ogg hpr${ep_num}.spx hpr:www/eps/
firefox http://hackerpublicradio.org/local/hpr${ep_num}.mp3
firefox http://hackerpublicradio.org/local/hpr${ep_num}.ogg
firefox file:///var/IA/uploads/hpr${ep_num}.mp3
firefox file:///var/IA/uploads/hpr${ep_num}.ogg
echo "Source: $( mediainfo --Output=XML --Full "${mediafile}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
mediainfo --Output=XML --Full hpr${ep_num}* /var/IA/uploads/hpr${ep_num}* | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' | sort | uniq -c
read -p "Remove files for \"${fname}\" (y|N) ? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
mediafilename=$(basename "${mediafile}")
mediaextension="${mediafilename##*.}"
ssh hpr -t "mkdir /home/hpr/www/eps/hpr${ep_num}" >/dev/null 2>&1
rsync -ave ssh --partial --progress --ignore-existing "${mediafile}" hpr:www/eps/hpr${ep_num}/hpr${ep_num}_source.${mediaextension}
ssh hpr -t "ls -al /home/hpr/www/eps/hpr${ep_num}*"
cp -v "${mediafile}" "/var/IA/uploads/hpr${ep_num}_source.${mediaextension}"
#echo "Remove temp files"
rm -v ${fname}_sox.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav
mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1" -O - | xmlstarlet val --err -
#rsync -ave ssh --partial --progress /var/IA/uploads/ hpr:/home/hpr/upload/processed/
rsync -ave ssh --partial --progress /var/IA/uploads/ borg:/data/IA/uploads/
find /var/IA/done/ -empty -delete
else
echo "skipping...."
echo "cp -v \"${mediafile}\" \"/var/IA/uploads/hpr${ep_num}_source.${mediaextension}\""
echo "rm -v ${fname}_sox.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav"
echo "mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "rsync -ave ssh --partial --progress /var/IA/uploads/ borg:/data/IA/uploads/"
fi

View File

@ -0,0 +1,238 @@
#!/bin/bash
# Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/
#============================================================
echoerr()
{
echo "$@" 1>&2;
}
TEMP_DIR="/var/tmp/"
CHANNELS="1"
FIXAUDIO="1"
ARTIST="EMPTY"
TITLE="EMPTY"
YEAR="EMPTY"
SLOT="EMPTY"
basedir="/var/IA"
upload_dir="${basedir}/uploads"
intro="${basedir}/short.flac"
if [ "$#" -ne 2 ]; then
echoerr "Please enter the source file and episode number"
exit
fi
mediafile=${1}
ep_num=${2}
ep_num=$(echo $ep_num | sed 's/hpr//g')
re='^[0-9]+$'
if ! [[ $ep_num =~ $re ]] ; then
echoerr "error: episode \"${ep_num}\" is not a number"
exit 1
fi
if [ ! -f "${mediafile}" ]; then
echoerr "sorry, media file \"${mediafile}\" does not exist"
exit
fi
if [ ! -r "${mediafile}" ]; then
echoerr "sorry, media file \"${mediafile}\" is not readable"
exit
fi
if [ $(ffprobe "${mediafile}" 2>&1 | grep "Audio:" | wc -l ) -eq 0 ]; then
echoerr "sorry, media file \"${mediafile}\" has no audio track"
exit
fi
# extract file name and extension
media_dir=$(dirname ${mediafile})
fname=${mediafile%.*}
ext=${mediafile/*./}
if [[ -e hpr${ep_num}.wav ]] || [[ -e hpr${ep_num}.mp3 ]] || [[ -e hpr${ep_num}.ogg ]] || [[ -e hpr${ep_num}.spx ]] || [[ -e hpr${ep_num}_summary.wav ]] || [[ -e hpr${ep_num}.wav ]] || [[ -e ${fname}_mez.wav ]] || [[ -e ${fname}_sox_norm.wav ]] || [[ -e ${fname}_mezzanine.wav ]] || [[ -e ${fname}_tmp.pcm ]] || [[ -e ${fname}_tmp.log ]]
then
echoerr "Files for this episode already exist."
ls -1 hpr${ep_num}* ${fname}_* 2>/dev/null
exit 1
fi
echo "--------------------------------------------------------------------------------"
echo "Geting metadata for hpr${ep_num}"
while read -r line
do
field=$(echo $line | awk -F ':' '{print $1}')
case $field in
"HPR_summary")
HPR_summary=$(echo $line | grep "HPR_summary: " | cut -c 14- )
continue
;;
"HPR_album")
HPR_album=$(echo $line | grep "HPR_album: " | cut -c 12- )
continue
;;
"HPR_artist")
HPR_artist=$(echo $line | grep "HPR_artist: " | cut -c 13- )
continue
;;
"HPR_comment")
HPR_comment=$(echo $line | grep "HPR_comment: " | cut -c 14- )
continue
;;
"HPR_genre")
HPR_genre=$(echo $line | grep "HPR_genre: " | cut -c 12- )
continue
;;
"HPR_title")
HPR_title=$(echo $line | grep "HPR_title: " | cut -c 12- )
continue
;;
"HPR_track")
HPR_track=$(echo $line | grep "HPR_track: " | cut -c 12- )
continue
;;
"HPR_year")
HPR_year=$(echo $line | grep "HPR_year: " | cut -c 11- )
continue
;;
"HPR_duration")
HPR_duration=$(echo $line | grep "HPR_duration: " | cut -c 15- )
continue
;;
"HPR_explicit")
HPR_explicit=$(echo $line | grep "HPR_explicit: " | cut -c 15- )
continue
;;
"HPR_license")
HPR_license=$(echo $line | grep "HPR_license: " | cut -c 14- )
continue
;;
esac
done < <( wget --timeout=10 --tries=1 --quiet http://hackerpublicradio.org/say.php?id=${ep_num} -O - )
if [[ -z "$HPR_album" || -z "$HPR_artist" || -z "$HPR_comment" || -z "$HPR_genre" || -z "$HPR_title" || -z "$HPR_track" || -z "$HPR_year" || -z "$HPR_summary" || -z "$HPR_duration" || -z "$HPR_explicit" || -z "$HPR_license" ]]
then
echoerr "Could not find information on ${ep_num}. Has the show been posted ?"
exit;
fi
echo "--------------------------------------------------------------------------------"
echo "album : $HPR_album"
echo "artist : $HPR_artist"
echo "comment : $HPR_comment"
echo "genre : $HPR_genre"
echo "title : $HPR_title"
echo "track : $HPR_track"
echo "year : $HPR_year"
echo "summary : $HPR_summary"
echo "duration : $HPR_duration"
echo "explicit : $HPR_explicit"
echo "license : $HPR_license"
if [[ $HPR_duration == "0" ]]
then
echoerr "The duration is set to 0. Please update the show with the correct time."
exit;
fi
#============================================================
# Preproc`s the source file
echo "--------------------------------------------------------------------------------"
echo "Prepare mezzanine file"
ffmpeg -i ${mediafile} -ar 44100 -ac $CHANNELS hpr${ep_num}.wav > ${fname}_tmp.log 2>&1
echo "Normalizing the wav files"
sox --temp "${TEMP_DIR}" --norm hpr${ep_num}.wav hpr${ep_num}_norm.wav
mv -v hpr${ep_num}_norm.wav ${upload_dir}/hpr${ep_num}.wav >> ${fname}_tmp.log 2>&1
echo "--------------------------------------------------------------------------------"
echo "File information"
ffprobe ${upload_dir}/hpr${ep_num}.wav 2>&1 | grep Audio:
mediainfo ${upload_dir}/hpr${ep_num}.wav
echo "--------------------------------------------------------------------------------"
echo "Convert to opus"
opusenc ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.opus 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------"
echo "Convert to flac"
sox --temp "${TEMP_DIR}" -S ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.flac 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------"
echo "Convert to mp3"
sox --temp "${TEMP_DIR}" -S ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.mp3 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------"
echo "Convert to ogg"
sox --temp "${TEMP_DIR}" -S ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.ogg 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------"
echo "Convert to spx"
sox --temp "${TEMP_DIR}" -S ${upload_dir}/hpr${ep_num}.wav -c 1 -r 16000 -t wav - | speexenc - ${upload_dir}/hpr${ep_num}.spx 2>> ${fname}_tmp.log 1>&2
if [[ ! -s ${upload_dir}/hpr${ep_num}.wav ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.opus ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.flac ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.mp3 ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.ogg ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.spx ]]
then
echoerr "ERROR: Something went wrong encoding the files"
exit 1
fi
fix_tags -album="$HPR_album" -artist="$HPR_artist" -comment="${HPR_comment} The license is ${HPR_license}" -genre="$HPR_genre" -title="$HPR_title" -track="$HPR_track" -year="$HPR_year" ${upload_dir}/hpr${ep_num}* 2>> ${fname}_tmp.log 1>&2
fix_tags ${upload_dir}/hpr${ep_num}*
#echo "Changing the file dates to the time of upload"
touch -r ${mediafile} hpr${ep_num}*
touch -r ${mediafile} ${upload_dir}/hpr${ep_num}*
ls -al hpr${ep_num}* ${upload_dir}/hpr${ep_num}*
rsync -ave ssh --partial --progress --ignore-existing ${upload_dir}/hpr${ep_num}.mp3 ${upload_dir}/hpr${ep_num}.ogg ${upload_dir}/hpr${ep_num}.spx hpr:www/eps/
firefox http://hackerpublicradio.org/local/hpr${ep_num}.mp3
firefox http://hackerpublicradio.org/local/hpr${ep_num}.ogg
firefox file://${upload_dir}/hpr${ep_num}.mp3
firefox file://${upload_dir}/hpr${ep_num}.ogg
mpv "http://hackerpublicradio.org/local/hpr${ep_num}.spx" "http://hackerpublicradio.org/local/hpr${ep_num}.ogg" "http://hackerpublicradio.org/local/hpr${ep_num}.mp3" "file://${upload_dir}/hpr${ep_num}.spx" "file://${upload_dir}/hpr${ep_num}.opus" "file://${upload_dir}/hpr${ep_num}.ogg" "file://${upload_dir}/hpr${ep_num}.mp3" "file://${upload_dir}/hpr${ep_num}.flac"
echo "Source: $( mediainfo --Output=XML --Full "${mediafile}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
mediainfo --Output=XML --Full hpr${ep_num}* ${upload_dir}/hpr${ep_num}* | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' | sort | uniq -c
read -p "Remove files for \"${fname}\" (y|N) ? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
mediafilename=$(basename "${mediafile}")
mediaextension="${mediafilename##*.}"
ssh hpr -t "mkdir /home/hpr/www/eps/hpr${ep_num}" >/dev/null 2>&1
rsync -ave ssh --partial --progress --ignore-existing "${mediafile}" hpr:www/eps/hpr${ep_num}/hpr${ep_num}_source.${mediaextension}
ssh hpr -t "ls -al /home/hpr/www/eps/hpr${ep_num}*"
cp -v "${mediafile}" "${upload_dir}/hpr${ep_num}_source.${mediaextension}"
#echo "Remove temp files"
rm -v ${fname}_mezzanine.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav
mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/rss-future.php" -O - | xmlstarlet val --err -
#rsync -ave ssh --partial --progress ${upload_dir}/ hpr:/home/hpr/upload/processed/
rsync -ave ssh --partial --progress ${upload_dir}/ borg:/data/IA/uploads/
echo "$( ssh borg -t "ls -al /data/IA/uploads/hpr${ep_num}*" ; ls -al ${upload_dir}/hpr${ep_num}* )" | grep "hpr${ep_num}" | awk '{print $5, $NF}' | sort
find /var/IA/done/ -empty -delete
else
echo "skipping...."
echo "cp -v \"${mediafile}\" \"${upload_dir}/hpr${ep_num}_source.${mediaextension}\""
echo "rm -v ${fname}_mezzanine.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav"
echo "mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "rsync -ave ssh --partial --progress ${upload_dir}/ borg:/data/IA/uploads/"
fi

603
workflow/hprtranscode-simple.bash Executable file
View File

@ -0,0 +1,603 @@
#!/usr/bin/env bash
# Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/
#============================================================
TEMP_DIR="/var/tmp/"
CHANNELS="1"
FIXAUDIO="1"
ARTIST="EMPTY"
TITLE="EMPTY"
YEAR="EMPTY"
SLOT="EMPTY"
basedir="/var/IA"
upload_dir="${basedir}/uploads"
theme="${basedir}/theme.wav"
outro="${basedir}/2022-03-07-outro.wav"
ttsserver="http://localhost:5500"
processing_dir="/home/ken/tmp/hpr/processing"
git_image_dir="/home/ken/sourcecode/hpr/HPR_Public_Code/www/images/hosts"
acceptable_duration_difference="2" # Seconds
thedate=$(/usr/bin/date -u +%Y-%m-%d_%H-%M-%SZ_%A)
echo "Processing the next HPR Show in the queue"
if [ $( curl -s -o /dev/null -w "%{http_code}" -X 'GET' "${ttsserver}/api/voices" -H 'accept: */*' ) != "200" ]
then
echo "Please start the tts-server \"podman run -it -p 5500:5500 synesthesiam/opentts:en\""
exit
fi
function abs_diff {
echo $(($1 >= $2 ? $1 - $2 : $2 - $1))
}
function create_tts_summary {
HPR_summary="$( cat "hpr${ep_num}_summary.txt" )"
echo "INFO: Converting text \"${HPR_summary}\" to speech."
curl -X 'GET' -G --data-urlencode "voice=coqui-tts:en_ljspeech" --data-urlencode "text=${HPR_summary}" --data-urlencode "vocoder=high" --data-urlencode "denoiserStrength=0.03" --data-urlencode "cache=false" ${ttsserver}/api/tts -H 'accept: */*' --output ~hpr${ep_num}_summary.wav
}
###################
# Locate media directory
#
#
show_type=""
if [[ -f "${1}" && -n "${2}" ]]
then
show_type="local"
mediafile="${1}"
media_dir="$( dirname "${mediafile}" )"
ep_num="${2}"
echo "The duration is \"$( \date -ud "1970-01-01 $( ffprobe -i "${mediafile}" 2>&1| awk -F ': |, ' '/Duration:/ { print $2 }' )" +%s )\"."
else
show_type="remote"
response=$( curl --silent --netrc-file ${HOME}/.netrc "https://hub.hackerpublicradio.org/cms/status.php" | \
grep 'SHOW_POSTED' | \
head -1 | \
sed 's/,/ /g' )
if [ -z "${response}" ]
then
echo "INFO: There appear to be no more shows with the status \"SHOW_POSTED\"."
echo "Getting a list of all the reservations."
curl --silent --netrc-file ${HOME}/.netrc "https://hub.hackerpublicradio.org/cms/status.php"
exit 3
fi
timestamp_epoc="$( echo ${response} | awk '{print $1}' )"
ep_num="$( echo ${response} | awk '{print $2}' )"
ep_date="$( echo ${response} | awk '{print $3}' )"
key="$( echo ${response} | awk '{print $4}' )"
status="$( echo ${response} | awk '{print $5}' )"
email="$( echo ${response} | awk '{print $6}' )"
#source_dir="hpr:/home/hpr/upload/${timestamp_epoc}_${ep_num}_${ep_date}_${key}"
dest_dir="${timestamp_epoc}_${ep_num}_${ep_date}_${key}"
media_dir="${processing_dir}/${timestamp_epoc}_${ep_num}_${ep_date}_${key}"
fi
if [ -z "${show_type}" ]
then
echo "sorry, variable \"show_type\" is not set."
exit
fi
if [ -z "${media_dir}" ]
then
echo "sorry, variable \"media_dir\" is not set."
exit
fi
if [ ! -d "${media_dir}" ]
then
echo "sorry, directory \"media_dir: ${media_dir}\" does not exist"
exit 1
fi
echo detox -v "${media_dir}/"
detox -vr "${media_dir}/"
if [[ "$( find "${media_dir}" \( -iname "hpr${ep_num}.wav" -o -iname "hpr${ep_num}.mp3" -o -iname "hpr${ep_num}.ogg" -o -iname "hpr${ep_num}.spx" -o -iname "hpr${ep_num}_summary.txt" -o -iname "hpr${ep_num}_summary.wav" -o -iname "hpr${ep_num}.wav" -o -iname "*_mez.wav" -o -iname "*_sox_norm.wav" -o -iname "*_mezzanine.wav" -o -iname "*_tmp.pcm" -o -iname "hpr${ep_num}_intro.wav" -o -iname "~hpr${ep_num}_summary.wav" -o -iname "~~hpr${ep_num}_summary.wav" -o -iname "*_tmp.log" -o -iname "silence.wav" -o -iname "hpr${ep_num}_norm.wav" \) | wc -l )" -ne 0 ]]
then
echo "Files for this episode already exist."
find "${media_dir}" \( -iname "hpr${ep_num}.wav" -o -iname "hpr${ep_num}.mp3" -o -iname "hpr${ep_num}.ogg" -o -iname "hpr${ep_num}.spx" -o -iname "hpr${ep_num}_summary.txt" -o -iname "hpr${ep_num}_summary.wav" -o -iname "hpr${ep_num}.wav" -o -iname "*_mez.wav" -o -iname "*_sox_norm.wav" -o -iname "*_mezzanine.wav" -o -iname "*_tmp.pcm" -o -iname "hpr${ep_num}_intro.wav" -o -iname "~hpr${ep_num}_summary.wav" -o -iname "~~hpr${ep_num}_summary.wav" -o -iname "*_tmp.log" -o -iname "silence.wav" -o -iname "hpr${ep_num}_norm.wav" \)
read -p "Shall I remove them ? (N|y) ? " -n 1 -r
echo # (optional) move to a new line
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo "skipping...."
exit
else
echo "Removing old files ...."
find "${media_dir}" \( -iname "hpr${ep_num}.wav" -o -iname "hpr${ep_num}.mp3" -o -iname "hpr${ep_num}.ogg" -o -iname "hpr${ep_num}.spx" -o -iname "hpr${ep_num}_summary.txt" -o -iname "hpr${ep_num}_summary.wav" -o -iname "hpr${ep_num}.wav" -o -iname "*_mez.wav" -o -iname "*_sox_norm.wav" -o -iname "*_mezzanine.wav" -o -iname "*_tmp.pcm" -o -iname "hpr${ep_num}_intro.wav" -o -iname "~hpr${ep_num}_summary.wav" -o -iname "~~hpr${ep_num}_summary.wav" -o -iname "*_tmp.log" -o -iname "silence.wav" -o -iname "hpr${ep_num}_norm.wav" \) -delete -print
fi
fi
#####################################################
# Process media
media=$( find "${media_dir}" -type f -exec file {} \; | grep -Ei 'audio|mpeg|video|MP4' | awk -F ': ' '{print $1}' )
if [ -z "${media}" ]
then
echo "ERROR: Can't find any media in \"${media_dir}/\""
find "${media_dir}/" -type f
exit 1
fi
mediafile=""
echo "Found more than one media file. Please select which one to use ?"
if [ "$( echo "${media}" | wc -l )" -ne 1 ]
then
select this_media in $( echo "${media}" )
do
echo "INFO: You selected \"${this_media}\"."
ls -al "${this_media}"
mediafile="${this_media}"
break
done
else
echo "INFO: Selecting media as \"${media}\"."
mediafile="${media}"
fi
# extract file name and extension
fname="$( basename "${mediafile%.*}" )"
ext="${mediafile/*./}"
cd "${media_dir}/"
pwd
echo "INFO: Processing hpr${ep_num} from ${email}"
echo "INFO: Working directory is \"${media_dir}/\""
echo ""
if [ -z "${mediafile}" ]
then
echo "sorry, variable \"mediafile\" is not set."
exit
fi
if [ -z "${fname}" ]
then
echo "sorry, variable \"fname\" is not set."
exit
fi
if [ -z "${ext}" ]
then
echo "sorry, variable \"ext\" is not set."
exit
fi
if [ ! -f "${mediafile}" ]
then
echo "sorry, media file \"${mediafile}\" does not exist"
exit
fi
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "File information for \"${mediafile}\"" | tee -a ${fname}_tmp.log
ffprobe ${mediafile} 2>&1 | grep Audio:
mediainfo ${mediafile}
audio2image.bash ${mediafile}
xdg-open ${mediafile%.*}.png >/dev/null 2>&1 &
unset REPLY
until [[ $REPLY =~ ^[yYnN]$ ]]
do
read -p "Source Waveform look ok ? (N|y) ? " -n 1 -r
echo ""
done
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo "skipping.... $REPLY"
exit
fi
re='^[0-9]+$'
if ! [[ $ep_num =~ $re ]]
then
echo "error: episode \"${ep_num}\" is not a number"
exit 1
fi
if [ ! -f "${outro}" ]
then
echo "sorry, file \"${outro}\" does not exist"
exit 1
fi
if [ ! -f "${theme}" ]
then
echo "sorry, file \"${theme}\" does not exist"
exit 1
fi
if [ ! -r "${mediafile}" ]
then
echo "sorry, media file \"${mediafile}\" is not readable"
exit
fi
if [ $(ffprobe "${mediafile}" 2>&1 | grep "Audio:" | wc -l ) -eq 0 ]
then
echo "sorry, media file \"${mediafile}\" has no audio track"
exit
fi
xdg-open "https://hackerpublicradio.org/eps/hpr${ep_num}/index.html" >/dev/null 2>&1 &
mpv -vo=null "${mediafile}"
unset REPLY
until [[ $REPLY =~ ^[yYnN]$ ]]
do
read -p "Is the audio ok (n|Y) ? " -n 1 -r
echo ""
done
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo "skipping.... $REPLY"
exit
fi
echo "--------------------------------------------------------------------------------"
echo "Geting metadata for hpr${ep_num}"
while read -r line
do
field=$(echo $line | awk -F ':' '{print $1}')
case $field in
"HPR_summary")
HPR_summary=$(echo $line | grep "HPR_summary: " | cut -c 14- )
continue
;;
"HPR_album")
HPR_album=$(echo $line | grep "HPR_album: " | cut -c 12- )
continue
;;
"HPR_artist")
HPR_artist=$(echo $line | grep "HPR_artist: " | cut -c 13- )
continue
;;
"HPR_comment")
HPR_comment=$(echo $line | grep "HPR_comment: " | cut -c 14- )
continue
;;
"HPR_genre")
HPR_genre=$(echo $line | grep "HPR_genre: " | cut -c 12- )
continue
;;
"HPR_title")
HPR_title=$(echo $line | grep "HPR_title: " | cut -c 12- )
continue
;;
"HPR_track")
HPR_track=$(echo $line | grep "HPR_track: " | cut -c 12- )
continue
;;
"HPR_year")
HPR_year=$(echo $line | grep "HPR_year: " | cut -c 11- )
continue
;;
"HPR_duration")
HPR_duration=$(echo $line | grep "HPR_duration: " | cut -c 15- )
continue
;;
"HPR_explicit")
HPR_explicit=$(echo $line | grep "HPR_explicit: " | cut -c 15- )
continue
;;
"HPR_license")
HPR_license=$(echo $line | grep "HPR_license: " | cut -c 14- )
continue
;;
esac
done < <( curl --silent --netrc-file ${HOME}/.netrc "https://hub.hackerpublicradio.org/cms/say.php?id=${ep_num}" )
if [[ -z "$HPR_album" || -z "$HPR_artist" || -z "$HPR_comment" || -z "$HPR_genre" || -z "$HPR_title" || -z "$HPR_track" || -z "$HPR_year" || -z "$HPR_summary" || -z "$HPR_duration" || -z "$HPR_explicit" || -z "$HPR_license" ]]
then
echo "Could not find information on ${ep_num}. Has the show been posted ?"
exit;
fi
echo "--------------------------------------------------------------------------------"
echo "album : $HPR_album"
echo "artist : $HPR_artist"
echo "comment : $HPR_comment"
echo "genre : $HPR_genre"
echo "title : $HPR_title"
echo "track : $HPR_track"
echo "year : $HPR_year"
echo "summary : $HPR_summary"
echo "duration : $HPR_duration"
echo "explicit : $HPR_explicit"
echo "license : $HPR_license"
echo "media_dir : ${media_dir}"
echo "mediafile : ${mediafile}"
echo "fname : ${fname}"
echo "ext : ${ext}"
if [[ $HPR_duration == "0" ]]
then
echo "The duration is set to 0. Please update the show with the correct time."
exit;
fi
#============================================================
# Preproc`s the source file
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Prepare mezzanine file" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${mediafile} -ar 44100 -ac $CHANNELS ${fname}_mezzanine.wav > ${fname}_tmp.log 2>&1
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Add HPR Branding" | tee -a ${fname}_tmp.log
echo "Creating the summary" | tee -a ${fname}_tmp.log
#echo "$HPR_summary" - | text2wave -F 44100 - -o hpr${ep_num}_summary.wav #festival --tts
#echo "$HPR_summary" - | espeak -w ~hpr${ep_num}_summary.wav
echo "$HPR_summary" > "hpr${ep_num}_summary.txt"
create_tts_summary_ok="not-ok"
while [ "${create_tts_summary_ok}" != "OK" ]
do
create_tts_summary
xdg-open "hpr${ep_num}_summary.txt" 2>&1 &
mpv --speed=1.8 ~hpr${ep_num}_summary.wav
read -p "Is the text to speech correct (y|N) ? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
create_tts_summary_ok="OK"
else
echo "WARN: Please correct the text and try again."
inotifywait --event modify "hpr${ep_num}_summary.txt"
fi
done
echo "INFO: TTS complete."
ffmpeg -y -i ~hpr${ep_num}_summary.wav -ar 44100 ~~hpr${ep_num}_summary.wav >> ${fname}_tmp.log 2>&1
sox -V2 -n -r 44100 -c 1 silence.wav trim 0.0 6.0 >> ${fname}_tmp.log 2>&1
sox -V2 silence.wav ~~hpr${ep_num}_summary.wav hpr${ep_num}_summary.wav >> ${fname}_tmp.log 2>&1
echo "Adding the theme" | tee -a ${fname}_tmp.log
sox -V2 -m "hpr${ep_num}_summary.wav" "${theme}" "hpr${ep_num}_intro.wav" >> ${fname}_tmp.log 2>&1
echo "Creating the sandwitch: \"hpr${ep_num}_intro.wav\" \"${fname}_mezzanine.wav\" \"${outro}\" \"hpr${ep_num}.wav\" " | tee -a ${fname}_tmp.log
sox -V2 "hpr${ep_num}_intro.wav" "${fname}_mezzanine.wav" "${outro}" "hpr${ep_num}.wav" >> ${fname}_tmp.log 2>&1
echo "Normalizing the wav files" | tee -a ${fname}_tmp.log
#sox --temp "${TEMP_DIR}" --norm hpr${ep_num}.wav hpr${ep_num}_norm.wav
ffmpeg -y -i hpr${ep_num}.wav -af loudnorm=I=-16:LRA=11:TP=-1.5 hpr${ep_num}_norm.wav >> ${fname}_tmp.log 2>&1
mv -v hpr${ep_num}_norm.wav ${upload_dir}/hpr${ep_num}.wav >> ${fname}_tmp.log 2>&1
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "File information" | tee -a ${fname}_tmp.log
ffprobe ${upload_dir}/hpr${ep_num}.wav 2>&1 | grep Audio:
mediainfo ${upload_dir}/hpr${ep_num}.wav
audio2image.bash ${upload_dir}/hpr${ep_num}.wav
xdg-open ${upload_dir}/hpr${ep_num}.png >/dev/null 2>&1 &
read -p "Processed Waveform look ok ? (N|y) ? " -n 1 -r
echo # (optional) move to a new line
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo "skipping...."
exit
fi
rm -v ${upload_dir}/hpr${ep_num}.png
echo "--------------------------------------------------------------------------------"
echo "Geting transcript of hpr${ep_num}"
whisper --model tiny --language en --output_dir "${media_dir}" "${upload_dir}/hpr${ep_num}.wav" | tee -a ${fname}_tmp.log
ls -al "${media_dir}/hpr${ep_num}".*
xdg-open "${media_dir}/hpr${ep_num}".txt 2>&1 &
echo mpv --no-audio-display --audio-channels=stereo --speed="3.5" "${media_dir}/hpr${ep_num}".txt
unset REPLY
until [[ $REPLY =~ ^[yYnN]$ ]]
do
read -p "Processed transcript look ok ? (N|y) ? " -n 1 -r
echo ""
done
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo "skipping.... $REPLY"
exit
fi
mkdir "${upload_dir}/hpr${ep_num}" >/dev/null 2>&1
cp -v "${media_dir}/hpr${ep_num}".vtt "${upload_dir}/hpr${ep_num}/hpr${ep_num}.vtt" | tee -a ${fname}_tmp.log
cp -v "${media_dir}/hpr${ep_num}".srt "${upload_dir}/hpr${ep_num}/hpr${ep_num}.srt" | tee -a ${fname}_tmp.log
cp -v "${media_dir}/hpr${ep_num}".txt "${upload_dir}/hpr${ep_num}/hpr${ep_num}.txt" | tee -a ${fname}_tmp.log
cp -v "${media_dir}/hpr${ep_num}".tsv "${upload_dir}/hpr${ep_num}/hpr${ep_num}.tsv" | tee -a ${fname}_tmp.log
cp -v "${media_dir}/hpr${ep_num}".json "${upload_dir}/hpr${ep_num}/hpr${ep_num}.json" | tee -a ${fname}_tmp.log
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to opus" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.opus 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to flac" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.flac 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to mp3" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.mp3 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to ogg" | tee -a ${fname}_tmp.log
#ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.ogg 2>> ${fname}_tmp.log 1>&2
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav -acodec libopus -f ogg ${upload_dir}/hpr${ep_num}.ogg 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to spx" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.spx 2>> ${fname}_tmp.log 1>&2
### End conversion
intro_duration="$( mediainfo --Output=XML --Full "hpr${ep_num}_intro.wav" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
outro_duration="$( mediainfo --Output=XML --Full "${outro}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
source_duration="$( mediainfo --Output=XML --Full "${mediafile}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
expected_duration=$(( ${intro_duration} + ${HPR_duration} + ${outro_duration} ))
echo "Intro(${intro_duration}) + Show(${HPR_duration}) + Outro(${outro_duration}) = ${expected_duration}"
media_error="0"
for check_file in \
${upload_dir}/hpr${ep_num}.wav \
${upload_dir}/hpr${ep_num}.opus \
${upload_dir}/hpr${ep_num}.flac \
${upload_dir}/hpr${ep_num}.mp3 \
${upload_dir}/hpr${ep_num}.spx \
${upload_dir}/hpr${ep_num}.ogg
do
# ${upload_dir}/hpr${ep_num}.spx
echo "INFO: Processing the file \"${check_file}\""
if [[ ! -s "${check_file}" ]]
then
echo "ERROR: Something went wrong encoding of the file \"${check_file}\""
exit
else
mediainfo --Output=XML --Full "${check_file}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}'
this_duration=$( mediainfo --Output=XML --Full "${check_file}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )
if [[ $(abs_diff "${this_duration}" "${expected_duration}" ) -le "${acceptable_duration_difference}" ]]
then
echo "INFO: The file \"${check_file}\" duration of ${this_duration} () is close enough to ${expected_duration}"
else
echo "ERROR: The file \"${check_file}\" actual duration of ${this_duration} is not close enough to posted duration of ${expected_duration}."
echo " Fix or update the posted duration to ${source_duration}."
media_error="1"
fi
#${expected_duration}
fi
done
echo "Source: ${source_duration}"
if [[ "${media_error}" -eq "1" ]]
then
echo "ERROR: Media is not encoded correctly"
exit
else
echo "INFO: Media duration is correct"
fi
if [[ ! -s ${upload_dir}/hpr${ep_num}.wav ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.opus ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.flac ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.mp3 ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.ogg ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.spx ]]
then
echo "ERROR: Something went wrong encoding the files"
exit 1
fi
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Fixing Tags" | tee -a ${fname}_tmp.log
fix_tags -album="$HPR_album" -artist="$HPR_artist" -comment="${HPR_comment} The license is ${HPR_license}" -genre="$HPR_genre" -title="$HPR_title" -track="$HPR_track" -year="$HPR_year" ${upload_dir}/hpr${ep_num}* 2>> ${fname}_tmp.log 1>&2
fix_tags ${upload_dir}/hpr${ep_num}*
#echo "Changing the file dates to the time of upload"
touch -r ${mediafile} hpr${ep_num}*
touch -r ${mediafile} ${upload_dir}/hpr${ep_num}*
ls -al hpr${ep_num}* ${upload_dir}/hpr${ep_num}* | tee -a ${fname}_tmp.log
# # echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
# # echo "Getting info for the asset table" | tee -a ${fname}_tmp.log
# #
# # echo "INSERT INTO assets (episode_id,filename,extension,size,sha1sum,mime_type,file_type) VALUES"
# #
# # for asset_file in \
# # ${upload_dir}/hpr${ep_num}.wav \
# # ${upload_dir}/hpr${ep_num}.opus \
# # ${upload_dir}/hpr${ep_num}.flac \
# # ${upload_dir}/hpr${ep_num}.mp3 \
# # ${upload_dir}/hpr${ep_num}.spx \
# # ${upload_dir}/hpr${ep_num}.ogg
# # do
# # size="$( ls -al "${asset_file}" | awk '{print $5}' )"
# # sha1sum="$( sha1sum "${asset_file}" | awk '{print $1}' )"
# # mime_type=$( file --dereference --brief --mime "${asset_file}" )
# # file_type=$( file --dereference --brief "${asset_file}" )
# # echo "(${ep_num},'${filename}','${extension}','${size}','${sha1sum}','${mime_type}','${file_type}'),"
# # done
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Transferring files to Servers" | tee -a ${fname}_tmp.log
#rsync -ave ssh --partial --progress --ignore-existing ${upload_dir}/hpr${ep_num}.mp3 ${upload_dir}/hpr${ep_num}.ogg ${upload_dir}/hpr${ep_num}.spx hpr:www/eps/
#firefox "https://hackerpublicradio.org/local/hpr${ep_num}.mp3" >/dev/null 2>&1 &
#firefox "https://hackerpublicradio.org/local/hpr${ep_num}.ogg" >/dev/null 2>&1 &
firefox "file://${upload_dir}/hpr${ep_num}.mp3" >/dev/null 2>&1 &
firefox "file://${upload_dir}/hpr${ep_num}.ogg" >/dev/null 2>&1 &
#firefox "https://hackerpublicradio.org/eps.php?id=${ep_num}" >/dev/null 2>&1 &
#mpv "http://hackerpublicradio.org/local/hpr${ep_num}.spx" "http://hackerpublicradio.org/local/hpr${ep_num}.ogg" "http://hackerpublicradio.org/local/hpr${ep_num}.mp3" "file://${upload_dir}/hpr${ep_num}.spx" "file://${upload_dir}/hpr${ep_num}.opus" "file://${upload_dir}/hpr${ep_num}.ogg" "file://${upload_dir}/hpr${ep_num}.mp3" "file://${upload_dir}/hpr${ep_num}.flac"
mpv "file://${upload_dir}/hpr${ep_num}.spx" "file://${upload_dir}/hpr${ep_num}.opus" "file://${upload_dir}/hpr${ep_num}.ogg" "file://${upload_dir}/hpr${ep_num}.mp3" "file://${upload_dir}/hpr${ep_num}.flac"
read -p "Remove files for \"${fname}\" (y|N) ? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
mediafilename=$(basename "${mediafile}")
mediaextension="${mediafilename##*.}" # "
# ssh hpr -t "mkdir /home/hpr/www/eps/hpr${ep_num}" >/dev/null 2>&1
# rsync -ave ssh --partial --progress --ignore-existing "${mediafile}" hpr:www/eps/hpr${ep_num}/hpr${ep_num}_source.${mediaextension} | tee -a ${fname}_tmp.log
#
# rsync -ave ssh --partial --progress --ignore-existing "${media_dir}/hpr${ep_num}.wav".vtt hpr:www/eps/hpr${ep_num}/hpr${ep_num}.vtt | tee -a ${fname}_tmp.log
# rsync -ave ssh --partial --progress --ignore-existing "${media_dir}/hpr${ep_num}.wav".srt hpr:www/eps/hpr${ep_num}/hpr${ep_num}.srt | tee -a ${fname}_tmp.log
# rsync -ave ssh --partial --progress --ignore-existing "${media_dir}/hpr${ep_num}.wav".txt hpr:www/eps/hpr${ep_num}/hpr${ep_num}.txt | tee -a ${fname}_tmp.log
#
# ssh hpr -t "ls -al /home/hpr/www/eps/hpr${ep_num}*"
# cp -v "${mediafile}" "${upload_dir}/hpr${ep_num}_source.${mediaextension}"
#echo "Remove temp files"
rm -v ~hpr${ep_num}_summary.wav ~~hpr${ep_num}_summary.wav silence.wav
rm -v ${fname}_mezzanine.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav
#mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/
# wget --timeout=0 -q "http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1" -O - | xmlstarlet val --err -
# wget --timeout=0 -q "http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1" -O - | xmlstarlet val --err -
# wget --timeout=0 -q "http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1" -O - | xmlstarlet val --err -
# wget --timeout=0 -q "http://hackerpublicradio.org/rss-future.php" -O - | xmlstarlet val --err -
echo "INFO: rsync -ave ssh --partial --progress ${upload_dir}/hpr${ep_num}* borg:/data/IA/uploads/"
rsync -ave ssh --partial --progress ${upload_dir}/hpr${ep_num}* borg:/data/IA/uploads/
echo "$( ssh borg -t "ls -al /data/IA/uploads/hpr${ep_num}*" ; ls -al ${upload_dir}/hpr${ep_num}* )" | grep "hpr${ep_num}" | awk '{print $5, $NF}' | sort
if [ ${show_type} == "remote" ]
then
echo "INFO: Setting the status"
# SHOW_SUBMITTED → METADATA_PROCESSED → SHOW_POSTED → MEDIA_TRANSCODED → UPLOADED_TO_IA → UPLOADED_TO_RSYNC_NET
curl --netrc-file ${HOME}/.netrc "https://hub.hackerpublicradio.org/cms/status.php?ep_num=${ep_num}&status=MEDIA_TRANSCODED"
curl --silent --netrc-file ${HOME}/.netrc "https://hub.hackerpublicradio.org/cms/status.php"
fi
else
echo "skipping...."
echo "cp -v \"${mediafile}\" \"${upload_dir}/hpr${ep_num}_source.${mediaextension}\""
echo "rm -v ${fname}_mezzanine.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav"
#echo "mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "rsync -ave ssh --partial --progress ${upload_dir}/ borg:/data/IA/uploads/"
fi

View File

@ -0,0 +1,397 @@
#!/usr/bin/env bash
# Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/
#============================================================
TEMP_DIR="/var/tmp/"
CHANNELS="1"
FIXAUDIO="1"
ARTIST="EMPTY"
TITLE="EMPTY"
YEAR="EMPTY"
SLOT="EMPTY"
basedir="/var/IA"
upload_dir="${basedir}/uploads"
theme="${basedir}/theme.wav"
outro="${basedir}/2022-03-07-outro.wav"
ttsserver="http://localhost:5500"
processing_dir="/home/ken/tmp/hpr/processing"
git_image_dir="/home/ken/sourcecode/hpr/HPR_Public_Code/www/images/hosts"
thedate=$(/usr/bin/date -u +%Y-%m-%d_%H-%M-%SZ_%A)
echo "Processing the next HPR Show in the queue"
if [ $( curl -s -o /dev/null -w "%{http_code}" -X 'GET' "${ttsserver}/api/voices" -H 'accept: */*' ) != "200" ]
then
echo "Please start the tts-server \"podman run -it -p 5500:5500 synesthesiam/opentts:en\""
exit
fi
###################
# Get the show
#
#
if [[ -f "${1}" && -n "${2}" ]]
then
mediafile="${1}"
ep_num="${2}"
echo "The duration is \"$( \date -ud "1970-01-01 $( ffprobe -i "${mediafile}" 2>&1| awk -F ': |, ' '/Duration:/ { print $2 }' )" +%s )\"."
else
response=$( curl --silent --netrc-file ${HOME}/.netrc "https://hackerpublicradio.org/cms/status.php" | \
grep 'SHOW_POSTED' | \
head -1 | \
sed 's/,/ /g' )
if [ -z "${response}" ]
then
echo "INFO: There appear to be no more shows with the status \"SHOW_POSTED\"."
echo "Getting a list of all the reservations."
curl --silent --netrc-file ${HOME}/.netrc "https://hackerpublicradio.org/cms/status.php"
exit 3
fi
timestamp_epoc="$( echo ${response} | awk '{print $1}' )"
ep_num="$( echo ${response} | awk '{print $2}' )"
ep_date="$( echo ${response} | awk '{print $3}' )"
key="$( echo ${response} | awk '{print $4}' )"
status="$( echo ${response} | awk '{print $5}' )"
email="$( echo ${response} | awk '{print $6}' )"
#source_dir="hpr:/home/hpr/upload/${timestamp_epoc}_${ep_num}_${ep_date}_${key}"
dest_dir="${timestamp_epoc}_${ep_num}_${ep_date}_${key}"
echo detox -v "${processing_dir}/${dest_dir}/"
detox -vr "${processing_dir}/${dest_dir}/"
cd "${processing_dir}/${dest_dir}/"
pwd
echo "INFO: Processing hpr${ep_num} from ${email}"
echo "INFO: Working directory is \"${processing_dir}/${dest_dir}/\""
echo ""
media=$( find "${processing_dir}/${timestamp_epoc}_${ep_num}_${ep_date}_${key}" -type f -exec file {} \; | grep -Ei 'audio|mpeg|video|MP4' | awk -F ': ' '{print $1}' )
if [ -z "${media}" ]
then
echo "ERROR: Can't find any media in \"${processing_dir}/${dest_dir}/\""
find "${processing_dir}/${dest_dir}/" -type f
exit 1
fi
mediafile=""
if [ "$( echo "${media}" | wc -l )" -ne 1 ]
then
select this_media in $( echo "${media}" )
do
echo "INFO: You selected \"${this_media}\"."
ls -al "${this_media}"
mediafile="${this_media}"
break
done
else
echo "INFO: Selecting media as \"${media}\"."
mediafile="${media}"
fi
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "File information for \"${mediafile}\"" | tee -a ${fname}_tmp.log
ffprobe ${mediafile} 2>&1 | grep Audio:
mediainfo ${mediafile}
audio2image.bash ${mediafile}
xdg-open ${mediafile%.*}.png >/dev/null 2>&1 &
read -p "Source Waveform look ok ? (N|y) ? " -n 1 -r
echo # (optional) move to a new line
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo "skipping...."
exit
fi
fi
re='^[0-9]+$'
if ! [[ $ep_num =~ $re ]]
then
echo "error: episode \"${ep_num}\" is not a number"
exit 1
fi
if [ ! -f "${outro}" ]
then
echo "sorry, file \"${outro}\" does not exist"
exit 1
fi
if [ ! -f "${theme}" ]
then
echo "sorry, file \"${theme}\" does not exist"
exit 1
fi
if [ ! -f "${mediafile}" ]
then
echo "sorry, media file \"${mediafile}\" does not exist"
exit
fi
if [ ! -r "${mediafile}" ]
then
echo "sorry, media file \"${mediafile}\" is not readable"
exit
fi
if [ $(ffprobe "${mediafile}" 2>&1 | grep "Audio:" | wc -l ) -eq 0 ]
then
echo "sorry, media file \"${mediafile}\" has no audio track"
exit
fi
mpv -vo=null "${mediafile}"
read -p "Is there any problems with the audio (N|y) ? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[yY]$ ]]
then
echo "skipping...."
exit 1
fi
# extract file name and extension
media_dir=$(dirname ${mediafile})
fname=${mediafile%.*}
ext=${mediafile/*./}
if [[ -e hpr${ep_num}.wav ]] || [[ -e hpr${ep_num}.mp3 ]] || [[ -e hpr${ep_num}.ogg ]] || [[ -e hpr${ep_num}.spx ]] || [[ -e hpr${ep_num}_summary.wav ]] || [[ -e hpr${ep_num}.wav ]] || [[ -e ${fname}_mez.wav ]] || [[ -e ${fname}_sox_norm.wav ]] || [[ -e ${fname}_mezzanine.wav ]] || [[ -e ${fname}_tmp.pcm ]] || [[ -e ${fname}_tmp.log ]] || [[ -e hpr${ep_num}_intro.wav ]] || [[ -e ~hpr${ep_num}_summary.wav ]] || [[ -e ~~hpr${ep_num}_summary.wav ]]
then
echo "Files for this episode already exist."
ls -1 *hpr${ep_num}* ${fname}_* 2>/dev/null
exit 1
fi
echo "--------------------------------------------------------------------------------"
echo "Geting metadata for hpr${ep_num}"
while read -r line
do
field=$(echo $line | awk -F ':' '{print $1}')
case $field in
"HPR_summary")
HPR_summary=$(echo $line | grep "HPR_summary: " | cut -c 14- )
continue
;;
"HPR_album")
HPR_album=$(echo $line | grep "HPR_album: " | cut -c 12- )
continue
;;
"HPR_artist")
HPR_artist=$(echo $line | grep "HPR_artist: " | cut -c 13- )
continue
;;
"HPR_comment")
HPR_comment=$(echo $line | grep "HPR_comment: " | cut -c 14- )
continue
;;
"HPR_genre")
HPR_genre=$(echo $line | grep "HPR_genre: " | cut -c 12- )
continue
;;
"HPR_title")
HPR_title=$(echo $line | grep "HPR_title: " | cut -c 12- )
continue
;;
"HPR_track")
HPR_track=$(echo $line | grep "HPR_track: " | cut -c 12- )
continue
;;
"HPR_year")
HPR_year=$(echo $line | grep "HPR_year: " | cut -c 11- )
continue
;;
"HPR_duration")
HPR_duration=$(echo $line | grep "HPR_duration: " | cut -c 15- )
continue
;;
"HPR_explicit")
HPR_explicit=$(echo $line | grep "HPR_explicit: " | cut -c 15- )
continue
;;
"HPR_license")
HPR_license=$(echo $line | grep "HPR_license: " | cut -c 14- )
continue
;;
esac
done < <( wget --timeout=10 --tries=1 --quiet http://hackerpublicradio.org/say.php?id=${ep_num} -O - )
if [[ -z "$HPR_album" || -z "$HPR_artist" || -z "$HPR_comment" || -z "$HPR_genre" || -z "$HPR_title" || -z "$HPR_track" || -z "$HPR_year" || -z "$HPR_summary" || -z "$HPR_duration" || -z "$HPR_explicit" || -z "$HPR_license" ]]
then
echo "Could not find information on ${ep_num}. Has the show been posted ?"
exit;
fi
echo "--------------------------------------------------------------------------------"
echo "album : $HPR_album"
echo "artist : $HPR_artist"
echo "comment : $HPR_comment"
echo "genre : $HPR_genre"
echo "title : $HPR_title"
echo "track : $HPR_track"
echo "year : $HPR_year"
echo "summary : $HPR_summary"
echo "duration : $HPR_duration"
echo "explicit : $HPR_explicit"
echo "license : $HPR_license"
if [[ $HPR_duration == "0" ]]
then
echo "The duration is set to 0. Please update the show with the correct time."
exit;
fi
#============================================================
# Preproc`s the source file
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Prepare mezzanine file" | tee -a ${fname}_tmp.log
ffmpeg -i ${mediafile} -ar 44100 -ac $CHANNELS ${fname}_mezzanine.wav > ${fname}_tmp.log 2>&1
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Add HPR Branding" | tee -a ${fname}_tmp.log
echo "Creating the summary" | tee -a ${fname}_tmp.log
#echo "$HPR_summary" - | text2wave -F 44100 - -o hpr${ep_num}_summary.wav #festival --tts
#echo "$HPR_summary" - | espeak -w ~hpr${ep_num}_summary.wav
echo "$HPR_summary" - | curl -X 'GET' -G --data-urlencode "voice=coqui-tts:en_ljspeech" --data-urlencode "text=${HPR_summary}" --data-urlencode "vocoder=high" --data-urlencode "denoiserStrength=0.03" --data-urlencode "cache=false" ${ttsserver}/api/tts -H 'accept: */*' --output ~hpr${ep_num}_summary.wav
ffmpeg -i ~hpr${ep_num}_summary.wav -ar 44100 ~~hpr${ep_num}_summary.wav >> ${fname}_tmp.log 2>&1
sox -n -r 44100 -c 1 silence.wav trim 0.0 6.0 >> ${fname}_tmp.log 2>&1
sox silence.wav ~~hpr${ep_num}_summary.wav hpr${ep_num}_summary.wav >> ${fname}_tmp.log 2>&1
echo "Adding the theme" | tee -a ${fname}_tmp.log
sox -m "hpr${ep_num}_summary.wav" "${theme}" "hpr${ep_num}_intro.wav" >> ${fname}_tmp.log 2>&1
echo "Creating the sandwitch" | tee -a ${fname}_tmp.log
sox "hpr${ep_num}_intro.wav" "${fname}_mezzanine.wav" "${outro}" "hpr${ep_num}.wav" >> ${fname}_tmp.log 2>&1
echo "Normalizing the wav files" | tee -a ${fname}_tmp.log
#sox --temp "${TEMP_DIR}" --norm hpr${ep_num}.wav hpr${ep_num}_norm.wav
ffmpeg -i hpr${ep_num}.wav -af loudnorm=I=-16:LRA=11:TP=-1.5 hpr${ep_num}_norm.wav >> ${fname}_tmp.log 2>&1
mv -v hpr${ep_num}_norm.wav ${upload_dir}/hpr${ep_num}.wav >> ${fname}_tmp.log 2>&1
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "File information" | tee -a ${fname}_tmp.log
ffprobe ${upload_dir}/hpr${ep_num}.wav 2>&1 | grep Audio:
mediainfo ${upload_dir}/hpr${ep_num}.wav
audio2image.bash ${upload_dir}/hpr${ep_num}.wav
xdg-open ${upload_dir}/hpr${ep_num}.png >/dev/null 2>&1 &
read -p "Processed Waveform look ok ? (N|y) ? " -n 1 -r
echo # (optional) move to a new line
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo "skipping...."
exit
fi
rm -v ${upload_dir}/hpr${ep_num}.png
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to opus" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.opus 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to flac" | tee -a ${fname}_tmp.log
sox --temp "${TEMP_DIR}" -S ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.flac 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to mp3" | tee -a ${fname}_tmp.log
sox --temp "${TEMP_DIR}" -S ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.mp3 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to ogg" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.ogg 2>> ${fname}_tmp.log 1>&2
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Convert to spx" | tee -a ${fname}_tmp.log
ffmpeg -y -i ${upload_dir}/hpr${ep_num}.wav ${upload_dir}/hpr${ep_num}.spx 2>> ${fname}_tmp.log 1>&2
for check_file in \
${upload_dir}/hpr${ep_num}.wav \
${upload_dir}/hpr${ep_num}.opus \
${upload_dir}/hpr${ep_num}.flac \
${upload_dir}/hpr${ep_num}.mp3 \
${upload_dir}/hpr${ep_num}.ogg \
${upload_dir}/hpr${ep_num}.spx
do
if [[ ! -s "${check_file}" ]]
then
echo "ERROR: Something went wrong encoding of the file \"${check_file}\""
fi
done
if [[ ! -s ${upload_dir}/hpr${ep_num}.wav ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.opus ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.flac ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.mp3 ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.ogg ]] || [[ ! -s ${upload_dir}/hpr${ep_num}.spx ]]
then
echo "ERROR: Something went wrong encoding the files"
exit 1
fi
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Fixing Tags" | tee -a ${fname}_tmp.log
fix_tags -album="$HPR_album" -artist="$HPR_artist" -comment="${HPR_comment} The license is ${HPR_license}" -genre="$HPR_genre" -title="$HPR_title" -track="$HPR_track" -year="$HPR_year" ${upload_dir}/hpr${ep_num}* 2>> ${fname}_tmp.log 1>&2
fix_tags ${upload_dir}/hpr${ep_num}*
#echo "Changing the file dates to the time of upload"
touch -r ${mediafile} hpr${ep_num}*
touch -r ${mediafile} ${upload_dir}/hpr${ep_num}*
ls -al hpr${ep_num}* ${upload_dir}/hpr${ep_num}* | tee -a ${fname}_tmp.log
echo "--------------------------------------------------------------------------------" | tee -a ${fname}_tmp.log
echo "Transferring files to Servers" | tee -a ${fname}_tmp.log
rsync -ave ssh --partial --progress --ignore-existing ${upload_dir}/hpr${ep_num}.mp3 ${upload_dir}/hpr${ep_num}.ogg ${upload_dir}/hpr${ep_num}.spx hpr:www/eps/
firefox "https://hackerpublicradio.org/local/hpr${ep_num}.mp3" >/dev/null 2>&1 &
firefox "https://hackerpublicradio.org/local/hpr${ep_num}.ogg" >/dev/null 2>&1 &
firefox "file://${upload_dir}/hpr${ep_num}.mp3" >/dev/null 2>&1 &
firefox "file://${upload_dir}/hpr${ep_num}.ogg" >/dev/null 2>&1 &
firefox "https://hackerpublicradio.org/eps.php?id=${ep_num}" >/dev/null 2>&1 &
mpv "http://hackerpublicradio.org/local/hpr${ep_num}.spx" "http://hackerpublicradio.org/local/hpr${ep_num}.ogg" "http://hackerpublicradio.org/local/hpr${ep_num}.mp3" "file://${upload_dir}/hpr${ep_num}.spx" "file://${upload_dir}/hpr${ep_num}.opus" "file://${upload_dir}/hpr${ep_num}.ogg" "file://${upload_dir}/hpr${ep_num}.mp3" "file://${upload_dir}/hpr${ep_num}.flac"
echo "Source: $( mediainfo --Output=XML --Full "${mediafile}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
mediainfo --Output=XML --Full hpr${ep_num}* ${upload_dir}/hpr${ep_num}* | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' | sort | uniq -c
read -p "Remove files for \"${fname}\" (y|N) ? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
mediafilename=$(basename "${mediafile}")
mediaextension="${mediafilename##*.}" # "
ssh hpr -t "mkdir /home/hpr/www/eps/hpr${ep_num}" >/dev/null 2>&1
rsync -ave ssh --partial --progress --ignore-existing "${mediafile}" hpr:www/eps/hpr${ep_num}/hpr${ep_num}_source.${mediaextension}
ssh hpr -t "ls -al /home/hpr/www/eps/hpr${ep_num}*"
cp -v "${mediafile}" "${upload_dir}/hpr${ep_num}_source.${mediaextension}"
#echo "Remove temp files"
rm -v ~hpr${ep_num}_summary.wav ~~hpr${ep_num}_summary.wav silence.wav
rm -v ${fname}_mezzanine.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav
#mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1" -O - | xmlstarlet val --err -
wget --timeout=0 -q "http://hackerpublicradio.org/rss-future.php" -O - | xmlstarlet val --err -
rsync -ave ssh --partial --progress ${upload_dir}/hpr${ep_num}* borg:/data/IA/uploads/
echo "$( ssh borg -t "ls -al /data/IA/uploads/hpr${ep_num}*" ; ls -al ${upload_dir}/hpr${ep_num}* )" | grep "hpr${ep_num}" | awk '{print $5, $NF}' | sort
# SHOW_SUBMITTED → METADATA_PROCESSED → SHOW_POSTED → MEDIA_TRANSCODED → UPLOADED_TO_IA → UPLOADED_TO_RSYNC_NET
curl --netrc-file ${HOME}/.netrc "https://hackerpublicradio.org/cms/status.php?ep_num=${ep_num}&status=MEDIA_TRANSCODED"
else
echo "skipping...."
echo "cp -v \"${mediafile}\" \"${upload_dir}/hpr${ep_num}_source.${mediaextension}\""
echo "rm -v ${fname}_mezzanine.wav ${fname}_tmp*.pcm ${fname}_tmp.log ${fname}_mez.wav"
#echo "mv -v ${fname}* hpr${ep_num}* *_${ep_num}_* /var/IA/done/"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_ogg_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_mp3_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "wget --timeout=0 -q \"http://hackerpublicradio.org/hpr_spx_rss.php?gomax=1\" -O - | xmlstarlet val --err -"
echo "rsync -ave ssh --partial --progress ${upload_dir}/ borg:/data/IA/uploads/"
fi

501
workflow/rss-2_0.xsd Normal file
View File

@ -0,0 +1,501 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
XML Schema for RSS v2.0
Copyright (C) 2003-2008 Jorgen Thelin
Microsoft Public License (Ms-PL)
This license governs use of the accompanying software.
If you use the software, you accept this license.
If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
version="2.0.2.16">
<xs:annotation>
<xs:documentation>XML Schema for RSS v2.0 feed files.</xs:documentation>
<xs:documentation>Project home: http://www.codeplex.com/rss2schema/ </xs:documentation>
<xs:documentation>Based on the RSS 2.0 specification document at http://cyber.law.harvard.edu/rss/rss.html </xs:documentation>
<xs:documentation>Author: Jorgen Thelin</xs:documentation>
<xs:documentation>Revision: 16</xs:documentation>
<xs:documentation>Date: 01-Nov-2008</xs:documentation>
<xs:documentation>Feedback to: http://www.codeplex.com/rss2schema/WorkItem/List.aspx </xs:documentation>
</xs:annotation>
<xs:element name="rss">
<xs:complexType>
<xs:sequence>
<xs:element name="channel" type="RssChannel"/>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="version" type="xs:decimal" use="required" fixed="2.0"/>
<xs:anyAttribute namespace="##any"/>
</xs:complexType>
</xs:element>
<xs:complexType name="RssItem">
<xs:annotation>
<xs:documentation>An item may represent a "story" -- much like a story in a newspaper or magazine; if so its description is a synopsis of the story, and the link points to the full story. An item may also be complete in itself, if so, the description contains the text (entity-encoded HTML is allowed), and the link and title may be omitted.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:choice maxOccurs="unbounded">
<xs:element name="title" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>The title of the item.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="description" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>The item synopsis.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="link" type="xs:anyURI" minOccurs="0">
<xs:annotation>
<xs:documentation>The URL of the item.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="author" type="EmailAddress" minOccurs="0">
<xs:annotation>
<xs:documentation>Email address of the author of the item.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="category" type="Category" minOccurs="0">
<xs:annotation>
<xs:documentation>Includes the item in one or more categories. </xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="comments" type="xs:anyURI" minOccurs="0">
<xs:annotation>
<xs:documentation>URL of a page for comments relating to the item.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="enclosure" type="Enclosure" minOccurs="0">
<xs:annotation>
<xs:documentation>Describes a media object that is attached to the item.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="guid" type="Guid" minOccurs="0">
<xs:annotation>
<xs:documentation>guid or permalink URL for this entry</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="pubDate" type="Rfc822FormatDate" minOccurs="0">
<xs:annotation>
<xs:documentation>Indicates when the item was published.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="source" type="Source" minOccurs="0">
<xs:annotation>
<xs:documentation>The RSS channel that the item came from.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Extensibility element.</xs:documentation>
</xs:annotation>
</xs:any>
</xs:choice>
</xs:sequence>
<xs:anyAttribute namespace="##any"/>
</xs:complexType>
<xs:complexType name="RssChannel">
<xs:sequence>
<xs:choice maxOccurs="unbounded">
<xs:element name="title" type="xs:string">
<xs:annotation>
<xs:documentation>The name of the channel. It's how people refer to your service. If you have an HTML website that contains the same information as your RSS file, the title of your channel should be the same as the title of your website.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="link" type="xs:anyURI">
<xs:annotation>
<xs:documentation>The URL to the HTML website corresponding to the channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="description" type="xs:string">
<xs:annotation>
<xs:documentation>Phrase or sentence describing the channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="language" type="xs:language" minOccurs="0">
<xs:annotation>
<xs:documentation>The language the channel is written in. This allows aggregators to group all Italian language sites, for example, on a single page. A list of allowable values for this element, as provided by Netscape, is here. You may also use values defined by the W3C.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="copyright" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>Copyright notice for content in the channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="managingEditor" type="EmailAddress" minOccurs="0">
<xs:annotation>
<xs:documentation>Email address for person responsible for editorial content.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="webMaster" type="EmailAddress" minOccurs="0">
<xs:annotation>
<xs:documentation>Email address for person responsible for technical issues relating to channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="pubDate" type="Rfc822FormatDate" minOccurs="0">
<xs:annotation>
<xs:documentation>The publication date for the content in the channel. All date-times in RSS conform to the Date and Time Specification of RFC 822, with the exception that the year may be expressed with two characters or four characters (four preferred).</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="lastBuildDate" type="Rfc822FormatDate" minOccurs="0">
<xs:annotation>
<xs:documentation>The last time the content of the channel changed.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="category" type="Category" minOccurs="0">
<xs:annotation>
<xs:documentation>Specify one or more categories that the channel belongs to.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="generator" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>A string indicating the program used to generate the channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="docs" type="xs:anyURI" minOccurs="0">
<xs:annotation>
<xs:documentation>A URL that points to the documentation for the format used in the RSS file. It's probably a pointer to this page. It's for people who might stumble across an RSS file on a Web server 25 years from now and wonder what it is.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="cloud" type="Cloud" minOccurs="0">
<xs:annotation>
<xs:documentation>Allows processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ttl" type="xs:nonNegativeInteger" minOccurs="0">
<xs:annotation>
<xs:documentation>ttl stands for time to live. It's a number of minutes that indicates how long a channel can be cached before refreshing from the source.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="image" type="Image" minOccurs="0">
<xs:annotation>
<xs:documentation>Specifies a GIF, JPEG or PNG image that can be displayed with the channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="rating" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>The PICS rating for the channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="textInput" type="TextInput" minOccurs="0">
<xs:annotation>
<xs:documentation>Specifies a text input box that can be displayed with the channel.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="skipHours" type="SkipHoursList" minOccurs="0">
<xs:annotation>
<xs:documentation>A hint for aggregators telling them which hours they can skip.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="skipDays" type="SkipDaysList" minOccurs="0">
<xs:annotation>
<xs:documentation>A hint for aggregators telling them which days they can skip.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Extensibility element.</xs:documentation>
</xs:annotation>
</xs:any>
</xs:choice>
<xs:element name="item" type="RssItem" minOccurs="1" maxOccurs="unbounded">
<!--
HACK: According to the RSS 2.0 spec, it should strictly be possible to have zero item elements,
but this makes the schema non-deterministic with regard to extensibility elements
so for the moment we undid bug-fix 10231 and set minOccurs=1 to work around this problem.
-->
</xs:element>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Extensibility element.</xs:documentation>
</xs:annotation>
</xs:any>
</xs:sequence>
<xs:anyAttribute namespace="##any"/>
</xs:complexType>
<xs:simpleType name="SkipHour">
<xs:annotation>
<xs:documentation>A time in GMT when aggregators should not request the channel data. The hour beginning at midnight is hour zero.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="23"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="SkipHoursList">
<xs:sequence>
<xs:element name="hour" type="SkipHour" minOccurs="0" maxOccurs="24"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="SkipDay">
<xs:annotation>
<xs:documentation>A day when aggregators should not request the channel data.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:enumeration value="Monday"/>
<xs:enumeration value="Tuesday"/>
<xs:enumeration value="Wednesday"/>
<xs:enumeration value="Thursday"/>
<xs:enumeration value="Friday"/>
<xs:enumeration value="Saturday"/>
<xs:enumeration value="Sunday"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="SkipDaysList">
<xs:sequence>
<xs:element name="day" type="SkipDay" minOccurs="0" maxOccurs="7">
<xs:annotation>
<xs:documentation>A time in GMT, when aggregators should not request the channel data. The hour beginning at midnight is hour zero.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Category">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="domain" type="xs:string" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="Image">
<xs:all>
<xs:element name="url" type="xs:anyURI">
<xs:annotation>
<xs:documentation>The URL of the image file.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="title" type="xs:string">
<xs:annotation>
<xs:documentation>Describes the image, it's used in the ALT attribute of the HTML &lt;img&gt; tag when the channel is rendered in HTML.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="link" type="xs:anyURI">
<xs:annotation>
<xs:documentation>The URL of the site, when the channel is rendered, the image is a link to the site. (Note, in practice the image &lt;title&gt; and &lt;link&gt; should have the same value as the channel's &lt;title&gt; and &lt;link&gt;. </xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="width" type="ImageWidth" default="88" minOccurs="0">
<xs:annotation>
<xs:documentation>The width of the image in pixels.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="height" type="ImageHeight" default="31" minOccurs="0">
<xs:annotation>
<xs:documentation>The height of the image in pixels.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="description" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>Text that is included in the TITLE attribute of the link formed around the image in the HTML rendering.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
<xs:simpleType name="ImageHeight">
<xs:annotation>
<xs:documentation>The height of the image in pixels.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:maxInclusive value="400"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ImageWidth">
<xs:annotation>
<xs:documentation>The width of the image in pixels.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:positiveInteger">
<xs:maxInclusive value="144"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="Cloud">
<xs:annotation>
<xs:documentation>Specifies a web service that supports the rssCloud interface which can be implemented in HTTP-POST, XML-RPC or SOAP 1.1. Its purpose is to allow processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds.</xs:documentation>
</xs:annotation>
<xs:attribute name="domain" type="xs:string" use="required"/>
<xs:attribute name="port" type="xs:positiveInteger" use="required"/>
<xs:attribute name="path" type="xs:string" use="required"/>
<xs:attribute name="registerProcedure" type="xs:string" use="required"/>
<xs:attribute name="protocol" type="CloudProtocol" use="required"/>
</xs:complexType>
<xs:simpleType name="CloudProtocol">
<xs:restriction base="xs:string">
<xs:enumeration value="xml-rpc"/>
<xs:enumeration value="http-post"/>
<xs:enumeration value="soap"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="TextInput">
<xs:annotation>
<xs:documentation>The purpose of this element is something of a mystery! You can use it to specify a search engine box. Or to allow a reader to provide feedback. Most aggregators ignore it.</xs:documentation>
</xs:annotation>
<xs:all>
<xs:element name="title" type="xs:string">
<xs:annotation>
<xs:documentation>The label of the Submit button in the text input area.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="description" type="xs:string">
<xs:annotation>
<xs:documentation>Explains the text input area.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="name" type="xs:string">
<xs:annotation>
<xs:documentation>The name of the text object in the text input area.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="link" type="xs:anyURI">
<xs:annotation>
<xs:documentation>The URL of the CGI script that processes text input requests.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
<xs:simpleType name="EmailAddress">
<xs:annotation>
<xs:documentation>Using the regexp definiton of E-Mail Address by Lucadean from the .NET RegExp Pattern Repository at http://www.3leaf.com/default/NetRegExpRepository.aspx </xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<!-- <xs:pattern value="([a-zA-Z0-9_\-])([a-zA-Z0-9_\-\.]*)@(\[((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}|((([a-zA-Z0-9\-]+)\.)+))([a-zA-Z]{2,}|(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\])"/>-->
<xs:pattern value="[A-Za-z0-9_]+([-+.'][A-Za-z0-9_]+)*@[A-Za-z0-9_]+([-.][A-Za-z0-9_]+)*\.[A-Za-z0-9_]+([-.][A-Za-z0-9_]+)*"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="Rfc822FormatDate">
<xs:annotation>
<xs:documentation>A date-time displayed in RFC-822 format.</xs:documentation>
<xs:documentation>Using the regexp definiton of rfc-822 date by Sam Ruby at http://www.intertwingly.net/blog/1360.html </xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="(((Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)), *)?\d\d? +((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec)) +\d\d(\d\d)? +\d\d:\d\d(:\d\d)? +(([+\-]?\d\d\d\d)|(UT)|(GMT)|(EST)|(EDT)|(CST)|(CDT)|(MST)|(MDT)|(PST)|(PDT)|\w)"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="Source">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="url" type="xs:anyURI"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="Enclosure">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="url" type="xs:anyURI" use="required">
<xs:annotation>
<xs:documentation>URL where the enclosure is located</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="length" type="xs:nonNegativeInteger" use="required">
<xs:annotation>
<xs:documentation>Size in bytes</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="type" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>MIME media-type of the enclosure</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="Guid">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="isPermaLink" type="xs:boolean" use="optional" default="true"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<!--
TODO:
- Need to add regexp pattern for MIME media-type value of tEnclosure/type
- Need to add regexp pattern for checking contents of guid is a URL when isPermaLink=true"
- Need to add some form of constraint to check on an item that one, or other, or both of title and description are present.
However, I'm not sure it is possible to represent these constraints in XML Schema language alone.
- Need some way to enforce cardinality constraints preventing repeated elements in channels or items
- Unfortunately the bug-fix for issue 10231 made this schema non-deterministic with respect to extensibitity elements.
We can't tell whether an extension element in tRssChannel is within the choice or after the item elements.
Need to reconsider the solution to bug-fix 10231.
-->
<!--
Change Log:
Date Revision Description
31-Mar-2003 1 Initial version released for comment
31-Mar-2003 2 Changes based on feedback from Gudge:
- Remove targetNamespace="" and use elemenfFormDefault="unqualified" instead
- Use namespace="##other" on <any>'s to create a more deterministic data model.
- Added missing xs:documentation inside xs:annotation at the schema level.
- Use xs:language for ISO Language Codes in <language> element.
- Change guid to a single declaration. This loses some of the checking of the
URL when the contents of the guid is a permaLink, so we will need to add
that back in with a regexp pattern.
14-Apr-2003 3 Changes to solve some element ordering problems.
- Use xs:all in place of xs:sequence to support flexible ordering of elements.
Although the ordering constraints for elements is not clear from the
original specification, the custom and practice seems to be that
element ordering is freeform.
- Use elemenfFormDefault="qualified" for explicit intent.
15-Apr-2003 4 Changes to solve some element ordering problems.
- Use xs:choice in place of xs:all as previous usage of <all> was invalid.
This creates the problem that unsufficient constraints can be applied
by the schema - for example, it can't prevent two title elements for an item.
- Use elemenfFormDefault="unqualified" for to get the correct behavious
when importing and combining schemas.
15-Apr-2003 5 Putting the extensibility element inside the repeating choice solves
all problems with element ordering.
15-Apr-2003 6 - skipHours and skipDays should contain a nested list of values,
not just a single value.
- Added version attribute to schema definition.
- Corrected type of the cloud element
25-Apr-2003 7 - Add regexp for RFC-822 date suggested by Sam Ruby
- I had to leave the base type of the tRfc822FormatDate type
as xs:string due to the problems with using
a pattern with xs:dateTime described at
http://www.thearchitect.co.uk/weblog/archives/2003/04/000142.html
19-Jun-2003 8 - Fixed a bug the Oxygen XML Editor spotted in the regexp for RFC-822 dates
23-Jun-2003 9 - Added legal boilerplate license text for LGPL.
- Minor formatting changes.
24-Jun-2003 10 - Missing types for item/title and item/description - Spotted by Andreas Schwotzer.
01-Jan-2008 11 - Copy made available under the Microsoft Public License (MS-PL).
25-May-2008 12 - Bug fix 10231 from Ken Gruven - channel can contain zero or more items.
06-Sep-2008 13 - Fixed tab-space whitespace issues. Now always use spaces.
- Undid the fix for bug-fix 10231 since it made the schema non-deterministic
with respect to extensibility eleemnts in tRssChannel - need to reconsider the fix.
08-Sep-2008 14 - Removed 't' prefixes from type names to improve class names
that get code-generated from the schema.
22-Sep-2008 15 - Move type def for rss element in-line for improved compativility with Java 1.6 tools.
01-Nov-2008 16 - Added the missing rating element from the spec to RssChannel.
-->
</xs:schema>

60
workflow/show2youtube.bash Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
# http://eddmann.com/posts/uploading-podcast-audio-to-youtube/
# http://cutycapt.sourceforge.net/ xvfb-run
# https://el-tramo.be/blog/ken-burns-ffmpeg/
#hpr${ep_num}.wav
ep_num=2463
xvfb-run --server-args="-screen 0, 1024x768x24" CutyCapt --url="http://hackerpublicradio.org/eps.php?id=${ep_num}" --out=${ep_num}.png --insecure
ffmpeg -i input.wav -filter_complex "[0:a]showwaves=s=800x600:mode=line:rate=25,format=yuv420p[v]" -map "[v]" -map 0:a output-showwaves.mp4
# ffmpeg -start_number n -i test_%d.jpg -vcodec mpeg4 test.avi
# ffmpeg -loop 1 -r 2 -i image.jpg -i input.mp3 -vf scale=-1:380 -c:v libx264 -preset slow -tune stillimage -crf 18 -c:a copy -shortest -pix_fmt yuv420p -threads 0 output.mkv
# https://video.stackexchange.com/questions/9644/how-do-i-turn-audio-into-video-that-is-show-the-waveforms-in-a-video
#this
#
# rm out.mp4 output.mp4 ; ffmpeg -i 2463.png -filter_complex "pad=w=9600:h=6000:x='(ow-iw)/2':y='(oh-ih)/2',zoompan=x='(iw-0.625*ih)/2':y='(1-on/(25*4))*(ih-ih/zoom)':z='if(eq(on,1),2.56,zoom+0.002)':d=25*4:s=1280x800" -pix_fmt yuv420p -c:v libx264 out.mp4
#
# rm out.mp4 output.mp4; ffmpeg -i 2463.png -filter_complex "pad=w=9600:h=6000:x='(ow-iw)/2':y='(oh-ih)/2',zoompan=z='zoom+0':d=25*4:s=1280x2048,crop=w=1280:h=800:x='(iw-ow)/2':y='(ih-oh)/2' " -pix_fmt yuv420p -c:v libx264 out.mp4
#
# ffmpeg -i in.jpg
# -filter_complex
# "zoompan=z='zoom+0.002':d=25*4:s=1280x800"
# -pix_fmt yuv420p -c:v libx264 out.mp4
#
#
#
# ffmpeg -i input.flac -filter_complex "[0:a]ahistogram,format=yuv420p[v]" -map "[v]" -map 0:a output.mp4
#
#
# ffmpeg -i input -i background.png -filter_complex "[0:a]showwavespic=s=640x240[fg];[1:v][top]overlay=format=auto" -frames:v 1 output.png
ffmpeg -loop 1 -i background.png -i video1.mp4 -i video2.mp4 -filter_complex \
"[1:v]scale=(iw/2)-20:-1[a]; \
[2:v]scale=(iw/2)-20:-1[b]; \
[0:v][a]overlay=10:(main_h/2)-(overlay_h/2):shortest=1[c]; \
[c][b]overlay=main_w-overlay_w-10:(main_h/2)-(overlay_h/2)[video]" \
-map "[video]" output.mkv
https://stackoverflow.com/questions/13390714/superimposing-two-videos-onto-a-static-image
https://video.stackexchange.com/questions/14519/add-image-under-the-video-with-ffmepg
ffmpeg \
-loop 1 -i hprback.png \
-i output-showwaves-320x240.mp4 \
-filter_complex "overlay=0:0:shortest=1" \
out.m4v
# ffmpeg -i input.wav -filter_complex "[0:a]showfreqs=s=320x266:mode=line:fscale=log,format=yuv420p[v]" -map "[v]" -map 0:a output-showfreqs-319x266.mp4
# ffmpeg -loop 1 -i hprback.png -i output-showfreqs-319x266.mp4 -filter_complex "overlay=663:143:shortest=1" out1.m4v

View File

@ -0,0 +1,233 @@
#!/usr/bin/env python3
#
# Copyright (C) 2017 Ken Fallon ken@fallon.ie
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# UTF-8 Test >> ÇirçösÚáóíéőöÓÁśł <<
# 213.46.252.136 gateway
import sys, getopt, json, os, logging, sys, requests, datetime, re, dateutil.parser, base64, xmltodict, random, psycopg2, time, automationhat
from pprint import pprint
#import traceback
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
#logging.disable(logging.DEBUG)
settings = dict()
def usage(exitcode, message = None):
'''
prints usage and exits
'''
if message is not None:
logging.error( message )
print( os.path.basename(sys.argv[0]) + ' [--help -l <lab> -c <config> ]')
sys.exit(exitcode)
def get_lab_settings(settings):
'''
Get configuration for the lab, usually from http://172.30.218.244/autotest/settings.json
'''
if os.path.isfile( settings[ 'config_url' ] ):
with open( settings[ 'config_url' ] ) as json_file:
info_dict = json.load(json_file)
return info_dict
else:
session = requests.session()
resp = session.get(url=settings[ 'config_url' ], headers={'Accept-Encoding': 'gzip'})
if resp.status_code == 200:
info_dict = json.loads(resp.text)
return info_dict
return None
def argumentTest():
'''
Gets the required paramaters that the program needs in order to run. These can either be provided on the command line, or as environemental variables, or both.
export LAB="lab4b"
export config_url="http://172.30.218.244/autotest/settings.json"
export service="purcha#!/usr/bin/env python
import automationhat
import time
import os
import sys
counter = -1
lightPattern = [
[1,0,0],
[0,1,0],
[0,0,1],
[0,1,1],
[0,1,0],
[1,1,0],
[1,0,0],
[1,1,0],
[0,1,0],
[0,1,1],
[0,0,1],
[0,0,0],
[1,0,1],
[0,1,0],
[0,0,0],
]
def increment():
global counter
counter+=1
if counter==len(lightPattern):
counter = 0
def doCurrentCycle():
currentCycle = lightPattern[counter]
print currentCycle
if (currentCycle[0]==1) :
automationhat.output.one.on()
else:
automationhat.output.one.off()
if (currentCycle[1] == 1):
automationhat.output.two.on()
else:
automationhat.output.two.off()
if (currentCycle[2] == 1):
automationhat.output.three.on()
else:
automationhat.output.three.off()
time.sleep(2)
while True:
increment()
doCurrentCycle()se-service"
export customers="customers.json"
'''
lab = str()
config_url = str()
service = str()
settings = dict()
for evariable in [ 'LAB', 'config_url' ]:
if os.getenv( evariable ) is not None:
settings[ evariable ] = os.environ[ evariable ]
logging.debug( "Setting \"" + evariable + "\" set to \"" + settings[ evariable ] + "\" from an enviroment variable.")
try:
options, remainder = getopt.getopt(sys.argv[1:], 'hl:c:', [ 'help', 'lab=', 'config=' ])
except getopt.GetoptError:
usage(1, "Unrecognized option was found or missing argument." )
for opt, arg in options:
if opt in ('-h', '--help'):
usage(2)
elif opt in ('-l', '--lab'):
logging.debug( "Setting \"LAB\" set to \"" + arg + "\" from the \"" + opt + "\" command line argument.")
settings[ 'LAB' ] = arg
elif opt in ('-c', '--config'):
logging.debug( "Setting \"config_url\" set to \"" + arg + "\" from the \"" + opt + "\" command line argument.")
settings[ 'config_url' ] = arg
for param in [ 'LAB', 'config_url' ]:
if not param in settings:
usage( 3, "Cant find value for \"" + param +"\"")
try:
settings[ 'config' ] = get_lab_settings( settings )[ settings[ 'LAB' ] ]
if 'jenkins' in settings.keys():
logging.debug('Updating TraxIS Customer file to include the Purchase Service status.')
except Exception as e:
print('Error: Could not get Lab Configuration for "%s".' % settings[ 'LAB' ] )
traceback.print_exc()
exit(1)
return settings
#def get_server_instance():
#jenkins_url = 'http://172.22.137.160:8080'
#server = Jenkins(jenkins_url, username='autotest', password='autotest')
#return server
def get_server_instance(settings):
jenkins_url = 'http://%s:%s' % ( settings[ 'config' ][ 'jenkins' ][ 'host' ] , settings[ 'config' ][ 'jenkins' ][ 'port' ] )
server = Jenkins(jenkins_url, username = settings[ 'config' ][ 'jenkins' ][ 'user' ], password = settings[ 'config' ][ 'jenkins' ][ 'password' ])
return server
def alloff():
automationhat.relay.one.off()
automationhat.output.one.off()
automationhat.output.two.off()
automationhat.output.three.off()
# Set according to status
def setstatus(status):
if status != "":
alloff()
if status == "SUCCESS":
automationhat.output.three.on()
print( "SUCCESS" )
if status == "UNSTABLE":
automationhat.output.two.on()
print( "UNSTABLE" )
if status == "FAILURE":
automationhat.output.one.on()
print( "FAILURE" )
return
def get_job_details( settings ):
# Refer Example #1 for definition of function 'get_server_instance'
server = get_server_instance( settings )
settings[ 'teststatus' ] = str()
for job in settings[ 'config' ][ 'jenkins' ][ 'jobs' ]:
logging.debug( "Checking job %s" % job )
job_instance = server.get_job( job )
this_status = job_instance.get_last_build().get_status()
if not ( settings[ 'teststatus' ] == "FAILURE" or (settings[ 'teststatus' ] == "UNSTABLE" and this_status == "SUCCESS") ):
settings[ 'teststatus' ] = this_status
return settings
if __name__ == "__main__":
logging.debug( "start" )
try:
while True:
alloff()
automationhat.relay.one.on()
settings = argumentTest()
#pprint( settings[ 'config' ][ 'jenkins' ] )
#print( "Hello" )
#pprint ( get_server_instance( settings ).version )
settings = get_job_details( settings )
status = settings[ 'teststatus' ]
setstatus(status)
#print( get_server_instance().version )
time.sleep(300)
except Exception as e:
print("Error: " + str(e))
traceback.print_exc()
exit(1)
logging.debug('done')
'''
export LAB="lab4b"
export config_url="http://172.30.218.244/autotest/settings.json"
'''

18
workflow/this_duration.bash Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/
find ${pwd} -type f | while read mediafile
do
duration=$( mediainfo --full --Output=XML "${mediafile}" | xmlstarlet sel -T -t -m "_:MediaInfo/_:media/_:track[@type='Audio']/_:Duration[1]" -v "." -n - | awk -F '.' '{print $1}' )
if [ "${duration}" != "" ]
then
echo "${duration} ${mediafile}"
continue
fi
duration=$( /bin/date -ud "1970-01-01 $( ffprobe -i "${mediafile}" 2>&1| awk -F ': |, ' '/Duration:/ { print $2 }' )" +%s )
if [ "${duration}" != 0 ]
then
echo "${duration} ${mediafile}"
continue
fi
done
find -type f -exec file {} \; | grep -vEi 'audio|mpeg|video'

638
workflow/transfer_tags Executable file
View File

@ -0,0 +1,638 @@
#!/usr/bin/perl
#===============================================================================
#
# FILE: transfer_tags
#
# USAGE: ./transfer_tags masterfile
#
# DESCRIPTION: Transfer ID3 (or equivalent) tags from a base file to
# whichever of FLAC, MP3, OGG, SPX and WAV versions are found.
#
# OPTIONS: ---
# REQUIREMENTS: ---
# BUGS: ---
# NOTES: ---
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
# VERSION: 1.4.2
# CREATED: 31/03/2013 14:18:55
# REVISION: 24/05/2013 15:54:36
#
#===============================================================================
use 5.010;
use strict;
use warnings;
use File::Basename;
use File::Find::Rule;
use Audio::TagLib;
#
# Version number (manually incremented)
#
our $VERSION = '1.4.2';
#
# Declarations
#
my ( $ref, $tag, $tags, %mastertags, %slavetags, $changed );
my ( $directories, $filename, $suffix );
#
# The extensions the various codings are expected to have (it looks both for
# the lower- and upper-case versions, but only the lower-case ones are
# required here)
#
my @exts = qw{ flac mp3 ogg spx wav };
#
# The additional name variants we'll accept
#
my @variants = qw{ _mez };
#
# Used to test tag conformity. The key is the tagname, the value is a hashref
# containing a regex (key 're') for detecting conformity and the correct value
# (key 'ok').
# To be passed to subroutine 'checkConformity', but kept here for ease of
# maintenance.
#
my %tag_control = (
album => { re => qr{^Hacker Public Radio$}, ok => 'Hacker Public Radio' },
comment => {
re => qr{^http://hackerpublicradio\.org/?},
ok => 'http://hackerpublicradio.org'
},
genre => { re => qr{(?i)^Podcast$}, ok => 'Podcast' },
);
#
# The Audio::TagLib methods to call for each tag manipulated by the script.
# The number after the method name is 1 if the value being set is a string,
# and zero otherwise.
#
my %tagmethods = (
album => [ 'setAlbum', 1 ],
artist => [ 'setArtist', 1 ],
comment => [ 'setComment', 1 ],
genre => [ 'setGenre', 1 ],
title => [ 'setTitle', 1 ],
track => [ 'setTrack', 0 ],
year => [ 'setYear', 0 ],
);
#
# Because Audio::TagLib::FileRef does not seem to commit the tag update until
# very late (during the DESTROY?) it's very difficult to update file times
# _after_ the tags have been written. The solution is to save all of the tag
# hashes (which contain the file path and times) and process then in the END{}
# block. Dirty, but very Perl-ish.
#
my @tag_stash;
#
# Script name
#
( my $PROG = $0 ) =~ s|.*/||mx;
#
# Ensure STDOUT is in UTF8 mode
#
binmode( STDOUT, ":encoding(utf8)" );
#
# Get the argument, the "master" file
#
my $masterfile = shift;
die "Usage: $PROG masterfilename\n" unless $masterfile;
#
# Check the file exists
#
die "$masterfile does not exist\n" unless ( -e $masterfile );
#
# Assume there are other versions of the same file with suffixes in the set
# { flac, mp3, ogg, spx, wav } so build these names from the master file name.
# Start by parsing the filename into its directories, filename and suffix.
# Remove the leading dot from the suffix.
#
($filename,$directories,$suffix) = fileparse($masterfile,qr/\.[^.]*/);
$suffix =~ s/^\.//;
#
# Reject the file if it doesn't have an expected suffix (we're not
# case-sensitive here)
#
die "$masterfile does not have a recognised suffix (expecting "
. join( ", ", @exts ) . ")\n"
unless ( grep {/$suffix/i} @exts );
#
# Use File::Find::Rule to find all the files that match a regular expression.
# This is built from the parsed file, with an optional list of variants and
# all of the extensions (case insensitively). We then remove the master file
# from the list and we're done.
#
my $re
= "^${filename}("
. join( '|', @variants ) . ")?\.(?i:"
. join( '|', @exts ) . ')$';
my @files = grep { !/^$masterfile$/ }
File::Find::Rule->file()->name(qr{$re})->in($directories);
#
# Log the file
#
print "$masterfile\n";
#
# Collect the tags from the master file (& stash them for later too)
#
( $ref, $tag, $tags ) = collectTags($masterfile);
%mastertags = %$tags;
push( @tag_stash, $tags );
#
# Report the tags found in the master file
#
reportTags( \%mastertags );
#
# Check that the master file conforms to the HPR standards, ensuring that any
# changes are returned from the routine
#
( $ref, $tag, $tags ) = checkConformity( $ref, $tag, $tags, \%tag_control );
%mastertags = %$tags;
print '=' x 80, "\n";
#
# Now process the "slave" files
#
foreach my $file (@files) {
#
# Check the "slave" file exists and report it if so
#
if ( -r $file ) {
print "$file\n";
}
else {
warn "$file is not readable\n";
next;
}
#
# Get the "slave" file's tags (& keep a copy in the stash)
#
( $ref, $tag, $tags ) = collectTags($file);
%slavetags = %$tags;
push( @tag_stash, $tags );
#
# Report the tags
#
reportTags( \%slavetags );
#
# Change the tags to match the "master" file's tags
#
$changed = 0;
for my $t ( sort( grep { !/^_/ } keys(%mastertags) ) ) {
$changed += changeTag( $tag, $t, $slavetags{$t}, $mastertags{$t},
@{ $tagmethods{$t} } );
}
print '-' x 80, "\n";
#
# Save any changes
#
if ($changed) {
$ref->save();
}
}
exit;
#-------------------------------------------------------------------------------
# Post-processing of file times
#
# Process all files we've visited (whether their tags were changed or not)
# and force the 'atime' and 'mtime' back to their starting values. This will
# be done after the rest of the script runs, when we know that all of the
# Audio::TagLib::FileRef objects have been destroyed and have done their lazy
# updates.
#-------------------------------------------------------------------------------
END {
for my $t (@tag_stash) {
warn "Time restoration failed on $t->{_path}\n"
unless restoreTimes($t);
}
}
#=== FUNCTION ================================================================
# NAME: collectTags
# PURPOSE: Collects tags from a media file
# PARAMETERS: $filepath Path to the file
# RETURNS: A list containing an Audio::TagLib::FileRef object, an
# Audio::TagLib::Tag object (containing the actual tags) and
# a hashref containing the converted tag values (along with
# a few other attributes).
# DESCRIPTION: Collects the tags and the timestamps from the file. Then the
# various tags and other attributes are placed in a hash which
# will be returned to the caller. The non-tag keys begin with
# '_' to differentiate them.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub collectTags {
my ($filepath) = @_;
my ( $atime, $mtime ) = ( stat($filepath) )[ 8, 9 ];
my $fileref = Audio::TagLib::FileRef->new($filepath);
my $ftag = $fileref->tag();
my %tags = (
album => $ftag->album()->toCString(),
artist => $ftag->artist()->toCString(),
comment => $ftag->comment()->toCString(),
genre => $ftag->genre()->toCString(),
title => $ftag->title()->toCString(),
track => $ftag->track(),
year => $ftag->year(),
_path => $filepath,
_atime => $atime,
_mtime => $mtime,
);
return ( $fileref, $ftag, \%tags );
}
#=== FUNCTION ================================================================
# NAME: reportTags
# PURPOSE: Print the tags in a hash
# PARAMETERS: $tags Hashref keyed by tagname and containing tag
# contents from a media file
# RETURNS: Nothing
# DESCRIPTION: Just prints all the "proper" tags held in the hash argument in
# alphabetical order of the keys. Note that the "secret" keys,
# those begining with '_', are skipped. See 'collectTags' for
# what they are.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub reportTags {
my ($tags) = @_;
my @keys = sort( grep { !/^_/ } keys(%$tags) );
for my $key (@keys) {
printf "%-10s: %s\n", $key, $tags->{$key};
}
return;
}
#=== FUNCTION ================================================================
# NAME: changeTag
# PURPOSE: Changes a tag to a new value if appropriate
# PARAMETERS: $tag Audio::TagLib::Tag object
# $tagname Name of tag
# $oldValue Current value of tag
# $newValue New value of tag or undefined
# $setFunc String containing the name of the 'set'
# function
# $isString True if the value being set is a string
# RETURNS: 1 if a change has been made, 0 otherwise
# DESCRIPTION: Performs some argument checks, returning on a missing new
# value, or if the old and new values are the same. The old and
# new values may be encoded integers, so we look for this
# eventuality. After all of this we know there's a change to be
# made and perform the appropriate steps to make it.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub changeTag {
my ( $tag, $tagname, $oldValue, $newValue, $setFunc, $isString ) = @_;
return 0 unless defined($newValue);
return 0 if $oldValue eq $newValue;
$isString = 0 unless defined($isString);
if ( !$isString ) {
return 0 if int($oldValue) == int($newValue);
}
print ">> Changing $tagname to '$newValue'\n";
$tag->$setFunc(
( $isString
? Audio::TagLib::String->new($newValue)
: $newValue
)
);
return 1;
}
#=== FUNCTION ================================================================
# NAME: restoreTimes
# PURPOSE: Restore the original times to a file which has had its tags
# changed
# PARAMETERS: $tags Hashref keyed by tagname and containing tag
# contents (and file attributes) from a media
# file. The file details have keys beginning
# with '_'.
# RETURNS: Number of files changed (see 'utime')
# DESCRIPTION: Uses the Perl 'utime' function to change the file's access
# time and modification time to whatever is in the hash. These
# are expected to be the times the file had when it was first
# encountered.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub restoreTimes {
my ($tags) = @_;
return utime( $tags->{_atime}, $tags->{_mtime}, $tags->{_path} );
}
#=== FUNCTION ================================================================
# NAME: checkConformity
# PURPOSE: Check that the master file has conforming tags, fixing them if
# not
# PARAMETERS: $ref Audio::TagLib::FileRef relating to the master
# file
# $tag Audio::TagLib::Tag containing the tags of the
# master file
# $tags Hashref containing the converted tags (and
# a few other odds and sods)
# $kosher Hashref containing the checking values (see
# %tag_control in the main program)
# RETURNS: A list containing $ref, $tag and $tags as described above
# DESCRIPTION: Implements a number of complex rules. Firstly the 'genre' tag
# is expected to contain 'Podcast'. Secondly the 'album' tag
# must contain 'Hacker Public Radio'. If it does not then the
# value is stored for later then replaced. Finally the
# 'comment' tag must begin with 'http://hackerpublicradio.org'.
# If it does not its current contents are stored and replaced
# with the required URL. However, the comment tag will also
# contain the saved album tag (if any) and the saved comment,
# and these will be placed at the end.
# THROWS: No exceptions
# COMMENTS: This code is ugly and difficult to extend and maintain.
# TODO look into ways of improving it!
# SEE ALSO:
#===============================================================================
sub checkConformity {
my ( $ref, $tag, $tags, $kosher ) = @_;
my $changed = 0;
my %saved;
my ( $t, $commentOK, $newval );
#
# The 'genre' tag
#
$t = 'genre';
unless ( $tags->{$t} =~ /$kosher->{$t}->{re}/ ) {
$changed += changeTag(
$tag, $t, $tags->{$t},
$kosher->{$t}->{ok},
@{ $tagmethods{$t} }
);
$tags->{genre} = $tag->genre()->toCString();
}
#
# The 'album' tag. We save this one for adding to the comment
#
$t = 'album';
unless ( $tags->{$t} =~ /$kosher->{$t}->{re}/ ) {
( $saved{$t} = $tags->{$t} ) =~ s/(^\s+|\s+$)//g;
$changed += changeTag(
$tag, $t, $tags->{$t},
$kosher->{$t}->{ok},
@{ $tagmethods{$t} }
);
$tags->{album} = $tag->album()->toCString();
}
#
# If the 'comment' is non-standard *or* if the 'album' was changed we want
# to do stuff here. We make sure the 'comment' is good and append the
# original 'album' and 'comment' as appropriate.
#
$t = 'comment';
$commentOK = $tags->{$t} =~ /$kosher->{$t}->{re}/;
unless ( !$changed && $commentOK ) {
( $saved{$t} = $tags->{$t} ) =~ s/(^\s+|\s+$)//g;
if ($changed) {
if ($commentOK) {
# Album had errors, comment is OK
$newval = concat( ", ", $saved{comment}, $saved{album} );
}
else {
# Album had errors, comment also
$newval = concat( ", ", $kosher->{$t}->{ok},
$saved{album}, $saved{comment} );
}
}
else {
# Comment had errors, album OK
$newval = concat( ", ", $kosher->{$t}->{ok}, $saved{comment} );
}
$changed += changeTag( $tag, $t, $tags->{$t},
$newval, @{ $tagmethods{$t} } );
$tags->{comment} = $tag->comment()->toCString();
}
#
# Save any changes
#
if ($changed) {
$ref->save();
}
#
# Return tag-related stuff so the caller can get the benefit
#
return ( $ref, $tag, $tags );
}
#=== FUNCTION ================================================================
# NAME: concat
# PURPOSE: Reimplementation of join but with any undefined or empty
# arguments removed
# PARAMETERS: $sep The string to be used to separate elements in
# the result
# [variable args] Any number of arguments to be joined together
# with the separator
# RETURNS: The concatenated arguments
# DESCRIPTION: Giving 'join' an array that may contain undefined elements will
# result in empty results in the output string and error
# messages as the undefined elements are processed. Giving it
# empty string elements will result in dangling separators in
# the output. This routine removes the undefined and empty
# elements before joining the rest.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO:
#===============================================================================
sub concat {
my $sep = shift;
my @args = grep { defined($_) && length($_) > 0 } @_;
return join( $sep, @args );
}
__END__
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Application Documentation
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#{{{
=head1 NAME
transfer_tags - standardise and transfer tags between HPR audio files
=head1 VERSION
This documentation refers to I<transfer_tags> version 1.4.2
=head1 USAGE
transfer_tags masterfile
=head1 REQUIRED ARGUMENTS
=over 4
=item B<masterfile>
This is the name of the audio file, which contains the definitive tags which
are to be copied to all of the other files of the same name but different
extensions.
=back
=head1 DESCRIPTION
The script transfers ID3 (or equivalent) tags from a base file to whichever of
FLAC, MP3, OGG, SPX and WAV versions are found. The tags copied are: B<album>,
B<artist>, B<comment>, B<genre>, B<title>, B<track> and B<year>. The target
files are determined by taking the name of the B<master file> without its
extension and appending all of the remaining extensions in the list. Files
with the string "B<_mez>" between the filename and the extension are also
included.
For example: if the B<master file> is called B<hpr1234.flac> and FLAC, MP3,
OGG, SPX and WAV versions exist, the tags found in the file B<hpr1234.flac>
are copied to B<hpr1234.mp3>, B<hpr1234.ogg>, B<hpr1234.spx> and
B<hpr1234.wav>. If B<hpr1234_mez.mp3> or any other variant existed it would
also receive a copy of the tags.
A certain amount of manipulation is performed before the tags are propagated.
The changes made conform to certain rules, which are:
=over 4
=item .
The B<genre> tag must contain the string "I<Podcast>".
=item .
The B<album> tag must contain the string "I<Hacker Public Radio>". If it does
not then the existing value is stored for later and is then replaced.
=item .
The B<comment> tag must begin with the string
"I<http://hackerpublicradio.org>". If it does not its current contents are
stored and replaced with the required URL. However, the comment tag will also
contain the saved album tag (if any) and the saved comment (if any), and these
will be placed at the end, separated by commas.
=back
The script saves the access time and modification time of all of the media
files it processes. It then restores these times at the end of its run. This
prevents any external processes which depend on these file times from being
confused by the tag changes.
=head1 DIAGNOSTICS
=over 4
=item B<Usage: transfer_tags masterfile>
This error is produced if the script is called without the mandatory argument.
The error is fatal.
=item B<... does not exist>
The master file specified as the argument does not exist. The error is fatal.
=item B<... does not have a recognised suffix (expecting ...)>
The master file specified as the argument does not have one of the expected
extensions (flac, mp3, ogg, spx, wav). The error is fatal.
=item B<... is not readable>
One of the target files was found not to be readable (probably due to file
permissions). The script will ignore this file.
=item B<Time restoration failed on ...>
The master file or one of the target files could not have its time restored.
The script will ignore this file.
=back
=head1 DEPENDENCIES
Audio::TagLib
File::Basename
File::Find::Rule
=head1 BUGS AND LIMITATIONS
There are no known bugs in this module.
Please report problems to Dave Morriss (Dave.Morriss@gmail.com).
Patches are welcome.
=head1 AUTHOR
Dave Morriss (Dave.Morriss@gmail.com) 2013
=head1 LICENCE AND COPYRIGHT
Copyright (c) 2013 Dave Morriss (Dave.Morriss@gmail.com). All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See perldoc perlartistic.
=cut
#}}}
# [zo to open fold, zc to close]
# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker

44
workflow/unpack.bash Normal file
View File

@ -0,0 +1,44 @@
#!/bin/bash
# Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/
intro_clip_end="00:01:30"
quality_clip_duration="90"
outro_clip="90"
#DEBUG="echo "
cd /mnt/DUMP/hpr-for-archive.org/todo/
for FILEX in *
do
if [[ "$(file "$FILEX" | grep -i audio | wc -l )" -ne 1 && "$(mediainfo "$FILEX" | grep -i audio | wc -l )" -ne 1 ]]
then
echo "$FILEX is not an audio file"
else
echo "Processing $FILEX"
fname=${FILEX%.*}
ext=${FILEX/*./}
# We get the druation from mediainfo in miliseconds and remove the last three digits to convert it to seconds
# Then calculate the times of the end clip working back from the end of the file
# We then pick a random segment between the intro and outro.
# Finally we convert to hour minute second format and the date command is the easiest way to do that
duration=$( mediainfo --full "${fname}.${ext}" | grep Duration | egrep -v ".*:.*:|ms|s|mn" | head -1 | awk -F ': ' '{print $2}' )
duration="${duration:0:${#duration}-3}"
outro_clip_start="$(($duration-$outro_clip))"
quality_clip_end="$(($outro_clip_start-$quality_clip_duration))"
quality_clip_start=$(shuf -i ${intro_clip_end}-${quality_clip_end} -n 1)
quality_clip_end="$(($quality_clip_start+$quality_clip_duration))"
duration=$(\date -d@${duration} -u +%H:%M:%S)
quality_clip_start=$(\date -d@${quality_clip_start} -u +%H:%M:%S)
quality_clip_end=$(\date -d@${quality_clip_end} -u +%H:%M:%S)
outro_clip_start=$(\date -d@${outro_clip_start} -u +%H:%M:%S)
echo -e "\tExtracting spectrogram"
${DEBUG} sox "${fname}.${ext}" -n spectrogram -Y 130 -l -r -t "${fname}.${ext}" -o "${fname}_spectrogram.png"
echo -e "\tExtracting intro clip from 0 to ${intro_clip_end}"
${DEBUG} ffmpeg -y -ss 0 -t ${intro_clip_end} -i "${fname}.${ext}" "${fname}_intro_clip".wav 2>/dev/null
echo -e "\tExtracting quality clip from ${quality_clip_start} to ${quality_clip_end}"
${DEBUG} ffmpeg -y -ss ${quality_clip_start} -t ${quality_clip_end} -i "${fname}.${ext}" "${fname}_quality_clip".wav 2>/dev/null
echo -e "\tExtracting outro clip from ${outro_clip_start} ${duration}"
${DEBUG} ffmpeg -y -ss ${outro_clip_start} -t ${duration} -i "${fname}.${ext}" "${fname}_outro_clip".wav 2>/dev/null
echo -e "\tCreating low fidelity version"
${DEBUG} ffmpeg -y -i "${fname}.${ext}" temp.wav 2>/dev/null
${DEBUG} sox "temp.wav" -c 1 -r 16000 -t wav - 2>/dev/null | speexenc - "${fname}_low_fidelity.spx" 2>/dev/null
${DEBUG} rm temp.wav
fi
done

50
workflow/x.bash Executable file
View File

@ -0,0 +1,50 @@
#cd /home/ken/processing/1659905303_3664_2022-08-18_037b55af6b77191b2610f5cedb5d2bd962f0251720bf0
ttsserver="http://localhost:5500"
ep_num="3664"
echo "This is Hacker Public Radio " > "hpr${ep_num}_summary.txt"
## Jump to encoding
function create_tts_summary {
HPR_summary="$( cat "hpr${ep_num}_summary.txt" )"
echo "INFO: Converting text \"${HPR_summary}\" to speech."
curl -X 'GET' -G --data-urlencode "voice=coqui-tts:en_ljspeech" --data-urlencode "text=${HPR_summary}" --data-urlencode "vocoder=high" --data-urlencode "denoiserStrength=0.03" --data-urlencode "cache=false" ${ttsserver}/api/tts -H 'accept: */*' --output ~hpr${ep_num}_summary.wav
}
create_tts_summary_ok="not-ok"
while [ "${create_tts_summary_ok}" != "OK" ]
do
create_tts_summary
mpv ~hpr${ep_num}_summary.wav
read -p "Is the text to speech correct (y|N) ? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
create_tts_summary_ok="OK"
else
echo "WARN: Please correct the text and try again."
xdg-open "hpr${ep_num}_summary.txt" 2>&1 &
inotifywait --event modify "hpr${ep_num}_summary.txt"
fi
done
echo "INFO: TTS complete."
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
# exit 9999