feat: batch clip download from motionEye (trim+concat, history Klip button)

This commit is contained in:
andrew committed 2026-09-28 15:39:33 +07:00
1 parent d3357fccef
commit 110d557dbf
8 files changed
+694 -6

No files matched your search

+54 -4
View File
@@ -269,11 +269,12 @@
<th>Start Time</th>
<th>End Time</th>
<th>Duration (Min)</th>
<th>Klip</th>
</tr>
</thead>
<tbody id="batchesTableBody">
<tr>
<td colspan="9" class="no-data">
<td colspan="10" class="no-data">
<i class="fa-solid fa-arrow-left"></i>
Silakan pilih tanggal dari daftar sebelah kiri untuk memuat detail batch.
</td>
@@ -357,7 +358,7 @@
const tableBody = document.getElementById('batchesTableBody');
tableBody.innerHTML = `
<tr>
<td colspan="9" class="no-data">
<td colspan="10" class="no-data">
<i class="fa-solid fa-circle-notch fa-spin"></i>
Memuat data batch untuk tanggal ${date}...
</td>
@@ -383,7 +384,7 @@
if (!data.batches || data.batches.length === 0) {
tableBody.innerHTML = `
<tr>
<td colspan="9" class="no-data">
<td colspan="10" class="no-data">
<i class="fa-solid fa-inbox"></i>
Tidak ada batch tercatat pada tanggal ${date}.
</td>
@@ -411,6 +412,15 @@
<td>${startStr}</td>
<td>${endStr}</td>
<td>${b.duration_minutes || 0} min</td>
<td style="text-align: center;">
<button type="button" class="btn btn-secondary"
style="padding: 5px 10px; font-size: 12px; gap: 6px;"
data-date="${escapeHtmlHist(selectedDate)}"
data-batch="${b.batch_number}"
onclick="downloadBatchClip(this)">
<i class="fa-solid fa-film"></i> Klip
</button>
</td>
`;
tableBody.appendChild(tr);
});
@@ -419,7 +429,7 @@
console.error('Failed to load date details:', err);
tableBody.innerHTML = `
<tr>
<td colspan="9" class="no-data" style="color: var(--accent-red);">
<td colspan="10" class="no-data" style="color: var(--accent-red);">
<i class="fa-solid fa-circle-exclamation"></i>
Gagal memuat detail data: ${err.message}
</td>
@@ -434,6 +444,46 @@
})[c]);
}
// Klip: fetch office-only /api/batch-clip/<date>/<batch> (may take ~30s),
// then save the blob under the filename from Content-Disposition.
async function downloadBatchClip(btn) {
if (btn.disabled) return;
const date = btn.dataset.date;
const batch = btn.dataset.batch;
const original = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Memuat...';
try {
const res = await fetch(`/api/batch-clip/${encodeURIComponent(date)}/${encodeURIComponent(batch)}`);
if (!res.ok) {
let msg = `Gagal mengunduh klip (HTTP ${res.status})`;
try {
const data = await res.json();
if (data && data.error) msg = data.error;
} catch (e) { /* body bukan JSON */ }
alert(msg);
return;
}
const blob = await res.blob();
const cd = res.headers.get('Content-Disposition') || '';
const m = cd.match(/filename="?([^";]+)"?/);
const filename = m ? m[1] : `b${batch}-${date}.mp4`;
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 60000);
} catch (err) {
alert('Gagal mengunduh klip: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = original;
}
}
document.addEventListener('DOMContentLoaded', loadDates);
</script>
{% endblock %}