av1converter 3.0.0

Batch convert video files to AV1 with FFmpeg, from a terminal UI or a daemon with a web UI
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AV1Converter</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<!-- Announcement regions. role and aria-live are fixed here and never
     reassigned; app.js writes text into one or the other. -->
<div id="live-polite" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></div>
<div id="live-assertive" class="sr-only" role="alert" aria-live="assertive" aria-atomic="true"></div>

<div id="offline-banner" class="banner hidden" data-i18n="offline">Daemon unreachable — retrying…</div>

<header>
  <h1 class="brand">AV1<span>Converter</span></h1>
  <!-- Live region for phase changes. setText() in app.js writes it only when
       the text differs, and a poll carrying no change announces nothing. -->
  <span id="status-pill" class="pill" role="status" aria-live="polite" aria-atomic="true">…</span>
  <nav data-i18n-aria-label="nav_primary" aria-label="Primary" role="tablist">
    <button id="queue-tab" class="tab active" data-tab="queue" data-i18n="tab_queue"
            role="tab" aria-selected="true" aria-controls="tab-queue">Queue</button>
    <button id="settings-tab" class="tab" data-tab="settings" data-i18n="tab_settings"
            role="tab" aria-selected="false" aria-controls="tab-settings" tabindex="-1">Settings</button>
  </nav>
</header>

<div class="layout">
<main>
  <!-- ── Queue ─────────────────────────────────────────────────── -->
  <section id="tab-queue" role="tabpanel" aria-labelledby="queue-tab">
    <h2 class="sr-only" data-i18n="tab_queue">Queue</h2>
    <div class="overview">
      <div class="tiles">
        <div class="tile"><h3 class="tile-label" data-i18n="stat_in_queue">In queue</h3><div class="tile-value" id="stat-total">0</div></div>
        <div class="tile"><h3 class="tile-label" data-i18n="stat_space_saved">Total space saved</h3><div class="tile-value" id="stat-saved">0 B</div></div>
      </div>
      <p id="host-status" class="muted"></p>

      <div class="panel">
        <h3 class="panel-title" id="current-label" data-i18n="current_file">Current file</h3>
        <div id="current-file" class="filename">Idle — nothing encoding</div>
        <progress class="bar" id="current-progress" aria-labelledby="current-label"
                  max="100" value="0">0%</progress>
        <div class="bar-meta">
          <span id="current-pct"></span>
          <span id="current-stage" class="muted"></span>
        </div>
      </div>

      <div class="panel">
        <h3 class="panel-title" id="overall-label" data-i18n="overall_progress">Overall progress</h3>
        <progress class="bar" id="overall-progress" aria-labelledby="overall-label"
                  max="100" value="0">0%</progress>
        <div class="bar-meta">
          <span id="overall-pct">0%</span>
          <span id="eta" class="muted"></span>
        </div>
      </div>
    </div>

    <div class="toolbar">
      <button id="btn-add-file" data-i18n="add_file">+ File</button>
      <button id="btn-add-folder" data-i18n="add_folder">+ Folder</button>
      <button id="btn-add-recursive" data-i18n="add_folder_recursive">+ Folder (recursive)</button>
      <button id="btn-add-disc" data-i18n="add_disc">+ Disc</button>
      <span class="spacer"></span>
      <button id="btn-cancel" class="danger">Cancel encoding</button>
      <button id="btn-cancel-analysis" class="danger" data-i18n="cancel_analysis" hidden>Cancel analysis</button>
      <button id="btn-cancel-disc" class="danger" data-i18n="cancel_disc" hidden>Cancel disc</button>
      <button id="btn-clear" data-i18n="clear_finished">Clear finished</button>
    </div>
    <!-- Session totals. Hidden while work remains or until a result exists. -->
    <div id="summary" class="summary hidden">
      <h3 class="summary-title" data-i18n="session_totals">Session totals</h3>
      <span class="summary-figure good">
        <span class="summary-label" data-i18n="summary_converted">Converted</span>
        <span id="summary-converted" class="summary-value">0</span>
      </span>
      <span id="summary-skipped-group" class="summary-figure">
        <span class="summary-label" data-i18n="badge_skipped">Skipped</span>
        <span id="summary-skipped" class="summary-value">0</span>
      </span>
      <span id="summary-cancelled-group" class="summary-figure hidden">
        <span class="summary-label" data-i18n="summary_cancelled">Cancelled</span>
        <span id="summary-cancelled" class="summary-value">0</span>
      </span>
      <span id="summary-errors-group" class="summary-figure">
        <span class="summary-label" data-i18n="summary_errors">Errors</span>
        <span id="summary-errors" class="summary-value">0</span>
      </span>
      <span class="summary-figure">
        <span class="summary-label" data-i18n="stat_space_saved">Total space saved</span>
        <span id="summary-saved" class="summary-value">0 B</span>
      </span>
      <span id="summary-time-group" class="summary-figure">
        <span class="summary-label" data-i18n="summary_time">Total time</span>
        <span id="summary-time" class="summary-value">—</span>
      </span>
      <button id="summary-dismiss" class="iconbtn"
              data-i18n-aria-label="summary_dismiss" aria-label="Dismiss summary">✕</button>
    </div>

    <div class="table-wrap">
      <table id="queue-table">
        <thead>
          <tr><th scope="col" data-i18n="col_file">File</th><th scope="col" data-i18n="col_source">Source</th><th scope="col" data-i18n="col_status">Status</th><th scope="col" data-i18n="col_size">Size</th><th scope="col" data-i18n="col_saved">Saved</th><th scope="col"><span class="sr-only" data-i18n="move_up">Move up</span></th><th scope="col"><span class="sr-only" data-i18n="tracks_title">Tracks</span></th><th scope="col"><span class="sr-only" data-i18n="remove_from_queue">Remove from queue</span></th></tr>
        </thead>
        <tbody id="queue-body"></tbody>
        <tbody id="queue-empty" class="hidden">
          <tr><td colspan="8" class="muted center" data-i18n="queue_empty">Queue is empty — add files to start encoding</td></tr>
        </tbody>
      </table>
    </div>
  </section>

  <!-- ── Settings ──────────────────────────────────────────────── -->
  <section id="tab-settings" class="hidden" role="tabpanel" aria-labelledby="settings-tab">
    <h2 class="sr-only" data-i18n="tab_settings">Settings</h2>
    <form id="settings-form" autocomplete="off"></form>
    <div class="toolbar">
      <button id="btn-save-settings" type="button" class="primary" data-i18n="save_settings" disabled>Save settings</button>
      <button id="btn-reset-settings" type="button" data-i18n="discard_changes" disabled>Discard changes</button>
      <span class="muted" data-i18n="settings_note">Changes apply to the next encode; daemon address/port need a restart.</span>
    </div>
  </section>
</main>

</div>

<!-- ── Server-side file browser ────────────────────────────────── -->
<dialog id="browser" aria-labelledby="browser-title">
  <div class="browser-head">
    <h2 id="browser-title">Select a video file</h2>
    <!-- Filled by loadDir() with one button per path segment. -->
    <div id="browser-path" class="crumbs"></div>
    <span id="browser-busy" class="muted hidden" data-i18n="loading">Loading…</span>
  </div>
  <ul id="browser-list"></ul>
  <div class="browser-foot">
    <label class="muted"><input type="checkbox" id="browser-hidden"> <span data-i18n="hidden_files">Hidden files</span></label>
    <span class="spacer"></span>
    <button id="browser-choose" class="primary hidden">Add this folder</button>
    <button id="browser-close" data-i18n="cancel">Cancel</button>
  </div>
</dialog>

<!-- ── Per-job track selection ─────────────────────────────────── -->
<dialog id="tracks-modal" aria-labelledby="tracks-title">
  <div class="modal-head">
    <h2 id="tracks-title"><span data-i18n="tracks_title">Tracks</span><span id="tracks-filename" class="muted"></span></h2>
    <button id="tracks-close" class="iconbtn" data-i18n-aria-label="tracks_close" aria-label="Close track selection">✕</button>
  </div>
  <div id="tracks-body" class="modal-body"></div>
  <div class="modal-foot">
    <span id="tracks-note" class="muted"></span>
    <label id="tracks-apply-wrap" class="apply-remaining hidden">
      <input type="checkbox" id="tracks-apply-remaining" aria-describedby="tracks-apply-hint">
      <span data-i18n="apply_remaining">Apply to remaining files</span>
    </label>
    <span id="tracks-apply-hint" class="muted hidden" data-i18n="apply_remaining_hint">Matches tracks by order — best for files with the same track layout.</span>
    <span class="spacer"></span>
    <button id="tracks-back" data-i18n="back_to_queue">Back to queue</button>
    <button id="tracks-save" class="primary" data-i18n="confirm_tracks">Confirm tracks</button>
  </div>
</dialog>

<!-- ── Disc import ─────────────────────────────────────────────── -->
<dialog id="disc-modal" aria-labelledby="disc-title">
  <div class="modal-head">
    <h2 id="disc-title" data-i18n="disc_title">Rip DVD / Blu-ray</h2>
    <button id="disc-close" class="iconbtn" data-i18n-aria-label="cancel" aria-label="Cancel">✕</button>
  </div>
  <div id="disc-body" class="modal-body"></div>
  <div class="modal-foot">
    <span id="disc-note" class="muted"></span>
    <span class="spacer"></span>
    <button id="disc-back" data-i18n="back" hidden>Back</button>
    <button id="disc-folder" data-i18n="disc_open_folder">Open a folder…</button>
    <button id="disc-rip" class="primary" data-i18n="disc_rip" disabled>Rip</button>
  </div>
</dialog>

<!-- ── Confirm stop ────────────────────────────────────────────── -->
<dialog id="confirm-modal" aria-labelledby="confirm-title" aria-describedby="confirm-body">
  <div class="modal-head">
    <h2 id="confirm-title" data-i18n="confirm">Confirm</h2>
  </div>
  <p id="confirm-body" class="modal-body"></p>
  <div class="modal-foot">
    <span class="spacer"></span>
    <button id="confirm-no" data-i18n="cancel">Cancel</button>
    <button id="confirm-yes" class="danger" data-i18n="confirm">Confirm</button>
  </div>
</dialog>

<div id="toast" class="toast hidden" popover="manual">
  <span id="toast-message"></span>
  <button id="toast-close" class="iconbtn" data-i18n-aria-label="dismiss" aria-label="Dismiss">✕</button>
</div>

<script src="/app.js"></script>
</body>
</html>