BWF Analyser: browser page and macOS app
Reads and edits BWF metadata for production sound. One source tree builds a single self-contained page and a native Tauri app with a Rust audio engine and WAV writer. Around 370 checks across seven test suites. First commit of the existing state, so that from here every change can be seen and undone.
This commit is contained in:
+404
@@ -0,0 +1,404 @@
|
||||
<div id="bwfa-app-1" class="bwfa-scope bwfa-app" data-bwfa-app aria-label="BWF Metadata Analyser">
|
||||
<div class="bwfa-header">
|
||||
<h2 class="bwfa-title">BWF Metadata Analyser</h2>
|
||||
<p class="bwfa-privacy-note">
|
||||
<span class="badge badge-success" aria-hidden="true">Local only</span>
|
||||
Your files are read directly in this browser tab and never leave your computer.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bwfa-dropzone" data-bwfa-dropzone tabindex="0" role="button"
|
||||
aria-label="Select or drop a folder or files of BWF/WAV recordings">
|
||||
<p class="bwfa-dropzone-text" data-bwfa-dropzone-text></p>
|
||||
<div class="bwfa-dropzone-actions">
|
||||
<button type="button" class="btn btn-primary" data-bwfa-select-folder>Select Folder</button>
|
||||
<button type="button" class="btn btn-secondary" data-bwfa-select-files>Select Files</button>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-clear disabled>Clear</button>
|
||||
</div>
|
||||
<p class="bwfa-dropzone-subnote">
|
||||
On iPhone/iPad, if the folder picker won't let you select a folder you've navigated into, use "Select Files" and choose the recordings directly instead — this is a known iOS quirk with folder pickers in general, not specific to this tool.
|
||||
</p>
|
||||
<input
|
||||
type="file"
|
||||
class="bwfa-visually-hidden"
|
||||
data-bwfa-file-input
|
||||
webkitdirectory
|
||||
directory
|
||||
multiple
|
||||
accept=".wav,.bwf,.broadcastwave"
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
class="bwfa-visually-hidden"
|
||||
data-bwfa-files-input
|
||||
multiple
|
||||
accept=".wav,.bwf,.broadcastwave"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="bwfa-edit-entry" data-bwfa-edit-entry hidden>
|
||||
<div class="bwfa-edit-divider"><span>or</span></div>
|
||||
<div class="bwfa-launch-logo" aria-hidden="true"></div>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-edit-folder>Edit Metadata in a Folder…</button>
|
||||
<span class="bwfa-current-folder" data-bwfa-current-folder hidden></span>
|
||||
<p class="bwfa-dropzone-subnote">
|
||||
This opens files for editing rather than just viewing. Saving a change writes it directly back to the original file on your disk — there is no undo, and no automatic backup is made. Available in Chromium-based browsers only (Chrome, Edge, Opera, Arc); Firefox and Safari, including iPhone/iPad, cannot write to local files from a webpage at all.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="bwfa-fs-warning" data-bwfa-fs-warning>
|
||||
Your browser blocked the folder-write permission because this page was opened straight from disk
|
||||
(a <code>file://</code> address), where writing to local files isn't allowed. Reading, playback and
|
||||
export all still work. To edit metadata, serve this folder over http instead: run
|
||||
<code>python3 -m http.server</code> in the folder holding this file, then open
|
||||
<code>http://localhost:8000/</code>.
|
||||
</p>
|
||||
|
||||
<div class="bwfa-status" data-bwfa-status role="status" aria-live="polite"></div>
|
||||
|
||||
<div class="bwfa-progress progress" data-bwfa-progress-wrap hidden>
|
||||
<div class="progress-bar" data-bwfa-progress-bar style="width:0%"></div>
|
||||
</div>
|
||||
|
||||
<div class="bwfa-results" data-bwfa-results hidden>
|
||||
<div class="bwfa-toolbar">
|
||||
<div class="form-group bwfa-search-group">
|
||||
<input
|
||||
type="search"
|
||||
class="form-control"
|
||||
data-bwfa-search
|
||||
placeholder="Filter by filename, scene, take…"
|
||||
/>
|
||||
</div>
|
||||
<div class="bwfa-export-actions">
|
||||
<!-- Columns moved to the cogwheel in the table's first header
|
||||
cell. The button stays here, hidden, because the analyser
|
||||
wires a listener to it at startup. -->
|
||||
<div class="dropdown bwfa-legacy-export" data-bwfa-columns-dropdown>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-columns-toggle>Columns</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-bulk-edit-toggle hidden>Bulk Edit</button>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-report-open>Sound Report</button>
|
||||
<!-- The three buttons the report modal replaced. Kept in the DOM,
|
||||
hidden by CSS: the analyser wires its export pipeline to
|
||||
them at startup, and the modal drives them rather than
|
||||
duplicating any of it. -->
|
||||
<button type="button" class="btn btn-outline bwfa-legacy-export" data-bwfa-export-fields-toggle>Export Fields</button>
|
||||
<button type="button" class="btn btn-secondary bwfa-legacy-export" data-bwfa-export-csv>Export CSV</button>
|
||||
<button type="button" class="btn btn-secondary bwfa-legacy-export" data-bwfa-export-pdf>Export PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bwfa-editing-note" data-bwfa-editing-note hidden>
|
||||
<span class="badge badge-warning">Editing</span>
|
||||
Changes save directly to the original files on disk.
|
||||
</div>
|
||||
|
||||
<div class="bwfa-bulk-edit-panel" data-bwfa-bulk-edit-panel hidden>
|
||||
<div class="bwfa-bulk-edit-header">
|
||||
<h4>Bulk Edit</h4>
|
||||
<p data-bwfa-bulk-edit-count></p>
|
||||
</div>
|
||||
<div class="bwfa-bulk-edit-fields" data-bwfa-bulk-edit-fields></div>
|
||||
<div class="bwfa-bulk-tracks" data-bwfa-bulk-tracks hidden></div>
|
||||
<div class="bwfa-bulk-edit-actions">
|
||||
<div class="bwfa-bulk-edit-progress" data-bwfa-bulk-edit-progress hidden></div>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-bulk-edit-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bwfa-bulk-edit-apply disabled>Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bwfa-player" data-bwfa-player hidden>
|
||||
<div class="bwfa-player-label">
|
||||
<span class="badge badge-info" data-bwfa-player-badge>Now playing</span>
|
||||
<span class="bwfa-player-filename" data-bwfa-player-filename></span>
|
||||
<button type="button" class="btn btn-sm btn-outline bwfa-player-edit"
|
||||
data-bwfa-player-edit>Edit</button>
|
||||
<span class="bwfa-player-decoding" data-bwfa-player-decoding hidden>Decoding…</span>
|
||||
<button type="button" class="bwfa-round-btn bwfa-mixer-open"
|
||||
data-bwfa-mixer-open aria-label="Mixer">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
|
||||
<circle cx="8" cy="8" r="5.6" fill="none" stroke="currentColor"
|
||||
stroke-width="1.4"/>
|
||||
<path d="M8 3.6 V 8" stroke="currentColor" stroke-width="1.6"
|
||||
stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="bwfa-round-btn bwfa-spectro-open"
|
||||
data-bwfa-spectro-open aria-label="Spectrogram">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
|
||||
<rect x="1" y="9" width="2" height="5" rx="0.6"/>
|
||||
<rect x="4.5" y="5" width="2" height="9" rx="0.6"/>
|
||||
<rect x="8" y="2" width="2" height="12" rx="0.6"/>
|
||||
<rect x="11.5" y="6.5" width="2" height="7.5" rx="0.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop bwfa-mixer-backdrop" data-bwfa-mixer-backdrop hidden></div>
|
||||
<div class="modal bwfa-mixer-modal" data-bwfa-mixer hidden>
|
||||
<div class="modal-dialog bwfa-mixer-dialog" role="dialog" aria-modal="true"
|
||||
aria-label="Mixer">
|
||||
<button type="button" class="modal-close" data-bwfa-mixer-close
|
||||
aria-label="Close">×</button>
|
||||
<div class="modal-header">
|
||||
<div class="bwfa-modal-title-group">
|
||||
<h3 class="modal-title">Mixer</h3>
|
||||
<span class="bwfa-modal-filename" data-bwfa-mixer-filename></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="bwfa-mixer-strips" data-bwfa-mixer-strips></div>
|
||||
<div class="bwfa-player-transport bwfa-mixer-transport">
|
||||
<button type="button" class="btn btn-sm btn-primary bwfa-player-playpause"
|
||||
data-bwfa-mixer-playpause><span class="bwfa-btn-label"
|
||||
data-bwfa-label>Play</span></button>
|
||||
<span class="bwfa-player-time" data-bwfa-mixer-elapsed>00:00:00</span>
|
||||
<canvas class="bwfa-player-waveform" data-bwfa-mixer-waveform
|
||||
width="700" height="56"
|
||||
aria-label="Waveform — click to seek"></canvas>
|
||||
<span class="bwfa-player-time" data-bwfa-mixer-duration>00:00:00</span>
|
||||
</div>
|
||||
<p class="bwfa-mixer-note">This mix is for listening only. Exports and
|
||||
reports ignore it.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="bwfa-modal-nav bwfa-modal-nav-back">
|
||||
<button type="button" class="btn btn-sm btn-outline"
|
||||
data-bwfa-mixer-prev aria-label="Previous file">‹</button>
|
||||
<span class="bwfa-modal-nav-name" data-bwfa-mixer-prev-name></span>
|
||||
</div>
|
||||
<div class="bwfa-modal-nav bwfa-modal-nav-on">
|
||||
<span class="bwfa-modal-nav-name" data-bwfa-mixer-next-name></span>
|
||||
<button type="button" class="btn btn-sm btn-outline"
|
||||
data-bwfa-mixer-next aria-label="Next file">›</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-mixer-close>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop bwfa-spectro-backdrop" data-bwfa-spectro-backdrop hidden></div>
|
||||
<div class="modal bwfa-spectro-modal" data-bwfa-spectro hidden>
|
||||
<div class="modal-dialog bwfa-spectro-dialog" role="dialog" aria-modal="true"
|
||||
aria-label="Spectrogram">
|
||||
<button type="button" class="modal-close" data-bwfa-spectro-close
|
||||
aria-label="Close">×</button>
|
||||
<div class="bwfa-spectro-head">
|
||||
<h4 data-bwfa-spectro-title>Spectrogram</h4>
|
||||
<p data-bwfa-spectro-note></p>
|
||||
<button type="button" class="btn btn-sm btn-outline bwfa-spectro-save"
|
||||
data-bwfa-spectro-save>Save Image</button>
|
||||
</div>
|
||||
<div class="bwfa-spectro-plot">
|
||||
<div class="bwfa-spectro-scale" data-bwfa-spectro-scale></div>
|
||||
<canvas class="bwfa-spectro-canvas" data-bwfa-spectro-canvas></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bwfa-player-transport">
|
||||
<button type="button" class="btn btn-sm btn-primary bwfa-player-playpause" data-bwfa-player-playpause><span class="bwfa-btn-label" data-bwfa-label>Play</span></button>
|
||||
<span class="bwfa-player-time" data-bwfa-player-elapsed>00:00:00</span>
|
||||
<canvas class="bwfa-player-waveform" data-bwfa-player-waveform width="700" height="56"
|
||||
aria-label="Waveform — click to seek"></canvas>
|
||||
<span class="bwfa-player-time" data-bwfa-player-duration>00:00:00</span>
|
||||
</div>
|
||||
<div class="bwfa-channel-row" data-bwfa-channel-row>
|
||||
<span class="bwfa-channel-hint">Click a channel to mute it, double-click to solo it:</span>
|
||||
<div class="chip-group bwfa-channel-chips" data-bwfa-channel-chips></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover bwfa-table">
|
||||
<thead data-bwfa-table-head></thead>
|
||||
<tbody data-bwfa-table-body></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bwfa-empty" data-bwfa-empty>No files analysed yet.</div>
|
||||
|
||||
<!-- Which columns the table shows. Opened by the cogwheel above the Play
|
||||
column, since that's where you are when you want it. -->
|
||||
<div class="modal-backdrop" data-bwfa-columns-backdrop hidden></div>
|
||||
<div class="modal" data-bwfa-columns-modal role="dialog" aria-modal="true"
|
||||
aria-labelledby="bwfa-app-1-columns-title" hidden>
|
||||
<div class="modal-dialog bwfa-columns-dialog">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="bwfa-app-1-columns-title">Settings</h3>
|
||||
<button type="button" class="modal-close" data-bwfa-columns-close aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<details class="bwfa-settings-section" data-bwfa-settings="appearance">
|
||||
<summary>Appearance</summary>
|
||||
<div class="bwfa-settings-body">
|
||||
<div class="bwfa-choice-list">
|
||||
<label class="bwfa-choice">
|
||||
<input type="radio" name="bwfa-app-1-theme" value="auto" checked data-bwfa-theme-mode>
|
||||
<span>Match the system<small>Follow macOS as it changes through the day</small></span>
|
||||
</label>
|
||||
<label class="bwfa-choice">
|
||||
<input type="radio" name="bwfa-app-1-theme" value="light" data-bwfa-theme-mode>
|
||||
<span>Light<small>Paper white, whatever the system is doing</small></span>
|
||||
</label>
|
||||
<label class="bwfa-choice">
|
||||
<input type="radio" name="bwfa-app-1-theme" value="dark" data-bwfa-theme-mode>
|
||||
<span>Dark<small>Anthracite, for a night shoot or a dark suite</small></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details class="bwfa-settings-section" data-bwfa-settings="playback">
|
||||
<summary>When a file finishes</summary>
|
||||
<div class="bwfa-settings-body">
|
||||
<div class="bwfa-choice-list">
|
||||
<label class="bwfa-choice">
|
||||
<input type="radio" name="bwfa-app-1-playback-mode" value="stop" checked
|
||||
data-bwfa-playback-mode>
|
||||
<span>Stop<small>Hold it in the player, ready to play again</small></span>
|
||||
</label>
|
||||
<label class="bwfa-choice">
|
||||
<input type="radio" name="bwfa-app-1-playback-mode" value="next" data-bwfa-playback-mode>
|
||||
<span>Play the next file<small>Work down the list as it's sorted and filtered</small></span>
|
||||
</label>
|
||||
<label class="bwfa-choice">
|
||||
<input type="radio" name="bwfa-app-1-playback-mode" value="repeat" data-bwfa-playback-mode>
|
||||
<span>Repeat it<small>Loop the same file until you stop it</small></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="bwfa-settings-action" data-bwfa-audio-reset-row>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-bwfa-audio-reset>Restart the app</button>
|
||||
<small>If a file plays with no sound at all, this rebuilds the audio path
|
||||
and reloads the window — the same thing quitting and reopening does,
|
||||
without the quitting. The folder comes back with it.</small>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details class="bwfa-settings-section" data-bwfa-settings="columns">
|
||||
<summary>Columns in the table</summary>
|
||||
<div class="bwfa-settings-body">
|
||||
<div class="bwfa-columns-menu" data-bwfa-columns-menu></div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bwfa-columns-close>Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The sound report: who it's for, what format it lands in, and which
|
||||
fields it carries. One field list for both formats, because a sound
|
||||
report is a sound report whichever way it's written. -->
|
||||
<div class="modal-backdrop" data-bwfa-export-backdrop hidden></div>
|
||||
<div class="modal" data-bwfa-export-modal role="dialog" aria-modal="true"
|
||||
aria-labelledby="bwfa-app-1-export-title" hidden>
|
||||
<div class="modal-dialog bwfa-export-dialog">
|
||||
<div class="modal-header">
|
||||
<div class="bwfa-modal-title-group">
|
||||
<h3 class="modal-title" id="bwfa-app-1-export-title">Sound Report</h3>
|
||||
<span class="bwfa-modal-filename" data-bwfa-report-subject></span>
|
||||
</div>
|
||||
<button type="button" class="modal-close" data-bwfa-export-close aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="bwfa-report-details">
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-company">Production company</label>
|
||||
<input type="text" class="form-control" id="bwfa-app-1-report-company"
|
||||
data-bwfa-report-field="company" autocomplete="off">
|
||||
</div>
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-project">Project / show name</label>
|
||||
<input type="text" class="form-control" id="bwfa-app-1-report-project"
|
||||
data-bwfa-report-field="project" autocomplete="off">
|
||||
</div>
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-director">Director</label>
|
||||
<input type="text" class="form-control" id="bwfa-app-1-report-director"
|
||||
data-bwfa-report-field="director" autocomplete="off">
|
||||
</div>
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-mixer">Sound mixer</label>
|
||||
<input type="text" class="form-control" id="bwfa-app-1-report-mixer"
|
||||
data-bwfa-report-field="mixer" autocomplete="off">
|
||||
</div>
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-phone">Mixer phone</label>
|
||||
<input type="text" class="form-control" id="bwfa-app-1-report-phone"
|
||||
data-bwfa-report-field="phone" autocomplete="off">
|
||||
</div>
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-email">Mixer email</label>
|
||||
<input type="text" class="form-control" id="bwfa-app-1-report-email"
|
||||
data-bwfa-report-field="email" autocomplete="off">
|
||||
</div>
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-note">Note</label>
|
||||
<input type="text" class="form-control" id="bwfa-app-1-report-note"
|
||||
data-bwfa-report-field="note" autocomplete="off">
|
||||
</div>
|
||||
<div class="bwfa-report-row">
|
||||
<label class="form-label" for="bwfa-app-1-report-format">Output format</label>
|
||||
<select class="form-select" id="bwfa-app-1-report-format" data-bwfa-report-format>
|
||||
<option value="pdf" selected>PDF (sound report)</option>
|
||||
<option value="csv">CSV (spreadsheet)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bwfa-report-fields-head">
|
||||
<h4>Fields in the report</h4>
|
||||
<span class="bwfa-report-count" data-bwfa-export-count></span>
|
||||
<div class="bwfa-export-actions-row">
|
||||
<button type="button" class="btn btn-sm btn-outline" data-bwfa-export-all>Select all</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-bwfa-export-none>Select none</button>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-bwfa-export-defaults>Restore defaults</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bwfa-export-fields" data-bwfa-export-fields></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" data-bwfa-export-close>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bwfa-report-create>Create Report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-backdrop" data-bwfa-modal-backdrop hidden></div>
|
||||
<div
|
||||
class="modal"
|
||||
data-bwfa-modal
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="bwfa-app-1-modal-title"
|
||||
hidden
|
||||
>
|
||||
<div class="modal-dialog bwfa-modal-dialog">
|
||||
<div class="modal-header">
|
||||
<div class="bwfa-modal-title-group">
|
||||
<h3 class="modal-title" id="bwfa-app-1-modal-title">File metadata</h3>
|
||||
<span class="bwfa-modal-filename" data-bwfa-modal-filename></span>
|
||||
</div>
|
||||
<button type="button" class="modal-close" data-bwfa-modal-close aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body" data-bwfa-modal-body></div>
|
||||
<div class="modal-footer">
|
||||
<div class="bwfa-modal-nav bwfa-modal-nav-back">
|
||||
<button type="button" class="btn btn-sm btn-outline" data-bwfa-modal-prev
|
||||
aria-label="Previous file">‹</button>
|
||||
<span class="bwfa-modal-nav-name" data-bwfa-modal-prev-name></span>
|
||||
</div>
|
||||
<div class="bwfa-modal-nav bwfa-modal-nav-on">
|
||||
<span class="bwfa-modal-nav-name" data-bwfa-modal-next-name></span>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-bwfa-modal-next
|
||||
aria-label="Next file">›</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" data-bwfa-modal-save hidden disabled>Save</button>
|
||||
<button type="button" class="btn btn-secondary" data-bwfa-modal-play>Play</button>
|
||||
<button type="button" class="btn btn-outline" data-bwfa-modal-close>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user