diff --git a/src/app/channel/page.tsx b/src/app/channel/page.tsx index 5ad87fcf557f64f8105abf7bc00bccc66065440c..d75c3c924f72a68d7124d502e7c957ebf88166aa 100644 --- a/src/app/channel/page.tsx +++ b/src/app/channel/page.tsx @@ -1,4 +1,4 @@ -import { AppQueryProps } from "@/types" +import { AppQueryProps } from "@/types/general" import { Main } from "../main" import { getChannel } from "../server/actions/ai-tube-hf/getChannel" diff --git a/src/app/config.ts b/src/app/config.ts index 2595b9fcd47a83cdde8241b591908a39fe5a8360..c51c9f5d3faac9e9d247bf82acc32e85a787ccf8 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -1,4 +1,4 @@ -import { VideoGenerationModel, VideoOrientation } from "@/types" +import { VideoGenerationModel, VideoOrientation } from "@/types/general" export const showBetaFeatures = `${ process.env.NEXT_PUBLIC_SHOW_BETA_FEATURES || "" diff --git a/src/app/interface/channel-card/index.tsx b/src/app/interface/channel-card/index.tsx index c9c8bcfc9c8f91a1e82af446edd8f65885a876b4..8cedbebb48f1462594e7f1642989ffcbe442c345 100644 --- a/src/app/interface/channel-card/index.tsx +++ b/src/app/interface/channel-card/index.tsx @@ -4,7 +4,7 @@ import { RiCheckboxCircleFill } from "react-icons/ri" import { IoAdd } from "react-icons/io5" import { cn } from "@/lib/utils" -import { ChannelInfo } from "@/types" +import { ChannelInfo } from "@/types/general" import { isCertifiedUser } from "@/app/certification" import { DefaultAvatar } from "../default-avatar" import { formatLargeNumber } from "@/lib/formatLargeNumber" diff --git a/src/app/interface/channel-list/index.tsx b/src/app/interface/channel-list/index.tsx index 971b8a3571901e2960f0213a863fbba5b5f37630..58517e62cc334aa95543ed9d6ecdeeb22eb3bfd4 100644 --- a/src/app/interface/channel-list/index.tsx +++ b/src/app/interface/channel-list/index.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils" -import { ChannelInfo } from "@/types" +import { ChannelInfo } from "@/types/general" import { ChannelCard } from "../channel-card" diff --git a/src/app/interface/collection-card/index.tsx b/src/app/interface/collection-card/index.tsx index 6b673d058de98f08be9eacb954bd6c846ef3437e..864e760e3555d2533506c4a78e82bce5e7282129 100644 --- a/src/app/interface/collection-card/index.tsx +++ b/src/app/interface/collection-card/index.tsx @@ -5,7 +5,7 @@ import Link from "next/link" import { RiCheckboxCircleFill } from "react-icons/ri" import { cn } from "@/lib/utils" -import { CollectionInfo } from "@/types" +import { CollectionInfo } from "@/types/general" import { formatDuration } from "@/lib/formatDuration" import { formatTimeAgo } from "@/lib/formatTimeAgo" import { isCertifiedUser } from "@/app/certification" diff --git a/src/app/interface/collection-list/index.tsx b/src/app/interface/collection-list/index.tsx index 18c704e9cdc14aaa2775b720e712a9542e7a8e18..8996fa7737c12b244ad3078970e946a1c9a5120c 100644 --- a/src/app/interface/collection-list/index.tsx +++ b/src/app/interface/collection-list/index.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils" -import { CollectionInfo } from "@/types" +import { CollectionInfo } from "@/types/general" import { CollectionCard } from "../collection-card" diff --git a/src/app/interface/comment-card/index.tsx b/src/app/interface/comment-card/index.tsx index 99ae827aab5dd98a096a6fe46079b47aefcd2cfa..4f15c91e2855e68b6d7d4115ca1437847cb4f111 100644 --- a/src/app/interface/comment-card/index.tsx +++ b/src/app/interface/comment-card/index.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils" -import { CommentInfo } from "@/types" +import { CommentInfo } from "@/types/general" import { useEffect, useState } from "react" import { DefaultAvatar } from "../default-avatar" import { formatTimeAgo } from "@/lib/formatTimeAgo" diff --git a/src/app/interface/comment-list/index.tsx b/src/app/interface/comment-list/index.tsx index 44d6628de75b062cd39d26c512057a1257a175b8..3e1c5d117994562d589a9c7c4964503e7008a44d 100644 --- a/src/app/interface/comment-list/index.tsx +++ b/src/app/interface/comment-list/index.tsx @@ -1,7 +1,7 @@ "use client" import { cn } from "@/lib/utils" -import { CommentInfo } from "@/types" +import { CommentInfo } from "@/types/general" import { CommentCard } from "../comment-card" export function CommentList({ diff --git a/src/app/interface/equirectangular-video-player/index.tsx b/src/app/interface/equirectangular-video-player/index.tsx index 21f6224018f7e7f4f263c6a92b3662a36348b40f..7639930cab2ec246b8193c776e1c61c3b0fe0869 100644 --- a/src/app/interface/equirectangular-video-player/index.tsx +++ b/src/app/interface/equirectangular-video-player/index.tsx @@ -3,7 +3,7 @@ import AutoSizer from "react-virtualized-auto-sizer" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { VideoSphereViewer } from "./viewer" diff --git a/src/app/interface/equirectangular-video-player/viewer.tsx b/src/app/interface/equirectangular-video-player/viewer.tsx index 12bec3dd67b608c7a22ffb949a93aa646714f51d..1cfe8f93de8b8cfdeafe1cde4708fb598c1cbdd7 100644 --- a/src/app/interface/equirectangular-video-player/viewer.tsx +++ b/src/app/interface/equirectangular-video-player/viewer.tsx @@ -6,7 +6,7 @@ import { PanoramaPosition, PluginConstructor, Point, Position, SphericalPosition import { EquirectangularVideoAdapter, LensflarePlugin, ReactPhotoSphereViewer, ResolutionPlugin, SettingsPlugin, VideoPlugin } from "react-photo-sphere-viewer" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" type PhotoSpherePlugin = (PluginConstructor | [PluginConstructor, any]) diff --git a/src/app/interface/like-button/index.tsx b/src/app/interface/like-button/index.tsx index 3f98d954a2c167424e9609bc7aa51fdc5d5bf234..c92279900e23280b1df2533b6428cf0e39dee3d7 100644 --- a/src/app/interface/like-button/index.tsx +++ b/src/app/interface/like-button/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useTransition } from "react" -import { VideoInfo, VideoRating } from "@/types" +import { VideoInfo, VideoRating } from "@/types/general" import { GenericLikeButton } from "./generic" import { getVideoRating, rateVideo } from "@/app/server/actions/stats" diff --git a/src/app/interface/media-list/index.tsx b/src/app/interface/media-list/index.tsx index 415d5d6b1657245cd843eace40b4c0d3eb785855..a3d891c6981e8418a35f98d203fae011d45f8601 100644 --- a/src/app/interface/media-list/index.tsx +++ b/src/app/interface/media-list/index.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils" -import { MediaDisplayLayout, VideoInfo } from "@/types" +import { MediaDisplayLayout, VideoInfo } from "@/types/general" import { TrackCard } from "../track-card" import { VideoCard } from "../video-card" diff --git a/src/app/interface/pending-video-card/index.tsx b/src/app/interface/pending-video-card/index.tsx index 57b31220d30936f23f8068cdce3c1f76429ee4ca..d41917489eac8fd93cef66b0df6d181f5a5587c0 100644 --- a/src/app/interface/pending-video-card/index.tsx +++ b/src/app/interface/pending-video-card/index.tsx @@ -3,7 +3,7 @@ import { PiTrashBold } from "react-icons/pi" import { TableCell, TableRow } from "@/components/ui/table" import { cn } from "@/lib/utils" import { MdLockClock } from "react-icons/md" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { truncate } from "./truncate" export function PendingVideoCard({ diff --git a/src/app/interface/pending-video-list/index.tsx b/src/app/interface/pending-video-list/index.tsx index e9f3373feaf342fb7280d24a49c42cfa7cbfc1f9..4f5d387c61d74b1d5593a2c308c02e61bd41fb43 100644 --- a/src/app/interface/pending-video-list/index.tsx +++ b/src/app/interface/pending-video-list/index.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { PendingVideoCard } from "../pending-video-card" diff --git a/src/app/interface/playlist-control/index.tsx b/src/app/interface/playlist-control/index.tsx index bbdf644708bc3fc78770cfb1d6f32cc1308e88c4..bd62db944b6f626d48fe2906b2b1cb7aff4630dd 100644 --- a/src/app/interface/playlist-control/index.tsx +++ b/src/app/interface/playlist-control/index.tsx @@ -3,7 +3,7 @@ import { IoIosPause } from "react-icons/io" import { cn } from "@/lib/utils" import { usePlaylist } from "@/lib/usePlaylist" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" export function PlaylistControl() { const playlist = usePlaylist() diff --git a/src/app/interface/recommended-videos/index.tsx b/src/app/interface/recommended-videos/index.tsx index 16549bc8a384a64bb8d7942ad9e9a102a995fd25..18e46a653e1cf7c42ec44963989168868833833a 100644 --- a/src/app/interface/recommended-videos/index.tsx +++ b/src/app/interface/recommended-videos/index.tsx @@ -3,7 +3,7 @@ import { useEffect, useTransition } from "react" import { useStore } from "@/app/state/useStore" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { VideoList } from "../video-list" import { getVideos } from "@/app/server/actions/ai-tube-hf/getVideos" diff --git a/src/app/interface/track-card/index.tsx b/src/app/interface/track-card/index.tsx index d32f448e40018749977e65395d1f0a1097b5c5ec..7afb2c96262ce0a3aded7e84011f80b05ed37831 100644 --- a/src/app/interface/track-card/index.tsx +++ b/src/app/interface/track-card/index.tsx @@ -5,7 +5,7 @@ import Link from "next/link" import { RiCheckboxCircleFill } from "react-icons/ri" import { cn } from "@/lib/utils" -import { MediaDisplayLayout, VideoInfo } from "@/types" +import { MediaDisplayLayout, VideoInfo } from "@/types/general" import { formatDuration } from "@/lib/formatDuration" import { formatTimeAgo } from "@/lib/formatTimeAgo" import { isCertifiedUser } from "@/app/certification" diff --git a/src/app/interface/video-card/index.tsx b/src/app/interface/video-card/index.tsx index d3a9e0ee1e95cc88357af3acf68037dc6258f8a6..5407335b14690dfb258aaf5126390153cd5e4573 100644 --- a/src/app/interface/video-card/index.tsx +++ b/src/app/interface/video-card/index.tsx @@ -5,7 +5,7 @@ import Link from "next/link" import { RiCheckboxCircleFill } from "react-icons/ri" import { cn } from "@/lib/utils" -import { MediaDisplayLayout, VideoInfo } from "@/types" +import { MediaDisplayLayout, VideoInfo } from "@/types/general" import { formatDuration } from "@/lib/formatDuration" import { formatTimeAgo } from "@/lib/formatTimeAgo" import { isCertifiedUser } from "@/app/certification" diff --git a/src/app/interface/video-player/cartesian.tsx b/src/app/interface/video-player/cartesian.tsx index 50ac3be005c5f8ba24a77f2a0ca35a0045c410f7..35a987e1155f21938099f3478d4a893f48524c5e 100644 --- a/src/app/interface/video-player/cartesian.tsx +++ b/src/app/interface/video-player/cartesian.tsx @@ -4,7 +4,7 @@ import { Player } from "react-tuby" import "react-tuby/css/main.css" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" export function CartesianVideoPlayer({ video, diff --git a/src/app/interface/video-player/equirectangular.tsx b/src/app/interface/video-player/equirectangular.tsx index 04354d8553f5927b065b16952285a921188b5bbd..fb6602792621568c4974a9b26a9b1359e9d3f002 100644 --- a/src/app/interface/video-player/equirectangular.tsx +++ b/src/app/interface/video-player/equirectangular.tsx @@ -6,7 +6,7 @@ import { PanoramaPosition, PluginConstructor, Point, Position, SphericalPosition import { EquirectangularVideoAdapter, LensflarePlugin, ReactPhotoSphereViewer, ResolutionPlugin, SettingsPlugin, VideoPlugin } from "react-photo-sphere-viewer" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" type PhotoSpherePlugin = (PluginConstructor | [PluginConstructor, any]) diff --git a/src/app/interface/video-player/index.tsx b/src/app/interface/video-player/index.tsx index 748890bb7eb8c683aa94b0a3df7e876cc9d76bfd..e2751feceb10bdd0a10c7d1dc37fe828d51b4ffc 100644 --- a/src/app/interface/video-player/index.tsx +++ b/src/app/interface/video-player/index.tsx @@ -3,7 +3,7 @@ import AutoSizer from "react-virtualized-auto-sizer" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { parseProjectionFromLoRA } from "@/app/server/actions/utils/parseProjectionFromLoRA" import { EquirectangularVideoPlayer } from "./equirectangular" diff --git a/src/app/main.tsx b/src/app/main.tsx index b2aac959fe477f4df1d2ff801ea763c649b544c2..6434295da21442c8d6d5f2c8dbf64796135ce19a 100644 --- a/src/app/main.tsx +++ b/src/app/main.tsx @@ -8,7 +8,7 @@ import { UserChannelView } from "./views/user-channel-view" import { PublicVideoView } from "./views/public-video-view" import { UserAccountView } from "./views/user-account-view" import { NotFoundView } from "./views/not-found-view" -import { ChannelInfo, InterfaceView, VideoInfo } from "@/types" +import { ChannelInfo, InterfaceView, VideoInfo } from "@/types/general" import { useEffect } from "react" import { usePathname, useRouter } from "next/navigation" import { TubeLayout } from "./interface/tube-layout" diff --git a/src/app/music/page.tsx b/src/app/music/page.tsx index f8dbf5e8bfda8ccf50f29f9f664d3bb256014c65..a8413a3550d6d0910163d0c776ead7a625b2f9d8 100644 --- a/src/app/music/page.tsx +++ b/src/app/music/page.tsx @@ -1,5 +1,5 @@ -import { AppQueryProps } from "@/types" +import { AppQueryProps } from "@/types/general" import { Main } from "../main" import { getVideos } from "../server/actions/ai-tube-hf/getVideos" import { getVideo } from "../server/actions/ai-tube-hf/getVideo" diff --git a/src/app/page.tsx b/src/app/page.tsx index 9c923df87995de557eabdcd31fde87011abc223b..de422020af14a8dbcbf3994c1387d4efed15869d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ -import { AppQueryProps } from "@/types" +import { AppQueryProps } from "@/types/general" import { Main } from "./main" import { getVideo } from "./server/actions/ai-tube-hf/getVideo" diff --git a/src/app/playlist/page.tsx b/src/app/playlist/page.tsx index e1fb5bc14496cf00d191ee65341fc2b442433d89..0940171086e2aa477c4189d7cfd6b3225c7f661e 100644 --- a/src/app/playlist/page.tsx +++ b/src/app/playlist/page.tsx @@ -1,5 +1,5 @@ -import { AppQueryProps } from "@/types" +import { AppQueryProps } from "@/types/general" import { Main } from "../main" import { getVideos } from "../server/actions/ai-tube-hf/getVideos" import { getVideo } from "../server/actions/ai-tube-hf/getVideo" diff --git a/src/app/server/actions/ai-tube-hf/deleteVideoRequest.ts b/src/app/server/actions/ai-tube-hf/deleteVideoRequest.ts index 8a98ebb7eeb51b16dbfeaff9a1fe78b67571329c..cc30f36183d112e4386fb11de3835434a9eb7c64 100644 --- a/src/app/server/actions/ai-tube-hf/deleteVideoRequest.ts +++ b/src/app/server/actions/ai-tube-hf/deleteVideoRequest.ts @@ -1,5 +1,5 @@ -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { deleteFileFromDataset } from "./deleteFileFromDataset" import { formatPromptFileName } from "../utils/formatPromptFileName" diff --git a/src/app/server/actions/ai-tube-hf/downloadClapProject.ts b/src/app/server/actions/ai-tube-hf/downloadClapProject.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f2739b2dca31aeac0e53f9238cb06264d5cf8e1 --- /dev/null +++ b/src/app/server/actions/ai-tube-hf/downloadClapProject.ts @@ -0,0 +1,99 @@ +import { v4 as uuidv4 } from "uuid" + +import { ClapProject } from "@/types/clap" +import { ChannelInfo, VideoInfo, VideoRequest } from "@/types/general" +import { defaultVideoModel } from "@/app/config" + +import { parseClap } from "../utils/parseClap" +import { parseVideoModelName } from "../utils/parseVideoModelName" +import { computeOrientationProjectionWidthHeight } from "../utils/computeOrientationProjectionWidthHeight" + +import { downloadFileAsText } from "./downloadFileAsText" +import { downloadFileAsBlob } from "./downloadFileAsBlob" + +export async function downloadClapProject({ + path, + apiKey, + channel +}: { + path: string + apiKey?: string + channel: ChannelInfo +}): Promise<{ + videoRequest: VideoRequest + videoInfo: VideoInfo + clapProject: ClapProject +}> { + // we recover the repo from the cnannel info + const repo = `datasets/${channel.datasetUser}/${channel.datasetName}` + + // we download the clap file (which might be in a private repo) + const clapString = await downloadFileAsBlob({ + repo, + path, + apiKey, + expectedMimeType: "application/gzip" + }) + + const clapProject = await parseClap(clapString) + + const id = uuidv4() + + const videoRequest: VideoRequest = { + id, + label: clapProject.meta.title || "Untitled", + description: clapProject.meta.description || "", + prompt: "", // there is no prompt - instead we use segments + model: parseVideoModelName(clapProject.meta.defaultVideoModel, channel.model), + style: channel.style, + lora: channel.lora, + voice: channel.voice, + music: channel.music, + thumbnailUrl: "", + clapUrl: `https://huggingface.co/${repo}/resolve/main/${path}`, + updatedAt: new Date().toISOString(), + tags: channel.tags, + channel, + duration: 0, // will be computed automatically + ...computeOrientationProjectionWidthHeight({ + lora: "", + orientation: clapProject.meta.orientation, + // projection, // <- will be extrapolated from the LoRA for now + }), + } + + const videoInfo: VideoInfo = { + id, + status: "submitted", + label: videoRequest.label || "", + description: videoRequest.description || "", + prompt: videoRequest.prompt || "", + model: videoRequest.model || defaultVideoModel, + style: videoRequest.style || "", + lora: videoRequest.lora || "", + voice: videoRequest.voice || "", + music: videoRequest.music || "", + thumbnailUrl: videoRequest.thumbnailUrl || "", // will be generated in async + clapUrl: videoRequest.clapUrl || "", + assetUrl: "", // will be generated in async + assetUrlHd: "", + numberOfViews: 0, + numberOfLikes: 0, + numberOfDislikes: 0, + updatedAt: new Date().toISOString(), + tags: videoRequest.tags, + channel, + duration: videoRequest.duration || 0, + ...computeOrientationProjectionWidthHeight({ + lora: videoRequest.lora, + orientation: videoRequest.orientation, + // projection, // <- will be extrapolated from the LoRA for now + }), + } + + return { + videoRequest, + videoInfo, + clapProject + } +} \ No newline at end of file diff --git a/src/app/server/actions/ai-tube-hf/downloadFileAsBlob.ts b/src/app/server/actions/ai-tube-hf/downloadFileAsBlob.ts new file mode 100644 index 0000000000000000000000000000000000000000..a39be349d18e9dee55f6644725301a733e9bc143 --- /dev/null +++ b/src/app/server/actions/ai-tube-hf/downloadFileAsBlob.ts @@ -0,0 +1,63 @@ +import { downloadFile } from "@/huggingface/hub/src" +import { getCredentials } from "./getCredentials" + +export async function downloadFileAsBlob({ + repo, + path, + apiKey, + expectedMimeType = "text/plain", + renewCache = false, + neverThrow = false +}: { + repo: string + + path: string + + apiKey?: string + + expectedMimeType?: string + + /** + * Force renewing the cache + * + * False by default + */ + renewCache?: boolean + + /** + * If set to true, this function will never throw an exception + * this is useful in workflow where we don't care about what happened + * + * False by default + */ + neverThrow?: boolean +}): Promise { + try { + const { credentials } = await getCredentials(apiKey) + + const response = await downloadFile({ + repo, + path, + credentials, + requestInit: renewCache + ? { cache: "no-cache" } + : undefined + }) + + if (!response) { + throw new Error("missing response") + } + const blob = await response.blob() + + return blob + } catch (err) { + if (neverThrow) { + console.error(`downloadFileAsBlob():`, err) + + const blobResult = new Blob([""], { type: expectedMimeType }) + return blobResult + } else { + throw err + } + } +} \ No newline at end of file diff --git a/src/app/server/actions/ai-tube-hf/downloadPlainText.ts b/src/app/server/actions/ai-tube-hf/downloadPlainText.ts new file mode 100644 index 0000000000000000000000000000000000000000..df2132894061f4ddca1c0da2d2b8045ec72c3dbe --- /dev/null +++ b/src/app/server/actions/ai-tube-hf/downloadPlainText.ts @@ -0,0 +1,13 @@ + +export async function downloadPlainText(url: string): Promise { + // Fetch the plain text file + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`Failed to download the plain/text file: ${response.statusText}`) + } + + const plainText = await response.text() + + return plainText +} \ No newline at end of file diff --git a/src/app/server/actions/ai-tube-hf/extendVideosWithStats.ts b/src/app/server/actions/ai-tube-hf/extendVideosWithStats.ts index c3a0d648c3842bcda0841a8d99f8437dd5d33ca2..b86d9e8c644f15a90b9191741cd8a9cd98cf6414 100644 --- a/src/app/server/actions/ai-tube-hf/extendVideosWithStats.ts +++ b/src/app/server/actions/ai-tube-hf/extendVideosWithStats.ts @@ -1,6 +1,6 @@ "use server" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { getStatsForVideos } from "../stats" diff --git a/src/app/server/actions/ai-tube-hf/getChannel.ts b/src/app/server/actions/ai-tube-hf/getChannel.ts index 903c74286a536a6dfab02e3c92190a54fc000fef..4e2dff45145c31fddf7ba3aa2db3932272b5253a 100644 --- a/src/app/server/actions/ai-tube-hf/getChannel.ts +++ b/src/app/server/actions/ai-tube-hf/getChannel.ts @@ -1,7 +1,7 @@ "use server" -import { ChannelInfo } from "@/types" +import { ChannelInfo } from "@/types/general" import { getChannels } from "./getChannels" diff --git a/src/app/server/actions/ai-tube-hf/getChannelVideos.ts b/src/app/server/actions/ai-tube-hf/getChannelVideos.ts index 49b31a31589438262118fbea6710e5c4af818b09..d1a4de70cd156d0f888b0126d65ac7828f821810 100644 --- a/src/app/server/actions/ai-tube-hf/getChannelVideos.ts +++ b/src/app/server/actions/ai-tube-hf/getChannelVideos.ts @@ -1,12 +1,13 @@ "use server" -import { ChannelInfo, VideoInfo, VideoStatus } from "@/types" +import { ChannelInfo, VideoInfo, VideoStatus } from "@/types/general" import { getVideoRequestsFromChannel } from "./getVideoRequestsFromChannel" import { adminApiKey } from "../config" import { getVideoIndex } from "./getVideoIndex" import { extendVideosWithStats } from "./extendVideosWithStats" import { computeOrientationProjectionWidthHeight } from "../utils/computeOrientationProjectionWidthHeight" +import { defaultVideoModel } from "@/app/config" // return export async function getChannelVideos({ @@ -40,16 +41,18 @@ export async function getChannelVideos({ let video: VideoInfo = { id: v.id, status: "submitted", - label: v.label, - description: v.description, - prompt: v.prompt, - thumbnailUrl: v.thumbnailUrl, - model: v.model, - lora: v.lora, - style: v.style, - voice: v.voice, - music: v.music, + label: v.label || "", + description: v.description || "", + prompt: v.prompt || "", + thumbnailUrl: v.thumbnailUrl || "", + clapUrl: v.clapUrl || "", + model: v.model || defaultVideoModel, + lora: v.lora || "", + style: v.style || "", + voice: v.voice || "", + music: v.music || "", assetUrl: "", + assetUrlHd: "", numberOfViews: 0, numberOfLikes: 0, numberOfDislikes: 0, diff --git a/src/app/server/actions/ai-tube-hf/getChannels.ts b/src/app/server/actions/ai-tube-hf/getChannels.ts index cca7322ecf23e603a72691006a8b1f14a83555f8..454a1d539fb5c2195cc5e763c0439f3d4b99e914 100644 --- a/src/app/server/actions/ai-tube-hf/getChannels.ts +++ b/src/app/server/actions/ai-tube-hf/getChannels.ts @@ -1,6 +1,6 @@ "use server" -import { ChannelInfo } from "@/types" +import { ChannelInfo } from "@/types/general" import { adminUsername } from "../config" diff --git a/src/app/server/actions/ai-tube-hf/getPrivateChannels.ts b/src/app/server/actions/ai-tube-hf/getPrivateChannels.ts index 748c286d8f72cec07ef133c2521a2328b4066e8c..c32de22e3a1f7e5380b84dc68853fde7f40b997d 100644 --- a/src/app/server/actions/ai-tube-hf/getPrivateChannels.ts +++ b/src/app/server/actions/ai-tube-hf/getPrivateChannels.ts @@ -1,7 +1,7 @@ "use server" import { Credentials, listDatasets, whoAmI } from "@/huggingface/hub/src" -import { ChannelInfo } from "@/types" +import { ChannelInfo } from "@/types/general" import { adminCredentials } from "../config" import { parseChannel } from "./parseChannel" diff --git a/src/app/server/actions/ai-tube-hf/getVideo.ts b/src/app/server/actions/ai-tube-hf/getVideo.ts index e51fb66aafddee16ead83c827a7dcb1a01aadc8a..b5318449c852a8b177e3a82c9fcb69733097b4eb 100644 --- a/src/app/server/actions/ai-tube-hf/getVideo.ts +++ b/src/app/server/actions/ai-tube-hf/getVideo.ts @@ -1,6 +1,6 @@ "use server" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { getVideoIndex } from "./getVideoIndex" import { getStatsForVideos } from "../stats" diff --git a/src/app/server/actions/ai-tube-hf/getVideoIndex.ts b/src/app/server/actions/ai-tube-hf/getVideoIndex.ts index 467c908d3627b8f2d094c89ab22c664e7010f4bd..2dafdc9df6947340f8d00f16db0e9e9532a75e73 100644 --- a/src/app/server/actions/ai-tube-hf/getVideoIndex.ts +++ b/src/app/server/actions/ai-tube-hf/getVideoIndex.ts @@ -1,4 +1,4 @@ -import { VideoInfo, VideoStatus } from "@/types" +import { VideoInfo, VideoStatus } from "@/types/general" import { adminUsername } from "../config" diff --git a/src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts b/src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts index 1f1538092ba9c471956815d7a5a318c1418d9674..66bba2eaf7524fd041d698c09a0abc640b18f7d7 100644 --- a/src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts +++ b/src/app/server/actions/ai-tube-hf/getVideoRequestsFromChannel.ts @@ -1,12 +1,13 @@ "use server" -import { ChannelInfo, VideoRequest } from "@/types" +import { ChannelInfo, VideoRequest } from "@/types/general" import { getCredentials } from "./getCredentials" import { listFiles } from "@/huggingface/hub/src" import { parsePromptFileName } from "../utils/parsePromptFileName" import { downloadFileAsText } from "./downloadFileAsText" import { parseDatasetPrompt } from "../utils/parseDatasetPrompt" import { computeOrientationProjectionWidthHeight } from "../utils/computeOrientationProjectionWidthHeight" +import { downloadClapProject } from "./downloadClapProject" /** * Return all the videos requests created by a user on their channel @@ -42,89 +43,115 @@ export async function getVideoRequestsFromChannel({ ? { cache: "no-cache" } : undefined })) { - - // TODO we should add some safety mechanisms here: - // skip lists of files that are too long - // skip files that are too big - // skip files with file.security.safe !== true - - // console.log("file.path:", file.path) - /// { type, oid, size, path } - if (file.path === "README.md") { - // console.log("found the README") - // TODO: read this readme - } else if (file.path.startsWith("prompt_") && file.path.endsWith(".md")) { - - const id = parsePromptFileName(file.path) - - if (!id) { continue } - - const rawMarkdown = await downloadFileAsText({ - repo, - path: file.path, - apiKey, - renewCache, - neverThrow: true, - }) - - if (!rawMarkdown) { - // console.log(`markdown file is empty, skipping`) - continue - } - - const { - title, - description, - tags, - prompt, - thumbnail, - model, - lora, - style, - music, - voice, - orientation, - } = parseDatasetPrompt(rawMarkdown, channel) - - if (!title || !description || !prompt) { - // console.log("dataset prompt is incomplete or unparseable") - continue - } - // console.log("prompt parsed markdown:", { title, description, tags }) - let thumbnailUrl = - thumbnail.startsWith("http") - ? thumbnail - : (thumbnail.endsWith(".jpg") || thumbnail.endsWith(".jpeg")) - ? `https://huggingface.co/${repo}/resolve/main/${thumbnail}` - : "" - - - const video: VideoRequest = { - id, - label: title, - description, - prompt, - thumbnailUrl, - model, - lora, - style, - voice, - music, - updatedAt: file.lastCommit?.date || new Date().toISOString(), - tags: Array.isArray(tags) && tags.length ? tags : channel.tags, - channel, - duration: 0, - ...computeOrientationProjectionWidthHeight({ + try { + const filePath = file.path.toLowerCase().trim() + // TODO we should add some safety mechanisms here: + // skip lists of files that are too long + // skip files that are too big + // skip files with file.security.safe !== true + + // console.log("file.path:", file.path) + /// { type, oid, size, path } + if (filePath === "readme.md") { + // console.log("found the README") + // TODO: read this readme + } else if (filePath.endsWith(".clap")) { + const clap = await downloadClapProject({ + path: file.path, + channel, + apiKey + }) + console.log("got a clap file:", clap.clapProject.meta) + + // in the frontend UI we want to display everything, + // we don't filter stuff even if they are incomplete + + videos[clap.videoRequest.id] = clap.videoRequest + } else if (filePath.startsWith("prompt_") && filePath.endsWith(".md")) { + + const id = parsePromptFileName(filePath) + + if (!id) { continue } + + const rawMarkdown = await downloadFileAsText({ + repo, + path: file.path, // be sure to use the original file.path (with capitalization if any) and not filePath + apiKey, + renewCache, + neverThrow: true, + }) + + if (!rawMarkdown) { + // console.log(`markdown file is empty, skipping`) + continue + } + + const { + title, + description, + tags, + prompt, + thumbnail, + model, lora, + style, + music, + voice, orientation, - // projection, // <- will be extrapolated from the LoRA for now - }), + } = parseDatasetPrompt(rawMarkdown, channel) + + /* + on ai-tube side (not the ai-tube robot) we are okay with partial video requests, + ie. drafts + if (!title || !description || !prompt) { + // console.log("dataset prompt is incomplete or unparseable") + // continue + } + */ + + // console.log("prompt parsed markdown:", { title, description, tags }) + let thumbnailUrl = + thumbnail.startsWith("http") + ? thumbnail + : (thumbnail.endsWith(".webp") || thumbnail.endsWith(".jpg") || thumbnail.endsWith(".jpeg")) + ? `https://huggingface.co/${repo}/resolve/main/${thumbnail}` + : "" + + // TODO: the clap file is empty if + // the video is prompted using Markdown + const clapUrl = "" + + const video: VideoRequest = { + id, + label: title, + description, + prompt, + thumbnailUrl, + clapUrl, + model, + lora, + style, + voice, + music, + updatedAt: file.lastCommit?.date || new Date().toISOString(), + tags: Array.isArray(tags) && tags.length ? tags : channel.tags, + channel, + duration: 0, + ...computeOrientationProjectionWidthHeight({ + lora, + orientation, + // projection, // <- will be extrapolated from the LoRA for now + }), + } + + videos[id] = video + + } else if (filePath.endsWith(".mp4")) { + // console.log("found a video:", file.path) } - - videos[id] = video - - } else if (file.path.endsWith(".mp4")) { - // console.log("found a video:", file.path) + } catch (err) { + console.error("error while processing a dataset file:") + console.error(err) } } diff --git a/src/app/server/actions/ai-tube-hf/getVideos.ts b/src/app/server/actions/ai-tube-hf/getVideos.ts index c185c1627c1549610640f8146256360f04c85e04..a7ca24d65f0763f7083fb477b0926361ec1118ec 100644 --- a/src/app/server/actions/ai-tube-hf/getVideos.ts +++ b/src/app/server/actions/ai-tube-hf/getVideos.ts @@ -1,6 +1,6 @@ "use server" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { getVideoIndex } from "./getVideoIndex" import { extendVideosWithStats } from "./extendVideosWithStats" diff --git a/src/app/server/actions/ai-tube-hf/parseChannel.ts b/src/app/server/actions/ai-tube-hf/parseChannel.ts index 807bb4687c626f6b3800a1fa3eaaa16f2e4359aa..3e4380cd07ae3557245b173c102ceffa456d4732 100644 --- a/src/app/server/actions/ai-tube-hf/parseChannel.ts +++ b/src/app/server/actions/ai-tube-hf/parseChannel.ts @@ -2,7 +2,7 @@ import { Credentials, downloadFile, whoAmI } from "@/huggingface/hub/src" import { parseDatasetReadme } from "@/app/server/actions/utils/parseDatasetReadme" -import { ChannelInfo, VideoGenerationModel, VideoOrientation } from "@/types" +import { ChannelInfo, VideoGenerationModel, VideoOrientation } from "@/types/general" import { adminCredentials } from "../config" import { defaultVideoModel, defaultVideoOrientation } from "@/app/config" diff --git a/src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts b/src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts index a8d95076c570aacec53a173fd9ce41abe7f20c1c..5faec7b61ab86886e54fbc1f436afa318522cd0b 100644 --- a/src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts +++ b/src/app/server/actions/ai-tube-hf/uploadVideoRequestToDataset.ts @@ -3,7 +3,7 @@ import { Blob } from "buffer" import { Credentials, uploadFile, whoAmI } from "@/huggingface/hub/src" -import { ChannelInfo, VideoGenerationModel, VideoInfo, VideoOrientation, VideoRequest } from "@/types" +import { ChannelInfo, VideoGenerationModel, VideoInfo, VideoOrientation, VideoRequest } from "@/types/general" import { formatPromptFileName } from "../utils/formatPromptFileName" import { computeOrientationProjectionWidthHeight } from "../utils/computeOrientationProjectionWidthHeight" @@ -126,6 +126,10 @@ ${prompt} voice, music, thumbnailUrl: channel.thumbnail, + + // for now AI Tube doesn't support upload of clap files + clapUrl: "", + updatedAt: new Date().toISOString(), tags, channel, @@ -149,7 +153,12 @@ ${prompt} voice, music, thumbnailUrl: channel.thumbnail, // will be generated in async + + // for now AI Tube doesn't support upload of clap files + clapUrl: "", + assetUrl: "", // will be generated in async + assetUrlHd: "", numberOfViews: 0, numberOfLikes: 0, numberOfDislikes: 0, diff --git a/src/app/server/actions/ai-tube-robot/updateQueue.ts b/src/app/server/actions/ai-tube-robot/updateQueue.ts index c860f89c0398f56b87d41f1e1db25b5064117c36..a74729ccb196c197ba035dc08fda43c2d1d21ec4 100644 --- a/src/app/server/actions/ai-tube-robot/updateQueue.ts +++ b/src/app/server/actions/ai-tube-robot/updateQueue.ts @@ -1,6 +1,6 @@ "use server" -import { ChannelInfo, UpdateQueueResponse } from "@/types" +import { ChannelInfo, UpdateQueueResponse } from "@/types/general" import { aiTubeRobotApi } from "../config" diff --git a/src/app/server/actions/comments.ts b/src/app/server/actions/comments.ts index e63f1b94e6a6c6c996e8a553b25de95168daa237..1b3cd9ce503245542c9663274a22b1067586ece3 100644 --- a/src/app/server/actions/comments.ts +++ b/src/app/server/actions/comments.ts @@ -2,7 +2,7 @@ import { v4 as uuidv4 } from "uuid" -import { CommentInfo, StoredCommentInfo } from "@/types" +import { CommentInfo, StoredCommentInfo } from "@/types/general" import { stripHtml } from "@/lib/stripHtml" import { getCurrentUser, getUsers } from "./users" import { redis } from "./redis" diff --git a/src/app/server/actions/stats.ts b/src/app/server/actions/stats.ts index 067dbebe0750e1554824ed2cd943ec4f175de276..5031e27f4f6191f45cfeb57dc9351510f8475eaf 100644 --- a/src/app/server/actions/stats.ts +++ b/src/app/server/actions/stats.ts @@ -2,7 +2,7 @@ import { developerMode } from "@/app/config" import { WhoAmIUser, whoAmI } from "@/huggingface/hub/src" -import { VideoRating } from "@/types" +import { VideoRating } from "@/types/general" import { redis } from "./redis"; export async function getStatsForVideos(videoIds: string[]): Promise> { diff --git a/src/app/server/actions/submitVideoRequest.ts b/src/app/server/actions/submitVideoRequest.ts index d93466d0d47be53f7024d757d5bb67a7fc958b5a..611e5c4ae24c0333d6c4a0cec29924e3ff249bf4 100644 --- a/src/app/server/actions/submitVideoRequest.ts +++ b/src/app/server/actions/submitVideoRequest.ts @@ -1,6 +1,6 @@ "use server" -import { ChannelInfo, VideoGenerationModel, VideoInfo, VideoOrientation } from "@/types" +import { ChannelInfo, VideoGenerationModel, VideoInfo, VideoOrientation } from "@/types/general" import { uploadVideoRequestToDataset } from "./ai-tube-hf/uploadVideoRequestToDataset" diff --git a/src/app/server/actions/users.ts b/src/app/server/actions/users.ts index 8549dec8434b458fb4ad2b7d0e197e5fddae4f91..f09902e1ff44be4e397dfa5e1f2bcb36977e9861 100644 --- a/src/app/server/actions/users.ts +++ b/src/app/server/actions/users.ts @@ -1,7 +1,7 @@ "use server" import { WhoAmIUser, whoAmI } from "@/huggingface/hub/src" -import { UserInfo } from "@/types" +import { UserInfo } from "@/types/general" import { adminApiKey } from "./config" import { redis } from "./redis" diff --git a/src/app/server/actions/utils/computeOrientationProjectionWidthHeight.ts b/src/app/server/actions/utils/computeOrientationProjectionWidthHeight.ts index f1a04fbda199c31637f5dbe1dc2c778b4df4bb29..ea56dd5909bad14ed236728c2d99113250ca06b9 100644 --- a/src/app/server/actions/utils/computeOrientationProjectionWidthHeight.ts +++ b/src/app/server/actions/utils/computeOrientationProjectionWidthHeight.ts @@ -1,4 +1,4 @@ -import { VideoOrientation, VideoProjection } from "@/types" +import { VideoOrientation, VideoProjection } from "@/types/general" import { parseVideoOrientation } from "./parseVideoOrientation" import { parseProjectionFromLoRA } from "./parseProjectionFromLoRA" diff --git a/src/app/server/actions/utils/isAntisocial.ts b/src/app/server/actions/utils/isAntisocial.ts index dc1fd4a8d87b2a59db5789dde9cfdf1695478046..ef88c3f236ebe368ea740c602a4a68c7a38848f6 100644 --- a/src/app/server/actions/utils/isAntisocial.ts +++ b/src/app/server/actions/utils/isAntisocial.ts @@ -1,4 +1,4 @@ -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" const winners = new Set(`${process.env.WINNERS || ""}`.toLowerCase().split(",").map(x => x.trim()).filter(x => x)) diff --git a/src/app/server/actions/utils/isHighQuality.ts b/src/app/server/actions/utils/isHighQuality.ts index 945d66638fd12fb160c495c399dcfbac54b7ff71..6d4e5ef9c9b61dd4a457715e2ba0e932debe2a70 100644 --- a/src/app/server/actions/utils/isHighQuality.ts +++ b/src/app/server/actions/utils/isHighQuality.ts @@ -1,4 +1,4 @@ -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" export function isHighQuality(video: VideoInfo) { const numberOfViews = Math.abs(Math.max(0, video.numberOfViews)) diff --git a/src/app/server/actions/utils/parseClap.ts b/src/app/server/actions/utils/parseClap.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb7b3376c8a1b532090ea29fd05c8929f45f989f --- /dev/null +++ b/src/app/server/actions/utils/parseClap.ts @@ -0,0 +1,91 @@ +import YAML from "yaml" +import { v4 as uuidv4 } from "uuid" + +import { ClapHeader, ClapMeta, ClapProject, ClapSegment } from "@/types/clap" +import { getValidNumber } from "@/lib/getValidNumber" + +/** + * import a Clap file (from a plain text string) + * + * note: it is not really async, because for some reason YAML.parse is a blocking call like for JSON, + * they is no async version although we are now in the 20s not 90s + */ +export async function parseClap(inputStringOrBlob: string | Blob): Promise { + + // Decompress the input blob using gzip + const decompressor = new DecompressionStream('gzip'); + + const inputBlob = + typeof inputStringOrBlob === "string" + ? new Blob([inputStringOrBlob], { type: "application/x-yaml" }) + : inputStringOrBlob; + + const decompressedStream = inputBlob.stream().pipeThrough(decompressor); + + // Convert the stream to text using a Response object + const text = await new Response(decompressedStream).text(); + + // Parse YAML string to raw data + const rawData = YAML.parse(text); + + if (!Array.isArray(rawData) || rawData.length < 2) { + throw new Error("invalid clap file (need a clap format header block and project metadata block)") + } + + const maybeClapHeader = rawData[0] as ClapHeader + + if (maybeClapHeader.format !== "clap-0") { + throw new Error("invalid clap file (sorry, but you can't make up version numbers like that)") + } + + const maybeClapMeta = rawData[1] as ClapMeta + + const clapMeta: ClapMeta = { + id: typeof maybeClapMeta.title === "string" ? maybeClapMeta.id : uuidv4(), + title: typeof maybeClapMeta.title === "string" ? maybeClapMeta.title : "", + description: typeof maybeClapMeta.description === "string" ? maybeClapMeta.description : "", + licence: typeof maybeClapMeta.licence === "string" ? maybeClapMeta.licence : "", + orientation: maybeClapMeta.orientation === "portrait" ? "portrait" : maybeClapMeta.orientation === "square" ? "square" : "landscape", + width: getValidNumber(maybeClapMeta.width, 256, 4096, 1024), + height: getValidNumber(maybeClapMeta.height, 256, 4096, 1024), + defaultVideoModel: typeof maybeClapMeta.defaultVideoModel === "string" ? maybeClapMeta.defaultVideoModel : "SVD", + } + + const maybeSegments = rawData.slice(2) as ClapSegment[] + + const clapSegments: ClapSegment[] = Array.isArray(maybeSegments) ? maybeSegments.map(({ + id, + track, + startTimeInMs, + endTimeInMs, + category, + modelId, + prompt, + outputType, + renderId, + status, + assetUrl, + outputGain, + seed, + }) => ({ + // TODO: we should verify each of those, probably + id, + track, + startTimeInMs, + endTimeInMs, + category, + modelId, + prompt, + outputType, + renderId, + status, + assetUrl, + outputGain, + seed, + })) : [] + + return { + meta: clapMeta, + segments: clapSegments + } +} diff --git a/src/app/server/actions/utils/parseDatasetPrompt.ts b/src/app/server/actions/utils/parseDatasetPrompt.ts index b97147dc45d73cb29b912732549a32a5f04753aa..c2eef8de8d3c08e4fc22eea43a6d5a467d459bed 100644 --- a/src/app/server/actions/utils/parseDatasetPrompt.ts +++ b/src/app/server/actions/utils/parseDatasetPrompt.ts @@ -1,5 +1,5 @@ -import { ChannelInfo, ParsedDatasetPrompt } from "@/types" +import { ChannelInfo, ParsedDatasetPrompt } from "@/types/general" import { parseVideoModelName } from "./parseVideoModelName" import { parseVideoOrientation } from "./parseVideoOrientation" import { defaultVideoModel, defaultVideoOrientation } from "@/app/config" diff --git a/src/app/server/actions/utils/parseDatasetReadme.ts b/src/app/server/actions/utils/parseDatasetReadme.ts index 48510fa9b57785baa7728db624b5c0420f53eb76..a25088ca055cc078ea374c2d4236239601816410 100644 --- a/src/app/server/actions/utils/parseDatasetReadme.ts +++ b/src/app/server/actions/utils/parseDatasetReadme.ts @@ -1,7 +1,7 @@ import metadataParser from "markdown-yaml-metadata-parser" -import { ParsedDatasetReadme, ParsedMetadataAndContent } from "@/types" +import { ParsedDatasetReadme, ParsedMetadataAndContent } from "@/types/general" import { parseVideoModelName } from "./parseVideoModelName" import { parseVideoOrientation } from "./parseVideoOrientation" import { defaultVideoModel, defaultVideoOrientation } from "@/app/config" diff --git a/src/app/server/actions/utils/parseProjectionFromLoRA.ts b/src/app/server/actions/utils/parseProjectionFromLoRA.ts index f747e2e8df62c7489df2e4c5106d8f9fffc07397..7b8a1ed61abf193f73956e9afa5dede15a9cfb6a 100644 --- a/src/app/server/actions/utils/parseProjectionFromLoRA.ts +++ b/src/app/server/actions/utils/parseProjectionFromLoRA.ts @@ -1,4 +1,4 @@ -import { VideoProjection } from "@/types" +import { VideoProjection } from "@/types/general" export function parseProjectionFromLoRA(input?: any): VideoProjection { const name = `${input || ""}`.trim().toLowerCase() diff --git a/src/app/server/actions/utils/parseVideoModelName.ts b/src/app/server/actions/utils/parseVideoModelName.ts index 04d7584a35d8d60995c373cc6191393c55cf7a24..8f406705552535930bbcdde5436420e487d59d9a 100644 --- a/src/app/server/actions/utils/parseVideoModelName.ts +++ b/src/app/server/actions/utils/parseVideoModelName.ts @@ -1,4 +1,4 @@ -import { VideoGenerationModel } from "@/types" +import { VideoGenerationModel } from "@/types/general" export function parseVideoModelName(text: any, defaultToUse: VideoGenerationModel): VideoGenerationModel { const rawModelString = `${text || ""}`.trim().toLowerCase() diff --git a/src/app/server/actions/utils/parseVideoOrientation.ts b/src/app/server/actions/utils/parseVideoOrientation.ts index 2aa76a5896769a97a3b02cd631088890504d4cf5..d5b50f4ec961d1d1b26f40c76773068c1e0b294c 100644 --- a/src/app/server/actions/utils/parseVideoOrientation.ts +++ b/src/app/server/actions/utils/parseVideoOrientation.ts @@ -1,5 +1,5 @@ import { defaultVideoOrientation } from "@/app/config" -import { VideoOrientation } from "@/types" +import { VideoOrientation } from "@/types/general" export function parseVideoOrientation(text: any, defaultToUse?: VideoOrientation): VideoOrientation { const rawOrientationString = `${text || ""}`.trim().toLowerCase() diff --git a/src/app/state/defaultSettings.ts b/src/app/state/defaultSettings.ts index 50cc0dcb5e208f4cc1775a875da6a9fb16a77637..d6140174c8dc5d8b47aff9927d7bef5b9376d93a 100644 --- a/src/app/state/defaultSettings.ts +++ b/src/app/state/defaultSettings.ts @@ -1,4 +1,4 @@ -import { Settings } from "@/types" +import { Settings } from "@/types/general" export const defaultSettings: Settings = { huggingfaceApiKey: "", diff --git a/src/app/state/localStorageKeys.ts b/src/app/state/localStorageKeys.ts index cf68537a6be5ea54ce94b9b5bdb021619e37ce33..73a36213c9579b1c65c812d1c6368e7d799a8d4b 100644 --- a/src/app/state/localStorageKeys.ts +++ b/src/app/state/localStorageKeys.ts @@ -1,4 +1,4 @@ -import { Settings } from "@/types" +import { Settings } from "@/types/general" export const localStorageKeys: Record = { // important: prefix with AI_TUBE to avoid collisions when running the app on localhost diff --git a/src/app/state/useStore.ts b/src/app/state/useStore.ts index 76cd033dbb918b647ee8f13dfdac63bd7665e2ce..1bb0fb203d8d223105893221a4b47505390d710e 100644 --- a/src/app/state/useStore.ts +++ b/src/app/state/useStore.ts @@ -2,7 +2,7 @@ import { create } from "zustand" -import { ChannelInfo, VideoInfo, InterfaceDisplayMode, InterfaceView, InterfaceMenuMode, InterfaceHeaderMode, CommentInfo, UserInfo } from "@/types" +import { ChannelInfo, VideoInfo, InterfaceDisplayMode, InterfaceView, InterfaceMenuMode, InterfaceHeaderMode, CommentInfo, UserInfo } from "@/types/general" export const useStore = create<{ displayMode: InterfaceDisplayMode diff --git a/src/app/state/userCurrentUser.ts b/src/app/state/userCurrentUser.ts index 7c2d47e2719b8d6cd2dc286bc788facbe2ba223d..cd06432229b57b8359754592d509662b5edec8c3 100644 --- a/src/app/state/userCurrentUser.ts +++ b/src/app/state/userCurrentUser.ts @@ -1,6 +1,6 @@ import { useEffect, useTransition } from "react" -import { UserInfo } from "@/types" +import { UserInfo } from "@/types/general" import { useStore } from "./useStore" import { useLocalStorage } from "usehooks-ts" diff --git a/src/app/views/home-view/index.tsx b/src/app/views/home-view/index.tsx index ffe28dce3bbd82452670a11c1015c83339ff0769..30cd502edbcfe35d3e03bb1960bd5485ce66c674 100644 --- a/src/app/views/home-view/index.tsx +++ b/src/app/views/home-view/index.tsx @@ -4,7 +4,7 @@ import { useEffect, useTransition } from "react" import { useStore } from "@/app/state/useStore" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { getVideos } from "@/app/server/actions/ai-tube-hf/getVideos" import { VideoList } from "@/app/interface/video-list" import { getTags } from "@/app/server/actions/ai-tube-hf/getTags" diff --git a/src/app/views/public-music-videos-view/index.tsx b/src/app/views/public-music-videos-view/index.tsx index 4d1b06bbb5ac75746e20d7a0c0520ae8683c2601..3ed1f11c56bd44f405176d6428671a88bf4fb653 100644 --- a/src/app/views/public-music-videos-view/index.tsx +++ b/src/app/views/public-music-videos-view/index.tsx @@ -4,7 +4,7 @@ import { useEffect, useTransition } from "react" import { useStore } from "@/app/state/useStore" import { cn } from "@/lib/utils" -import { VideoInfo } from "@/types" +import { VideoInfo } from "@/types/general" import { getVideos } from "@/app/server/actions/ai-tube-hf/getVideos" import { TrackList } from "@/app/interface/track-list" import { PlaylistControl } from "@/app/interface/playlist-control" diff --git a/src/app/views/report-modal/index.tsx b/src/app/views/report-modal/index.tsx index fb6025d21308f397f25a7826575cb124b8991298..78086a2d3b3a2abb08de3375b890d45dea449958 100644 --- a/src/app/views/report-modal/index.tsx +++ b/src/app/views/report-modal/index.tsx @@ -4,7 +4,7 @@ import { LuShieldAlert } from "react-icons/lu" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTrigger } from "@/components/ui/dialog" -import { ChannelInfo, VideoInfo } from "@/types" +import { ChannelInfo, VideoInfo } from "@/types/general" import { ActionButton } from "@/app/interface/action-button" // modal to report a video or channel diff --git a/src/app/views/user-account-view/index.tsx b/src/app/views/user-account-view/index.tsx index 5b554ceefe97333adef66bc25226b3e91d8ec74c..f2935749a54548fdd24a41eeacb6de3cc79c6e9c 100644 --- a/src/app/views/user-account-view/index.tsx +++ b/src/app/views/user-account-view/index.tsx @@ -19,6 +19,7 @@ export function UserAccountView() { defaultSettings.huggingfaceApiKey ) const setView = useStore(s => s.setView) + const userChannel = useStore(s => s.userChannel) const setUserChannel = useStore(s => s.setUserChannel) const userChannels = useStore(s => s.userChannels) @@ -26,23 +27,23 @@ export function UserAccountView() { const [isLoaded, setLoaded] = useState(false) useEffect(() => { - if (!isLoaded) { startTransition(async () => { - try { - const channels = await getPrivateChannels({ - apiKey: huggingfaceApiKey, - renewCache: true, - }) - setUserChannels(channels) - } catch (err) { - console.error("failed to load the channel for the current user:", err) - setUserChannels([]) - } finally { - setLoaded(true) + if (!isLoaded && huggingfaceApiKey) { + try { + const newUserChannels = await getPrivateChannels({ + apiKey: huggingfaceApiKey, + renewCache: true, + }) + setUserChannels(newUserChannels) + } catch (err) { + console.error("failed to load the channel for the current user:", err) + setUserChannels([]) + } finally { + setLoaded(true) + } } }) - } - }, [isLoaded, huggingfaceApiKey, setUserChannels, setLoaded]) + }, [isLoaded, huggingfaceApiKey, userChannels.map(c => c.id).join(","), setUserChannels, setLoaded]) return (
diff --git a/src/app/views/user-channel-view/index.tsx b/src/app/views/user-channel-view/index.tsx index ffab738fc650c223d62bc8f10f6abbbb28dcb308..f969ea941002c401c5cd6f3e4b04eebec41597e0 100644 --- a/src/app/views/user-channel-view/index.tsx +++ b/src/app/views/user-channel-view/index.tsx @@ -4,7 +4,7 @@ import { useEffect, useState, useTransition } from "react" import { useStore } from "@/app/state/useStore" import { cn } from "@/lib/utils" -import { VideoGenerationModel, VideoInfo } from "@/types" +import { VideoGenerationModel, VideoInfo } from "@/types/general" import { useLocalStorage } from "usehooks-ts" import { localStorageKeys } from "@/app/state/localStorageKeys" diff --git a/src/app/watch/page.tsx b/src/app/watch/page.tsx index 48b6432135862de02451a8e53877164bad934f4b..adfdb8a4b8c4f85482f7e8c444b081a7b6dab530 100644 --- a/src/app/watch/page.tsx +++ b/src/app/watch/page.tsx @@ -1,7 +1,7 @@ import { Metadata, ResolvingMetadata } from "next" -import { AppQueryProps } from "@/types" +import { AppQueryProps } from "@/types/general" import { Main } from "../main" import { getVideo } from "../server/actions/ai-tube-hf/getVideo" diff --git a/src/lib/getChannelRating.ts b/src/lib/getChannelRating.ts index 79dbd5fa70c696cd1ae280a54d5756dcc2c5e1ff..f23f77443f5a19e7469b82b467ab37285f3d4553 100644 --- a/src/lib/getChannelRating.ts +++ b/src/lib/getChannelRating.ts @@ -1,4 +1,4 @@ -import { ChannelInfo } from "@/types" +import { ChannelInfo } from "@/types/general" const winners = new Set(`${process.env.WINNERS || ""}`.toLowerCase().split(",").map(x => x.trim()).filter(x => x)) diff --git a/src/lib/getInitialRenderedScene.ts b/src/lib/getInitialRenderedScene.ts index 7c0739bf8bebdaf16aa4acf610eb6bdad9c15fd2..119337f9f6e80d7e92ffdf822edf3ff7a84666c2 100644 --- a/src/lib/getInitialRenderedScene.ts +++ b/src/lib/getInitialRenderedScene.ts @@ -1,4 +1,4 @@ -import { RenderedScene } from "@/types" +import { RenderedScene } from "@/types/general" export const getInitialRenderedScene = (): RenderedScene => ({ renderId: "", diff --git a/src/lib/usePlaylist.ts b/src/lib/usePlaylist.ts index ede3450b711af6d3b0fee884e44f4753a666066b..eb3de6fc3fa4da270bc05437d20dcce8bc9546eb 100644 --- a/src/lib/usePlaylist.ts +++ b/src/lib/usePlaylist.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef } from "react"; import { create } from "zustand"; -import { VideoInfo } from "@/types"; +import { VideoInfo } from "@/types/general"; // Define the new track type with an optional playNow property interface PlaybackOptions { diff --git a/src/types/clap.ts b/src/types/clap.ts new file mode 100644 index 0000000000000000000000000000000000000000..cce54d029c408ed9a41c50c217f845eda4113fa0 --- /dev/null +++ b/src/types/clap.ts @@ -0,0 +1,35 @@ +export type ClapHeader = { + format: "clap-0" +} + +export type ClapMeta = { + id: string + title: string + description: string + licence: string + orientation: string + width: number + height: number + defaultVideoModel: string +} + +export type ClapSegment = { + id: string + track: number + startTimeInMs: number + endTimeInMs: number + category: "render" | "preview" | "characters" | "location" | "time" | "era" | "lighting" | "weather" | "action" | "music" | "sound" | "dialogue" | "style" | "camera" | "generic" + modelId: string + prompt: string + outputType: "text" | "movement" | "image" | "video" | "audio" + renderId: string + status: "pending" | "completed" | "error" + assetUrl: string + outputGain: number + seed: number +} + +export type ClapProject = { + meta: ClapMeta + segments: ClapSegment[] +} diff --git a/src/types.ts b/src/types/general.ts similarity index 94% rename from src/types.ts rename to src/types/general.ts index b1ceee38f2fa3e06f4176934b7f56bc65a4cd39b..b98992fd5ae0effb4cb6aa9e396121989b81c92b 100644 --- a/src/types.ts +++ b/src/types/general.ts @@ -239,9 +239,12 @@ export type ChannelInfo = { } export type VideoStatus = - | "submitted" // the prompt has been submitted, but is not added to the index queue yet - | "queued" // the prompt has been added to the index queue, but is not processed yet. Once queued it cannot be modified. + | "draft" // the video should be ignored for now + | "submitted" // the video has been submitted, but is not added to the index queue yet + | "queued" // the video has been added to the index queue, but is not processed yet. Once queued it cannot be modified. | "generating" // the video is being generated + // TODO add a state to indicate the audio is being generated + // this will be useful in case generation fails | "published" // success! | "error" // video failed to generate @@ -277,6 +280,11 @@ export type VideoRequest = { */ thumbnailUrl: string + /** + * URL to a clap file + */ + clapUrl: string + /** * When was the video updated */ @@ -369,11 +377,28 @@ export type VideoInfo = { */ thumbnailUrl: string + /** + * URL to a clap file + */ + clapUrl: string + /** * URL to the binary file + * + * This is the standard format + * + * see width and height for details + * + * (it will be something like 1024x576, 576x1024, 1024x512) + * */ assetUrl: string + /** + * This is contain the storage URL of the higher-resolution video + */ + assetUrlHd: string + /** * Counter for the number of views *