This commit introduces face detection capabilities to the video platform, enabling automatic identification of faces in video thumbnails. It integrates face-api.js and sharp for image analysis, allowing for face-centered thumbnail crops and dynamic object-positioning. New API endpoints are added to process thumbnails individually and in batches. The database schema is updated to store face detection data, and the storage layer is modified to support these updates and cache face data. The project's dependencies are also updated to include necessary libraries for these new features. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 2eb1084e-b728-4449-9231-f1665924c8d5 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2eb1084e-b728-4449-9231-f1665924c8d5/xF0EUqR
165 lines
6.9 KiB
TypeScript
165 lines
6.9 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import { pgTable, text, varchar, integer, timestamp, boolean, real } from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { z } from "zod";
|
|
|
|
export const videos = pgTable("videos", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
title: text("title").notNull(),
|
|
description: text("description").default("").notNull(),
|
|
thumbnailUrl: text("thumbnail_url").notNull(),
|
|
customThumbnailUrl: text("custom_thumbnail_url"),
|
|
faceCenterPosition: text("face_center_position"), // CSS object-position for face centering
|
|
facesDetected: integer("faces_detected").default(0), // Number of faces detected
|
|
faceConfidence: real("face_confidence").default(0), // Confidence score of primary face
|
|
videoUrl: text("video_url").notNull(),
|
|
videoUrlMp4: text("video_url_mp4"),
|
|
videoUrlIframe: text("video_url_iframe"),
|
|
duration: integer("duration").notNull(), // in seconds
|
|
views: integer("views").notNull().default(0),
|
|
category: text("category").default("").notNull(),
|
|
tags: text("tags").array().default([]).notNull(),
|
|
isPublic: boolean("is_public").default(true).notNull(),
|
|
uploadStatus: text("upload_status").default("pending").notNull(), // pending, processing, completed, failed
|
|
originalFileName: text("original_file_name"),
|
|
fileSize: integer("file_size"), // in bytes
|
|
bitrate: integer("bitrate"), // in kbps
|
|
resolution: text("resolution"), // e.g., "1920x1080"
|
|
format: text("format"), // e.g., "mp4", "avi", "mov"
|
|
encoding: text("encoding"), // e.g., "h264", "h265"
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// User table for authentication and video ownership
|
|
export const users = pgTable("users", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
username: varchar("username", { length: 50 }).notNull().unique(),
|
|
email: varchar("email", { length: 255 }).notNull().unique(),
|
|
password: varchar("password", { length: 255 }).notNull(),
|
|
firstName: varchar("first_name", { length: 100 }),
|
|
lastName: varchar("last_name", { length: 100 }),
|
|
profileImageUrl: text("profile_image_url"),
|
|
isAdmin: boolean("is_admin").default(false).notNull(),
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Video uploads table for tracking upload progress
|
|
export const videoUploads = pgTable("video_uploads", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").notNull().references(() => users.id),
|
|
videoId: varchar("video_id").references(() => videos.id),
|
|
originalFileName: text("original_file_name").notNull(),
|
|
fileSize: integer("file_size").notNull(),
|
|
mimeType: text("mime_type").notNull(),
|
|
uploadStatus: text("upload_status").default("uploading").notNull(), // uploading, processing, completed, failed
|
|
uploadProgress: real("upload_progress").default(0).notNull(), // 0-1
|
|
errorMessage: text("error_message"),
|
|
uploadUrl: text("upload_url"), // presigned URL for upload
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Categories table for better organization
|
|
export const categories = pgTable("categories", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
name: text("name").notNull().unique(),
|
|
description: text("description"),
|
|
color: varchar("color", { length: 7 }).default("#000000").notNull(), // hex color
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Tags table for better organization
|
|
export const tags = pgTable("tags", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
name: text("name").notNull().unique(),
|
|
useCount: integer("use_count").default(0).notNull(),
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Video tags junction table
|
|
export const videoTags = pgTable("video_tags", {
|
|
videoId: varchar("video_id").notNull().references(() => videos.id, { onDelete: "cascade" }),
|
|
tagId: varchar("tag_id").notNull().references(() => tags.id, { onDelete: "cascade" }),
|
|
});
|
|
|
|
// Video ads/spots table for storing advertisement metadata
|
|
export const videoAds = pgTable("video_ads", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
videoId: varchar("video_id").notNull().references(() => videos.id, { onDelete: "cascade" }),
|
|
adType: text("ad_type").notNull(), // preroll, midroll, postroll
|
|
adUrl: text("ad_url").notNull(), // VAST URL or direct video URL
|
|
adTitle: text("ad_title"),
|
|
adDuration: integer("ad_duration"), // in seconds
|
|
position: integer("position").default(0), // for midroll ads - position in video (seconds)
|
|
skipAfter: integer("skip_after"), // seconds after which ad can be skipped
|
|
vastTag: text("vast_tag"), // VAST XML tag URL
|
|
adNetwork: text("ad_network"), // network name (e.g., Google AdX, Publift)
|
|
isActive: boolean("is_active").default(true).notNull(),
|
|
priority: integer("priority").default(1), // ad priority (1 = highest)
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Schemas for form validation and API
|
|
export const insertVideoSchema = createInsertSchema(videos).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const updateVideoSchema = createInsertSchema(videos).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
}).partial();
|
|
|
|
export const insertUserSchema = createInsertSchema(users).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertVideoUploadSchema = createInsertSchema(videoUploads).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertCategorySchema = createInsertSchema(categories).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertTagSchema = createInsertSchema(tags).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertVideoAdSchema = createInsertSchema(videoAds).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
// Type exports
|
|
export type Video = typeof videos.$inferSelect;
|
|
export type InsertVideo = z.infer<typeof insertVideoSchema>;
|
|
export type UpdateVideo = z.infer<typeof updateVideoSchema>;
|
|
|
|
export type VideoAd = typeof videoAds.$inferSelect;
|
|
export type InsertVideoAd = z.infer<typeof insertVideoAdSchema>;
|
|
|
|
export type User = typeof users.$inferSelect;
|
|
export type InsertUser = z.infer<typeof insertUserSchema>;
|
|
|
|
export type VideoUpload = typeof videoUploads.$inferSelect;
|
|
export type InsertVideoUpload = z.infer<typeof insertVideoUploadSchema>;
|
|
|
|
export type Category = typeof categories.$inferSelect;
|
|
export type InsertCategory = z.infer<typeof insertCategorySchema>;
|
|
|
|
export type Tag = typeof tags.$inferSelect;
|
|
export type InsertTag = z.infer<typeof insertTagSchema>;
|