diff --git a/recounting_dashboard.py b/recounting_dashboard.py
index 4104274..0d52856 100644
--- a/recounting_dashboard.py
+++ b/recounting_dashboard.py
@@ -42,6 +42,7 @@ _FILENAME_RE = re.compile(r"batch[_-](\d+)[_-](\d{8})[_-]\d{6}\.mp4", re.IGNOREC
_streaming_mp4 = None
_streaming_lock = threading.Lock()
_streaming_proc = None
+_current_file = None
def _parse_filename(filename):
@@ -115,7 +116,7 @@ def api_mp4_files():
@app.route("/api/start-recount", methods=["POST"])
def start_recount():
- global _streaming_mp4, _streaming_proc
+ global _streaming_mp4, _streaming_proc, _current_file
data = request.get_json(force=True) or {}
mp4_path = data.get("path", "")
if not mp4_path:
@@ -125,6 +126,7 @@ def start_recount():
with _streaming_lock:
_stop_ffmpeg_locked()
+ _current_file = mp4_path
cmd = [
"ffmpeg", "-re", "-i", mp4_path,
"-vf", "scale=640:480",
@@ -139,16 +141,39 @@ def start_recount():
"success": True,
"stream_url": f"{RECOUNT_API_URL}/api/live-video",
"file": Path(mp4_path).name,
+ "path": mp4_path,
})
@app.route("/api/stop-recount", methods=["POST"])
def stop_recount():
+ global _current_file
with _streaming_lock:
_stop_ffmpeg_locked()
+ _current_file = None
return jsonify({"success": True})
+@app.route("/api/state")
+def api_state():
+ with _streaming_lock:
+ streaming = _streaming_proc is not None and _streaming_proc.poll() is None
+ mp4 = _streaming_mp4
+ current_file = _current_file
+ result = {
+ "streaming": streaming,
+ }
+ if current_file:
+ result["file"] = current_file
+ result["file_name"] = Path(current_file).name
+ result["stream_url"] = f"{RECOUNT_API_URL}/api/live-video"
+ batch_num, date_str = _parse_filename(Path(current_file).name)
+ if batch_num is not None:
+ result["batch_number"] = batch_num
+ result["batch_date"] = date_str
+ return jsonify(result)
+
+
@app.route("/api/download-mp4")
def api_download_mp4():
fpath = request.args.get("file", "")
diff --git a/templates/recounting.html b/templates/recounting.html
index bde823f..e88f512 100644
--- a/templates/recounting.html
+++ b/templates/recounting.html
@@ -434,11 +434,56 @@ let streamUrl = '';
document.addEventListener('DOMContentLoaded', () => {
loadRecountProgress();
loadFiles();
+ syncState();
setInterval(loadRecountProgress, 500);
setInterval(loadFiles, 30000);
+ setInterval(syncState, 1000);
});
+async function syncState() {
+ try {
+ const res = await fetch('/api/state');
+ const data = await res.json();
+ applyState(data);
+ } catch (err) {}
+}
+
+function applyState(data) {
+ if (!data) return;
+
+ if (data.file) {
+ selectedFile = data.file;
+ document.getElementById('btn-start').disabled = data.streaming;
+ document.getElementById('btn-download').disabled = false;
+ }
+
+ if (data.streaming && data.file) {
+ streaming = true;
+ streamUrl = data.stream_url;
+ const img = document.getElementById('stream-img');
+ const placeholder = document.getElementById('video-placeholder');
+ if (img.src.indexOf(streamUrl) === -1) {
+ img.src = streamUrl + '?' + Date.now();
+ img.style.display = 'block';
+ if (placeholder) placeholder.style.display = 'none';
+ }
+ document.getElementById('btn-stop').disabled = false;
+ document.getElementById('btn-start').disabled = true;
+ document.getElementById('btn-start').textContent = 'STREAMING';
+ document.getElementById('btn-start').classList.add('btn-ghost');
+ document.getElementById('video-status').innerHTML =
+ ' streaming ' + (data.file_name || '');
+ document.getElementById('video-status').className = 'video-status connected';
+
+ loadBatchResult(data.file);
+ } else if (!data.streaming && data.file) {
+ loadBatchResult(data.file);
+ }
+
+ loadFiles();
+}
+
// --- File list ---
async function loadFiles() {
@@ -485,8 +530,8 @@ function selectFile(path, name) {
return;
}
selectedFile = path;
- document.getElementById('btn-start').disabled = false;
document.getElementById('btn-download').disabled = false;
+ if (!streaming) document.getElementById('btn-start').disabled = false;
loadFiles();
loadBatchResult(path);
}
@@ -557,7 +602,6 @@ function downloadFile() {
async function startRecount() {
if (!selectedFile) return;
const btnStart = document.getElementById('btn-start');
- const btnStop = document.getElementById('btn-stop');
btnStart.disabled = true;
btnStart.textContent = 'CONNECTING...';
@@ -576,6 +620,7 @@ async function startRecount() {
}
streamUrl = data.stream_url;
streaming = true;
+ selectedFile = data.path;
const img = document.getElementById('stream-img');
const placeholder = document.getElementById('video-placeholder');
@@ -583,15 +628,9 @@ async function startRecount() {
img.style.display = 'block';
if (placeholder) placeholder.style.display = 'none';
- btnStop.disabled = false;
+ document.getElementById('btn-stop').disabled = false;
btnStart.textContent = 'STREAMING';
btnStart.classList.add('btn-ghost');
-
- document.getElementById('video-status').innerHTML =
- ' streaming ' + data.file;
- document.getElementById('video-status').className = 'video-status connected';
-
- loadFiles();
} catch (err) {
alert('Error: ' + err.message);
btnStart.disabled = false;
@@ -605,6 +644,8 @@ async function stopRecount() {
} catch (err) {}
streaming = false;
streamUrl = '';
+ selectedFile = null;
+ batchResultLoaded = false;
const img = document.getElementById('stream-img');
const placeholder = document.getElementById('video-placeholder');
@@ -614,7 +655,7 @@ async function stopRecount() {
const btnStart = document.getElementById('btn-start');
const btnStop = document.getElementById('btn-stop');
- btnStart.disabled = selectedFile ? false : true;
+ btnStart.disabled = true;
btnStart.textContent = '▶ START RECOUNT';
btnStart.classList.remove('btn-ghost');
btnStop.disabled = true;
@@ -623,6 +664,14 @@ async function stopRecount() {
' stopped';
document.getElementById('video-status').className = 'video-status stopped';
+ document.getElementById('live-title').innerHTML = '◉ Live Counter';
+ document.getElementById('live-count').textContent = '--';
+ document.getElementById('live-label').textContent = 'Objects Counted';
+ document.getElementById('live-batch').textContent = '--';
+ document.getElementById('live-time').textContent = '--';
+ document.getElementById('live-status').textContent = 'no file selected';
+ document.getElementById('live-status').className = 'counter-status disconnected';
+
loadFiles();
}