Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lite: Make the Examples component display media files using pseudo HTTP requests to the Wasm server #5627

Merged
merged 14 commits into from Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/early-planes-vanish.md
@@ -0,0 +1,10 @@
---
"@gradio/app": minor
"@gradio/audio": minor
"@gradio/image": minor
"@gradio/video": minor
"@gradio/wasm": minor
"gradio": minor
---

feat:Lite: Make the Examples component display media files using pseudo HTTP requests to the Wasm server
6 changes: 6 additions & 0 deletions js/app/src/Index.svelte
Expand Up @@ -70,6 +70,8 @@
import { StatusTracker } from "@gradio/statustracker";
import { _ } from "svelte-i18n";
import { setupi18n } from "./i18n";
import type { WorkerProxy } from "@gradio/wasm";
import { setWorkerProxyContext } from "@gradio/wasm/svelte";

setupi18n();

Expand All @@ -89,6 +91,10 @@
export let mount_css: typeof default_mount_css = default_mount_css;
export let client: ReturnType<typeof api_factory>["client"];
export let upload_files: ReturnType<typeof api_factory>["upload_files"];
export let worker_proxy: WorkerProxy | undefined = undefined;
if (worker_proxy) {
setWorkerProxyContext(worker_proxy);
}

export let space: string | null;
export let host: string | null;
Expand Down
1 change: 1 addition & 0 deletions js/app/src/lite/index.ts
Expand Up @@ -132,6 +132,7 @@ export function create(options: Options): GradioAppController {
// TODO: Remove -- i think this is just for autoscroll behavhiour, app vs embeds
app_mode: options.appMode,
// For Wasm mode
worker_proxy,
client,
upload_files,
mount_css: overridden_mount_css
Expand Down
29 changes: 16 additions & 13 deletions js/audio/interactive/Audio.svelte
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only media component where the interactive version has been changed rather than the example. Is there a reason for that?

Copy link
Member Author

@whitphx whitphx Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just because of the codebase structure.
In the case of Audio, example/Audio.svelte doesn't have the <audio /> tag as it only shows the example file name, and modifying interactive/Audio.svelte was needed to make audio apps work instead.
In turn, for example in the case of Video, all Video components uses shared/Player.svelte under the hood, so I just modified it.

FYI, I benchmarked the following 3 spaces to make sure the pair of a media input component and gr.Examples() works.

So there might be missing parts. Will catch them in another PR if found.

Expand Up @@ -13,9 +13,9 @@
import { Upload, ModifyUpload } from "@gradio/upload";
import { BlockLabel } from "@gradio/atoms";
import { Music } from "@gradio/icons";
import Audio from "../shared/Audio.svelte";
// @ts-ignore
import Range from "svelte-range-slider-pips";
import { loaded } from "../shared/utils";
import { _ } from "svelte-i18n";

import type { IBlobEvent, IMediaRecorder } from "extendable-media-recorder";
Expand Down Expand Up @@ -295,17 +295,20 @@
absolute={true}
/>

<audio
use:loaded={{ autoplay, crop_values }}
controls
bind:this={player}
preload="metadata"
src={value?.data}
on:play
on:pause
on:ended={handle_ended}
data-testid={`${label}-audio`}
/>
<div class="container">
<Audio
controls
{autoplay}
{crop_values}
bind:node={player}
preload="metadata"
src={value?.data}
on:play
on:pause
on:ended={handle_ended}
data-testid={`${label}-audio`}
/>
</div>

{#if mode === "edit" && player?.duration}
<Range
Expand Down Expand Up @@ -360,7 +363,7 @@
}
}

audio {
.container {
padding: var(--size-2);
width: var(--size-full);
height: var(--size-14);
Expand Down
1 change: 1 addition & 0 deletions js/audio/package.json
Expand Up @@ -14,6 +14,7 @@
"@gradio/statustracker": "workspace:^",
"@gradio/upload": "workspace:^",
"@gradio/utils": "workspace:^",
"@gradio/wasm": "workspace:^",
"extendable-media-recorder": "^9.0.0",
"extendable-media-recorder-wav-encoder": "^7.0.76",
"svelte-range-slider-pips": "^2.0.1"
Expand Down
33 changes: 33 additions & 0 deletions js/audio/shared/Audio.svelte
@@ -0,0 +1,33 @@
<script lang="ts">
import type { HTMLAudioAttributes } from "svelte/elements";
import { createEventDispatcher } from "svelte";
import { loaded, type LoadedParams } from "../shared/utils";
import { resolve_wasm_src } from "@gradio/wasm/svelte";

export let src: HTMLAudioAttributes["src"] = undefined;

export let autoplay: LoadedParams["autoplay"] = undefined;
export let crop_values: LoadedParams["crop_values"] = undefined;
export let controls: HTMLAudioAttributes["controls"] = undefined;
export let preload: HTMLAudioAttributes["preload"] = undefined;

export let node: HTMLAudioElement | undefined = undefined;

const dispatch = createEventDispatcher();
</script>

{#await resolve_wasm_src(src) then resolved_src}
<audio
src={resolved_src}
{controls}
{preload}
on:play={dispatch.bind(null, "play")}
on:pause={dispatch.bind(null, "pause")}
on:ended={dispatch.bind(null, "ended")}
bind:this={node}
use:loaded={{ autoplay, crop_values }}
data-testid={$$props["data-testid"]}
/>
{:catch error}
<p style="color: red;">{error.message}</p>
{/await}
2 changes: 1 addition & 1 deletion js/audio/shared/utils.ts
@@ -1,6 +1,6 @@
import type { ActionReturn } from "svelte/action";

interface LoadedParams {
export interface LoadedParams {
crop_values?: [number, number];
autoplay?: boolean;
}
Expand Down
20 changes: 12 additions & 8 deletions js/image/example/Image.svelte
@@ -1,29 +1,32 @@
<script lang="ts">
import Image from "../shared/Image.svelte";

export let value: string;
export let samples_dir: string;
export let type: "gallery" | "table";
export let selected = false;
</script>

<img
src={samples_dir + value}
<div
class="container"
class:table={type === "table"}
class:gallery={type === "gallery"}
class:selected
alt=""
/>
>
<Image src={samples_dir + value} alt="" />
</div>

<style>
img {
.container {
border-radius: var(--radius-lg);
max-width: none;
}

img.selected {
.container.selected {
border-color: var(--border-color-accent);
}

.table {
.container.table {
margin: 0 auto;
border: 2px solid var(--border-color-primary);
border-radius: var(--radius-lg);
Expand All @@ -32,8 +35,9 @@
object-fit: cover;
}

.gallery {
.container.gallery {
border: 2px solid var(--border-color-primary);
height: var(--size-20);
max-height: var(--size-20);
object-fit: cover;
}
Expand Down
1 change: 1 addition & 0 deletions js/image/package.json
Expand Up @@ -13,6 +13,7 @@
"@gradio/statustracker": "workspace:^",
"@gradio/upload": "workspace:^",
"@gradio/utils": "workspace:^",
"@gradio/wasm": "workspace:^",
"cropperjs": "^1.5.12",
"lazy-brush": "^1.0.1",
"resize-observer-polyfill": "^1.5.1"
Expand Down
22 changes: 22 additions & 0 deletions js/image/shared/Image.svelte
@@ -0,0 +1,22 @@
<script lang="ts">
import type { HTMLImgAttributes } from "svelte/elements";
type $$Props = HTMLImgAttributes;

import { resolve_wasm_src } from "@gradio/wasm/svelte";

export let src: HTMLImgAttributes["src"] = undefined;
</script>

{#await resolve_wasm_src(src) then resolved_src}
<!-- svelte-ignore a11y-missing-attribute -->
<img src={resolved_src} {...$$restProps} />
{:catch error}
<p style="color: red;">{error.message}</p>
{/await}

<style>
img {
max-width: 100%;
max-height: 100%;
}
</style>
39 changes: 20 additions & 19 deletions js/video/example/Video.svelte
@@ -1,6 +1,5 @@
<script lang="ts">
import { playable } from "../shared";
import { onMount } from "svelte";
import { playable, Video } from "../shared";

export let type: "gallery" | "table";
export let selected = false;
Expand All @@ -17,48 +16,50 @@
await video.play();
video.pause();
}

onMount(() => {
init();
});
</script>

{#if playable()}
<video
muted
playsinline
bind:this={video}
<div
class="container"
class:table={type === "table"}
class:gallery={type === "gallery"}
class:selected
on:mouseover={video.play}
on:mouseout={video.pause}
src={samples_dir + value}
/>
>
<Video
muted
playsinline
bind:node={video}
on:loadeddata={init}
on:mouseover={video.play.bind(video)}
on:mouseout={video.pause.bind(video)}
src={samples_dir + value}
/>
</div>
{:else}
<div>{value}</div>
{/if}

<style>
video {
.container {
flex: none;
border: 2px solid var(--border-color-primary);
border-radius: var(--radius-lg);
max-width: none;
}

video:hover,
video.selected {
.container:hover,
.container.selected {
border-color: var(--border-color-accent);
}
.table {
.container.table {
margin: 0 auto;
width: var(--size-20);
height: var(--size-20);
object-fit: cover;
}

.gallery {
.container.gallery {
height: var(--size-20);
max-height: var(--size-20);
object-fit: cover;
}
Expand Down
3 changes: 2 additions & 1 deletion js/video/package.json
Expand Up @@ -13,7 +13,8 @@
"@gradio/image": "workspace:^",
"@gradio/statustracker": "workspace:^",
"@gradio/upload": "workspace:^",
"@gradio/utils": "workspace:^"
"@gradio/utils": "workspace:^",
"@gradio/wasm": "workspace:^"
},
"main_changeset": true,
"exports": {
Expand Down
37 changes: 19 additions & 18 deletions js/video/shared/Player.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { Play, Pause, Maximise, Undo } from "@gradio/icons";
import { loaded } from "./utils";
import Video from "./Video.svelte";

export let src: string;
export let subtitle: string | null = null;
Expand Down Expand Up @@ -83,23 +83,24 @@
</script>

<div class="wrap">
<video
{src}
preload="auto"
on:click={play_pause}
on:play
on:pause
on:ended={handle_end}
bind:currentTime={time}
bind:duration
bind:paused
bind:this={video}
class:mirror
use:loaded={{ autoplay }}
data-testid={`${label}-player`}
>
<track kind="captions" src={subtitle} default />
</video>
<div class:mirror>
<Video
{src}
preload="auto"
{autoplay}
on:click={play_pause}
on:play
on:pause
on:ended={handle_end}
bind:currentTime={time}
bind:duration
bind:paused
bind:node={video}
data-testid={`${label}-player`}
>
<track kind="captions" src={subtitle} default />
</Video>
</div>

<div class="controls">
<div class="inner">
Expand Down