Compare commits
23 Commits
1dec9996f8
...
i316_Have-
| Author | SHA1 | Date | |
|---|---|---|---|
|
0a5dea473b
|
|||
| de316e7f6b | |||
|
7161b99eb0
|
|||
| 84e89a53ad | |||
|
5004f4fe88
|
|||
|
6520bdac8b
|
|||
| 2eece012fa | |||
|
3d74b6f084
|
|||
| 24f2b5f9ee | |||
|
020d6395c1
|
|||
| a40774b1e8 | |||
|
face5e1fbe
|
|||
|
7b9e8a94f0
|
|||
| 3ca1a903df | |||
|
ae5bfc12b4
|
|||
|
21c664ecf9
|
|||
|
c17ce1bf74
|
|||
|
c922ea6281
|
|||
|
98c51ee9fe
|
|||
| 904d14d083 | |||
|
7170015a0e
|
|||
|
23f91a0410
|
|||
|
2be718287f
|
1
_sql/sqlite/Rename_twt_table.sql
Normal file
1
_sql/sqlite/Rename_twt_table.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE twat_eps RENAME TO twt_eps;
|
||||||
@@ -736,10 +736,13 @@ fieldset > table td input[type="radio"] {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.series-description {
|
.series-description {
|
||||||
margin: 0;
|
margin: 0 0 1rem 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
.series-desciption > *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
.sr-only {
|
.sr-only {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
@@ -755,11 +758,14 @@ fieldset > table td input[type="radio"] {
|
|||||||
}
|
}
|
||||||
#show_notes pre
|
#show_notes pre
|
||||||
{
|
{
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
#show_notes code {
|
||||||
|
display: inline-block;
|
||||||
background-color: var(--show-notes-pre-background);
|
background-color: var(--show-notes-pre-background);
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
overflow: scroll;
|
|
||||||
padding: 0.1em 0;
|
padding: 0.1em 0;
|
||||||
}
|
}
|
||||||
nav.episodes {
|
nav.episodes {
|
||||||
color: var(--background-primary);
|
color: var(--background-primary);
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|||||||
@@ -1,315 +0,0 @@
|
|||||||
// @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
|
|
||||||
6
site.cfg
6
site.cfg
@@ -177,3 +177,9 @@ root_template: m3u.tpl.m3u8
|
|||||||
content: m3u-correspondent.tpl.m3u8
|
content: m3u-correspondent.tpl.m3u8
|
||||||
filename: correspondents/[id]/playlist.m3u8
|
filename: correspondents/[id]/playlist.m3u8
|
||||||
multipage: true
|
multipage: true
|
||||||
|
|
||||||
|
[series_episodes_m3u]
|
||||||
|
root_template: m3u.tpl.m3u8
|
||||||
|
content: m3u-series_episodes.tpl.m3u8
|
||||||
|
filename: series/[id].m3u8
|
||||||
|
multipage: true
|
||||||
|
|||||||
@@ -29,18 +29,10 @@
|
|||||||
<p><label>email:</label> <u><!--% this_host.email %--></u></p>
|
<p><label>email:</label> <u><!--% this_host.email %--></u></p>
|
||||||
<div><label>profile:</label> <!--% this_host.profile %--></div>
|
<div><label>profile:</label> <!--% this_host.profile %--></div>
|
||||||
<p><label>episodes:</label> <strong><!--% hpr_show_count + twt_show_count %--></strong></p>
|
<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>
|
||||||
<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>
|
||||||
<div class="lane stack">
|
<div id="episodes" class="lane stack">
|
||||||
<!--% FOREACH hpr_show IN hpr_shows; %-->
|
<!--% FOREACH hpr_show IN hpr_shows; %-->
|
||||||
<article>
|
<article>
|
||||||
<!--% show_summary(hpr_show, 'hide_host') %-->
|
<!--% show_summary(hpr_show, 'hide_host') %-->
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ Subscribe to the comments <a href="<!--% absolute_path(baseurl) %-->comments.rss
|
|||||||
<h2>Leave Comment</h2>
|
<h2>Leave Comment</h2>
|
||||||
<p>
|
<p>
|
||||||
<strong>Note to Verbose Commenters</strong><br />
|
<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>
|
||||||
<p>
|
<p>
|
||||||
<strong>Note to Spammers</strong><br />
|
<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>
|
</p>
|
||||||
<form method="POST" action="<!--% hub_baseurl %-->comment_confirm.php">
|
<form method="POST" action="<!--% hub_baseurl %-->comment_confirm.php">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
hosts.hostid,
|
hosts.hostid,
|
||||||
hosts.host, hosts.email, hosts.local_image,
|
hosts.host, hosts.email, hosts.local_image,
|
||||||
miniseries.name AS series, miniseries.id AS seriesid
|
miniseries.name AS series, miniseries.id AS seriesid
|
||||||
FROM twat_eps as eps
|
FROM twt_eps as eps
|
||||||
INNER JOIN hosts ON eps.hostid = hosts.hostid
|
INNER JOIN hosts ON eps.hostid = hosts.hostid
|
||||||
INNER JOIN miniseries ON eps.series = miniseries.id
|
INNER JOIN miniseries ON eps.series = miniseries.id
|
||||||
ORDER BY eps.id DESC
|
ORDER BY eps.id DESC
|
||||||
|
|||||||
@@ -104,6 +104,7 @@
|
|||||||
<!--% host_cnt = host_cnt + 1 %-->
|
<!--% host_cnt = host_cnt + 1 %-->
|
||||||
<!--% END %-->
|
<!--% END %-->
|
||||||
</dl>
|
</dl>
|
||||||
|
<p><a href="<!--% absolute_path(baseurl) %-->eps/index.html">More Episodes…</a></p>
|
||||||
</section>
|
</section>
|
||||||
<section id="latest_comments">
|
<section id="latest_comments">
|
||||||
<header><h2>Latest Comments</h2></header>
|
<header><h2>Latest Comments</h2></header>
|
||||||
@@ -138,5 +139,6 @@
|
|||||||
<dd>on hpr<!--% item.eps_id %--> (<!--% item.episode_date %-->) "<!--% item.episode_title %-->" by <!--% item.host %--></dd>
|
<dd>on hpr<!--% item.eps_id %--> (<!--% item.episode_date %-->) "<!--% item.episode_title %-->" by <!--% item.host %--></dd>
|
||||||
<!--% END %-->
|
<!--% END %-->
|
||||||
</dl>
|
</dl>
|
||||||
|
<p><a href="<!--% absolute_path(baseurl) %-->comments_viewer.html">More Comments…</a></p>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<li>Date of earliest show: <!--% series.earliest_show %--></li>
|
<li>Date of earliest show: <!--% series.earliest_show %--></li>
|
||||||
<li>Date of latest show: <!--% series.latest_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>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>
|
</ul>
|
||||||
<div class="series-description"><!--% series.description %--></div>
|
<div class="series-description"><!--% series.description %--></div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<li>Date of earliest show: <!--% series.earliest_show %--></li>
|
<li>Date of earliest show: <!--% series.earliest_show %--></li>
|
||||||
<li>Date of latest show: <!--% series.latest_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>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>
|
</ul>
|
||||||
<p><em><!--% series.description %--></em></p>
|
<p><em><!--% series.description %--></em></p>
|
||||||
<section id="series_episodes" class="lane stack">
|
<section id="series_episodes" class="lane stack">
|
||||||
|
|||||||
@@ -6,23 +6,23 @@
|
|||||||
<!--% query_episodes = DBI.prepare('
|
<!--% query_episodes = DBI.prepare('
|
||||||
WITH episode_maxmin AS (
|
WITH episode_maxmin AS (
|
||||||
SELECT MAX(id) AS \'latest\', MIN(id) AS \'earliest\', ? AS \'id\'
|
SELECT MAX(id) AS \'latest\', MIN(id) AS \'earliest\', ? AS \'id\'
|
||||||
FROM twat_eps AS eps
|
FROM twt_eps AS eps
|
||||||
),
|
),
|
||||||
episode_date AS (
|
episode_date AS (
|
||||||
SELECT eps.date
|
SELECT eps.date
|
||||||
FROM twat_eps AS eps
|
FROM twt_eps AS eps
|
||||||
WHERE eps.id = ?
|
WHERE eps.id = ?
|
||||||
),
|
),
|
||||||
episode_previous AS (
|
episode_previous AS (
|
||||||
SELECT MAX(id) AS \'previous\', ? AS \'id\'
|
SELECT MAX(id) AS \'previous\', ? AS \'id\'
|
||||||
FROM twat_eps AS eps
|
FROM twt_eps AS eps
|
||||||
INNER JOIN episode_date
|
INNER JOIN episode_date
|
||||||
ON eps.date < episode_date.date
|
ON eps.date < episode_date.date
|
||||||
WHERE eps.id > 1
|
WHERE eps.id > 1
|
||||||
),
|
),
|
||||||
episode_next AS (
|
episode_next AS (
|
||||||
SELECT MIN(id) AS \'next\', ? AS \'id\'
|
SELECT MIN(id) AS \'next\', ? AS \'id\'
|
||||||
FROM twat_eps AS eps
|
FROM twt_eps AS eps
|
||||||
INNER JOIN episode_date
|
INNER JOIN episode_date
|
||||||
ON eps.date > episode_date.date
|
ON eps.date > episode_date.date
|
||||||
)
|
)
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
hosts.hostid, hosts.host,
|
hosts.hostid, hosts.host,
|
||||||
miniseries.name AS \'series\', miniseries.id AS \'seriesid\',
|
miniseries.name AS \'series\', miniseries.id AS \'seriesid\',
|
||||||
miniseries.description AS \'series_description\'
|
miniseries.description AS \'series_description\'
|
||||||
FROM twat_eps AS eps
|
FROM twt_eps AS eps
|
||||||
INNER JOIN hosts ON eps.hostid = hosts.hostid
|
INNER JOIN hosts ON eps.hostid = hosts.hostid
|
||||||
INNER JOIN miniseries ON eps.series = miniseries.id
|
INNER JOIN miniseries ON eps.series = miniseries.id
|
||||||
INNER JOIN episode_maxmin ON eps.id = episode_maxmin.id
|
INNER JOIN episode_maxmin ON eps.id = episode_maxmin.id
|
||||||
|
|||||||
7
templates/ids-series_episodes_m3u.tpl.html
Normal file
7
templates/ids-series_episodes_m3u.tpl.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<!--% USE DBI(constants.driver, constants.user, constants.password) %-->
|
||||||
|
<!--% FOREACH series IN DBI.query(
|
||||||
|
'select s.id from miniseries as s'
|
||||||
|
) %-->
|
||||||
|
,<!--% series.id %-->
|
||||||
|
<!--% END %-->
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<!--% USE DBI(constants.driver) %-->
|
<!--% USE DBI(constants.driver) %-->
|
||||||
<!--% FOREACH episode IN DBI.query(
|
<!--% FOREACH episode IN DBI.query(
|
||||||
'select eps.id from twat_eps AS eps'
|
'select eps.id from twt_eps AS eps'
|
||||||
) %-->
|
) %-->
|
||||||
,<!--% episode.id %-->
|
,<!--% episode.id %-->
|
||||||
<!--% END %-->
|
<!--% END %-->
|
||||||
|
|||||||
13
templates/m3u-series_episodes.tpl.m3u8
Normal file
13
templates/m3u-series_episodes.tpl.m3u8
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!--% 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 %-->
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
%-->
|
%-->
|
||||||
<!--% query_twt_show_count = '
|
<!--% query_twt_show_count = '
|
||||||
SELECT COUNT(id) as Tally
|
SELECT COUNT(id) as Tally
|
||||||
FROM twat_eps AS eps
|
FROM twt_eps AS eps
|
||||||
WHERE eps.hostid = ?
|
WHERE eps.hostid = ?
|
||||||
'
|
'
|
||||||
%-->
|
%-->
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
hosts.hostid,
|
hosts.hostid,
|
||||||
hosts.host, hosts.email, hosts.profile,
|
hosts.host, hosts.email, hosts.profile,
|
||||||
miniseries.name AS series, miniseries.id AS seriesid
|
miniseries.name AS series, miniseries.id AS seriesid
|
||||||
FROM twat_eps AS eps
|
FROM twt_eps AS eps
|
||||||
INNER JOIN hosts ON eps.hostid = hosts.hostid
|
INNER JOIN hosts ON eps.hostid = hosts.hostid
|
||||||
INNER JOIN miniseries ON eps.series = miniseries.id
|
INNER JOIN miniseries ON eps.series = miniseries.id
|
||||||
WHERE hosts.hostid = ?
|
WHERE hosts.hostid = ?
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<!--% query_tags = 'SELECT id, tags FROM eps' %-->
|
<!--% query_tags = 'SELECT id, tags FROM eps WHERE eps.date <= date(\'now\')' %-->
|
||||||
|
|||||||
Reference in New Issue
Block a user