mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-04 03:27:14 -05:00 
			
		
		
		
	Start the smart video controller
This commit is contained in:
		
							parent
							
								
									015f58226a
								
							
						
					
					
						commit
						1a2ab48c73
					
				@ -1,16 +1,23 @@
 | 
			
		||||
import { and, eq, inArray, sql } from "drizzle-orm";
 | 
			
		||||
import { Elysia, t } from "elysia";
 | 
			
		||||
import { db } from "~/db";
 | 
			
		||||
import { videos as videosT } from "~/db/schema";
 | 
			
		||||
import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
 | 
			
		||||
import { bubbleVideo } from "~/models/examples";
 | 
			
		||||
import { SeedVideo, Video } from "~/models/video";
 | 
			
		||||
import { comment } from "~/utils";
 | 
			
		||||
import { computeVideoSlug } from "./seed/insert/entries";
 | 
			
		||||
 | 
			
		||||
const CreatedVideo = t.Object({
 | 
			
		||||
	id: t.String({ format: "uuid" }),
 | 
			
		||||
	path: t.String({ example: bubbleVideo.path }),
 | 
			
		||||
	path: t.String({ examples: [bubbleVideo.path] }),
 | 
			
		||||
	// entries: t.Array(
 | 
			
		||||
	// 	t.Object({
 | 
			
		||||
	// 		slug: t.String({ format: "slug", examples: ["bubble-v2"] }),
 | 
			
		||||
	// 	}),
 | 
			
		||||
	// ),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const videos = new Elysia({ prefix: "/videos", tags: ["videos"] })
 | 
			
		||||
export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
 | 
			
		||||
	.model({
 | 
			
		||||
		video: Video,
 | 
			
		||||
		"created-videos": t.Array(CreatedVideo),
 | 
			
		||||
@ -22,20 +29,101 @@ export const videos = new Elysia({ prefix: "/videos", tags: ["videos"] })
 | 
			
		||||
	.post(
 | 
			
		||||
		"",
 | 
			
		||||
		async ({ body, error }) => {
 | 
			
		||||
			const ret = await db
 | 
			
		||||
				.insert(videosT)
 | 
			
		||||
			const oldRet = await db
 | 
			
		||||
				.insert(videos)
 | 
			
		||||
				.values(body)
 | 
			
		||||
				.onConflictDoNothing()
 | 
			
		||||
				.returning({ id: videosT.id, path: videosT.path });
 | 
			
		||||
			return error(201, ret);
 | 
			
		||||
				.returning({
 | 
			
		||||
					pk: videos.pk,
 | 
			
		||||
					id: videos.id,
 | 
			
		||||
					path: videos.path,
 | 
			
		||||
					guess: videos.guess,
 | 
			
		||||
				});
 | 
			
		||||
			return error(201, oldRet);
 | 
			
		||||
 | 
			
		||||
			// TODO: this is a huge untested wip
 | 
			
		||||
			const vidsI = db.$with("vidsI").as(
 | 
			
		||||
				db.insert(videos).values(body).onConflictDoNothing().returning({
 | 
			
		||||
					pk: videos.pk,
 | 
			
		||||
					id: videos.id,
 | 
			
		||||
					path: videos.path,
 | 
			
		||||
					guess: videos.guess,
 | 
			
		||||
				}),
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			const findEntriesQ = db
 | 
			
		||||
				.select({
 | 
			
		||||
					guess: videos.guess,
 | 
			
		||||
					entryPk: entries.pk,
 | 
			
		||||
					showSlug: shows.slug,
 | 
			
		||||
					// TODO: handle extras here
 | 
			
		||||
					// guessit can't know if an episode is a special or not. treat specials like a normal episode.
 | 
			
		||||
					kind: sql`
 | 
			
		||||
						case when ${entries.kind} = 'movie' then 'movie' else 'episode' end
 | 
			
		||||
					`.as("kind"),
 | 
			
		||||
					season: entries.seasonNumber,
 | 
			
		||||
					episode: entries.episodeNumber,
 | 
			
		||||
				})
 | 
			
		||||
				.from(entries)
 | 
			
		||||
				.leftJoin(entryVideoJoin, eq(entryVideoJoin.entry, entries.pk))
 | 
			
		||||
				.leftJoin(videos, eq(videos.pk, entryVideoJoin.video))
 | 
			
		||||
				.leftJoin(shows, eq(shows.pk, entries.showPk))
 | 
			
		||||
				.as("find_entries");
 | 
			
		||||
 | 
			
		||||
			const hasRenderingQ = db
 | 
			
		||||
				.select()
 | 
			
		||||
				.from(entryVideoJoin)
 | 
			
		||||
				.where(eq(entryVideoJoin.entry, findEntriesQ.entryPk));
 | 
			
		||||
 | 
			
		||||
			const ret = await db
 | 
			
		||||
				.with(vidsI)
 | 
			
		||||
				.insert(entryVideoJoin)
 | 
			
		||||
				.select(
 | 
			
		||||
					db
 | 
			
		||||
						.select({
 | 
			
		||||
							entry: findEntriesQ.entryPk,
 | 
			
		||||
							video: vidsI.pk,
 | 
			
		||||
							slug: computeVideoSlug(
 | 
			
		||||
								findEntriesQ.showSlug,
 | 
			
		||||
								sql`exists(${hasRenderingQ})`,
 | 
			
		||||
							),
 | 
			
		||||
						})
 | 
			
		||||
						.from(vidsI)
 | 
			
		||||
						.leftJoin(
 | 
			
		||||
							findEntriesQ,
 | 
			
		||||
							and(
 | 
			
		||||
								eq(
 | 
			
		||||
									sql`${findEntriesQ.guess}->'title'`,
 | 
			
		||||
									sql`${vidsI.guess}->'title'`,
 | 
			
		||||
								),
 | 
			
		||||
								// TODO: find if @> with a jsonb created on the fly is
 | 
			
		||||
								// better than multiples checks
 | 
			
		||||
								sql`${vidsI.guess} @> {"kind": }::jsonb`,
 | 
			
		||||
								inArray(findEntriesQ.kind, sql`${vidsI.guess}->'type'`),
 | 
			
		||||
								inArray(findEntriesQ.episode, sql`${vidsI.guess}->'episode'`),
 | 
			
		||||
								inArray(findEntriesQ.season, sql`${vidsI.guess}->'season'`),
 | 
			
		||||
							),
 | 
			
		||||
						),
 | 
			
		||||
				)
 | 
			
		||||
				.onConflictDoNothing()
 | 
			
		||||
				.returning({
 | 
			
		||||
					slug: entryVideoJoin.slug,
 | 
			
		||||
					entryPk: entryVideoJoin.entry,
 | 
			
		||||
					id: vidsI.id,
 | 
			
		||||
					path: vidsI.path,
 | 
			
		||||
				});
 | 
			
		||||
			return error(201, ret as any);
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			body: t.Array(SeedVideo),
 | 
			
		||||
			response: { 201: "created-videos" },
 | 
			
		||||
			response: { 201: t.Array(CreatedVideo) },
 | 
			
		||||
			detail: {
 | 
			
		||||
				description: comment`
 | 
			
		||||
					Create videos in bulk.
 | 
			
		||||
					Duplicated videos will simply be ignored.
 | 
			
		||||
 | 
			
		||||
					If a videos has a \`guess\` field, it will be used to automatically register the video under an existing
 | 
			
		||||
					movie or entry.
 | 
			
		||||
				`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ import { movies } from "./controllers/movies";
 | 
			
		||||
import { seasonsH } from "./controllers/seasons";
 | 
			
		||||
import { seed } from "./controllers/seed";
 | 
			
		||||
import { series } from "./controllers/series";
 | 
			
		||||
import { videos } from "./controllers/videos";
 | 
			
		||||
import { videosH } from "./controllers/videos";
 | 
			
		||||
import { migrate } from "./db";
 | 
			
		||||
import { Image } from "./models/utils";
 | 
			
		||||
import { comment } from "./utils";
 | 
			
		||||
@ -75,7 +75,7 @@ const app = new Elysia()
 | 
			
		||||
	.use(series)
 | 
			
		||||
	.use(entries)
 | 
			
		||||
	.use(seasonsH)
 | 
			
		||||
	.use(videos)
 | 
			
		||||
	.use(videosH)
 | 
			
		||||
	.use(seed)
 | 
			
		||||
	.listen(3000);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,6 @@ import { type TSchema, t } from "elysia";
 | 
			
		||||
import { comment } from "../utils";
 | 
			
		||||
import { bubbleVideo, registerExamples } from "./examples";
 | 
			
		||||
 | 
			
		||||
const Guess = <T extends TSchema>(schema: T) =>
 | 
			
		||||
	t.Optional(t.Union([schema, t.Array(schema)]));
 | 
			
		||||
 | 
			
		||||
export const Video = t.Object({
 | 
			
		||||
	id: t.String({ format: "uuid" }),
 | 
			
		||||
	slug: t.String({ format: "slug" }),
 | 
			
		||||
@ -40,9 +37,9 @@ export const Video = t.Object({
 | 
			
		||||
			t.Object(
 | 
			
		||||
				{
 | 
			
		||||
					title: t.String(),
 | 
			
		||||
					year: Guess(t.Integer()),
 | 
			
		||||
					season: Guess(t.Integer()),
 | 
			
		||||
					episode: Guess(t.Integer()),
 | 
			
		||||
					year: t.Array(t.Integer(), { default: [] }),
 | 
			
		||||
					season: t.Array(t.Integer(), { default: [] }),
 | 
			
		||||
					episode: t.Array(t.Integer(), { default: [] }),
 | 
			
		||||
					// TODO: maybe replace "extra" with the `extraKind` value (aka behind-the-scene, trailer, etc)
 | 
			
		||||
					type: t.Optional(t.UnionEnum(["episode", "movie", "extra"])),
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import { base } from "~/base";
 | 
			
		||||
import { movies } from "~/controllers/movies";
 | 
			
		||||
import { seed } from "~/controllers/seed";
 | 
			
		||||
import { series } from "~/controllers/series";
 | 
			
		||||
import { videos } from "~/controllers/videos";
 | 
			
		||||
import { videosH } from "~/controllers/videos";
 | 
			
		||||
import type { SeedMovie } from "~/models/movie";
 | 
			
		||||
import type { SeedVideo } from "~/models/video";
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ export const app = new Elysia()
 | 
			
		||||
	.use(base)
 | 
			
		||||
	.use(movies)
 | 
			
		||||
	.use(series)
 | 
			
		||||
	.use(videos)
 | 
			
		||||
	.use(videosH)
 | 
			
		||||
	.use(seed);
 | 
			
		||||
 | 
			
		||||
export const getMovie = async (
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user