Files
vibedit/web/index.html
2026-11-06 17:57:01 +00:00

413 lines
22 KiB
HTML
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.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VibEdit</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%234c8dff'/%3E%3Cpath d='M12 9l11 7-11 7z' fill='white'/%3E%3C/svg%3E">
<link rel="stylesheet" href="style.css">
</head>
<body>
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs>
<symbol id="i-play" viewBox="0 0 24 24"><path d="M7 4.5v15l12-7.5z" fill="currentColor" stroke="none"/></symbol>
<symbol id="i-pause" viewBox="0 0 24 24"><path d="M6.5 4.5h4v15h-4zM13.5 4.5h4v15h-4z" fill="currentColor" stroke="none"/></symbol>
<symbol id="i-start" viewBox="0 0 24 24"><path d="M6 5v14M19 5l-9 7 9 7z"/></symbol>
<symbol id="i-end" viewBox="0 0 24 24"><path d="M18 5v14M5 5l9 7-9 7z"/></symbol>
<symbol id="i-frame-prev" viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></symbol>
<symbol id="i-frame-next" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></symbol>
<symbol id="i-sec-prev" viewBox="0 0 24 24"><path d="M12 6l-6 6 6 6M19 6l-6 6 6 6"/></symbol>
<symbol id="i-sec-next" viewBox="0 0 24 24"><path d="M12 6l6 6-6 6M5 6l6 6-6 6"/></symbol>
<symbol id="i-key-prev" viewBox="0 0 24 24"><path d="M5 5v14"/><path d="M14 7l-5 5 5 5 5-5z"/></symbol>
<symbol id="i-key-next" viewBox="0 0 24 24"><path d="M19 5v14"/><path d="M10 7l-5 5 5 5 5-5z"/></symbol>
<symbol id="i-vol" viewBox="0 0 24 24"><path d="M4 9h4l5-4v14l-5-4H4z"/><path d="M16.5 8.5a5 5 0 010 7M19 6a8.5 8.5 0 010 12"/></symbol>
<symbol id="i-mute" viewBox="0 0 24 24"><path d="M4 9h4l5-4v14l-5-4H4z"/><path d="M17 9l5 6M22 9l-5 6"/></symbol>
<symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
<symbol id="i-folder" viewBox="0 0 24 24"><path d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"/></symbol>
<symbol id="i-film" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 4v16M17 4v16M3 9h4M3 15h4M17 9h4M17 15h4"/></symbol>
<symbol id="i-trash" viewBox="0 0 24 24"><path d="M4 7h16M10 11v6M14 11v6M6 7l1 12a2 2 0 002 2h6a2 2 0 002-2l1-12M9 7V4h6v3"/></symbol>
<symbol id="i-copy" viewBox="0 0 24 24"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 00-2-2H6a2 2 0 00-2 2v8a2 2 0 002 2h2"/></symbol>
<symbol id="i-cut" viewBox="0 0 24 24"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M8.5 7.5L20 18M8.5 16.5L20 6"/></symbol>
<symbol id="i-undo" viewBox="0 0 24 24"><path d="M9 14L4 9l5-5"/><path d="M4 9h10a6 6 0 010 12h-3"/></symbol>
<symbol id="i-redo" viewBox="0 0 24 24"><path d="M15 14l5-5-5-5"/><path d="M20 9H10a6 6 0 000 12h3"/></symbol>
<symbol id="i-export" viewBox="0 0 24 24"><path d="M12 15V3M7 8l5-5 5 5"/><path d="M4 14v5a2 2 0 002 2h12a2 2 0 002-2v-5"/></symbol>
<symbol id="i-help" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9.5 9.5a2.5 2.5 0 114 2c-1 .6-1.5 1.2-1.5 2.5M12 17.5v.01"/></symbol>
<symbol id="i-x" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></symbol>
<symbol id="i-left" viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></symbol>
<symbol id="i-right" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></symbol>
<symbol id="i-up" viewBox="0 0 24 24"><path d="M12 19V5M6 11l6-6 6 6"/></symbol>
<symbol id="i-new" viewBox="0 0 24 24"><path d="M14 3H6a2 2 0 00-2 2v14a2 2 0 002 2h12a2 2 0 002-2V9z"/><path d="M14 3v6h6M12 12v6M9 15h6"/></symbol>
<symbol id="i-in" viewBox="0 0 24 24"><path d="M9 4H6v16h3M10 12h9M15 8l4 4-4 4"/></symbol>
<symbol id="i-out" viewBox="0 0 24 24"><path d="M15 4h3v16h-3M5 12h9M10 8l4 4-4 4"/></symbol>
<symbol id="i-check" viewBox="0 0 24 24"><path d="M5 12.5l4.5 4.5L19 7.5"/></symbol>
<symbol id="i-warn" viewBox="0 0 24 24"><path d="M12 3l10 18H2z"/><path d="M12 10v5M12 18v.01"/></symbol>
<symbol id="i-whole" viewBox="0 0 24 24"><rect x="3" y="7" width="18" height="10" rx="2"/><path d="M12 10v4M10 12h4"/></symbol>
</defs>
</svg>
<header class="topbar">
<div class="brand"><span class="logo"><svg><use href="#i-play"/></svg></span>VibEdit</div>
<div class="tb-group">
<button id="btnAdd" class="btn primary" title="Add video files"><svg class="ic"><use href="#i-plus"/></svg>Add videos</button>
<button id="btnBrowse" class="btn ghost" title="Add videos using the built-in file browser"><svg class="ic"><use href="#i-folder"/></svg></button>
</div>
<div class="tb-group">
<button id="btnUndo" class="btn ghost" title="Undo (Ctrl+Z)"><svg class="ic"><use href="#i-undo"/></svg></button>
<button id="btnRedo" class="btn ghost" title="Redo (Ctrl+Y)"><svg class="ic"><use href="#i-redo"/></svg></button>
<button id="btnNew" class="btn ghost" title="Start a new, empty project"><svg class="ic"><use href="#i-new"/></svg>New</button>
</div>
<span class="grow"></span>
<button id="jobPill" class="job-pill hidden" title="Show export progress"></button>
<button id="btnHelp" class="btn ghost" title="Keyboard shortcuts"><svg class="ic"><use href="#i-help"/></svg></button>
<button id="btnExport" class="btn accent" title="Export the clips"><svg class="ic"><use href="#i-export"/></svg>Export…</button>
</header>
<div id="banner" class="banner hidden"></div>
<main class="layout">
<aside class="panel sources">
<div class="panel-head"><span>Source videos</span><span id="srcCount" class="count"></span></div>
<ul id="sourceList" class="source-list"></ul>
<div id="sourceEmpty" class="empty">
<svg class="big-ic"><use href="#i-film"/></svg>
<p>No videos yet.</p>
<p class="dim">Click <b>Add videos</b> to load one or more source files.</p>
</div>
</aside>
<section class="panel viewer">
<div id="screen" class="screen">
<video id="video" preload="auto" playsinline></video>
<img id="still" class="still hidden" alt="">
<div id="captions" class="captions hidden"></div>
<div id="screenMsg" class="screen-msg hidden"></div>
<div id="noSource" class="screen-empty">Add a video, then click it in the list to preview it here.</div>
</div>
<div class="transport">
<div class="tc-box">
<span id="tcCur" class="tc editable" title="Current position — click to type a time">00:00.000</span>
<span class="tc-sep">/</span>
<span id="tcDur" class="tc dim">00:00.000</span>
<span id="tcFrame" class="tc-frame dim"></span>
</div>
<div class="tp-buttons">
<button class="tbtn" data-act="start" title="Go to start (Home)"><svg><use href="#i-start"/></svg></button>
<button class="tbtn" data-act="prevKey" title="Previous keyframe (↑)"><svg><use href="#i-key-prev"/></svg></button>
<button class="tbtn" data-act="back1s" title="Back 1 second (Shift+←)"><svg><use href="#i-sec-prev"/></svg></button>
<button class="tbtn" data-act="prevFrame" title="Previous frame (←)"><svg><use href="#i-frame-prev"/></svg></button>
<button class="tbtn play" data-act="play" id="btnPlay" title="Play / pause (Space)"><svg><use href="#i-play"/></svg></button>
<button class="tbtn" data-act="nextFrame" title="Next frame (→)"><svg><use href="#i-frame-next"/></svg></button>
<button class="tbtn" data-act="fwd1s" title="Forward 1 second (Shift+→)"><svg><use href="#i-sec-next"/></svg></button>
<button class="tbtn" data-act="nextKey" title="Next keyframe (↓)"><svg><use href="#i-key-next"/></svg></button>
<button class="tbtn" data-act="end" title="Go to end (End)"><svg><use href="#i-end"/></svg></button>
</div>
<div class="tp-right">
<button class="tbtn" data-act="mute" id="btnMute" title="Mute (M)"><svg><use href="#i-vol"/></svg></button>
<input id="volume" type="range" min="0" max="1" step="0.05" value="1" title="Volume">
</div>
</div>
<div class="timeline-row">
<div id="timeline" class="timeline">
<canvas id="tlCanvas"></canvas>
<div id="tlHover" class="tl-hover hidden"><img alt=""><span></span></div>
</div>
<div class="zoom">
<button class="zbtn" data-act="zoomIn" title="Zoom in (+ or mouse wheel)">+</button>
<button class="zbtn" data-act="zoomOut" title="Zoom out (−)">−</button>
<button class="zbtn fit" data-act="zoomFit" title="Show the whole video (0)">Fit</button>
</div>
</div>
<div class="marks">
<button class="btn mark-btn" data-act="setIn" title="Set the In point at the playhead (I). Shift+I jumps to it."><svg class="ic"><use href="#i-in"/></svg>Set In <kbd>I</kbd></button>
<div class="mark-val"><label>In</label><span id="markIn" class="tc editable" title="Click to type">–</span></div>
<button class="btn mark-btn" data-act="setOut" title="Set the Out point at the playhead (O). Shift+O jumps to it."><svg class="ic"><use href="#i-out"/></svg>Set Out <kbd>O</kbd></button>
<div class="mark-val"><label>Out</label><span id="markOut" class="tc editable" title="Click to type">–</span></div>
<div class="mark-val"><label>Length</label><span id="markLen" class="tc">–</span></div>
<span class="grow"></span>
<button class="btn ghost" data-act="split" title="Split the clip under the playhead in two (S)"><svg class="ic"><use href="#i-cut"/></svg>Split <kbd>S</kbd></button>
<button class="btn ghost" data-act="playSel" title="Play from In to Out (P)"><svg class="ic"><use href="#i-play"/></svg>Play In→Out <kbd>P</kbd></button>
<button class="btn primary" id="btnAddClip" data-act="addClip" title="Add the In→Out range as a clip (Enter)"><svg class="ic"><use href="#i-plus"/></svg><span>Add clip</span> <kbd>Enter</kbd></button>
</div>
<div id="markHint" class="mark-hint hidden"></div>
</section>
<section class="panel clips">
<div class="panel-head">
<span>Clips</span><span id="clipCount" class="count"></span>
<span id="clipTotal" class="dim small"></span>
<span class="grow"></span>
<span class="dim small">Export method</span>
<div class="seg" id="modeSeg">
<button data-mode="copy" title="Copy the original streams: no quality loss, very fast. Cuts land on keyframes.">Lossless</button>
<button data-mode="encode" title="Re-encode: frame-exact cuts and a choice of format, size and quality. Slower.">Re-encode</button>
<button data-mode="gif" title="Animated GIF: a looping animation without sound, with frame-exact cuts.">GIF</button>
</div>
</div>
<div id="clipList" class="clip-list"></div>
<div id="clipEmpty" class="empty row">
<p>No clips yet. Set an <b>In</b> and <b>Out</b> point on the timeline and press <b>Add clip</b>,
or use <b>+ Whole</b> on a source and <b>Split</b> it. Clips are joined in this order — drag them to reorder.</p>
</div>
</section>
</main>
<!-- Export dialog -->
<dialog id="dlgExport" class="dlg">
<div class="dlg-head"><h2>Export</h2><button class="icon-btn" data-close title="Close"><svg class="ic"><use href="#i-x"/></svg></button></div>
<div class="dlg-body" id="exportForm">
<div class="summary" id="exSummary"></div>
<div class="field">
<div class="label">Method</div>
<div class="cards3">
<label class="rcard"><input type="radio" name="mode" value="copy">
<div><b>Lossless</b><small>Copies the original video and audio: no quality loss and very fast. Cuts can only start on keyframes, so a clip may begin a little early.</small></div></label>
<label class="rcard"><input type="radio" name="mode" value="encode">
<div><b>Re-encode</b><small>Frame-exact cuts. Choose the format, resolution and quality. Clips of different sizes are fitted together. Slower.</small></div></label>
<label class="rcard"><input type="radio" name="mode" value="gif">
<div><b>Animated GIF</b><small>A looping animation without sound that plays almost anywhere. Frame-exact cuts. Best for short clips: GIF files get large quickly.</small></div></label>
</div>
</div>
<div class="field">
<div class="label">Output</div>
<div class="radios">
<label><input type="radio" name="join" value="1"> Join all clips into one video</label>
<label><input type="radio" name="join" value="0"> Save each clip as a separate file</label>
</div>
</div>
<div class="field">
<div class="label">Save as</div>
<div class="row">
<input id="exOutput" type="text" spellcheck="false" autocomplete="off">
<select id="exContainer" title="File type"></select>
<button class="btn" id="exBrowse" type="button"><svg class="ic"><use href="#i-folder"/></svg>Browse…</button>
</div>
<div class="hint" id="exOutHint"></div>
</div>
<div id="exCopyOpts" class="field">
<div class="label">Audio</div>
<div class="radios">
<label id="exAllAudioRow"><input type="checkbox" id="exAllAudio"> Keep all audio tracks</label>
<label><input type="checkbox" id="exNoAudioCopy"> Remove audio</label>
</div>
</div>
<div id="exEncOpts">
<div class="grid2">
<div class="field">
<div class="label">Video codec</div>
<select id="exVCodec"></select>
</div>
<div class="field">
<div class="label">Speed</div>
<select id="exSpeed">
<option value="fast">Fast (bigger file)</option>
<option value="balanced">Balanced</option>
<option value="slow">Slow (smaller file)</option>
</select>
</div>
</div>
<div class="field">
<div class="label">Quality <span id="exQLabel" class="dim"></span></div>
<div class="row q-row"><span class="dim small">Best</span><input type="range" id="exQuality" min="0" max="51" step="1"><span class="dim small">Smallest</span></div>
</div>
<div class="grid2">
<div class="field">
<div class="label">Resolution</div>
<select id="exHeight">
<option value="0">Same as source</option>
<option value="2160">2160p (4K)</option>
<option value="1440">1440p</option>
<option value="1080">1080p</option>
<option value="720">720p</option>
<option value="480">480p</option>
<option value="360">360p</option>
</select>
</div>
<div class="field">
<div class="label">Frame rate</div>
<select id="exFPS">
<option value="">Same as source</option>
<option value="24000/1001">23.976</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="30000/1001">29.97</option>
<option value="30">30</option>
<option value="50">50</option>
<option value="60000/1001">59.94</option>
<option value="60">60</option>
</select>
</div>
</div>
<div class="grid2">
<div class="field">
<div class="label">Audio codec</div>
<select id="exACodec"></select>
</div>
<div class="field">
<div class="label">Audio bitrate</div>
<select id="exABitrate">
<option value="96">96 kbps</option>
<option value="128">128 kbps</option>
<option value="160">160 kbps</option>
<option value="192">192 kbps</option>
<option value="256">256 kbps</option>
<option value="320">320 kbps</option>
</select>
</div>
</div>
<div class="hint" id="exEncHint"></div>
</div>
<div id="exGifOpts">
<div class="grid2">
<div class="field">
<div class="label">Resolution</div>
<select id="exGifHeight">
<option value="0">Same as source</option>
<option value="720">720p</option>
<option value="480">480p</option>
<option value="360">360p</option>
<option value="240">240p</option>
</select>
</div>
<div class="field">
<div class="label">Frame rate</div>
<select id="exGifFPS">
<option value="10">10</option>
<option value="12">12</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="30">30</option>
</select>
</div>
</div>
<div class="grid2">
<div class="field">
<div class="label">Dithering</div>
<select id="exGifDither">
<option value="sierra2_4a">Smooth (best looking)</option>
<option value="bayer">Pattern (smaller file)</option>
<option value="none">None (smallest, may show bands)</option>
</select>
</div>
<div class="field">
<div class="label">Playback</div>
<select id="exGifLoop">
<option value="1">Loop forever</option>
<option value="0">Play once</option>
</select>
</div>
</div>
<div class="hint" id="exGifHint"></div>
</div>
<div id="exCapOpts">
<div class="field">
<div class="label">Captions</div>
<label><input type="checkbox" id="exCaptions"> Burn the captions into the picture</label>
<div class="hint" id="exCapHint"></div>
</div>
<div class="grid2" id="exCapLook">
<div class="field">
<div class="label">Caption size</div>
<select id="exCapSize">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
</div>
<div class="field">
<div class="label">Caption style</div>
<select id="exCapStyle">
<option value="outline">White with a black outline</option>
<option value="box">White on black boxes</option>
</select>
</div>
</div>
</div>
<div id="exWarn" class="notice warn hidden"></div>
<div id="exProgress" class="progress hidden">
<div class="bar"><div id="exBar"></div></div>
<div class="pinfo"><span id="exPText"></span><span id="exPEta" class="dim"></span></div>
</div>
<div id="exResult" class="hidden"></div>
</div>
<div class="dlg-foot">
<button class="btn" data-close>Close</button>
<span class="grow"></span>
<button class="btn danger hidden" id="exCancel">Cancel export</button>
<button class="btn accent" id="exGo"><svg class="ic"><use href="#i-export"/></svg>Export</button>
</div>
</dialog>
<!-- Built-in file browser -->
<dialog id="dlgFiles" class="dlg files">
<div class="dlg-head"><h2 id="fbTitle">Add videos</h2><button class="icon-btn" data-close title="Close"><svg class="ic"><use href="#i-x"/></svg></button></div>
<div class="fb-bar">
<select id="fbRoots" title="Drive"></select>
<button class="btn" id="fbUp" title="Parent folder"><svg class="ic"><use href="#i-up"/></svg></button>
<input id="fbPath" type="text" spellcheck="false" autocomplete="off" title="Folder — type a path and press Enter">
</div>
<ul id="fbList" class="fb-list"></ul>
<div class="fb-save hidden" id="fbSaveRow"><label>File name</label><input id="fbName" type="text" spellcheck="false"></div>
<div class="dlg-foot">
<label class="small"><input type="checkbox" id="fbAll"> Show all files</label>
<span class="grow"></span>
<span id="fbSelInfo" class="dim small"></span>
<button class="btn" data-close>Cancel</button>
<button class="btn primary" id="fbOk">Add</button>
</div>
</dialog>
<!-- Confirm -->
<dialog id="dlgConfirm" class="dlg small">
<div class="dlg-body"><p id="cfText"></p></div>
<div class="dlg-foot"><span class="grow"></span><button class="btn" id="cfNo">Cancel</button><button class="btn accent" id="cfYes">OK</button></div>
</dialog>
<!-- Help -->
<dialog id="dlgHelp" class="dlg">
<div class="dlg-head"><h2>How to use VibEdit</h2><button class="icon-btn" data-close title="Close"><svg class="ic"><use href="#i-x"/></svg></button></div>
<div class="dlg-body help">
<ol class="steps">
<li><b>Add videos</b> and click one to preview it.</li>
<li>Move the playhead and press <kbd>I</kbd> / <kbd>O</kbd> to mark the start and end of a part you want, then <kbd>Enter</kbd> to add it as a clip. Repeat for every part (from any source).</li>
<li>Or add a whole video with <b>+ Whole</b>, put the playhead where you want to cut and press <kbd>S</kbd> to split; delete the pieces you don't want.</li>
<li>Drag clips to put them in order, then <b>Export</b> — joined into one video or as separate files.</li>
<li>To add captions, click <b>CC</b> on a source and pick its <b>.srt</b> file. They show in the viewer, and Re-encode and GIF exports burn them into the picture, timed to each clip.</li>
</ol>
<p class="dim">The In point includes the frame you see; the Out point cuts just before the frame you see. Your project is saved automatically.</p>
<table class="keys">
<tr><td><kbd>Space</kbd></td><td>Play / pause</td><td><kbd>I</kbd> <kbd>O</kbd></td><td>Set In / Out</td></tr>
<tr><td><kbd>←</kbd> <kbd>→</kbd></td><td>Previous / next frame</td><td><kbd>Shift</kbd>+<kbd>I</kbd>/<kbd>O</kbd></td><td>Go to In / Out</td></tr>
<tr><td><kbd>Shift</kbd>+<kbd>←</kbd> <kbd>→</kbd></td><td>Back / forward 1 s</td><td><kbd>Enter</kbd></td><td>Add clip (or finish editing)</td></tr>
<tr><td><kbd>Ctrl</kbd>+<kbd>←</kbd> <kbd>→</kbd></td><td>Back / forward 10 s</td><td><kbd>S</kbd></td><td>Split clip at playhead</td></tr>
<tr><td><kbd>↑</kbd> <kbd>↓</kbd></td><td>Previous / next keyframe</td><td><kbd>P</kbd></td><td>Play In → Out</td></tr>
<tr><td><kbd>Home</kbd> <kbd>End</kbd></td><td>Start / end</td><td><kbd>Delete</kbd></td><td>Delete selected clip</td></tr>
<tr><td><kbd>+</kbd> <kbd>−</kbd> <kbd>0</kbd></td><td>Zoom timeline / fit</td><td><kbd>Esc</kbd></td><td>Stop editing clip</td></tr>
<tr><td><kbd>M</kbd></td><td>Mute</td><td><kbd>Ctrl</kbd>+<kbd>Z</kbd> / <kbd>Y</kbd></td><td>Undo / redo</td></tr>
</table>
<p class="dim small" id="helpVersion"></p>
</div>
</dialog>
<div id="toasts" class="toasts"></div>
<div id="offline" class="offline hidden">
<div class="offline-box">
<h2>VibEdit is not running</h2>
<p>The VibEdit program was closed. Your project is saved — start VibEdit again to continue where you left off.</p>
</div>
</div>
<script src="app.js"></script>
</body>
</html>