Merge branch 'master' into feature/mvp

This commit is contained in:
BFlat authored and GitHub committed 2024-05-09 08:48:53 +07:00
commit 7f6adabb77
14 files changed
+413 -152

No files matched your search

@@ -24,5 +24,8 @@ export enum MediaFileType {
// This is a temporary format that will eventually go away.
SceneRon = "scene_ron",
// Engine scene
SceneJson = "scene_json",
None = "none",
}
@@ -0,0 +1,20 @@
import MakeRequest from "../MakeRequest";
export interface CreateFeaturedItemRequest {
entity_token: string,
entity_type: string,
}
export interface CreateFeaturedItemResponse {
success: boolean
}
export const CreateFeaturedItem = MakeRequest<
string,
CreateFeaturedItemRequest,
CreateFeaturedItemResponse,
{}
>({
method: "POST",
routingFunction: () => "/v1/featured_item/create",
});
@@ -0,0 +1,20 @@
import MakeRequest from "../MakeRequest";
export interface DeleteFeaturedItemRequest {
entity_token: string,
entity_type: string,
}
export interface DeleteFeaturedItemResponse {
success: boolean
}
export const DeleteFeaturedItem = MakeRequest<
string,
DeleteFeaturedItemRequest,
DeleteFeaturedItemResponse,
{}
>({
method: "DELETE",
routingFunction: () => "/v1/featured_item/delete",
});
@@ -19,8 +19,7 @@ export interface MediaFile {
maybe_creator_user: UserDetailsLight | null;
maybe_prompt_token: string | null;
creator_set_visibility: string;
created_at: Date;
updated_at: Date;
is_featured: boolean;
maybe_model_weight_info: {
title: string;
weight_token: string;
@@ -36,6 +35,8 @@ export interface MediaFile {
},
maybe_cover_image_public_bucket_path: string | null
};
created_at: Date;
updated_at: Date;
}
export interface GetMediaRequest {}
@@ -1,4 +1,24 @@
{
"MediaCategories": {
"all": "All",
"audio": "Audio",
"image": "Image",
"video": "Video",
"engine_asset": "3D Asset"
},
"MediaCategoriesPlural": {
"all": "All",
"audio": "Audio",
"image": "Images",
"video": "Videos",
"engine_asset": "3D Assets"
},
"WeightsCategories": {
"all": "All",
"faceAnimation": "Face Animator",
"tts": "Text to speech",
"voiceConversion": "Voice conversion"
},
"WeightTypes": {
"all": "All weights",
"hifigan_tt2": "HiFi-GAN TT2",
@@ -20,5 +40,5 @@
"glb": "GLB",
"gltf": "gltf",
"fbx": "FBX"
},
}
}
}
@@ -17,6 +17,24 @@ export type VstStyle
| "pop_art"
| "realistic_1"
| "realistic_2"
| "hr_giger"
| "simpsons"
| "carnage"
| "pastel_cute_anime"
| "bloom_lighting"
| "25d_horror"
| "creepy"
| "creepy_vhs"
| "trail_cam_footage"
| "old_black_white_movie"
| "horror_noir_black_white"
| "techno_noir_black_white"
| "black_white_20s"
| "cyberpunk_anime"
| "dragonball"
| "realistic_matrix"
| "realistic_cyberpunk"
;
// Definition of the different options.
@@ -102,6 +120,80 @@ export const STYLE_OPTIONS: readonly StyleOption[] = [
label: "Realistic 2",
value: "realistic_2",
},
{
label: "HR Giger",
value: "hr_giger",
},
{
label: "Simpsons",
value: "simpsons",
},
{
label: "Carnage",
value: "carnage",
},
{
label: "Anime Pastel Cute",
value: "pastel_cute_anime",
},
{
label: "iBloom Lighting",
value: "bloom_lighting",
},
{
label: "Horror 2.5D",
value: "25d_horror",
},
{
label: "Creepy",
value: "creepy",
},
{
label: "Creepy VHS",
value: "creepy_vhs",
},
{
label: "Trail Cam Footage",
value: "trail_cam_footage",
},
{
label: "Old Black and White Movie",
value: "old_black_white_movie",
},
{
label: "Horror Noir Black and White",
value: "horror_noir_black_white",
},
{
label: "Techno Noir Black and White",
value: "techno_noir_black_white",
},
{
label: "Black White 20s",
value: "black_white_20s",
},
{
label: "Cyberpunk Anime",
value: "cyberpunk_anime",
},
{
label: "Dragonball",
value: "dragonball",
},
{
label: "Realistic Matrix",
value: "realistic_matrix",
},
{
label: "Realistic Cyberpunk",
value: "realistic_cyberpunk",
},
];
export const STYLES_BY_KEY : Map<string, StyleOption> = new Map(STYLE_OPTIONS.map((opt) => [opt.value, opt]));
@@ -75,6 +75,7 @@ describe('media files', () => {
updated_at: new Date(),
maybe_creator_user: null,
creator_set_visibility: "public",
is_featured: false,
// TODO(bt,2024-03-11): Make these fields optional
maybe_model_weight_info: {
title: "title",
@@ -1,6 +1,6 @@
import { enumToKeyArr } from "resources";
import { UploadMediaResponse } from "@storyteller/components/src/api/media_files/UploadMedia";
import { UploadEngineAssetResponse }from "@storyteller/components/src/api/media_files/UploadEngineAsset";
import { UploadEngineAssetResponse } from "@storyteller/components/src/api/media_files/UploadEngineAsset";
// 1
@@ -10,7 +10,7 @@ import { UploadEngineAssetResponse }from "@storyteller/components/src/api/media_
export enum EntityType {
unknown,
media,
weights
weights,
}
// 2
@@ -23,14 +23,14 @@ export enum MediaFilters {
audio,
image,
video,
engine_asset
engine_asset,
}
export enum WeightsCategories {
all,
faceAnimation,
tts,
voiceConversion
voiceConversion,
}
// 3
@@ -49,7 +49,7 @@ export enum WeightsFilters {
so_vits_svc,
tt2,
loRA,
vall_e
vall_e,
}
// media file types
@@ -60,17 +60,17 @@ export enum EngineTypes {
glb,
gltf,
obj,
ron
ron,
}
export enum AudioTypes {
mp3,
wav
wav,
}
export enum ImageTypes {
jpg,
png
png,
}
export enum VideoTypes {
@@ -128,15 +128,19 @@ export type JobSelection = WeightCategoriesProp | WeightFilterProp;
export type UploaderResponse = UploadEngineAssetResponse | UploadMediaResponse;
export const ListEntityFilters = (mode?: EntityInputMode) => {
// this an object of all types
// in most cases it is better to have granular caterogization of types
// but because any entity can/could(?) be bookmarked all types are agregated here
// this is condensed because typescript didn't like when I broke it apart.
// Will clean up soon, I promise
const bookmarkFilters = Object.keys({ ...MediaFilters, ...WeightsFilters }).filter(val => isNaN(Number(val))).reduce((obj,current) => ({ ...obj, [current]: current }),{});
const bookmarkFilters = Object.keys({ ...MediaFilters, ...WeightsFilters })
.filter(val => isNaN(Number(val)))
.reduce((obj, current) => ({ ...obj, [current]: current }), {});
const selectedFilters = mode !== undefined ? [bookmarkFilters,MediaFilters,WeightsFilters,WeightsFilters][mode] : EntityInputMode;
const selectedFilters =
mode !== undefined
? [bookmarkFilters, MediaFilters, WeightsFilters, WeightsFilters][mode]
: EntityInputMode;
return Object.values(selectedFilters).filter(val => isNaN(Number(val)));
};
@@ -145,36 +149,59 @@ export const ListEntityFilters = (mode?: EntityInputMode) => {
// note the use t() to translate labels
// this allows easily maintained translations for inputs
export const EntityFilterOptions = (mode?: EntityInputMode, t = (v:string) => v) => {
return ListEntityFilters(mode).map((value) => {
if (typeof value === "string") return { value, label: t(value) }
return { label: "all", value: "all" };
export const EntityFilterOptions = (
mode?: EntityInputMode,
t = (v: string) => v
) => {
const translationNamespaces = [
"BookmarksCategories",
"MediaCategoriesPlural",
"WeightsCategories",
"WeightsCategories",
];
const selectedNamespace = translationNamespaces[mode || -1];
return ListEntityFilters(mode).map(value => {
if (typeof value === "string")
return {
value,
label:
selectedNamespace !== undefined
? t(`${selectedNamespace}.${value}`)
: value,
};
return { label: t(`${selectedNamespace}.all`), value: "all" };
});
};
export const getMediaTypesByCategory = (mediaCategory: MediaFilters) => enumToKeyArr([
{ ...AudioTypes, ...ImageTypes, ...VideoTypes, ...EngineTypes },
AudioTypes,
ImageTypes,
VideoTypes,
EngineTypes
][mediaCategory]);
export const getMediaTypesByCategory = (mediaCategory: MediaFilters) =>
enumToKeyArr(
[
{ ...AudioTypes, ...ImageTypes, ...VideoTypes, ...EngineTypes },
AudioTypes,
ImageTypes,
VideoTypes,
EngineTypes,
][mediaCategory]
);
export const isSelectedType = (mediaCategory: MediaFilters, fileExtension: string) =>
getMediaTypesByCategory(mediaCategory).includes(fileExtension);
export const isSelectedType = (
mediaCategory: MediaFilters,
fileExtension: string
) => getMediaTypesByCategory(mediaCategory).includes(fileExtension);
export const mediaCategoryfromString = (categoryStr: string) => enumToKeyArr(MediaFilters).indexOf(categoryStr);
export const mediaCategoryfromString = (categoryStr: string) =>
enumToKeyArr(MediaFilters).indexOf(categoryStr);
//renaming to getMediaCatgoryByType
export const getMediaCategory = (fileExtension: string) => {
isSelectedType(MediaFilters.image,fileExtension);
if (isSelectedType(MediaFilters.engine_asset, fileExtension)) return MediaFilters.engine_asset;
if (isSelectedType(MediaFilters.audio, fileExtension)) return MediaFilters.audio;
if (isSelectedType(MediaFilters.image, fileExtension)) return MediaFilters.image;
if (isSelectedType(MediaFilters.video, fileExtension)) return MediaFilters.video;
isSelectedType(MediaFilters.image, fileExtension);
if (isSelectedType(MediaFilters.engine_asset, fileExtension))
return MediaFilters.engine_asset;
if (isSelectedType(MediaFilters.audio, fileExtension))
return MediaFilters.audio;
if (isSelectedType(MediaFilters.image, fileExtension))
return MediaFilters.image;
if (isSelectedType(MediaFilters.video, fileExtension))
return MediaFilters.video;
return MediaFilters.all; // will change to "unknown" eventually
}
};
@@ -9,6 +9,7 @@ export const mediaTypeLabels = {
[MediaFileType.GLB]: "GLB",
[MediaFileType.GLTF]: "glTF",
[MediaFileType.SceneRon]: "RON",
[MediaFileType.SceneJson]: "Scene",
[MediaFileType.FBX]: "FBX",
[MediaFileType.None]: "Unknown",
};
@@ -36,13 +36,16 @@ import { useBookmarks, useMedia, useRatings, useSession } from "hooks";
import { WeightCategory } from "@storyteller/components/src/api/_common/enums/WeightCategory";
import SdBatchMediaPanel from "./components/SdBatchMediaPanel/SdBatchMediaPanel";
import { GetMediaBatchImages } from "@storyteller/components/src/api/media_files/GetMediaBatchImages";
import { CreateFeaturedItem } from "@storyteller/components/src/api/featured_items/CreateFeaturedItem";
import { DeleteFeaturedItem } from "@storyteller/components/src/api/featured_items/DeleteFeaturedItem";
import { mediaTypeLabels } from "utils/mediaTypeLabels";
import { EngineMediaPanel } from "./components/EngineMediaPanel/EngineMediaPanel";
import { GetMediaFileTitle } from "common/GetMediaFileTitle";
import { STYLES_BY_KEY } from "common/StyleOptions";
import { faStarShooting } from "@fortawesome/pro-duotone-svg-icons";
export default function MediaPage() {
const { canAccessStudio, canEditMediaFile, user } = useSession();
const { canAccessStudio, canEditMediaFile, canBanUsers, user } = useSession();
const { token } = useParams<{ token: string }>();
const bookmarks = useBookmarks();
const ratings = useRatings();
@@ -70,6 +73,8 @@ export default function MediaPage() {
mediaFile?.maybe_creator_user?.user_token || ""
);
const viewerCanMakeFeatured = canBanUsers() || false;
// Inside MediaPage.tsx
useEffect(() => {
@@ -99,6 +104,31 @@ export default function MediaPage() {
const deleteMedia = () => remove(!!user?.can_ban_users);
const handleFeatureMedia = async () => {
setFeatureMedia(!mediaFile?.is_featured);
};
const setFeatureMedia = async (setFeatured: boolean) => {
if (mediaFile === undefined) {
return;
}
const request = {
entity_type: "media_file",
entity_token: mediaFile.token,
};
// NB: Victor, I don't know how to re-query media with the media context thing. :(
// Sorry, I'm forcing a page reload instead. I know this sucks.
if (setFeatured) {
await CreateFeaturedItem("", request);
window.location.reload();
} else {
await DeleteFeaturedItem("", request);
window.location.reload();
}
};
const copyToClipboard = async (
text: string,
setButtonText: React.Dispatch<React.SetStateAction<string>>
@@ -167,16 +197,15 @@ export default function MediaPage() {
{prompt?.maybe_style_name && (
<>
<div className="d-flex gap-3 align-items-center mb-2 mt-3">
<h6 className="fw-semibold mb-0 flex-grow-1">
Style Name
</h6>
<h6 className="fw-semibold mb-0 flex-grow-1">Style Name</h6>
</div>
<div className="panel-inner p-2 rounded">
<p className="fs-7">{STYLES_BY_KEY.get(prompt.maybe_style_name)?.label}</p>
<p className="fs-7">
{STYLES_BY_KEY.get(prompt.maybe_style_name)?.label}
</p>
</div>
</>
)}
</Panel>
)}
</>
@@ -270,8 +299,24 @@ export default function MediaPage() {
</div>
</Panel>
);
case MediaFileType.SceneJson:
return (
<div
{...{
className:
"rounded w-100 h-100 panel d-flex align-items-center justify-content-center p-3",
}}
>
<Button
{...{
label: "View in engine",
href: `https://studio.storyteller.ai/${mediaFile.token}`,
}}
/>
</div>
);
default:
return <div>Unsupported media type</div>;
return <div>Unsupported media typeaaa</div>;
}
}
@@ -475,30 +520,37 @@ export default function MediaPage() {
const title = GetMediaFileTitle(mediaFile);
const showEngineCover = () => {
const coverMediaPath = mediaFile?.cover_image?.maybe_cover_image_public_bucket_path || "";
const coverMediaPath =
mediaFile?.cover_image?.maybe_cover_image_public_bucket_path || "";
if (mediaFile) {
switch (mediaFile.media_type) {
case MediaFileType.BVH:
case MediaFileType.GLB:
case MediaFileType.GLTF:
case MediaFileType.SceneRon: return <WeightCoverImage {...{
...coverMediaPath ? { src: bucketConfig.getGcsUrl(coverMediaPath) } : {},
...viewerCanEdit ? { to: `/edit-cover-image/${ token }` } : {},
coverIndex: mediaFile.cover_image.default_cover.image_index,
}}/>;
default: return null;
case MediaFileType.BVH:
case MediaFileType.GLB:
case MediaFileType.GLTF:
case MediaFileType.SceneRon:
return (
<WeightCoverImage
{...{
...(coverMediaPath
? { src: bucketConfig.getGcsUrl(coverMediaPath) }
: {}),
...(viewerCanEdit ? { to: `/edit-cover-image/${token}` } : {}),
coverIndex: mediaFile.cover_image.default_cover.image_index,
}}
/>
);
default:
return null;
}
}
}
};
return (
<div>
<Container type="panel" className="mb-5">
<Panel clear={true} className="py-4">
<div className="d-flex flex-column flex-lg-row gap-3 gap-lg-2">
{
showEngineCover()
}
{showEngineCover()}
<div>
<div className="d-flex gap-2 align-items-center flex-wrap">
<h1 className="fw-bold mb-2">{title}</h1>
@@ -578,7 +630,8 @@ export default function MediaPage() {
}}
/>
) : null}
{ canAccessStudio() && mediaFile?.media_type === MediaFileType.Video ? (
{canAccessStudio() &&
mediaFile?.media_type === MediaFileType.Video ? (
<Button
{...{
icon: faArrowRightArrowLeft,
@@ -813,7 +866,7 @@ export default function MediaPage() {
</div>
</Panel>
{ viewerCanEdit && (
{viewerCanEdit && (
<>
<div className="d-flex gap-2">
<Button
@@ -832,6 +885,23 @@ export default function MediaPage() {
</div>
</>
)}
{viewerCanMakeFeatured && (
<>
<div className="d-flex gap-2">
<Button
full={true}
variant="secondary"
icon={faStarShooting}
label={
mediaFile?.is_featured
? "Remove Featured"
: "Set Featured"
}
onClick={handleFeatureMedia}
/>
</div>
</>
)}
</div>
</div>
</div>
@@ -2,11 +2,18 @@ import React, { useState } from "react";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { v4 as uuidv4 } from "uuid";
import { useInferenceJobs, useSession } from "hooks";
import { Button, Container, Panel, TextArea, TempSelect as Select } from "components/common";
import {
Button,
Container,
Panel,
SegmentButtons,
TextArea,
TempSelect as Select,
} from "components/common";
import { EntityInput } from "components/entities";
import {
EnqueueVST,
EnqueueVSTResponse
EnqueueVSTResponse,
} from "@storyteller/components/src/api/video_styleTransfer/Enqueue_VST";
import "./StyleVideo.scss";
import { useParams } from "react-router-dom";
@@ -19,24 +26,28 @@ export default function StyleVideo() {
const [mediaToken, mediaTokenSet] = useState(pageMediaToken || "");
const [prompt, promptSet] = useState("");
const [negativePrompt, negativePromptSet] = useState("");
const [length, lengthSet] = useState(3000);
const { enqueue } = useInferenceJobs();
const onClick = () => {
if (mediaToken) {
EnqueueVST("",{
EnqueueVST("", {
creator_set_visibility: "private",
enable_lipsync: true,
input_file: mediaToken,
negative_prompt: negativePrompt,
prompt,
style,
trim_end_millis: 3000,
trim_end_millis: length,
trim_start_millis: 0,
uuid_idempotency_token: uuidv4()
})
.then((res: EnqueueVSTResponse) => {
uuid_idempotency_token: uuidv4(),
}).then((res: EnqueueVSTResponse) => {
if (res.success && res.inference_job_token) {
enqueue(res.inference_job_token,true,FrontendInferenceJobType.VideoStyleTransfer);
enqueue(
res.inference_job_token,
true,
FrontendInferenceJobType.VideoStyleTransfer
);
} else {
console.log("Failed to enqueue job", res);
}
@@ -44,55 +55,92 @@ export default function StyleVideo() {
}
};
const options = STYLE_OPTIONS.map((option) => {
const styleOptions = STYLE_OPTIONS.map(option => {
return {
label: option.label,
label: option.label,
value: option.value,
}
};
});
return studioAccessCheck(<Container {...{ className: "fy-style-video-page mt-5", type: "panel" }}>
const lengthOptions = [
{ label: 3, value: 3000 },
{ label: 5, value: 5000 },
{ label: 7, value: 7000 },
];
return studioAccessCheck(
<Container {...{ className: "fy-style-video-page mt-5", type: "panel" }}>
<Panel {...{ padding: true }}>
<header className="d-flex gap-3 flex-wrap">
<h1 className="fw-semibold">
Style a Video
</h1>
<h1 className="fw-semibold">Style a Video</h1>
</header>
<EntityInput {...{
accept: ["video"],
aspectRatio: "landscape",
label: "Choose a video",
name: "mediaToken",
value: pageMediaToken,
onChange: ({ target }: { target: any }) => { mediaTokenSet(target.value) },
type: "media"
}}/>
<Select {...{
label: "Style",
onChange: ({ target }: { target: any }) => { styleSet(target.value) },
options,
value: style
}}/>
<EntityInput
{...{
accept: ["video"],
aspectRatio: "landscape",
label: "Choose a video",
name: "mediaToken",
value: pageMediaToken,
onChange: ({ target }: { target: any }) => {
mediaTokenSet(target.value);
},
type: "media",
}}
/>
<Select
{...{
label: "Style",
onChange: ({ target }: { target: any }) => {
styleSet(target.value);
},
options: styleOptions,
value: style,
}}
/>
<div {...{ className: "prompt-row" }}>
<TextArea {...{
label: "Positive prompt",
rows: 5,
onChange: ({ target }: { target: any }) => { promptSet(target.value) },
}}/>
<TextArea {...{
label: "Negative prompt",
rows: 5,
onChange: ({ target }: { target: any }) => { negativePromptSet(target.value) },
}}/>
<TextArea
{...{
label: "Positive prompt",
rows: 5,
onChange: ({ target }: { target: any }) => {
promptSet(target.value);
},
}}
/>
<TextArea
{...{
label: "Negative prompt",
rows: 5,
onChange: ({ target }: { target: any }) => {
negativePromptSet(target.value);
},
}}
/>
</div>
<div {...{ className: "prompt-row" }}>
<SegmentButtons
{...{
className: "fy-style-video-length",
label: "Final video length (seconds)",
onChange: ({ target }: { target: any }) => {
lengthSet(target.value);
},
options: lengthOptions,
value: length,
}}
/>
</div>
<div {...{ className: "d-flex justify-content-center mt-3" }}>
<Button {...{
disabled: !mediaToken,
label: "Style",
onClick,
variant: "primary"
}} />
<Button
{...{
disabled: !mediaToken,
label: "Style",
onClick,
variant: "primary",
}}
/>
</div>
</Panel>
</Container>);
};
</Container>
);
}
File renamed without changes.
-21
View File
@@ -1,21 +0,0 @@
#!/bin/bash
set -euxo pipefail
MASTER_BRANCH='master'
PRODUCTION_BRANCH='production-storyteller'
git stash
git checkout $MASTER_BRANCH
git pull
if git show-ref --verify --quiet "refs/heads/$PRODUCTION_BRANCH"; then
echo "Deleting branch ${PRODUCTION_BRANCH}"
git branch -D $PRODUCTION_BRANCH
fi
git checkout -b $PRODUCTION_BRANCH
git push --set-upstream origin $PRODUCTION_BRANCH
git checkout $MASTER_BRANCH
-21
View File
@@ -1,21 +0,0 @@
#!/bin/bash
set -euxo pipefail
MASTER_BRANCH='master'
PRODUCTION_BRANCH='production-trumped'
git stash
git checkout $MASTER_BRANCH
git pull
if git show-ref --verify --quiet "refs/heads/$PRODUCTION_BRANCH"; then
echo "Deleting branch ${PRODUCTION_BRANCH}"
git branch -D $PRODUCTION_BRANCH
fi
git checkout -b $PRODUCTION_BRANCH
git push --set-upstream origin $PRODUCTION_BRANCH
git checkout $MASTER_BRANCH