Files
Piperless/CHANGELOG.md
T

361 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to the Piperless WordPress plugin.
## [1.1.4] — 2026-05-12
### Added
- **Dashboard widget** — new "Piperless — Audio Storage" widget on the WordPress admin dashboard (togglable via Screen Options). Shows Opus/MP3/WAV file counts and total sizes in a card grid, published posts without audio transcripts with a quick "View posts" link, and server disk usage with a horizontal usage bar and percentage indicator.
### Changed
- **About tab** — SVG icons now appear inline before the GitHub Repository and Buy Me a Coffee links for a polished look.
### Fixed
- **Dashboard TypeError** — `human_size()` parameter changed from `int` to `float` to handle `disk_free_space()` return type.
## [1.1.3] — 2026-05-11
### Changed
- **About tab** — removed Help tab, added GitHub Repository and Buy Me a Coffee links with official SVG icons
- **Style descriptions** — cleaned hex color codes from all player theme descriptions
## [1.1.1] — 2026-05-10
### Fixed
- **Player not rendering for logged-out users** — removed `in_the_loop()` guard from `maybe_prepend_player()`. Some themes override `in_the_loop()` in non-admin contexts, causing the player HTML to be suppressed for logged-out visitors. `is_main_query()` is retained as the sole duplicate-check guard.
### Changed
- **Audio Format field moved above MP3 Bitrate** — the Piper tab now shows Audio Format first, then the relevant bitrate selectors.
## [1.1.0] — 2026-05-10
### Added
- **Opus audio format** — new "Audio Format" selector (MP3 / Opus) in the Piper tab. Opus encodes with `libopus` (`-application voip`) for better quality at lower bitrates. Separate bitrate selector for Opus: 24k (standard), 16k (compact), 12k (minimal). Cache key includes format so switching regenerates files.
- **Gutenberg sidebar format support** — preview player uses `<source>` with explicit `type="audio/ogg"` for Opus files. Format tracked in `_piperless_audio_format` post meta and returned in REST responses.
- **ffprobe-based duration detection** — for MP3 and Opus files, uses `find_ffprobe()` (same directory as resolved ffmpeg) to read exact duration. Falls back to WAV header calculation.
- **Cache entry deletion clears post meta** — deleting an entry now removes all related meta fields from the owning post.
- **Server-side cache sorting** — Size and Created columns now sort the entire dataset before pagination, not just the current page.
- **Professional pagination** — Previous/Next/First/Last buttons, smart page numbers with ellipsis, "X items" count, placed at both top and bottom of the cache browser.
### Changed
- **FFmpeg Binaries Path** — field renamed from "FFmpeg Binary Path" to reflect that the directory is used for the full toolchain (ffmpeg, ffprobe). Description updated to mention MP3/Opus.
- **Cache scanning includes `.opus`** — all cache methods (`get_entries`, `clear`, `delete`, `stats`, `clear_orphans`) now handle `.opus` alongside `.mp3` and `.wav`.
- **Opus cache badge** — blue badge (#1565c0) in the cache browser Format column.
- **Translations** — all 8 locales at 118/118 strings.
## [1.0.0] — 2026-05-09
### Added
- **Voice model aliases** — assign custom display names to voice models from the Piper tab. Aliases appear in admin dropdowns and the Gutenberg sidebar, making technical model names like "en_US-lessac-low" readable as "American Female". Stored in `piperless_settings['voice_aliases']`.
- **Accessibility landmark** — player container exposes `role="region"` + `aria-label="Audio transcript: Post Title"`. Screen readers announce the player's purpose and allow keyboard navigation as a landmark region.
- **Configurable player max-width** — new "Player Max Width" field in the Styling tab. Set to any pixel value (default 680) or 0 for 100% width. Injected as inline CSS; works across all 6 themes and admin preview.
- **"Alias" string** translated to all 5 locales (de_DE, fr_FR, es_ES, nl_NL, zh_CN).
### Changed
- **Voice column** in model table shows original lowercase names (`amy`, `lessac`) instead of `ucfirst()`.
- **Gutenberg voice dropdown** wired to use aliases from the REST API.
- **Title moved inside player container** — `__title` div is now a flex child of `.piperless-player`, using `flex-wrap: wrap` + `flex: 0 0 100%`. Eliminates gap between title and controls.
- **Translations** — all 5 locales at 100/100 strings including "Audio transcript: %s" accessibility label.
## [0.8.2] — 2026-05-09
### Fixes
- **Player `max-width: 680px` restored** after earlier 100% width experiment.
- **CSS cache bust**
## [0.8.1] — 2026-05-09
### Changed
- **Title moved inside player flex container** — `__title` is now a child of `.piperless-player` with `flex-wrap: wrap`. Applies to all 6 themes.
- **Base player margin** reduced from `1.5em` to `0.5em` vertical.
## [0.8.0] — 2026-05-09
### Fixed
- **Title spacing gap** — `.piperless-player__title` `margin-bottom` set to `0`, player `margin` reduced to close gap between title and player on themed frontends.
## [0.7.9] — 2026-05-09
### Fixed
- **Title margin** — `.piperless-player__title` removed `margin-bottom: 4px`.
- Removed unused `.piperless-player__title + .piperless-player` sibling collapse rule (never matched DOM structure).
## [0.7.8] — 2026-05-09
### Security
- **`escapeshellcmd``escapeshellarg`** on ffmpeg binary path in `Cache_Manager::put()`. All shell calls now use `escapeshellarg` — zero `escapeshellcmd()` calls remain in the codebase.
- **Removed `FOLLOW_SYMLINKS`** from model directory scanner to prevent symbolic links from escaping the allowed models directory.
- **Removed duplicate database query** in `Cache_Manager::get_entries()` — dead code that re-queried `_piperless_cache_key` meta into an unused variable.
## [0.7.7] — 2026-05-09
### Added
- **Player Preview field label** — "Player Preview" now appears as a proper left-aligned label in the Styling tab.
- **Show Duration preview live** — toggling the checkbox instantly shows/hides the duration in the player preview, no save required.
### Changed
- **Model table layout** — removed the Name column. "Available models" now appears as a field label to the left. Columns are shrink-wrapped (`table-layout: auto`, `max-width: 620px`) so the Preview button stays near the data on wide screens.
- **Model table sorting** — Voice and Quality columns are click-to-sort. Voice sorts alphabetically; Quality sorts by tier priority (low → medium → high). Sort arrows initialize on page load.
- **Voice names capitalized** in model table.
- **Minimal player style** — removed the bottom border divider for a cleaner look.
## [0.7.6] — 2026-05-09
### Changed
- _Incremental CSS/JS cache-bust bumps._
## [0.7.2] — 2026-05-09
### Added
- **Sortable model table** — Voice and Quality columns in the model preview table are now click-to-sort. Voice sorts alphabetically; Quality sorts by tier priority (low → medium → high). Sort arrows show active column and direction on load.
### Changed
- **Voice names capitalized** — model table voice column now displays with the first letter uppercased (e.g., "Amy").
- **Per-post overrides** — Gutenberg sidebar gained Player Style and Placement dropdowns in a new Display Settings panel, separate from Voice Settings. Per-post meta (`_piperless_style`, `_piperless_placement`) overrides global settings.
## [0.7.1] — 2026-05-09
### Added
_Nothing new — version skipped due to uploader glitch._
## [0.7.0] — 2026-05-09
### Added
- **NewsViews Classic player style** — based on the NewsViews theme with `#233452` dark navy replacing the burgundy accent.
- **Above & below content** placement option — renders the player both before and after the post content.
- **Dropdown separators** — visual divider lines in the Player Style and Placement dropdowns, separating preset options from Custom CSS / Manual.
### Changed
- **Style dropdown reordered** — sorted alphabetically (Classic, Minimal, Modern Dark, NewsViews, NewsViews Classic) with Custom CSS remaining last.
- **Player width** — removed `max-width: 680px` from base styles. Players now fill 100% of their container.
### Fixed
- **Audio player progress bar, seeking, and time display** — all three now work on WAV-only setups. The REST proxy gained HTTP `Range` request support (`206 Partial Content` + `Content-Range`), which browsers require for duration detection and seeking. Removed the hardcoded `type="audio/wav"` attribute so the browser auto-detects MIME type from the proxy's `Content-Type` header. Preload changed from `metadata` to `auto`.
- **`is_path_accessible()` fatal crash** — when `realpath()` returned `false` on parent directories outside `open_basedir`, the method fell back to a non-string causing `str_starts_with()` TypeError.
- **`file_exists('/')` warning** — parent-directory walker in `is_path_accessible()` now stops at filesystem root.
- **ffmpeg `open_basedir` warnings** — `find_ffmpeg()` now suppresses `file_exists()`/`is_executable()` on default and custom paths.
## [0.6.3] — 2026-05-09
### Fixed
- **Audio player progress bar, seeking, and time display** — all three now work on WAV-only setups. The REST proxy gained HTTP `Range` request support (`206 Partial Content` + `Content-Range`), which browsers require for duration detection and seeking. Removed the hardcoded `type="audio/wav"` attribute so the browser auto-detects MIME type from the proxy's `Content-Type` header. Preload changed from `metadata` to `auto` to ensure the browser reads enough data for duration.
- **`is_path_accessible()` fatal crash** — when `realpath()` returned `false` on parent directories outside `open_basedir`, `$real_path` became `false` instead of a string, crashing `str_starts_with()`. Now falls back to the original path string.
- **`file_exists('/')` warning** — the parent-directory walker in `is_path_accessible()` now stops at filesystem root (`'/' !== $parent`) and suppresses all `file_exists()`/`realpath()` calls with `@`.
- **ffmpeg `open_basedir` warnings** — `find_ffmpeg()` now uses `@` suppression on all `file_exists()`/`is_executable()` calls for both custom and default paths.
### Changed
- **Player width** — removed `max-width: 680px` from player base styles. Players now fill 100% of their container.
## [0.6.2] — 2026-05-09
### Fixed
- **`file_exists('/')` warning on `open_basedir`** — `is_path_accessible()` parent-directory walker now stops at filesystem root (`'/' !== $parent`) and suppresses `file_exists()` and `realpath()` with `@`. Prevents the walker from checking `/` when the configured path doesn't exist on the server.
## [0.6.1] — 2026-05-09
### Fixed
- **Admin panel timeout on `open_basedir`** — `check_binary_status()`, `scan_models()`, and `synthesise()` now check `open_basedir` with a pure string-path comparison before any filesystem call. On hosts where `file_exists()`/`is_dir()` hang on blocked paths (stale NFS mounts, slow network filesystems), the page now loads instantly. Filesystem calls also use `@` suppression as a second layer against warning-log delays.
## [0.6.0] — 2026-05-09
### Added
- **Performance tab** — configurable Piper process timeout (303600s, default 300) and audio endpoint rate limit (1600 req/min per IP, default 60). Both clamp to safe ranges and pre-fill with existing defaults.
### Changed
- **Block text extraction rewritten** — the Skip Embedded Content path now extracts text directly from the block tree (`innerHTML` recursion) instead of calling `render_block()` + `the_content`. Fixes a regression where paragraphs and headings were lost, leaving only the post title.
- **Raw PCM synthesis streams to temp file** — Piper's stdout is now streamed in 64KB chunks to a temp file instead of loaded entirely into memory via `stream_get_contents`. Avoids 25+ MB peaks on long posts.
### Security
- **REST endpoints** — `rest_status`, `rest_generate`, and `rest_remove_audio` now verify per-post ownership (`current_user_can('edit_post', $post_id)`) in addition to the `edit_posts` capability gate.
- **Shell escaping** — all `escapeshellcmd` calls replaced with `escapeshellarg` for binary paths across all three synthesis modes and `run_help()`. Zero `escapeshellcmd` remaining.
- **Error capture** — `error_clear_last()` called before every `@`-suppressed filesystem operation that's followed by `log_last_error()`, making error attribution deterministic.
- **CSS sanitization** — custom CSS now strips lines containing `url()`, `expression()`, `@import`, `behavior:`, and `-moz-binding` in addition to HTML tag removal.
- **Rate limiting** — audio proxy endpoint now rate-limited per IP via transient (configurable in Performance tab).
- **Synthesis mutex** — only one Piper process per cache key at a time; concurrent requests for the same content receive a "generation already in progress" response.
- **Auto-generate deferred** — `maybe_auto_generate` now schedules a `wp_schedule_single_event` cron job instead of running Piper synchronously on publish, preventing browser hangs.
- **Log file permissions** — `chmod 0600` applied after every log write, restricting read to the file owner on servers without `.htaccess` protection.
- **REST models endpoint** — absolute filesystem paths stripped from the response; only the model basename is exposed.
- **Shutdown hooks** — `stream_file()` now fires `do_action('shutdown')` before `die()`, allowing WordPress cron spawners and cleanup callbacks to execute.
## [0.5.0] — 2026-05-09
### Added
- **`set_time_limit()` guard** on all Piper synthesis calls — bumps the PHP execution time limit to 300s during `proc_open`, then restores it. Prevents `max_execution_time` kills on long posts.
- **Logger error fallback** — when the dedicated `piperless.log` can't be written (unwritable uploads directory), log entries fall through to PHP's `error_log()`, ensuring diagnostics are never completely lost.
- **`log_last_error()` helper** — captures `error_get_last()` after `@`-suppressed filesystem operations (failed `file_put_contents`, `mkdir`, `file_get_contents`) and writes the OS-level error to the log. Wired into `Cache_Manager` and `Piper` model validation.
### Changed
- **`scan_models()` cached per request** — the models directory is now scanned once and the result reused across admin page load (previously 3×), model preview table, and JS localisation.
## [0.4.0] — 2026-05-09
### Added
- **Binary pre-flight check** — on every admin page load, the plugin checks whether the Piper binary is configured, exists, and is executable using only filesystem calls (no binary invocation, no timeout risk). A dismissible WordPress admin notice warns the user with a specific message for each state: not configured, not found, or not executable.
## [0.3.1] — 2026-05-09
### Changed
- **Logging Level** moved from Content tab to Logs tab — now appears as the first setting above the log viewer, with its own Save button.
## [0.3.0] — 2026-05-09
### Added
- **Skip Embedded Content** setting — when enabled and no manual excerpt exists, Gutenberg embed blocks (core/embed, core-embed/*, extensible via `piperless_skip_blocks` filter) are stripped from the body before text extraction.
- **nl_NL (Dutch) translation** — full locale support with 84 translated strings, compiled .po/.mo.
- **Translation toolchain** — `tools/pot2json.sh`, `tools/sync-translations.sh`, `tools/json2po.sh`, `tools/lock-translations.sh`, and `Makefile` targets for extracting, syncing, converting, and locking per-locale JSON translation files.
### Changed
- **Admin panel restructured** — the single Configuration tab is now three: **Piper** (TTS engine settings), **Content** (auto-generate, skip embeds, logging), and **Styling** (player appearance/placement). All three share one form.
- Model preview table and player preview moved within their respective tabs.
## [1.1.6] — 2026-05-08
### Fixed
- Model enumeration regression from 1.1.5: models without a `.onnx.json` companion file are no longer excluded. Only models where the `.json` exists but is broken (LFS pointer, empty, malformed JSON) are filtered out. Missing `.json` is allowed since native Piper reads config from the `.onnx` file itself.
## [1.1.5] — 2026-05-08
### Fixed
- `scan_models()` now validates each model's `.onnx.json` companion file before listing it. Detects: missing JSON, empty files, Git LFS pointer files (common on Hugging Face without git-lfs), and malformed JSON. Models with broken configs are excluded from the UI and logged with specific warnings in the Debug Log.
## [1.1.4] — 2026-05-08
### Fixed
- Quality tier detection expanded to recognize `lite`, `small`, `fast`, `quality` in addition to `low`/`medium`/`high`. Filterable via `piperless_quality_tiers`.
- `scan_models()` now warns in the Debug Log when a model's companion `.onnx.json` file is missing or empty — the leading cause of `JSONDecodeError` with the Python Piper wheel.
- Default Quality dropdown in admin now includes Lite and Small options.
## [1.1.3] — 2026-05-08
### Added
- **open_basedir detection**: all filesystem checks (binary path, models directory) now detect when the path is outside PHP's `open_basedir` restriction and show a clear, actionable error message. The Piper Configuration section header displays the current open_basedir status.
- Admin page load now writes an INFO-level log entry, so the Debug Log always shows at least one entry when the page is accessed — proving the logger works.
### Changed
- `scan_models()` logs a WARNING when the models directory is outside open_basedir.
- Test Connection response now includes `open_basedir` info.
## [1.1.2] — 2026-05-08
### Fixed
- Debug Log viewer was non-functional — it called the wrong AJAX endpoint and never displayed log data. Now renders server-side on page load (`tail 50`) and refreshes via the new `piperless_log_tail` AJAX handler.
- Test Connection error for missing binary now includes the exact path that was checked, making misconfiguration immediately visible.
## [1.1.1] — 2026-05-08
### Fixed
- `scan_models()` now wraps the recursive directory iterator in try-catch and logs the scanned directory path + file count. Permission errors or unreadable subdirectories no longer silently return an empty list — the error appears in the Debug Log.
- Switched from `SplFileInfo::getExtension()` to `pathinfo(…, PATHINFO_EXTENSION)` for broader PHP compatibility with the recursive iterator.
- Added `FOLLOW_SYMLINKS` flag so symlinked model directories are followed.
## [1.1.0] — 2026-05-08
### Added
- **Positional wrapper mode** — for custom scripts like `/opt/bin/run-piper` that take `binary model_path "text"` as positional arguments and write `output.wav` to CWD. Choose "Positional" in the new Piper Interface Mode setting.
- `piper_interface` setting (Auto-detect / Standard / Positional) — overrides auto-detection for non-standard wrappers. Test Connection reports the effective mode.
### Changed
- `scan_models()` now scans the models directory recursively using `RecursiveDirectoryIterator`, so models nested in subdirectories are discovered.
- The Default Voice text field is now a dropdown populated from discovered model voice names, with an "— Use first available —" empty option.
- Default `models_directory` changed to `/opt/var/piper/voices`.
## [1.0.4] — 2026-05-08
### Fixed
- Test Connection no longer treats a non-zero `--help` exit as a hard failure. Python wheel wrappers that don't implement `--help` now report OK (binary found) with the detected output mode. The `--help` invocation is also cached so `detect_output_mode()` and `test()` share a single call.
## [1.0.3] — 2026-05-08
### Added
- Auto-detection of Piper output mode: `Piper` class now probes `--help` to determine whether the binary supports `--output-raw` (native CLI) or `--output_file` (Python wheel wrappers like `/opt/bin/run-piper`). Falls back to file mode for unknown wrappers. Test Connection button now shows the detected mode.
### Changed
- Default Piper binary path is now empty (force user to configure), with an updated description mentioning Python wheel wrappers.
## [1.0.2] — 2026-05-08
### Fixed
- Removed invalid module-level `useDispatch()` call in gutenberg.js that crashed the script with "Invalid hook call", preventing the Gutenberg sidebar from appearing at all. `createNotice` was already correctly obtained inside the component.
## [1.0.1] — 2026-05-08
### Fixed
- Autoloader now converts underscores to hyphens in class names, fixing "Class 'Piperless\\Cache_Manager' not found" fatal error on plugin activation.
## [1.0.0] — 2024-01-01
### Added
- Piper TTS integration via CLI wrapper (`Piper` class) with proc_open, raw PCM → WAV conversion, length-scale quality control, and voice model scanner.
- Audio transcript generation pipeline (`Transcriber` class): excerpt-first text extraction, SHA-256 content caching, WAV duration parsing from RIFF headers.
- Hash-based audio cache (`Cache_Manager`) in `wp-content/uploads/piperless/` with orphan detection (cross-referenced post meta), flush, and stats.
- PSR-3-style file logger (`Logger`) with 8 severity levels, WordPress debug.log integration, and dedicated `piperless.log`.
- Admin settings page under Settings → Piperless with three sections:
- **Piper Configuration** — binary path, models directory, default voice/language/quality, logging level, and Test Connection button.
- **Voice Model Previews** — discovered models table with per-model audio preview (play/pause, cached per model + quality).
- **Audio Player Settings** — 3 preset themes (Classic, Modern Dark, Minimal) + Custom CSS, player placement (before/after/manual), duration toggle.
- **Player Preview** — live theme preview that swaps on dropdown change.
- **Cache Management** — clear orphaned audio, flush entire cache, live stats.
- **Debug Log** — log viewer with refresh and clear.
- Gutenberg PluginSidebar with Generate/Regenerate Audio button, per-post voice/language/quality overrides (SelectControls), audio preview player, and remove button.
- Custom HTML5 audio player (zero-dependency JS) with play/pause SVG toggle, click-to-seek progress bar, buffer indicator, duration display (mm:ss), touch support, MutationObserver for dynamic content, error state handling, and multi-instance support.
- REST API endpoints: POST generate, GET status, GET models, DELETE audio.
- Shortcode: `[piperless_player post_id="123"]`.
- `the_content` filter auto-placement (before/after/manual).
- `build.sh` — reads version from plugin header, creates `piperless-X.Y.Z.zip`.
- `uninstall.php` — removes all options, post meta, cached files, and scheduled hooks.
- Full i18n support with `piperless.pot` (100+ strings).
- Activation version checks (WP 6.0+, PHP 8.0+) with user-facing error messages.
- Security: ABSPATH guards, `index.php` in all directories, `.htaccess` deny in cache dir, nonce verification on all AJAX/REST endpoints, capability checks, input sanitization, output escaping.