mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-06 12:30:29 +08:00
More gr.render content (#8418)
* changes * Update 04_dynamic-apps-with-render-decorator.md --------- Co-authored-by: Ali Abid <aliabid94@gmail.com>
This commit is contained in:
parent
5c6d4c4083
commit
1a124d95d7
@ -1 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_mixer"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " track_count = gr.State(1)\n", " add_track_btn = gr.Button(\"Add Track\")\n", "\n", " @gr.render(inputs=track_count)\n", " def render_tracks(count):\n", " tracks = []\n", " with gr.Row():\n", " for i in range(count):\n", " with gr.Column(variant=\"panel\", scale=0):\n", " track_name = gr.Textbox(placeholder=\"Track Name\", key=f\"name-{i}\", show_label=False)\n", " track_audio = gr.Audio(label=f\"Track {i}\", key=f\"track-{i}\")\n", " track_volume = gr.Slider(0, 1, step=0.01, label=\"Volume\", key=f\"volume-{i}\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_mixer"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import numpy as np\n", "\n", "with gr.Blocks() as demo:\n", " track_count = gr.State(1)\n", " add_track_btn = gr.Button(\"Add Track\")\n", "\n", " add_track_btn.click(lambda count: count + 1, track_count, track_count)\n", "\n", " @gr.render(inputs=track_count)\n", " def render_tracks(count):\n", " audios = []\n", " volumes = []\n", " with gr.Row():\n", " for i in range(count):\n", " with gr.Column(variant=\"panel\", min_width=200):\n", " gr.Textbox(placeholder=\"Track Name\", key=f\"name-{i}\", show_label=False)\n", " track_audio = gr.Audio(label=f\"Track {i}\", key=f\"track-{i}\")\n", " track_volume = gr.Slider(0, 100, value=100, label=\"Volume\", key=f\"volume-{i}\")\n", " audios.append(track_audio)\n", " volumes.append(track_volume)\n", "\n", " def merge(data):\n", " sr, output = None, None\n", " for audio, volume in zip(audios, volumes):\n", " sr, audio_val = data[audio]\n", " volume_val = data[volume]\n", " final_track = audio_val * (volume_val / 100)\n", " if output is None:\n", " output = final_track\n", " else:\n", " min_shape = tuple(min(s1, s2) for s1, s2 in zip(output.shape, final_track.shape))\n", " trimmed_output = output[:min_shape[0], ...][:, :min_shape[1], ...] if output.ndim > 1 else output[:min_shape[0]]\n", " trimmed_final = final_track[:min_shape[0], ...][:, :final_track[1], ...] if final_track.ndim > 1 else final_track[:min_shape[0]]\n", " output += trimmed_output + trimmed_final\n", " return (sr, output)\n", " \n", " merge_btn.click(merge, set(audios + volumes), output_audio)\n", "\n", " merge_btn = gr.Button(\"Merge Tracks\")\n", " output_audio = gr.Audio(label=\"Output\")\n", " \n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
@ -1,18 +1,44 @@
|
||||
import gradio as gr
|
||||
import numpy as np
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
track_count = gr.State(1)
|
||||
add_track_btn = gr.Button("Add Track")
|
||||
|
||||
add_track_btn.click(lambda count: count + 1, track_count, track_count)
|
||||
|
||||
@gr.render(inputs=track_count)
|
||||
def render_tracks(count):
|
||||
tracks = []
|
||||
audios = []
|
||||
volumes = []
|
||||
with gr.Row():
|
||||
for i in range(count):
|
||||
with gr.Column(variant="panel", scale=0):
|
||||
track_name = gr.Textbox(placeholder="Track Name", key=f"name-{i}", show_label=False)
|
||||
with gr.Column(variant="panel", min_width=200):
|
||||
gr.Textbox(placeholder="Track Name", key=f"name-{i}", show_label=False)
|
||||
track_audio = gr.Audio(label=f"Track {i}", key=f"track-{i}")
|
||||
track_volume = gr.Slider(0, 1, step=0.01, label="Volume", key=f"volume-{i}")
|
||||
track_volume = gr.Slider(0, 100, value=100, label="Volume", key=f"volume-{i}")
|
||||
audios.append(track_audio)
|
||||
volumes.append(track_volume)
|
||||
|
||||
def merge(data):
|
||||
sr, output = None, None
|
||||
for audio, volume in zip(audios, volumes):
|
||||
sr, audio_val = data[audio]
|
||||
volume_val = data[volume]
|
||||
final_track = audio_val * (volume_val / 100)
|
||||
if output is None:
|
||||
output = final_track
|
||||
else:
|
||||
min_shape = tuple(min(s1, s2) for s1, s2 in zip(output.shape, final_track.shape))
|
||||
trimmed_output = output[:min_shape[0], ...][:, :min_shape[1], ...] if output.ndim > 1 else output[:min_shape[0]]
|
||||
trimmed_final = final_track[:min_shape[0], ...][:, :final_track[1], ...] if final_track.ndim > 1 else final_track[:min_shape[0]]
|
||||
output += trimmed_output + trimmed_final
|
||||
return (sr, output)
|
||||
|
||||
merge_btn.click(merge, set(audios + volumes), output_audio)
|
||||
|
||||
merge_btn = gr.Button("Merge Tracks")
|
||||
output_audio = gr.Audio(label="Output")
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
1
demo/todo_list/run.ipynb
Normal file
1
demo/todo_list/run.ipynb
Normal file
@ -0,0 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: todo_list"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " \n", " tasks = gr.State([])\n", " new_task = gr.Textbox(label=\"Task Name\", autofocus=True)\n", "\n", " def add_task(tasks, new_task_name):\n", " return tasks + [{\"name\": new_task_name, \"complete\": False}], \"\"\n", "\n", " new_task.submit(add_task, [tasks, new_task], [tasks, new_task])\n", "\n", " @gr.render(inputs=tasks)\n", " def render_todos(task_list):\n", " complete = [task for task in task_list if task[\"complete\"]]\n", " incomplete = [task for task in task_list if not task[\"complete\"]]\n", " gr.Markdown(f\"### Incomplete Tasks ({len(incomplete)})\")\n", " for task in incomplete:\n", " with gr.Row():\n", " gr.Textbox(task['name'], show_label=False, container=False)\n", " done_btn = gr.Button(\"Done\", scale=0)\n", " def mark_done(task=task):\n", " _task_list = task_list[:]\n", " _task_list[task_list.index(task)] = {\"name\": task[\"name\"], \"complete\": True}\n", " return _task_list\n", " done_btn.click(mark_done, None, [tasks])\n", "\n", " delete_btn = gr.Button(\"Delete\", scale=0, variant=\"stop\")\n", " def delete(task=task):\n", " task_index = task_list.index(task)\n", " return task_list[:task_index] + task_list[task_index+1:]\n", " delete_btn.click(delete, None, [tasks])\n", "\n", " gr.Markdown(f\"### Complete Tasks ({len(complete)})\")\n", " for task in complete:\n", " gr.Textbox(task['name'], show_label=False, container=False)\n", "\n", "\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
42
demo/todo_list/run.py
Normal file
42
demo/todo_list/run.py
Normal file
@ -0,0 +1,42 @@
|
||||
import gradio as gr
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
|
||||
tasks = gr.State([])
|
||||
new_task = gr.Textbox(label="Task Name", autofocus=True)
|
||||
|
||||
def add_task(tasks, new_task_name):
|
||||
return tasks + [{"name": new_task_name, "complete": False}], ""
|
||||
|
||||
new_task.submit(add_task, [tasks, new_task], [tasks, new_task])
|
||||
|
||||
@gr.render(inputs=tasks)
|
||||
def render_todos(task_list):
|
||||
complete = [task for task in task_list if task["complete"]]
|
||||
incomplete = [task for task in task_list if not task["complete"]]
|
||||
gr.Markdown(f"### Incomplete Tasks ({len(incomplete)})")
|
||||
for task in incomplete:
|
||||
with gr.Row():
|
||||
gr.Textbox(task['name'], show_label=False, container=False)
|
||||
done_btn = gr.Button("Done", scale=0)
|
||||
def mark_done(task=task):
|
||||
_task_list = task_list[:]
|
||||
_task_list[task_list.index(task)] = {"name": task["name"], "complete": True}
|
||||
return _task_list
|
||||
done_btn.click(mark_done, None, [tasks])
|
||||
|
||||
delete_btn = gr.Button("Delete", scale=0, variant="stop")
|
||||
def delete(task=task):
|
||||
task_index = task_list.index(task)
|
||||
return task_list[:task_index] + task_list[task_index+1:]
|
||||
delete_btn.click(delete, None, [tasks])
|
||||
|
||||
gr.Markdown(f"### Complete Tasks ({len(complete)})")
|
||||
for task in complete:
|
||||
gr.Textbox(task['name'], show_label=False, container=False)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
@ -17,7 +17,7 @@ See how we can now create a variable number of Textboxes using our custom logic
|
||||
2. Add the input components to the `inputs=` argument of @gr.render, and create a corresponding argument in your function for each component. This function will automatically re-run on any change to a component.
|
||||
3. Add all components inside the function that you want to render based on the inputs.
|
||||
|
||||
Now whenever the inputs change, the funciton re-runs, and replaces the components created from the previous funciton run with the latest run. Pretty straightforward! Let's add a little more complexity to this app:
|
||||
Now whenever the inputs change, the function re-runs, and replaces the components created from the previous function run with the latest run. Pretty straightforward! Let's add a little more complexity to this app:
|
||||
|
||||
$code_render_split
|
||||
$demo_render_split
|
||||
@ -40,7 +40,27 @@ Let's take a look at what's happening here:
|
||||
|
||||
Just as with Components, whenever a function re-renders, the event listeners created from the previous render are cleared and the new event listeners from the latest run are attached.
|
||||
|
||||
This allows us to create highly customizable and complex interactions! Take a look at the example below, which spices up the previous example with a lot more event listeners:
|
||||
This allows us to create highly customizable and complex interactions!
|
||||
|
||||
$code_render_merge
|
||||
$demo_render_merge
|
||||
## Putting it Together
|
||||
|
||||
Let's look at two examples that use all the features above. First, try out the to-do list app below:
|
||||
|
||||
$code_todo_list
|
||||
$demo_todo_list
|
||||
|
||||
Note that almost the entire app is inside a single `gr.render` that reacts to the tasks `gr.State` variable. This variable is a nested list, which presents some complexity. If you design a `gr.render` to react to a list or dict structure, ensure you do the following:
|
||||
|
||||
1. Any event listener that modifies the state variable in a manner that should trigger a re-render should return a new value, not simply modify the state variable directly. A `gr.State` change event listener will not trigger if the state is provided as input and then modified directly. A new variable must be returned that can be compared against. That's why in the `mark_done` method, we create a new list that we edit. Similarly, in `delete`, we create a new list instead of calling `task_list.remove(task)`, and in `add_task`, we don't use `task_list.append`.
|
||||
2. In a `gr.render`, if a variable in a loop is used inside an event listener function, that variable should be "frozen" via setting it to itself as a default argument in the function header. See how we have `task=task` in both `mark_done` and `delete`. This freezes the variable to its "loop-time" value.
|
||||
|
||||
Let's take a look at one last example that uses everything we learned. Below is an audio mixer. Provide multiple audio tracks and mix them together.
|
||||
|
||||
$code_audio_mixer
|
||||
$demo_audio_mixer
|
||||
|
||||
Two things to not in this app:
|
||||
1. Here we provide `key=` to all the components! We need to do this so that if we add another track after setting the values for an existing track, our input values to the existing track do not get reset on re-render.
|
||||
2. When there are lots of components of different types and arbitrary counts passed to an event listener, it is easier to use the set and dictionary notation for inputs rather than list notation. Above, we make one large set of all the input `gr.Audio` and `gr.Slider` components when we pass the inputs to the `merge` function. In the function body we query the component values as a dict.
|
||||
|
||||
The `gr.render` expands gradio capabilities extensively - see what you can make out of it!
|
||||
|
Loading…
x
Reference in New Issue
Block a user