Skip to content

Commit

Permalink
Render feed from episode (#9)
Browse files Browse the repository at this point in the history
- Render chapter if present (#5)
- Render transcript if present (#8)
- Require transcript type if URL entered (#8)
  • Loading branch information
danieljsummers committed Jun 28, 2022
1 parent 80c65bc commit 16603bb
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 47 deletions.
32 changes: 17 additions & 15 deletions src/MyWebLog.Data/SQLite/SQLiteWebLogData.fs
Original file line number Diff line number Diff line change
Expand Up @@ -233,22 +233,24 @@ type SQLiteWebLogData (conn : SqliteConnection) =
let postSubQuery = subQuery "post"
let pageSubQuery = subQuery "page"
cmd.CommandText <-
$"""DELETE FROM post_comment 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_tag WHERE post_id IN {postSubQuery};
DELETE FROM post_category WHERE post_id IN {postSubQuery};
DELETE FROM post_meta WHERE post_id IN {postSubQuery};
DELETE FROM post WHERE web_log_id = @webLogId;
DELETE FROM page_revision WHERE page_id IN {pageSubQuery};
DELETE FROM page_meta WHERE page_id IN {pageSubQuery};
DELETE FROM page 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 post_comment WHERE post_id IN {postSubQuery};
DELETE FROM post_revision WHERE post_id IN {postSubQuery};
DELETE FROM post_permalink WHERE post_id IN {postSubQuery};
DELETE FROM post_episode WHERE post_id IN {postSubQuery};
DELETE FROM post_tag WHERE post_id IN {postSubQuery};
DELETE FROM post_category WHERE post_id IN {postSubQuery};
DELETE FROM post_meta WHERE post_id IN {postSubQuery};
DELETE FROM post WHERE web_log_id = @webLogId;
DELETE FROM page_revision WHERE page_id IN {pageSubQuery};
DELETE FROM page_permalink WHERE page_id IN {pageSubQuery};
DELETE FROM page_meta WHERE page_id IN {pageSubQuery};
DELETE FROM page 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 WHERE web_log_id = @webLogId;
DELETE FROM web_log WHERE id = @webLogId"""
DELETE FROM web_log_feed WHERE web_log_id = @webLogId;
DELETE FROM web_log WHERE id = @webLogId"""
do! write cmd
}

Expand Down
101 changes: 71 additions & 30 deletions src/MyWebLog/Handlers/Feed.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ module private Namespace =
/// iTunes elements
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
let psc = "http://podlove.org/simple-chapters/"

Expand Down Expand Up @@ -127,36 +130,23 @@ let private toFeedItem webLog (authors : MetaItem list) (cats : DisplayCategory[
item

/// Add episode information to a podcast feed item
let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : SyndicationItem) =
let podcast = Option.get feed.podcast
let meta name = post.metadata |> List.tryFind (fun it -> it.name = name)
let value (item : MetaItem) = item.value
let private addEpisode webLog (podcast : PodcastOptions) (episode : Episode) (post : Post) (item : SyndicationItem) =
// Convert non-absolute URLs to an absolute URL for this web log
let toAbsolute (link : string) = if link.StartsWith "http" then link else WebLog.absoluteUrl webLog (Permalink link)
let epMediaUrl =
match (meta >> Option.get >> value) "episode_media_file" with
match episode.media with
| link when link.StartsWith "http" -> link
| link when Option.isSome podcast.mediaBaseUrl -> $"{podcast.mediaBaseUrl.Value}{link}"
| link -> WebLog.absoluteUrl webLog (Permalink link)
let epMediaType =
match meta "episode_media_type", podcast.defaultMediaType with
| Some epType, _ -> Some epType.value
| 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 epMediaType = [ episode.mediaType; podcast.defaultMediaType ] |> List.tryFind Option.isSome |> Option.flatten
let epImageUrl = defaultArg episode.imageUrl (Permalink.toString podcast.imageUrl) |> toAbsolute
let epExplicit = defaultArg episode.explicit podcast.explicit |> ExplicitRating.toString

let xmlDoc = XmlDocument ()
let enclosure =
let it = xmlDoc.CreateElement "enclosure"
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))
it
let image =
Expand All @@ -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 ("author", Namespace.iTunes, podcast.displayedAuthor)
item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit)
meta "episode_subtitle"
|> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it.value))
meta "episode_duration"
|> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.value))
episode.subtitle |> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it))
episode.duration
|> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.ToString """hh\:mm\:ss"""))

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
try
Expand Down Expand Up @@ -216,7 +253,12 @@ let private addPodcast webLog (rssFeed : SyndicationFeed) (feed : CustomFeed) =

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)

let categorization =
Expand Down Expand Up @@ -314,10 +356,9 @@ let createFeed (feedType : FeedType) posts : HttpHandler = fun next ctx -> backg

let toItem post =
let item = toFeedItem webLog authors cats tagMaps post
match podcast with
| Some feed when post.metadata |> List.exists (fun it -> it.name = "episode_media_file") ->
addEpisode webLog feed post item
| Some _ ->
match podcast, post.episode with
| Some feed, Some episode -> addEpisode webLog (Option.get feed.podcast) episode post item
| Some _, _ ->
warn "Feed" ctx $"[{webLog.name} {Permalink.toString self}] \"{stripHtml post.title}\" has no media"
item
| _ -> item
Expand Down
6 changes: 4 additions & 2 deletions src/admin-theme/post-edit.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,17 @@
<div class="col-12 col-md-8 pb-3">
<div class="form-floating">
<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>
<div class="form-text">Optional; relative URL served from this web log</div>
</div>
</div>
<div class="col-12 col-md-4 pb-3">
<div class="form-floating">
<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>
<div class="form-text">Recommended if transcript file provided</div>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/admin-theme/wwwroot/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,13 @@
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
* @param messages The messages from the response
Expand Down

0 comments on commit 16603bb

Please sign in to comment.