2 Commits

Author SHA1 Message Date
1dec9996f8 Add listening to and downloading of host M3U playlist 2025-11-15 13:07:41 -05:00
354d8d24cd Add javascript to play m3u file via audio tag
This is an open source (GPL licensed) javascript file which
fetches, parses an M3U formatted file and attaches the files
to the corresponding audio tag within a page.
2025-11-15 12:10:28 -05:00
31 changed files with 365 additions and 2148 deletions

View File

@@ -1 +0,0 @@
ALTER TABLE twat_eps RENAME TO twt_eps;

View File

@@ -736,13 +736,10 @@ fieldset > table td input[type="radio"] {
padding: 0;
}
.series-description {
margin: 0 0 1rem 0;
margin: 0;
padding: 0;
font-style: italic;
}
.series-desciption > *:last-child {
margin-bottom: 0;
}
.sr-only {
position: absolute;
width: 1px;
@@ -758,14 +755,11 @@ fieldset > table td input[type="radio"] {
}
#show_notes pre
{
overflow: auto;
}
#show_notes code {
display: inline-block;
background-color: var(--show-notes-pre-background);
border: 1px solid #ddd;
overflow: scroll;
padding: 0.1em 0;
}
}
nav.episodes {
color: var(--background-primary);
font-size: 0.9em;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,315 @@
// @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2-or-Later
// see: m3u-Playlist player in Vanilla Javascript (https://www.draketo.de/software/m3u-player.html)
// last viewed: 2025-11-15
const nodes = document.querySelectorAll("audio,video");
const playlists = {};
const prefetchedTracks = new Map(); // use a map for insertion order, so we can just blow away old entries.
// maximum prefetched blobs that are kept.
const MAX_PREFETCH_KEEP = 10;
// maximum allowed number of entries in a playlist to prevent OOM attacks against the browser with self-referencing playlists
const MAX_PLAYLIST_LENGTH = 1000;
const PLAYLIST_MIME_TYPES = ["audio/x-mpegurl", "audio/mpegurl", "application/vnd.apple.mpegurl","application/mpegurl","application/x-mpegurl"];
function stripUrlParameters(link) {
const url = new URL(link, window.location);
url.search = "";
url.hash = "";
return url.href;
}
function isPlaylist(link) {
const linkHref = stripUrlParameters(link);
return linkHref.endsWith(".m3u") || linkHref.endsWith(".m3u8");
}
function isBlob(link) {
return new URL(link, window.location).protocol == 'blob';
}
function parsePlaylist(textContent) {
return textContent.match(/^(?!#)(?!\s).*$/mg)
.filter(s => s); // filter removes empty strings
}
/**
* Download the given playlist, parse it, and store the tracks in the
* global playlists object using the url as key.
*
* Runs callback once the playlist downloaded successfully.
*/
function fetchPlaylist(url, onload, onerror) {
const playlistFetcher = new XMLHttpRequest();
playlistFetcher.open("GET", url, true);
playlistFetcher.responseType = "blob"; // to get a mime type
playlistFetcher.onload = () => {
if (PLAYLIST_MIME_TYPES.includes(playlistFetcher.response.type)) { // security check to ensure that filters have run
const reader = new FileReader();
const load = onload; // propagate to inner scope
reader.addEventListener("loadend", e => {
playlists[url] = parsePlaylist(reader.result);
onload();
});
reader.readAsText(playlistFetcher.response);
} else {
console.error("playlist must have one of the playlist MIME type '" + PLAYLIST_MIME_TYPES + "' but it had MIME type '" + playlistFetcher.response.type + "'.");
onerror();
}
};
playlistFetcher.onerror = onerror;
playlistFetcher.abort = onerror;
playlistFetcher.send();
}
function servedPartialDataAndCanRequestAll (xhr) {
if (xhr.status === 206) {
if (xhr.getResponseHeader("content-range").includes("/")) {
if (!xhr.getResponseHeader("content-range").includes("/*")) {
return true;
}
}
}
return false;
}
function prefetchTrack(url, onload) {
if (prefetchedTracks.has(url)) {
return;
}
// first cleanup: kill the oldest entries until we're back at the allowed size
while (prefetchedTracks.size > MAX_PREFETCH_KEEP) {
const key = prefetchedTracks.keys().next().value;
const track = prefetchedTracks.get(key);
prefetchedTracks.delete(key);
}
// first set the prefetched to the url so we will never request twice
prefetchedTracks.set(url, url);
// now start replacing it with a blob
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "blob";
xhr.onload = () => {
if (servedPartialDataAndCanRequestAll(xhr)) {
const endRange = Number(xhr.getResponseHeader("content-range").split("/")[1]) - 1;
const rangeXhr = new XMLHttpRequest();
rangeXhr.open("GET", url, true);
rangeXhr.responseType = "blob";
rangeXhr.setRequestHeader("range", "bytes=0-" + endRange);
rangeXhr.onload = () => {
prefetchedTracks.set(url, rangeXhr.response);
if (onload) {
onload();
}
};
rangeXhr.send();
} else {
prefetchedTracks.set(url, xhr.response);
if (onload) {
onload();
}
}
};
xhr.send();
}
function showStaticOverlay(mediaTag, canvas) {
if (mediaTag instanceof Audio) {
return;
}
// take screenshot of video and overlay it to mask short-term flicker.
const realWidth = mediaTag.getBoundingClientRect().width;
const realHeight = mediaTag.getBoundingClientRect().height;
canvas.width = realWidth;
canvas.height = realHeight;
// need the actual video size
const videoAspectRatio = mediaTag.videoHeight / mediaTag.videoWidth;
const tagAspectRatio = realHeight / realWidth;
const videoIsPartialHeight = tagAspectRatio > (videoAspectRatio * 1.01); // avoid rounding errors
const videoIsPartialWidth = videoAspectRatio > (tagAspectRatio * 1.01); // avoid rounding errors
if (videoIsPartialHeight) {
canvas.height = realWidth * videoAspectRatio;
} else if (videoIsPartialWidth) {
canvas.width = realHeight / videoAspectRatio;
}
const context = canvas.getContext("2d");
context.scale(canvas.width / mediaTag.videoWidth, canvas.height / mediaTag.videoHeight);
context.drawImage(mediaTag, 0, 0);
canvas.hidden = true;
mediaTag.parentNode.insertBefore(canvas, mediaTag.nextSibling);
canvas.style.position = "absolute";
// shift canvas to cover only the space where the video actually is
if (videoIsPartialWidth) {
canvas.style.marginLeft = "-" + ((realWidth + canvas.width) / 2.) + "px";
} else {
canvas.style.marginLeft = "-" + realWidth + "px";
}
if (videoIsPartialHeight) {
canvas.style.marginTop = ((realHeight - canvas.height) / 2.) + "px";
}
canvas.hidden = false;
}
function updateSrc(mediaTag, callback) {
const playlistUrl = mediaTag.getAttribute("playlist");
const trackIndex = mediaTag.getAttribute("track-index");
// deepcopy playlists to avoid shared mutation
let playlist = [...playlists[playlistUrl]];
let trackUrl = playlist[trackIndex];
// download and splice in playlists as needed
if (isPlaylist(trackUrl)) {
if (playlist.length >= MAX_PLAYLIST_LENGTH) {
// skip playlist if we already have too many tracks
changeTrack(mediaTag, +1);
} else {
// do not use the cached playlist here, though it is tempting: it might genuinely change to allow for updates
fetchPlaylist(
trackUrl,
() => {
playlist.splice(trackIndex, 1, ...playlists[trackUrl]);
playlists[playlistUrl] = playlist;
updateSrc(mediaTag, callback);
},
() => callback());
}
} else {
let url = prefetchedTracks.has(trackUrl)
? prefetchedTracks.get(trackUrl) instanceof Blob
? URL.createObjectURL(prefetchedTracks.get(trackUrl))
: trackUrl : trackUrl;
const oldUrl = mediaTag.getAttribute("src");
// prevent size flickering by setting height before src change
const canvas = document.createElement("canvas");
if (!isNaN(mediaTag.duration) // already loaded a valid file
&& document.fullscreen !== true) { // overlay does not work for fullscreen
// mask flickering with a static overlay
try {
showStaticOverlay(mediaTag, canvas);
} catch (error) {
console.log(error);
}
}
// force sizes to stay constant during loading of the next segment
mediaTag.style.height = mediaTag.getBoundingClientRect().height.toString() + 'px';
mediaTag.style.width = mediaTag.getBoundingClientRect().width.toString() + 'px';
// swich to the next segment
mediaTag.setAttribute("src", url);
mediaTag.oncanplaythrough = () => {
if (!isNaN(mediaTag.duration)) { // already loaded a valid file
// unset element styles to allow recomputation if sizes changed
mediaTag.style.height = null;
mediaTag.style.width = null;
}
// remove overlay
canvas.hidden = true;
canvas.remove(); // to allow garbage collection
};
setTimeout(() => canvas.remove(), 300); // fallback
// replace the url when done, because a blob from an xhr request
// is more reliable in the media tag;
// the normal URL caused jumping prematurely to the next track.
if (url == trackUrl) {
prefetchTrack(trackUrl, () => {
if (mediaTag.paused) {
if (url == mediaTag.getAttribute("src")) {
if (mediaTag.currentTime === 0) {
mediaTag.setAttribute("src", URL.createObjectURL(
prefetchedTracks.get(url)));
}
}
}
});
}
// allow releasing memory
if (isBlob(oldUrl)) {
URL.revokeObjectURL(oldUrl);
}
// update title
mediaTag.parentElement.querySelector(".m3u-player--title").title = trackUrl;
mediaTag.parentElement.querySelector(".m3u-player--title").textContent = trackUrl;
// start prefetching the next three tracks.
for (const i of [1, 2, 3]) {
if (playlist.length > Number(trackIndex) + i) {
prefetchTrack(playlist[Number(trackIndex) + i]);
}
}
callback();
}
}
function changeTrack(mediaTag, diff) {
const currentTrackIndex = Number(mediaTag.getAttribute("track-index"));
const nextTrackIndex = currentTrackIndex + diff;
const tracks = playlists[mediaTag.getAttribute("playlist")];
if (nextTrackIndex >= 0) { // do not collapse the if clauses with double-and, that does not survive inlining
if (tracks.length > nextTrackIndex) {
mediaTag.setAttribute("track-index", nextTrackIndex);
updateSrc(mediaTag, () => mediaTag.play());
}
}
}
/**
* Turn a media tag into playlist player.
*/
function initPlayer(mediaTag) {
mediaTag.setAttribute("playlist", mediaTag.getAttribute("src"));
mediaTag.setAttribute("track-index", 0);
const url = mediaTag.getAttribute("playlist");
const wrapper = mediaTag.parentElement.insertBefore(document.createElement("div"), mediaTag);
const controls = document.createElement("div");
const left = document.createElement("span");
const title = document.createElement("span");
const right = document.createElement("span");
controls.appendChild(left);
controls.appendChild(title);
controls.appendChild(right);
left.classList.add("m3u-player--left");
right.classList.add("m3u-player--right");
title.classList.add("m3u-player--title");
title.style.overflow = "hidden";
title.style.textOverflow = "ellipsis";
title.style.whiteSpace = "nowrap";
title.style.opacity = "0.3";
title.style.direction = "rtl"; // for truncation on the left
title.style.paddingLeft = "0.5em";
title.style.paddingRight = "0.5em";
controls.style.display = "flex";
controls.style.justifyContent = "space-between";
const styleTag = document.createElement("style");
styleTag.innerHTML = ".m3u-player--left:hover, .m3u-player--right:hover {color: wheat; background-color: DarkSlateGray}";
wrapper.appendChild(styleTag);
wrapper.appendChild(controls);
controls.style.width = mediaTag.getBoundingClientRect().width.toString() + "px";
// appending the media tag to the wrapper removes it from the outer scope but keeps the event listeners
wrapper.appendChild(mediaTag);
left.innerHTML = "<"; // not textContent, because we MUST escape
// the tag here and textContent shows the
// escaped version
left.onclick = () => changeTrack(mediaTag, -1);
right.innerHTML = ">";
right.onclick = () => changeTrack(mediaTag, +1);
fetchPlaylist(
url,
() => {
updateSrc(mediaTag, () => null);
mediaTag.addEventListener("ended", event => {
if (mediaTag.currentTime >= mediaTag.duration) {
changeTrack(mediaTag, +1);
}
});
},
() => null);
// keep the controls aligned to the media tag
mediaTag.resizeObserver = new ResizeObserver(entries => {
controls.style.width = entries[0].contentRect.width.toString() + "px";
});
mediaTag.resizeObserver.observe(mediaTag);
}
function processTag(mediaTag) {
const canPlayClaim = mediaTag.canPlayType('audio/x-mpegurl');
let supportsPlaylists = !!canPlayClaim;
if (canPlayClaim == 'maybe') { // yes, seriously: specced as you only know when you try
supportsPlaylists = false;
}
if (!supportsPlaylists) {
if (isPlaylist(mediaTag.getAttribute("src"))) {
initPlayer(mediaTag);
}
}
}
document.addEventListener('DOMContentLoaded', () => {
const nodes = document.querySelectorAll("audio,video");
nodes.forEach(processTag);
});
// @license-end

View File

@@ -26,10 +26,6 @@ media_baseurl: https://hub.hackerpublicradio.org/ccdn.php?filename=/eps/hpr$eps_
generator_name: The HPR Robot
generator_email: robot.nospam@nospam.hackerpublicradio.org
# Is safe for work: 0 for true, 1 for false -- if true substitute
# variations of Hobby for Hacker
is_sfw: 1
# Configure the navigation menu and the content templates for each page
# of the site:
#
@@ -134,10 +130,6 @@ content: content-promote.tpl.html
navigation: navigation-about.tpl.html
content: content-comments_viewer.tpl.html
[new_year]
navigation: navigation-about.tpl.html
content: content-new_year.tpl.html
[hpr_ogg]
root_template: rss.tpl.xml
content: rss-hpr.tpl.xml
@@ -185,9 +177,3 @@ root_template: m3u.tpl.m3u8
content: m3u-correspondent.tpl.m3u8
filename: correspondents/[id]/playlist.m3u8
multipage: true
[series_episodes_m3u]
root_template: m3u.tpl.m3u8
content: m3u-series_episodes.tpl.m3u8
filename: series/[id].m3u8
multipage: true

View File

@@ -1,4 +1,3 @@
<!--% page_title = "HPR ~ About this community podcast" %-->
<h1 id="welcome">Welcome to HPR<a href="<!--% absolute_url(baseurl,'about.html#about') %-->">.</a></h1>
<ul>
@@ -27,7 +26,7 @@
<p>What differentiates HPR from other podcasts is that the shows are crowd sourced from the community -
fellow listeners like <strong><a href="<!--% absolute_url(baseurl,'about.html#contact') %-->">you</a></strong>.
There is no restriction on how long the show can be, nor on the topic you can cover as long as they are not spam
and <em>&quot;are of interest to <a href="http://en.wikipedia.org/wiki/Hacker_(hobbyist)" ><!--% make_sfw(is_sfw, "Hobbyists", "Hackers") %--></a>&quot;</em>.
and <em>&quot;are of interest to <a href="http://en.wikipedia.org/wiki/Hacker_(hobbyist)" >Hackers</a>&quot;</em>.
If you want to see what topics have been covered so far just have a look at our
<a href="<!--% absolute_url(baseurl,'eps/index.html') %-->">Archive</a>.
We also allow for a <a href="<!--% absolute_url(baseurl,'series/index.html') %-->">series</a> of shows so that
@@ -41,7 +40,7 @@ You can copy and redistribute the shows for free provided you adhere to the
Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License</a>.</p>
<h2 id="history">History<a href="<!--% absolute_url(baseurl,'about.html#history') %-->">.</a></h2>
<p><!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio (HPR) is an Internet Radio show (podcast) that releases shows every weekday Monday through Friday.
<p>Hacker Public Radio (HPR) is an Internet Radio show (podcast) that releases shows every weekday Monday through Friday.
HPR has a long lineage going back to <a href="https://web.archive.org/web/20230323053905/http://www.oldskoolphreak.com/" >Radio FreeK America</a>,
<a href="https://web.archive.org/web/20220123174618/https://www.binrev.com/forums/" >Binary Revolution Radio</a> &amp; <a href="https://web.archive.org/web/20150208172826/http://www.nomicon.info/" >Infonomicon</a>,
and it is a rename of <a href="<!--% absolute_url(baseurl,'eps/index.html#twt_episodes') %-->" >Today With a Techie</a> radio.
@@ -49,7 +48,7 @@ Please listen to StankDawg&#39;s &quot;<a href="<!--% media_path(1, 'hpr', 'mp3'
Introduction to HPR</a>&quot; for more information.</p>
<h2 id="free_culture">Free Culture<a href="<!--% absolute_url(baseurl,'about.html#free_culture') %-->">.</a></h2>
<p><!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio is dedicated to sharing knowledge. We do not accept donations so please consider supporting our patrons.
<p>Hacker Public Radio is dedicated to sharing knowledge. We do not accept donations so please consider supporting our patrons.
If you listen to HPR, then we would love you to <a href="contribute.html">contribute</a> one show a year.
Our shows are by default released under a <a href="https://creativecommons.org/licenses/by-sa/4.0/" >
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)</a> license.
@@ -102,7 +101,7 @@ now hosting our media files.
</header>
<h2 id="contact_primary">Primary Contact Points</h2>
<ul>
<li>email: <strong>admin -at- <!--% make_sfw(is_sfw, "hobby", "hacker") %-->publicradio org</strong>, will put you in touch with the Janitors who are
<li>email: <strong>admin -at- hackerpublicradio org</strong>, will put you in touch with the Janitors who are
the first point of contact for any issues related to the HPR community.
If you have any issue of concern please bring it to their attention first.
<br>
@@ -142,7 +141,7 @@ now hosting our media files.
<li>2016-05-18: <a href="http://solutionsreview.com/endpoint-security/top-ten-cybersecurity-podcasts/">Ten Security Podcasts You Should Be Listening To</a> by Lauren Cooke in Endpoint Security News (<a href="<!--% absolute_url(baseurl,'press/2016-05-18_solutionsreview.com_endpoint-security_top-ten-cybersecurity-podcasts.png') %-->">Archived</a>)</li>
<li>2016-01-18: <a href="http://www.tripwire.com/state-of-security/security-awareness/information-security-podcast-roundup-2016-edition/">Information Security Podcast Roundup: 2016 Edition</a> from TripWire.com (<a href="<!--% absolute_url(baseurl,'press/2016-01-18_Information_Security_Podcast_Roundup_2016_Edition.pdf') %-->">Archived</a>)</li>
<li>2014-12-09: <a href="http://joeyh.name/blog/entry/podcasts_that_dont_suck_2014/">podcasts that don't suck, 2014 edition (Joey Hess)</a>(<a href="<!--% absolute_url(baseurl,'press/2014-12-09_podcasts_that_dont_suck_2014_edition.pdf') %-->">Archived</a>)</li>
<li>2014-06-30: <a href="http://opensource.com/life/14/6/hacker-public-radio">Free software on <!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio</a> by Bryan Behrenshausen (Red Hat)(<a href="<!--% absolute_url(baseurl,'press/2014-06-30_Free_software_on_Hacker_Public_Radio.pdf') %-->">Archived</a>)</li>
<li>2014-06-30: <a href="http://opensource.com/life/14/6/hacker-public-radio">Free software on Hacker Public Radio</a> by Bryan Behrenshausen (Red Hat)(<a href="<!--% absolute_url(baseurl,'press/2014-06-30_Free_software_on_Hacker_Public_Radio.pdf') %-->">Archived</a>)</li>
<li>2014-04-19: <a href="http://www.efytimes.com/e1/fullnews.asp?edid=136312">10 Useful Places Hosting Informative Podcasts On Linux</a> (<a href="<!--% absolute_url(baseurl,'press/2014-04-19_10_Useful_Places_Hosting_Informative_Podcasts_On_Linux.pdf') %-->">Archived</a>)</li>
<li>2014-04-11: <a href="http://blog.sleeplessbeastie.eu/2014/04/11/hand-picked-audio-podcasts-for-linux-users/">Hand-picked audio podcasts for Linux users</a> by Milosz Galazka (<a href="<!--% absolute_url(baseurl,'press/2014-04-11_hand-picked-audio-podcasts-for-linux-users.pdf') %-->">Archive</a>)</li>
<li>2013-10-11: <a href="http://www.linuxlinks.com/article/20130706190339587/LinuxPodcasts-Page1.html">Illuminating Linux Podcasts</a> by Dan Petersen (<a href="<!--% absolute_url(baseurl,'press/2013-10-11_Illuminating_Linux_Podcasts.pdf') %-->">Archive</a>)</li>
@@ -333,7 +332,7 @@ people who will help.
<h2 id="motto">Our Mottos</h2>
<blockquote>Any audio is better than no audio.</blockquote>
<blockquote>Any topic of intrest to <!--% make_sfw(is_sfw, "hobbyist", "hacker") %-->s.</blockquote>
<blockquote>Any topic of intrest to hackers.</blockquote>
<blockquote>It ain't a show unless it's on the server.</blockquote>
<blockquote>If you tell us you are doing a show, then you owe us a show.</blockquote>
@@ -913,6 +912,9 @@ Your profile information is used on the website, in the RSS feeds, and on social
<li><strong>Do not paste un-rendored markup (HTML, Markdown,
RestructuredText)</strong> unless your intention is to have un-rendored
markup as shownotes.</li>
<li>The amount you can enter is restricted to 4000 characters, but you can provide additional show notes if you wish.
Please add them to an full_shownotes.html file which you can expect to be served from
<!--% absolute_url(baseurl) %-->eps/hpr9999/full_shownotes.html</li>
</ul>
</li>
</ul>
@@ -1145,7 +1147,7 @@ Your show information is used to process the media files, on the website, in the
If I notice an error in my show's details how can it be fixed?</h2>
<ul>
<li><p>The HPR administrators can make changes to show titles, summaries, notes and so forth. Ideally send an
email to <code>admin</code> at <code><!--% make_sfw(is_sfw, "hobby", "hacker") %-->publicradio.org</code> explaining what the problem is and we'll fix it for you.
email to <code>admin</code> at <code>hackerpublicradio.org</code> explaining what the problem is and we'll fix it for you.
We'll also ensure that the changes are propagated to the relevant page on archive.org.</p></li>
<li><p>Don't be tempted to send in your corrections as a comment. Comments are not propagated to archive.org, so people
referring to that copy will not see the changes.</p></li>
@@ -1181,11 +1183,11 @@ Your show information is used to process the media files, on the website, in the
</ul>
<h2 data-number="0.14" id="faq_wikipedia"><span class="header-section-number">0.14</span> Why has HPR not got a Wikipedia page?</h2>
<p>
Once someone contributes to <!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio, the Wikipedia rules prevent us from editing a page.
Once someone contributes to Hacker Public Radio, the Wikipedia rules prevent us from editing a page.
You can of course create one prior to contributing a show, or ask for one to be created.
</p>
<p>
We do of course believe that <!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio should have a Wikipedia entry.
We do of course believe that Hacker Public Radio should have a Wikipedia entry.
We are one of the longest running podcasts having started as Today with a Techie on 2005-09-19.
We also are unique in our community driven approach to producing shows.
And of course the fact that Wikipedia itself references us as a source

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
<!--% PROCESS 'shared-listen-now.tpl.html' %-->
<!--% PROCESS "queries-episode.tpl.html" %-->
<!--% USE DBI(constants.driver) %-->
<!--% page_title = "HPR ~ Current comments on the shows" %-->
<h2>Comment Viewer</h2>
<p>Because of the spammers we have had to turn on comment moderation. Sorry about the delay this will cause.</p>
<p><a href="<!--% absolute_path(baseurl) %-->comments.rss">Subscribe</a> to the comment feed.</p>

View File

@@ -1,4 +1,3 @@
<!--% page_title = "HPR ~ Contact the Community" %-->
<article>
<header>
<h1>Contact</h1>

View File

@@ -18,7 +18,6 @@
<!--% hosts = DBI.tie('hosts', 'hostid') %-->
<!--% this_host = hosts.$id %-->
<!--% page_title = "HPR ~ Host: " _ this_host.host %-->
<h2 class="title">Correspondent</h2>
<div id="host" class="lane">
<div id="host_id">
@@ -30,10 +29,18 @@
<p><label>email:</label> <u><!--% this_host.email %--></u></p>
<div><label>profile:</label> <!--% this_host.profile %--></div>
<p><label>episodes:</label> <strong><!--% hpr_show_count + twt_show_count %--></strong></p>
<p><a href="<!--% absolute_url(baseurl,'//correspondents') %-->/<!--% zero_pad_left(this_host.hostid) %-->/playlist.m3u8">Download the M3U playlist</a>.</p>
</div>
<div id="listen_to_shows">
<p>Listen to the shows:<br>
<script src="<!--% absolute_url(baseurl,'//scripts/m3u-player.js') %-->" defer="defer"></script>
<audio controls src="<!--% absolute_url(baseurl,'//correspondents') %-->/<!--% zero_pad_left(this_host.hostid) %-->/playlist.m3u8" type="audio/x-mpegurl">
Your browser does not support the HTML5 audio player.
</audio>
<noscript>Playback without Javascript needs native m3u-support.</noscript><br>
<a href="<!--% absolute_url(baseurl,'//correspondents') %-->/<!--% zero_pad_left(this_host.hostid) %-->/playlist.m3u8">Download the playlist</a>.
<p>
</div>
<div id="episodes" class="lane stack">
<div class="lane stack">
<!--% FOREACH hpr_show IN hpr_shows; %-->
<article>
<!--% show_summary(hpr_show, 'hide_host') %-->

View File

@@ -1,6 +1,5 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% PROCESS 'shared-avatar.tpl.html' %-->
<!--% page_title = "HPR ~ Hosts/Correspondents" %-->
<h2 class="title">Correspondents</h2>
<p>For more information on how to become a Correspondent see our <a href="<!--% absolute_url(baseurl) %-->../about.html#so_you_want_to_record_a_podcast">contribute</a></center> page. To add a logo here, either email one to admin at hpr or setup your email on <a href="https://en.gravatar.com/">Gravatar</a>. To protect your browsing privacy we gather the images every hour and serve them directly from HPR.<p />
<!--% USE DBI(constants.driver) %-->
@@ -19,7 +18,7 @@
from hosts as h
inner join (select hostid, max(date) as date from eps group by hostid) as e
on h.hostid = e.hostid
order by h.host COLLATE NOCASE asc'
order by h.host'
) %-->
<tr class="lane">
<td><!--% get_avatar(host.hostid, host.host, host_cnt > 8) %--></td>

View File

@@ -1,4 +1,3 @@
<!--% page_title = "HPR ~ Download all the episodes" %-->
<article>
<header>
<h1>Download Archive</h1>

View File

@@ -10,7 +10,6 @@
<!--% FOREACH episode IN episode_result %-->
<!--% in_window = ( episode.id > episode.latest || episode.id < ( episode.latest - 20 ) ? 0 : 1 ) %-->
<!--% episode_navigation = step_navigation(baseurl,episode) %-->
<!--% page_title = "HPR" _ zero_pad_left(episode.id) _ "::" _ episode.title %-->
<article>
<header>
<h1>hpr<!--% zero_pad_left(episode.id) %--> :: <!--% episode.title %--></h1>
@@ -62,11 +61,11 @@ Subscribe to the comments <a href="<!--% absolute_path(baseurl) %-->comments.rss
<h2>Leave Comment</h2>
<p>
<strong>Note to Verbose Commenters</strong><br />
If you can't fit everything you want to say in the comment below then you really should <a href="<!--% absolute_url(baseurl) %-->../../about.html#so_you_want_to_record_a_podcast">record</a> a response show instead.
If you can't fit everything you want to say in the comment below then you really should <a href="<!--% absolute_url(baseurl) %-->about.html#so_you_want_to_record_a_podcast">record</a> a response show instead.
</p>
<p>
<strong>Note to Spammers</strong><br />
All comments are moderated. All links are checked by humans. We strip out all html. Feel free to <a href="<!--% absolute_url(baseurl) %-->../../about.html#so_you_want_to_record_a_podcast">record</a> a show about yourself, or your industry, or any other topic we may find interesting. <em>We also check shows for spam :)</em>.
All comments are moderated. All links are checked by humans. We strip out all html. Feel free to <a href="<!--% absolute_url(baseurl) %-->about.html#so_you_want_to_record_a_podcast">record</a> a show about yourself, or your industry, or any other topic we may find interesting. <em>We also check shows for spam :)</em>.
</p>
<form method="POST" action="<!--% hub_baseurl %-->comment_confirm.php">
<fieldset>

View File

@@ -1,6 +1,5 @@
<!--% PROCESS 'shared-episode-summary.tpl.html' %-->
<!--% PROCESS "queries-episodes.tpl.html" %-->
<!--% page_title = "HPR ~ All the shows" %-->
<h1>Complete Archive of Shows.</h1>
<p>
All this information is available to the public. Scrape if you wish but if we can format the data for you then we're happy to help.
@@ -30,7 +29,7 @@
hosts.hostid,
hosts.host, hosts.email, hosts.local_image,
miniseries.name AS series, miniseries.id AS seriesid
FROM twt_eps as eps
FROM twat_eps as eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
ORDER BY eps.id DESC

View File

@@ -1,4 +1 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% IF date.format(date.now, '%m') == 12 || date.format(date.now, '%j') == 1 %-->
<!--% PROCESS 'content-new_year_show_announcement.tpl.html' %-->
<!--% END %-->

View File

@@ -3,6 +3,7 @@
<!--% PROCESS 'shared-listen-now.tpl.html' %-->
<!--% PROCESS 'shared-show-transcript.tpl.html' %-->
<!--% PROCESS 'shared-call_for_shows.tpl.html' %-->
<!--% INCLUDE 'content-index-announcement.tpl.html' %-->
<!--% PROCESS "queries-index.tpl.html" %-->
<!--% MACRO tidy_notes(all_lines) BLOCK %-->
<!--% lines = all_lines %-->
@@ -35,8 +36,7 @@
<!--% delta = date.calc.N_Delta_YMD(2005,9,19, date.format(date.now, '%Y'),date.format(date.now, '%m'),date.format(date.now, '%d')) %-->
<section id="welcome">
<p><!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio is a technology focused podcast that releases shows every weekday Monday to Friday. Our shows are produced by listeners like you and can be on any topic that is of interest to <!--% make_sfw(is_sfw, "hobbyists", "hackers, hobbyists") %-->, makers, etc. We are a welcoming community that offers positive feedback and encourages respectful debate.</p>
<!--% PROCESS 'content-index-announcement.tpl.html' %-->
<p>Hacker Public Radio is a technology focused podcast that releases shows every weekday Monday to Friday. Our shows are produced by listeners like you and can be on any topic that is of interest to hackers, makers, hobbyists, etc. We are a welcoming community that offers positive feedback and encourages respectful debate.</p>
<div id="call_for_shows">
<!--% display_call_for_shows() %-->
</div>
@@ -104,7 +104,6 @@
<!--% host_cnt = host_cnt + 1 %-->
<!--% END %-->
</dl>
<p><a href="<!--% absolute_path(baseurl) %-->eps/index.html">More Episodes…</a></p>
</section>
<section id="latest_comments">
<header><h2>Latest Comments</h2></header>
@@ -139,6 +138,5 @@
<dd>on hpr<!--% item.eps_id %--> (<!--% item.episode_date %-->) "<!--% item.episode_title %-->" by <!--% item.host %--></dd>
<!--% END %-->
</dl>
<p><a href="<!--% absolute_path(baseurl) %-->comments_viewer.html">More Comments…</a></p>
</section>
</section>

View File

@@ -1,106 +0,0 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<h1><u>The <!--% get_new_year_show_ordinal_year %--> Annual 26 Hour New Year's Eve Show</u></h1>
<h2>Welcome every <a href="https://www.timeanddate.com/counters/multicountdown.html">TimeZone</a>!</h2>
<p>
<em>If you keep talking, we'll keep recording.</em>
</p>
<p>For those who don't know, on New Year's Eve <!--% get_new_year_show_start() %-->, we will have a recording going on the HPR Mumble server for anyone to come on and say "Happy New Year" and talk about whatever they want.
We will leave the recording going until at least <!--% get_new_year_show_end() %-->, and keep recording until the conversation stops.</p>
<p>
So please stop in. Say "Hi" and maybe join in the conversation with other HPR listeners and contributors. It's always a good time!!<br />
</p>
<h1>Listen to the live stream</h1>
<p>
<img src="images/livestream.png" /><br />
<audio controls autoplay>
<source src="https://files.shownotes.ooguy.com/stream" type="audio/mpeg" >
</audio> <br />
<a href="https://files.shownotes.ooguy.com/stream">https://files.shownotes.ooguy.com/stream</a><br />
<a href="https://hackerpublicradio.org/live">https://hackerpublicradio.org/live</a><br />
</p>
<h1>Join the conversation on Mumble</h1>
<h2>Ground Rules</h2>
<h3>Use Push to Talk, and a Headset</h3>
<p>With so many people on the chat, you must use push to talk.
You also need to use a Headset so the audio of the room is not fed back.
If there is a problem with your setup, then please drop and listen to the stream.
Trying to correct poor audio in post is a lot of work, that someone else will have to do.</p>
<h3>Be Polite</h3>
<p>When you enter the room please do not interrupt ongoing conversations.
Wait for a pause in the conversation and say Hi.
It's quite common for people you might not know to join as they wish to speak with other people in the room.
They may have been waiting all year for the chance to meet, so please give people the space to have these conversations.</p>
<h3>Do not announce the Time Zones</h3>
<p>As there are so <a moz-do-not-send="true" href="https://en.wikipedia.org/wiki/Time_zone">many Time Zones</a> there is no need to interrupt the conversation to announce every one.
If you are joining from a time zone that is currently switching to the New Year, then please wait for a pause and wish people Happy New Year.</p>
<h3>Don't Fill Dead Air</h3>
<p>This is a relaxed meet-up, and not a traditional "Radio Broadcast".
If there are no conversations going on at a given time, don't feel the need to "fill dead air".
Before the recording is posted as a podcast the <a href="https://manual.audacityteam.org/man/truncate_silence.html">silences will be truncated</a>.</p>
<h3>Do not monopolise the conversation</h3>
<p>Please be mindful that you (or your beverage of choice) may be speaking too much ;-).</p>
<h3>You are a Guest</h3>
<p>Be respectful, and remember that attendance on the New Year Show does not <a href="https://hub.hackerpublicradio.org/calendar.php">constitute a show</a>.</p>
<h3>CC-BY-SA</h3>
<p>The recording will be released as a podcast on HPR under a <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/"> Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)</a> license.
The recordings will be released mid year, when there is a lull in submissions.</p>
<h2>Mumble Setup</h2>
<p>Already have Mumble installed, then <a href="mumble://chatter.skyehaven.net:64738/Hacker%20Public%20Radio?version=1.2.0">this link will bring you to the HPR Room</a>.</p>
<p>Get the PC Client <a href="https://wiki.mumble.info/wiki/Main_Page">https://wiki.mumble.info/wiki/Main_Page</a><br />
<p>Get the Android Client Mumla <a href="https://f-droid.org/packages/se.lublin.mumla">https://f-droid.org/packages/se.lublin.mumla</a></p>
<p>Then join the Server: <strong>chatter.skyehaven.net</strong><br />
Port: <strong>64738</strong><br />
Channel: <strong>HPR</strong></p>
<p><strong>Use Push to Talk</strong>. With so many people hands free is not an option.</p>
<p>For those who have never used Mumble before, see this <a href="https://hackerpublicradio.org/mumble-howto">how-to explaining the setup of the desktop Mumble client</a>,
or listen to <a href="https://hackerpublicradio.org/eps.php?id=3503">hpr3503 :: Configuring Mumble</a>.</p>
<h3>Video Only (no audio) server</h3>
<p>
For those who wish to see the video of some of the participants we have a jitsi server.<br />
<a href="https://jitsi.minnix.dev/hprnye">https://jitsi.minnix.dev/hprnye</a><br />
<strong>Please make sure your mic is muted!</strong>
</p>
<h3>Add to the Show-notes</h2>
<p>
<strong>Please help fill out the show notes</strong><br />
<a href="https://shownotes.lugcast.mywire.org/p/hpr-nye-2026">https://shownotes.lugcast.mywire.org/p/hpr-nye-2026</a><br />
We are using Etherpad for people to share links and info to things they are discussing.
The Etherpad will be used for the HPR audio shownotes so please feel free to add to it.
The entire event will be broken into 2 to 3 hour segments and made available as HPR episodes.<br/>
<em>This really, really helps us out to post the show faster !</em>
</p>
<h2>History</h2>
<p>Suggested by <a href="https://hackerpublicradio.org/correspondents/0128.html">pokey</a> in 2011.
As podcasting tends to be a one way conversation, he thought it would be nice to get all the FLOSS, Linux, Free Culture, podcasters and Listeners in one place to get together and chat in person.
Initially it was planned to be just a few hours, but we kept missing members from other parts of the world.
The show was extended to welcome ever time zone to the New Year, which actually turns out to be <a href="https://en.wikipedia.org/wiki/List_of_UTC_offsets">38 Timezones</a>, over 26 Hours.</p>
<p>So we will record for at least 26 Hours, but will keep the recording going for an "After Show".
Some years the after show has been even longer than the show itself.
While this is on the HPR Site, it is entirely a community initiative which is supported heavily by the fine folks over at the <a href="https://linuxlugcast.com/">LinuxLugCast</a>, with the Mumble Server been provided by <a href="https://hackerpublicradio.org/correspondents/0228.html">Delwin</a>.</p>

View File

@@ -1,29 +0,0 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<div class="lane stack">
<article>
<header>
<h2 class="title"><a href="<!--% absolute_path(baseurl) %-->new_year.html">The <!--% get_new_year_show_ordinal_year %--> Annual 26 Hour New Year's Eve Show</a></h2>
</header>
<h3>Welcome every <a href="https://www.timeanddate.com/counters/multicountdown.html">TimeZone</a>!</h3>
<p>
<em>If you keep talking, we'll keep recording.</em>
</p>
<p>For those who don't know, on New Year's Eve <!--% get_new_year_show_start() %-->, we will have a recording going on the HPR Mumble server for anyone to come on and say "Happy New Year" and talk about whatever they want.
We will leave the recording going until at least <!--% get_new_year_show_end() %-->, and keep recording until the conversation stops.</p>
<p>
So please stop in. Say "Hi" and maybe join in the conversation with other HPR listeners and contributors. It's always a good time!!<br>
<h2>Listen to the live stream</h2>
<p>
<img src="images/livestream.png" /><br />
<audio controls autoplay>
<source src="https://files.shownotes.ooguy.com/stream" type="audio/mpeg" >
</audio> <br />
<a href="https://files.shownotes.ooguy.com/stream">https://files.shownotes.ooguy.com/stream</a><br />
<a href="https://hackerpublicradio.org/live">https://hackerpublicradio.org/live</a><br />
</p>
<p><a href="<!--% absolute_path(baseurl) %-->new_year.html">More information…</a></p>
</article>
</div>

View File

@@ -1,4 +1,3 @@
<!--% page_title = "HPR ~ Find a show" %-->
<section id="search">
<header>
<h1>Search HPR.</h1>

View File

@@ -1,7 +1,6 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% PROCESS "queries-series.tpl.html" %-->
<!--% USE DBI(constants.driver) %-->
<!--% page_title = "HPR ~ Show series" %-->
<h1 class="title">In-Depth Series</h1>
<div class="lane stack">
<!--% FOREACH series IN DBI.query(query_episodes) %-->
@@ -14,7 +13,6 @@
<li>Date of earliest show: <!--% series.earliest_show %--></li>
<li>Date of latest show: <!--% series.latest_show %--></li>
<li>Series RSS feeds: <a href="<!--% absolute_path(baseurl) %-->hpr_ogg_rss.php?series=<!--% series.id %-->">ogg</a>, <a href="<!--% absolute_path(baseurl) %-->hpr_spx_rss.php?series=<!--% series.id %-->">spx</a>, <a href="<!--% absolute_path(baseurl) %-->hpr_mp3_rss.php?series=<!--% series.id %-->">mp3</a></li>
<li><a href="<!--% absolute_url(baseurl,'//series') %-->/<!--% zero_pad_left(series.id) %-->.m3u8">Download the M3U playlist</a></li>
</ul>
<div class="series-description"><!--% series.description %--></div>
</article>

View File

@@ -6,7 +6,6 @@
%-->
<!--% series_result = query_series.execute(id) %-->
<!--% FOREACH series IN series_result %-->
<!--% page_title = "HPR ~ Series: " _ series.name %-->
<h1 class="title">In-Depth Series: <!--% series.name %--></h1>
<ul>
<li>Number of episodes: <!--% series.number_of_episodes %--></li>
@@ -14,7 +13,6 @@
<li>Date of earliest show: <!--% series.earliest_show %--></li>
<li>Date of latest show: <!--% series.latest_show %--></li>
<li>Series RSS feeds: <a href="<!--% absolute_path(baseurl) %-->hpr_ogg_rss.php?series=<!--% series.id %-->&full=1&gomax=1">ogg</a>, <a href="<!--% absolute_path(baseurl) %-->hpr_spx_rss.php?series=<!--% series.id %-->&full=1&gomax=1">spx</a>, <a href="<!--% absolute_path(baseurl) %-->hpr_mp3_rss.php?series=<!--% series.id %-->&full=1&gomax=1">mp3</a></li>
<li><a href="<!--% absolute_url(baseurl,'//series') %-->/<!--% zero_pad_left(series.id) %-->.m3u8">Download the M3U playlist</a></li>
</ul>
<p><em><!--% series.description %--></em></p>
<section id="series_episodes" class="lane stack">

View File

@@ -1,5 +1,4 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% page_title = "HPR ~ Sitemap" %-->
<h1>Sitemap</h1>
<ul>
<li><a href="<!--% hub_baseurl %-->calendar.php"><strong>⇧Upload⇧</strong></a> ← Upload Your Show</li>

View File

@@ -1,4 +1,3 @@
<!--% page_title = "HPR ~ Subscribe to our feeds" %-->
<h1>RSS Syndication</h1>
<h2>Subscribe to our Feeds</h2>
<p>

View File

@@ -6,23 +6,23 @@
<!--% query_episodes = DBI.prepare('
WITH episode_maxmin AS (
SELECT MAX(id) AS \'latest\', MIN(id) AS \'earliest\', ? AS \'id\'
FROM twt_eps AS eps
FROM twat_eps AS eps
),
episode_date AS (
SELECT eps.date
FROM twt_eps AS eps
FROM twat_eps AS eps
WHERE eps.id = ?
),
episode_previous AS (
SELECT MAX(id) AS \'previous\', ? AS \'id\'
FROM twt_eps AS eps
FROM twat_eps AS eps
INNER JOIN episode_date
ON eps.date < episode_date.date
WHERE eps.id > 1
),
episode_next AS (
SELECT MIN(id) AS \'next\', ? AS \'id\'
FROM twt_eps AS eps
FROM twat_eps AS eps
INNER JOIN episode_date
ON eps.date > episode_date.date
)
@@ -34,7 +34,7 @@
hosts.hostid, hosts.host,
miniseries.name AS \'series\', miniseries.id AS \'seriesid\',
miniseries.description AS \'series_description\'
FROM twt_eps AS eps
FROM twat_eps AS eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
INNER JOIN episode_maxmin ON eps.id = episode_maxmin.id
@@ -46,7 +46,6 @@
<!--% episode_result = query_episodes.execute(id, id, id, id, id) %-->
<!--% FOREACH episode IN episode_result %-->
<!--% episode_navigation = step_navigation(baseurl,episode,"twt") %-->
<!--% page_title = "TWT" _ episode.id _ "::" _ episode.title %-->
<article>
<header>
<h1><!--% episode.id %--> :: <!--% episode.title %--></h1>

View File

@@ -1,7 +0,0 @@
<!--% USE DBI(constants.driver, constants.user, constants.password) %-->
<!--% FOREACH series IN DBI.query(
'select s.id from miniseries as s'
) %-->
,<!--% series.id %-->
<!--% END %-->

View File

@@ -1,6 +1,6 @@
<!--% USE DBI(constants.driver) %-->
<!--% FOREACH episode IN DBI.query(
'select eps.id from twt_eps AS eps'
'select eps.id from twat_eps AS eps'
) %-->
,<!--% episode.id %-->
<!--% END %-->

View File

@@ -1,13 +0,0 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% PROCESS 'queries-series_episodes.tpl.html' %-->
<!--% USE DBI(constants.driver) %-->
<!--% results_hpr_shows = DBI.prepare(query_shows_sql) %-->
<!--% hpr_shows = results_hpr_shows.execute(id); %-->
<!--% FOREACH hpr_show IN hpr_shows; %-->
#EXTINF: <!--% hpr_show.duration %-->,<!--% hpr_show.host %--> - <!--% hpr_show.title %-->
<!--% media_path(hpr_show.id, 'hpr', 'mp3', baseurl, media_baseurl) %-->
<!--% END %-->

View File

@@ -1,18 +1,9 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% USE date %-->
<!--% page_title = make_sfw(is_sfw, "Hobby", "Hacker") _ " Public Radio (HPR) ~ The Technology Community Podcast" %-->
<!--% WRAPPER page
# The default page_title variable set above can be overridden
# in each individual $content template (i.e. templates/conten-<template name>.tpl.html)
# processed below.
%-->
<!--% PROCESS $content %-->
<!--% END %-->
<!--% BLOCK page %-->
<!DOCTYPE HTML>
<html lang="en">
<head>
<title><!--% page_title %--></title>
<title>Hacker Public Radio ~ The Technology Community Podcast</title>
<!--% IF baseurl %-->
<base href="<!--% baseurl %-->">
<!--% END %-->
@@ -20,12 +11,12 @@
<meta http-equiv="X-Clacks-Overhead" content="GNU Terry Pratchett" />
<meta http-equiv="last-modified" content="<!--% format_feed_date(date.now) %-->">
<meta name="keywords" content="Technology, Tech News, Education, Training" />
<meta name="description" content="<!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio is a podcast that releases shows every weekday Monday through Friday. Our shows are produced by the community (you) and can be on any topic that is of interest to <!--% make_sfw(is_sfw, "makers", "hackers") %--> and hobbyists." />
<link rel="shortcut icon" href="<!--% absolute_url(baseurl) %-->hpr.ico" >
<link rel="alternate" type="application/rss+xml" title="<!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio Opus RSS" href="<!--% absolute_path(baseurl) %-->hpr_opus_rss.php" />
<link rel="alternate" type="application/rss+xml" title="<!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio Ogg Vorbis RSS" href="<!--% absolute_path(baseurl) %-->hpr_ogg_rss.php" />
<link rel="alternate" type="application/rss+xml" title="<!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio MP3 RSS" href="<!--% absolute_path(baseurl) %-->hpr_mp3_rss.php" />
<link rel="alternate" type="application/rss+xml" title="<!--% make_sfw(is_sfw, "Hobby", "Hacker") %--> Public Radio Comments RSS" href="<!--% absolute_path(baseurl) %-->comments.rss" />
<meta name="description" content="Hacker Public Radio is a podcast that releases shows every weekday Monday through Friday. Our shows are produced by the community (you) and can be on any topic that is of interest to hackers and hobbyists." />
<link rel="shortcut icon" href="/hpr.ico" >
<link rel="alternate" type="application/rss+xml" title="Hacker Public Radio Opus RSS" href="<!--% absolute_path(baseurl) %-->hpr_opus_rss.php" />
<link rel="alternate" type="application/rss+xml" title="Hacker Public Radio Ogg Vorbis RSS" href="<!--% absolute_path(baseurl) %-->hpr_ogg_rss.php" />
<link rel="alternate" type="application/rss+xml" title="Hacker Public Radio MP3 RSS" href="<!--% absolute_path(baseurl) %-->hpr_mp3_rss.php" />
<link rel="alternate" type="application/rss+xml" title="Hacker Public Radio Comments RSS" href="<!--% absolute_path(baseurl) %-->comments.rss" />
<link rel="license" title="CC BY-SA 4.0" href="https://creativecommons.org/licenses/by-sa/4.0/" />
<link href="/css/hpr.css" rel="stylesheet" />
<!--[if IE]>
@@ -50,7 +41,7 @@
<hgroup id="title">
<h1 id="site_acronym"><a href="<!--% absolute_path(baseurl) %-->index.html">HPR</a></h1>
<p id="site_name">
<a href="<!--% absolute_path(baseurl) %-->correspondents/index.html">H</a><!--% make_sfw(is_sfw, "obby", "acker") %-->
<a href="<!--% absolute_path(baseurl) %-->correspondents/index.html">H</a>acker
<a href="<!--% absolute_path(baseurl) %-->comments_viewer.html">P</a>ublic
<a href="<!--% absolute_path(baseurl) %-->syndication.html">R</a>adio
</p>
@@ -63,8 +54,7 @@
</div>
</header>
<main id="main_content" role="main">
<!--% content %-->
<!--% page_title = "acck" %-->
<!--% INCLUDE $content %-->
</main>
<footer role="contentinfo">
<hr class="no-css">
@@ -80,4 +70,3 @@
</footer>
</body>
</html>
<!--% END %-->

View File

@@ -33,7 +33,7 @@
%-->
<!--% query_twt_show_count = '
SELECT COUNT(id) as Tally
FROM twt_eps AS eps
FROM twat_eps AS eps
WHERE eps.hostid = ?
'
%-->
@@ -48,7 +48,7 @@
hosts.hostid,
hosts.host, hosts.email, hosts.profile,
miniseries.name AS series, miniseries.id AS seriesid
FROM twt_eps AS eps
FROM twat_eps AS eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
WHERE hosts.hostid = ?

View File

@@ -1 +1 @@
<!--% query_tags = 'SELECT id, tags FROM eps WHERE eps.date <= date(\'now\')' %-->
<!--% query_tags = 'SELECT id, tags FROM eps' %-->

View File

@@ -134,57 +134,3 @@
<!--% END %-->
<span><a href="<!--% absolute_path(baseurl) %-->eps/<!--% folder %--><!--% zero_pad_left(links.latest) %-->/index.html" rel="last" aria-label="latest episode">Latest<span class="no-css"> &gt;&gt;</span><!--% INSERT $arrow_last %--></a></span></nav>
<!--% END %-->
<!--% MACRO get_new_year_show_start BLOCK %-->
<!--% start_year = date.format(date.now, '%Y') %-->
<!--% IF date.format(date.now, '%m') != 12 || date.format(date.now, '%j') == 1 %-->
<!--% start_year = start_year - 1 %-->
<!--% END %-->
<time datetime="<!--% start_year %-->-12-31T10:00Z"><!--% start_year %-->-12-31 10:00 UTC (5:00 AM EST)</time>
<!--% END %-->
<!--% MACRO get_new_year_show_end BLOCK %-->
<!--% end_year = date.format(date.now, '%Y') %-->
<!--% IF date.format(date.now, '%m') == 12 || date.format(date.now, '%j') == 1 %-->
<!--% end_year = end_year + 1 %-->
<!--% END %-->
<time datetime="<!--% end_year %-->-01-01T12:00Z"><!--% end_year %-->-01-01 12:00 UTC (7:00 AM EST)</time>
<!--% END %-->
<!--% MACRO get_new_year_show_ordinal_year BLOCK %-->
<!--% ordinal_year = date.format(date.now, '%Y') - 2011 %-->
<!--% IF date.format(date.now, '%m') != 12 || date.format(date.now, '%j') == 1 %-->
<!--% ordinal_year = ordinal_year - 1 %-->
<!--% END %-->
<!--% years.0 = 'th'
years.1 = 'st'
years.2 = 'nd'
years.3 = 'rd'
years.4 = 'th'
years.5 = 'th'
years.6 = 'th'
years.7 = 'th'
years.8 = 'th'
years.9 = 'th'
years.14 = 'Fourteenth'
years.15 = 'Fifteenth'
years.16 = 'Sixteenth'
years.17 = 'Seventeenth'
years.18 = 'Eighteenth'
years.19 = 'Nineteenth'
years.20 = 'Twentieth'
years.30 = 'Thirtieth'
years.40 = 'Fortieth'
years.50 = 'Fiftieth' %-->
<!--% index = ordinal_year % 10 %-->
<!--% IF ordinal_year < 21 || (index == 0 && ordinal_year < 51) %-->
<!--% years.$ordinal_year %-->
<!--% ELSE %-->
<!--% ordinal_year %--><sup><!--% years.$index %--></sup>
<!--% END %-->
<!--% END %-->
<!--% MACRO make_sfw(swf, sfw_text, nsfw_text) BLOCK %-->
<!--% IF swf == 0 %--><!--% sfw_text %-->
<!--% ELSE %--><!--% nsfw_text %-->
<!--% END %--><!--% END %-->