/ proof-of-concept / takes
Sign in
proof-of-concept / takes
takes / src / api.ts
61 lines · 3.3 KBCodeBlameHistory
f3253d0Takes: record, organize, and edit explainer videosJeremy Magland 1import type { ExportJob, Peaks, PublicConfig, Take, TrackKind, Transcript, Video, VideoSummary } from '../shared/types.ts';
3async function request<T>(method: string, url: string, body?: unknown): Promise<T> {
4 const headers: Record<string, string> = {};
5 let payload: BodyInit | undefined;
6 if (method !== 'GET') headers['X-Takes'] = '1';
7 if (body instanceof Blob) {
8 payload = body;
9 headers['Content-Type'] = body.type || 'application/octet-stream';
10 } else if (body !== undefined) {
11 payload = JSON.stringify(body);
12 headers['Content-Type'] = 'application/json';
13 }
14 const res = await fetch(url, { method, headers, body: payload });
15 if (!res.ok) {
16 let msg = `${res.status} ${res.statusText}`;
17 try {
18 msg = (await res.json()).error ?? msg;
19 } catch {
20 // not JSON
21 }
22 throw new Error(msg);
23 }
24 return res.json() as Promise<T>;
27async function getOptional<T>(url: string): Promise<T | null> {
28 const res = await fetch(url, { cache: 'no-cache' });
29 return res.ok ? ((await res.json()) as T) : null;
32const v = (id: string) => `/api/videos/${id}`;
34export const api = {
35 config: () => request<PublicConfig>('GET', '/api/config'),
36 saveConfig: (patch: Record<string, unknown>) => request<{ ok: true }>('PUT', '/api/config', patch),
38 videos: () => request<VideoSummary[]>('GET', '/api/videos'),
39 setOrder: (order: string[]) => request<{ ok: true }>('PUT', '/api/order', { order }),
40 createVideo: (input: { title: string; description: string; notes: string }) => request<Video>('POST', '/api/videos', input),
41 video: (id: string) => request<Video>('GET', v(id)),
42 patchVideo: (id: string, patch: Partial<Video>) => request<Video>('PATCH', v(id), patch),
43 deleteVideo: (id: string) => request<{ ok: true }>('DELETE', v(id)),
45 createTake: (id: string, input: { tracks: { screen: boolean; camera: boolean }; audio: TrackKind | null }) => request<Take>('POST', `${v(id)}/takes`, input),
46 uploadChunk: (id: string, takeId: string, track: TrackKind, blob: Blob) => request<{ ok: true }>('POST', `${v(id)}/takes/${takeId}/chunk?track=${track}`, blob),
47 finalizeTake: (id: string, takeId: string) => request<Video>('POST', `${v(id)}/takes/${takeId}/finalize`),
48 deleteTake: (id: string, takeId: string) => request<Video>('DELETE', `${v(id)}/takes/${takeId}`),
49 transcribe: (id: string, takeId: string) => request<Video>('POST', `${v(id)}/takes/${takeId}/transcribe`),
50 transcript: (id: string, takeId: string) => getOptional<Transcript>(`/media/${id}/takes/${takeId}/transcript.json`),
51 peaks: (id: string, takeId: string) => getOptional<Peaks>(`/media/${id}/takes/${takeId}/peaks.json`),
53 requestMattes: (id: string, takeId?: string) => request<Video>('POST', `${v(id)}/matte`, takeId ? { takeId } : {}),
54 uploadThumbnail: (id: string, jpeg: Blob) => request<Video>('POST', `${v(id)}/thumbnail`, jpeg),
55 startExport: (id: string) => request<ExportJob>('POST', `${v(id)}/export`),
56 exportStatus: (id: string) => request<ExportJob | null>('GET', `${v(id)}/export`),
57 openFolder: (id: string) => request<{ ok: true; dir: string }>('POST', `${v(id)}/open-folder`),
58};
60export const takeFileUrl = (videoId: string, takeId: string, track: TrackKind | 'cutout', version?: string) =>
61 `/media/${videoId}/takes/${takeId}/${track}.webm${version ? `?v=${encodeURIComponent(version)}` : ''}`;
↑↓ move↵openescclose