Commit
·
df60b7b
1
Parent(s):
4712d9f
update component's frontend
Browse filesNow the component is able to display each sources and corresponding
annotation separately
- sourceviewer/frontend/Index.svelte +7 -20
- sourceviewer/frontend/interactive/InteractiveAudio.svelte +8 -8
- sourceviewer/frontend/package-lock.json +5 -4
- sourceviewer/frontend/package.json +3 -3
- sourceviewer/frontend/player/AudioPlayer.svelte +29 -15
- sourceviewer/frontend/shared/WaveformControls.svelte +0 -128
- sourceviewer/frontend/shared/WaveformRecordControls.svelte +0 -2
- sourceviewer/frontend/shared/types.ts +6 -0
- sourceviewer/frontend/static/StaticAudio.svelte +6 -4
sourceviewer/frontend/Index.svelte
CHANGED
@@ -10,13 +10,13 @@
|
|
10 |
import InteractiveAudio from "./interactive/InteractiveAudio.svelte";
|
11 |
import { StatusTracker } from "@gradio/statustracker";
|
12 |
import { Block, UploadText } from "@gradio/atoms";
|
13 |
-
import type { WaveformOptions } from "./shared/types";
|
14 |
|
15 |
export let elem_id = "";
|
16 |
export let elem_classes: string[] = [];
|
17 |
export let visible = true;
|
18 |
export let interactive: boolean;
|
19 |
-
export let value: null | FileData = null;
|
20 |
export let sources:
|
21 |
| ["microphone"]
|
22 |
| ["upload"]
|
@@ -54,11 +54,11 @@
|
|
54 |
share: ShareData;
|
55 |
}>;
|
56 |
|
57 |
-
let old_value: null | FileData = null;
|
58 |
|
59 |
let active_source: "microphone" | "upload";
|
60 |
|
61 |
-
let initial_value: null | FileData = value;
|
62 |
|
63 |
$: if (value && initial_value === null) {
|
64 |
initial_value = value;
|
@@ -105,23 +105,10 @@
|
|
105 |
normalize: true,
|
106 |
minPxPerSec: 20,
|
107 |
mediaControls: waveform_options.show_controls,
|
108 |
-
sampleRate: waveform_options.sample_rate || 44100
|
|
|
109 |
};
|
110 |
|
111 |
-
const trim_region_settings = {
|
112 |
-
color: waveform_options.trim_region_color,
|
113 |
-
drag: true,
|
114 |
-
resize: true
|
115 |
-
};
|
116 |
-
|
117 |
-
function set_trim_region_colour(): void {
|
118 |
-
document.documentElement.style.setProperty(
|
119 |
-
"--trim-region-color",
|
120 |
-
trim_region_settings.color || color_accent
|
121 |
-
);
|
122 |
-
}
|
123 |
-
|
124 |
-
set_trim_region_colour();
|
125 |
|
126 |
function handle_error({ detail }: CustomEvent<string>): void {
|
127 |
const [level, status] = detail.includes("Invalid file type")
|
@@ -160,6 +147,7 @@
|
|
160 |
{show_share_button}
|
161 |
{value}
|
162 |
{label}
|
|
|
163 |
{waveform_settings}
|
164 |
{waveform_options}
|
165 |
{editable}
|
@@ -220,7 +208,6 @@
|
|
220 |
i18n={gradio.i18n}
|
221 |
{waveform_settings}
|
222 |
{waveform_options}
|
223 |
-
{trim_region_settings}
|
224 |
>
|
225 |
<UploadText i18n={gradio.i18n} type="audio" />
|
226 |
</InteractiveAudio>
|
|
|
10 |
import InteractiveAudio from "./interactive/InteractiveAudio.svelte";
|
11 |
import { StatusTracker } from "@gradio/statustracker";
|
12 |
import { Block, UploadText } from "@gradio/atoms";
|
13 |
+
import type { WaveformOptions, Segment } from "./shared/types";
|
14 |
|
15 |
export let elem_id = "";
|
16 |
export let elem_classes: string[] = [];
|
17 |
export let visible = true;
|
18 |
export let interactive: boolean;
|
19 |
+
export let value: null | {"segments": Segment[], "sources_file": FileData} = null;
|
20 |
export let sources:
|
21 |
| ["microphone"]
|
22 |
| ["upload"]
|
|
|
54 |
share: ShareData;
|
55 |
}>;
|
56 |
|
57 |
+
let old_value: null | {"segments": Segment[], "sources_file": FileData} = null;
|
58 |
|
59 |
let active_source: "microphone" | "upload";
|
60 |
|
61 |
+
let initial_value: null | {"segments": Segment[], "sources_file": FileData} = value;
|
62 |
|
63 |
$: if (value && initial_value === null) {
|
64 |
initial_value = value;
|
|
|
105 |
normalize: true,
|
106 |
minPxPerSec: 20,
|
107 |
mediaControls: waveform_options.show_controls,
|
108 |
+
sampleRate: waveform_options.sample_rate || 44100,
|
109 |
+
splitChannels: true,
|
110 |
};
|
111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
|
113 |
function handle_error({ detail }: CustomEvent<string>): void {
|
114 |
const [level, status] = detail.includes("Invalid file type")
|
|
|
147 |
{show_share_button}
|
148 |
{value}
|
149 |
{label}
|
150 |
+
{root}
|
151 |
{waveform_settings}
|
152 |
{waveform_options}
|
153 |
{editable}
|
|
|
208 |
i18n={gradio.i18n}
|
209 |
{waveform_settings}
|
210 |
{waveform_options}
|
|
|
211 |
>
|
212 |
<UploadText i18n={gradio.i18n} type="audio" />
|
213 |
</InteractiveAudio>
|
sourceviewer/frontend/interactive/InteractiveAudio.svelte
CHANGED
@@ -18,7 +18,7 @@
|
|
18 |
import { SelectSource } from "@gradio/atoms";
|
19 |
import type { WaveformOptions } from "../shared/types";
|
20 |
|
21 |
-
export let value: null | FileData = null;
|
22 |
export let label: string;
|
23 |
export let root: string;
|
24 |
export let show_label = true;
|
@@ -32,7 +32,6 @@
|
|
32 |
export let streaming = false;
|
33 |
export let i18n: I18nFormatter;
|
34 |
export let waveform_settings: Record<string, any>;
|
35 |
-
export let trim_region_settings = {};
|
36 |
export let waveform_options: WaveformOptions = {};
|
37 |
export let dragging: boolean;
|
38 |
export let active_source: "microphone" | "upload";
|
@@ -74,7 +73,7 @@
|
|
74 |
}
|
75 |
|
76 |
const dispatch = createEventDispatcher<{
|
77 |
-
change:
|
78 |
stream: FileData;
|
79 |
edit: never;
|
80 |
play: never;
|
@@ -96,7 +95,7 @@
|
|
96 |
): Promise<void> => {
|
97 |
let _audio_blob = new File(blobs, "audio.wav");
|
98 |
const val = await prepare_files([_audio_blob], event === "stream");
|
99 |
-
value = (
|
100 |
(await upload(val, root, undefined, upload_fn))?.filter(
|
101 |
Boolean
|
102 |
) as FileData[]
|
@@ -193,8 +192,9 @@
|
|
193 |
}
|
194 |
|
195 |
function handle_load({ detail }: { detail: FileData }): void {
|
196 |
-
value =
|
197 |
-
|
|
|
198 |
dispatch("upload", detail);
|
199 |
}
|
200 |
|
@@ -264,7 +264,7 @@
|
|
264 |
{i18n}
|
265 |
on:clear={clear}
|
266 |
on:edit={() => (mode = "edit")}
|
267 |
-
download={show_download_button ? value.url : null}
|
268 |
absolute={true}
|
269 |
/>
|
270 |
|
@@ -272,11 +272,11 @@
|
|
272 |
bind:mode
|
273 |
{value}
|
274 |
{label}
|
|
|
275 |
{i18n}
|
276 |
{dispatch_blob}
|
277 |
{waveform_settings}
|
278 |
{waveform_options}
|
279 |
-
{trim_region_settings}
|
280 |
{handle_reset_value}
|
281 |
{editable}
|
282 |
interactive
|
|
|
18 |
import { SelectSource } from "@gradio/atoms";
|
19 |
import type { WaveformOptions } from "../shared/types";
|
20 |
|
21 |
+
export let value: null | {"segments": Segment[], "sources_file": FileData} = null;
|
22 |
export let label: string;
|
23 |
export let root: string;
|
24 |
export let show_label = true;
|
|
|
32 |
export let streaming = false;
|
33 |
export let i18n: I18nFormatter;
|
34 |
export let waveform_settings: Record<string, any>;
|
|
|
35 |
export let waveform_options: WaveformOptions = {};
|
36 |
export let dragging: boolean;
|
37 |
export let active_source: "microphone" | "upload";
|
|
|
73 |
}
|
74 |
|
75 |
const dispatch = createEventDispatcher<{
|
76 |
+
change: typeof value;
|
77 |
stream: FileData;
|
78 |
edit: never;
|
79 |
play: never;
|
|
|
95 |
): Promise<void> => {
|
96 |
let _audio_blob = new File(blobs, "audio.wav");
|
97 |
const val = await prepare_files([_audio_blob], event === "stream");
|
98 |
+
value.sources_file = (
|
99 |
(await upload(val, root, undefined, upload_fn))?.filter(
|
100 |
Boolean
|
101 |
) as FileData[]
|
|
|
192 |
}
|
193 |
|
194 |
function handle_load({ detail }: { detail: FileData }): void {
|
195 |
+
value = {"segments": [], "sources_file": null}
|
196 |
+
value.sources_file = detail;
|
197 |
+
dispatch("change", value);
|
198 |
dispatch("upload", detail);
|
199 |
}
|
200 |
|
|
|
264 |
{i18n}
|
265 |
on:clear={clear}
|
266 |
on:edit={() => (mode = "edit")}
|
267 |
+
download={show_download_button ? value.sources_file.url : null}
|
268 |
absolute={true}
|
269 |
/>
|
270 |
|
|
|
272 |
bind:mode
|
273 |
{value}
|
274 |
{label}
|
275 |
+
{root}
|
276 |
{i18n}
|
277 |
{dispatch_blob}
|
278 |
{waveform_settings}
|
279 |
{waveform_options}
|
|
|
280 |
{handle_reset_value}
|
281 |
{editable}
|
282 |
interactive
|
sourceviewer/frontend/package-lock.json
CHANGED
@@ -22,7 +22,7 @@
|
|
22 |
"extendable-media-recorder-wav-encoder": "^7.0.76",
|
23 |
"resize-observer-polyfill": "^1.5.1",
|
24 |
"svelte-range-slider-pips": "^2.0.1",
|
25 |
-
"wavesurfer.js": "^7.
|
26 |
}
|
27 |
},
|
28 |
"node_modules/@ampproject/remapping": {
|
@@ -1258,9 +1258,10 @@
|
|
1258 |
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="
|
1259 |
},
|
1260 |
"node_modules/wavesurfer.js": {
|
1261 |
-
"version": "7.8.
|
1262 |
-
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.8.
|
1263 |
-
"integrity": "sha512-
|
|
|
1264 |
},
|
1265 |
"node_modules/worker-factory": {
|
1266 |
"version": "7.0.34",
|
|
|
22 |
"extendable-media-recorder-wav-encoder": "^7.0.76",
|
23 |
"resize-observer-polyfill": "^1.5.1",
|
24 |
"svelte-range-slider-pips": "^2.0.1",
|
25 |
+
"wavesurfer.js": "^7.8.9"
|
26 |
}
|
27 |
},
|
28 |
"node_modules/@ampproject/remapping": {
|
|
|
1258 |
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="
|
1259 |
},
|
1260 |
"node_modules/wavesurfer.js": {
|
1261 |
+
"version": "7.8.9",
|
1262 |
+
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.8.9.tgz",
|
1263 |
+
"integrity": "sha512-d7jDSrzNp3UErXWEKFlIYRdvzaMlxCoM6Or4qR8uURFp9smcxLXS81uatcs6aPX1OSJgEyyBuffMR5Io3Ft2FQ==",
|
1264 |
+
"license": "BSD-3-Clause"
|
1265 |
},
|
1266 |
"node_modules/worker-factory": {
|
1267 |
"version": "7.0.34",
|
sourceviewer/frontend/package.json
CHANGED
@@ -15,12 +15,12 @@
|
|
15 |
"@gradio/upload": "0.8.5",
|
16 |
"@gradio/utils": "0.3.2",
|
17 |
"@gradio/wasm": "0.10.0",
|
|
|
18 |
"extendable-media-recorder": "^9.0.0",
|
19 |
"extendable-media-recorder-wav-encoder": "^7.0.76",
|
20 |
"resize-observer-polyfill": "^1.5.1",
|
21 |
"svelte-range-slider-pips": "^2.0.1",
|
22 |
-
"wavesurfer.js": "^7.
|
23 |
-
"@types/wavesurfer.js": "^6.0.10"
|
24 |
},
|
25 |
"main_changeset": true,
|
26 |
"main": "index.ts",
|
@@ -30,4 +30,4 @@
|
|
30 |
"./shared": "./shared/index.ts",
|
31 |
"./package.json": "./package.json"
|
32 |
}
|
33 |
-
}
|
|
|
15 |
"@gradio/upload": "0.8.5",
|
16 |
"@gradio/utils": "0.3.2",
|
17 |
"@gradio/wasm": "0.10.0",
|
18 |
+
"@types/wavesurfer.js": "^6.0.10",
|
19 |
"extendable-media-recorder": "^9.0.0",
|
20 |
"extendable-media-recorder-wav-encoder": "^7.0.76",
|
21 |
"resize-observer-polyfill": "^1.5.1",
|
22 |
"svelte-range-slider-pips": "^2.0.1",
|
23 |
+
"wavesurfer.js": "^7.8.9"
|
|
|
24 |
},
|
25 |
"main_changeset": true,
|
26 |
"main": "index.ts",
|
|
|
30 |
"./shared": "./shared/index.ts",
|
31 |
"./package.json": "./package.json"
|
32 |
}
|
33 |
+
}
|
sourceviewer/frontend/player/AudioPlayer.svelte
CHANGED
@@ -3,17 +3,19 @@
|
|
3 |
import { Music } from "@gradio/icons";
|
4 |
import { format_time, type I18nFormatter } from "@gradio/utils";
|
5 |
import WaveSurfer from "wavesurfer.js";
|
|
|
6 |
import { skip_audio, process_audio } from "../shared/utils";
|
7 |
import WaveformControls from "../shared/WaveformControls.svelte";
|
8 |
import { Empty } from "@gradio/atoms";
|
9 |
import { resolve_wasm_src } from "@gradio/wasm/svelte";
|
10 |
import type { FileData } from "@gradio/client";
|
11 |
-
import type { WaveformOptions } from "../shared/types";
|
12 |
import { createEventDispatcher } from "svelte";
|
13 |
|
14 |
-
export let value: null | FileData
|
15 |
-
$: url = value?.url;
|
16 |
export let label: string;
|
|
|
17 |
export let i18n: I18nFormatter;
|
18 |
export let dispatch_blob: (
|
19 |
blobs: Uint8Array[] | Blob[],
|
@@ -21,7 +23,6 @@
|
|
21 |
) => Promise<void> = () => Promise.resolve();
|
22 |
export let interactive = false;
|
23 |
export let editable = true;
|
24 |
-
export let trim_region_settings = {};
|
25 |
export let waveform_settings: Record<string, any>;
|
26 |
export let waveform_options: WaveformOptions;
|
27 |
export let mode = "";
|
@@ -29,12 +30,15 @@
|
|
29 |
|
30 |
let container: HTMLDivElement;
|
31 |
let waveform: WaveSurfer | undefined;
|
|
|
32 |
let playing = false;
|
33 |
|
34 |
let timeRef: HTMLTimeElement;
|
35 |
let durationRef: HTMLTimeElement;
|
36 |
let audio_duration: number;
|
37 |
|
|
|
|
|
38 |
let trimDuration = 0;
|
39 |
|
40 |
let show_volume_slider = false;
|
@@ -52,11 +56,7 @@
|
|
52 |
container: container,
|
53 |
...waveform_settings
|
54 |
});
|
55 |
-
|
56 |
-
if (resolved_src && waveform) {
|
57 |
-
return waveform.load(resolved_src);
|
58 |
-
}
|
59 |
-
});
|
60 |
};
|
61 |
|
62 |
$: if (container !== undefined) {
|
@@ -69,6 +69,23 @@
|
|
69 |
$: waveform?.on("decode", (duration: any) => {
|
70 |
audio_duration = duration;
|
71 |
durationRef && (durationRef.textContent = format_time(duration));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
});
|
73 |
|
74 |
$: waveform?.on(
|
@@ -120,7 +137,7 @@
|
|
120 |
|
121 |
async function load_audio(data: string): Promise<void> {
|
122 |
await resolve_wasm_src(data).then((resolved_src) => {
|
123 |
-
if (!resolved_src || value?.is_stream) return;
|
124 |
return waveform?.load(resolved_src);
|
125 |
});
|
126 |
}
|
@@ -143,10 +160,10 @@
|
|
143 |
<Empty size="small">
|
144 |
<Music />
|
145 |
</Empty>
|
146 |
-
{:else if value.is_stream}
|
147 |
<audio
|
148 |
class="standard-player"
|
149 |
-
src={value.url}
|
150 |
controls
|
151 |
autoplay={waveform_settings.autoplay}
|
152 |
/>
|
@@ -171,20 +188,17 @@
|
|
171 |
|
172 |
{#if waveform}
|
173 |
<WaveformControls
|
174 |
-
{container}
|
175 |
{waveform}
|
176 |
{playing}
|
177 |
{audio_duration}
|
178 |
{i18n}
|
179 |
{interactive}
|
180 |
-
{handle_trim_audio}
|
181 |
bind:mode
|
182 |
bind:trimDuration
|
183 |
bind:show_volume_slider
|
184 |
show_redo={interactive}
|
185 |
{handle_reset_value}
|
186 |
{waveform_options}
|
187 |
-
{trim_region_settings}
|
188 |
{editable}
|
189 |
/>
|
190 |
{/if}
|
|
|
3 |
import { Music } from "@gradio/icons";
|
4 |
import { format_time, type I18nFormatter } from "@gradio/utils";
|
5 |
import WaveSurfer from "wavesurfer.js";
|
6 |
+
import RegionsPlugin, {type Region} from "wavesurfer.js/dist/plugins/regions";
|
7 |
import { skip_audio, process_audio } from "../shared/utils";
|
8 |
import WaveformControls from "../shared/WaveformControls.svelte";
|
9 |
import { Empty } from "@gradio/atoms";
|
10 |
import { resolve_wasm_src } from "@gradio/wasm/svelte";
|
11 |
import type { FileData } from "@gradio/client";
|
12 |
+
import type { WaveformOptions, Segment } from "../shared/types";
|
13 |
import { createEventDispatcher } from "svelte";
|
14 |
|
15 |
+
export let value: null | {"segments": Segment[], "sources_file": FileData}= null;
|
16 |
+
$: url = value?.sources_file.url;
|
17 |
export let label: string;
|
18 |
+
export let root: string;
|
19 |
export let i18n: I18nFormatter;
|
20 |
export let dispatch_blob: (
|
21 |
blobs: Uint8Array[] | Blob[],
|
|
|
23 |
) => Promise<void> = () => Promise.resolve();
|
24 |
export let interactive = false;
|
25 |
export let editable = true;
|
|
|
26 |
export let waveform_settings: Record<string, any>;
|
27 |
export let waveform_options: WaveformOptions;
|
28 |
export let mode = "";
|
|
|
30 |
|
31 |
let container: HTMLDivElement;
|
32 |
let waveform: WaveSurfer | undefined;
|
33 |
+
let wsRegion: RegionsPlugin | undefined;
|
34 |
let playing = false;
|
35 |
|
36 |
let timeRef: HTMLTimeElement;
|
37 |
let durationRef: HTMLTimeElement;
|
38 |
let audio_duration: number;
|
39 |
|
40 |
+
let colors: string[] = ["red", "green", "blue", "yellow", "magenta", "cyan"];
|
41 |
+
|
42 |
let trimDuration = 0;
|
43 |
|
44 |
let show_volume_slider = false;
|
|
|
56 |
container: container,
|
57 |
...waveform_settings
|
58 |
});
|
59 |
+
waveform.load(root + `/file=${value.sources_file.path}`)
|
|
|
|
|
|
|
|
|
60 |
};
|
61 |
|
62 |
$: if (container !== undefined) {
|
|
|
69 |
$: waveform?.on("decode", (duration: any) => {
|
70 |
audio_duration = duration;
|
71 |
durationRef && (durationRef.textContent = format_time(duration));
|
72 |
+
|
73 |
+
if(!wsRegion){
|
74 |
+
wsRegion = waveform.registerPlugin(RegionsPlugin.create())
|
75 |
+
value.segments.forEach(segment => {
|
76 |
+
const region = wsRegion.addRegion({
|
77 |
+
start: segment.start,
|
78 |
+
end: segment.end,
|
79 |
+
channelIdx: segment.channel,
|
80 |
+
drag: false,
|
81 |
+
resize: false,
|
82 |
+
color: colors[segment.channel % colors.length],
|
83 |
+
});
|
84 |
+
console.log(region.color)
|
85 |
+
const regionHeight = 100 / waveform.getDecodedData().numberOfChannels;
|
86 |
+
region.element.style.cssText += `height: ${regionHeight}% !important;`
|
87 |
+
});
|
88 |
+
}
|
89 |
});
|
90 |
|
91 |
$: waveform?.on(
|
|
|
137 |
|
138 |
async function load_audio(data: string): Promise<void> {
|
139 |
await resolve_wasm_src(data).then((resolved_src) => {
|
140 |
+
if (!resolved_src || value?.sources_file.is_stream) return;
|
141 |
return waveform?.load(resolved_src);
|
142 |
});
|
143 |
}
|
|
|
160 |
<Empty size="small">
|
161 |
<Music />
|
162 |
</Empty>
|
163 |
+
{:else if value.sources_file.is_stream}
|
164 |
<audio
|
165 |
class="standard-player"
|
166 |
+
src={value.sources_file.url}
|
167 |
controls
|
168 |
autoplay={waveform_settings.autoplay}
|
169 |
/>
|
|
|
188 |
|
189 |
{#if waveform}
|
190 |
<WaveformControls
|
|
|
191 |
{waveform}
|
192 |
{playing}
|
193 |
{audio_duration}
|
194 |
{i18n}
|
195 |
{interactive}
|
|
|
196 |
bind:mode
|
197 |
bind:trimDuration
|
198 |
bind:show_volume_slider
|
199 |
show_redo={interactive}
|
200 |
{handle_reset_value}
|
201 |
{waveform_options}
|
|
|
202 |
{editable}
|
203 |
/>
|
204 |
{/if}
|
sourceviewer/frontend/shared/WaveformControls.svelte
CHANGED
@@ -16,12 +16,9 @@
|
|
16 |
export let playing: boolean;
|
17 |
export let show_redo = false;
|
18 |
export let interactive = false;
|
19 |
-
export let handle_trim_audio: (start: number, end: number) => void;
|
20 |
export let mode = "";
|
21 |
-
export let container: HTMLDivElement;
|
22 |
export let handle_reset_value: () => void;
|
23 |
export let waveform_options: WaveformOptions = {};
|
24 |
-
export let trim_region_settings: WaveformOptions = {};
|
25 |
export let show_volume_slider = false;
|
26 |
export let editable = true;
|
27 |
|
@@ -30,94 +27,10 @@
|
|
30 |
let playbackSpeeds = [0.5, 1, 1.5, 2];
|
31 |
let playbackSpeed = playbackSpeeds[1];
|
32 |
|
33 |
-
let trimRegion: RegionsPlugin;
|
34 |
let activeRegion: Region | null = null;
|
35 |
|
36 |
-
let leftRegionHandle: HTMLDivElement | null;
|
37 |
-
let rightRegionHandle: HTMLDivElement | null;
|
38 |
-
let activeHandle = "";
|
39 |
-
|
40 |
let currentVolume = 1;
|
41 |
|
42 |
-
$: trimRegion = waveform.registerPlugin(RegionsPlugin.create());
|
43 |
-
|
44 |
-
$: trimRegion?.on("region-out", (region) => {
|
45 |
-
region.play();
|
46 |
-
});
|
47 |
-
|
48 |
-
$: trimRegion?.on("region-updated", (region) => {
|
49 |
-
trimDuration = region.end - region.start;
|
50 |
-
});
|
51 |
-
|
52 |
-
$: trimRegion?.on("region-clicked", (region, e) => {
|
53 |
-
e.stopPropagation(); // prevent triggering a click on the waveform
|
54 |
-
activeRegion = region;
|
55 |
-
region.play();
|
56 |
-
});
|
57 |
-
|
58 |
-
const addTrimRegion = (): void => {
|
59 |
-
activeRegion = trimRegion.addRegion({
|
60 |
-
start: audio_duration / 4,
|
61 |
-
end: audio_duration / 2,
|
62 |
-
...trim_region_settings
|
63 |
-
});
|
64 |
-
|
65 |
-
trimDuration = activeRegion.end - activeRegion.start;
|
66 |
-
};
|
67 |
-
|
68 |
-
$: if (activeRegion) {
|
69 |
-
const shadowRoot = container.children[0]!.shadowRoot!;
|
70 |
-
|
71 |
-
rightRegionHandle = shadowRoot.querySelector('[data-resize="right"]');
|
72 |
-
leftRegionHandle = shadowRoot.querySelector('[data-resize="left"]');
|
73 |
-
|
74 |
-
if (leftRegionHandle && rightRegionHandle) {
|
75 |
-
leftRegionHandle.setAttribute("role", "button");
|
76 |
-
rightRegionHandle.setAttribute("role", "button");
|
77 |
-
leftRegionHandle?.setAttribute("aria-label", "Drag to adjust start time");
|
78 |
-
rightRegionHandle?.setAttribute("aria-label", "Drag to adjust end time");
|
79 |
-
leftRegionHandle?.setAttribute("tabindex", "0");
|
80 |
-
rightRegionHandle?.setAttribute("tabindex", "0");
|
81 |
-
|
82 |
-
leftRegionHandle.addEventListener("focus", () => {
|
83 |
-
if (trimRegion) activeHandle = "left";
|
84 |
-
});
|
85 |
-
|
86 |
-
rightRegionHandle.addEventListener("focus", () => {
|
87 |
-
if (trimRegion) activeHandle = "right";
|
88 |
-
});
|
89 |
-
}
|
90 |
-
}
|
91 |
-
|
92 |
-
const trimAudio = (): void => {
|
93 |
-
if (waveform && trimRegion) {
|
94 |
-
if (activeRegion) {
|
95 |
-
const start = activeRegion.start;
|
96 |
-
const end = activeRegion.end;
|
97 |
-
handle_trim_audio(start, end);
|
98 |
-
mode = "";
|
99 |
-
activeRegion = null;
|
100 |
-
}
|
101 |
-
}
|
102 |
-
};
|
103 |
-
|
104 |
-
const clearRegions = (): void => {
|
105 |
-
trimRegion?.getRegions().forEach((region) => {
|
106 |
-
region.remove();
|
107 |
-
});
|
108 |
-
trimRegion?.clearRegions();
|
109 |
-
};
|
110 |
-
|
111 |
-
const toggleTrimmingMode = (): void => {
|
112 |
-
clearRegions();
|
113 |
-
if (mode === "edit") {
|
114 |
-
mode = "";
|
115 |
-
} else {
|
116 |
-
mode = "edit";
|
117 |
-
addTrimRegion();
|
118 |
-
}
|
119 |
-
};
|
120 |
-
|
121 |
const adjustRegionHandles = (handle: string, key: string): void => {
|
122 |
let newStart;
|
123 |
let newEnd;
|
@@ -149,14 +62,6 @@
|
|
149 |
trimDuration = activeRegion.end - activeRegion.start;
|
150 |
};
|
151 |
|
152 |
-
$: trimRegion &&
|
153 |
-
window.addEventListener("keydown", (e) => {
|
154 |
-
if (e.key === "ArrowLeft") {
|
155 |
-
adjustRegionHandles(activeHandle, "ArrowLeft");
|
156 |
-
} else if (e.key === "ArrowRight") {
|
157 |
-
adjustRegionHandles(activeHandle, "ArrowRight");
|
158 |
-
}
|
159 |
-
});
|
160 |
</script>
|
161 |
|
162 |
<div class="controls" data-testid="waveform-controls">
|
@@ -246,27 +151,12 @@
|
|
246 |
aria-label="Reset audio"
|
247 |
on:click={() => {
|
248 |
handle_reset_value();
|
249 |
-
clearRegions();
|
250 |
mode = "";
|
251 |
}}
|
252 |
>
|
253 |
<Undo />
|
254 |
</button>
|
255 |
{/if}
|
256 |
-
|
257 |
-
{#if mode === ""}
|
258 |
-
<button
|
259 |
-
class="action icon"
|
260 |
-
aria-label="Trim audio to selection"
|
261 |
-
on:click={toggleTrimmingMode}
|
262 |
-
>
|
263 |
-
<Trim />
|
264 |
-
</button>
|
265 |
-
{:else}
|
266 |
-
<button class="text-button" on:click={trimAudio}>Trim</button>
|
267 |
-
<button class="text-button" on:click={toggleTrimmingMode}>Cancel</button
|
268 |
-
>
|
269 |
-
{/if}
|
270 |
{/if}
|
271 |
</div>
|
272 |
</div>
|
@@ -278,24 +168,6 @@
|
|
278 |
align-items: center;
|
279 |
grid-area: editing;
|
280 |
}
|
281 |
-
.text-button {
|
282 |
-
border: 1px solid var(--neutral-400);
|
283 |
-
border-radius: var(--radius-sm);
|
284 |
-
font-weight: 300;
|
285 |
-
font-size: var(--size-3);
|
286 |
-
text-align: center;
|
287 |
-
color: var(--neutral-400);
|
288 |
-
height: var(--size-5);
|
289 |
-
font-weight: bold;
|
290 |
-
padding: 0 5px;
|
291 |
-
margin-left: 5px;
|
292 |
-
}
|
293 |
-
|
294 |
-
.text-button:hover,
|
295 |
-
.text-button:focus {
|
296 |
-
color: var(--color-accent);
|
297 |
-
border-color: var(--color-accent);
|
298 |
-
}
|
299 |
|
300 |
.controls {
|
301 |
display: grid;
|
|
|
16 |
export let playing: boolean;
|
17 |
export let show_redo = false;
|
18 |
export let interactive = false;
|
|
|
19 |
export let mode = "";
|
|
|
20 |
export let handle_reset_value: () => void;
|
21 |
export let waveform_options: WaveformOptions = {};
|
|
|
22 |
export let show_volume_slider = false;
|
23 |
export let editable = true;
|
24 |
|
|
|
27 |
let playbackSpeeds = [0.5, 1, 1.5, 2];
|
28 |
let playbackSpeed = playbackSpeeds[1];
|
29 |
|
|
|
30 |
let activeRegion: Region | null = null;
|
31 |
|
|
|
|
|
|
|
|
|
32 |
let currentVolume = 1;
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
const adjustRegionHandles = (handle: string, key: string): void => {
|
35 |
let newStart;
|
36 |
let newEnd;
|
|
|
62 |
trimDuration = activeRegion.end - activeRegion.start;
|
63 |
};
|
64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
</script>
|
66 |
|
67 |
<div class="controls" data-testid="waveform-controls">
|
|
|
151 |
aria-label="Reset audio"
|
152 |
on:click={() => {
|
153 |
handle_reset_value();
|
|
|
154 |
mode = "";
|
155 |
}}
|
156 |
>
|
157 |
<Undo />
|
158 |
</button>
|
159 |
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
{/if}
|
161 |
</div>
|
162 |
</div>
|
|
|
168 |
align-items: center;
|
169 |
grid-area: editing;
|
170 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
.controls {
|
173 |
display: grid;
|
sourceviewer/frontend/shared/WaveformRecordControls.svelte
CHANGED
@@ -245,7 +245,6 @@
|
|
245 |
|
246 |
:global(::part(region)) {
|
247 |
border-radius: var(--radius-md);
|
248 |
-
height: 98% !important;
|
249 |
border: 1px solid var(--trim-region-color);
|
250 |
background-color: unset;
|
251 |
border-width: 1px 3px;
|
@@ -258,7 +257,6 @@
|
|
258 |
left: 0;
|
259 |
width: 100%;
|
260 |
height: 100%;
|
261 |
-
background: var(--trim-region-color);
|
262 |
opacity: 0.2;
|
263 |
border-radius: var(--radius-md);
|
264 |
}
|
|
|
245 |
|
246 |
:global(::part(region)) {
|
247 |
border-radius: var(--radius-md);
|
|
|
248 |
border: 1px solid var(--trim-region-color);
|
249 |
background-color: unset;
|
250 |
border-width: 1px 3px;
|
|
|
257 |
left: 0;
|
258 |
width: 100%;
|
259 |
height: 100%;
|
|
|
260 |
opacity: 0.2;
|
261 |
border-radius: var(--radius-md);
|
262 |
}
|
sourceviewer/frontend/shared/types.ts
CHANGED
@@ -7,3 +7,9 @@ export type WaveformOptions = {
|
|
7 |
show_recording_waveform?: boolean;
|
8 |
sample_rate?: number;
|
9 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
show_recording_waveform?: boolean;
|
8 |
sample_rate?: number;
|
9 |
};
|
10 |
+
|
11 |
+
export type Segment = {
|
12 |
+
start: number;
|
13 |
+
end: number;
|
14 |
+
channel: number;
|
15 |
+
}
|
sourceviewer/frontend/static/StaticAudio.svelte
CHANGED
@@ -8,10 +8,11 @@
|
|
8 |
import { createEventDispatcher } from "svelte";
|
9 |
import type { FileData } from "@gradio/client";
|
10 |
import { DownloadLink } from "@gradio/wasm/svelte";
|
11 |
-
import type { WaveformOptions } from "../shared/types";
|
12 |
|
13 |
-
export let value: null | FileData = null;
|
14 |
export let label: string;
|
|
|
15 |
export let show_label = true;
|
16 |
export let show_download_button = true;
|
17 |
export let show_share_button = false;
|
@@ -21,7 +22,7 @@
|
|
21 |
export let editable = true;
|
22 |
|
23 |
const dispatch = createEventDispatcher<{
|
24 |
-
change:
|
25 |
play: undefined;
|
26 |
pause: undefined;
|
27 |
end: undefined;
|
@@ -41,7 +42,7 @@
|
|
41 |
{#if value !== null}
|
42 |
<div class="icon-buttons">
|
43 |
{#if show_download_button}
|
44 |
-
<DownloadLink href={value.url} download={value.orig_name || value.path}>
|
45 |
<IconButton Icon={Download} label={i18n("common.download")} />
|
46 |
</DownloadLink>
|
47 |
{/if}
|
@@ -63,6 +64,7 @@
|
|
63 |
<AudioPlayer
|
64 |
{value}
|
65 |
{label}
|
|
|
66 |
{i18n}
|
67 |
{waveform_settings}
|
68 |
{waveform_options}
|
|
|
8 |
import { createEventDispatcher } from "svelte";
|
9 |
import type { FileData } from "@gradio/client";
|
10 |
import { DownloadLink } from "@gradio/wasm/svelte";
|
11 |
+
import type { WaveformOptions, Segment } from "../shared/types";
|
12 |
|
13 |
+
export let value: null | {"segments": Segment[], "sources_file": FileData} = null;
|
14 |
export let label: string;
|
15 |
+
export let root: string;
|
16 |
export let show_label = true;
|
17 |
export let show_download_button = true;
|
18 |
export let show_share_button = false;
|
|
|
22 |
export let editable = true;
|
23 |
|
24 |
const dispatch = createEventDispatcher<{
|
25 |
+
change: typeof value;
|
26 |
play: undefined;
|
27 |
pause: undefined;
|
28 |
end: undefined;
|
|
|
42 |
{#if value !== null}
|
43 |
<div class="icon-buttons">
|
44 |
{#if show_download_button}
|
45 |
+
<DownloadLink href={value.sources_file.url} download={value.sources_file.orig_name || value.sources_file.path}>
|
46 |
<IconButton Icon={Download} label={i18n("common.download")} />
|
47 |
</DownloadLink>
|
48 |
{/if}
|
|
|
64 |
<AudioPlayer
|
65 |
{value}
|
66 |
{label}
|
67 |
+
{root}
|
68 |
{i18n}
|
69 |
{waveform_settings}
|
70 |
{waveform_options}
|