From dda0f1c7a44835644a7b79e2c78365e17f22fa34 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 13:39:57 -0800 Subject: [PATCH 01/16] Discourse uploading works. --- .gitignore | 1 + audiotools/core/display.py | 37 ++++++++++++++++++++++++++++++++++++- audiotools/core/playback.py | 5 +---- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 36b5281a..54e95962 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ dmypy.json profile_speed.py.lprof scratch/ +*.ipynb diff --git a/audiotools/core/display.py b/audiotools/core/display.py index febfbee1..02669590 100644 --- a/audiotools/core/display.py +++ b/audiotools/core/display.py @@ -1,5 +1,9 @@ import numpy as np - +import os +import subprocess +import shlex +import tempfile +import json class DisplayMixin: def specshow(self, batch_idx=0, x_axis="time", y_axis="linear", **kwargs): @@ -24,3 +28,34 @@ def waveplot(self, batch_idx=0, x_axis="time", **kwargs): librosa.display.waveplot( audio_data, x_axis=x_axis, sr=self.sample_rate, **kwargs ) + + def upload_to_discourse(self, api_username=None, api_key=None, batch_idx=0, discourse_server=None, ext=".wav"): # pragma: no cover + if api_username is None: + api_username = os.environ.get("DISCOURSE_API_USERNAME", None) + if api_key is None: + api_key = os.environ.get("DISCOURSE_API_KEY", None) + if discourse_server is None: + discourse_server = os.environ.get("DISCOURSE_SERVER", None) + + if discourse_server is None or api_key is None or api_username is None: + raise RuntimeError("DISCOURSE_API_KEY, DISCOURSE_SERVER, DISCOURSE_API_USERNAME must be set in your environment!") + + with tempfile.NamedTemporaryFile(suffix=ext) as f: + self.write(f.name, batch_idx=batch_idx) + + command = ( + f"curl -X POST {discourse_server}/uploads.json " + f"-H 'content-type: multipart/form-data;' " + f"-H 'Api-Key: {api_key}' " + f"-H 'Api-Username: {api_username}' " + f"-F 'type=composer' " + f"-F 'files[]=@{f.name}' " + ) + info = json.loads(subprocess.check_output(shlex.split(command))) + + label = self.path_to_input_file + if label is None: + label = "unknown" + + formatted = f"![{label}|audio]({info['short_path']})" + return formatted, info diff --git a/audiotools/core/playback.py b/audiotools/core/playback.py index 80ae9df1..d6d140da 100644 --- a/audiotools/core/playback.py +++ b/audiotools/core/playback.py @@ -88,7 +88,6 @@ def widget( self, batch_idx=0, ext=".mp3", - display=True, add_headers=True, player_width="100%", max_width="600px", @@ -166,9 +165,7 @@ def widget( widget_html = widget_html.replace("IMAGE_SRC", tag) widget_html = widget_html.replace("PLAYER_ID", player_id) - if display: - IPython.display.display(IPython.display.HTML(widget_html)) - return IPython.display.HTML(header_html + widget_html) + IPython.display.display(IPython.display.HTML(widget_html)) def play(self, batch_idx=0): """ From 974cc246fa91525f9cdc338a2a6c0e04eb6c8c18 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 13:41:34 -0800 Subject: [PATCH 02/16] Updating version. --- audiotools/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/audiotools/__init__.py b/audiotools/__init__.py index 47fdbc2a..18893331 100644 --- a/audiotools/__init__.py +++ b/audiotools/__init__.py @@ -1,3 +1,3 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" from .core import AudioSignal, STFTParams, Meter, util from . import metrics diff --git a/setup.py b/setup.py index 88964aa0..5c95b290 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="audiotools", - version="0.1.4", + version="0.1.5", classifiers=[ "Intended Audience :: Developers", "Intended Audience :: Education", From fca964ea2974c8d42447af520a4f0d7133a4d9d1 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 13:43:06 -0800 Subject: [PATCH 03/16] Ran pre-commit. --- audiotools/core/display.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/audiotools/core/display.py b/audiotools/core/display.py index 02669590..a7c04d87 100644 --- a/audiotools/core/display.py +++ b/audiotools/core/display.py @@ -1,9 +1,11 @@ -import numpy as np +import json import os -import subprocess import shlex +import subprocess import tempfile -import json + +import numpy as np + class DisplayMixin: def specshow(self, batch_idx=0, x_axis="time", y_axis="linear", **kwargs): @@ -16,7 +18,7 @@ def specshow(self, batch_idx=0, x_axis="time", y_axis="linear", **kwargs): x_axis=x_axis, y_axis=y_axis, sr=self.sample_rate, - **kwargs + **kwargs, ) def waveplot(self, batch_idx=0, x_axis="time", **kwargs): @@ -29,7 +31,14 @@ def waveplot(self, batch_idx=0, x_axis="time", **kwargs): audio_data, x_axis=x_axis, sr=self.sample_rate, **kwargs ) - def upload_to_discourse(self, api_username=None, api_key=None, batch_idx=0, discourse_server=None, ext=".wav"): # pragma: no cover + def upload_to_discourse( + self, + api_username=None, + api_key=None, + batch_idx=0, + discourse_server=None, + ext=".wav", + ): # pragma: no cover if api_username is None: api_username = os.environ.get("DISCOURSE_API_USERNAME", None) if api_key is None: @@ -38,7 +47,9 @@ def upload_to_discourse(self, api_username=None, api_key=None, batch_idx=0, disc discourse_server = os.environ.get("DISCOURSE_SERVER", None) if discourse_server is None or api_key is None or api_username is None: - raise RuntimeError("DISCOURSE_API_KEY, DISCOURSE_SERVER, DISCOURSE_API_USERNAME must be set in your environment!") + raise RuntimeError( + "DISCOURSE_API_KEY, DISCOURSE_SERVER, DISCOURSE_API_USERNAME must be set in your environment!" + ) with tempfile.NamedTemporaryFile(suffix=ext) as f: self.write(f.name, batch_idx=batch_idx) @@ -56,6 +67,6 @@ def upload_to_discourse(self, api_username=None, api_key=None, batch_idx=0, disc label = self.path_to_input_file if label is None: label = "unknown" - + formatted = f"![{label}|audio]({info['short_path']})" return formatted, info From 3190145c3d23f772509d996fea2a21a0c7361e20 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 13:54:53 -0800 Subject: [PATCH 04/16] Adding option for adding label. --- audiotools/core/display.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/audiotools/core/display.py b/audiotools/core/display.py index a7c04d87..abd6ccce 100644 --- a/audiotools/core/display.py +++ b/audiotools/core/display.py @@ -33,6 +33,7 @@ def waveplot(self, batch_idx=0, x_axis="time", **kwargs): def upload_to_discourse( self, + label=None, api_username=None, api_key=None, batch_idx=0, @@ -64,7 +65,7 @@ def upload_to_discourse( ) info = json.loads(subprocess.check_output(shlex.split(command))) - label = self.path_to_input_file + label = self.path_to_input_file if label is None else label if label is None: label = "unknown" From c5949f8c71b2c0fe43f6769c000a1ae2f005de41 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 14:31:26 -0800 Subject: [PATCH 05/16] Updating linter. --- .pre-commit-config.yaml | 4 ++-- audiotools/core/effects.py | 14 +++++++------- audiotools/metrics/distance.py | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a71c618..b02b064c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,12 +4,12 @@ repos: hooks: - id: reorder-python-imports - repo: https://github.com/psf/black - rev: 21.4b2 + rev: 22.1.0 hooks: - id: black language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: end-of-file-fixer - id: trailing-whitespace diff --git a/audiotools/core/effects.py b/audiotools/core/effects.py index 804fdf5a..839baa42 100644 --- a/audiotools/core/effects.py +++ b/audiotools/core/effects.py @@ -206,7 +206,7 @@ def equalizer(self, db): else: db = db.unsqueeze(0) - weights = (10 ** db).to(self.device).float() + weights = (10**db).to(self.device).float() fbank = fbank * weights[:, None, None, :] eq_audio_data = fbank.sum(-1) self.audio_data = eq_audio_data @@ -252,8 +252,8 @@ def decompose_ir(self): def measure_drr(self): early_response, late_field, _ = self.decompose_ir() - num = (early_response ** 2).sum(dim=-1) - den = (late_field ** 2).sum(dim=-1) + num = (early_response**2).sum(dim=-1) + den = (late_field**2).sum(dim=-1) drr = 10 * torch.log10(num / den) return drr @@ -263,17 +263,17 @@ def solve_alpha(early_response, late_field, wd, target_drr): # ---------- # Apply the good ol' quadratic formula. - wd_sq = wd ** 2 + wd_sq = wd**2 wd_sq_1 = (1 - wd) ** 2 - e_sq = early_response ** 2 - l_sq = late_field ** 2 + e_sq = early_response**2 + l_sq = late_field**2 a = (wd_sq * e_sq).sum(dim=-1) b = (2 * (1 - wd) * wd * e_sq).sum(dim=-1) c = (wd_sq_1 * e_sq).sum(dim=-1) - torch.pow(10, target_drr / 10) * l_sq.sum( dim=-1 ) - expr = ((b ** 2) - 4 * a * c).sqrt() + expr = ((b**2) - 4 * a * c).sqrt() alpha = torch.maximum( (-b - expr) / (2 * a), (-b + expr) / (2 * a), diff --git a/audiotools/metrics/distance.py b/audiotools/metrics/distance.py index d8aa85ab..a450df24 100644 --- a/audiotools/metrics/distance.py +++ b/audiotools/metrics/distance.py @@ -77,7 +77,7 @@ def forward(self, x: AudioSignal, y: AudioSignal): _references = references - mean_reference _estimates = estimates - mean_estimate - references_projection = (_references ** 2).sum(dim=-2) + eps + references_projection = (_references**2).sum(dim=-2) + eps references_on_estimates = (_estimates * _references).sum(dim=-2) + eps scale = ( @@ -89,8 +89,8 @@ def forward(self, x: AudioSignal, y: AudioSignal): e_true = scale * _references e_res = _estimates - e_true - signal = (e_true ** 2).sum(dim=1) - noise = (e_res ** 2).sum(dim=1) + signal = (e_true**2).sum(dim=1) + noise = (e_res**2).sum(dim=1) sdr = -10 * torch.log10(signal / noise + eps) if self.clip_min is not None: From 2fde10400a5e8f2cf6d11187a4e1dfc3c98cf8b6 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 14:43:51 -0800 Subject: [PATCH 06/16] Changing linter. --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 90cc16f6..8f5ab2df 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -8,7 +8,7 @@ jobs: - uses: actions/checkout@v2 # Run isort + black formatter - name: Python Code Formatter - uses: tdeboissiere/python-format-action@master + uses: descriptinc/python-format-action@master build: runs-on: ubuntu-latest From bf538ff684a45f72a5af320898c94879fd6250a8 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 15:15:33 -0800 Subject: [PATCH 07/16] Just use ffmpy to implement `load_from_file_with_ffmpeg`. --- audiotools/core/ffmpeg.py | 12 +++++++++--- tests/core/test_ffmpeg.py | 8 ++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/audiotools/core/ffmpeg.py b/audiotools/core/ffmpeg.py index e137a9e8..9efe9e14 100644 --- a/audiotools/core/ffmpeg.py +++ b/audiotools/core/ffmpeg.py @@ -2,6 +2,7 @@ import subprocess import tempfile +import ffmpy import numpy as np import torch @@ -79,10 +80,15 @@ def ffmpeg_resample(self, sample_rate, quiet=True): @classmethod def load_from_file_with_ffmpeg(cls, audio_path, quiet=True, **kwargs): with tempfile.NamedTemporaryFile(suffix=".wav") as f: - command = f"ffmpeg -i '{audio_path}' {f.name} -y" + global_options = "-y" if quiet: - command += " -hide_banner -loglevel error" + global_options += " -loglevel error" - subprocess.check_call(shlex.split(command)) + ff = ffmpy.FFmpeg( + inputs={audio_path: None}, + outputs={f.name: None}, + global_options=global_options, + ) + ff.run() signal = cls(f.name, **kwargs) return signal diff --git a/tests/core/test_ffmpeg.py b/tests/core/test_ffmpeg.py index 62398b4b..37c06645 100644 --- a/tests/core/test_ffmpeg.py +++ b/tests/core/test_ffmpeg.py @@ -64,3 +64,11 @@ def test_ffmpeg_load(): signal_from_ffmpeg = AudioSignal.load_from_file_with_ffmpeg(out_path) assert og_signal.signal_length == signal_from_ffmpeg.signal_length + + # test quotes in title + with tempfile.TemporaryDirectory() as tmpdir: + out_path = str(Path(tmpdir) / "Someone's title with spaces.wav") + og_signal.write(out_path) + signal_from_ffmpeg = AudioSignal.load_from_file_with_ffmpeg(out_path) + + assert og_signal.signal_length == signal_from_ffmpeg.signal_length From acc92fc52ba313ec93b94c15f03821a3af944730 Mon Sep 17 00:00:00 2001 From: pseeth Date: Wed, 2 Feb 2022 17:44:30 -0800 Subject: [PATCH 08/16] Making curl quiet. --- audiotools/core/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audiotools/core/display.py b/audiotools/core/display.py index abd6ccce..a734cc09 100644 --- a/audiotools/core/display.py +++ b/audiotools/core/display.py @@ -56,7 +56,7 @@ def upload_to_discourse( self.write(f.name, batch_idx=batch_idx) command = ( - f"curl -X POST {discourse_server}/uploads.json " + f"curl -s -X POST {discourse_server}/uploads.json " f"-H 'content-type: multipart/form-data;' " f"-H 'Api-Key: {api_key}' " f"-H 'Api-Username: {api_username}' " From b31fe93cfd510ace9dad88f37c19358d22949718 Mon Sep 17 00:00:00 2001 From: pseeth Date: Thu, 3 Feb 2022 17:51:38 -0800 Subject: [PATCH 09/16] Fixing up the widget a bunch. --- audiotools/core/playback.py | 70 ++++++++++++++++++++------ audiotools/core/templates/headers.html | 5 +- audiotools/core/templates/widget.html | 3 +- tests/core/test_playback.py | 2 +- 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/audiotools/core/playback.py b/audiotools/core/playback.py index d6d140da..df0c28d1 100644 --- a/audiotools/core/playback.py +++ b/audiotools/core/playback.py @@ -86,6 +86,7 @@ def embed(self, batch_idx=0, ext=".mp3", display=True): def widget( self, + title=None, batch_idx=0, ext=".mp3", add_headers=True, @@ -93,6 +94,7 @@ def widget( max_width="600px", margin="10px", plot_fn=None, + fig_size=(12, 4), **kwargs, ): """Creates a playable widget with spectrogram. Inspired (heavily) by @@ -116,6 +118,10 @@ def widget( Margin on all sides of player, by default "10px" plot_fn : function, optional Plotting function to use (by default self.specshow). + title : str, optional + Title of plot, placed in upper right of top-most axis. + fig_size : tuple, optional + Size of figure. kwargs : dict, optional Keyword arguments to plot_fn (by default self.specshow). @@ -124,6 +130,32 @@ def widget( HTML HTML object. """ + + def _adjust_figure(fig): + fig.set_size_inches(*fig_size) + plt.ioff() + + axs = fig.axes + for ax in axs: + ax.margins(0, 0) + ax.set_axis_off() + ax.xaxis.set_major_locator(plt.NullLocator()) + ax.yaxis.set_major_locator(plt.NullLocator()) + + plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) + + def _save_fig_to_tag(): + buffer = io.BytesIO() + + plt.savefig(buffer, bbox_inches="tight", pad_inches=0) + plt.close() + + buffer.seek(0) + data_uri = base64.b64encode(buffer.read()).decode("ascii") + tag = "data:image/png;base64,{0}".format(data_uri) + + return tag + _, IPython = _check_imports() header_html = "" @@ -138,31 +170,39 @@ def widget( if plot_fn is None: plot_fn = self.specshow + kwargs["batch_idx"] = batch_idx plot_fn(**kwargs) - plt.ioff() - plt.gca().set_axis_off() - plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) - plt.margins(0, 0) - plt.gca().xaxis.set_major_locator(plt.NullLocator()) - plt.gca().yaxis.set_major_locator(plt.NullLocator()) - - buffer = io.BytesIO() - - plt.savefig(buffer, bbox_inches="tight", pad_inches=0) - plt.close() - - buffer.seek(0) + fig = plt.gcf() + axs = fig.axes + _adjust_figure(fig) + + if title is not None: + t = axs[0].text( + 0.995, + 0.92, + title, + size=25, + color="white", + transform=axs[0].transAxes, + horizontalalignment="right", + ) + t.set_bbox(dict(facecolor="black", alpha=0.75, edgecolor="black")) - data_uri = base64.b64encode(buffer.read()).decode("ascii") + tag = _save_fig_to_tag() - tag = "data:image/png;base64,{0}".format(data_uri) + # Make the source image for the levels + fig = plt.figure() + self.specshow(batch_idx=batch_idx) + _adjust_figure(fig) + levels_tag = _save_fig_to_tag() player_id = "".join(random.choice(string.ascii_uppercase) for _ in range(10)) audio_elem = self.embed(batch_idx=batch_idx, ext=ext, display=False) widget_html = widget_html.replace("AUDIO_SRC", audio_elem.src_attr()) widget_html = widget_html.replace("IMAGE_SRC", tag) + widget_html = widget_html.replace("LEVELS_SRC", levels_tag) widget_html = widget_html.replace("PLAYER_ID", player_id) IPython.display.display(IPython.display.HTML(widget_html)) diff --git a/audiotools/core/templates/headers.html b/audiotools/core/templates/headers.html index e07efa8d..cbf2f403 100644 --- a/audiotools/core/templates/headers.html +++ b/audiotools/core/templates/headers.html @@ -130,6 +130,7 @@ overflow:hidden; position: absolute; z-index: 10; + border-left: solid 1px rgba(0, 0, 0, 0.664); position: absolute; pointer-events: none; @@ -223,7 +224,7 @@ this.download.onclick = download_audio; } - load(audio_fname, img_fname) { + load(audio_fname, img_fname, levels_fname) { this.pause() window.cancelAnimationFrame(this.progress) this.playpause.disabled = true @@ -233,7 +234,7 @@ this.demo_img.setAttribute("src", img_fname) this.overlay.style.width = '0%' - fetch(img_fname) + fetch(levels_fname) .then(response => response.arrayBuffer()) .then(text => { this.mat = this.parse(text); diff --git a/audiotools/core/templates/widget.html b/audiotools/core/templates/widget.html index 2cc04b38..c63e7eff 100644 --- a/audiotools/core/templates/widget.html +++ b/audiotools/core/templates/widget.html @@ -45,7 +45,8 @@ var PLAYER_ID = new Player('PLAYER_ID') PLAYER_ID.load( "AUDIO_SRC", - "IMAGE_SRC" + "IMAGE_SRC", + "LEVELS_SRC" ) window.addEventListener("resize", function() {PLAYER_ID.redraw()}) diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index 37b85831..1d6597a2 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -19,5 +19,5 @@ def test_embed(): def test_widget(): array = np.zeros((1, 10000)) AudioSignal(array, sample_rate=16000).widget() - AudioSignal(array, sample_rate=16000).widget(ext=".wav") + AudioSignal(array, sample_rate=16000).widget("Some title") From e3a254b0bb27d48f114ac37aba1ca63d933ed7e3 Mon Sep 17 00:00:00 2001 From: pseeth Date: Thu, 3 Feb 2022 22:08:51 -0800 Subject: [PATCH 10/16] Fixing small things for the widget. --- audiotools/core/playback.py | 61 +++++++++++++++++++++++--- audiotools/core/templates/headers.html | 1 - audiotools/core/templates/widget.html | 2 +- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/audiotools/core/playback.py b/audiotools/core/playback.py index df0c28d1..20ea246d 100644 --- a/audiotools/core/playback.py +++ b/audiotools/core/playback.py @@ -95,6 +95,7 @@ def widget( margin="10px", plot_fn=None, fig_size=(12, 4), + return_html=False, **kwargs, ): """Creates a playable widget with spectrogram. Inspired (heavily) by @@ -178,16 +179,18 @@ def _save_fig_to_tag(): _adjust_figure(fig) if title is not None: - t = axs[0].text( - 0.995, - 0.92, + t = axs[0].annotate( title, - size=25, + xy=(1, 1), + xycoords="axes fraction", + fontsize=25, + xytext=(-5, -5), + textcoords="offset points", + ha="right", + va="top", color="white", - transform=axs[0].transAxes, - horizontalalignment="right", ) - t.set_bbox(dict(facecolor="black", alpha=0.75, edgecolor="black")) + t.set_bbox(dict(facecolor="black", alpha=0.5, edgecolor="black")) tag = _save_fig_to_tag() @@ -205,8 +208,17 @@ def _save_fig_to_tag(): widget_html = widget_html.replace("LEVELS_SRC", levels_tag) widget_html = widget_html.replace("PLAYER_ID", player_id) + # Calculate height of figure based on figure size. + padding_amount = str(fig_size[1] * 9) + "%" + widget_html = widget_html.replace("PADDING_AMOUNT", padding_amount) + IPython.display.display(IPython.display.HTML(widget_html)) + if return_html: + html = header_html if add_headers else "" + html += widget_html + return html + def play(self, batch_idx=0): """ Plays an audio signal if ffplay from the ffmpeg suite of tools is installed. @@ -234,3 +246,38 @@ def play(self, batch_idx=0): ] ) return self + + +if __name__ == "__main__": + from audiotools import AudioSignal + from matplotlib.gridspec import GridSpec + + signal = AudioSignal( + "tests/audio/spk/f10_script4_produced.mp3", offset=5, duration=5 + ) + + wave_html = signal.widget( + "Waveform plot", plot_fn=signal.waveplot, return_html=True, fig_size=(12, 2) + ) + + spec_html = signal.widget("Spectrogram plot", return_html=True, add_headers=False) + + def plot_fn(): + gs = GridSpec(6, 1) + plt.subplot(gs[0, :]) + signal.waveplot() + plt.subplot(gs[1:, :]) + signal.specshow() + + combined_html = signal.widget( + "Combined plot", + plot_fn=plot_fn, + return_html=True, + fig_size=(12, 5), + add_headers=False, + ) + + with open("/tmp/index.html", "w") as f: + f.write(wave_html) + f.write(spec_html) + f.write(combined_html) diff --git a/audiotools/core/templates/headers.html b/audiotools/core/templates/headers.html index cbf2f403..b2647fa5 100644 --- a/audiotools/core/templates/headers.html +++ b/audiotools/core/templates/headers.html @@ -16,7 +16,6 @@ height: 0; width: 100%; position: relative; - padding-top: 35%; } .audio-controls { diff --git a/audiotools/core/templates/widget.html b/audiotools/core/templates/widget.html index c63e7eff..877300c6 100644 --- a/audiotools/core/templates/widget.html +++ b/audiotools/core/templates/widget.html @@ -1,5 +1,5 @@
-
+
From a66bea442807c575cc4cce70e3e3d4c108a0fceb Mon Sep 17 00:00:00 2001 From: pseeth Date: Thu, 3 Feb 2022 22:18:41 -0800 Subject: [PATCH 11/16] Adding presets for display purposes. --- audiotools/core/display.py | 9 +++++++++ audiotools/core/playback.py | 23 +++++++---------------- tests/core/test_display.py | 5 +++++ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/audiotools/core/display.py b/audiotools/core/display.py index a734cc09..c966bd47 100644 --- a/audiotools/core/display.py +++ b/audiotools/core/display.py @@ -4,7 +4,9 @@ import subprocess import tempfile +import matplotlib.pyplot as plt import numpy as np +from matplotlib.gridspec import GridSpec class DisplayMixin: @@ -31,6 +33,13 @@ def waveplot(self, batch_idx=0, x_axis="time", **kwargs): audio_data, x_axis=x_axis, sr=self.sample_rate, **kwargs ) + def wavespec(self, batch_idx=0, x_axis="time", **kwargs): + gs = GridSpec(6, 1) + plt.subplot(gs[0, :]) + self.waveplot(batch_idx=batch_idx, x_axis=x_axis) + plt.subplot(gs[1:, :]) + self.specshow(batch_idx=batch_idx, x_axis=x_axis, **kwargs) + def upload_to_discourse( self, label=None, diff --git a/audiotools/core/playback.py b/audiotools/core/playback.py index 20ea246d..586a957d 100644 --- a/audiotools/core/playback.py +++ b/audiotools/core/playback.py @@ -8,12 +8,10 @@ import random import string import subprocess -from copy import deepcopy from tempfile import NamedTemporaryFile import importlib_resources as pkg_resources import matplotlib.pyplot as plt -from PIL import Image from . import templates from .util import _close_temp_files @@ -93,7 +91,7 @@ def widget( player_width="100%", max_width="600px", margin="10px", - plot_fn=None, + plot_fn="specshow", fig_size=(12, 4), return_html=False, **kwargs, @@ -169,8 +167,8 @@ def _save_fig_to_tag(): widget_html = widget - if plot_fn is None: - plot_fn = self.specshow + if isinstance(plot_fn, str): + plot_fn = getattr(self, plot_fn) kwargs["batch_idx"] = batch_idx plot_fn(**kwargs) @@ -257,21 +255,14 @@ def play(self, batch_idx=0): ) wave_html = signal.widget( - "Waveform plot", plot_fn=signal.waveplot, return_html=True, fig_size=(12, 2) + "Waveform", plot_fn="waveplot", return_html=True, fig_size=(12, 2) ) - spec_html = signal.widget("Spectrogram plot", return_html=True, add_headers=False) - - def plot_fn(): - gs = GridSpec(6, 1) - plt.subplot(gs[0, :]) - signal.waveplot() - plt.subplot(gs[1:, :]) - signal.specshow() + spec_html = signal.widget("Spectrogram", return_html=True, add_headers=False) combined_html = signal.widget( - "Combined plot", - plot_fn=plot_fn, + "Waveform + spectrogram", + plot_fn="wavespec", return_html=True, fig_size=(12, 5), add_headers=False, diff --git a/tests/core/test_display.py b/tests/core/test_display.py index b9da766f..2ad3055f 100644 --- a/tests/core/test_display.py +++ b/tests/core/test_display.py @@ -12,3 +12,8 @@ def test_specshow(): def test_waveplot(): array = np.zeros((1, 16000)) AudioSignal(array, sample_rate=16000).waveplot() + + +def test_wavespec(): + array = np.zeros((1, 16000)) + AudioSignal(array, sample_rate=16000).wavespec() From 32ffe194e31a1fef9f3ccda13c0efbcbca6bc4be Mon Sep 17 00:00:00 2001 From: pseeth Date: Thu, 3 Feb 2022 22:20:49 -0800 Subject: [PATCH 12/16] For coverage. --- audiotools/core/playback.py | 3 +-- tests/core/test_playback.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/audiotools/core/playback.py b/audiotools/core/playback.py index 586a957d..73f751b5 100644 --- a/audiotools/core/playback.py +++ b/audiotools/core/playback.py @@ -246,9 +246,8 @@ def play(self, batch_idx=0): return self -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover from audiotools import AudioSignal - from matplotlib.gridspec import GridSpec signal = AudioSignal( "tests/audio/spk/f10_script4_produced.mp3", offset=5, duration=5 diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index 1d6597a2..ecf42e29 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -21,3 +21,4 @@ def test_widget(): AudioSignal(array, sample_rate=16000).widget() AudioSignal(array, sample_rate=16000).widget(ext=".wav") AudioSignal(array, sample_rate=16000).widget("Some title") + AudioSignal(array, sample_rate=16000).widget("Some title", return_html=True) From 51ca4c1cf309c33984a4f49cef3709cf01ec3882 Mon Sep 17 00:00:00 2001 From: pseeth Date: Thu, 3 Feb 2022 22:32:29 -0800 Subject: [PATCH 13/16] Adding some defaults for figure sizes. --- audiotools/core/playback.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/audiotools/core/playback.py b/audiotools/core/playback.py index 73f751b5..0125f11c 100644 --- a/audiotools/core/playback.py +++ b/audiotools/core/playback.py @@ -19,6 +19,12 @@ headers = pkg_resources.read_text(templates, "headers.html") widget = pkg_resources.read_text(templates, "widget.html") +DEFAULT_FIG_SIZES = { + "specshow": (12, 4), + "waveplot": (12, 2), + "wavespec": (12, 5), +} + def _check_imports(): # pragma: no cover try: @@ -92,7 +98,7 @@ def widget( max_width="600px", margin="10px", plot_fn="specshow", - fig_size=(12, 4), + fig_size=None, return_html=False, **kwargs, ): @@ -130,8 +136,8 @@ def widget( HTML object. """ - def _adjust_figure(fig): - fig.set_size_inches(*fig_size) + def _adjust_figure(fig, _fig_size): + fig.set_size_inches(*_fig_size) plt.ioff() axs = fig.axes @@ -167,6 +173,9 @@ def _save_fig_to_tag(): widget_html = widget + if fig_size is None: + fig_size = DEFAULT_FIG_SIZES.get(plot_fn, (12, 4)) + if isinstance(plot_fn, str): plot_fn = getattr(self, plot_fn) kwargs["batch_idx"] = batch_idx @@ -174,7 +183,7 @@ def _save_fig_to_tag(): fig = plt.gcf() axs = fig.axes - _adjust_figure(fig) + _adjust_figure(fig, fig_size) if title is not None: t = axs[0].annotate( @@ -195,7 +204,7 @@ def _save_fig_to_tag(): # Make the source image for the levels fig = plt.figure() self.specshow(batch_idx=batch_idx) - _adjust_figure(fig) + _adjust_figure(fig, (12, 1.5)) levels_tag = _save_fig_to_tag() player_id = "".join(random.choice(string.ascii_uppercase) for _ in range(10)) @@ -254,7 +263,9 @@ def play(self, batch_idx=0): ) wave_html = signal.widget( - "Waveform", plot_fn="waveplot", return_html=True, fig_size=(12, 2) + "Waveform", + plot_fn="waveplot", + return_html=True, ) spec_html = signal.widget("Spectrogram", return_html=True, add_headers=False) @@ -263,7 +274,6 @@ def play(self, batch_idx=0): "Waveform + spectrogram", plot_fn="wavespec", return_html=True, - fig_size=(12, 5), add_headers=False, ) From f6ecb1139226a5883462c412542d5a14a96a56e4 Mon Sep 17 00:00:00 2001 From: pseeth Date: Fri, 4 Feb 2022 00:01:51 -0800 Subject: [PATCH 14/16] Tie max-width to player. --- audiotools/core/playback.py | 2 +- audiotools/core/templates/headers.html | 2 -- audiotools/core/templates/widget.html | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/audiotools/core/playback.py b/audiotools/core/playback.py index 0125f11c..636d51a3 100644 --- a/audiotools/core/playback.py +++ b/audiotools/core/playback.py @@ -167,7 +167,6 @@ def _save_fig_to_tag(): if add_headers: header_html = headers.replace("PLAYER_WIDTH", str(player_width)) - header_html = header_html.replace("MAX_WIDTH", str(max_width)) header_html = header_html.replace("MARGIN", str(margin)) IPython.display.display(IPython.display.HTML(header_html)) @@ -218,6 +217,7 @@ def _save_fig_to_tag(): # Calculate height of figure based on figure size. padding_amount = str(fig_size[1] * 9) + "%" widget_html = widget_html.replace("PADDING_AMOUNT", padding_amount) + widget_html = widget_html.replace("MAX_WIDTH", str(max_width)) IPython.display.display(IPython.display.HTML(widget_html)) diff --git a/audiotools/core/templates/headers.html b/audiotools/core/templates/headers.html index b2647fa5..9eaef4a9 100644 --- a/audiotools/core/templates/headers.html +++ b/audiotools/core/templates/headers.html @@ -1,10 +1,8 @@