Chapters (from podcast 2.0 spec)
This commit is contained in:
parent
28abe3c86a
commit
3e23bf443e
16
deno.lock
16
deno.lock
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
15
lib/nrk.ts
15
lib/nrk.ts
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue