import * as studio from "@storyteller/studio"; import { LitElement, type PropertyValues, html, nothing, unsafeCSS, TemplateResult } from "lit"; import { type Ref, createRef, ref } from "lit/directives/ref.js"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { styleMap } from "lit/directives/style-map.js"; import { type ScreenshotReadyEvent } from "@storyteller/studio"; import { DialogElement } from "@storyteller/studio-ui/dialog"; import { bind, on } from "@storyteller/framework"; import { VideoStylePreview } from "./api/VideoStylePreview"; import "./bubble-select.element"; import styles from "./temp-video-preview.element.scss?inline"; import { STYLE_OPTIONS, type VstStyle } from "./inspector-tabs/style-tab"; enum InspectorMode { Properties, Style } @customElement("sts-temp-video-preview") export class TempVideoPreview extends LitElement { @property({ attribute: false }) trigger?: Ref; @state() sourceBlob?: Blob; @state() resultBlob?: Blob; @state() sourceImageUrl?: string; @state() resultImageUrl?: string; @state() selectedStyle: VstStyle = "anime_2d_flat"; @state() prompt = ""; @state() negPrompt = ""; #temporaryVideoPreviewRef = createRef(); protected override willUpdate(changes: PropertyValues): void { if (changes.has("trigger")) { const prevTrigger = changes.get("trigger"); prevTrigger?.value?.removeEventListener("click", this.onDialogOpen); this.trigger?.value?.addEventListener("click", this.onDialogOpen); } super.willUpdate(changes) } override disconnectedCallback(): void { this.trigger?.value?.removeEventListener("click", this.onDialogOpen); if (this.sourceImageUrl) { URL.revokeObjectURL(this.sourceImageUrl); this.sourceImageUrl = undefined; } if (this.resultImageUrl) { URL.revokeObjectURL(this.resultImageUrl); this.resultImageUrl = undefined; } super.disconnectedCallback(); } requestScreen = () => { studio.requestScreenshot(); console.log("🟢"); } onDialogOpen = () => { studio.requestScreenshot(); } @on("window:screenshot-ready") async onScreenshotReady (event: ScreenshotReadyEvent) { console.log("🟣",event.detail.data); const file = new File( [event.detail.data], "studio-scene-screen.png", { type: "image/png" }, ); this.sourceBlob = file; const prevSourceUrl = this.sourceImageUrl; this.sourceImageUrl = URL.createObjectURL(this.sourceBlob); if (prevSourceUrl) URL.revokeObjectURL(prevSourceUrl); this.resultImageUrl = undefined; try { this.resultBlob = await VideoStylePreview({ video: file, request: JSON.stringify({ "style": this.selectedStyle, "positive_prompt": this.prompt, "negative_prompt": this.negPrompt, }) }); console.log("🟡", this.resultBlob); } catch (err) { // TODO: Show error message console.error(err); return; } const prevResultUrl = this.resultImageUrl; this.resultImageUrl = URL.createObjectURL(this.resultBlob); if (prevResultUrl) URL.revokeObjectURL(prevResultUrl); } stopTextInputBubbling(event: KeyboardEvent): void { // We want modifier keys like "Alt" to always be received by the Bevy // canvas because they affect the camera controller behavior, but we do // NOT want Bevy to receive typing inputs like W, A, S, and D while the // user is trying to type into the Prompt field. if (/^(Alt|Control)$/.test(event.key)) return; event.stopPropagation(); } protected override render = (): TemplateResult => html` { this.#temporaryVideoPreviewRef.value?.close(); }} @keydown=${this.stopTextInputBubbling} @keyup=${this.stopTextInputBubbling} > Video Style Preview

Raw Preview

${!this.sourceImageUrl ? html`

Loading...

` : nothing}

Styled Preview

${!this.resultImageUrl ? html`

Loading...

` : nothing}
Update `] as const} >
` }