451 lines
16 KiB
HTML
451 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<meta name="description" content="Server-side video compression tool using FFmpeg. Browse, select, and compress large video files." />
|
|
<title>VideoPress — FFmpeg Compressor</title>
|
|
<link rel="stylesheet" href="/static/css/main.css" />
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<!-- Fallback font stack if Google Fonts unavailable -->
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Skip navigation for accessibility -->
|
|
<a href="#main-content" class="skip-link">Skip to main content</a>
|
|
|
|
<header class="app-header" role="banner">
|
|
<div class="header-inner">
|
|
<div class="logo" aria-label="VideoPress FFmpeg Compressor">
|
|
<span class="logo-icon" aria-hidden="true">▶</span>
|
|
<span class="logo-text">Video<strong>Press</strong></span>
|
|
</div>
|
|
<div class="header-actions">
|
|
<button
|
|
id="settings-btn"
|
|
class="btn-icon"
|
|
aria-label="Open SMTP email settings"
|
|
title="Email Settings"
|
|
>
|
|
<span aria-hidden="true">⚙</span>
|
|
</button>
|
|
<button
|
|
id="theme-toggle"
|
|
class="btn-icon"
|
|
aria-label="Toggle dark/light mode"
|
|
title="Toggle dark/light mode"
|
|
>
|
|
<span class="theme-icon" aria-hidden="true" id="theme-icon">◑</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="main-content" class="app-main" role="main">
|
|
|
|
<!-- STEP 1: Directory & Filter Configuration -->
|
|
<section class="card" id="section-config" aria-labelledby="config-heading">
|
|
<h2 id="config-heading" class="card-title">
|
|
<span class="step-badge" aria-hidden="true">01</span>
|
|
Configure Source
|
|
</h2>
|
|
|
|
<div class="config-grid">
|
|
<!-- Directory Browser -->
|
|
<div class="field-group">
|
|
<label for="dir-input" class="field-label">Server Directory</label>
|
|
<div class="dir-input-row">
|
|
<input
|
|
type="text"
|
|
id="dir-input"
|
|
class="text-input"
|
|
placeholder="/path/to/videos"
|
|
value="/"
|
|
aria-describedby="dir-hint"
|
|
autocomplete="off"
|
|
spellcheck="false"
|
|
/>
|
|
<button class="btn btn-secondary" id="browse-btn" aria-label="Browse server directories">
|
|
Browse
|
|
</button>
|
|
</div>
|
|
<p id="dir-hint" class="field-hint">Enter or browse to a directory on the server where videos are stored.</p>
|
|
</div>
|
|
|
|
<!-- Minimum File Size -->
|
|
<div class="field-group">
|
|
<label for="min-size-input" class="field-label">
|
|
Minimum File Size
|
|
<span class="field-unit">(GB)</span>
|
|
</label>
|
|
<input
|
|
type="number"
|
|
id="min-size-input"
|
|
class="text-input"
|
|
min="0.1"
|
|
max="1000"
|
|
step="0.1"
|
|
value="1.0"
|
|
aria-describedby="size-hint"
|
|
/>
|
|
<p id="size-hint" class="field-hint">Only files larger than this threshold will be listed.</p>
|
|
</div>
|
|
|
|
<!-- Suffix -->
|
|
<div class="field-group">
|
|
<label for="suffix-input" class="field-label">
|
|
Output Filename Suffix
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="suffix-input"
|
|
class="text-input"
|
|
value="_new"
|
|
placeholder="_new"
|
|
aria-describedby="suffix-hint"
|
|
maxlength="64"
|
|
/>
|
|
<p id="suffix-hint" class="field-hint">
|
|
Appended before the file extension. E.g. <code>movie_new.mp4</code>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-footer">
|
|
<button class="btn btn-primary btn-lg" id="scan-btn" aria-describedby="scan-status">
|
|
<span class="btn-icon-prefix" aria-hidden="true">⊙</span>
|
|
Scan for Files
|
|
</button>
|
|
<span id="scan-status" class="status-text" aria-live="polite" aria-atomic="true"></span>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Directory Browser Modal -->
|
|
<div
|
|
id="browser-modal"
|
|
class="modal-backdrop"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="browser-modal-title"
|
|
hidden
|
|
>
|
|
<div class="modal-panel">
|
|
<div class="modal-header">
|
|
<h3 id="browser-modal-title" class="modal-title">Browse Server Directory</h3>
|
|
<button class="btn-icon" id="close-browser" aria-label="Close directory browser">✕</button>
|
|
</div>
|
|
<div class="browser-path-bar">
|
|
<span class="browser-path-label" aria-label="Current path:">
|
|
<span id="browser-current-path" aria-live="polite">/</span>
|
|
</span>
|
|
</div>
|
|
<div id="browser-list" class="browser-list" role="listbox" aria-label="Directory contents" tabindex="0">
|
|
<p class="browser-loading">Loading…</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" id="browser-cancel">Cancel</button>
|
|
<button class="btn btn-primary" id="browser-select">Select This Directory</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STEP 2: File List -->
|
|
<section class="card" id="section-files" aria-labelledby="files-heading" hidden>
|
|
<h2 id="files-heading" class="card-title">
|
|
<span class="step-badge" aria-hidden="true">02</span>
|
|
Select Files to Compress
|
|
</h2>
|
|
|
|
<div class="files-toolbar" role="toolbar" aria-label="File selection controls">
|
|
<div class="toolbar-left">
|
|
<button class="btn btn-sm btn-outline" id="select-all-btn" aria-label="Mark all files for compression">
|
|
☑ Select All
|
|
</button>
|
|
<button class="btn btn-sm btn-outline" id="deselect-all-btn" aria-label="Unmark all files">
|
|
☐ Deselect All
|
|
</button>
|
|
</div>
|
|
<div class="toolbar-right">
|
|
<span id="selection-summary" class="selection-summary" aria-live="polite" aria-atomic="true">
|
|
0 of 0 selected
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrapper" role="region" aria-label="Video files list" tabindex="0">
|
|
<table class="file-table" id="file-table" aria-describedby="files-heading">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" class="col-check">
|
|
<span class="sr-only">Select</span>
|
|
</th>
|
|
<th scope="col" class="col-name">File Name</th>
|
|
<th scope="col" class="col-size">Size (GB)</th>
|
|
<th scope="col" class="col-bitrate">Current Bitrate</th>
|
|
<th scope="col" class="col-target">Target Bitrate</th>
|
|
<th scope="col" class="col-codec">Codec</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="file-tbody">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="card-footer">
|
|
<!-- Notification opt-in -->
|
|
<div class="notify-group" id="notify-group">
|
|
<div class="notify-checkbox-row">
|
|
<input
|
|
type="checkbox"
|
|
id="notify-chk"
|
|
class="notify-checkbox"
|
|
aria-describedby="notify-hint"
|
|
aria-controls="notify-email-row"
|
|
/>
|
|
<label for="notify-chk" class="notify-label">
|
|
Notify me when the compression run is complete.
|
|
</label>
|
|
</div>
|
|
<div class="notify-email-row" id="notify-email-row" hidden>
|
|
<label for="notify-email" class="field-label notify-email-label">
|
|
Email address
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id="notify-email"
|
|
class="text-input notify-email-input"
|
|
placeholder="you@example.com"
|
|
autocomplete="email"
|
|
aria-describedby="notify-hint"
|
|
aria-required="false"
|
|
maxlength="254"
|
|
/>
|
|
<p id="notify-hint" class="field-hint">
|
|
An email will be sent via your configured SMTP server when all files are processed.
|
|
Configure SMTP in <button class="btn-link" id="open-settings-from-hint">⚙ Settings</button>.
|
|
</p>
|
|
<p id="smtp-not-configured-warn" class="field-hint smtp-warn" hidden>
|
|
⚠ No SMTP server configured yet.
|
|
<button class="btn-link" id="open-settings-from-warn">Open ⚙ Settings</button>
|
|
to set one up before starting compression.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="notify-divider" aria-hidden="true"></div>
|
|
|
|
<button class="btn btn-primary btn-lg" id="compress-btn" disabled>
|
|
<span class="btn-icon-prefix" aria-hidden="true">⚡</span>
|
|
Compress Selected Files
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- STEP 3: Compression Progress -->
|
|
<section class="card" id="section-progress" aria-labelledby="progress-heading" hidden>
|
|
<h2 id="progress-heading" class="card-title">
|
|
<span class="step-badge" aria-hidden="true">03</span>
|
|
Compression Progress
|
|
<button
|
|
class="btn btn-sm btn-outline reconnect-btn"
|
|
id="reconnect-btn"
|
|
aria-label="Reconnect to live progress stream"
|
|
title="Stream disconnected — click to reconnect"
|
|
hidden
|
|
>
|
|
⟳ Reconnect
|
|
</button>
|
|
</h2>
|
|
|
|
<!-- Stream-lost warning banner -->
|
|
<div
|
|
id="stream-lost-banner"
|
|
class="stream-lost-banner"
|
|
role="alert"
|
|
aria-live="assertive"
|
|
hidden
|
|
>
|
|
<span class="banner-icon" aria-hidden="true">⚠</span>
|
|
<span class="banner-text">
|
|
Live progress stream disconnected — the compression is still running on the server.
|
|
</span>
|
|
<button class="btn btn-sm btn-primary" id="reconnect-btn-banner">
|
|
⟳ Reconnect Now
|
|
</button>
|
|
</div>
|
|
|
|
<div class="progress-overview" role="region" aria-label="Overall progress">
|
|
<div class="overview-stats">
|
|
<div class="stat-chip">
|
|
<span class="stat-label">Total Files</span>
|
|
<span class="stat-value" id="prog-total">—</span>
|
|
</div>
|
|
<div class="stat-chip">
|
|
<span class="stat-label">Completed</span>
|
|
<span class="stat-value" id="prog-done">0</span>
|
|
</div>
|
|
<div class="stat-chip">
|
|
<span class="stat-label">Status</span>
|
|
<span class="stat-value" id="prog-status" aria-live="polite">Waiting</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overall-bar-wrap" aria-label="Overall compression progress">
|
|
<div class="overall-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" id="overall-bar">
|
|
<div class="overall-bar-fill" id="overall-bar-fill" style="width:0%"></div>
|
|
</div>
|
|
<span class="overall-pct" id="overall-pct" aria-hidden="true">0%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="file-progress-list" class="file-progress-list" aria-label="Individual file progress" role="list">
|
|
</div>
|
|
|
|
<div class="card-footer">
|
|
<button class="btn btn-danger" id="cancel-btn" aria-label="Cancel all compression operations">
|
|
✕ Cancel Compression
|
|
</button>
|
|
<span id="notify-status" class="notify-status" aria-live="polite" aria-atomic="true" hidden></span>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Results summary (shown after done/cancelled) -->
|
|
<section class="card" id="section-results" aria-labelledby="results-heading" hidden>
|
|
<h2 id="results-heading" class="card-title">
|
|
<span class="step-badge" aria-hidden="true">04</span>
|
|
Results
|
|
</h2>
|
|
<div id="results-content" class="results-content"></div>
|
|
<div class="card-footer">
|
|
<button class="btn btn-secondary" id="restart-btn">
|
|
↺ Start New Session
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Settings modal -->
|
|
<div
|
|
id="settings-modal"
|
|
class="modal-backdrop"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="settings-modal-title"
|
|
hidden
|
|
>
|
|
<div class="modal-panel settings-panel">
|
|
<div class="modal-header">
|
|
<h3 id="settings-modal-title" class="modal-title">⚙ Email / SMTP Settings</h3>
|
|
<button class="btn-icon" id="close-settings" aria-label="Close settings">✕</button>
|
|
</div>
|
|
|
|
<div class="settings-body">
|
|
<p class="settings-intro">
|
|
Configure your outgoing mail server so VideoPress can send completion
|
|
notifications. Settings are saved on the server in a local SQLite database.
|
|
</p>
|
|
|
|
<div class="settings-grid">
|
|
<!-- SMTP Host -->
|
|
<div class="field-group">
|
|
<label for="smtp-host" class="field-label">SMTP Server Host</label>
|
|
<input type="text" id="smtp-host" class="text-input"
|
|
placeholder="smtp.example.com"
|
|
autocomplete="off" spellcheck="false" />
|
|
</div>
|
|
|
|
<!-- Port + Security -->
|
|
<div class="settings-row-2">
|
|
<div class="field-group">
|
|
<label for="smtp-port" class="field-label">Port</label>
|
|
<input type="number" id="smtp-port" class="text-input"
|
|
value="587" min="1" max="65535" />
|
|
</div>
|
|
<div class="field-group">
|
|
<label for="smtp-security" class="field-label">Security</label>
|
|
<select id="smtp-security" class="text-input select-input">
|
|
<option value="tls">STARTTLS (587)</option>
|
|
<option value="ssl">SSL / TLS (465)</option>
|
|
<option value="none">None (25)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- From address -->
|
|
<div class="field-group">
|
|
<label for="smtp-from" class="field-label">From Address</label>
|
|
<input type="text" id="smtp-from" class="text-input"
|
|
placeholder="videopress@yourdomain.com"
|
|
autocomplete="off"
|
|
spellcheck="false" />
|
|
</div>
|
|
|
|
<!-- Username -->
|
|
<div class="field-group">
|
|
<label for="smtp-user" class="field-label">
|
|
Username
|
|
<span class="field-unit">(optional)</span>
|
|
</label>
|
|
<input type="text" id="smtp-user" class="text-input"
|
|
placeholder="user@yourdomain.com"
|
|
autocomplete="off" />
|
|
</div>
|
|
|
|
<!-- Password -->
|
|
<div class="field-group">
|
|
<label for="smtp-password" class="field-label">
|
|
Password
|
|
<span class="field-unit">(optional)</span>
|
|
</label>
|
|
<div class="password-row">
|
|
<input type="password" id="smtp-password" class="text-input"
|
|
placeholder="Leave blank to keep existing password"
|
|
autocomplete="new-password" />
|
|
<button type="button" class="btn-icon btn-icon-inline"
|
|
id="toggle-password"
|
|
aria-label="Show or hide password"
|
|
title="Show / hide password">👁</button>
|
|
</div>
|
|
<p class="field-hint" id="smtp-password-hint"></p>
|
|
</div>
|
|
|
|
<!-- Test recipient -->
|
|
<div class="field-group settings-divider-above">
|
|
<label for="smtp-test-to" class="field-label">Send Test Email To</label>
|
|
<div class="dir-input-row">
|
|
<input type="text" id="smtp-test-to" class="text-input"
|
|
placeholder="you@example.com"
|
|
autocomplete="email"
|
|
spellcheck="false" />
|
|
<button class="btn btn-secondary" id="smtp-test-btn">
|
|
Send Test
|
|
</button>
|
|
</div>
|
|
<p class="field-hint settings-test-result" id="smtp-test-result"
|
|
aria-live="polite" aria-atomic="true"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" id="settings-cancel">Cancel</button>
|
|
<button class="btn btn-primary" id="settings-save">Save Settings</button>
|
|
</div>
|
|
|
|
<!-- Save status -->
|
|
<p class="settings-save-status" id="settings-save-status"
|
|
aria-live="polite" aria-atomic="true"></p>
|
|
</div>
|
|
</div>
|
|
|
|
</main>
|
|
|
|
<footer class="app-footer" role="contentinfo">
|
|
<p>VideoPress uses <strong>FFmpeg</strong> for video compression. Files are processed on the server.</p>
|
|
</footer>
|
|
|
|
<!-- Live region for screen reader announcements -->
|
|
<div id="sr-announce" class="sr-only" aria-live="assertive" aria-atomic="true"></div>
|
|
|
|
<script type="module" src="/static/js/app.js"></script>
|
|
</body>
|
|
</html>
|