Render feed from episode (#9)

- Render chapter if present (#5)
- Render transcript if present (#8)
- Require transcript type if URL entered (#8)
This commit is contained in:
Daniel J. Summers 2022-06-27 20:34:30 -04:00
parent 80c65bcad6
commit 16603bbcaf
4 changed files with 99 additions and 47 deletions

View File

@ -233,22 +233,24 @@ type SQLiteWebLogData (conn : SqliteConnection) =
let postSubQuery = subQuery "post" let postSubQuery = subQuery "post"
let pageSubQuery = subQuery "page" let pageSubQuery = subQuery "page"
cmd.CommandText <- cmd.CommandText <-
$"""DELETE FROM post_comment WHERE post_id IN {postSubQuery}; $"""DELETE FROM post_comment WHERE post_id IN {postSubQuery};
DELETE FROM post_revision WHERE post_id IN {postSubQuery}; DELETE FROM post_revision WHERE post_id IN {postSubQuery};
DELETE FROM post_episode WHERE post_id IN {postSubQuery}; DELETE FROM post_permalink WHERE post_id IN {postSubQuery};
DELETE FROM post_tag WHERE post_id IN {postSubQuery}; DELETE FROM post_episode WHERE post_id IN {postSubQuery};
DELETE FROM post_category WHERE post_id IN {postSubQuery}; DELETE FROM post_tag WHERE post_id IN {postSubQuery};
DELETE FROM post_meta WHERE post_id IN {postSubQuery}; DELETE FROM post_category WHERE post_id IN {postSubQuery};
DELETE FROM post WHERE web_log_id = @webLogId; DELETE FROM post_meta WHERE post_id IN {postSubQuery};
DELETE FROM page_revision WHERE page_id IN {pageSubQuery}; DELETE FROM post WHERE web_log_id = @webLogId;
DELETE FROM page_meta WHERE page_id IN {pageSubQuery}; DELETE FROM page_revision WHERE page_id IN {pageSubQuery};
DELETE FROM page WHERE web_log_id = @webLogId; DELETE FROM page_permalink WHERE page_id IN {pageSubQuery};
DELETE FROM category WHERE web_log_id = @webLogId; DELETE FROM page_meta WHERE page_id IN {pageSubQuery};
DELETE FROM tag_map WHERE web_log_id = @webLogId; DELETE FROM page WHERE web_log_id = @webLogId;
DELETE FROM web_log_user WHERE web_log_id = @webLogId; DELETE FROM category WHERE web_log_id = @webLogId;
DELETE FROM tag_map WHERE web_log_id = @webLogId;
DELETE FROM web_log_user WHERE web_log_id = @webLogId;
DELETE FROM web_log_feed_podcast WHERE feed_id IN {subQuery "web_log_feed"}; DELETE FROM web_log_feed_podcast WHERE feed_id IN {subQuery "web_log_feed"};
DELETE FROM web_log_feed WHERE web_log_id = @webLogId; DELETE FROM web_log_feed WHERE web_log_id = @webLogId;
DELETE FROM web_log WHERE id = @webLogId""" DELETE FROM web_log WHERE id = @webLogId"""
do! write cmd do! write cmd
} }

View File

@ -76,6 +76,9 @@ module private Namespace =
/// iTunes elements /// iTunes elements
let iTunes = "http://www.itunes.com/dtds/podcast-1.0.dtd" let iTunes = "http://www.itunes.com/dtds/podcast-1.0.dtd"
/// Podcast Index (AKA "podcasting 2.0")
let podcast = "https://podcastindex.org/namespace/1.0"
/// Enables chapters /// Enables chapters
let psc = "http://podlove.org/simple-chapters/" let psc = "http://podlove.org/simple-chapters/"
@ -127,36 +130,23 @@ let private toFeedItem webLog (authors : MetaItem list) (cats : DisplayCategory[
item item
/// Add episode information to a podcast feed item /// Add episode information to a podcast feed item
let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : SyndicationItem) = let private addEpisode webLog (podcast : PodcastOptions) (episode : Episode) (post : Post) (item : SyndicationItem) =
let podcast = Option.get feed.podcast // Convert non-absolute URLs to an absolute URL for this web log
let meta name = post.metadata |> List.tryFind (fun it -> it.name = name) let toAbsolute (link : string) = if link.StartsWith "http" then link else WebLog.absoluteUrl webLog (Permalink link)
let value (item : MetaItem) = item.value
let epMediaUrl = let epMediaUrl =
match (meta >> Option.get >> value) "episode_media_file" with match episode.media with
| link when link.StartsWith "http" -> link | link when link.StartsWith "http" -> link
| link when Option.isSome podcast.mediaBaseUrl -> $"{podcast.mediaBaseUrl.Value}{link}" | link when Option.isSome podcast.mediaBaseUrl -> $"{podcast.mediaBaseUrl.Value}{link}"
| link -> WebLog.absoluteUrl webLog (Permalink link) | link -> WebLog.absoluteUrl webLog (Permalink link)
let epMediaType = let epMediaType = [ episode.mediaType; podcast.defaultMediaType ] |> List.tryFind Option.isSome |> Option.flatten
match meta "episode_media_type", podcast.defaultMediaType with let epImageUrl = defaultArg episode.imageUrl (Permalink.toString podcast.imageUrl) |> toAbsolute
| Some epType, _ -> Some epType.value let epExplicit = defaultArg episode.explicit podcast.explicit |> ExplicitRating.toString
| None, Some defType -> Some defType
| _ -> None
let epImageUrl =
match defaultArg ((meta >> Option.map value) "episode_image") (Permalink.toString podcast.imageUrl) with
| link when link.StartsWith "http" -> link
| link -> WebLog.absoluteUrl webLog (Permalink link)
let epExplicit =
try
(meta >> Option.map (value >> ExplicitRating.parse)) "episode_explicit"
|> Option.defaultValue podcast.explicit
|> ExplicitRating.toString
with :? ArgumentException -> ExplicitRating.toString podcast.explicit
let xmlDoc = XmlDocument () let xmlDoc = XmlDocument ()
let enclosure = let enclosure =
let it = xmlDoc.CreateElement "enclosure" let it = xmlDoc.CreateElement "enclosure"
it.SetAttribute ("url", epMediaUrl) it.SetAttribute ("url", epMediaUrl)
meta "episode_media_length" |> Option.iter (fun len -> it.SetAttribute ("length", len.value)) it.SetAttribute ("length", string episode.length)
epMediaType |> Option.iter (fun typ -> it.SetAttribute ("type", typ)) epMediaType |> Option.iter (fun typ -> it.SetAttribute ("type", typ))
it it
let image = let image =
@ -169,10 +159,57 @@ let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : Syndicat
item.ElementExtensions.Add ("creator", Namespace.dc, podcast.displayedAuthor) item.ElementExtensions.Add ("creator", Namespace.dc, podcast.displayedAuthor)
item.ElementExtensions.Add ("author", Namespace.iTunes, podcast.displayedAuthor) item.ElementExtensions.Add ("author", Namespace.iTunes, podcast.displayedAuthor)
item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit) item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit)
meta "episode_subtitle" episode.subtitle |> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it))
|> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it.value)) episode.duration
meta "episode_duration" |> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.ToString """hh\:mm\:ss"""))
|> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.value))
match episode.chapterFile with
| Some chapters ->
let url = toAbsolute chapters
let typ =
match episode.chapterType with
| Some mime -> Some mime
| None when chapters.EndsWith ".json" -> Some "application/json+chapters"
| None -> None
let elt = xmlDoc.CreateElement ("podcast", "chapters", Namespace.podcast)
elt.SetAttribute ("url", url)
typ |> Option.iter (fun it -> elt.SetAttribute ("type", it))
item.ElementExtensions.Add elt
| None -> ()
match episode.transcriptUrl with
| Some transcript ->
let url = toAbsolute transcript
let elt = xmlDoc.CreateElement ("podcast", "transcript", Namespace.podcast)
elt.SetAttribute ("url", url)
elt.SetAttribute ("type", Option.get episode.transcriptType)
episode.transcriptLang |> Option.iter (fun it -> elt.SetAttribute ("language", it))
if defaultArg episode.transcriptCaptions false then
elt.SetAttribute ("rel", "captions")
item.ElementExtensions.Add elt
| None -> ()
match episode.seasonNumber with
| Some season ->
match episode.seasonDescription with
| Some desc ->
let elt = xmlDoc.CreateElement ("podcast", "season", Namespace.podcast)
elt.SetAttribute ("name", desc)
elt.InnerText <- string season
item.ElementExtensions.Add elt
| None -> item.ElementExtensions.Add ("season", Namespace.podcast, string season)
| None -> ()
match episode.episodeNumber with
| Some epNumber ->
match episode.episodeDescription with
| Some desc ->
let elt = xmlDoc.CreateElement ("podcast", "episode", Namespace.podcast)
elt.SetAttribute ("name", desc)
elt.InnerText <- string epNumber
item.ElementExtensions.Add elt
| None -> item.ElementExtensions.Add ("episode", Namespace.podcast, string epNumber)
| None -> ()
if post.metadata |> List.exists (fun it -> it.name = "chapter") then if post.metadata |> List.exists (fun it -> it.name = "chapter") then
try try
@ -216,7 +253,12 @@ let private addPodcast webLog (rssFeed : SyndicationFeed) (feed : CustomFeed) =
let xmlDoc = XmlDocument () let xmlDoc = XmlDocument ()
[ "dc", Namespace.dc; "itunes", Namespace.iTunes; "psc", Namespace.psc; "rawvoice", Namespace.rawVoice ] [ "dc", Namespace.dc
"itunes", Namespace.iTunes
"podcast", Namespace.podcast
"psc", Namespace.psc
"rawvoice", Namespace.rawVoice
]
|> List.iter (fun (alias, nsUrl) -> addNamespace rssFeed alias nsUrl) |> List.iter (fun (alias, nsUrl) -> addNamespace rssFeed alias nsUrl)
let categorization = let categorization =
@ -314,10 +356,9 @@ let createFeed (feedType : FeedType) posts : HttpHandler = fun next ctx -> backg
let toItem post = let toItem post =
let item = toFeedItem webLog authors cats tagMaps post let item = toFeedItem webLog authors cats tagMaps post
match podcast with match podcast, post.episode with
| Some feed when post.metadata |> List.exists (fun it -> it.name = "episode_media_file") -> | Some feed, Some episode -> addEpisode webLog (Option.get feed.podcast) episode post item
addEpisode webLog feed post item | Some _, _ ->
| Some _ ->
warn "Feed" ctx $"[{webLog.name} {Permalink.toString self}] \"{stripHtml post.title}\" has no media" warn "Feed" ctx $"[{webLog.name} {Permalink.toString self}] \"{stripHtml post.title}\" has no media"
item item
| _ -> item | _ -> item

View File

@ -157,7 +157,8 @@
<div class="col-12 col-md-8 pb-3"> <div class="col-12 col-md-8 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="transcriptUrl" id="transcriptUrl" class="form-control" <input type="text" name="transcriptUrl" id="transcriptUrl" class="form-control"
placeholder="Transcript URL" value="{{ model.transcript_url }}"> placeholder="Transcript URL" value="{{ model.transcript_url }}"
onkeyup="Admin.requireTranscriptType()">
<label for="transcriptUrl">Transcript URL</label> <label for="transcriptUrl">Transcript URL</label>
<div class="form-text">Optional; relative URL served from this web log</div> <div class="form-text">Optional; relative URL served from this web log</div>
</div> </div>
@ -165,7 +166,8 @@
<div class="col-12 col-md-4 pb-3"> <div class="col-12 col-md-4 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="transcriptType" id="transcriptType" class="form-control" <input type="text" name="transcriptType" id="transcriptType" class="form-control"
placeholder="Transcript Type" value="{{ model.transcript_type }}"> placeholder="Transcript Type" value="{{ model.transcript_type }}"
{%- if model.transcript_url != "" %} required{% endif %}>
<label for="transcriptType">Transcript MIME Type</label> <label for="transcriptType">Transcript MIME Type</label>
<div class="form-text">Recommended if transcript file provided</div> <div class="form-text">Recommended if transcript file provided</div>
</div> </div>

View File

@ -255,6 +255,13 @@
document.getElementById(`link_${idx}`).remove() document.getElementById(`link_${idx}`).remove()
}, },
/**
* Require transcript type if transcript URL is present
*/
requireTranscriptType() {
document.getElementById("transcriptType").required = document.getElementById("transcriptUrl").value.trim() !== ""
},
/** /**
* Show messages that may have come with an htmx response * Show messages that may have come with an htmx response
* @param messages The messages from the response * @param messages The messages from the response