#pragma once #include #include #include #include #include #include #include "chicken_counter/config.hpp" namespace cc { struct CameraDiscoveryResult { std::unordered_map found; std::unordered_map skipped; }; inline std::string replace_glob_placeholder(const std::string& pattern, int num) { std::string s = pattern; std::string token = "{num}"; size_t pos = s.find(token); if (pos != std::string::npos) { s.replace(pos, token.size(), std::to_string(num)); } return s; } inline std::vector glob_filenames(const std::string& dir, const std::string& pattern) { namespace fs = std::filesystem; std::vector matches; std::string regex_str = "^"; for (char c : pattern) { if (c == '*') regex_str += ".*"; else if (c == '?') regex_str += "."; else if (c == '.' || c == '[' || c == ']' || c == '(' || c == ')' || c == '{' || c == '}') regex_str += std::string("\\") + c; else regex_str += c; } regex_str += "$"; std::regex re(regex_str); for (auto& entry : fs::directory_iterator(dir)) { if (!entry.is_regular_file()) continue; std::string fname = entry.path().filename().string(); if (std::regex_match(fname, re)) matches.push_back(entry.path().string()); } std::sort(matches.begin(), matches.end()); return matches; } inline CameraDiscoveryResult discover_camera_videos( const std::string& day_dir, const BatchSettings& settings) { namespace fs = std::filesystem; if (!fs::is_directory(day_dir)) throw std::runtime_error("Daily input folder does not exist: " + day_dir); CameraDiscoveryResult result; int total = static_cast(settings.cameras.size()); using pair_t = std::pair; std::vector sorted_cams(settings.cameras.begin(), settings.cameras.end()); std::sort(sorted_cams.begin(), sorted_cams.end(), [](const pair_t& a, const pair_t& b) { return a.second.camera_num < b.second.camera_num; }); for (const auto& [camera_id, preset] : sorted_cams) { std::string pattern = replace_glob_placeholder(settings.batch.camera_glob, preset.camera_num); auto matches = glob_filenames(day_dir, pattern); if (matches.empty()) { result.skipped[camera_id] = "video_not_found"; continue; } if (matches.size() > 1) { result.skipped[camera_id] = "multiple_matches"; continue; } result.found[camera_id] = matches[0]; } if (result.found.empty()) { std::string summary; for (const auto& [id, reason] : result.skipped) summary += id + " (" + reason + "), "; throw std::runtime_error("No camera videos found in " + day_dir + ". Skipped: " + summary); } int found_count = static_cast(result.found.size()); if (!result.skipped.empty()) { std::string summary; for (const auto& [id, reason] : result.skipped) summary += id + " (" + reason + "), "; std::cerr << "[batch] discovered " << found_count << "/" << total << " cameras; skipped: " << summary << "\n"; } else { std::cerr << "[batch] discovered " << found_count << "/" << total << " cameras\n"; } return result; } } // namespace cc