Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be338c2de0 | ||
|
|
06c077b7f8 |
12
__init__.py
12
__init__.py
@ -1,4 +1,5 @@
|
||||
from flask import Blueprint, render_template, request
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
blueprint = Blueprint(
|
||||
'vue3_neusik',
|
||||
@ -14,3 +15,14 @@ def index():
|
||||
'vue3_neusik/index.html',
|
||||
import_on_load='true' if request.args.get('import') == '1' else 'false',
|
||||
)
|
||||
|
||||
@blueprint.route('/audiowidget', methods=['GET'])
|
||||
def audiowidget():
|
||||
try:
|
||||
return render_template(
|
||||
'vue3_neusik/audiowidget.tmpl',
|
||||
result_url=request.args.get('result_url', ''),
|
||||
errors=request.args.get('errors', ''),
|
||||
)
|
||||
except TemplateNotFound:
|
||||
return '', 204
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
{# audiowidget.tmpl.stub
|
||||
Copy to neusician/templates/ and rename to audiowidget.tmpl.
|
||||
Include from base.tmpl or any score page with:
|
||||
{% include 'audiowidget.tmpl' %}
|
||||
|
||||
Variables expected in context (all provided by /sompyle/status.json
|
||||
via the sompyler_status_json route):
|
||||
result_url – URL to the rendered audio file (e.g. /sompyle/result.mp3)
|
||||
errors – non-empty string on synthesis failure, else empty/absent
|
||||
|
||||
The default JS audio widget in PaneCP renders a plain <audio> element.
|
||||
Replace or extend this stub to match your site's look and feel.
|
||||
#}
|
||||
|
||||
{% if errors %}
|
||||
<div class="se-error">{{ errors }}</div>
|
||||
{% elif result_url %}
|
||||
<figure class="audio-result">
|
||||
<figcaption>Rendered result</figcaption>
|
||||
<audio controls preload="metadata">
|
||||
<source src="{{ result_url }}" type="audio/mpeg">
|
||||
<a href="{{ result_url }}">Download MP3</a>
|
||||
</audio>
|
||||
</figure>
|
||||
{% endif %}
|
||||
@ -44,3 +44,12 @@ export async function fetchStatus(credentials) {
|
||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchAudioWidget(resultUrl, errors) {
|
||||
const params = new URLSearchParams();
|
||||
if (resultUrl) params.set('result_url', resultUrl);
|
||||
if (errors) params.set('errors', errors);
|
||||
const res = await fetch(`${U.audiowidget}?${params}`);
|
||||
if (!res.ok || res.status === 204) return '';
|
||||
return res.text();
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { fetchScoreText, putScoreText, URLS } from '../api.js';
|
||||
import { h, ref, watch } from 'vue';
|
||||
import { fetchScoreText, putScoreText, fetchAudioWidget, URLS } from '../api.js';
|
||||
import { patchScore } from '../exporter.js';
|
||||
import { StatusPoller } from './StatusPoller.js';
|
||||
|
||||
@ -40,6 +40,18 @@ export const PaneCP = {
|
||||
setup(props) {
|
||||
const exporting = ref(false);
|
||||
const exportError = ref('');
|
||||
const audioWidgetHtml = ref('');
|
||||
|
||||
watch(
|
||||
() => props.store.synthesisStatus,
|
||||
async (s) => {
|
||||
if (!s?.frozen) { audioWidgetHtml.value = ''; return; }
|
||||
audioWidgetHtml.value = await fetchAudioWidget(
|
||||
s.file_accomplished ? URLS.result : null,
|
||||
s.errors ?? null,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
async function doExport() {
|
||||
exportError.value = '';
|
||||
@ -118,18 +130,10 @@ export const PaneCP = {
|
||||
store.synthesisStatus && !store.synthesisStatus.frozen
|
||||
? h(StatusPoller, { store }) : null,
|
||||
|
||||
// Synthesis error
|
||||
store.synthesisStatus?.frozen && store.synthesisStatus?.errors
|
||||
? h('div', { class: 'se-error', style: 'margin-top:0.5rem' },
|
||||
store.synthesisStatus.errors) : null,
|
||||
|
||||
// Audio result
|
||||
store.synthesisStatus?.file_accomplished
|
||||
? h('audio', {
|
||||
controls: true,
|
||||
src: URLS.result,
|
||||
style: 'display:block;width:100%;margin-top:0.5rem',
|
||||
}) : null,
|
||||
// Audio widget (rendered server-side from audiowidget.tmpl)
|
||||
audioWidgetHtml.value
|
||||
? h('div', { innerHTML: audioWidgetHtml.value, style: 'margin-top:0.5rem' })
|
||||
: null,
|
||||
]);
|
||||
};
|
||||
},
|
||||
|
||||
18
templates/vue3_neusik/audiowidget.tmpl.stub
Normal file
18
templates/vue3_neusik/audiowidget.tmpl.stub
Normal file
@ -0,0 +1,18 @@
|
||||
{# audiowidget.tmpl.stub — copy to audiowidget.tmpl in the same directory to activate.
|
||||
Customise to match your site's look and feel.
|
||||
|
||||
Context variables (passed as query params by the score editor):
|
||||
result_url – URL of the rendered MP3, or empty string
|
||||
errors – synthesis error message, or empty string
|
||||
#}
|
||||
{% if errors %}
|
||||
<div class="se-error">{{ errors }}</div>
|
||||
{% elif result_url %}
|
||||
<figure class="audio-result">
|
||||
<figcaption>Rendered result</figcaption>
|
||||
<audio controls preload="metadata" style="display:block;width:100%">
|
||||
<source src="{{ result_url }}" type="audio/mpeg">
|
||||
<a href="{{ result_url }}">Download MP3</a>
|
||||
</audio>
|
||||
</figure>
|
||||
{% endif %}
|
||||
@ -14,8 +14,9 @@ window.NEUSICIAN_URLS = {
|
||||
astlog: "{{ url_for('astlog') }}",
|
||||
score: "{{ url_for('score') }}",
|
||||
status: "{{ url_for('statusjson') }}",
|
||||
result: "{{ url_for('send_audio_generated') }}",
|
||||
submit: "{{ url_for('public-yaml-acceptor') }}"
|
||||
result: "{{ url_for('send_audio_generated') }}",
|
||||
submit: "{{ url_for('public-yaml-acceptor') }}",
|
||||
audiowidget: "{{ url_for('vue3_neusik.audiowidget') }}"
|
||||
};
|
||||
</script>
|
||||
<script type="importmap">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user