forked from dsutanto/bytetrack-counter-cpp
Compare commits
2
Commits
e5a4658ae0
...
cb9be7a402
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb9be7a402 | ||
|
|
9ea6820a37 |
No files matched your search
+5707
-5
File diff suppressed because it is too large.
Load diff
@@ -45,6 +45,21 @@ Place a `.reset` file in `SHM_DIR` (default `/dev/shm/bytetrack-counter`) to tri
|
||||
touch /dev/shm/bytetrack-counter/.reset
|
||||
```
|
||||
|
||||
## STATE_FILE lifecycle (file sources)
|
||||
|
||||
For local file input, STATE_FILE persists the recount result so the dashboard's
|
||||
`/api/current-batch` still shows the final batch after the process exits:
|
||||
|
||||
- **Start**: STATE_FILE is deleted unless a live instance is running — detected by
|
||||
a `/proc/*/cmdline` scan for another `bytetrack-counter` whose source is
|
||||
`rtsp://`/`http://` (argv or `SOURCE=` in its config). If live is active, the
|
||||
file is kept and a `[INFO] Live instance active — keeping STATE_FILE` line is
|
||||
logged. Stop the live service before recounting so stale recount state is cleared.
|
||||
- **Finish**: on graceful shutdown (`end_batch("shutdown")` — EOF or SIGTERM) the
|
||||
final batch snapshot is written to STATE_FILE (in both the persist and the
|
||||
discard-below-minimum paths) instead of being deleted.
|
||||
- Live sources are unchanged: resume at start, delete on finish.
|
||||
|
||||
## Shared memory
|
||||
|
||||
`SHM_DIR` (`/dev/shm/bytetrack-counter` by default) stores:
|
||||
|
||||
@@ -55,6 +55,7 @@ V15: RECORD_CRF (lower = better, -1 unset) & RECORD_PRESET → OPENCV_FFMPEG_WRI
|
||||
V16: pause_on_first_frame (env/--pause-on-first-frame, file source only; live ignored w/ warn): process frame 0 → idle-loop, re-write annotated frame to LIVE_STREAM_FRAME_PATH, wait for SHM_DIR/.continue → delete → resume
|
||||
V17: file sources (non-live) record from frame 0: SPECULATING never discarded, any active recording saved on batch close and shutdown → output first frame = source frame 0 (no RECORD_DELAY offset)
|
||||
V18: file sources: cap opened w/ CAP_PROP_POS_FRAMES=0 (frame-0 seek); CAP_PROP_BUFFERSIZE=1 live-only; ∀ live|file → first decoded frame == source frame 0 (no build-buffer offset into file)
|
||||
V19: file sources: STATE_FILE deleted @ startup IF no live instance active (bytetrack-counter w/ rtsp|http src in argv|config → /proc scan); preserved @ graceful shutdown (end_batch "shutdown" → final snapshot written, ⊥ deleted). Live: resume @ start, delete @ finish (unchanged)
|
||||
```
|
||||
|
||||
## §T — Tasks
|
||||
@@ -81,6 +82,7 @@ T17|~?|Python DEBUG_TRACKING equivalent (per-frame score/track dump)|-
|
||||
T18|x|CLI --source/-s/--source= override of SOURCE (rtsp|http|local mp4; file = single pass)|I.cmd
|
||||
T19|x|Pause-on-first-frame for file source: process frame 0, hold annotated frame as live view, wait for SHM_DIR/.continue, resume. Env PAUSE_ON_FIRST_FRAME + --pause-on-first-frame; stale .continue cleared @ startup; live source ignored w/ warning.|V16,V18
|
||||
T20|x|File-source capture: POS_FRAMES=0 seek @ open (frame 0 !), BUFFERSIZE live-only; log pos_frames/pos_msec/frame_count/fps on first decoded frame|V18
|
||||
T21|x|File-source STATE_FILE lifecycle: startup delete (guard: live-instance /proc scan → skip delete if active), preserve final snapshot @ graceful shutdown (end_batch "shutdown")|V19
|
||||
```
|
||||
|
||||
## §B — Bugs
|
||||
@@ -88,4 +90,5 @@ T20|x|File-source capture: POS_FRAMES=0 seek @ open (frame 0 !), BUFFERSIZE live
|
||||
```
|
||||
id|date|cause|fix
|
||||
B1|2026-08-10|file source CAP_PROP_BUFFERSIZE=1 @ open → OpenCV FFMPEG build buffer offset → first decoded frame ≠ source frame 0 (paused view showed ~11s-later clock 21:07:39 vs mp4 frame0 21:07:28)|V18
|
||||
B2|2026-08-12|file input graceful finish deleted STATE_FILE (save_state w/ null current_state_) → recount result invisible to /api/current-batch after exit; stale file resumed next run|V19
|
||||
```
|
||||
Binary file not shown.
+12
-2
@@ -332,7 +332,12 @@ bool BatchStore::end_batch_locked(const std::string& closed_by) {
|
||||
|
||||
if (count < min_object_per_batch_ || duration_sec < min_duration_per_batch_) {
|
||||
current_state_ = nullptr;
|
||||
save_state();
|
||||
if (preserve_state_file_ && closed_by == "shutdown") {
|
||||
std::ofstream f(state_file_);
|
||||
f << state.dump(2);
|
||||
} else {
|
||||
save_state();
|
||||
}
|
||||
batch_timer_running_ = false;
|
||||
if (batch_closed_cb_) batch_closed_cb_(batch_number, counting_date);
|
||||
return false;
|
||||
@@ -347,7 +352,12 @@ bool BatchStore::end_batch_locked(const std::string& closed_by) {
|
||||
}
|
||||
|
||||
current_state_ = nullptr;
|
||||
save_state();
|
||||
if (preserve_state_file_ && closed_by == "shutdown") {
|
||||
std::ofstream f(state_file_);
|
||||
f << state.dump(2);
|
||||
} else {
|
||||
save_state();
|
||||
}
|
||||
batch_timer_running_ = false;
|
||||
if (batch_closed_cb_) batch_closed_cb_(batch_number, counting_date);
|
||||
return true;
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
int display_total();
|
||||
|
||||
void set_batch_closed_callback(std::function<void(int, const std::string&)> cb);
|
||||
void set_preserve_state_file(bool v) { preserve_state_file_ = v; }
|
||||
void reset();
|
||||
|
||||
bool check_cutoff_reset();
|
||||
@@ -85,4 +86,5 @@ private:
|
||||
|
||||
json current_state_;
|
||||
json previous_state_;
|
||||
bool preserve_state_file_ = false;
|
||||
};
|
||||
@@ -14,11 +14,16 @@
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <deque>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <dirent.h>
|
||||
|
||||
static std::atomic<bool> shutdown_requested{false};
|
||||
|
||||
@@ -35,6 +40,53 @@ static std::string now_str() {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static bool live_instance_running() {
|
||||
pid_t self_pid = getpid();
|
||||
DIR* dir = opendir("/proc");
|
||||
if (!dir) return false;
|
||||
struct dirent* ent;
|
||||
while ((ent = readdir(dir)) != nullptr) {
|
||||
if (ent->d_name[0] < '0' || ent->d_name[0] > '9') continue;
|
||||
if (std::atoi(ent->d_name) == static_cast<int>(self_pid)) continue;
|
||||
std::string cmdline_path = std::string("/proc/") + ent->d_name + "/cmdline";
|
||||
std::ifstream f(cmdline_path);
|
||||
if (!f) continue;
|
||||
std::vector<std::string> args;
|
||||
std::string tok;
|
||||
while (std::getline(f, tok, '\0')) {
|
||||
if (!tok.empty()) args.push_back(tok);
|
||||
}
|
||||
if (args.empty()) continue;
|
||||
std::string base = args[0];
|
||||
auto slash = base.find_last_of("/\\");
|
||||
if (slash != std::string::npos) base = base.substr(slash + 1);
|
||||
if (base != "bytetrack-counter") continue;
|
||||
for (const auto& a : args) {
|
||||
if (a.rfind("rtsp://", 0) == 0 || a.rfind("http://", 0) == 0) {
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::string cfg = args.size() > 1 ? args[1] : "";
|
||||
if (!cfg.empty() && cfg[0] != '-' && cfg.find("rtsp://") == std::string::npos &&
|
||||
cfg.find("http://") == std::string::npos) {
|
||||
std::ifstream cf(cfg);
|
||||
std::string line;
|
||||
while (std::getline(cf, line)) {
|
||||
if (line.rfind("SOURCE=", 0) == 0) {
|
||||
std::string v = line.substr(7);
|
||||
if (v.rfind("rtsp://", 0) == 0 || v.rfind("http://", 0) == 0) {
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
static cv::VideoCapture open_capture(const std::string& source) {
|
||||
bool is_live = source.substr(0, 7) == "rtsp://" || source.substr(0, 7) == "http://";
|
||||
if (is_live) {
|
||||
@@ -206,6 +258,16 @@ int main(int argc, char* argv[]) {
|
||||
std::filesystem::remove(g_config.shm_dir + "/.continue", ce);
|
||||
}
|
||||
|
||||
if (!is_live && !live_instance_running()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(g_config.state_file, ec);
|
||||
std::cout << "[INFO] File source — cleared STATE_FILE "
|
||||
<< g_config.state_file << std::endl;
|
||||
} else if (!is_live) {
|
||||
std::cout << "[INFO] Live instance active — keeping STATE_FILE "
|
||||
<< g_config.state_file << std::endl;
|
||||
}
|
||||
|
||||
int class_ayam = g_config.ayam_class_id;
|
||||
int class_talenan = g_config.talenan_class_id;
|
||||
|
||||
@@ -221,6 +283,7 @@ int main(int argc, char* argv[]) {
|
||||
g_config.min_object_per_batch,
|
||||
g_config.min_duration_per_batch,
|
||||
logger);
|
||||
store.set_preserve_state_file(!is_live);
|
||||
store.start_cutoff_watcher();
|
||||
if (startup_reset) store.reset();
|
||||
|
||||
|
||||
Reference in new issue
Block a user