Merge branch 'feature/mvp' of https://github.com/storytold/storyteller-frontend into feature/mvp

This commit is contained in:
Victor committed 2024-02-19 15:29:01 -08:00
commit f82d0bf78d
92 files changed
+3007 -203

No files matched your search

+5
View File
@@ -501,6 +501,11 @@ class ApiConfig {
return `${this.getApiOrigin()}/v1/workflow/comfy/create`;
}
// =============== Video Styletransfer ===============
enqueueVideoStyleTransfer(): string {
return `${this.getApiOrigin()}/v1/workflow/comfy/create`;
}
// =============== Convert FBX to glTF ===============
enqueueFbxToGltf(): string {
return `${this.getApiOrigin()}/v1/conversion/enqueue_fbx_to_gltf`;
@@ -1,64 +1,73 @@
import { ApiConfig } from "@storyteller/components";
import { UserDetailsLight } from "../_common/UserDetailsLight";
export interface ListCommentsSuccessResponse {
success: boolean,
comments: Array<Comment>
success: boolean;
comments: Array<Comment>;
}
export interface Comment {
token: string,
token: string;
user: UserDetailsLight;
user_token: string;
username: string;
user_display_name: string;
user_gravatar_hash: string;
user_token: string,
username: string,
user_display_name: string,
user_gravatar_hash: string,
comment_markdown: string;
comment_rendered_html: string;
comment_markdown: string,
comment_rendered_html: string,
created_at: Date,
updated_at: Date,
maybe_edited_at?: Date,
created_at: Date;
updated_at: Date;
maybe_edited_at?: Date;
}
export interface ListCommentsErrorResponse {
success: boolean,
success: boolean;
}
type ListCommentsResponse = ListCommentsSuccessResponse | ListCommentsErrorResponse;
type ListCommentsResponse =
| ListCommentsSuccessResponse
| ListCommentsErrorResponse;
export function ListCommentsIsOk(response: ListCommentsResponse): response is ListCommentsSuccessResponse {
export function ListCommentsIsOk(
response: ListCommentsResponse
): response is ListCommentsSuccessResponse {
return response?.success === true;
}
export function ListCommentsIsError(response: ListCommentsResponse): response is ListCommentsErrorResponse {
export function ListCommentsIsError(
response: ListCommentsResponse
): response is ListCommentsErrorResponse {
return response?.success === false;
}
export async function ListComments(entityType: string, entityToken: string) : Promise<ListCommentsResponse>
{
export async function ListComments(
entityType: string,
entityToken: string
): Promise<ListCommentsResponse> {
const endpoint = new ApiConfig().commentList(entityType, entityToken);
return await fetch(endpoint, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include',
})
.then(res => res.json())
.then(res => {
if (!res) {
return { success : false }; // TODO: This loses error semantics and is deprecated
}
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
return await fetch(endpoint, {
method: "GET",
headers: {
Accept: "application/json",
},
credentials: "include",
})
.catch(e => {
return { success : false };
});
.then(res => res.json())
.then(res => {
if (!res) {
return { success: false }; // TODO: This loses error semantics and is deprecated
}
if (res && "success" in res) {
return res;
} else {
return { success: false };
}
})
.catch(e => {
return { success: false };
});
}
@@ -8,6 +8,7 @@ export interface MediaFile {
token: string;
media_type: MediaFileType;
public_bucket_path: string;
maybe_batch_token: string;
maybe_creator_user: UserDetailsLight | null;
creator_set_visibility: string;
created_at: Date;
@@ -25,11 +26,17 @@ export interface MediaFile {
export interface GetMediaRequest {}
export interface GetMediaResponse {
success: boolean,
media_file?: MediaFile
success: boolean;
media_file?: MediaFile;
}
export const GetMedia = MakeRequest<string, GetMediaRequest, GetMediaResponse,{}>({
export const GetMedia = MakeRequest<
string,
GetMediaRequest,
GetMediaResponse,
{}
>({
method: "GET",
routingFunction: (mediaFileToken: string) => `/v1/media_files/file/${mediaFileToken}`,
});
routingFunction: (mediaFileToken: string) =>
`/v1/media_files/file/${mediaFileToken}`,
});
@@ -0,0 +1,22 @@
import MakeRequest from "../MakeRequest";
import { MediaFile } from "./GetMedia";
export interface GetMediaBatchImagesRequest {}
export interface GetMediaBatchImagesResponse {
success: boolean;
results: MediaFile[];
}
export interface GetMediaBatchImagesParams {}
export const GetMediaBatchImages = MakeRequest<
string,
GetMediaBatchImagesRequest,
GetMediaBatchImagesResponse,
GetMediaBatchImagesParams
>({
method: "GET",
routingFunction: (batchToken: string) =>
`/v1/media_files/batch/${batchToken}`,
});
@@ -1,21 +1,26 @@
import MakeRequest from "../MakeRequest";
import { MediaFile } from "./GetMedia";
import { MediaFileType } from "../_common/enums/MediaFileType";
import { Pagination } from "../_common/SharedFetchTypes";
export interface GetMediaRequest {}
export interface GetMediaListResponse {
pagination: Pagination,
success: boolean,
results: MediaFile[]
pagination: Pagination;
success: boolean;
results: MediaFile[];
}
export interface GetMediaParams {
page_index: number,
page_index: number;
}
export const GetMediaByUser = MakeRequest<string, GetMediaRequest, GetMediaListResponse, GetMediaParams>({
export const GetMediaByUser = MakeRequest<
string,
GetMediaRequest,
GetMediaListResponse,
GetMediaParams
>({
method: "GET",
routingFunction: (userToken: string) => `/v1/media_files/list/user/${userToken}`,
});
routingFunction: (userToken: string) =>
`/v1/media_files/list/user/${userToken}`,
});
@@ -22,6 +22,7 @@ export interface MediaFile {
maybe_cover_image_public_bucket_path: string;
};
maybe_text_transcript?: string;
maybe_batch_token?: string;
}
export enum MediaFileLookupError {
@@ -20,6 +20,7 @@ export interface ByQueueStats {
pending_svc_jobs: number,
pending_tacotron2_jobs: number,
pending_voice_designer: number,
pending_stable_diffusion: number,
}
export interface LegacyTtsInfo {
@@ -0,0 +1,68 @@
import { ApiConfig } from "@storyteller/components";
export interface EnqueueVideoStyleTransferRequest {
"uuid_idempotency_token": string,
"maybe_sd_model": string,
"maybe_workflow_config": string,
"maybe_input_file": string,
"maybe_output_path": string,
"maybe_json_modifications": {
"$.510.inputs.Text": string,
"$.8.inputs.text": string,
"$.173.inputs.seed": string,
"$.401.inputs.Value": number,
"$.918.inputs.Value": number,
"$.137.inputs.Value": number,
"$.186.inputs.Value": number,
"$.140.inputs.Value":number,
"$.154.inputs.Value": number,
"$.445.inputs.number": number,
"$.947.inputs.Value": number,
"$.800.inputs.Value": number,
"$.797.inputs.Value": number,
"$.796.inputs.Value": number,
"$.772.inputs.Value": number,
"$.771.inputs.Value": number,
"$.527.inputs.Value": number,
"$.403.inputs.Value": number,
}
}
export interface EnqueueVideoStyleTransferResponse {
success: boolean,
inference_job_token?: string,
}
export async function EnqueueVideoStyleTransfer(request: EnqueueVideoStyleTransferRequest) : Promise<EnqueueVideoStyleTransferResponse>
{
// const endpoint = "/v1/workflow/comfy/create";
const endpoint = new ApiConfig().enqueueVideoStyleTransfer();
return await fetch(endpoint, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (!res) {
return { success : false };
}
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -0,0 +1,7 @@
export type {
EnqueueVideoStyleTransferRequest, EnqueueVideoStyleTransferResponse,
} from './EnqueueVideoStyleTransfer';
export {
EnqueueVideoStyleTransfer as default
} from './EnqueueVideoStyleTransfer';
@@ -13,6 +13,7 @@ export enum FrontendInferenceJobType {
ConvertFbxtoGltf,
EngineComposition,
VideoWorkflow,
VideoStyleTransfer,
}
// NB: Many of these fields are optional despite the response payload containing them
+1
View File
@@ -75,6 +75,7 @@
"react-twitch-embed": "^3.0.1",
"react-wavesurfer.js": "^0.0.8",
"sass": "^1.57.1",
"swiper": "^11.0.5",
"typescript": "^3.7.5",
"universal-cookie": "^6.1.1",
"uuid": "^8.3.2",
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@@ -19,6 +19,7 @@
"speechVc": "ভয়েস টু ভয়েস",
"videoFaceAnimator": "ফেস অ্যানিমেটর",
"videoMotionCapture": "ভিডিও মোশন ক্যাপচার",
"videoStyleTransfer": "ভিডিও শৈলী স্থানান্তর",
"videoTitle": "ভিডিও জেনারেশন",
"videoW2l": "ভিডিও লিপ সিঙ্ক",
"videoWorkflow": "ভিডিও ওয়ার্কফ্লো"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "বাতিল করুন",
"enqueue": "স্টাইল ট্রান্সফার প্রয়োগ করুন",
"generateNew": "অন্য ভিডিও স্থানান্তর করুন",
"proceed": "স্টাইল ট্রান্সফারে এগিয়ে যান",
"upload": "আপলোড করুন"
},
"headings": {
"subtitle": "আপনার ভিডিওর স্টাইল একটি থেকে অন্যটিতে পরিবর্তন করুন",
"title": "ভিডিও শৈলী স্থানান্তর"
},
"image": {
"label": {
"preview": "স্টাইল ট্রান্সফার প্রয়োগ করার পরে একটি ফ্রেমের পূর্বরূপ"
}
},
"input": {
"label": {
"negPrompt": "নেতিবাচক প্রম্পট",
"prompt": "একটি প্রম্পট লিখুন",
"selectStyle": "একটি শৈলী নির্বাচন করুন",
"styleStrength": "শৈলী শক্তি"
},
"placeholder": {
"negPrompt": "এখানে প্রম্পট টাইপ করুন। যেমন: মানুষ ভাল্লুক নয়",
"prompt": "এখানে প্রম্পট টাইপ করুন। যেমন: ম্যান অ্যানিমে স্টাইলে নাচছে"
},
"thumbtip": {
"styleStrength": "শক্তি"
}
},
"message": {
"fileUploading": "ফাইল আপলোড হচ্ছে..."
},
"tabTitle": {
"library": "আপলোড করা থেকে বেছে নিন",
"upload": "একটি ভিডিও আপলোড করুন"
},
"video": {
"instruction": "একটি ভিডিও বাছাই করুন, তারপর একটি শৈলীর একটি মডেল চয়ন করুন এবং তারপর StoryTeller.ai কে এটি ঘটতে দিন৷",
"label": {
"original": "অরিজিনাল ভিডিও"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "বাতিল করুন",
"enqueue": "স্টাইল ট্রান্সফার প্রয়োগ করুন",
"generateNew": "অন্য ভিডিও স্থানান্তর করুন",
"proceed": "স্টাইল ট্রান্সফারে এগিয়ে যান",
"upload": "আপলোড করুন"
},
"headings": {
"subtitle": "আপনার ভিডিওর স্টাইল একটি থেকে অন্যটিতে পরিবর্তন করুন",
"title": "ভিডিও শৈলী স্থানান্তর"
},
"image": {
"label": {
"preview": "স্টাইল ট্রান্সফার প্রয়োগ করার পরে একটি ফ্রেমের পূর্বরূপ"
}
},
"input": {
"label": {
"negPrompt": "নেতিবাচক প্রম্পট",
"prompt": "একটি প্রম্পট লিখুন",
"selectStyle": "একটি শৈলী নির্বাচন করুন",
"styleStrength": "শৈলী শক্তি"
},
"placeholder": {
"negPrompt": "এখানে প্রম্পট টাইপ করুন। যেমন: মানুষ ভাল্লুক নয়",
"prompt": "এখানে প্রম্পট টাইপ করুন। যেমন: ম্যান অ্যানিমে স্টাইলে নাচছে"
},
"thumbtip": {
"styleStrength": "শক্তি"
}
},
"message": {
"fileUploading": "ফাইল আপলোড হচ্ছে..."
},
"tabTitle": {
"library": "আপলোড করা থেকে বেছে নিন",
"upload": "একটি ভিডিও আপলোড করুন"
},
"video": {
"instruction": "একটি ভিডিও বাছাই করুন, তারপর একটি শৈলীর একটি মডেল চয়ন করুন এবং তারপর StoryTeller.ai কে এটি ঘটতে দিন৷",
"label": {
"original": "অরিজিনাল ভিডিও"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Stimme zu Stimme",
"videoFaceAnimator": "Gesichtsanimator",
"videoMotionCapture": "Video-Bewegungserfassung",
"videoStyleTransfer": "Übertragung des Videostils",
"videoTitle": "Videogenerierung",
"videoW2l": "Video-Lippensynchronisation",
"videoWorkflow": "Video-Workflow"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Stornieren",
"enqueue": "Stilübertragung anwenden",
"generateNew": "Übertragen Sie ein anderes Video",
"proceed": "Fahren Sie mit der Stilübertragung fort",
"upload": "Hochladen"
},
"headings": {
"subtitle": "Ändern Sie den Stil Ihres Videos von einem zum anderen",
"title": "Übertragung des Videostils"
},
"image": {
"label": {
"preview": "Vorschau eines Rahmens nach der Anwendung von Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Negative Aufforderung",
"prompt": "Geben Sie eine Eingabeaufforderung ein",
"selectStyle": "Wählen Sie einen Stil aus",
"styleStrength": "Stilstärke"
},
"placeholder": {
"negPrompt": "Geben Sie hier prompt ein. ZB: Der Mensch ist kein Bär",
"prompt": "Geben Sie hier prompt ein. Beispiel: Mann tanzt im Anime-Stil"
},
"thumbtip": {
"styleStrength": "Stärke"
}
},
"message": {
"fileUploading": "Datei wird hochgeladen..."
},
"tabTitle": {
"library": "Wählen Sie aus den hochgeladenen",
"upload": "Ein Video hochladen"
},
"video": {
"instruction": "Wählen Sie ein Video, dann ein Modell eines Stils aus und lassen Sie es dann von StoryTeller.ai Wirklichkeit werden",
"label": {
"original": "Originalvideo"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Stornieren",
"enqueue": "Stilübertragung anwenden",
"generateNew": "Übertragen Sie ein anderes Video",
"proceed": "Fahren Sie mit der Stilübertragung fort",
"upload": "Hochladen"
},
"headings": {
"subtitle": "Ändern Sie den Stil Ihres Videos von einem zum anderen",
"title": "Übertragung des Videostils"
},
"image": {
"label": {
"preview": "Vorschau eines Rahmens nach der Anwendung von Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Negative Aufforderung",
"prompt": "Geben Sie eine Eingabeaufforderung ein",
"selectStyle": "Wählen Sie einen Stil aus",
"styleStrength": "Stilstärke"
},
"placeholder": {
"negPrompt": "Geben Sie hier prompt ein. ZB: Der Mensch ist kein Bär",
"prompt": "Geben Sie hier prompt ein. Beispiel: Mann tanzt im Anime-Stil"
},
"thumbtip": {
"styleStrength": "Stärke"
}
},
"message": {
"fileUploading": "Datei wird hochgeladen..."
},
"tabTitle": {
"library": "Wählen Sie aus den hochgeladenen",
"upload": "Ein Video hochladen"
},
"video": {
"instruction": "Wählen Sie ein Video, dann ein Modell eines Stils aus und lassen Sie es dann von StoryTeller.ai Wirklichkeit werden",
"label": {
"original": "Originalvideo"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Voice to Voice",
"videoFaceAnimator": "Face Animator",
"videoMotionCapture": "Video Motion Capture",
"videoStyleTransfer": "Video Style Transfer",
"videoTitle": "Video Generation",
"videoW2l": "Video Lip Sync",
"videoWorkflow": "Video Workflow"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Cancel",
"enqueue": "Apply Style Transfer",
"generateNew": "Transfer another video",
"proceed": "Proceed to Style Transfer",
"upload": "Upload"
},
"headings": {
"subtitle": "Change the style of your video from one to another",
"title": "Video Style Transfer"
},
"image": {
"label": {
"preview": "Preview of a frame after applying Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Negative Prompt",
"prompt": "Enter a prompt",
"selectStyle": "Select a style",
"styleStrength": "Style Strength"
},
"placeholder": {
"negPrompt": "Type prompt here. Eg: Man is not a bear",
"prompt": "Type prompt here. Eg: Man dancing in the style of anime"
},
"thumbtip": {
"styleStrength": "Strength"
}
},
"message": {
"fileUploading": "Uploading File..."
},
"tabTitle": {
"library": "Choose from uploaded",
"upload": "Upload a video"
},
"video": {
"instruction": "Pick a video, then pick a model of a style, and then let StoryTeller.ai make it happen",
"label": {
"original": "Original Video"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Cancel",
"enqueue": "Apply Style Transfer",
"generateNew": "Transfer another video",
"proceed": "Proceed to Style Transfer",
"upload": "Upload"
},
"headings": {
"subtitle": "Change the style of your video from one to another",
"title": "Video Style Transfer"
},
"image": {
"label": {
"preview": "Preview of a frame after applying Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Negative Prompt",
"prompt": "Enter a prompt",
"selectStyle": "Select a style",
"styleStrength": "Style Strength"
},
"placeholder": {
"negPrompt": "Type prompt here. Eg: Man is not a bear",
"prompt": "Type prompt here. Eg: Man dancing in the style of anime"
},
"thumbtip": {
"styleStrength": "Strength"
}
},
"message": {
"fileUploading": "Uploading File..."
},
"tabTitle": {
"library": "Choose from uploaded",
"upload": "Upload a video"
},
"video": {
"instruction": "Pick a video, then pick a model of a style, and then let StoryTeller.ai make it happen",
"label": {
"original": "Original Video"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Voz a Voz",
"videoFaceAnimator": "Animador de rostros",
"videoMotionCapture": "Captura de movimiento de vídeo",
"videoStyleTransfer": "Transferencia de estilo de vídeo",
"videoTitle": "Generación de vídeo",
"videoW2l": "Sincronización de labios de vídeo",
"videoWorkflow": "Flujo de trabajo de vídeo"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Cancelar",
"enqueue": "Aplicar transferencia de estilo",
"generateNew": "Transferir otro video",
"proceed": "Continuar con la transferencia de estilo",
"upload": "Subir"
},
"headings": {
"subtitle": "Cambia el estilo de tu vídeo de uno a otro",
"title": "Transferencia de estilo de vídeo"
},
"image": {
"label": {
"preview": "Vista previa de un marco después de aplicar Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Aviso negativo",
"prompt": "Ingrese un mensaje",
"selectStyle": "Selecciona un estilo",
"styleStrength": "Fuerza del estilo"
},
"placeholder": {
"negPrompt": "Escriba el mensaje aquí. Por ejemplo: el hombre no es un oso",
"prompt": "Escriba el mensaje aquí. Por ejemplo: hombre bailando al estilo anime."
},
"thumbtip": {
"styleStrength": "Fortaleza"
}
},
"message": {
"fileUploading": "Subiendo archivo..."
},
"tabTitle": {
"library": "Elige entre cargado",
"upload": "Subir un video"
},
"video": {
"instruction": "Elija un video, luego elija un modelo de estilo y luego deje que StoryTeller.ai lo haga realidad.",
"label": {
"original": "Vídeo original"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Cancelar",
"enqueue": "Aplicar transferencia de estilo",
"generateNew": "Transferir otro video",
"proceed": "Continuar con la transferencia de estilo",
"upload": "Subir"
},
"headings": {
"subtitle": "Cambia el estilo de tu vídeo de uno a otro",
"title": "Transferencia de estilo de vídeo"
},
"image": {
"label": {
"preview": "Vista previa de un marco después de aplicar Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Aviso negativo",
"prompt": "Ingrese un mensaje",
"selectStyle": "Selecciona un estilo",
"styleStrength": "Fuerza del estilo"
},
"placeholder": {
"negPrompt": "Escriba el mensaje aquí. Por ejemplo: el hombre no es un oso",
"prompt": "Escriba el mensaje aquí. Por ejemplo: hombre bailando al estilo anime."
},
"thumbtip": {
"styleStrength": "Fortaleza"
}
},
"message": {
"fileUploading": "Subiendo archivo..."
},
"tabTitle": {
"library": "Elige entre cargado",
"upload": "Subir un video"
},
"video": {
"instruction": "Elija un video, luego elija un modelo de estilo y luego deje que StoryTeller.ai lo haga realidad.",
"label": {
"original": "Vídeo original"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Voix à voix",
"videoFaceAnimator": "Animateur de visage",
"videoMotionCapture": "Capture de mouvement vidéo",
"videoStyleTransfer": "Transfert de style vidéo",
"videoTitle": "Génération vidéo",
"videoW2l": "Synchronisation labiale vidéo",
"videoWorkflow": "Flux de travail vidéo"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Annuler",
"enqueue": "Appliquer le transfert de style",
"generateNew": "Transférer une autre vidéo",
"proceed": "Passer au transfert de style",
"upload": "Télécharger"
},
"headings": {
"subtitle": "Changez le style de votre vidéo de l'un à l'autre",
"title": "Transfert de style vidéo"
},
"image": {
"label": {
"preview": "Aperçu d'un cadre après application de Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Invite négative",
"prompt": "Entrez une invite",
"selectStyle": "Sélectionnez un style",
"styleStrength": "Force du style"
},
"placeholder": {
"negPrompt": "Tapez l'invite ici. Ex : L'homme n'est pas un ours",
"prompt": "Tapez l'invite ici. Par exemple : un homme qui danse à la manière d'un anime"
},
"thumbtip": {
"styleStrength": "Force"
}
},
"message": {
"fileUploading": "Téléchargement du fichier..."
},
"tabTitle": {
"library": "Choisissez parmi les fichiers téléchargés",
"upload": "Télécharger une vidéo"
},
"video": {
"instruction": "Choisissez une vidéo, puis choisissez un modèle d'un style, puis laissez StoryTeller.ai le réaliser",
"label": {
"original": "Vidéo originale"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Annuler",
"enqueue": "Appliquer le transfert de style",
"generateNew": "Transférer une autre vidéo",
"proceed": "Passer au transfert de style",
"upload": "Télécharger"
},
"headings": {
"subtitle": "Changez le style de votre vidéo de l'un à l'autre",
"title": "Transfert de style vidéo"
},
"image": {
"label": {
"preview": "Aperçu d'un cadre après application de Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Invite négative",
"prompt": "Entrez une invite",
"selectStyle": "Sélectionnez un style",
"styleStrength": "Force du style"
},
"placeholder": {
"negPrompt": "Tapez l'invite ici. Ex : L'homme n'est pas un ours",
"prompt": "Tapez l'invite ici. Par exemple : un homme qui danse à la manière d'un anime"
},
"thumbtip": {
"styleStrength": "Force"
}
},
"message": {
"fileUploading": "Téléchargement du fichier..."
},
"tabTitle": {
"library": "Choisissez parmi les fichiers téléchargés",
"upload": "Télécharger une vidéo"
},
"video": {
"instruction": "Choisissez une vidéo, puis choisissez un modèle d'un style, puis laissez StoryTeller.ai le réaliser",
"label": {
"original": "Vidéo originale"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "आवाज से आवाज",
"videoFaceAnimator": "फेस एनिमेटर",
"videoMotionCapture": "वीडियो मोशन कैप्चर",
"videoStyleTransfer": "वीडियो शैली स्थानांतरण",
"videoTitle": "वीडियो जनरेशन",
"videoW2l": "वीडियो लिप सिंक",
"videoWorkflow": "वीडियो वर्कफ़्लो"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "रद्द करना",
"enqueue": "स्टाइल ट्रांसफर लागू करें",
"generateNew": "दूसरा वीडियो ट्रांसफर करें",
"proceed": "स्टाइल ट्रांसफर के लिए आगे बढ़ें",
"upload": "डालना"
},
"headings": {
"subtitle": "अपने वीडियो की शैली को एक से दूसरे में बदलें",
"title": "वीडियो शैली स्थानांतरण"
},
"image": {
"label": {
"preview": "स्टाइल ट्रांसफर लागू करने के बाद फ्रेम का पूर्वावलोकन"
}
},
"input": {
"label": {
"negPrompt": "नकारात्मक संकेत",
"prompt": "एक संकेत दर्ज करें",
"selectStyle": "एक शैली चुनें",
"styleStrength": "शैली की ताकत"
},
"placeholder": {
"negPrompt": "यहां प्रॉम्प्ट टाइप करें। उदाहरणार्थ: मनुष्य भालू नहीं है",
"prompt": "यहां प्रॉम्प्ट टाइप करें। जैसे: आदमी एनीमे की शैली में नृत्य कर रहा है"
},
"thumbtip": {
"styleStrength": "ताकत"
}
},
"message": {
"fileUploading": "फ़ाइल अपलोड हो रही है..."
},
"tabTitle": {
"library": "अपलोड किए गए में से चुनें",
"upload": "एक वीडियो अपलोड करें"
},
"video": {
"instruction": "एक वीडियो चुनें, फिर एक शैली का मॉडल चुनें, और फिर StoryTeller.ai को ऐसा करने दें",
"label": {
"original": "मूल वीडियो"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "रद्द करना",
"enqueue": "स्टाइल ट्रांसफर लागू करें",
"generateNew": "दूसरा वीडियो ट्रांसफर करें",
"proceed": "स्टाइल ट्रांसफर के लिए आगे बढ़ें",
"upload": "डालना"
},
"headings": {
"subtitle": "अपने वीडियो की शैली को एक से दूसरे में बदलें",
"title": "वीडियो शैली स्थानांतरण"
},
"image": {
"label": {
"preview": "स्टाइल ट्रांसफर लागू करने के बाद फ्रेम का पूर्वावलोकन"
}
},
"input": {
"label": {
"negPrompt": "नकारात्मक संकेत",
"prompt": "एक संकेत दर्ज करें",
"selectStyle": "एक शैली चुनें",
"styleStrength": "शैली की ताकत"
},
"placeholder": {
"negPrompt": "यहां प्रॉम्प्ट टाइप करें। उदाहरणार्थ: मनुष्य भालू नहीं है",
"prompt": "यहां प्रॉम्प्ट टाइप करें। जैसे: आदमी एनीमे की शैली में नृत्य कर रहा है"
},
"thumbtip": {
"styleStrength": "ताकत"
}
},
"message": {
"fileUploading": "फ़ाइल अपलोड हो रही है..."
},
"tabTitle": {
"library": "अपलोड किए गए में से चुनें",
"upload": "एक वीडियो अपलोड करें"
},
"video": {
"instruction": "एक वीडियो चुनें, फिर एक शैली का मॉडल चुनें, और फिर StoryTeller.ai को ऐसा करने दें",
"label": {
"original": "मूल वीडियो"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Suara ke Suara",
"videoFaceAnimator": "Animator Wajah",
"videoMotionCapture": "Pengambilan Gerakan Video",
"videoStyleTransfer": "Transfer Gaya Video",
"videoTitle": "Pembuatan Video",
"videoW2l": "Sinkronisasi Bibir Video",
"videoWorkflow": "Alur Kerja Video"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Membatalkan",
"enqueue": "Terapkan Transfer Gaya",
"generateNew": "Transfer video lain",
"proceed": "Lanjutkan ke Transfer Gaya",
"upload": "Mengunggah"
},
"headings": {
"subtitle": "Ubah gaya video Anda dari satu ke yang lain",
"title": "Transfer Gaya Video"
},
"image": {
"label": {
"preview": "Pratinjau bingkai setelah menerapkan Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Perintah Negatif",
"prompt": "Masukkan perintah",
"selectStyle": "Pilih gaya",
"styleStrength": "Kekuatan Gaya"
},
"placeholder": {
"negPrompt": "Ketik perintah di sini. Misalnya: Manusia bukan beruang",
"prompt": "Ketik perintah di sini. Misalnya: Pria menari dengan gaya anime"
},
"thumbtip": {
"styleStrength": "Kekuatan"
}
},
"message": {
"fileUploading": "Mengunggah Berkas..."
},
"tabTitle": {
"library": "Pilih dari yang diunggah",
"upload": "Unggah video"
},
"video": {
"instruction": "Pilih video, lalu pilih model gayanya, lalu biarkan StoryTeller.ai mewujudkannya",
"label": {
"original": "Video Asli"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Membatalkan",
"enqueue": "Terapkan Transfer Gaya",
"generateNew": "Transfer video lain",
"proceed": "Lanjutkan ke Transfer Gaya",
"upload": "Mengunggah"
},
"headings": {
"subtitle": "Ubah gaya video Anda dari satu ke yang lain",
"title": "Transfer Gaya Video"
},
"image": {
"label": {
"preview": "Pratinjau bingkai setelah menerapkan Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Perintah Negatif",
"prompt": "Masukkan perintah",
"selectStyle": "Pilih gaya",
"styleStrength": "Kekuatan Gaya"
},
"placeholder": {
"negPrompt": "Ketik perintah di sini. Misalnya: Manusia bukan beruang",
"prompt": "Ketik perintah di sini. Misalnya: Pria menari dengan gaya anime"
},
"thumbtip": {
"styleStrength": "Kekuatan"
}
},
"message": {
"fileUploading": "Mengunggah Berkas..."
},
"tabTitle": {
"library": "Pilih dari yang diunggah",
"upload": "Unggah video"
},
"video": {
"instruction": "Pilih video, lalu pilih model gayanya, lalu biarkan StoryTeller.ai mewujudkannya",
"label": {
"original": "Video Asli"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Da voce a voce",
"videoFaceAnimator": "Animatore di volti",
"videoMotionCapture": "Acquisizione del movimento video",
"videoStyleTransfer": "Trasferimento di stili video",
"videoTitle": "Generazione video",
"videoW2l": "Sincronizzazione labiale video",
"videoWorkflow": "Flusso di lavoro video"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Annulla",
"enqueue": "Applica il trasferimento di stile",
"generateNew": "Trasferisci un altro video",
"proceed": "Procedi al trasferimento dello stile",
"upload": "Caricamento"
},
"headings": {
"subtitle": "Cambia lo stile del tuo video da uno all'altro",
"title": "Trasferimento di stili video"
},
"image": {
"label": {
"preview": "Anteprima di un fotogramma dopo l'applicazione del Trasferimento di stile"
}
},
"input": {
"label": {
"negPrompt": "Richiesta negativa",
"prompt": "Inserisci un messaggio",
"selectStyle": "Seleziona uno stile",
"styleStrength": "Forza dello stile"
},
"placeholder": {
"negPrompt": "Digita la richiesta qui. Ad esempio: l'uomo non è un orso",
"prompt": "Digita la richiesta qui. Ad esempio: un uomo che balla in stile anime"
},
"thumbtip": {
"styleStrength": "Forza"
}
},
"message": {
"fileUploading": "Caricamento file..."
},
"tabTitle": {
"library": "Scegli tra caricati",
"upload": "Carica un video"
},
"video": {
"instruction": "Scegli un video, poi scegli un modello di stile e poi lascia che StoryTeller.ai lo realizzi",
"label": {
"original": "Video originale"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Annulla",
"enqueue": "Applica il trasferimento di stile",
"generateNew": "Trasferisci un altro video",
"proceed": "Procedi al trasferimento dello stile",
"upload": "Caricamento"
},
"headings": {
"subtitle": "Cambia lo stile del tuo video da uno all'altro",
"title": "Trasferimento di stili video"
},
"image": {
"label": {
"preview": "Anteprima di un fotogramma dopo l'applicazione del Trasferimento di stile"
}
},
"input": {
"label": {
"negPrompt": "Richiesta negativa",
"prompt": "Inserisci un messaggio",
"selectStyle": "Seleziona uno stile",
"styleStrength": "Forza dello stile"
},
"placeholder": {
"negPrompt": "Digita la richiesta qui. Ad esempio: l'uomo non è un orso",
"prompt": "Digita la richiesta qui. Ad esempio: un uomo che balla in stile anime"
},
"thumbtip": {
"styleStrength": "Forza"
}
},
"message": {
"fileUploading": "Caricamento file..."
},
"tabTitle": {
"library": "Scegli tra caricati",
"upload": "Carica un video"
},
"video": {
"instruction": "Scegli un video, poi scegli un modello di stile e poi lascia che StoryTeller.ai lo realizzi",
"label": {
"original": "Video originale"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "声から声へ",
"videoFaceAnimator": "フェイスアニメーター",
"videoMotionCapture": "ビデオモーションキャプチャ",
"videoStyleTransfer": "ビデオスタイルの転送",
"videoTitle": "ビデオ生成",
"videoW2l": "ビデオリップシンク",
"videoWorkflow": "ビデオワークフロー"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "キャンセル",
"enqueue": "スタイル転送の適用",
"generateNew": "別のビデオを転送する",
"proceed": "スタイル転送に進む",
"upload": "アップロード"
},
"headings": {
"subtitle": "ビデオのスタイルを別のスタイルに変更する",
"title": "ビデオスタイルの転送"
},
"image": {
"label": {
"preview": "スタイル転送適用後のフレームのプレビュー"
}
},
"input": {
"label": {
"negPrompt": "否定的なプロンプト",
"prompt": "プロンプトを入力してください",
"selectStyle": "スタイルを選択してください",
"styleStrength": "スタイルの強さ"
},
"placeholder": {
"negPrompt": "ここに「プロンプト」と入力します。例: 人間はクマではありません",
"prompt": "ここに「プロンプト」と入力します。例: アニメ風に踊る男性"
},
"thumbtip": {
"styleStrength": "強さ"
}
},
"message": {
"fileUploading": "ファイルをアップロードしています..."
},
"tabTitle": {
"library": "アップロードされたものから選択",
"upload": "動画をアップロードする"
},
"video": {
"instruction": "ビデオを選択し、スタイルのモデルを選択して、StoryTeller.ai にそれを実現させましょう",
"label": {
"original": "オリジナルビデオ"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "キャンセル",
"enqueue": "スタイル転送の適用",
"generateNew": "別のビデオを転送する",
"proceed": "スタイル転送に進む",
"upload": "アップロード"
},
"headings": {
"subtitle": "ビデオのスタイルを別のスタイルに変更する",
"title": "ビデオスタイルの転送"
},
"image": {
"label": {
"preview": "スタイル転送適用後のフレームのプレビュー"
}
},
"input": {
"label": {
"negPrompt": "否定的なプロンプト",
"prompt": "プロンプトを入力してください",
"selectStyle": "スタイルを選択してください",
"styleStrength": "スタイルの強さ"
},
"placeholder": {
"negPrompt": "ここに「プロンプト」と入力します。例: 人間はクマではありません",
"prompt": "ここに「プロンプト」と入力します。例: アニメ風に踊る男性"
},
"thumbtip": {
"styleStrength": "強さ"
}
},
"message": {
"fileUploading": "ファイルをアップロードしています..."
},
"tabTitle": {
"library": "アップロードされたものから選択",
"upload": "動画をアップロードする"
},
"video": {
"instruction": "ビデオを選択し、スタイルのモデルを選択して、StoryTeller.ai にそれを実現させましょう",
"label": {
"original": "オリジナルビデオ"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "음성 대 음성",
"videoFaceAnimator": "얼굴 애니메이터",
"videoMotionCapture": "비디오 모션 캡처",
"videoStyleTransfer": "비디오 스타일 전송",
"videoTitle": "비디오 생성",
"videoW2l": "비디오 립싱크",
"videoWorkflow": "비디오 워크플로우"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "취소",
"enqueue": "스타일 전송 적용",
"generateNew": "다른 비디오 전송",
"proceed": "스타일 이전으로 진행",
"upload": "업로드"
},
"headings": {
"subtitle": "비디오 스타일을 다른 스타일로 변경하세요.",
"title": "비디오 스타일 전송"
},
"image": {
"label": {
"preview": "스타일 전송 적용 후 프레임 미리보기"
}
},
"input": {
"label": {
"negPrompt": "부정적인 프롬프트",
"prompt": "프롬프트를 입력하세요",
"selectStyle": "스타일을 선택하세요",
"styleStrength": "스타일 강도"
},
"placeholder": {
"negPrompt": "여기에 프롬프트를 입력하세요. 예: 사람은 곰이 아니다",
"prompt": "여기에 프롬프트를 입력하세요. 예: 애니메이션 스타일로 춤추는 남자"
},
"thumbtip": {
"styleStrength": "힘"
}
},
"message": {
"fileUploading": "파일 업로드 중..."
},
"tabTitle": {
"library": "업로드된 것 중에서 선택하세요",
"upload": "동영상 업로드"
},
"video": {
"instruction": "비디오를 선택한 다음 스타일 모델을 선택한 다음 StoryTeller.ai를 통해 이를 구현해 보세요.",
"label": {
"original": "원본 영상"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "취소",
"enqueue": "스타일 전송 적용",
"generateNew": "다른 비디오 전송",
"proceed": "스타일 이전으로 진행",
"upload": "업로드"
},
"headings": {
"subtitle": "비디오 스타일을 다른 스타일로 변경하세요.",
"title": "비디오 스타일 전송"
},
"image": {
"label": {
"preview": "스타일 전송 적용 후 프레임 미리보기"
}
},
"input": {
"label": {
"negPrompt": "부정적인 프롬프트",
"prompt": "프롬프트를 입력하세요",
"selectStyle": "스타일을 선택하세요",
"styleStrength": "스타일 강도"
},
"placeholder": {
"negPrompt": "여기에 프롬프트를 입력하세요. 예: 사람은 곰이 아니다",
"prompt": "여기에 프롬프트를 입력하세요. 예: 애니메이션 스타일로 춤추는 남자"
},
"thumbtip": {
"styleStrength": "힘"
}
},
"message": {
"fileUploading": "파일 업로드 중..."
},
"tabTitle": {
"library": "업로드된 것 중에서 선택하세요",
"upload": "동영상 업로드"
},
"video": {
"instruction": "비디오를 선택한 다음 스타일 모델을 선택한 다음 StoryTeller.ai를 통해 이를 구현해 보세요.",
"label": {
"original": "원본 영상"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Voz para Voz",
"videoFaceAnimator": "Animador facial",
"videoMotionCapture": "Captura de movimento de vídeo",
"videoStyleTransfer": "Transferência de estilo de vídeo",
"videoTitle": "Geração de Vídeo",
"videoW2l": "Sincronização labial de vídeo",
"videoWorkflow": "Fluxo de trabalho de vídeo"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Cancelar",
"enqueue": "Aplicar transferência de estilo",
"generateNew": "Transferir outro vídeo",
"proceed": "Prossiga para a transferência de estilo",
"upload": "Carregar"
},
"headings": {
"subtitle": "Mude o estilo do seu vídeo de um para outro",
"title": "Transferência de estilo de vídeo"
},
"image": {
"label": {
"preview": "Visualização de um quadro após aplicar o Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Alerta negativo",
"prompt": "Insira um prompt",
"selectStyle": "Selecione um estilo",
"styleStrength": "Força do estilo"
},
"placeholder": {
"negPrompt": "Digite prompt aqui. Ex.: O homem não é um urso",
"prompt": "Digite prompt aqui. Ex.: Homem dançando no estilo anime"
},
"thumbtip": {
"styleStrength": "Força"
}
},
"message": {
"fileUploading": "Carregando arquivo..."
},
"tabTitle": {
"library": "Escolha entre os enviados",
"upload": "Carregar um vídeo"
},
"video": {
"instruction": "Escolha um vídeo, depois escolha um modelo de estilo e deixe StoryTeller.ai fazer acontecer",
"label": {
"original": "Vídeo Original"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Cancelar",
"enqueue": "Aplicar transferência de estilo",
"generateNew": "Transferir outro vídeo",
"proceed": "Prossiga para a transferência de estilo",
"upload": "Carregar"
},
"headings": {
"subtitle": "Mude o estilo do seu vídeo de um para outro",
"title": "Transferência de estilo de vídeo"
},
"image": {
"label": {
"preview": "Visualização de um quadro após aplicar o Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Alerta negativo",
"prompt": "Insira um prompt",
"selectStyle": "Selecione um estilo",
"styleStrength": "Força do estilo"
},
"placeholder": {
"negPrompt": "Digite prompt aqui. Ex.: O homem não é um urso",
"prompt": "Digite prompt aqui. Ex.: Homem dançando no estilo anime"
},
"thumbtip": {
"styleStrength": "Força"
}
},
"message": {
"fileUploading": "Carregando arquivo..."
},
"tabTitle": {
"library": "Escolha entre os enviados",
"upload": "Carregar um vídeo"
},
"video": {
"instruction": "Escolha um vídeo, depois escolha um modelo de estilo e deixe StoryTeller.ai fazer acontecer",
"label": {
"original": "Vídeo Original"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Голос в голос",
"videoFaceAnimator": "Лицевой Аниматор",
"videoMotionCapture": "Захват видео движения",
"videoStyleTransfer": "Передача стиля видео",
"videoTitle": "Создание видео",
"videoW2l": "Видео синхронизация губ",
"videoWorkflow": "Видео рабочий процесс"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Отмена",
"enqueue": "Применить перенос стиля",
"generateNew": "Перенести другое видео",
"proceed": "Перейдите к переносу стиля",
"upload": "Загрузить"
},
"headings": {
"subtitle": "Меняйте стиль вашего видео с одного на другой",
"title": "Передача стиля видео"
},
"image": {
"label": {
"preview": "Предварительный просмотр кадра после применения Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Отрицательная подсказка",
"prompt": "Введите подсказку",
"selectStyle": "Выберите стиль",
"styleStrength": "Сила стиля"
},
"placeholder": {
"negPrompt": "Введите подсказку здесь. Например: Человек не медведь",
"prompt": "Введите подсказку здесь. Например: Мужчина танцует в стиле аниме."
},
"thumbtip": {
"styleStrength": "Сила"
}
},
"message": {
"fileUploading": "Загрузка файла..."
},
"tabTitle": {
"library": "Выбрать из загруженных",
"upload": "Загрузить видео"
},
"video": {
"instruction": "Выберите видео, затем выберите модель стиля, а затем позвольте StoryTeller.ai воплотить это в жизнь.",
"label": {
"original": "Оригинальное видео"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Отмена",
"enqueue": "Применить перенос стиля",
"generateNew": "Перенести другое видео",
"proceed": "Перейдите к переносу стиля",
"upload": "Загрузить"
},
"headings": {
"subtitle": "Меняйте стиль вашего видео с одного на другой",
"title": "Передача стиля видео"
},
"image": {
"label": {
"preview": "Предварительный просмотр кадра после применения Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "Отрицательная подсказка",
"prompt": "Введите подсказку",
"selectStyle": "Выберите стиль",
"styleStrength": "Сила стиля"
},
"placeholder": {
"negPrompt": "Введите подсказку здесь. Например: Человек не медведь",
"prompt": "Введите подсказку здесь. Например: Мужчина танцует в стиле аниме."
},
"thumbtip": {
"styleStrength": "Сила"
}
},
"message": {
"fileUploading": "Загрузка файла..."
},
"tabTitle": {
"library": "Выбрать из загруженных",
"upload": "Загрузить видео"
},
"video": {
"instruction": "Выберите видео, затем выберите модель стиля, а затем позвольте StoryTeller.ai воплотить это в жизнь.",
"label": {
"original": "Оригинальное видео"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "เสียงเป็นเสียง",
"videoFaceAnimator": "แอนิเมชั่นใบหน้า",
"videoMotionCapture": "จับภาพวิดีโอเคลื่อนไหว",
"videoStyleTransfer": "การถ่ายโอนสไตล์วิดีโอ",
"videoTitle": "การสร้างวิดีโอ",
"videoW2l": "ลิปซิงค์วิดีโอ",
"videoWorkflow": "เวิร์กโฟลว์วิดีโอ"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "ยกเลิก",
"enqueue": "ใช้การถ่ายโอนสไตล์",
"generateNew": "ถ่ายโอนวิดีโออื่น",
"proceed": "ดำเนินการโอนสไตล์",
"upload": "ที่อัพโหลด"
},
"headings": {
"subtitle": "เปลี่ยนสไตล์วิดีโอของคุณจากที่หนึ่งไปอีกที่หนึ่ง",
"title": "การถ่ายโอนสไตล์วิดีโอ"
},
"image": {
"label": {
"preview": "ดูตัวอย่างเฟรมหลังจากใช้ Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "พรอมต์เชิงลบ",
"prompt": "ป้อนข้อความแจ้ง",
"selectStyle": "เลือกสไตล์",
"styleStrength": "ความแข็งแกร่งของสไตล์"
},
"placeholder": {
"negPrompt": "พิมพ์ prompt ที่นี่ เช่น มนุษย์ไม่ใช่หมี",
"prompt": "พิมพ์ prompt ที่นี่ เช่น ผู้ชายเต้นตามสไตล์อนิเมะ"
},
"thumbtip": {
"styleStrength": "ความแข็งแกร่ง"
}
},
"message": {
"fileUploading": "กำลังอัพโหลดไฟล์..."
},
"tabTitle": {
"library": "เลือกจากการอัพโหลด",
"upload": "อัปโหลดวิดีโอ"
},
"video": {
"instruction": "เลือกวิดีโอ จากนั้นเลือกโมเดลของสไตล์ จากนั้นปล่อยให้ StoryTeller.ai ทำให้มันเกิดขึ้น",
"label": {
"original": "วิดีโอต้นฉบับ"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "ยกเลิก",
"enqueue": "ใช้การถ่ายโอนสไตล์",
"generateNew": "ถ่ายโอนวิดีโออื่น",
"proceed": "ดำเนินการโอนสไตล์",
"upload": "ที่อัพโหลด"
},
"headings": {
"subtitle": "เปลี่ยนสไตล์วิดีโอของคุณจากที่หนึ่งไปอีกที่หนึ่ง",
"title": "การถ่ายโอนสไตล์วิดีโอ"
},
"image": {
"label": {
"preview": "ดูตัวอย่างเฟรมหลังจากใช้ Style Transfer"
}
},
"input": {
"label": {
"negPrompt": "พรอมต์เชิงลบ",
"prompt": "ป้อนข้อความแจ้ง",
"selectStyle": "เลือกสไตล์",
"styleStrength": "ความแข็งแกร่งของสไตล์"
},
"placeholder": {
"negPrompt": "พิมพ์ prompt ที่นี่ เช่น มนุษย์ไม่ใช่หมี",
"prompt": "พิมพ์ prompt ที่นี่ เช่น ผู้ชายเต้นตามสไตล์อนิเมะ"
},
"thumbtip": {
"styleStrength": "ความแข็งแกร่ง"
}
},
"message": {
"fileUploading": "กำลังอัพโหลดไฟล์..."
},
"tabTitle": {
"library": "เลือกจากการอัพโหลด",
"upload": "อัปโหลดวิดีโอ"
},
"video": {
"instruction": "เลือกวิดีโอ จากนั้นเลือกโมเดลของสไตล์ จากนั้นปล่อยให้ StoryTeller.ai ทำให้มันเกิดขึ้น",
"label": {
"original": "วิดีโอต้นฉบับ"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Sesten Sese",
"videoFaceAnimator": "Yüz Animatörü",
"videoMotionCapture": "Video Hareket Yakalama",
"videoStyleTransfer": "Video Stili Aktarımı",
"videoTitle": "Video Oluşturma",
"videoW2l": "Video Dudak Senkronizasyonu",
"videoWorkflow": "Video İş Akışı"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "İptal etmek",
"enqueue": "Stil Transferini Uygula",
"generateNew": "Başka bir videoyu aktar",
"proceed": "Stil Transferine Devam Edin",
"upload": "Yüklemek"
},
"headings": {
"subtitle": "Videonuzun stilini birinden diğerine değiştirin",
"title": "Video Stili Aktarımı"
},
"image": {
"label": {
"preview": "Stil Aktarımı uygulandıktan sonra bir çerçevenin önizlemesi"
}
},
"input": {
"label": {
"negPrompt": "Olumsuz İstem",
"prompt": "Bir istem girin",
"selectStyle": "Bir stil seçin",
"styleStrength": "Stil Gücü"
},
"placeholder": {
"negPrompt": "İstemi buraya yazın. Örnek: İnsan ayı değildir",
"prompt": "İstemi buraya yazın. Örnek: Anime tarzında dans eden adam"
},
"thumbtip": {
"styleStrength": "Kuvvet"
}
},
"message": {
"fileUploading": "Dosya Yükleniyor..."
},
"tabTitle": {
"library": "Yüklenenler arasından seçim yapın",
"upload": "Video yüklemek"
},
"video": {
"instruction": "Bir video seçin, ardından bir stil modeli seçin ve bırakın StoryTeller.ai bunu gerçekleştirsin",
"label": {
"original": "Orijinal Video"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "İptal etmek",
"enqueue": "Stil Transferini Uygula",
"generateNew": "Başka bir videoyu aktar",
"proceed": "Stil Transferine Devam Edin",
"upload": "Yüklemek"
},
"headings": {
"subtitle": "Videonuzun stilini birinden diğerine değiştirin",
"title": "Video Stili Aktarımı"
},
"image": {
"label": {
"preview": "Stil Aktarımı uygulandıktan sonra bir çerçevenin önizlemesi"
}
},
"input": {
"label": {
"negPrompt": "Olumsuz İstem",
"prompt": "Bir istem girin",
"selectStyle": "Bir stil seçin",
"styleStrength": "Stil Gücü"
},
"placeholder": {
"negPrompt": "İstemi buraya yazın. Örnek: İnsan ayı değildir",
"prompt": "İstemi buraya yazın. Örnek: Anime tarzında dans eden adam"
},
"thumbtip": {
"styleStrength": "Kuvvet"
}
},
"message": {
"fileUploading": "Dosya Yükleniyor..."
},
"tabTitle": {
"library": "Yüklenenler arasından seçim yapın",
"upload": "Video yüklemek"
},
"video": {
"instruction": "Bir video seçin, ardından bir stil modeli seçin ve bırakın StoryTeller.ai bunu gerçekleştirsin",
"label": {
"original": "Orijinal Video"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "Giọng nói thành giọng nói",
"videoFaceAnimator": "khuôn mặt hoạt hình",
"videoMotionCapture": "Quay video chuyển động",
"videoStyleTransfer": "Chuyển kiểu video",
"videoTitle": "Tạo video",
"videoW2l": "Hát nhép video",
"videoWorkflow": "Quy trình làm việc video"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Hủy bỏ",
"enqueue": "Áp dụng chuyển kiểu",
"generateNew": "Chuyển một video khác",
"proceed": "Tiến hành Chuyển kiểu",
"upload": "Tải lên"
},
"headings": {
"subtitle": "Thay đổi phong cách video của bạn từ cái này sang cái khác",
"title": "Chuyển kiểu video"
},
"image": {
"label": {
"preview": "Xem trước khung hình sau khi áp dụng Chuyển kiểu"
}
},
"input": {
"label": {
"negPrompt": "Lời nhắc tiêu cực",
"prompt": "Nhập lời nhắc",
"selectStyle": "Chọn một phong cách",
"styleStrength": "Sức mạnh phong cách"
},
"placeholder": {
"negPrompt": "Nhập lời nhắc ở đây. Ví dụ: Con người không phải là gấu",
"prompt": "Nhập lời nhắc ở đây. Ví dụ: Người đàn ông nhảy theo phong cách anime"
},
"thumbtip": {
"styleStrength": "Sức mạnh"
}
},
"message": {
"fileUploading": "Đang tải tệp lên..."
},
"tabTitle": {
"library": "Chọn từ đã tải lên",
"upload": "Tải lên một video"
},
"video": {
"instruction": "Chọn một video, sau đó chọn mô hình theo phong cách và để StoryTeller.ai thực hiện điều đó",
"label": {
"original": "Video gốc"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "Hủy bỏ",
"enqueue": "Áp dụng chuyển kiểu",
"generateNew": "Chuyển một video khác",
"proceed": "Tiến hành Chuyển kiểu",
"upload": "Tải lên"
},
"headings": {
"subtitle": "Thay đổi phong cách video của bạn từ cái này sang cái khác",
"title": "Chuyển kiểu video"
},
"image": {
"label": {
"preview": "Xem trước khung hình sau khi áp dụng Chuyển kiểu"
}
},
"input": {
"label": {
"negPrompt": "Lời nhắc tiêu cực",
"prompt": "Nhập lời nhắc",
"selectStyle": "Chọn một phong cách",
"styleStrength": "Sức mạnh phong cách"
},
"placeholder": {
"negPrompt": "Nhập lời nhắc ở đây. Ví dụ: Con người không phải là gấu",
"prompt": "Nhập lời nhắc ở đây. Ví dụ: Người đàn ông nhảy theo phong cách anime"
},
"thumbtip": {
"styleStrength": "Sức mạnh"
}
},
"message": {
"fileUploading": "Đang tải tệp lên..."
},
"tabTitle": {
"library": "Chọn từ đã tải lên",
"upload": "Tải lên một video"
},
"video": {
"instruction": "Chọn một video, sau đó chọn mô hình theo phong cách và để StoryTeller.ai thực hiện điều đó",
"label": {
"original": "Video gốc"
}
}
}
@@ -19,6 +19,7 @@
"speechVc": "语音到语音",
"videoFaceAnimator": "脸部动画师",
"videoMotionCapture": "视频动作捕捉",
"videoStyleTransfer": "视频风格迁移",
"videoTitle": "视频生成",
"videoW2l": "视频唇形同步",
"videoWorkflow": "视频工作流程"
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "取消",
"enqueue": "应用风格转移",
"generateNew": "传输另一个视频",
"proceed": "继续风格转移",
"upload": "上传"
},
"headings": {
"subtitle": "将视频风格从一种更改为另一种",
"title": "视频风格迁移"
},
"image": {
"label": {
"preview": "应用样式传输后的帧预览"
}
},
"input": {
"label": {
"negPrompt": "否定提示",
"prompt": "输入提示",
"selectStyle": "选择风格",
"styleStrength": "风格实力"
},
"placeholder": {
"negPrompt": "在此输入提示。例如:人不是熊",
"prompt": "在此输入提示。例如:以动漫风格跳舞的男人"
},
"thumbtip": {
"styleStrength": "力量"
}
},
"message": {
"fileUploading": "正在上传文件..."
},
"tabTitle": {
"library": "从上传的中选择",
"upload": "上传视频"
},
"video": {
"instruction": "选择一个视频,然后选择一种风格的模型,然后让 StoryTeller.ai 实现它",
"label": {
"original": "原创视频"
}
}
}
@@ -0,0 +1,46 @@
{
"button": {
"cancel": "取消",
"enqueue": "应用风格转移",
"generateNew": "传输另一个视频",
"proceed": "继续风格转移",
"upload": "上传"
},
"headings": {
"subtitle": "将视频风格从一种更改为另一种",
"title": "视频风格迁移"
},
"image": {
"label": {
"preview": "应用样式传输后的帧预览"
}
},
"input": {
"label": {
"negPrompt": "否定提示",
"prompt": "输入提示",
"selectStyle": "选择风格",
"styleStrength": "风格实力"
},
"placeholder": {
"negPrompt": "在此输入提示。例如:人不是熊",
"prompt": "在此输入提示。例如:以动漫风格跳舞的男人"
},
"thumbtip": {
"styleStrength": "力量"
}
},
"message": {
"fileUploading": "正在上传文件..."
},
"tabTitle": {
"library": "从上传的中选择",
"upload": "上传视频"
},
"video": {
"instruction": "选择一个视频,然后选择一种风格的模型,然后让 StoryTeller.ai 实现它",
"label": {
"original": "原创视频"
}
}
}
+127 -117
View File
@@ -60,11 +60,9 @@ import { VoiceConversionModelUploadJob } from "@storyteller/components/src/jobs/
import { VoiceConversionModelListItem } from "@storyteller/components/src/api/voice_conversion/ListVoiceConversionModels";
import HttpBackend from "i18next-http-backend";
import {
InferenceJobs,
ModalProvider,
SessionProvider,
} from "components/providers";
import { SessionProvider, InferenceJobs } from "components/providers";
//this caused an error for me (Bombay) so i had to import it as a default
import ModalProvider from "components/providers/ModalProvider";
// NB: We're transitioning over to this instance of i18n-next that loads translations over HTTP from Json Files.
// The old i18n-next instance (see below) bakes in translations into the compiled javascript blob.
@@ -210,7 +208,8 @@ function initInferenceJobsByCategoryMap(): Map<
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoMotionCapture, []);
inferenceJobsByCategory.set(FrontendInferenceJobType.ImageGeneration, []);
inferenceJobsByCategory.set(FrontendInferenceJobType.ConvertFbxtoGltf, []);
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoWorkflow, [])
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoWorkflow, []);
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoStyleTransfer, []);
return inferenceJobsByCategory;
}
@@ -347,7 +346,7 @@ class App extends React.Component<Props, State> {
// state needs to be set here, or if instead can be refreshed locally on
// the page where the new "state" needed
if (this.state.sessionFetched === false){
if (this.state.sessionFetched === false) {
this.setState({ sessionFetched: true });
}
@@ -855,7 +854,7 @@ class App extends React.Component<Props, State> {
{...{
enqueue: this.enqueueInferenceJob,
byCategory: this.state.inferenceJobsByCategory,
inferenceJobs: this.state.inferenceJobs
inferenceJobs: this.state.inferenceJobs,
}}
>
<SessionProvider
@@ -866,115 +865,126 @@ class App extends React.Component<Props, State> {
sessionFetched: this.state.sessionFetched,
}}
>
<ModalProvider>
<Switch>
<Route path="/">
<PageContainer
sessionWrapper={this.state.sessionWrapper}
querySessionAction={this.querySession}
sessionSubscriptionsWrapper={
this.state.sessionSubscriptionsWrapper
}
querySessionSubscriptionsAction={
this.querySessionSubscriptions
}
isShowingVocodesNotice={
this.state.isShowingVocodesNotice
}
clearVocodesNotice={this.clearVocodesNotice}
isShowingLangaugeNotice={
this.state.isShowingLanguageNotice
}
clearLanguageNotice={this.clearLanguageNotice}
displayLanguage={this.state.displayLanguage}
primaryLanguageCode={this.state.primaryLanguageCode}
isShowingTwitchTtsNotice={
this.state.isShowingTwitchTtsNotice
}
clearTwitchTtsNotice={this.clearTwitchTtsNotice}
isShowingPleaseFollowNotice={
this.state.isShowingPleaseFollowNotice
}
clearPleaseFollowNotice={this.clearPleaseFollowNotice}
isShowingBootstrapLanguageNotice={
this.state.isShowingBootstrapLanguageNotice
}
clearBootstrapLanguageNotice={
this.clearBootstrapLanguageNotice
}
enqueueInferenceJob={this.enqueueInferenceJob}
inferenceJobs={this.state.inferenceJobs}
inferenceJobsByCategory={
this.state.inferenceJobsByCategory
}
enqueueTtsJob={this.enqueueTtsJob}
ttsInferenceJobs={this.state.ttsInferenceJobs}
enqueueW2lJob={this.enqueueW2lJob}
w2lInferenceJobs={this.state.w2lInferenceJobs}
enqueueTtsModelUploadJob={this.enqueueTtsModelUploadJob}
ttsModelUploadJobs={this.state.ttsModelUploadJobs}
enqueueW2lTemplateUploadJob={
this.enqueueW2lTemplateUploadJob
}
w2lTemplateUploadJobs={this.state.w2lTemplateUploadJobs}
enqueueVocoderUploadJob={this.enqueueVocoderUploadJob}
vocoderUploadJobs={this.state.vocoderUploadJobs}
enqueueVoiceConversionModelUploadJob={
this.enqueueVoiceConversionModelUploadJob
}
voiceConversionModelUploadJobs={
this.state.voiceConversionModelUploadJobs
}
textBuffer={this.state.textBuffer}
setTextBuffer={this.setTextBuffer}
clearTextBuffer={this.clearTextBuffer}
ttsModels={this.props.allTtsModels}
setTtsModels={this.props.setAllTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
allTtsModelsByTokenMap={
this.props.allTtsModelsByTokenMap
}
ttsModelsByCategoryToken={
this.props.ttsModelsByCategoryToken
}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={this.props.setDropdownCategories}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={this.props.setSelectedCategories}
maybeSelectedTtsModel={this.props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={
this.props.setMaybeSelectedTtsModel
}
selectedTtsLanguageScope={
this.props.selectedTtsLanguageScope
}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
voiceConversionModels={this.state.voiceConversionModels}
setVoiceConversionModels={
this.setAllVoiceConversionModels
}
maybeSelectedVoiceConversionModel={
this.state.maybeSelectedVoiceConversionModel
}
setMaybeSelectedVoiceConversionModel={
this.setMaybeSelectedVoiceConversionModel
}
/>
</Route>
</Switch>
<ModalProvider>
<Switch>
<Route path="/">
<PageContainer
sessionWrapper={this.state.sessionWrapper}
querySessionAction={this.querySession}
sessionSubscriptionsWrapper={
this.state.sessionSubscriptionsWrapper
}
querySessionSubscriptionsAction={
this.querySessionSubscriptions
}
isShowingVocodesNotice={
this.state.isShowingVocodesNotice
}
clearVocodesNotice={this.clearVocodesNotice}
isShowingLangaugeNotice={
this.state.isShowingLanguageNotice
}
clearLanguageNotice={this.clearLanguageNotice}
displayLanguage={this.state.displayLanguage}
primaryLanguageCode={this.state.primaryLanguageCode}
isShowingTwitchTtsNotice={
this.state.isShowingTwitchTtsNotice
}
clearTwitchTtsNotice={this.clearTwitchTtsNotice}
isShowingPleaseFollowNotice={
this.state.isShowingPleaseFollowNotice
}
clearPleaseFollowNotice={this.clearPleaseFollowNotice}
isShowingBootstrapLanguageNotice={
this.state.isShowingBootstrapLanguageNotice
}
clearBootstrapLanguageNotice={
this.clearBootstrapLanguageNotice
}
enqueueInferenceJob={this.enqueueInferenceJob}
inferenceJobs={this.state.inferenceJobs}
inferenceJobsByCategory={
this.state.inferenceJobsByCategory
}
enqueueTtsJob={this.enqueueTtsJob}
ttsInferenceJobs={this.state.ttsInferenceJobs}
enqueueW2lJob={this.enqueueW2lJob}
w2lInferenceJobs={this.state.w2lInferenceJobs}
enqueueTtsModelUploadJob={
this.enqueueTtsModelUploadJob
}
ttsModelUploadJobs={this.state.ttsModelUploadJobs}
enqueueW2lTemplateUploadJob={
this.enqueueW2lTemplateUploadJob
}
w2lTemplateUploadJobs={
this.state.w2lTemplateUploadJobs
}
enqueueVocoderUploadJob={this.enqueueVocoderUploadJob}
vocoderUploadJobs={this.state.vocoderUploadJobs}
enqueueVoiceConversionModelUploadJob={
this.enqueueVoiceConversionModelUploadJob
}
voiceConversionModelUploadJobs={
this.state.voiceConversionModelUploadJobs
}
textBuffer={this.state.textBuffer}
setTextBuffer={this.setTextBuffer}
clearTextBuffer={this.clearTextBuffer}
ttsModels={this.props.allTtsModels}
setTtsModels={this.props.setAllTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
allTtsModelsByTokenMap={
this.props.allTtsModelsByTokenMap
}
ttsModelsByCategoryToken={
this.props.ttsModelsByCategoryToken
}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={
this.props.setDropdownCategories
}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={
this.props.setSelectedCategories
}
maybeSelectedTtsModel={
this.props.maybeSelectedTtsModel
}
setMaybeSelectedTtsModel={
this.props.setMaybeSelectedTtsModel
}
selectedTtsLanguageScope={
this.props.selectedTtsLanguageScope
}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
voiceConversionModels={
this.state.voiceConversionModels
}
setVoiceConversionModels={
this.setAllVoiceConversionModels
}
maybeSelectedVoiceConversionModel={
this.state.maybeSelectedVoiceConversionModel
}
setMaybeSelectedVoiceConversionModel={
this.setMaybeSelectedVoiceConversionModel
}
/>
</Route>
</Switch>
</ModalProvider>
</SessionProvider>
</InferenceJobs>
@@ -12,6 +12,7 @@ interface Props{
max: number;
step?: number;
initialValue?: number;
value?: number;
onChange?: (x:number) => void;
required?: boolean;
withRevert?: boolean;
@@ -31,9 +32,11 @@ const renderTrack = ({ props: { style, ...props }, children }: any) => (
const thumb =
(thumbTip = "") =>
({ props: { style, ...props } }: any) => {
const key = Date.now()
return (
<Tippy
{...{
key,
arrow: false,
content: thumbTip,
placement: "bottom",
@@ -56,6 +59,7 @@ export default function NumberSlider({
onChange: onChangeCallback,
required,
initialValue: initialValueProps,
value,
withRevert = false,
propsButtonRevertToDefault,
}: Props) {
@@ -63,7 +67,8 @@ export default function NumberSlider({
const step = stepProps && stepProps <= max-min ? stepProps
: max-min >= 1 ? 1 : (max-min) / 10;
const initialValue = initialValueProps !== undefined && initialValueProps <= max && initialValueProps >= min ? initialValueProps
: max-min === step ? min : roundToStep((max+min)/2, step);
: value !== undefined && value <=max && value>=min ? value
:max-min === step ? min : roundToStep((max+min)/2, step);
function handleInputOnChange(e: React.ChangeEvent<HTMLInputElement>){
if(onChangeCallback)onChangeCallback(Number.parseInt(e.target.value))
@@ -81,7 +86,7 @@ export default function NumberSlider({
<input
className="fy-number-slider-value"
type="number"
{...{ min, max, step, value:initialValue}}
{...{ min, max, step, value:value || initialValue}}
onChange={handleInputOnChange}
/>
<div className="fy-number-slider-range">
@@ -91,7 +96,7 @@ export default function NumberSlider({
renderThumb: thumb(thumbTip),
renderTrack,
thumbTip,
values: [initialValue],
values: [value || initialValue],
}}
/>
</div>
@@ -1,28 +1,37 @@
import React, { useState } from "react";
import React, { useState, forwardRef } from "react";
import { useMedia } from "hooks";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import makeClass from "resources/makeClass";
import { Label } from "components/common"
interface Props {
className?: string;
src?: string;
mediaToken?: string;
label?: string;
onResponse?: (res:any)=>void
}
export default function VideoFromFakeyou({
type Ref = HTMLVideoElement;
const VideoFromFakeyou = forwardRef<Ref, Props>(({
className,
src,
mediaToken,
label,
onResponse,
...rest
}: Props) {
}: Props, ref) => {
const [mediaFile, setMediaFile] = useState<MediaFile>();
useMedia({
mediaToken: mediaToken,
onSuccess: (res: any) => {
setMediaFile(res)
if(onResponse) onResponse(res)
},
});
@@ -31,11 +40,14 @@ export default function VideoFromFakeyou({
if (mediaLink){
return (
<div {...{ ...makeClass("fy-basic-video",className) }}>
<video controls key={mediaToken} {...rest}>
{label && <Label label={label}/>}
<video controls key={mediaToken} {...rest} ref={ref}>
<source src={mediaLink} type="video/mp4" />
</video>
</div>
)
}
return null;
};
});
export default VideoFromFakeyou;
@@ -40,6 +40,7 @@ import TextArea from "./TextArea";
import TextAreaV2 from "./TextAreaV2";
import TintSpinner from "./TintSpinner";
import VideoBasic from "./VideoBasic";
import VideoFakeyou from "./VideoFakeyou";
import VideoInput from "./VideoInput";
export {
@@ -85,5 +86,6 @@ export {
TextAreaV2,
TintSpinner,
VideoBasic,
VideoFakeyou,
VideoInput
};
@@ -8,6 +8,7 @@ import {
faCameraMovie,
faCompass,
faCloudUpload,
faFilms,
faFaceViewfinder,
faHome,
faMessageDots,
@@ -145,6 +146,7 @@ export default function SideNav({
pending_svc_jobs: 0,
pending_tacotron2_jobs: 0,
pending_voice_designer: 0,
pending_stable_diffusion: 0,
},
},
legacy_tts: {
@@ -191,6 +193,7 @@ export default function SideNav({
variant="secondary"
onClick={() => {
history.push("/login");
handleNavLinkClick();
}}
/>
</>
@@ -203,6 +206,7 @@ export default function SideNav({
small
onClick={() => {
history.push("/signup");
handleNavLinkClick();
}}
/>
</>
@@ -227,6 +231,7 @@ export default function SideNav({
variant="secondary"
onClick={() => {
history.push(url);
handleNavLinkClick();
}}
/>
</>
@@ -241,6 +246,7 @@ export default function SideNav({
variant="danger"
onClick={async () => {
await logoutHandler();
handleNavLinkClick();
}}
/>
</>
@@ -305,6 +311,17 @@ export default function SideNav({
</NavLink>
</li>
<li>
<NavLink
to="/video-styletransfer"
activeClassName="active-link"
onClick={handleNavLinkClick}
>
<FontAwesomeIcon icon={faFilms} className="sidebar-heading-icon" />
{t("videoStyleTransfer")}
</NavLink>
</li>
<li>
<NavLink
to="/studio"
@@ -551,6 +568,12 @@ export default function SideNav({
{queueStats.inference.by_queue.pending_svc_jobs}
</span>
</div>
<div>
Image Generation:{" "}
<span className="text-red">
{queueStats.inference.by_queue.pending_stable_diffusion}
</span>
</div>
<div>
{t("queueFaceAnimator")}:{" "}
<span className="text-red">
@@ -26,6 +26,12 @@ export default function SideNavJobs({ byQueue, t, ttsQueuedCount }: Props) {
{ byQueue.pending_svc_jobs }
</span>
</div>
<div>
Image Generation:{" "}
<span className="text-red">
{ byQueue.pending_stable_diffusion }
</span>
</div>
<div>
{t("queueFaceAnimator")}:{" "}
<span className="text-red">
@@ -8,6 +8,7 @@ import { ModerationIpBanListFc } from "./pages/moderation/moderation_ip_ban_list
import { ModerationViewIpBanFc } from "./pages/moderation/moderation_view_ip_ban/ModerationViewIpBanFc";
import FaceAnimator from "./pages/face_animator";
import VideoMocap from "./pages/video_mocap";
import VideoStyleTransfer from "./pages/video_styletransfer";
import { ProfileEditFc } from "./pages/profile/profile_edit/ProfileEditFc";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
@@ -866,6 +867,20 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/video-styletransfer">
<VideoStyleTransfer
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
inferenceJobs: this.props.inferenceJobs,
inferenceJobsByCategory:
this.props.inferenceJobsByCategory,
sessionWrapper: this.props.sessionWrapper,
}}
/>
</Route>
<Route path="/text-to-image">
<TextToImagePage
sessionSubscriptionsWrapper={
@@ -5,7 +5,7 @@ import React from "react";
export function StudioNotAvailable() {
const subText = (
<p> Please visit our <DiscordLink2 /> to join the wait list. </p>
<> Please visit our <DiscordLink2 /> to join the wait list. </>
);
return (
@@ -2,7 +2,6 @@ import React from "react";
import { formatDistance } from "date-fns";
import { Comment } from "@storyteller/components/src/api/comments/ListComments";
import { SafeDeleteCommentButton } from "./SafeDeleteCommentButton";
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
import { Link } from "react-router-dom";
import { useSession } from "hooks";
@@ -57,12 +56,22 @@ function CommentList(props: Props) {
}
let profileLink = `/profile/${comment.username}`;
let username = comment?.user.username;
let gravatarHash = comment?.user.gravatar_hash;
let avatarIndex = comment?.user.default_avatar.image_index;
let backgroundColorIndex = comment?.user.default_avatar.color_index;
rows.push(
<tr key={comment.token}>
<td className="px-0">
<div className="d-flex gap-3 py-3">
<Gravatar size={42} email_hash={comment.user_gravatar_hash} />
<Gravatar
email_hash={gravatarHash || ""}
username={username || ""}
avatarIndex={avatarIndex || 0}
backgroundIndex={backgroundColorIndex || 0}
size={40}
/>
<div>
<div className="d-flex gap-2 align-items-center flex-wrap">
<Link to={profileLink} className="fw-medium text-white">
@@ -24,9 +24,8 @@ function CreateCommentForm(props: Props) {
const { entityType, entityToken, loadComments } = props;
const [commentMarkdown, setCommentMarkdown] = useState<string>("");
const [uuidIdempotencyToken, setUuidIdempotencyToken] = useState<string>(
uuidv4()
);
const [uuidIdempotencyToken, setUuidIdempotencyToken] =
useState<string>(uuidv4());
const [buttonVisible, setButtonVisible] = useState(false);
const postComment = useCallback(async () => {
@@ -91,8 +90,21 @@ function CreateCommentForm(props: Props) {
let gravatarHash = user?.email_gravatar_hash;
let gravatar = <span />;
let username = user?.username;
let emailHash = user?.email_gravatar_hash;
let avatarIndex = user?.core_info.default_avatar.image_index;
let backgroundColorIndex = user?.core_info.default_avatar.color_index;
if (gravatarHash !== undefined) {
gravatar = <Gravatar email_hash={gravatarHash} size={40} />;
gravatar = (
<Gravatar
email_hash={emailHash || ""}
username={username || ""}
avatarIndex={avatarIndex || 0}
backgroundIndex={backgroundColorIndex || 0}
size={40}
/>
);
}
let commentButton = <></>;
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import MediaAudioComponent from "./MediaAudioComponent";
import MediaVideoComponent from "./MediaVideoComponent";
@@ -32,9 +32,10 @@ import { Badge, Input, Modal } from "components/common";
import BookmarkButton from "components/common/BookmarkButton";
import LikeButton from "components/common/LikeButton";
import { useBookmarks, useMedia, useRatings, useSession } from "hooks";
import SdCoverImagePanel from "../weight/cover_image_panels/SdCoverImagePanel";
import { WeightCategory } from "@storyteller/components/src/api/_common/enums/WeightCategory";
import Iframe from "react-iframe";
import SdBatchMediaPanel from "./components/SdBatchMediaPanel/SdBatchMediaPanel";
import { GetMediaBatchImages } from "@storyteller/components/src/api/media_files/GetMediaBatchImages";
export default function MediaPage() {
const { canEditTtsModel, user } = useSession();
@@ -51,11 +52,31 @@ export default function MediaPage() {
ratings.gather({ res, key: "token" });
},
});
const batchToken = mediaFile?.maybe_batch_token;
const [images, setImages] = useState<string[]>([]);
const bucketConfig = new BucketConfig();
const timeCreated = moment(mediaFile?.created_at || "").fromNow();
const dateCreated = moment(mediaFile?.created_at || "").format("LLL");
const [buttonLabel, setButtonLabel] = useState("Copy");
useEffect(() => {
if (batchToken) {
GetMediaBatchImages(batchToken, {}, {})
.then(response => {
if (response.success) {
const imageUrls = response.results.map(result =>
bucketConfig.getGcsUrl(result.public_bucket_path)
);
setImages(imageUrls);
} else {
console.error("Failed to fetch batch images");
}
})
.catch(err => console.error("Error fetching batch images:", err));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [batchToken]);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const closeDeleteModal = () => setIsDeleteModalOpen(false);
@@ -98,11 +119,17 @@ export default function MediaPage() {
);
case MediaFileType.Image:
let sdMediaImage = "/images/avatars/default-pfp.png";
let sdMediaImage = ["/images/avatars/default-pfp.png"];
if (mediaFile.public_bucket_path) {
sdMediaImage = bucketConfig.getGcsUrl(mediaFile.public_bucket_path);
sdMediaImage = [bucketConfig.getGcsUrl(mediaFile.public_bucket_path)];
}
return <SdCoverImagePanel src={sdMediaImage} />;
return (
<SdBatchMediaPanel
key={images.length}
images={mediaFile.maybe_batch_token ? images : sdMediaImage}
/>
);
case MediaFileType.BVH:
const bvhUrl = bucketConfig.getGcsUrl(mediaFile.public_bucket_path);
return (
@@ -335,8 +362,6 @@ export default function MediaPage() {
const subtitleDivider = <span className="opacity-25 fs-5 fw-light">|</span>;
const bucketConfig = new BucketConfig();
let weightUsedCoverImage = "/images/avatars/default-pfp.png";
if (
mediaFile?.maybe_model_weight_info !== null &&
@@ -438,7 +463,9 @@ export default function MediaPage() {
}}
/>
) : null}
{mediaFile?.media_type === MediaFileType.BVH || mediaFile?.media_type === MediaFileType.GLTF || mediaFile?.media_type === MediaFileType.GLB ? (
{mediaFile?.media_type === MediaFileType.BVH ||
mediaFile?.media_type === MediaFileType.GLTF ||
mediaFile?.media_type === MediaFileType.GLB ? (
<Button
{...{
icon: faVideoPlus,
@@ -0,0 +1,84 @@
@import "scss/colours.scss";
.swiper {
width: 100%;
height: 300px;
margin: 0;
margin-left: auto;
margin-right: auto;
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
}
.swiper-slide {
background-size: contain;
background-position: center;
}
.secondSwiper {
width: 100%;
}
.landscape {
height: 100%;
}
.portrait {
height: 70vh;
}
// .secondSwiper .swiper-slide {
// height: 100%;
// }
.firstSwiper {
height: 20%;
box-sizing: border-box;
}
.firstSwiper .swiper-slide {
transition: 0.2s;
width: 25%;
height: 100%;
opacity: 0.4;
}
.firstSwiper .swiper-slide-thumb-active {
opacity: 1;
}
.swiper-slide img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 0.5rem;
cursor: pointer;
transition: 0.2s;
background-color: $panel;
}
.swiper-button-prev,
.swiper-button-next {
color: $white;
opacity: 0.5;
transition: 0.2s;
&:hover {
opacity: 1;
}
}
@media screen and (max-width: 768px) {
.portrait {
height: 550px;
}
}
@media screen and (max-width: 500px) {
.portrait {
height: 400px;
}
}
@@ -0,0 +1,90 @@
import { Panel } from "components/common";
import React, { useEffect, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/thumbs";
import { FreeMode, Navigation, Thumbs } from "swiper/modules";
import { Swiper as SwiperType } from "swiper/types";
import "./SdBatchMediaPanel.scss";
interface SdCoverImagePanelProps {
images: string[];
}
export default function SdBatchMediaPanel({ images }: SdCoverImagePanelProps) {
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
const [forceUpdateKey, setForceUpdateKey] = useState(0);
const [isPortrait, setIsPortrait] = useState(false);
useEffect(() => {
images.forEach(imgSrc => {
const img = new Image();
img.onload = () => {
// If any image is portrait, update isPortrait to true
if (img.height > img.width && !isPortrait) {
setIsPortrait(true);
}
};
img.src = imgSrc;
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [images]);
useEffect(() => {
if (images.length > 0) {
// Force re-render of the Swiper component by updating a key
setForceUpdateKey(prevKey => prevKey + 1);
}
}, [images]);
const handleSwiper = (swiper: SwiperType) => {
setThumbsSwiper(swiper);
};
const secondSwiperClass = `secondSwiper ${
isPortrait ? "portrait" : "landscape"
}`;
return (
<Panel padding={false} clear className="d-flex flex-column gap-3">
<Swiper
key={forceUpdateKey}
loop={images.length > 1}
spaceBetween={10}
navigation={images.length > 1}
thumbs={{ swiper: images.length > 1 ? thumbsSwiper : null }}
modules={[FreeMode, Navigation, Thumbs]}
className={secondSwiperClass}
slidesPerView={1}
initialSlide={0}
>
{images.map((imgSrc, index) => (
<SwiperSlide key={index}>
<img src={imgSrc} alt={`Slide ${index + 1}`} />
</SwiperSlide>
))}
</Swiper>
{images.length > 1 && (
<Swiper
onSwiper={handleSwiper}
loop={true}
spaceBetween={10}
slidesPerView={5}
freeMode={true}
watchSlidesProgress={true}
modules={[FreeMode, Navigation, Thumbs]}
className="firstSwiper"
initialSlide={0}
>
{images.map((imgSrc, index) => (
<SwiperSlide key={index}>
<img src={imgSrc} alt={`Thumbnail ${index + 1}`} />
</SwiperSlide>
))}
</Swiper>
)}
</Panel>
);
}
@@ -0,0 +1,65 @@
import React, {useReducer, useEffect} from 'react';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { StudioNotAvailable } from 'v2/view/_common/StudioNotAvailable';
import { useInferenceJobs, useLocalize } from "hooks";
import { FrontendInferenceJobType, InferenceJob } from '@storyteller/components/src/jobs/InferenceJob';
import { Container } from "components/common";
import PageHeader from "components/layout/PageHeader";
import { states, reducer } from "./reducer";
import SubRoutes from "./routes";
export default function VideoStyleTransfer(props:{
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
) => void;
inferenceJobs: Array<InferenceJob>;
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
sessionWrapper: SessionWrapper;
}){
const debug = false;
const {t} = useLocalize("VideoStyleTransfer");
const { NO_FILE } = states;
const [pageState, dispatchPageState] = useReducer(reducer, {
status: NO_FILE,
});
const { enqueueInferenceJob } = props;
useInferenceJobs(
FrontendInferenceJobType.VideoStyleTransfer
);
useEffect(() => {
if (
pageState.status === states.JOB_ENQUEUED &&
pageState.inferenceJobToken
) {
enqueueInferenceJob(
pageState.inferenceJobToken,
FrontendInferenceJobType.VideoStyleTransfer
);
dispatchPageState({
type: "enqueueJobSuccess",
payload: { inferenceJobToken: undefined },
});
}
}, [pageState, enqueueInferenceJob]);
if (!props.sessionWrapper.canAccessStudio()) {
return <StudioNotAvailable />
}
return(
<Container type="panel" className="mb-5">
{debug && <p>{`Status:${pageState.status} MediaToken:${pageState.mediaFileToken}`}</p>}
<PageHeader
title={t("headings.title")}
subText={t("headings.subtitle")}
/>
<SubRoutes {...{debug, t, pageState, dispatchPageState}}/>
</Container>
);
}
@@ -0,0 +1,35 @@
import React from 'react';
export const inferenceFailures = (fail = "") => {
switch (fail) {
case "sample case":
return "Sample Case, this should not have been shown";
default:
return "Unknown failure";
}
};
export function TableOfKeyValues(
{
keyValues,
height
}:{
keyValues:{
[key:string]:number|string|boolean|undefined
};
height?: number | string
}){
return(<table style={{
display:'block',
height: height || "100%",
overflowY: "scroll",
overflowX: "clip",
border: "1px solid white",
borderTopLeftRadius: "1rem",
borderBottomLeftRadius: "1rem",
}}><tbody>{
Object.entries(keyValues).map(([key, val], index: number)=>{
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
})
}</tbody></table>);
}
@@ -0,0 +1,45 @@
import React, { memo } from 'react'
import { NavLink } from 'react-router-dom';
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { Analytics } from "common/Analytics";
import { Button, Spinner } from 'components/common';
import { states, Action, State } from "../reducer";
import { inferenceFailures } from '../commons';
export default memo (function PageWorkflowJoblist({
t, pageState, dispatchPageState, parentPath
}: {
debug?: boolean;
t: Function;
pageState: State;
parentPath: string
dispatchPageState: (action: Action) => void;
}) {
return(
<>
<div className="row mb-3">
<NavLink to={`${parentPath}`}>
<Button
label={t("button.generateNew")}
variant="primary"
/>
</NavLink>
</div>
{pageState.status === states.JOB_ENQUEUEING &&
<div>
<h2>t("message.requestingJob")</h2>
<Spinner />
</div>
}
<InferenceJobsList {...{
showNoJobs:true,
failures: inferenceFailures,
onSelect: () => Analytics.voiceConversionClickDownload(),
jobType: FrontendInferenceJobType.VideoStyleTransfer,
}}/>
</>
);
});
@@ -0,0 +1,83 @@
import { v4 as uuidv4 } from "uuid";
export type WorkflowValuesType = {
// File Settings
fileToken: string;
outputPath: string;
// Video Settings
width: number;
height: number;
maxFrames: number;
framesCap: number;
skipFrames: number;
everyNthFrame: number;
inputFps: number;
interpolationMultiplier: number;
// Basic Inputs
workflowConfig: string;
seed: string;
sdModelToken: string;
loraModelToken: string;
posPrompt: string;
negPrompt: string;
// Advance Options Values
firstPass: number;
upscalePass: number;
motionScale: number;
upscaleMultiplier: number;
useEmptyLatent: boolean;
useFaceDetailer: boolean;
denoiseFaceDetailer: number;
useLCM: boolean;
lcmCFG: number;
lcmSteps: number;
// Control Net Values
cnCanny: number;
cnDepth: number;
cnLinearAnime: number;
cnLinearRealistic: number;
cnOpenPose: number;
cnPipeFace: number;
cnSparse: number;
cnTile: number;
}
export function isInputValid(keyValues: {[key:string]:number|string|boolean|undefined}){
return (
keyValues.sdModelToken
&& keyValues.sdModelToken !== ""
&& keyValues.posPrompt
&& keyValues.posPrompt !== ""
);
}
export function mapRequest(workflowValues: WorkflowValuesType){
//TODO: improve the typing of this
return {
"uuid_idempotency_token": uuidv4(),
"maybe_sd_model": workflowValues.sdModelToken,
"maybe_workflow_config": workflowValues.workflowConfig,
"maybe_input_file": workflowValues.fileToken,
"maybe_output_path": workflowValues.outputPath,
"maybe_json_modifications": {
"$.510.inputs.Text": workflowValues.posPrompt,
"$.8.inputs.text": workflowValues.negPrompt,
"$.173.inputs.seed": workflowValues.seed,
"$.401.inputs.Value": workflowValues.firstPass,
"$.918.inputs.Value": workflowValues.motionScale,
"$.137.inputs.Value": workflowValues.framesCap,
"$.186.inputs.Value": workflowValues.skipFrames,
"$.140.inputs.Value": workflowValues.everyNthFrame,
"$.154.inputs.Value": workflowValues.inputFps,
"$.445.inputs.number": workflowValues.interpolationMultiplier,
"$.947.inputs.Value": workflowValues.cnTile,
"$.800.inputs.Value": workflowValues.cnCanny,
"$.797.inputs.Value": workflowValues.cnLinearAnime,
"$.796.inputs.Value": workflowValues.cnLinearRealistic,
"$.772.inputs.Value": workflowValues.cnDepth,
"$.771.inputs.Value": workflowValues.cnOpenPose,
"$.527.inputs.Value": workflowValues.cnPipeFace,
"$.403.inputs.Value": workflowValues.cnSparse
},
}
}
@@ -0,0 +1 @@
export { default } from "./pageVSTApp";
@@ -0,0 +1,45 @@
const initialValues = {
//config
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
//video Settings
width: 544,
height: 544,
maxFrames: 17,
framesCap: 16,
skipFrames: 0,
everyNthFrame: 2,
inputFps: 24,
interpolationMultiplier: 2,
//basics
seed: "",
sdModelToken: "",
loraModelToken: "",
posPrompt: "",
negPrompt: "",
//advance
firstPass: 1,
upscalePass: 0.42,
motionScale:1,
upscaleMultiplier: 1.5,
useEmptyLatent: false,
useFaceDetailer: false,
denoiseFaceDetailer: 0.45,
useLCM: false,
lcmCFG: 2,
lcmSteps: 8,
//Control Net
cnCanny: 0,
cnDepth: 0,
cnLinearAnime: 0,
cnLinearRealistic: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparse: 0.7,
cnTile: 0,
};
export default initialValues;
@@ -0,0 +1,232 @@
import React, { useRef, useState } from "react";
import { NavLink } from 'react-router-dom';
import { useParams, useHistory } from "react-router-dom";
import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer";
import { Action, State } from "../../reducer";
import { TableOfKeyValues } from "../../commons";
import ivs from "./initialValues";
import {
isInputValid,
mapRequest,
WorkflowValuesType,
} from "./helpers";
import {
Button,
Label,
NumberSliderV2,
Panel,
SelectModal,
TextArea,
VideoFakeyou
} from "components/common";
export default function PageVSTApp({
debug, t, pageState, dispatchPageState, parentPath
}: {
debug?: boolean;
t: Function;
pageState: State;
parentPath: string;
dispatchPageState: (action: Action) => void;
}) {
const videoRef = useRef<HTMLVideoElement>(null);
const { mediaToken } = useParams<any>();
const [workflowValues, setWorkflowValues] = useState<WorkflowValuesType>({
fileToken: pageState.mediaFileToken || mediaToken,
...ivs
});
const [styleStrength, setStyleStrength]= useState<number>(8);
if(videoRef?.current){
if(debug) console.log("set-up video element listeners");
const ve = videoRef.current
ve.onloadedmetadata = () =>{
const newValues : {
width?: number;
height?: number;
maxFrames?: number;
framesCap?: number;
} = {};
if (ve.videoWidth && ve.videoHeight) {
const aspectRatio = ve.videoWidth/ve.videoHeight
if (aspectRatio > 1) newValues.width = 960;
else if (aspectRatio < 1) newValues.height = 960
else if (debug) console.log(`aspectRaio: ${aspectRatio}`);
}
if(ve.duration){
newValues.maxFrames = Math.floor(ve.duration)*workflowValues.inputFps;
newValues.framesCap = newValues.maxFrames;
}
setWorkflowValues((curr)=>({
...curr,
...newValues,
}))
}
}
const handleOnChange = (key: string, newValue:any,) => {
setWorkflowValues((curr)=>({...curr, [key]: newValue}));
}
const handleFramesCap = (newValue: number)=>{
if(newValue - workflowValues.skipFrames >= 16)
setWorkflowValues((curr)=>({...curr, framesCap: newValue}));
}
const handleSkipFrames = (newValue: number)=>{
if(workflowValues.framesCap - newValue >= 16)
setWorkflowValues((curr)=>({...curr, skipFrames: newValue}));
}
const handleStyleStrength = (value:number)=>{
setStyleStrength(value);
};
const history = useHistory();
const handleGenerate = ()=>{
if(debug) console.log(workflowValues)
if (isInputValid(workflowValues)){
const request = mapRequest(workflowValues);
if (debug) console.log(request);
EnqueueVideoStyleTransfer(request).then(res => {
if (res.success && res.inference_job_token) {
dispatchPageState({
type: 'enqueueJobSuccess',
payload: {
inferenceJobToken: res.inference_job_token
}
})
}else{
console.log(res);
}
})
dispatchPageState({type: 'enqueueJob'})
history.push(`${parentPath}/jobs`);
}else{
alert("you must pick an sd weight and input a prompt");
}
}
return(
<Panel className="mb-4 p-4">
<div className="row g-3 mb-4">
<div className="col-12 col-md-6">
<VideoFakeyou
label={t("video.label.original")}
mediaToken={workflowValues.fileToken}
ref={videoRef}
onResponse={(res)=>{
dispatchPageState({
type: 'loadFileSuccess',
payload: {
mediaFile: res,
mediaFileToken: pageState.mediaFileToken || mediaToken
}
});
}}
/>
</div>
<div className="col-12 col-md-6">
<Label label={t("image.label.preview")}/>
{debug && <TableOfKeyValues keyValues={workflowValues} height={400}/>}
{/*TODO: MAYBE INTEGRATE INTO PLAYER */}
<br/>
<NumberSliderV2 {...{
min: 16, max: workflowValues.maxFrames, step: 1,
value: workflowValues.framesCap,
label: "Frames Cap",
thumbTip: "24 frames = 1 sec",
onChange: handleFramesCap
}}/>
<br/>
<NumberSliderV2 {...{
min: 0, max: workflowValues.maxFrames-16, step: 1,
value: workflowValues.skipFrames,
label: "Skip Frames",
thumbTip: "24 frames = 1 sec",
onChange: handleSkipFrames
}}/>
{/*TODO: END */}
</div>
</div>
<div className="row g-3 mb-4">
<div className="col-12 col-md-6">
<SelectModal
modalTitle={t("modal.title.selectStyle")}
label={t("input.label.selectStyle")}
onSelect={({token})=>{
handleOnChange("sdModelToken", token);
}}
tabs={[
{
label: "All Weights",
tabKey: "allWeights",
typeFilter: "sd_1.5",
searcher: true,
type: "weights",
},
{
label: "Bookmarked",
tabKey: "bookmarkedWeights",
typeFilter: "sd_1.5",
searcher: false,
type: "weights",
},
]}
/>
<br/>
<NumberSliderV2 {...{
min: 1, max: 10, step: 1,
initialValue: styleStrength,
label: t("input.label.styleStrength"),
thumbTip: t("input.thumbtip.styleStrength"),
withRevert:true,
onChange: handleStyleStrength
}}/>
</div>
<div className="col-12 col-md-6">
<TextArea
{...{
label: t("input.label.prompt"),
placeholder: t("input.placeholder.prompt"),
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("posPrompt", e.target.value),
value: workflowValues.posPrompt,
required: false,
}}
/>
<br/>
<br/>
<TextArea
{...{
label: t("input.label.negPrompt"),
placeholder: t("input.placeholder.negPrompt"),
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("negPrompt", e.target.value),
value: workflowValues.negPrompt,
required: false,
}}
/>
</div>
</div>
<div className="row g-3">
<div className="col-12 d-flex justify-content-between">
<NavLink to={`${parentPath}`}>
<Button
label={t("button.cancel")}
// onClick={handleGenerate}
variant="primary"
/>
</NavLink>
<Button
label={t("button.enqueue")}
onClick={handleGenerate}
variant="primary"
/>
</div>
</div>
</Panel>
);
};
@@ -0,0 +1,65 @@
import React from "react";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { Analytics } from "common/Analytics";
import { inferenceFailures } from "../commons";
import { Panel, Tabs } from "components/common";
import { State, Action } from "../reducer";
import TabContentUpload from "./tabContentUpload";
import TabContentLibrary from "./tabContentLibrary";
export default function PageVideoProvision({
debug=false, parentPath, t, pageState, dispatchPageState
}: {
debug?: boolean;
parentPath: string;
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
const tabs = [
{
label: t("tabTitle.upload"),
content: <TabContentUpload {...{
debug, t, pageState, dispatchPageState
}} />,
to: `${parentPath}/upload`,
padding: true,
},
{
label: t("tabTitle.library"),
content: <TabContentLibrary {...{
debug, t, pageState, dispatchPageState
}} />,
to: `${parentPath}/select-media`,
padding: true,
},
];
return(
<>
<Panel className="mb-4">
<div className="row g-0">
<div className="col-12 col-md-6">
<Tabs tabs={tabs} />
</div>
<div className="col-12 col-md-6 p-5 mt-3">
<p>{t("video.instruction")}</p>
</div>
</div>
</Panel>
<InferenceJobsList {...{
showNoJobs:false,
failures: inferenceFailures,
onSelect: () => Analytics.voiceConversionClickDownload(),
jobType: FrontendInferenceJobType.VideoStyleTransfer,
}}/>
</>
);
}
@@ -0,0 +1,62 @@
import React from "react";
import { NavLink } from "react-router-dom";
import { states, Action, State } from "../reducer";
import { Button } from "components/common";
import SelectModal, { SelectModalData } from "components/common/SelectModal/SelectModal";
import VideoFakeyou from "components/common/VideoFakeyou";
export default function TabContentLibrary({
t, pageState, dispatchPageState
}: {
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}){
const handleProceed = ()=>{
dispatchPageState({type: "loadFile"})
}
const handleOnSelect = (data:SelectModalData)=>{
if (data.token !== ""){
dispatchPageState({
type: "selectedFile",
payload: {mediaFileToken: data.token}
});
}else{
dispatchPageState({type: "clearedFile"});
};
};
return (
<div className="row g-3">
<div className="col-12">
<SelectModal
modalTitle="Select a Video"
label="Select a Video"
onSelect={handleOnSelect}
tabs={[
{
label: "All Videos",
tabKey: "allVideos",
typeFilter: "video",
searcher: false,
type: "media",
},
]}
/>
{pageState.mediaFileToken &&
<VideoFakeyou mediaToken={pageState.mediaFileToken} />
}
</div>
{pageState.status === states.FILE_SELECTED &&
<div className="col-12 d-flex justify-content-center mt-5">
<NavLink to={`load/${pageState.mediaFileToken}`}>
<Button
label={t("button.proceed")}
onClick={handleProceed}
/>
</NavLink>
</div>
}
</div>
);
}
@@ -0,0 +1,98 @@
import React from "react";
import { Redirect } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import { UploadMedia } from "@storyteller/components/src/api/media_files/UploadMedia";
import { useFile } from "hooks";
import { ErrorMessage, VideoInput, Spinner } from "components/common";
import { states, Action, State } from "../reducer";
export default function TabContentUpload({
debug=false, t, pageState, dispatchPageState
}: {
debug?: boolean;
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
const videoProps = useFile({});
const {NO_FILE, FILE_STAGED, FILE_SELECTED, FILE_UPLOADING, FILE_UPLOADED} = states;
const makeVideoUploadRequest = () => ({
uuid_idempotency_token: uuidv4(),
file: videoProps.file,
source: "file",
type: "video",
});
const handleUploadVideo = () => {
dispatchPageState({type: 'uploadFile'});
UploadMedia(makeVideoUploadRequest()).then(res => {
if (res.success && res.media_file_token) {
dispatchPageState({
type: 'uploadFileSuccess',
payload:{
mediaFileToken :res.media_file_token
}
})
}
});
};
// contains upload inout state and controls, see docs
if (pageState.status === NO_FILE
|| pageState.status === FILE_STAGED
|| pageState.status === FILE_SELECTED
) {
return (
<>
<div className="row">
<div className="col-12">
<VideoInput
{...{
t,
...videoProps,
onStateChange: () =>{
if (pageState.status === NO_FILE && videoProps.file)
dispatchPageState({type: "stagedFile"})
else if (pageState.status === FILE_STAGED && !videoProps.file)
dispatchPageState({type: "clearedFile"})
}
}}
/>
</div>
</div>
<div className="row py-3">
<div className="col-12">
<div className="d-flex justify-content-end gap-3">
<button
className="btn btn-primary"
disabled={pageState.status !== FILE_STAGED}
onClick={handleUploadVideo}
>
{t("button.upload")}
</button>
</div>
</div>
</div>
</>
);
}else if (pageState.status === FILE_UPLOADING){
return(
<div className="row">
<div className="col-12">
<h1>{t("message.fileUploading")}</h1>
</div>
<div className="col-12">
<Spinner />
</div>
</div>
);
}else if (pageState.status === FILE_UPLOADED){
return(
<Redirect to={`load/${pageState.mediaFileToken}`}/>
)
}
return <ErrorMessage />;
}
@@ -0,0 +1,2 @@
export {default} from "./VideoStyleTransfer";
@@ -0,0 +1,87 @@
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
export enum states{
NO_FILE,
FILE_STAGED,
FILE_SELECTED,
FILE_UPLOADING,
FILE_UPLOADED,
FILE_LOADING,
FILE_LOADED,
JOB_ENQUEUEING,
JOB_ENQUEUED
}
export type State = {
status: number;
mediaFileToken?: string;
mediaFile?: MediaFile;
inferenceJobToken?: string;
}
export type Action =
| {type: 'reset'}
| {type: 'stagedFile'}
| {type: 'clearedFile'}
| {type: 'selectedFile', payload: { mediaFileToken: string}}
| {type: 'uploadFile'}
| {type: 'uploadFileSuccess', payload:{ mediaFileToken: string}}
| {type: 'loadFile'}
| {type: 'loadFileSuccess', payload:{
mediaFileToken: string, mediaFile: MediaFile
}}
| {type: 'enqueueJob'}
| {type: 'enqueueJobSuccess', payload: {inferenceJobToken: string|undefined}}
export function reducer (state: State, action: Action): State {
switch(action.type){
case 'stagedFile':
return {...state, status: states.FILE_STAGED}
case 'clearedFile':
return {...state,
status: states.NO_FILE,
mediaFileToken: ""
}
case 'selectedFile':
return {
...state,
status: states.FILE_SELECTED,
mediaFileToken: action.payload.mediaFileToken
}
case 'uploadFile':
return {...state,status: states.FILE_UPLOADING};
case 'uploadFileSuccess':
return {
...state,
status: states.FILE_UPLOADED,
mediaFileToken: action.payload.mediaFileToken
}
case 'loadFile':
return {...state, status: states.FILE_LOADING}
case 'loadFileSuccess':
console.log("LOAD FILE SUCCESS");
return {
...state,
status: states.FILE_LOADED,
mediaFile: action.payload.mediaFile,
mediaFileToken : action.payload.mediaFileToken,
}
case 'enqueueJob':
return {
...state,
status: states.JOB_ENQUEUEING
}
case 'enqueueJobSuccess':
return {
...state,
status: states.JOB_ENQUEUED,
inferenceJobToken: action.payload.inferenceJobToken
}
case 'reset':
default:
return {status: states.NO_FILE};
}
}
@@ -0,0 +1,50 @@
import React from "react";
import {Switch, Route, useRouteMatch, Redirect, useHistory} from "react-router-dom";
import PageVSTApp from "./components/pageVSTApp";
import PageVideoProvision from "./components/pageVideoProvision";
import PageJoblist from "./components/pageJoblist";
import {states,State,Action} from "./reducer";
export default function SubRoutes ({
debug, t, pageState, dispatchPageState
}:{
debug?:boolean,
t:Function,
pageState:State,
dispatchPageState: (action: Action) => void
}){
const { path } = useRouteMatch();
const history = useHistory();
history.listen(({pathname}, action) => {
if ((pathname === path || pathname.match(/upload|select-media/g)) && pageState.status >= states.FILE_LOADING){
//reset page if landed on page "Freshly"
dispatchPageState({type: 'reset'})
}
})
const commonPageProps = {
debug, parentPath: path, t, pageState, dispatchPageState
};
return(
<Switch>
<Route exact path={`${path}/load/:mediaToken`} >
<PageVSTApp {...commonPageProps}/>
</Route>
<Route exact path={`${path}/upload`}>
<PageVideoProvision {...commonPageProps}/>
</Route>
<Route exact path={`${path}/select-media`}>
<PageVideoProvision {...commonPageProps}/>
</Route>
<Route exact path={`${path}/jobs`}>
<PageJoblist {...commonPageProps}/>
</Route>
<Route path={`${path}`}>
<Redirect to={`${path}/upload`} />
</Route>
</Switch>
)
}
@@ -11,7 +11,7 @@ import SubRoutes from "./videoWorkflowRoutes";
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { StudioNotAvailable } from 'v2/view/_common/StudioNotAvailable';
export default function StorytellerFilter(props:{
export default function VideoWorkflow(props:{
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
@@ -24,6 +24,15 @@ export default memo( function SectionVideoSettings({
}){
const iv = VideoSettingsInitialValues;
const handleFramesCap = (newValue: number)=>{
if(newValue - wfVal.skipFrames >= 16)
handleOnChange("framesCap",newValue);
}
const handleSkipFrames = (newValue: number)=>{
if(wfVal.framesCap - newValue >= 16)
handleOnChange("skipFrames",newValue);
}
if(ve && ve!==null){
ve.onloadedmetadata = () =>{
if (ve.videoWidth && ve.videoHeight) {
@@ -60,19 +69,19 @@ export default memo( function SectionVideoSettings({
<div className="col-md-6">
<NumberSliderV2 {...{
min: 16, max: wfVal.maxFrames, step: 1,
initialValue: wfVal.framesCap,
value: wfVal.framesCap,
label: "Frames Cap",
thumbTip: "24 frames = 1 sec",
onChange: (val)=>{handleOnChange("framesCap",val)}
onChange: handleFramesCap
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: wfVal.maxFrames-16, step: 1,
initialValue: wfVal.skipFrames,
value: wfVal.skipFrames,
label: "Skip Frames",
thumbTip: "24 frames = 1 sec",
onChange: (val)=>{handleOnChange("skipFrames",val)}
onChange: handleSkipFrames
}}/>
</div>
</div>
@@ -196,6 +196,12 @@ export default function WeightPage({
return (
<div className="d-flex flex-column gap-3">
<SdCoverImagePanel src={sdCoverImage} />
{!!weight.description_rendered_html && (
<Panel padding={true}>
<h5 className="fw-semibold mb-2">Description</h5>
<p className="fs-7">{weight.description_rendered_html}</p>
</Panel>
)}
{imageGenPanel}
</div>
);
@@ -487,14 +493,14 @@ export default function WeightPage({
<div className="row g-4">
<div className="col-12 col-xl-8 d-flex flex-column gap-3">
<div className="media-wrapper">{renderWeightComponent(weight)}</div>
{!!weight.description_rendered_html && (
{ !!weight.description_rendered_html && (
<Panel padding={true}>
<h4 className="fw-semibold mb-3">Description</h4>
<p>{weight.description_rendered_html}</p>
</Panel>
)}
)
}
<div className="media-wrapper">{renderWeightComponent(weight)}</div>
<div className="panel p-3 py-4 p-md-4 d-none d-xl-block">
<h4 className="fw-semibold mb-3">Comments</h4>
+1
View File
@@ -6,6 +6,7 @@ MASTER_BRANCH='master'
PRODUCTION_BRANCH='production-fakeyou'
git stash
git branch --set-upstream-to="origin/${MASTER_BRANCH}" ${MASTER_BRANCH}
git checkout $MASTER_BRANCH
git pull
+8
View File
@@ -4484,6 +4484,7 @@ __metadata:
react-twitch-embed: ^3.0.1
react-wavesurfer.js: ^0.0.8
sass: ^1.57.1
swiper: ^11.0.5
typescript: ^3.7.5
universal-cookie: ^6.1.1
uuid: ^8.3.2
@@ -20178,6 +20179,13 @@ __metadata:
languageName: node
linkType: hard
"swiper@npm:^11.0.5":
version: 11.0.5
resolution: "swiper@npm:11.0.5"
checksum: 83b125d16733a66ed5f58627c81263116e39eea5cead92667d2e24692ff94632de9aa05fec1ed80dc352779c0d7525e9a18424fce1c39c5e970823414b212edb
languageName: node
linkType: hard
"swiper@npm:^8.3.2":
version: 8.3.2
resolution: "swiper@npm:8.3.2"