Root causes found: 1. Scheduler crashing every 15s: assets table has no error_message column. Fix: remove error_message from UPDATE in scheduler.js (#66 regression). 2. Clip freezing: client-side filmstrip seek loop runs on main thread, seeks same proxy the player is streaming → both stall → freeze. Fix: replace browser seek loop entirely with server-side FFmpeg worker. 3. No dedicated filmstrip worker: filmstrip was never pre-built server-side. Changes: - services/mam-api/src/db/migrations/018-add-filmstrip-s3-key.sql Add filmstrip_s3_key TEXT column to assets table - services/worker/src/workers/filmstrip.js (new) BullMQ worker: downloads proxy, runs FFmpeg fps filter to extract 28 evenly-spaced JPEG frames, base64-encodes them, uploads JSON array to S3 at filmstrips/<assetId>.json, stores key in DB - services/worker/src/workers/thumbnail.js Queue filmstrip job automatically after thumbnail completes - services/worker/src/index.js Register filmstrip worker (concurrency=2), export filmstripQueue singleton, close it on SIGTERM - services/mam-api/src/routes/assets.js - filmstripQueue added - POST /reprocess?type=filmstrip now supported - GET /:id/filmstrip returns signed S3 URL for JSON frames - services/mam-api/src/routes/jobs.js filmstrip queue visible in Jobs UI - services/web-ui/public/screens-asset.jsx Replace browser seek loop with fetch of /assets/:id/filmstrip → fetch S3 JSON → render frames. Zero browser-side video seeking. Right-click and Files tab re-generate via API endpoint.
373 lines
18 KiB
HTML
373 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Wild Dragon MAM</title>
|
|
<link rel="stylesheet" href="css/styles.css">
|
|
</head>
|
|
<body>
|
|
<div id="panel-container">
|
|
<!-- Connection Bar -->
|
|
<div class="connection-bar">
|
|
<div class="connection-controls connection-controls--stacked">
|
|
<div class="server-input-row">
|
|
<input
|
|
type="text"
|
|
id="server-url"
|
|
class="server-url"
|
|
placeholder="http://10.0.0.25:47434"
|
|
title="MAM server URL"
|
|
>
|
|
<div class="status-indicator" id="status-indicator"></div>
|
|
</div>
|
|
<div class="server-input-row">
|
|
<input
|
|
type="password"
|
|
id="api-token"
|
|
class="server-url"
|
|
placeholder="API token (wd_…)"
|
|
title="API token"
|
|
autocomplete="off"
|
|
>
|
|
<button id="connect-btn" class="connect-btn btn btn-primary btn-sm">Connect</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Navigation -->
|
|
<div class="tab-nav">
|
|
<button id="tab-library" class="tab-btn active">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/>
|
|
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>
|
|
</svg>
|
|
Library
|
|
</button>
|
|
<button id="tab-growing" class="tab-btn">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M23 7l-7 5 7 5V7z"/>
|
|
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
|
|
</svg>
|
|
Growing
|
|
<span id="growing-count" class="badge">0</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Search and Filter Area -->
|
|
<div class="search-filter-area">
|
|
<div class="search-bar">
|
|
<input
|
|
type="text"
|
|
id="search-input"
|
|
class="search-input"
|
|
placeholder="Search assets..."
|
|
>
|
|
</div>
|
|
<div class="filter-controls">
|
|
<select id="project-filter" title="Filter by project">
|
|
<option value="all">All Projects</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Sequence Info Bar -->
|
|
<div id="seq-info-bar" class="seq-info-bar hidden">
|
|
<span class="seq-info-label chip chip--good"><span class="chip-dot"></span>SEQ</span>
|
|
<span id="seq-info-name" class="seq-info-name"></span>
|
|
<button id="seq-refresh-btn" class="seq-refresh-btn btn btn-ghost btn-sm" title="Refresh active sequence">↻</button>
|
|
</div>
|
|
|
|
<!-- Main Content Area -->
|
|
<div class="content-area">
|
|
<!-- Library Grid -->
|
|
<div class="asset-grid-container" id="library-container">
|
|
<div id="asset-grid" class="asset-grid">
|
|
<div id="empty-state" class="empty-state">
|
|
<div class="empty-state-icon">
|
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
<path d="M9 9a3 3 0 116 0m-6 3h6m-3 3h.01"/>
|
|
</svg>
|
|
</div>
|
|
<div class="empty-state-title">No assets</div>
|
|
<div class="empty-state-body">Connect to server and load assets</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Growing Grid -->
|
|
<div class="asset-grid-container hidden" id="growing-container">
|
|
<div id="growing-grid" class="asset-grid">
|
|
<div id="growing-empty-state" class="empty-state">
|
|
<div class="empty-state-icon">
|
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/>
|
|
<line x1="7" y1="2" x2="7" y2="22"/>
|
|
<line x1="17" y1="2" x2="17" y2="22"/>
|
|
<line x1="2" y1="12" x2="22" y2="12"/>
|
|
<line x1="2" y1="7" x2="7" y2="7"/>
|
|
<line x1="2" y1="17" x2="7" y2="17"/>
|
|
<line x1="17" y1="17" x2="22" y2="17"/>
|
|
<line x1="17" y1="7" x2="22" y2="7"/>
|
|
</svg>
|
|
</div>
|
|
<div class="empty-state-title">No growing files</div>
|
|
<div class="empty-state-body">Active recordings will appear here</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Details Panel -->
|
|
<div id="details-panel" class="details-panel hidden">
|
|
<div class="details-header">
|
|
<span class="details-header-label">Asset Info</span>
|
|
</div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-label">Filename</div>
|
|
<div id="details-filename" class="details-value truncate-lines-2"></div>
|
|
</div>
|
|
|
|
<div class="divider"></div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-label">Codec</div>
|
|
<div id="details-codec" class="details-value"></div>
|
|
</div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-label">Resolution</div>
|
|
<div id="details-resolution" class="details-value"></div>
|
|
</div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-label">Frame Rate</div>
|
|
<div id="details-fps" class="details-value"></div>
|
|
</div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-label">Duration</div>
|
|
<div id="details-duration" class="details-value"></div>
|
|
</div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-label">File Size</div>
|
|
<div id="details-size" class="details-value"></div>
|
|
</div>
|
|
|
|
<div class="divider"></div>
|
|
|
|
<div class="details-section">
|
|
<div class="details-label">Tags</div>
|
|
<div id="details-tags" class="tags-list"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress Indicator -->
|
|
<div id="progress-container" class="progress-container">
|
|
<div class="progress-label" id="progress-label">Downloading...</div>
|
|
<div class="progress-bar">
|
|
<div id="progress-fill" class="progress-fill"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Export Panel -- Push Timeline to MAM -->
|
|
<div id="export-panel" class="export-panel hidden">
|
|
<div class="export-panel-title">Push Timeline to MAM</div>
|
|
<input
|
|
type="text"
|
|
id="export-seq-name"
|
|
class="input"
|
|
placeholder="Sequence name"
|
|
title="Name this sequence will have in the MAM"
|
|
>
|
|
<select id="export-proj-select" title="Target project in MAM">
|
|
<option value="">— Select project —</option>
|
|
</select>
|
|
<div id="export-clip-info" class="export-clip-info"></div>
|
|
<div class="export-panel-actions">
|
|
<button id="export-confirm-btn" class="btn btn-primary">Push to MAM</button>
|
|
<button id="export-cancel-btn" class="btn btn-secondary">Cancel</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Section: FCP XML Export & Hi-Res Auto-Relink -->
|
|
<div class="advanced-section">
|
|
<div class="advanced-section-title">Advanced</div>
|
|
<div class="advanced-row">
|
|
<button id="export-conform-btn" class="btn btn-primary btn-sm" disabled title="Export timeline as FCP XML and start conform job">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
|
<polyline points="7 10 12 15 17 10"/>
|
|
<line x1="12" y1="15" x2="12" y2="3"/>
|
|
</svg>
|
|
Export & Conform
|
|
</button>
|
|
<button id="fetch-relink-btn" class="btn btn-secondary btn-sm" disabled title="Fetch hi-res media for all timeline clips and auto-relink">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="11" cy="11" r="8"/>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
</svg>
|
|
Fetch & Relink All
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Bar -->
|
|
<div class="action-bar">
|
|
<div class="action-row">
|
|
<button id="import-btn" class="btn btn-primary" disabled title="Download proxy and import into Premiere">Import Proxy</button>
|
|
<button id="import-hires-btn" class="btn btn-secondary" disabled title="Download original hi-res and import into Premiere">Hi-Res</button>
|
|
</div>
|
|
<div class="action-row">
|
|
<button id="mount-live-btn" class="btn btn-secondary" disabled title="Open the live file directly from the SMB share">Mount Live</button>
|
|
<button id="relink-btn" class="btn btn-secondary" disabled title="Relink the imported clip from proxy to the finalized hi-res original">Relink to Hi-Res</button>
|
|
</div>
|
|
<div class="action-row">
|
|
<button id="import-all-btn" class="btn btn-secondary" title="Import all visible assets as proxy">Import All</button>
|
|
<button id="export-timeline-btn" class="btn btn-secondary" title="Push the current Premiere sequence to MAM">Export Timeline ↑</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FCP XML Export & Conform Slide Panel -->
|
|
<div id="export-conform-overlay" class="slide-overlay"></div>
|
|
<div id="export-conform-panel" class="slide-panel">
|
|
<div class="slide-panel-header">
|
|
<span class="slide-panel-title">Export & Conform</span>
|
|
<button id="export-conform-close-btn" class="btn btn-ghost btn-sm" title="Close">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="slide-panel-body">
|
|
<!-- Preset Selection -->
|
|
<div class="form-group">
|
|
<span class="form-label">Preset</span>
|
|
<div id="preset-cards" class="preset-grid">
|
|
<div class="preset-card selected" data-preset="broadcast">
|
|
<div class="preset-card-title">Broadcast</div>
|
|
<div class="preset-card-desc">ProRes 422 HQ, 1920x1080, 48kHz</div>
|
|
</div>
|
|
<div class="preset-card" data-preset="web">
|
|
<div class="preset-card-title">Web</div>
|
|
<div class="preset-card-desc">H.264, 1920x1080, AAC 320kbps</div>
|
|
</div>
|
|
<div class="preset-card" data-preset="archive">
|
|
<div class="preset-card-title">Archive</div>
|
|
<div class="preset-card-desc">ProRes 4444, UHD, 48kHz</div>
|
|
</div>
|
|
<div class="preset-card" data-preset="custom">
|
|
<div class="preset-card-title">Custom</div>
|
|
<div class="preset-card-desc">Manual settings</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Codec -->
|
|
<div class="form-group">
|
|
<span class="form-label">Codec</span>
|
|
<select id="conform-codec">
|
|
<option value="prores_hq">ProRes 422 HQ</option>
|
|
<option value="prores_4444">ProRes 4444</option>
|
|
<option value="h264">H.264</option>
|
|
<option value="h265">H.265 / HEVC</option>
|
|
<option value="dnxhr_hq">DNxHR HQ</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Quality -->
|
|
<div class="form-group">
|
|
<span class="form-label">Quality</span>
|
|
<select id="conform-quality">
|
|
<option value="high">High</option>
|
|
<option value="medium" selected>Medium</option>
|
|
<option value="low">Low</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Resolution -->
|
|
<div class="form-group">
|
|
<span class="form-label">Resolution</span>
|
|
<select id="conform-resolution">
|
|
<option value="uhd">UHD (3840x2160)</option>
|
|
<option value="1080p" selected>Full HD (1920x1080)</option>
|
|
<option value="720p">HD (1280x720)</option>
|
|
<option value="source">Source</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Audio Preset -->
|
|
<div class="form-group">
|
|
<span class="form-label">Audio Preset</span>
|
|
<select id="conform-audio">
|
|
<option value="broadcast">Broadcast (48kHz, 24-bit, PCM)</option>
|
|
<option value="web">Web (44.1kHz, AAC 320kbps)</option>
|
|
<option value="archive">Archive (96kHz, 24-bit, PCM)</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Timeline Clip Summary -->
|
|
<div class="form-group">
|
|
<span class="form-label">Timeline Clips</span>
|
|
<div id="conform-clip-info" class="export-clip-info"></div>
|
|
</div>
|
|
</div>
|
|
<div class="slide-panel-footer">
|
|
<button id="export-conform-cancel-btn" class="btn btn-secondary">Cancel</button>
|
|
<button id="export-conform-start-btn" class="btn btn-primary">Start Conform</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hi-Res Relink Slide Panel -->
|
|
<div id="relink-overlay" class="slide-overlay"></div>
|
|
<div id="relink-panel" class="slide-panel">
|
|
<div class="slide-panel-header">
|
|
<span class="slide-panel-title">Fetch & Relink Hi-Res</span>
|
|
<button id="relink-close-btn" class="btn btn-ghost btn-sm" title="Close">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="slide-panel-body">
|
|
<div class="form-group">
|
|
<span class="form-label">Select clips to relink</span>
|
|
<span class="form-hint">Clips matched to MAM assets will be listed below. Check the ones you want to upgrade to hi-res.</span>
|
|
</div>
|
|
<div id="clip-list-container" class="clip-list-container">
|
|
<div id="clip-list" class="clip-list">
|
|
<!-- populated by JS -->
|
|
</div>
|
|
</div>
|
|
<div id="relink-summary" class="relink-summary hidden">
|
|
<div class="relink-summary-icon">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
</svg>
|
|
</div>
|
|
<div class="relink-summary-text" id="relink-summary-text"></div>
|
|
<div class="relink-summary-detail" id="relink-summary-detail"></div>
|
|
</div>
|
|
</div>
|
|
<div class="slide-panel-footer">
|
|
<button id="relink-cancel-btn" class="btn btn-secondary">Cancel</button>
|
|
<button id="relink-start-btn" class="btn btn-primary" disabled>Start Relink</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Adobe CSInterface Library -->
|
|
<script src="js/CSInterface.js"></script>
|
|
|
|
<!-- Main Panel Script -->
|
|
<script src="js/main.js"></script>
|
|
</body>
|
|
</html>
|