Release v1.48.0
Some checks are pending
Test and SonarQube / quality (push) Waiting to run

This commit is contained in:
mARTin-B78 2026-09-22 23:00:00 +02:00
parent 0fa042f494
commit 633aede41c
5 changed files with 27 additions and 12 deletions

View File

@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
--- ---
## [1.48.0] — 2026-09-22
### Added
- **Magazine: "Generate all audio" resumes instead of redoing everything.** It now only renders approved articles that don't already have an MP3 — safe to click again after an aborted or partial run (a container restart mid-batch, closing the tab, etc.) without re-synthesising what already finished.
---
## [1.47.1] — 2026-09-22 ## [1.47.1] — 2026-09-22
### Added ### Added
@ -4358,7 +4365,8 @@ Initial feature-complete release.
--- ---
[Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.47.1...HEAD [Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.48.0...HEAD
[1.48.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.47.1...v1.48.0
[1.47.1]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.47.0...v1.47.1 [1.47.1]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.47.0...v1.47.1
[1.47.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.46.0...v1.47.0 [1.47.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.46.0...v1.47.0
[1.46.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.45.0...v1.46.0 [1.46.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.45.0...v1.46.0

View File

@ -1 +1 @@
1.47.1 1.48.0

View File

@ -10,7 +10,7 @@
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB"> <meta name="theme-color" content="#2563EB">
<meta name="app-version" content="1.47.1"> <meta name="app-version" content="1.48.0">
<link rel="manifest" href="/manifest.webmanifest"> <link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml"> <link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg"> <link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,12 +27,12 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── --> <!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css"> <link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.47.1"> <link rel="stylesheet" href="/static/style.css?v=1.48.0">
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.47.1"> <link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.48.0">
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.47.1"> <link rel="stylesheet" href="/static/css/background-jobs.css?v=1.48.0">
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.47.1"> <link rel="stylesheet" href="/static/css/voice-design.css?v=1.48.0">
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.47.1"> <link rel="stylesheet" href="/static/css/rehearser.css?v=1.48.0">
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.47.1"> <link rel="stylesheet" href="/static/css/audiobook.css?v=1.48.0">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── --> <!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -403,7 +403,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script> </script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton --> <!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.47.1"></script> <script src="/static/loader.js?v=1.48.0"></script>
</body> </body>
</html> </html>

View File

@ -820,6 +820,13 @@ function renderContentsBulkBar() {
async function magGenerateAllAudio() { async function magGenerateAllAudio() {
const voice = ($('mag-audio-voice')?.value || '').trim(); const voice = ($('mag-audio-voice')?.value || '').trim();
if (!voice) return say('Pick a narrator voice for the export first', 'err'); if (!voice) return say('Pick a narrator voice for the export first', 'err');
// Skip whatever already has audio -- resuming after an aborted or
// partial run (confirmed live: a magazine this size can genuinely take
// a while, and killing the job partway through, deliberately or via a
// container restart, is a real scenario, not an edge case) should not
// mean re-synthesising articles that are already done.
const missing = state.articles.filter((a) => a.status === 'approved' && !a.has_audio).map((a) => a.id);
if (!missing.length) return say('Every approved article already has audio', '');
const btn = $('mag-audio-export-btn'); const btn = $('mag-audio-export-btn');
const status = $('mag-audio-export-status'); const status = $('mag-audio-export-status');
const progressWrap = $('mag-audio-export-progress-wrap'); const progressWrap = $('mag-audio-export-progress-wrap');
@ -831,7 +838,7 @@ async function magGenerateAllAudio() {
try { try {
const r = await api('/api/magazine/' + state.doc.id + '/audio/batch', { const r = await api('/api/magazine/' + state.doc.id + '/audio/batch', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ voice }), body: JSON.stringify({ voice, article_ids: missing }),
}); });
jobId = r.job_id; jobId = r.job_id;
} catch (e) { } catch (e) {

View File

@ -84,7 +84,7 @@
<label for="mag-audio-voice">Narrator for exported MP3s</label> <label for="mag-audio-voice">Narrator for exported MP3s</label>
<select id="mag-audio-voice"></select> <select id="mag-audio-voice"></select>
<button type="button" class="btn-sm" id="mag-audio-export-btn" onclick="magGenerateAllAudio()" <button type="button" class="btn-sm" id="mag-audio-export-btn" onclick="magGenerateAllAudio()"
title="Render every approved article to a single MP3 with its title, author, cover image and full text embedded as tags -- for a phone's own music/podcast player or a car head unit"> title="Render every approved article that doesn't already have an MP3 -- title, author, cover image and full text embedded as tags -- for a phone's own music/podcast player or a car head unit. Safe to click again after an aborted run: already-generated articles are skipped.">
<span class="mdi mdi-file-music-outline"></span> Generate all audio (MP3) <span class="mdi mdi-file-music-outline"></span> Generate all audio (MP3)
</button> </button>
<a class="btn-sm" id="mag-audio-zip-link" href="#" hidden download> <a class="btn-sm" id="mag-audio-zip-link" href="#" hidden download>