From 1c01a5cfc60bb6d01371334bd22020db632fbad2 Mon Sep 17 00:00:00 2001 From: Tom Gobich Date: Fri, 3 Jan 2025 19:36:45 -0500 Subject: [PATCH] feat: fixing video preview for cloudflare r2 --- .env.example | 3 +++ app/actions/utils/get_hls_signature.ts | 32 ++++++++++++++++++++++++++ app/models/post.ts | 22 ++++++++++++++---- inertia/components/TipTapEditor.vue | 2 +- inertia/components/VideoPreview.vue | 6 ++++- start/env.ts | 2 ++ 6 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 app/actions/utils/get_hls_signature.ts diff --git a/.env.example b/.env.example index 66ab8c8..a755b9d 100644 --- a/.env.example +++ b/.env.example @@ -65,6 +65,9 @@ STRIPE_WEBHOOK_SECRET= PLOTMYCOURSE_API_URL=https://plotmycourse.app/api/v1 PLOTMYCOURSE_API_KEY= +# used by our Cloudflare R2 worker for request autorization (you can ignore this) +R2_SIGNING_KEY=dYmMU1KGTXhI0cmAHHAZi-scEf17PNG- + # not needed locally DISCORD_WEBHOOK= PLAUSIBLE_API_KEY= diff --git a/app/actions/utils/get_hls_signature.ts b/app/actions/utils/get_hls_signature.ts new file mode 100644 index 0000000..25ecdff --- /dev/null +++ b/app/actions/utils/get_hls_signature.ts @@ -0,0 +1,32 @@ +import Post from '#models/post' +import User from '#models/user' +import env from '#start/env' +import { DateTime } from 'luxon' +import { createHmac } from 'node:crypto' + +type Params = { + user: User + post: Post +} + +export default class GetHlsSignature { + static async handle({ user, post }: Params) { + const version = 'v1' + const userId = user.id ?? 'NA' + const videoId = post.videoR2Id + const expiration = DateTime.now().plus({ hours: 48 }).toISO() + const payload = [version, userId, videoId, expiration].join('|') + const signature = createHmac('sha256', env.get('R2_SIGNING_KEY')) + .update(payload) + .setEncoding('base64') + .digest('hex') + + return { + signature, + version, + userId, + videoId, + expiration, + } + } +} diff --git a/app/models/post.ts b/app/models/post.ts index b7fd0ee..c0b2d57 100644 --- a/app/models/post.ts +++ b/app/models/post.ts @@ -321,11 +321,15 @@ export default class Post extends AppBaseModel { } @computed() - get videoId() { - if (this.videoTypeId === VideoTypes.BUNNY) { - return this.videoBunnyId - } + get videoR2Id() { + if (this.videoTypeId !== VideoTypes.R2 || !this.videoUrl) return '' + return this.videoUrl + } + @computed() + get videoId() { + if (this.videoTypeId === VideoTypes.BUNNY) return this.videoBunnyId + if (this.videoTypeId === VideoTypes.R2) return this.videoR2Id return this.videoYouTubeId } @@ -362,6 +366,12 @@ export default class Post extends AppBaseModel { @computed() get transcriptUrl() { + if (this.videoTypeId === VideoTypes.R2 && this.captions?.length) { + const filename = this.captions.at(0)?.filename + if (!filename) return + return `https://vid.adocasts.com/${this.videoId}/${filename}` + } + if (this.videoTypeId !== VideoTypes.BUNNY || !this.videoBunnyId) return return this.bunnySubtitleUrls?.at(0)?.src @@ -369,6 +379,10 @@ export default class Post extends AppBaseModel { @computed() get animatedPreviewUrl() { + if (this.videoTypeId === VideoTypes.R2 && this.videoId) { + return `https://vid.adocasts.com/${this.videoId}/video.webp` + } + if (this.videoTypeId !== VideoTypes.BUNNY || !this.videoBunnyId) return return `https://videos.adocasts.com/${this.videoBunnyId}/preview.webp` diff --git a/inertia/components/TipTapEditor.vue b/inertia/components/TipTapEditor.vue index 36ea9a7..8442c8c 100644 --- a/inertia/components/TipTapEditor.vue +++ b/inertia/components/TipTapEditor.vue @@ -123,7 +123,7 @@ async function uploadImage(file: File) { }, }) - return `/assets/${data.filename}` + return `https://adocasts.com/${data.filename}?w=900` } diff --git a/inertia/components/VideoPreview.vue b/inertia/components/VideoPreview.vue index c1ca36a..62de206 100644 --- a/inertia/components/VideoPreview.vue +++ b/inertia/components/VideoPreview.vue @@ -108,7 +108,11 @@ function onVideoReady(event: any) {

Enter a valid video id to add a video to this post