add unit-tested way to construct Engine URLs

This commit is contained in:
Brandon Thomas committed 2024-03-11 21:02:36 -04:00
1 parent be2efc8b44
commit a803bfb79b
3 files changed
+211

No files matched your search

@@ -0,0 +1,5 @@
export enum EngineMode {
Viewer = "viewer",
Studio = "studio",
}
@@ -0,0 +1,119 @@
import { MediaFileType } from "@storyteller/components/src/api/_common/enums/MediaFileType";
import { EngineMode } from "./EngineMode";
import { GetEngineUrl } from "./GetEngineUrl";
import { WeightCategory } from "@storyteller/components/src/api/_common/enums/WeightCategory";
import { WeightType } from "@storyteller/components/src/api/_common/enums/WeightType";
import { MediaFileSubtype } from "@storyteller/components/src/api/enums/MediaFileSubtype";
describe('mode', () => {
test('studio', () => {
const url = GetEngineUrl({mode: EngineMode.Studio, load: { objectId: "foo" } });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&objectId=foo");
});
test('viewer', () => {
const url = GetEngineUrl({mode: EngineMode.Viewer, load: { objectId: "foo" } });
expect(url).toEqual("https://engine.fakeyou.com/?mode=viewer&objectId=foo");
});
});
describe('skybox', () => {
test('from named skybox', () => {
const url = GetEngineUrl({mode: EngineMode.Studio, load: { objectId: "foo" }, skybox: "gum_trees_4k" });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&skybox=gum_trees_4k&objectId=foo");
});
test('from hex color', () => {
const url = GetEngineUrl({mode: EngineMode.Studio, load: { objectId: "foo" }, skybox: "ff0000" });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&skybox=ff0000&objectId=foo");
});
});
describe('object ids', () => {
test('object id urls should work', () => {
const url = GetEngineUrl({mode: EngineMode.Studio, load: { objectId: "foo" } });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&objectId=foo");
});
});
// TODO(bt,2024-03-11): We're only testing one type of media file
describe('media files', () => {
let mediaFile = {
token: "MEDIA_FILE_TOKEN",
media_type: MediaFileType.GLB,
maybe_media_subtype: null,
public_bucket_path: "path/to/file",
maybe_batch_token: "BATCH_TOKEN",
created_at: new Date(),
updated_at: new Date(),
maybe_creator_user: null,
creator_set_visibility: "public",
// TODO(bt,2024-03-11): Make optional
maybe_model_weight_info: {
title: "title",
weight_token: "WEIGHT_TOKEN",
weight_category: WeightCategory.SD,
weight_type: WeightType.HIFIGAN_TT2,
maybe_cover_image_public_bucket_path: "image",
maybe_weight_creator: {
user_token: "USER_TOKEN",
username: "username",
display_name: "display_name",
gravatar_hash: "foo",
default_avatar: {
image_index: 1,
color_index: 2,
}
},
}
};
describe('scene files', () => {
test('from media type', () => {
mediaFile.media_type = MediaFileType.SceneRon;
// NB: Not the real subtype; forcing test to act on type.
mediaFile.maybe_media_subtype = null;
const url = GetEngineUrl({mode: EngineMode.Studio, load: mediaFile });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&scene=remote://MEDIA_FILE_TOKEN.scn.ron");
});
test('from media subtype', () => {
// NB: Not the real time; forcing test to act on subtype.
mediaFile.media_type = MediaFileType.Audio;
// TODO(bt,2024-03-11): Why does the IDE complain here?
mediaFile.maybe_media_subtype = MediaFileSubtype.StorytellerScene as any;
const url = GetEngineUrl({mode: EngineMode.Studio, load: mediaFile });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&scene=remote://MEDIA_FILE_TOKEN.scn.ron");
});
});
describe('mixamo animations', () => {
test('from media subtype', () => {
// NB: Not the real time; forcing test to act on subtype.
mediaFile.media_type = MediaFileType.Audio;
// TODO(bt,2024-03-11): Why does the IDE complain here?
mediaFile.maybe_media_subtype = MediaFileSubtype.Mixamo as any;
const url = GetEngineUrl({mode: EngineMode.Studio, load: mediaFile });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&mixamo=https://storage.googleapis.com/dev-vocodes-public/path/to/file");
});
});
describe('bvh animations', () => {
test('from media subtype', () => {
// NB: Not the real time; forcing test to act on subtype.
mediaFile.media_type = MediaFileType.Audio;
// TODO(bt,2024-03-11): Why does the IDE complain here?
mediaFile.maybe_media_subtype = MediaFileSubtype.MocapNet as any;
const url = GetEngineUrl({mode: EngineMode.Studio, load: mediaFile });
expect(url).toEqual("https://engine.fakeyou.com/?mode=studio&bvh=https://storage.googleapis.com/dev-vocodes-public/path/to/file");
});
});
});
@@ -0,0 +1,87 @@
import { MediaFile as MediaFileOne } from "@storyteller/components/src/api/media_files/GetMedia";
import { MediaFile as MediaFileTwo } from "@storyteller/components/src/api/media_files/GetMediaFile";
import { EngineMode } from "./EngineMode";
import { MediaFileSubtype } from "@storyteller/components/src/api/enums/MediaFileSubtype";
import { MediaFileType } from "@storyteller/components/src/api/_common/enums/MediaFileType";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
// Storyteller Engine parameters
// These are documented here:
// https://www.notion.so/storytellerai/Studio-Iframe-Query-Params-a748a9929ec3404780c3884e7fb89bdb
interface GetEngineUrlArgs {
mode: EngineMode,
// Polymorphic type of asset we're loading into the engine.
load: LoadableAsset
// Optional skybox color (hex) or name
// - Predefined skyboxes, eg. `gum_trees_4k`, `kloofendal_28d_misty_4k`, `meadow_4k`, `promenade_de_vidy_4k`, `scythian_tombs_4k`
// - Hex colors, eg. `ff0000` or `1a1a27`.
skybox?: string,
}
// Support multiple "Media File" API response payloads.
type MediaFile = MediaFileOne | MediaFileTwo ;
// Used to load raw built-in objects into the engine.
interface EngineObject {
objectId: string;
}
type LoadableAsset = MediaFile | EngineObject;
const ENGINE_BASE_URL = "https://engine.fakeyou.com";
export function GetEngineUrl(args: GetEngineUrlArgs) : string {
let engineUrl = `${ENGINE_BASE_URL}/?mode=${args.mode}`;
if (args.skybox) {
engineUrl += `&skybox=${args.skybox}`;
}
if (isMediaFile(args.load)) {
const bucketAssetUrl = new BucketConfig().getGcsUrl(args.load.public_bucket_path);
if (
args.load.maybe_media_subtype === MediaFileSubtype.StorytellerScene ||
args.load.media_type === MediaFileType.SceneRon
) {
// Storyteller Engine Scenes
// NB: Storyteller Engine makes the API call to load the scene.
// We don't need to pass the bucket path.
// The engine, does, however, need a `.scn.ron` file extension.
const sceneUrlRef = `remote://${args.load.token}.scn.ron`;
engineUrl += `&scene=${sceneUrlRef}`;
} else if (
args.load.maybe_media_subtype === MediaFileSubtype.Mixamo
) {
engineUrl += `&mixamo=${bucketAssetUrl}`;
} else if (
args.load.maybe_media_subtype === MediaFileSubtype.MocapNet
) {
engineUrl += `&bvh=${bucketAssetUrl}`;
} else {
engineUrl += `&sceneImport=${bucketAssetUrl}`;
}
} else if (isEngineObject(args.load)) {
// NB: This is an engine built-in, eg. `couch.gltf` or `sample-room.gltf`.
engineUrl += `&objectId=${args.load.objectId}`;
}
return engineUrl;
}
// Type guard for media files
const isMediaFile = (loadable: LoadableAsset) : loadable is MediaFile => {
return "token" in loadable;
}
// Type guard for engine objects
const isEngineObject = (loadable: LoadableAsset) : loadable is EngineObject => {
return "objectId" in loadable;
}