Chapters (from podcast 2.0 spec) #11

Merged
olaven merged 3 commits from feat/podcast-2.0-compliancy into main 2023-03-21 07:59:07 +00:00
6 changed files with 76 additions and 8 deletions
Showing only changes of commit 3e23bf443e - Show all commits

View File

@ -55,6 +55,16 @@
"https://deno.land/std@0.152.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239", "https://deno.land/std@0.152.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239",
"https://deno.land/std@0.152.0/async/tee.ts": "bcfae0017ebb718cf4eef9e2420e8675d91cb1bcc0ed9b668681af6e6caad846", "https://deno.land/std@0.152.0/async/tee.ts": "bcfae0017ebb718cf4eef9e2420e8675d91cb1bcc0ed9b668681af6e6caad846",
"https://deno.land/std@0.152.0/http/server.ts": "0b0a9f3abfcfecead944b31ee9098a0c11a59b0495bf873ee200eb80e7441483", "https://deno.land/std@0.152.0/http/server.ts": "0b0a9f3abfcfecead944b31ee9098a0c11a59b0495bf873ee200eb80e7441483",
"https://deno.land/std@0.180.0/datetime/_common.ts": "f5c1cb784c616151a3d8198a4ab29f65b7fe5c20a105d8979bde9558c7b52910",
"https://deno.land/std@0.180.0/datetime/constants.ts": "0e91a7019814e6df5ebb710b09aceb79f52cbc618bb06b34431f4f8b89d79505",
"https://deno.land/std@0.180.0/datetime/day_of_year.ts": "f9b9c8f1958e843fa072510fbb36e455431a3cc74ab8f39757bd0a59f4aaad10",
"https://deno.land/std@0.180.0/datetime/difference.ts": "556a5edb807f36f0ca334f235180650ccf05dfeb94aa2573642ef20314ef101c",
"https://deno.land/std@0.180.0/datetime/format.ts": "416807ca1971c5432bfa7d1e429b7c6360cb81fee598e8510dde6e7b0e819a57",
"https://deno.land/std@0.180.0/datetime/is_leap.ts": "cc74a158e029ffe1dfcbdd911107a6de428ee64585b1071d8fd4e1a997670d30",
"https://deno.land/std@0.180.0/datetime/mod.ts": "858d0d695b3a391a9c5faa561e46813e346feaef65bd6ddadb7ee2e65ac4e5a8",
"https://deno.land/std@0.180.0/datetime/parse.ts": "b0af5e272382e516f34dddf9dd1eda31f1197bdf8ee2ff7d93505073e97bd76b",
"https://deno.land/std@0.180.0/datetime/to_imf.ts": "851d072a1c86c7ec34c753e61b1951ceda50c9da805b170a0ecaea3b024dd266",
"https://deno.land/std@0.180.0/datetime/week_of_year.ts": "00ce4203f1efc69e2c0902ff4a4451a49d8a87a561e941b692f3b5f056f1f441",
"https://deno.land/x/code_block_writer@11.0.3/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", "https://deno.land/x/code_block_writer@11.0.3/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5",
"https://deno.land/x/code_block_writer@11.0.3/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", "https://deno.land/x/code_block_writer@11.0.3/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff",
"https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6",
@ -110,6 +120,7 @@
"https://deno.land/x/ts_morph@16.0.0/ts_morph.d.ts": "38668b0e3780282a56a805425494490b0045d1928bd040c47a94095749dab8c3", "https://deno.land/x/ts_morph@16.0.0/ts_morph.d.ts": "38668b0e3780282a56a805425494490b0045d1928bd040c47a94095749dab8c3",
"https://deno.land/x/ts_morph@16.0.0/ts_morph.js": "9fc0f3d6a3997c2df023fabc4e529d2117d214ffd4fd04247ca2f56c4e9cd470", "https://deno.land/x/ts_morph@16.0.0/ts_morph.js": "9fc0f3d6a3997c2df023fabc4e529d2117d214ffd4fd04247ca2f56c4e9cd470",
"https://esm.sh/*preact-render-to-string@5.2.4": "5c965103e5039039a89c5de1f551a690aecd3918afe53b5d967ce5d96d939584", "https://esm.sh/*preact-render-to-string@5.2.4": "5c965103e5039039a89c5de1f551a690aecd3918afe53b5d967ce5d96d939584",
"https://esm.sh/iso8601-duration@2.1.1": "111246e7e1d01ec7d70bf0f84be58d00d09cb1b88c2136f98d05046aea8376a2",
"https://esm.sh/preact@10.11.0": "e888b244446037c56f1881173fb51d1f5fa7aae5599e6c5154619346a6a5094e", "https://esm.sh/preact@10.11.0": "e888b244446037c56f1881173fb51d1f5fa7aae5599e6c5154619346a6a5094e",
"https://esm.sh/preact@10.11.0/hooks": "2b8ec155eb8b87501663f074acff1d55a9114fa7d88f0b39da06c940af1ff736", "https://esm.sh/preact@10.11.0/hooks": "2b8ec155eb8b87501663f074acff1d55a9114fa7d88f0b39da06c940af1ff736",
"https://esm.sh/preact@10.11.0/jsx-runtime": "5c123264f19799ab243211132dded45f6d42d594b5c78dd585f947d07bf20eae", "https://esm.sh/preact@10.11.0/jsx-runtime": "5c123264f19799ab243211132dded45f6d42d594b5c78dd585f947d07bf20eae",
@ -119,6 +130,8 @@
"https://esm.sh/twind@0.16.17": "519a5a4d20ff4f797ddf747af70a5600b3581c50a6edf38a4d77e8d7b4caff74", "https://esm.sh/twind@0.16.17": "519a5a4d20ff4f797ddf747af70a5600b3581c50a6edf38a4d77e8d7b4caff74",
"https://esm.sh/twind@0.16.17/sheets": "5a2b275c294ceffb1b09f2cc367b386172b6ccaa14273dbde473865d4d3f6721", "https://esm.sh/twind@0.16.17/sheets": "5a2b275c294ceffb1b09f2cc367b386172b6ccaa14273dbde473865d4d3f6721",
"https://esm.sh/v111/csstype@3.1.1/index.d.ts": "1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383", "https://esm.sh/v111/csstype@3.1.1/index.d.ts": "1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383",
"https://esm.sh/v111/iso8601-duration@2.1.1/deno/iso8601-duration.js": "5acc3e5498178f378a2b9e724c6493bd92310f369056293dbc49cc90adb8e3b6",
"https://esm.sh/v111/iso8601-duration@2.1.1/lib/index.d.ts": "0765e891e321959e930ffd832d20e7937eb8b279e1fc9cf15b35b0db0c7b8541",
"https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/deno/preact-render-to-string.js": "7651121e4bb5ef2a48f840dba3f0c13087293ca2eb825aa23ffea3a87bd463b3", "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/deno/preact-render-to-string.js": "7651121e4bb5ef2a48f840dba3f0c13087293ca2eb825aa23ffea3a87bd463b3",
"https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0", "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0",
"https://esm.sh/v111/preact@10.11.0/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3", "https://esm.sh/v111/preact@10.11.0/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3",
@ -129,6 +142,7 @@
"https://esm.sh/v111/twind@0.16.17/deno/sheets.js": "c504a460f5df2954f7334a821ef3ac9bedfc94dcc3f0dbf60af8d22f000391db", "https://esm.sh/v111/twind@0.16.17/deno/sheets.js": "c504a460f5df2954f7334a821ef3ac9bedfc94dcc3f0dbf60af8d22f000391db",
"https://esm.sh/v111/twind@0.16.17/deno/twind.js": "076dfc344b9507b3f5ee8a20d0b1e6eeb2ea3c6688649026ed0e13e4de158ff2", "https://esm.sh/v111/twind@0.16.17/deno/twind.js": "076dfc344b9507b3f5ee8a20d0b1e6eeb2ea3c6688649026ed0e13e4de158ff2",
"https://esm.sh/v111/twind@0.16.17/sheets/sheets.d.ts": "103ecfb19311b86eb5f7a4899f6a3b1192a84cfb73b6ff9a8b84d78b053e6704", "https://esm.sh/v111/twind@0.16.17/sheets/sheets.d.ts": "103ecfb19311b86eb5f7a4899f6a3b1192a84cfb73b6ff9a8b84d78b053e6704",
"https://esm.sh/v111/twind@0.16.17/twind.d.ts": "35492f009a6d8695748d121d0b3b210b0819a663eafc77d68296d3110d6ec27f" "https://esm.sh/v111/twind@0.16.17/twind.d.ts": "35492f009a6d8695748d121d0b3b210b0819a663eafc77d68296d3110d6ec27f",
"https://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts": "0d8a42635bd6e0130b248d9bae27ab36a40076cfa7a9be0f743fa51d7f64eb9f"
} }
} }

View File

@ -4,13 +4,15 @@
import config from "./deno.json" assert { type: "json" }; import config from "./deno.json" assert { type: "json" };
import * as $0 from "./routes/api/feeds/[seriesId].ts"; import * as $0 from "./routes/api/feeds/[seriesId].ts";
import * as $1 from "./routes/index.tsx"; import * as $1 from "./routes/api/feeds/[seriesId]/[episodeId]/chapters.ts";
import * as $2 from "./routes/index.tsx";
import * as $$0 from "./islands/CopyButton.tsx"; import * as $$0 from "./islands/CopyButton.tsx";
const manifest = { const manifest = {
routes: { routes: {
"./routes/api/feeds/[seriesId].ts": $0, "./routes/api/feeds/[seriesId].ts": $0,
"./routes/index.tsx": $1, "./routes/api/feeds/[seriesId]/[episodeId]/chapters.ts": $1,
"./routes/index.tsx": $2,
}, },
islands: { islands: {
"./islands/CopyButton.tsx": $$0, "./islands/CopyButton.tsx": $$0,

View File

@ -6,7 +6,7 @@ import { components as catalogComponents } from './nrk-catalog.ts';
type ArrayElement<A> = A extends readonly (infer T)[] ? T : never type ArrayElement<A> = A extends readonly (infer T)[] ? T : never
export type Serie = catalogComponents["schemas"]["SeriesViewModel"] export type Serie = catalogComponents["schemas"]["SeriesViewModel"]
type OriginalEpisode = catalogComponents["schemas"]["EpisodeHalResource"]; type OriginalEpisode = catalogComponents["schemas"]["EpisodeHalResource"];
export type Episode = OriginalEpisode & { url: string; } export type OriginalEpisode = OriginalEpisode & { url: string; }
export type SearchResultList = searchComponents["schemas"]["seriesResult"]["results"]; export type SearchResultList = searchComponents["schemas"]["seriesResult"]["results"];
export type SearchResult = ArrayElement<SearchResultList> & { description?: string } export type SearchResult = ArrayElement<SearchResultList> & { description?: string }
@ -25,7 +25,7 @@ interface Manifest {
}; };
} }
async function withDownloadLink(episode: OriginalEpisode): Promise<Episode> { async function withDownloadLink(episode: OriginalEpisode): Promise<OriginalEpisode> {
// getting stream link // getting stream link
const [playbackStatus, playbackResponse] = await get<Manifest>(`https://psapi.nrk.no/playback/manifest/podcast/${episode.episodeId}`) const [playbackStatus, playbackResponse] = await get<Manifest>(`https://psapi.nrk.no/playback/manifest/podcast/${episode.episodeId}`)
@ -67,5 +67,16 @@ export const nrkRadio = {
throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}` throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`
} }
},
getEpisode: async (seriesId: string, episodeId: string): Promise<OriginalEpisode> => {
const url = `https://psapi.nrk.no/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`
const [status, episode] = await get<OriginalEpisode>(url);
if (status !== OK) {
throw `Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`;
}
return episode;
} }
} }

View File

@ -2,8 +2,10 @@ import { nrkRadio, Episode } from "../../../lib/nrk.ts"
import { HandlerContext } from "$fresh/server.ts"; import { HandlerContext } from "$fresh/server.ts";
import { serialize, tag, declaration } from "https://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts"; import { serialize, tag, declaration } from "https://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts";
import { getHostName } from "../../../utils.ts";
function toItemTag(seriesId: string, episode: Episode) {
function toItemTag(episode: Episode) {
const description = episode.titles.subtitle || ""; const description = episode.titles.subtitle || "";
return tag("item", [ return tag("item", [
tag("title", episode.titles.title), tag("title", episode.titles.title),
@ -13,6 +15,10 @@ function toItemTag(episode: Episode) {
tag("guid", episode.id, [["isPermaLink", "false"]]), tag("guid", episode.id, [["isPermaLink", "false"]]),
tag("pubDate", new Date(episode.date).toUTCString()), tag("pubDate", new Date(episode.date).toUTCString()),
tag("itunes:duration", episode.durationInSeconds.toString()), tag("itunes:duration", episode.durationInSeconds.toString()),
tag("podcast:chapters", "", [
["url", `${getHostName()}/api/feeds/${seriesId}/${episode.episodeId}/chapters`],
["type", "application/json+chapters"],
]),
tag("enclosure", "", [ tag("enclosure", "", [
["url", episode.url], ["url", episode.url],
["length", episode.durationInSeconds.toString()], ["length", episode.durationInSeconds.toString()],
@ -66,13 +72,14 @@ async function buildFeed(seriesId: string) {
tag("link", linkValue), tag("link", linkValue),
]) ])
] : []), ] : []),
...serie.episodes.map(toItemTag), ...serie.episodes.map(episode => toItemTag(seriesId, episode)),
]), ]),
], ],
[ [
["version", "2.0"], ["version", "2.0"],
["xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd"], ["xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd"],
["xmlns:content", "http://purl.org/rss/1.0/modules/content/"], ["xmlns:content", "http://purl.org/rss/1.0/modules/content/"],
["xmlns:podcast", "https://podcastindex.org/namespace/1.0"],
] ]
) )
); );
@ -84,7 +91,7 @@ export const handler = async (req: Request, _ctx: HandlerContext): Promise<Respo
return new Response(feedContent, { return new Response(feedContent, {
headers: { headers: {
"Content-Type": "application/rss+xml" "Content-Type": "application/xml"
} }
}); });
}; };

View File

@ -0,0 +1,23 @@
import { parse, toSeconds } from "https://esm.sh/iso8601-duration@2.1.1";
import { nrkRadio, OriginalEpisode } from "../../../../../lib/nrk.ts";
function toChapters(episode: OriginalEpisode) {
return episode.indexPoints.map(indexPoint => ({
title: indexPoint.title,
startTime: toSeconds(parse(indexPoint.startPoint))
}))
}
export const handler = async (req: Request, _ctx: HandlerContext): Promise<Response> => {
const seriesId = _ctx.params.seriesId;
const episodeId = _ctx.params.episodeId;
console.log("going to get ep for ", episodeId);
const episode = await nrkRadio.getEpisode(seriesId, episodeId);
const chapters = toChapters(episode);
return new Response(JSON.stringify(chapters), {
headers: {
"Content-Type": "application/json+chapters"
}
});
};

11
utils.ts Normal file
View File

@ -0,0 +1,11 @@
export function getHostName() {
const deploymentId = Deno.env.get("DENO_DEPLOYMENT_ID");
if (deploymentId) {
return `https://nrss-${deploymentId}.deno.dev`;
} else {
// assume env
return 'http://localhost:8000';
}
}