feat(dashcam): mobile layout fix + optional hw-encode conversion on import

- yaris.css: on mobile (<576px) hide file/size/date/upload cols, show
  filename inside thumb cell, icon-only buttons, dash-actions-td nowrap
- local-videos.js: filename in thumb cell for mobile; Converting badge with
  spinner; auto-refresh every 10s while any file is converting
- app_video.py: CONVERT_SETTING, is_convert_enabled(), queue_convert(),
  _convert_bg() — h264_v4l2m2m + aac faststart, deletes original on success
- app_dash.py: call queue_convert() after each successful copy on import
- app_settings.py: expose video_convert_enabled in get/save settings
- settings.html: toggle for 'Convert imported videos for web playback'

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 00:30:59 -04:00
co-authored by Copilot
parent dd2f95b9c1
commit 01704d5429
7 changed files with 130 additions and 18 deletions
+24 -6
View File
@@ -91,12 +91,13 @@
body.replaceChildren();
const files = result.files || [];
let uploadedCount = 0;
let convertingCount = 0;
files.forEach(function (file) {
const row = document.createElement("tr");
row.dataset.path = file.path;
// Thumbnail cell
// Thumbnail cell — also contains filename for mobile view
const preview = document.createElement("td");
preview.style.width = "9rem";
preview.style.padding = "0.25rem";
@@ -105,6 +106,12 @@
image.alt = "";
image.dataset.path = file.path;
preview.appendChild(image);
// Filename shown only on mobile (hidden on desktop via CSS)
const mobileLabel = document.createElement("div");
mobileLabel.className = "dash-thumb-name text-truncate small mt-1";
mobileLabel.style.maxWidth = "5rem";
mobileLabel.textContent = file.path.split("/").pop();
preview.appendChild(mobileLabel);
row.appendChild(preview);
thumbnailObserver.observe(image);
@@ -124,19 +131,25 @@
dateCell.className = "dash-col-date";
row.appendChild(dateCell);
// Upload badge
// Upload / convert badge
const upload = document.createElement("td");
upload.className = "dash-col-upload";
const badge = document.createElement("span");
badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark";
badge.textContent = file.uploaded ? "✓" : "…";
if (file.converting) {
badge.className = "badge bg-info text-dark";
badge.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Converting';
convertingCount++;
} else {
badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark";
badge.textContent = file.uploaded ? "✓" : "…";
}
upload.appendChild(badge);
row.appendChild(upload);
if (file.uploaded) uploadedCount++;
// Actions — wrap in div so td stays a table cell (not flex)
const actionsTd = document.createElement("td");
actionsTd.style.whiteSpace = "nowrap";
actionsTd.className = "dash-actions-td";
const actionsDiv = document.createElement("div");
actionsDiv.className = "data-list-actions";
@@ -160,12 +173,17 @@
});
summary.textContent = `${files.length} video(s), ${YarisUI.formatBytes(result.total_size || 0)} total, ${uploadedCount} uploaded`;
if (convertingCount > 0) summary.textContent += `; ${convertingCount} converting…`;
if (result.retention_days > 0) {
summary.textContent += `; > ${result.retention_days}d uploaded auto-deleted`;
summary.textContent += `; > ${result.retention_days}d uploaded auto-deleted`;
}
if (result.errors && result.errors.length) {
summary.textContent += ` (${result.errors.length} error(s))`;
}
// Auto-refresh every 10s while conversions are running
if (convertingCount > 0) {
setTimeout(refreshFiles, 10000);
}
}
function refreshFiles() {
+3
View File
@@ -15,6 +15,7 @@ let initialUplinkStatusLoaded = false;
const fields = {
dashcamUpload: document.getElementById('dashcam-upload-enabled'),
retention: document.getElementById('retention-days'),
videoConvert: document.getElementById('video-convert-enabled'),
cellularEnabled: document.getElementById('cellular-enabled'),
cellularApn: document.getElementById('cellular-apn'),
ntfyEnabled: document.getElementById('ntfy-enabled'),
@@ -80,6 +81,7 @@ function populate(settings) {
const thresholds = settings.alert_thresholds;
fields.dashcamUpload.checked = settings.dashcam_upload_enabled;
fields.retention.value = settings.local_video_retention_days;
fields.videoConvert.checked = settings.video_convert_enabled;
fields.cellularEnabled.checked = settings.cellular_enabled;
fields.cellularApn.value = settings.cellular_apn;
fields.ntfyEnabled.checked = settings.ntfy_enabled;
@@ -105,6 +107,7 @@ function payload() {
return {
dashcam_upload_enabled: fields.dashcamUpload.checked,
local_video_retention_days: Number(fields.retention.value),
video_convert_enabled: fields.videoConvert.checked,
cellular_enabled: fields.cellularEnabled.checked,
cellular_apn: fields.cellularApn.value,
ntfy_enabled: fields.ntfyEnabled.checked,