| |
| |
| |
| import copy |
| import csv |
| import random |
| import sys |
| import traceback |
| from collections import defaultdict |
| from pathlib import Path |
| from typing import List |
|
|
| import gradio as gr |
|
|
| from audiotools.core.util import find_audio |
|
|
| |
| |
| |
|
|
| WAVESURFER = """<div id="waveform"></div><div id="wave-timeline"></div>""" |
|
|
| CUSTOM_CSS = """ |
| .gradio-container { |
| max-width: 840px !important; |
| } |
| region.wavesurfer-region:before { |
| content: attr(data-region-label); |
| } |
| |
| block { |
| min-width: 0 !important; |
| } |
| |
| #wave-timeline { |
| background-color: rgba(0, 0, 0, 0.8); |
| } |
| |
| .head.svelte-1cl284s { |
| display: none; |
| } |
| """ |
|
|
| load_wavesurfer_js = """ |
| function load_wavesurfer() { |
| function load_script(url) { |
| const script = document.createElement('script'); |
| script.src = url; |
| document.body.appendChild(script); |
| |
| return new Promise((res, rej) => { |
| script.onload = function() { |
| res(); |
| } |
| script.onerror = function () { |
| rej(); |
| } |
| }); |
| } |
| |
| function create_wavesurfer() { |
| var options = { |
| container: '#waveform', |
| waveColor: '#F2F2F2', // Set a darker wave color |
| progressColor: 'white', // Set a slightly lighter progress color |
| loaderColor: 'white', // Set a slightly lighter loader color |
| cursorColor: 'black', // Set a slightly lighter cursor color |
| backgroundColor: '#00AAFF', // Set a black background color |
| barWidth: 4, |
| barRadius: 3, |
| barHeight: 1, // the height of the wave |
| plugins: [ |
| WaveSurfer.regions.create({ |
| regionsMinLength: 0.0, |
| dragSelection: { |
| slop: 5 |
| }, |
| color: 'hsla(200, 50%, 70%, 0.4)', |
| }), |
| WaveSurfer.timeline.create({ |
| container: "#wave-timeline", |
| primaryLabelInterval: 5.0, |
| secondaryLabelInterval: 1.0, |
| primaryFontColor: '#F2F2F2', |
| secondaryFontColor: '#F2F2F2', |
| }), |
| ] |
| }; |
| wavesurfer = WaveSurfer.create(options); |
| wavesurfer.on('region-created', region => { |
| wavesurfer.regions.clear(); |
| }); |
| wavesurfer.on('finish', function () { |
| var loop = document.getElementById("loop-button").textContent.includes("ON"); |
| if (loop) { |
| wavesurfer.play(); |
| } |
| else { |
| var button_elements = document.getElementsByClassName('playpause') |
| var buttons = Array.from(button_elements); |
| |
| for (let j = 0; j < buttons.length; j++) { |
| buttons[j].classList.remove("primary"); |
| buttons[j].classList.add("secondary"); |
| buttons[j].textContent = buttons[j].textContent.replace("Stop", "Play") |
| } |
| } |
| }); |
| |
| wavesurfer.on('region-out', function () { |
| var loop = document.getElementById("loop-button").textContent.includes("ON"); |
| if (!loop) { |
| var button_elements = document.getElementsByClassName('playpause') |
| var buttons = Array.from(button_elements); |
| |
| for (let j = 0; j < buttons.length; j++) { |
| buttons[j].classList.remove("primary"); |
| buttons[j].classList.add("secondary"); |
| buttons[j].textContent = buttons[j].textContent.replace("Stop", "Play") |
| } |
| wavesurfer.pause(); |
| } |
| }); |
| |
| console.log("Created WaveSurfer object.") |
| } |
| |
| load_script('https://unpkg.com/wavesurfer.js@6.6.4') |
| .then(() => { |
| load_script("https://unpkg.com/wavesurfer.js@6.6.4/dist/plugin/wavesurfer.timeline.min.js") |
| .then(() => { |
| load_script('https://unpkg.com/wavesurfer.js@6.6.4/dist/plugin/wavesurfer.regions.min.js') |
| .then(() => { |
| console.log("Loaded regions"); |
| create_wavesurfer(); |
| document.getElementById("start-survey").click(); |
| }) |
| }) |
| }); |
| } |
| """ |
|
|
| play = lambda i: """ |
| function play() { |
| var audio_elements = document.getElementsByTagName('audio'); |
| var button_elements = document.getElementsByClassName('playpause') |
| |
| var audio_array = Array.from(audio_elements); |
| var buttons = Array.from(button_elements); |
| |
| var src_link = audio_array[{i}].getAttribute("src"); |
| console.log(src_link); |
| |
| var loop = document.getElementById("loop-button").textContent.includes("ON"); |
| var playing = buttons[{i}].textContent.includes("Stop"); |
| |
| for (let j = 0; j < buttons.length; j++) { |
| if (j != {i} || playing) { |
| buttons[j].classList.remove("primary"); |
| buttons[j].classList.add("secondary"); |
| buttons[j].textContent = buttons[j].textContent.replace("Stop", "Play") |
| } |
| else { |
| buttons[j].classList.remove("secondary"); |
| buttons[j].classList.add("primary"); |
| buttons[j].textContent = buttons[j].textContent.replace("Play", "Stop") |
| } |
| } |
| |
| if (playing) { |
| wavesurfer.pause(); |
| wavesurfer.seekTo(0.0); |
| } |
| else { |
| wavesurfer.load(src_link); |
| wavesurfer.on('ready', function () { |
| var region = Object.values(wavesurfer.regions.list)[0]; |
| |
| if (region != null) { |
| region.loop = loop; |
| region.play(); |
| } else { |
| wavesurfer.play(); |
| } |
| }); |
| } |
| } |
| """.replace( |
| "{i}", str(i) |
| ) |
|
|
| clear_regions = """ |
| function clear_regions() { |
| wavesurfer.clearRegions(); |
| } |
| """ |
|
|
| reset_player = """ |
| function reset_player() { |
| wavesurfer.clearRegions(); |
| wavesurfer.pause(); |
| wavesurfer.seekTo(0.0); |
| |
| var button_elements = document.getElementsByClassName('playpause') |
| var buttons = Array.from(button_elements); |
| |
| for (let j = 0; j < buttons.length; j++) { |
| buttons[j].classList.remove("primary"); |
| buttons[j].classList.add("secondary"); |
| buttons[j].textContent = buttons[j].textContent.replace("Stop", "Play") |
| } |
| } |
| """ |
|
|
| loop_region = """ |
| function loop_region() { |
| var element = document.getElementById("loop-button"); |
| var loop = element.textContent.includes("OFF"); |
| console.log(loop); |
| |
| try { |
| var region = Object.values(wavesurfer.regions.list)[0]; |
| region.loop = loop; |
| } catch {} |
| |
| if (loop) { |
| element.classList.remove("secondary"); |
| element.classList.add("primary"); |
| element.textContent = "Looping ON"; |
| } else { |
| element.classList.remove("primary"); |
| element.classList.add("secondary"); |
| element.textContent = "Looping OFF"; |
| } |
| } |
| """ |
|
|
|
|
| class Player: |
| def __init__(self, app): |
| self.app = app |
|
|
| self.app.load(_js=load_wavesurfer_js) |
| self.app.css = CUSTOM_CSS |
|
|
| self.wavs = [] |
| self.position = 0 |
|
|
| def create(self): |
| gr.HTML(WAVESURFER) |
| gr.Markdown( |
| "Click and drag on the waveform above to select a region for playback. " |
| "Once created, the region can be moved around and resized. " |
| "Clear the regions using the button below. Hit play on one of the buttons below to start!" |
| ) |
|
|
| with gr.Row(): |
| clear = gr.Button("Clear region") |
| loop = gr.Button("Looping OFF", elem_id="loop-button") |
|
|
| loop.click(None, _js=loop_region) |
| clear.click(None, _js=clear_regions) |
|
|
| gr.HTML("<hr>") |
|
|
| def add(self, name: str = "Play"): |
| i = self.position |
| self.wavs.append( |
| { |
| "audio": gr.Audio(visible=False), |
| "button": gr.Button(name, elem_classes=["playpause"]), |
| "position": i, |
| } |
| ) |
| self.wavs[-1]["button"].click(None, _js=play(i)) |
| self.position += 1 |
| return self.wavs[-1] |
|
|
| def to_list(self): |
| return [x["audio"] for x in self.wavs] |
|
|
|
|
| |
| |
| |
|
|
| load_tracker = lambda name: """ |
| function load_name() { |
| function setCookie(name, value, exp_days) { |
| var d = new Date(); |
| d.setTime(d.getTime() + (exp_days*24*60*60*1000)); |
| var expires = "expires=" + d.toGMTString(); |
| document.cookie = name + "=" + value + ";" + expires + ";path=/"; |
| } |
| |
| function getCookie(name) { |
| var cname = name + "="; |
| var decodedCookie = decodeURIComponent(document.cookie); |
| var ca = decodedCookie.split(';'); |
| for(var i = 0; i < ca.length; i++){ |
| var c = ca[i]; |
| while(c.charAt(0) == ' '){ |
| c = c.substring(1); |
| } |
| if(c.indexOf(cname) == 0){ |
| return c.substring(cname.length, c.length); |
| } |
| } |
| return ""; |
| } |
| |
| name = getCookie("{name}"); |
| if (name == "") { |
| name = Math.random().toString(36).slice(2); |
| console.log(name); |
| setCookie("name", name, 30); |
| } |
| name = getCookie("{name}"); |
| return name; |
| } |
| """.replace( |
| "{name}", name |
| ) |
|
|
| |
|
|
| progress_template = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Progress Bar</title> |
| <style> |
| .progress-bar { |
| background-color: #ddd; |
| border-radius: 4px; |
| height: 30px; |
| width: 100%; |
| position: relative; |
| } |
| |
| .progress { |
| background-color: #00AAFF; |
| border-radius: 4px; |
| height: 100%; |
| width: {PROGRESS}%; /* Change this value to control the progress */ |
| } |
| |
| .progress-text { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| font-size: 18px; |
| font-family: Arial, sans-serif; |
| font-weight: bold; |
| color: #333 !important; |
| text-shadow: 1px 1px #fff; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="progress-bar"> |
| <div class="progress"></div> |
| <div class="progress-text">{TEXT}</div> |
| </div> |
| </body> |
| </html> |
| """ |
|
|
|
|
| def create_tracker(app, cookie_name="name"): |
| user = gr.Text(label="user", interactive=True, visible=False, elem_id="user") |
| app.load(_js=load_tracker(cookie_name), outputs=user) |
| return user |
|
|
|
|
| |
| |
| |
|
|
| slider_abx = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Labels Example</title> |
| <style> |
| body { |
| margin: 0; |
| padding: 0; |
| } |
| |
| .labels-container { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| width: 100%; |
| height: 40px; |
| padding: 0px 12px 0px; |
| } |
| |
| .label { |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| width: 33%; |
| height: 100%; |
| font-weight: bold; |
| text-transform: uppercase; |
| padding: 10px; |
| font-family: Arial, sans-serif; |
| font-size: 16px; |
| font-weight: 700; |
| letter-spacing: 1px; |
| line-height: 1.5; |
| } |
| |
| .label-a { |
| background-color: #00AAFF; |
| color: #333 !important; |
| } |
| |
| .label-tie { |
| background-color: #f97316; |
| color: #333 !important; |
| } |
| |
| .label-b { |
| background-color: #00AAFF; |
| color: #333 !important; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="labels-container"> |
| <div class="label label-a">Prefer A</div> |
| <div class="label label-tie">Toss-up</div> |
| <div class="label label-b">Prefer B</div> |
| </div> |
| </body> |
| </html> |
| """ |
|
|
| slider_mushra = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Labels Example</title> |
| <style> |
| body { |
| margin: 0; |
| padding: 0; |
| } |
| |
| .labels-container { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| width: 100%; |
| height: 30px; |
| padding: 10px; |
| } |
| |
| .label { |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| width: 20%; |
| height: 100%; |
| font-weight: bold; |
| text-transform: uppercase; |
| padding: 10px; |
| font-family: Arial, sans-serif; |
| font-size: 13.5px; |
| font-weight: 700; |
| line-height: 1.5; |
| } |
| |
| .label-bad { |
| background-color: #ff5555; |
| color: #333 !important; |
| } |
| |
| .label-poor { |
| background-color: #ffa500; |
| color: #333 !important; |
| } |
| |
| .label-fair { |
| background-color: #ffd700; |
| color: #333 !important; |
| } |
| |
| .label-good { |
| background-color: #97d997; |
| color: #333 !important; |
| } |
| |
| .label-excellent { |
| background-color: #04c822; |
| color: #333 !important; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="labels-container"> |
| <div class="label label-bad">bad</div> |
| <div class="label label-poor">poor</div> |
| <div class="label label-fair">fair</div> |
| <div class="label label-good">good</div> |
| <div class="label label-excellent">excellent</div> |
| </div> |
| </body> |
| </html> |
| """ |
|
|
| |
| |
| |
|
|
|
|
| class Samples: |
| def __init__(self, folder: str, shuffle: bool = True, n_samples: int = None): |
| files = find_audio(folder) |
| samples = defaultdict(lambda: defaultdict()) |
|
|
| for f in files: |
| condition = f.parent.stem |
| samples[f.name][condition] = f |
|
|
| self.samples = samples |
| self.names = list(samples.keys()) |
| self.filtered = False |
| self.current = 0 |
|
|
| if shuffle: |
| random.shuffle(self.names) |
|
|
| self.n_samples = len(self.names) if n_samples is None else n_samples |
|
|
| def get_updates(self, idx, order): |
| key = self.names[idx] |
| return [gr.update(value=str(self.samples[key][o])) for o in order] |
|
|
| def progress(self): |
| try: |
| pct = self.current / len(self) * 100 |
| except: |
| pct = 100 |
| text = f"On {self.current} / {len(self)} samples" |
| pbar = ( |
| copy.copy(progress_template) |
| .replace("{PROGRESS}", str(pct)) |
| .replace("{TEXT}", str(text)) |
| ) |
| return gr.update(value=pbar) |
|
|
| def __len__(self): |
| return self.n_samples |
|
|
| def filter_completed(self, user, save_path): |
| if not self.filtered: |
| done = [] |
| if Path(save_path).exists(): |
| with open(save_path, "r") as f: |
| reader = csv.DictReader(f) |
| done = [r["sample"] for r in reader if r["user"] == user] |
| self.names = [k for k in self.names if k not in done] |
| self.names = self.names[: self.n_samples] |
| self.filtered = True |
|
|
| def get_next_sample(self, reference, conditions): |
| random.shuffle(conditions) |
| if reference is not None: |
| self.order = [reference] + conditions |
| else: |
| self.order = conditions |
|
|
| try: |
| updates = self.get_updates(self.current, self.order) |
| self.current += 1 |
| done = gr.update(interactive=True) |
| pbar = self.progress() |
| except: |
| traceback.print_exc() |
| updates = [gr.update() for _ in range(len(self.order))] |
| done = gr.update(value="No more samples!", interactive=False) |
| self.current = len(self) |
| pbar = self.progress() |
|
|
| return updates, done, pbar |
|
|
|
|
| def save_result(result, save_path): |
| with open(save_path, mode="a", newline="") as file: |
| writer = csv.DictWriter(file, fieldnames=sorted(list(result.keys()))) |
| if file.tell() == 0: |
| writer.writeheader() |
| writer.writerow(result) |
|
|