Save video under batch counting date; fix retention timezone

This commit is contained in:
proitlab committed 2026-08-04 23:58:35 +07:00
1 parent a94c3dff87
commit 37f98f243c
9 files changed
+37 -19

No files matched your search

+14
View File
@@ -53,3 +53,17 @@ touch /dev/shm/bytetrack-counter/.reset
- `.reset` trigger file
Directory auto-created at startup.
## Video recordings
- Recordings are written to `SHM_DIR` as `batch_pending_*.mp4`, confirmed when ayam detected, and moved via `fork()` (non-blocking) to `OUTPUT_DIR/<batch counting_date>/batch_N_HHMMSS.mp4`.
- The day folder uses the **batch counting date** (today before `DAILY_CUTOFF_TIME`, tomorrow after), forwarded from `BatchStore` through the batch-closed callback — matching the SQLite `counting_date`.
- Retention cleanup compares day folders with `std::localtime` to match folder naming.
## Source override
```bash
bytetrack-counter <config.env> --source /tmp/test.mp4 # local file (single pass)
bytetrack-counter <config.env> --source rtsp://cam:554/x # live stream
bytetrack-counter <config.env> -s path # or --source=path
```
+6 -5
View File
@@ -23,13 +23,13 @@ env: .env KEY=VALUE file — all config per config.env.example
env: SHM_DIR → shared memory directory (/dev/shm/bytetrack-counter)
env: LIVE_STREAM_FRAME_PATH → JPEG snapshot path
env: RECORD_VIDEO, RECORD_DELAY_SECONDS, RECORD_END_DELAY, RECORD_RETENTION_DAYS
cmd: bytetrack-counter <config.env>
cmd: bytetrack-counter <config.env> [--source <rtsp://|http://|/path/to/file.mp4>]
db: SQLite batches(id,counting_date,batch_number,camera_name,object_label,count,start_time,end_time)
db: SQLite daily_summaries(id,counting_date,camera_name,object_label,total_count,total_batches)
file: JSON state — batch_number,count,start_time,last_detection_time,counted_event_ids[]
file: CSV crossing log — batch,frame,timestamp,chicken_id
file: SHM_DIR/.reset → trigger full state reset
file: SHM_DIR/batch_N_HHMMSS.mp4 → per-batch video recording (moved to OUTPUT_DIR/YYYY-MM-DD/)
file: SHM_DIR/batch_N_HHMMSS.mp4 → per-batch video recording (moved to OUTPUT_DIR/<batch counting_date>/)
sig: SIGTERM|SIGINT → shutdown_requested → graceful exit
```
@@ -48,8 +48,8 @@ V9: live_stream_enabled → parent dir of LIVE_STREAM_FRAME_PATH created @ star
V10: stale track (> TRACKED_PRUNE_SEC unseen) → prune from external tracking maps
V11: all dirs (output_dir, shm_dir, live_stream parent) created @ startup
V12: SHM_DIR/.reset → BatchStore::reset() — end active batch, clear memory, delete state_file, write blank live JPEG
V13: video recording: motion → SPECULATING (≤RECORD_DELAY_SECONDS), ayam → CONFIRMED → talenan/batch-end → STOPPING (+RECORD_END_DELAY) → move to OUTPUT_DIR/YYYY-MM-DD/
V14: video retention: day-dirs > RECORD_RETENTION_DAYS old → removed @ startup & @ each CUTOFF_TIME
V13: video recording: motion → SPECULATING (≤RECORD_DELAY_SECONDS), ayam → CONFIRMED → talenan/batch-end → STOPPING (+RECORD_END_DELAY) → move to OUTPUT_DIR/<batch counting_date>/ via fork
V14: video retention: day-dirs > RECORD_RETENTION_DAYS old → removed @ startup & @ each CUTOFF_TIME (localtime cutoff matches folder names)
```
## §T — Tasks
@@ -71,8 +71,9 @@ T12|x|CsvLogger for crossing events|I.file
T13|x|Live JPEG snapshot to shared memory|V9
T14|x|systemd service with ProtectSystem + ReadWritePaths|I.cmd
T15|x|Graceful shutdown: SIGTERM → close batch → save state|I.sig
T16|x|Video per-batch recording: motion-triggered → speculating in shm, confirmed when ayam detected ≤ RECORD_DELAY_SECONDS, stopped RECORD_END_DELAY after batch close, moved to OUTPUT_DIR/YYYY-MM-DD/ via fork, retention cleanup at startup & cutoff. Unconfirmed recordings deleted.|V13,V14
T16|x|Video per-batch recording: motion-triggered → speculating in shm, confirmed when ayam detected ≤ RECORD_DELAY_SECONDS, stopped RECORD_END_DELAY after batch close, moved to OUTPUT_DIR/<batch counting_date>/ via fork, retention cleanup at startup & cutoff. Unconfirmed recordings deleted.|V13,V14
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
```
## §B — Bugs
Binary file not shown.
+2 -2
View File
@@ -4,11 +4,11 @@
namespace version {
const std::string GIT_COMMIT = "eefff489";
const std::string GIT_COMMIT = "a94c3dff";
const std::string GIT_BRANCH = "main";
const std::string GIT_TAG = "";
const std::string BUILD_DATE = "2026-08-04";
const std::string BUILD_TIME = "23:35:10";
const std::string BUILD_TIME = "23:55:47";
const int BUILD_YEAR = 2026;
const bool BUILD_RKNN = 1;
+4 -3
View File
@@ -328,12 +328,13 @@ bool BatchStore::end_batch_locked(const std::string& closed_by) {
int count = state["count"].get<int>();
int batch_number = state["batch_number"].get<int>();
std::string counting_date = state.value("counting_date", "");
if (count < min_object_per_batch_ || duration_sec < min_duration_per_batch_) {
current_state_ = nullptr;
save_state();
batch_timer_running_ = false;
if (batch_closed_cb_) batch_closed_cb_(batch_number);
if (batch_closed_cb_) batch_closed_cb_(batch_number, counting_date);
return false;
}
@@ -348,7 +349,7 @@ bool BatchStore::end_batch_locked(const std::string& closed_by) {
current_state_ = nullptr;
save_state();
batch_timer_running_ = false;
if (batch_closed_cb_) batch_closed_cb_(batch_number);
if (batch_closed_cb_) batch_closed_cb_(batch_number, counting_date);
return true;
}
@@ -409,7 +410,7 @@ int BatchStore::display_total() {
return get_closed_total_for_day(get_counting_date()) + current_batch_count();
}
void BatchStore::set_batch_closed_callback(std::function<void(int)> cb) {
void BatchStore::set_batch_closed_callback(std::function<void(int, const std::string&)> cb) {
batch_closed_cb_ = std::move(cb);
}
+2 -2
View File
@@ -31,7 +31,7 @@ public:
int current_batch_count() const;
int display_total();
void set_batch_closed_callback(std::function<void(int)> cb);
void set_batch_closed_callback(std::function<void(int, const std::string&)> cb);
void reset();
bool check_cutoff_reset();
@@ -70,7 +70,7 @@ private:
int min_duration_per_batch_;
int carry_ids_ = 50;
std::function<void(const std::string&)> logger_;
std::function<void(int)> batch_closed_cb_;
std::function<void(int, const std::string&)> batch_closed_cb_;
sqlite3* db_ = nullptr;
std::mutex state_mutex_;
+2 -2
View File
@@ -266,7 +266,7 @@ int main(int argc, char* argv[]) {
g_config.record_end_delay_seconds,
g_config.record_retention_days);
auto* vw = video_writer.get();
store.set_batch_closed_callback([vw](int bn) { vw->on_batch_closed(bn); });
store.set_batch_closed_callback([vw](int bn, const std::string& date) { vw->on_batch_closed(bn, date); });
}
int reconnect_count = 0;
@@ -617,8 +617,8 @@ int main(int argc, char* argv[]) {
}
cap.release();
if (video_writer) video_writer->shutdown();
store.shutdown();
if (video_writer) video_writer->shutdown();
std::cout << "\n=== Batch Summary (SQLite) ===" << std::endl;
std::cout << "Database: " << g_config.db_path << std::endl;
+5 -4
View File
@@ -41,7 +41,7 @@ void VideoBatchWriter::cleanup_retention() {
auto cutoff = std::chrono::system_clock::now() - std::chrono::hours(retention_days_ * 24);
auto cutoff_t = std::chrono::system_clock::to_time_t(cutoff);
std::ostringstream cutoff_ss;
cutoff_ss << std::put_time(std::gmtime(&cutoff_t), "%Y-%m-%d");
cutoff_ss << std::put_time(std::localtime(&cutoff_t), "%Y-%m-%d");
std::string cutoff_str = cutoff_ss.str();
for (const auto& entry : fs::directory_iterator(output_dir_)) {
@@ -86,8 +86,9 @@ void VideoBatchWriter::note_ayam_detection() {
}
}
void VideoBatchWriter::on_batch_closed(int batch_number) {
void VideoBatchWriter::on_batch_closed(int batch_number, const std::string& counting_date) {
current_batch_ = batch_number;
current_date_ = counting_date;
if (state_ == CONFIRMED) {
state_ = STOPPING;
state_start_ = std::chrono::steady_clock::now();
@@ -175,7 +176,7 @@ void VideoBatchWriter::stop_and_move(int batch_number) {
std::string src = current_path_;
current_path_.clear();
std::string day_dir = output_dir_ + "/" + today_str();
std::string day_dir = output_dir_ + "/" + (current_date_.empty() ? today_str() : current_date_);
ensure_dir(day_dir);
std::ostringstream name;
@@ -205,7 +206,7 @@ void VideoBatchWriter::shutdown() {
current_path_.clear();
if (prev_state == CONFIRMED || prev_state == STOPPING) {
std::string day_dir = output_dir_ + "/" + today_str();
std::string day_dir = output_dir_ + "/" + (current_date_.empty() ? today_str() : current_date_);
ensure_dir(day_dir);
std::string dst = day_dir + "/" +
fs::path(src).filename().string();
+2 -1
View File
@@ -16,7 +16,7 @@ public:
void note_motion();
void note_ayam_detection();
void on_batch_closed(int batch_number);
void on_batch_closed(int batch_number, const std::string& counting_date);
void feed_frame(const cv::Mat& raw);
void cleanup_retention();
void shutdown();
@@ -41,4 +41,5 @@ private:
cv::VideoWriter writer_;
std::string current_path_;
int current_batch_ = 0;
std::string current_date_;
};