recoder/index.html
2025-06-10 12:05:45 +09:00

478 lines
14 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>Recoder — Batch Video Transcoding</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
body {
margin: 0; padding: 0;
background: #2e2e2e;
color: #ddd;
font-family: 'Inter', sans-serif;
line-height: 1.5;
}
a {
color: #58a6ff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}
.container {
max-width: 900px;
margin: 2rem auto 4rem;
padding: 0 1rem;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.logo-section {
display: flex;
flex-direction: column;
align-items: flex-start;
flex: 1 1 auto;
min-width: 240px;
}
header img {
width: 120px;
height: 120px;
margin-bottom: 0.5rem;
}
header h1 {
font-weight: 700;
font-size: 2.5rem;
color: #58a6ff;
margin: 0.2rem 0 0.5rem;
}
header p {
font-size: 1.1rem;
color: #aaa;
margin: 0;
}
/* Install section with button + CLI */
.install-section {
flex-shrink: 0;
min-width: 160px;
display: flex;
flex-direction: column;
align-items: flex-end;
position: relative;
height: 130px; /* reserve space */
}
.install-btn {
background-color: #3390ff;
color: white;
font-weight: 700;
font-size: 1.15rem;
padding: 0.7rem 2rem;
border-radius: 6px;
box-shadow: 0 4px 10px rgb(51 144 255 / 0.6);
border: none;
cursor: pointer;
transition: background-color 0.3s ease;
text-decoration: none !important;
text-align: center;
}
.install-btn:hover {
background-color: #1c6ddb;
}
.install-instructions {
background: #222; /* Dark but not pure black */
color: #58a6ff;
font-size: 1rem;
padding: 1rem 1.2rem;
border-radius: 6px;
margin-top: 0.5rem;
overflow-x: auto;
display: none; /* Initially hidden */
width: 100%;
box-sizing: border-box;
}
.install-instructions.show {
display: block;
}
.install-method {
margin-bottom: 1em;
}
.install-label {
font-weight: bold;
margin-bottom: 0.25em;
}
.install-command {
display: block;
background: #f4f4f4;
padding: 0.5em;
border-radius: 5px;
font-family: monospace;
margin-bottom: 0.25em;
color: black;
font-size: 0.9em;
}
.toggle-link {
cursor: pointer;
color: #58a6ff;
font-weight: 600;
margin-top: 1rem;
display: inline-block;
}
/* Carousel styles */
.carousel {
width: 708px;
height: 472px;
margin: 3rem auto 2rem;
overflow: hidden;
border-radius: 12px;
box-shadow: 0 6px 20px rgb(0 0 0 / 0.4);
background: #222;
position: relative;
user-select: none;
}
.carousel-track {
display: flex;
transition: transform 0.5s ease;
height: 100%;
will-change: transform;
}
.carousel-track img {
width: 708px;
height: 472px;
object-fit: contain;
flex-shrink: 0;
margin: 0;
user-select: none;
-webkit-user-drag: none;
display: block;
}
.carousel-controls {
position: absolute;
top: 50%;
width: 100%;
display: flex;
justify-content: space-between;
transform: translateY(-50%);
pointer-events: none;
}
.carousel-controls button {
background: rgba(0,0,0,0.5);
border: none;
color: #58a6ff;
font-size: 2rem;
cursor: pointer;
pointer-events: all;
user-select: none;
transition: background-color 0.3s ease;
border-radius: 4px;
margin: 0 0.5rem;
}
.carousel-controls button:hover {
background: #58a6ff;
color: #222;
}
.why-recoder {
background-color: #1c1c1c;
padding: 2rem;
border-top: 2px solid #333;
color: #ccc;
font-style: normal;
border-radius: 10px;
}
.why-recoder-inner {
column-count: 2;
column-gap: 3rem;
column-rule: 1px solid #333; /* adds a vertical line between columns */
max-width: 900px;
margin: 0 auto;
font-size: 1rem;
line-height: 1.5;
}
.why-recoder-inner h2 {
column-span: all; /* headline spans both columns */
margin: 0;
}
.why-recoder h2 {
font-size: 1.8rem;
margin-bottom: 1rem;
color: #58a6ff;
}
/* Features grid */
.features {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(280px,1fr));
gap: 1.8rem;
margin-top: 3rem;
}
.feature-item {
background: #3a3a3a;
color: #ddd;
border-radius: 10px;
padding: 1.6rem 1.8rem;
box-shadow: 0 3px 8px rgb(0 0 0 / 0.3);
transition: background 0.3s ease;
}
.feature-item:hover {
background: #444444;
}
.feature-item h3 {
color: #58a6ff;
margin-top: 0;
margin-bottom: 0.6rem;
font-weight: 600;
}
.help-section {
margin: 3rem 0;
padding: 1.5rem;
background-color: #1a1a1a;
border-radius: 8px;
color: #ccc;
}
.help-section h2 {
margin: 0.5rem 0;
font-size: 1.3rem;
}
.help-section a {
color: #58a6ff;
text-decoration: underline;
}
footer {
text-align: center;
}
/* Responsive */
@media (max-width: 480px) {
header {
flex-direction: column;
align-items: flex-start;
}
header h1 {
font-size: 2rem;
}
.install-section {
width: 100%;
min-width: auto;
height: auto;
}
.install-btn {
padding: 0.6rem 1.5rem;
font-size: 1rem;
}
.carousel {
width: 100%;
height: auto;
}
.carousel-track img {
width: 100%;
height: auto;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo-section" aria-label="Recoder logo and tagline">
<img src="https://raw.githubusercontent.com/jeena/recoder/refs/heads/main/src/resources/net.jeena.Recoder.svg" alt="Recoder Logo" />
<h1>Recoder</h1>
<p>Batch transcode family videos to DNxHD for smooth Davinci Resolve editing</p>
</div>
<div class="install-section">
<a href="https://jeena.github.io/recoder/net.jeena.Recoder.flatpakref" class="install-btn" aria-label="Install Recoder via Flatpak">Install</a>
<span class="toggle-link" id="toggle-install-cli" tabindex="0" role="button" aria-expanded="false" aria-controls="cli-commands">
Manual installation ▼
</span>
</div>
<div id="cli-commands" class="install-instructions" aria-hidden="true" tabindex="0">
<div class="install-method">
<div class="install-label">Arch Linux (AUR):</div>
<code class="install-command">yay -S recoder</code>
</div>
<div class="install-method">
<div class="install-label">Flatpak install via terminal:</div>
<code class="install-command">flatpak install --user https://jeena.github.io/recoder/net.jeena.Recoder.flatpakref</code>
<code class="install-command">flatpak run net.jeena.Recoder</code>
</div>
</div>
</header>
<div class="carousel" aria-label="Recoder screenshots carousel">
<div class="carousel-track">
<img src="https://raw.githubusercontent.com/jeena/recoder/refs/heads/main/docs/screenshot-1.png" alt="Recoder initial prompt view" />
<img src="https://raw.githubusercontent.com/jeena/recoder/refs/heads/main/docs/screenshot-2.png" alt="Recoder folder loaded with files" />
<img src="https://raw.githubusercontent.com/jeena/recoder/refs/heads/main/docs/screenshot-3.png" alt="Recoding in progress" />
<img src="https://raw.githubusercontent.com/jeena/recoder/refs/heads/main/docs/screenshot-4.png" alt="Preferences view with output folder options" />
</div>
<div class="carousel-controls">
<button id="prev" aria-label="Previous screenshot">&#8592;</button>
<button id="next" aria-label="Next screenshot">&#8594;</button>
</div>
</div>
<section class="why-recoder" aria-label="Why Recoder exists">
<div class="why-recoder-inner">
<h2>🎬 Why Recoder?</h2>
<p>
I used to edit family videos in Kdenlive without a problem — it handled footage from all our devices without complaining. But then I switched to <strong>DaVinci Resolve</strong>, and suddenly nothing worked right. My Sony Alpha 7C, my Galaxy S24, and my wife's iPhone all produced files that Resolve couldnt handle without transcoding.
</p>
<h3>😤 Too Much Fuss, Too Many Steps</h3>
<p>
Every time I wanted to edit, I had to hunt down the right <code>ffmpeg</code> settings and manually run them on each video — a frustrating and repetitive task.
</p>
<p>
My typical workflow is simple: I create one folder per event on an external HDD and drop in videos from all our cameras. A script renames the files based on the date and time so I can easily sort them. But for Resolve, everything has to be <strong>transcoded to DNxHD</strong> — which only supports resolutions like 1920×1080 and 1280×720.
</p>
<h3>🔄 Vertical Videos? Extra Pain</h3>
<p>
That also meant vertical videos couldnt work. So now, I rotate them during transcoding to preserve resolution and rotate them back in Resolve during editing.
</p>
<h3>✨ Enter Recoder</h3>
<p>
I built Recoder to automate this annoying step — so I could spend more time editing memories and less time fiddling with command-line tools.
</p>
</div>
</section>
<section class="features" aria-label="Features of Recoder">
<div class="feature-item">
<h3>⚙️ Powerful Transcoding with ffmpeg</h3>
<p>Powered by <a href="https://ffmpeg.org/">ffmpeg</a> to support virtually all input formats.</p>
</div>
<div class="feature-item">
<h3>🎥 Consistent Output Quality</h3>
<p>Output videos are always 1920×1080 DNxHD, perfect for smooth editing.</p>
<p>Vertical videos are rotated 90° during transcoding to preserve quality.</p>
</div>
<div class="feature-item">
<h3>🎞️ Batch Transcoding Made Easy</h3>
<p>Drop one video or a folder of videos to transcode them all at once. Subfolders and non-video files are ignored automatically.</p>
</div>
<div class="feature-item">
<h3>🖱️ Drag &amp; Drop Friendly</h3>
<p>Simply drag a file or a folder onto the app and get a preview of which files will be transcoded.</p>
</div>
<div class="feature-item">
<h3>🧭 Clear &amp; Intuitive UI</h3>
<p>Modern libadwaita interface with simple controls: Transcode, Pause, Resume, and Clear buttons.</p>
</div>
<div class="feature-item">
<h3>📁 Flexible Output Folder</h3>
<p>Customize where transcoded files go — use relative or absolute paths and variables like <code>{{source_folder_name}}</code>.</p>
</div>
<div class="feature-item">
<h3>🛡️ Safe File Management</h3>
<p>Files are processed without altering originals; new files are saved separately based on your settings.</p>
</div>
<div class="feature-item">
<h3>📊 Live Progress &amp; Notifications</h3>
<p>See detailed progress bars for each file and the entire batch. Buttons update states dynamically, toast notifications keep you informed, and a completion sound plays when the batch finishes.</p>
</div>
<div class="feature-item">
<h3>🧩 System Integration</h3>
<p>Supports Flatpak for easy installation, runs smoothly on Linux desktops.</p>
</div>
</section>
<section class="help-section" aria-label="Help and Documentation">
<h2>Need Help?</h2>
<p>
📖 Learn how to use Recoder in the
<a href="https://github.com/jeena/recoder/blob/main/docs/HELP.md" target="_blank" rel="noopener">HELP.md</a>.
</p>
<p>
🛠 Found a bug or have a suggestion? Report it via the
<a href="https://github.com/jeena/recoder/issues" target="_blank" rel="noopener">issue tracker</a>.
</p>
<p>
💻 Explore the source code on
<a href="https://github.com/jeena/recoder" target="_blank" rel="noopener">GitHub</a>.
</p>
</section>
<footer class="site-footer" aria-label="Site footer">
<p>Developed by <a href="https://jeena.net">Jeena</a></p>
</footer>
</div>
<script>
const track = document.querySelector('.carousel-track');
const images = Array.from(track.children);
const prevBtn = document.getElementById('prev');
const nextBtn = document.getElementById('next');
const toggleBtn = document.getElementById('toggle-install-cli');
const commands = document.getElementById('cli-commands');
let currentIndex = 0;
const total = images.length;
function updateCarousel() {
const carouselWidth = document.querySelector('.carousel').offsetWidth;
track.style.transform = `translateX(${-currentIndex * carouselWidth}px)`;
}
prevBtn.addEventListener('click', () => {
currentIndex = (currentIndex - 1 + total) % total;
updateCarousel();
});
nextBtn.addEventListener('click', () => {
currentIndex = (currentIndex + 1) % total;
updateCarousel();
});
toggleBtn.addEventListener('click', () => {
const isShown = commands.classList.toggle('show');
toggleBtn.setAttribute('aria-expanded', isShown);
toggleBtn.textContent = isShown
? 'Manual installation ▲'
: 'Manual installation ▼';
commands.setAttribute('aria-hidden', !isShown);
});
toggleBtn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleBtn.click();
}
});
window.addEventListener('resize', updateCarousel);
// Initialize carousel position
updateCarousel();
</script>
</body>
</html>