mirror of
https://github.com/forkless/Piperless.git
synced 2026-08-18 17:32:31 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bd853ba21 | ||
|
|
a59c4c8ef0 | ||
|
|
c61205cf9b | ||
|
|
c0596cde7c | ||
|
|
0ac8bc93ad | ||
|
|
ca1e44c408 | ||
|
|
72b0829218 | ||
|
|
7c24206d11 | ||
|
|
539816b800 | ||
|
|
afe006fde7 | ||
|
|
8e6582dd33 | ||
|
|
257befe9a7 | ||
|
|
bd45b5c47d | ||
|
|
bf57b04ccb |
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
All notable changes to the Piperless WordPress plugin.
|
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
|
## [1.1.3] — 2026-05-11
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ all: translations
|
|||||||
# ── Build ────────────────────────────────────────────────────────────────────
|
# ── Build ────────────────────────────────────────────────────────────────────
|
||||||
build:
|
build:
|
||||||
@echo "=== Building Piperless ==="
|
@echo "=== Building Piperless ==="
|
||||||
./build.sh
|
./tools/build.sh
|
||||||
|
|
||||||
# ── Translations ─────────────────────────────────────────────────────────────
|
# ── Translations ─────────────────────────────────────────────────────────────
|
||||||
translations:
|
translations:
|
||||||
@@ -85,5 +85,6 @@ help:
|
|||||||
@echo " make check-translations Validate translation integrity & lock state"
|
@echo " make check-translations Validate translation integrity & lock state"
|
||||||
@echo " make json2po Convert all locale JSON files → .po files"
|
@echo " make json2po Convert all locale JSON files → .po files"
|
||||||
@echo " make build Create distribution ZIP via build.sh"
|
@echo " make build Create distribution ZIP via build.sh"
|
||||||
|
@echo " make release Build, tag, push, and upload a GitHub release"
|
||||||
@echo " make clean Remove build artifacts"
|
@echo " make clean Remove build artifacts"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ Piperless converts every published post into a natural-sounding audio transcript
|
|||||||
|
|
||||||
- **Visually impaired** readers can listen instead of read
|
- **Visually impaired** readers can listen instead of read
|
||||||
- **Dyslexic** readers get an alternative format
|
- **Dyslexic** readers get an alternative format
|
||||||
- **Low-literacy** audiences can access your content
|
|
||||||
- **Commuting** readers can consume posts as audio
|
- **Commuting** readers can consume posts as audio
|
||||||
- **Non-native speakers** benefit from hearing correct pronunciation
|
- **Non-native speakers** benefit from hearing correct pronunciation
|
||||||
|
|
||||||
@@ -28,6 +27,7 @@ Piperless converts every published post into a natural-sounding audio transcript
|
|||||||
- **Smart text extraction** — uses the post excerpt when available to avoid reading embedded content (YouTube, Twitter, third-party embeds), decimal numbers, and other artifacts that produce garbled speech. Falls back to the full post body when no excerpt is set.
|
- **Smart text extraction** — uses the post excerpt when available to avoid reading embedded content (YouTube, Twitter, third-party embeds), decimal numbers, and other artifacts that produce garbled speech. Falls back to the full post body when no excerpt is set.
|
||||||
- **No external services** — Piper runs on your server. Text never leaves your infrastructure. GDPR-compliant by architecture.
|
- **No external services** — Piper runs on your server. Text never leaves your infrastructure. GDPR-compliant by architecture.
|
||||||
- **Production-hardened** — 29/29 security audit clearance. Rate limiting, authorization layering, process timeout guards, open_basedir aware.
|
- **Production-hardened** — 29/29 security audit clearance. Rate limiting, authorization layering, process timeout guards, open_basedir aware.
|
||||||
|
- **Dashboard widget** — at-a-glance storage stats on the WordPress dashboard (togglable via Screen Options). File counts and sizes by audio format, posts awaiting transcription, and disk usage bar.
|
||||||
- **8 admin languages** — Dutch, German, French, Spanish, Chinese (Simplified), Japanese, Brazilian Portuguese, and Italian translations included.
|
- **8 admin languages** — Dutch, German, French, Spanish, Chinese (Simplified), Japanese, Brazilian Portuguese, and Italian translations included.
|
||||||
|
|
||||||
|
|
||||||
@@ -117,6 +117,7 @@ Piperless adds a settings page under **Settings → Piperless** with 8 tabs:
|
|||||||
| **Cache Management** | Stats, cache browser, clear orphaned files, flush entire cache |
|
| **Cache Management** | Stats, cache browser, clear orphaned files, flush entire cache |
|
||||||
| **Logs** | Logging level configuration, live log viewer, clear log |
|
| **Logs** | Logging level configuration, live log viewer, clear log |
|
||||||
| **About** | Version, GitHub repository, and support links |
|
| **About** | Version, GitHub repository, and support links |
|
||||||
|
| **Dashboard** | WordPress admin dashboard widget with audio storage stats and disk usage |
|
||||||
|
|
||||||
|
|
||||||
## Player Themes
|
## Player Themes
|
||||||
@@ -206,6 +207,7 @@ includes/
|
|||||||
├── class-cache-manager.php # Content-addressed file cache, MP3 conversion
|
├── class-cache-manager.php # Content-addressed file cache, MP3 conversion
|
||||||
├── class-gutenberg.php # Block editor sidebar, REST API endpoints
|
├── class-gutenberg.php # Block editor sidebar, REST API endpoints
|
||||||
├── class-settings.php # Admin panel (8 tabs), AJAX handlers
|
├── class-settings.php # Admin panel (8 tabs), AJAX handlers
|
||||||
|
├── class-dashboard.php # Dashboard widget with file stats and disk usage
|
||||||
├── class-player.php # Frontend HTML5 player (6 themes)
|
├── class-player.php # Frontend HTML5 player (6 themes)
|
||||||
└── class-logger.php # PSR-3 logger with dual-channel fallback
|
└── class-logger.php # PSR-3 logger with dual-channel fallback
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -236,6 +236,18 @@
|
|||||||
color: #757575;
|
color: #757575;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.piperless-about-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-about-link-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Cache browser badges */
|
/* Cache browser badges */
|
||||||
.piperless-cache-badge {
|
.piperless-cache-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* Piperless Dashboard Widget Styles
|
||||||
|
*
|
||||||
|
* Card-based layout for the WordPress admin dashboard widget
|
||||||
|
* showing audio storage stats at a glance.
|
||||||
|
*
|
||||||
|
* @package Piperless
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ── Dashboard container ───────────────────────────────────────────────── */
|
||||||
|
.piperless-dashboard {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-empty {
|
||||||
|
color: #757575;
|
||||||
|
font-style: italic;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Stat cards grid ───────────────────────────────────────────────────── */
|
||||||
|
.piperless-dash-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-card {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-left: 4px solid var(--card-accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-card-value {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #1d2327;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-card-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #646970;
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-card-sub {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #757575;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Info rows ─────────────────────────────────────────────────────────── */
|
||||||
|
.piperless-dash-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-info-text {
|
||||||
|
flex: 1;
|
||||||
|
color: #1d2327;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-info-link {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #2271b1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-info-link:hover {
|
||||||
|
color: #135e96;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Color highlights per info row ─────────────────────────────────────── */
|
||||||
|
.piperless-dash-info-posts {
|
||||||
|
border-left: 4px solid #dba617;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-info-storage {
|
||||||
|
border-left: 4px solid #2271b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Storage usage bar ─────────────────────────────────────────────────── */
|
||||||
|
.piperless-dash-info-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
background: #dcdcde;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: #2271b1;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piperless-dash-bar-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
+3
-3
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Piperless v1.1.3 — Developer Documentation</title>
|
<title>Piperless v1.1.4 — Developer Documentation</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg: #ffffff;
|
--bg: #ffffff;
|
||||||
@@ -55,7 +55,7 @@ td:first-child { font-family: "SF Mono", Monaco, monospace; font-size: 13px; }
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<h2>Piperless 1.1.3</h2>
|
<h2>Piperless 1.1.4</h2>
|
||||||
<a href="#overview">Overview</a>
|
<a href="#overview">Overview</a>
|
||||||
<a href="#architecture">Architecture</a>
|
<a href="#architecture">Architecture</a>
|
||||||
<a href="#plugin">Plugin (Orchestrator)</a>
|
<a href="#plugin">Plugin (Orchestrator)</a>
|
||||||
@@ -74,7 +74,7 @@ td:first-child { font-family: "SF Mono", Monaco, monospace; font-size: 13px; }
|
|||||||
<main>
|
<main>
|
||||||
|
|
||||||
<h1>Piperless</h1>
|
<h1>Piperless</h1>
|
||||||
<p>WordPress Plugin — Audio Transcripts via Piper TTS · v1.1.3 · Generated 2026-05-11</p>
|
<p>WordPress Plugin — Audio Transcripts via Piper TTS · v1.1.4 · Generated 2026-05-11</p>
|
||||||
|
|
||||||
<h2 id="overview">Overview</h2>
|
<h2 id="overview">Overview</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -0,0 +1,319 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Dashboard widget — audio storage at a glance.
|
||||||
|
*
|
||||||
|
* @package Piperless
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Piperless;
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard widget.
|
||||||
|
*
|
||||||
|
* Registers a WordPress dashboard widget (togglable via Screen Options)
|
||||||
|
* that shows:
|
||||||
|
*
|
||||||
|
* - Counts and total size of WAV, MP3, and Opus files in the cache
|
||||||
|
* - Number of published posts without an audio transcript
|
||||||
|
* - Available disk space on the server
|
||||||
|
*
|
||||||
|
* @since 1.1.4
|
||||||
|
*/
|
||||||
|
class Dashboard {
|
||||||
|
|
||||||
|
/** @var Logger */
|
||||||
|
private Logger $logger;
|
||||||
|
|
||||||
|
/** @var Cache_Manager */
|
||||||
|
private Cache_Manager $cache;
|
||||||
|
|
||||||
|
/** @var string Widget slug. */
|
||||||
|
private string $widget_id = 'piperless_dashboard';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param Logger $logger Logger.
|
||||||
|
* @param Cache_Manager $cache Cache manager.
|
||||||
|
*/
|
||||||
|
public function __construct( Logger $logger, Cache_Manager $cache ) {
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register hooks.
|
||||||
|
*/
|
||||||
|
public function init(): void {
|
||||||
|
add_action( 'wp_dashboard_setup', [ $this, 'register_widget' ] );
|
||||||
|
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the dashboard widget.
|
||||||
|
*/
|
||||||
|
public function register_widget(): void {
|
||||||
|
wp_add_dashboard_widget(
|
||||||
|
$this->widget_id,
|
||||||
|
__( 'Piperless — Audio Storage', 'piperless' ),
|
||||||
|
[ $this, 'render' ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the dashboard widget.
|
||||||
|
*/
|
||||||
|
public function render(): void {
|
||||||
|
$file_stats = $this->get_file_stats();
|
||||||
|
$without = $this->get_posts_without_audio();
|
||||||
|
$disk = $this->get_disk_usage();
|
||||||
|
$total_files = ( $file_stats['mp3']['count'] ?? 0 )
|
||||||
|
+ ( $file_stats['wav']['count'] ?? 0 )
|
||||||
|
+ ( $file_stats['opus']['count'] ?? 0 );
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="piperless-dashboard">
|
||||||
|
<?php if ( 0 === $total_files ) : ?>
|
||||||
|
<p class="piperless-dash-empty"><?php esc_html_e( 'No audio files generated yet. Publish a post or generate audio from the editor sidebar.', 'piperless' ); ?></p>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="piperless-dash-grid">
|
||||||
|
<?php $this->render_file_card(
|
||||||
|
__( 'Opus', 'piperless' ),
|
||||||
|
$file_stats['opus'] ?? [ 'count' => 0, 'bytes' => 0 ],
|
||||||
|
'#1565c0'
|
||||||
|
); ?>
|
||||||
|
<?php $this->render_file_card(
|
||||||
|
__( 'MP3', 'piperless' ),
|
||||||
|
$file_stats['mp3'] ?? [ 'count' => 0, 'bytes' => 0 ],
|
||||||
|
'#008a20'
|
||||||
|
); ?>
|
||||||
|
<?php $this->render_file_card(
|
||||||
|
__( 'WAV', 'piperless' ),
|
||||||
|
$file_stats['wav'] ?? [ 'count' => 0, 'bytes' => 0 ],
|
||||||
|
'#f56e28'
|
||||||
|
); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="piperless-dash-info">
|
||||||
|
<div class="piperless-dash-info-item piperless-dash-info-posts">
|
||||||
|
<span class="piperless-dash-info-text">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %d: number of posts without audio */
|
||||||
|
esc_html( _n(
|
||||||
|
'%d post without an audio transcript.',
|
||||||
|
'%d posts without an audio transcript.',
|
||||||
|
$without,
|
||||||
|
'piperless'
|
||||||
|
) ),
|
||||||
|
$without
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
<?php if ( $without > 0 ) : ?>
|
||||||
|
<a href="<?php echo esc_url( admin_url( 'edit.php?post_type=post' ) ); ?>" class="piperless-dash-info-link">
|
||||||
|
<?php esc_html_e( 'View posts', 'piperless' ); ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="piperless-dash-info-item piperless-dash-info-storage">
|
||||||
|
<div class="piperless-dash-info-body">
|
||||||
|
<span class="piperless-dash-info-text">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: 1: free space, 2: total space */
|
||||||
|
esc_html__( 'Server storage: %1$s free of %2$s', 'piperless' ),
|
||||||
|
esc_html( $this->human_size( $disk['free'] ) ),
|
||||||
|
esc_html( $this->human_size( $disk['total'] ) )
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
<div class="piperless-dash-bar">
|
||||||
|
<div class="piperless-dash-bar-fill" style="width:<?php echo esc_attr( (string) round( $disk['pct'] ) ); ?>%;"></div>
|
||||||
|
</div>
|
||||||
|
<span class="piperless-dash-bar-label">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: percentage used */
|
||||||
|
esc_html__( '%s%% used', 'piperless' ),
|
||||||
|
esc_html( (string) round( $disk['pct'] ) )
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single file-type stat card.
|
||||||
|
*
|
||||||
|
* @param string $label e.g. "MP3".
|
||||||
|
* @param array $stats ['count' => int, 'bytes' => int].
|
||||||
|
* @param string $color Accent color hex.
|
||||||
|
*/
|
||||||
|
private function render_file_card( string $label, array $stats, string $color ): void {
|
||||||
|
$count = $stats['count'] ?? 0;
|
||||||
|
$size = $this->human_size( $stats['bytes'] ?? 0 );
|
||||||
|
?>
|
||||||
|
<div class="piperless-dash-card" style="--card-accent: <?php echo esc_attr( $color ); ?>;">
|
||||||
|
<div class="piperless-dash-card-value"><?php echo esc_html( (string) $count ); ?></div>
|
||||||
|
<div class="piperless-dash-card-label"><?php echo esc_html( $label ); ?></div>
|
||||||
|
<div class="piperless-dash-card-sub"><?php echo esc_html( $size ); ?></div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan the cache directory and return per-format file counts and byte totals.
|
||||||
|
*
|
||||||
|
* @return array<string,array{count:int,bytes:int}>
|
||||||
|
*/
|
||||||
|
private function get_file_stats(): array {
|
||||||
|
$cache_dir = $this->cache->dir();
|
||||||
|
$stats = [
|
||||||
|
'mp3' => [ 'count' => 0, 'bytes' => 0 ],
|
||||||
|
'wav' => [ 'count' => 0, 'bytes' => 0 ],
|
||||||
|
'opus' => [ 'count' => 0, 'bytes' => 0 ],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( ! is_dir( $cache_dir ) ) {
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( [ 'mp3', 'wav', 'opus' ] as $ext ) {
|
||||||
|
$files = (array) glob( $cache_dir . '/*.' . $ext );
|
||||||
|
foreach ( $files as $file ) {
|
||||||
|
$size = @filesize( $file );
|
||||||
|
if ( false !== $size ) {
|
||||||
|
$stats[ $ext ]['count']++;
|
||||||
|
$stats[ $ext ]['bytes'] += $size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count published posts / pages without an audio transcript.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function get_posts_without_audio(): int {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$types = $this->supported_post_types();
|
||||||
|
$placeholders = implode( ',', array_fill( 0, count( $types ), '%s' ) );
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
$count = $wpdb->get_var(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT COUNT(*)
|
||||||
|
FROM {$wpdb->posts} p
|
||||||
|
WHERE p.post_type IN ( {$placeholders} )
|
||||||
|
AND p.post_status = 'publish'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM {$wpdb->postmeta} pm
|
||||||
|
WHERE pm.post_id = p.ID
|
||||||
|
AND pm.meta_key = '_piperless_audio_url'
|
||||||
|
AND pm.meta_value != ''
|
||||||
|
)",
|
||||||
|
...$types
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (int) $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get disk usage stats for the uploads directory volume.
|
||||||
|
*
|
||||||
|
* @return array{total:float,free:float,used:float,pct:float}
|
||||||
|
*/
|
||||||
|
private function get_disk_usage(): array {
|
||||||
|
$dir = $this->cache->dir();
|
||||||
|
|
||||||
|
// Walk up until we find a directory that exists.
|
||||||
|
while ( ! is_dir( $dir ) && '/' !== $dir ) {
|
||||||
|
$dir = dirname( $dir );
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = @disk_total_space( $dir );
|
||||||
|
$free = @disk_free_space( $dir );
|
||||||
|
|
||||||
|
$fallback = [ 'total' => 0.0, 'free' => 0.0, 'used' => 0.0, 'pct' => 0.0 ];
|
||||||
|
|
||||||
|
if ( false === $total || null === $total || false === $free || null === $free || $total <= 0.0 ) {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
$used = $total - $free;
|
||||||
|
$pct = ( $used / $total ) * 100.0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => (float) $total,
|
||||||
|
'free' => (float) $free,
|
||||||
|
'used' => (float) $used,
|
||||||
|
'pct' => (float) $pct,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format bytes as a human-readable string.
|
||||||
|
*
|
||||||
|
* @param float $bytes Raw byte count.
|
||||||
|
* @return string e.g. "24.5 MB"
|
||||||
|
*/
|
||||||
|
private function human_size( float $bytes ): string {
|
||||||
|
if ( $bytes <= 0.0 ) {
|
||||||
|
return '0 B';
|
||||||
|
}
|
||||||
|
|
||||||
|
$units = [ 'B', 'KB', 'MB', 'GB', 'TB' ];
|
||||||
|
$exp = (int) floor( log( $bytes, 1024 ) );
|
||||||
|
$exp = min( $exp, count( $units ) - 1 );
|
||||||
|
|
||||||
|
$val = $bytes / ( 1024 ** $exp );
|
||||||
|
|
||||||
|
return round( $val, 1 ) . ' ' . $units[ $exp ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue dashboard-specific styles on the dashboard page only.
|
||||||
|
*
|
||||||
|
* @param string $hook_suffix Current admin page hook.
|
||||||
|
*/
|
||||||
|
public function enqueue_styles( string $hook_suffix ): void {
|
||||||
|
if ( 'index.php' !== $hook_suffix ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'piperless-dashboard',
|
||||||
|
PIPERLESS_PLUGIN_URL . 'assets/css/dashboard.css',
|
||||||
|
[],
|
||||||
|
PIPERLESS_VERSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported post types for audio transcripts.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function supported_post_types(): array {
|
||||||
|
$types = apply_filters( 'piperless_post_types', [ 'post', 'page' ] );
|
||||||
|
return is_array( $types ) ? $types : [ 'post' ];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,9 @@ class Plugin {
|
|||||||
/** @var Gutenberg */
|
/** @var Gutenberg */
|
||||||
private Gutenberg $gutenberg;
|
private Gutenberg $gutenberg;
|
||||||
|
|
||||||
|
/** @var Dashboard */
|
||||||
|
private Dashboard $dashboard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor — use ::instance().
|
* Private constructor — use ::instance().
|
||||||
*/
|
*/
|
||||||
@@ -68,6 +71,7 @@ class Plugin {
|
|||||||
$this->transcriber = new Transcriber( $this->logger, $this->piper, $this->cache_manager );
|
$this->transcriber = new Transcriber( $this->logger, $this->piper, $this->cache_manager );
|
||||||
$this->player = new Player( $this->logger );
|
$this->player = new Player( $this->logger );
|
||||||
$this->gutenberg = new Gutenberg( $this->logger, $this->transcriber, $this->cache_manager );
|
$this->gutenberg = new Gutenberg( $this->logger, $this->transcriber, $this->cache_manager );
|
||||||
|
$this->dashboard = new Dashboard( $this->logger, $this->cache_manager );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,6 +93,7 @@ class Plugin {
|
|||||||
$this->settings->init();
|
$this->settings->init();
|
||||||
$this->gutenberg->init();
|
$this->gutenberg->init();
|
||||||
$this->player->init();
|
$this->player->init();
|
||||||
|
$this->dashboard->init();
|
||||||
|
|
||||||
// Auto-generate on publish.
|
// Auto-generate on publish.
|
||||||
add_action( 'transition_post_status', [ $this, 'maybe_auto_generate' ], 10, 3 );
|
add_action( 'transition_post_status', [ $this, 'maybe_auto_generate' ], 10, 3 );
|
||||||
|
|||||||
@@ -634,10 +634,16 @@ class Settings {
|
|||||||
<?php esc_html_e( 'Piperless turns your WordPress posts into audio transcripts. Developed by Forkless, it makes your content accessible, engaging, and easy to listen to. Quick setup, high-quality speech, and seamless integration.', 'piperless' ); ?>
|
<?php esc_html_e( 'Piperless turns your WordPress posts into audio transcripts. Developed by Forkless, it makes your content accessible, engaging, and easy to listen to. Quick setup, high-quality speech, and seamless integration.', 'piperless' ); ?>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="https://github.com/forkless/Piperless" target="_blank"><?php esc_html_e( 'GitHub Repository', 'piperless' ); ?></a>
|
<a href="https://github.com/forkless/Piperless" target="_blank" class="piperless-about-link">
|
||||||
|
<img src="<?php echo esc_url( PIPERLESS_PLUGIN_URL . 'assets/svg/github.svg' ); ?>" class="piperless-about-link-icon" alt="" aria-hidden="true" width="18" height="18">
|
||||||
|
<?php esc_html_e( 'GitHub Repository', 'piperless' ); ?>
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="https://buymeacoffee.com/forkless" target="_blank"><?php esc_html_e( 'Buy me a coffee', 'piperless' ); ?></a>
|
<a href="https://buymeacoffee.com/forkless" target="_blank" class="piperless-about-link">
|
||||||
|
<img src="<?php echo esc_url( PIPERLESS_PLUGIN_URL . 'assets/svg/coffee.svg' ); ?>" class="piperless-about-link-icon" alt="" aria-hidden="true" width="18" height="18">
|
||||||
|
<?php esc_html_e( 'Buy me a coffee', 'piperless' ); ?>
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p class="piperless-about-contact">
|
<p class="piperless-about-contact">
|
||||||
<?php esc_html_e( 'Questions? Contact the developers at', 'piperless' ); ?>
|
<?php esc_html_e( 'Questions? Contact the developers at', 'piperless' ); ?>
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: Piperless — Audio Transcripts
|
* Plugin Name: Piperless — Audio Transcripts
|
||||||
* Plugin URI: https://forkless.com
|
* Plugin URI: https://forkless.com
|
||||||
* Description: Generate audio transcripts of WordPress posts using Piper TTS. Customizable players, caching, and full Gutenberg integration.
|
* Description: Generate audio transcripts of WordPress posts using Piper TTS. Customizable players, caching, and full Gutenberg integration.
|
||||||
* Version: 1.1.2
|
* Version: 1.1.4
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 8.0
|
* Requires PHP: 8.0
|
||||||
* Author: Forkless
|
* Author: Forkless
|
||||||
@@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Constants ────────────────────────────────────────────────────────────────
|
// ── Constants ────────────────────────────────────────────────────────────────
|
||||||
define( 'PIPERLESS_VERSION', '1.1.3' );
|
define( 'PIPERLESS_VERSION', '1.1.4' );
|
||||||
define( 'PIPERLESS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'PIPERLESS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'PIPERLESS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
define( 'PIPERLESS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||||
define( 'PIPERLESS_PLUGIN_FILE', __FILE__ );
|
define( 'PIPERLESS_PLUGIN_FILE', __FILE__ );
|
||||||
|
|||||||
@@ -13,18 +13,19 @@ set -euo pipefail
|
|||||||
|
|
||||||
PLUGIN_SLUG="piperless"
|
PLUGIN_SLUG="piperless"
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
PROJECT_DIR="$( cd "$SCRIPT_DIR/.." && pwd )"
|
||||||
|
|
||||||
# ── Read version from plugin header ───────────────────────────────────────
|
# ── Read version from plugin header ───────────────────────────────────────
|
||||||
VERSION=$( grep -oP '^\s*\*\s*Version:\s*\K[0-9.]+' "${SCRIPT_DIR}/${PLUGIN_SLUG}.php" | head -1 )
|
VERSION=$( grep -oP '^\s*\*\s*Version:\s*\K[0-9.]+' "${PROJECT_DIR}/${PLUGIN_SLUG}.php" | head -1 )
|
||||||
|
|
||||||
if [[ -z "${VERSION}" ]]; then
|
if [[ -z "${VERSION}" ]]; then
|
||||||
echo "Error: could not determine plugin version from ${PLUGIN_SLUG}.php"
|
echo "Error: could not determine plugin version from ${PLUGIN_SLUG}.php"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build"
|
BUILD_DIR="${PROJECT_DIR}/build"
|
||||||
STAGING="${BUILD_DIR}/${PLUGIN_SLUG}"
|
STAGING="${BUILD_DIR}/${PLUGIN_SLUG}"
|
||||||
ZIP_FILE="${SCRIPT_DIR}/${PLUGIN_SLUG}-${VERSION}.zip"
|
ZIP_FILE="${PROJECT_DIR}/${PLUGIN_SLUG}-${VERSION}.zip"
|
||||||
|
|
||||||
echo "=== Piperless Build Script ==="
|
echo "=== Piperless Build Script ==="
|
||||||
echo "Version: ${VERSION}"
|
echo "Version: ${VERSION}"
|
||||||
@@ -49,7 +50,7 @@ INCLUDE=(
|
|||||||
)
|
)
|
||||||
|
|
||||||
for item in "${INCLUDE[@]}"; do
|
for item in "${INCLUDE[@]}"; do
|
||||||
src="${SCRIPT_DIR}/${item}"
|
src="${PROJECT_DIR}/${item}"
|
||||||
if [[ -e "${src}" ]]; then
|
if [[ -e "${src}" ]]; then
|
||||||
cp -r "${src}" "${STAGING}/"
|
cp -r "${src}" "${STAGING}/"
|
||||||
else
|
else
|
||||||
@@ -67,7 +68,7 @@ find "${STAGING}" -name '*.map' -delete
|
|||||||
echo "Creating ZIP archive…"
|
echo "Creating ZIP archive…"
|
||||||
cd "${BUILD_DIR}"
|
cd "${BUILD_DIR}"
|
||||||
zip -r "${ZIP_FILE}" "${PLUGIN_SLUG}" > /dev/null
|
zip -r "${ZIP_FILE}" "${PLUGIN_SLUG}" > /dev/null
|
||||||
cd "${SCRIPT_DIR}"
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
# ── Verify ────────────────────────────────────────────────────────────────
|
# ── Verify ────────────────────────────────────────────────────────────────
|
||||||
ZIP_SIZE=$( du -h "${ZIP_FILE}" | cut -f1 )
|
ZIP_SIZE=$( du -h "${ZIP_FILE}" | cut -f1 )
|
||||||
Reference in New Issue
Block a user