#include "record_manager.hpp" #include #include #include #include #include "logger.hpp" namespace fs = std::filesystem; // // 构造函数:自动加载 SRS 配置 // RecordManager::RecordManager(const std::string& srs_record_cfg_path) : srs_record_cfg_path_(srs_record_cfg_path) { LOG_INFO("[RecordManager] Loading SRS config: " + srs_record_cfg_path_); bool ok = loadSrsConfig(); if (!ok) { LOG_ERROR("[RecordManager] Failed to load SRS config."); } else { LOG_INFO("[RecordManager] SRS config loaded. record_dir=" + record_dir_ + ", dvr_duration=" + std::to_string(dvr_duration_sec_) + "s"); } } // // 解析 SRS DVR 配置,提取 record_dir_ 与 dvr_duration_sec_ // bool RecordManager::loadSrsConfig() { std::ifstream ifs(srs_record_cfg_path_); if (!ifs.is_open()) { LOG_ERROR("[RecordManager] Cannot open SRS config file: " + srs_record_cfg_path_); return false; } std::string line; std::string dvr_path; while (std::getline(ifs, line)) { line.erase(0, line.find_first_not_of(" \t")); if (line.empty() || line[0] == '#') continue; // 解析 dvr_path if (line.find("dvr_path") != std::string::npos) { auto pos = line.find('/'); auto semicolon = line.find(';'); if (pos != std::string::npos && semicolon != std::string::npos) dvr_path = line.substr(pos, semicolon - pos); } // 解析 dvr_duration else if (line.find("dvr_duration") != std::string::npos) { int sec = 0; if (sscanf(line.c_str(), "dvr_duration %d", &sec) == 1) dvr_duration_sec_ = sec; } } if (dvr_path.empty()) { LOG_ERROR("[RecordManager] dvr_path not found in config."); return false; } // /sata/record/[stream]/[2006]-[01]-[02]/... auto pos = dvr_path.find("[stream]"); if (pos != std::string::npos) record_dir_ = dvr_path.substr(0, pos); else { LOG_ERROR("[RecordManager] Cannot extract record_dir from dvr_path: " + dvr_path); return false; } return true; } // // 扫描所有文件 // void RecordManager::scanAll() { index_.clear(); if (!fs::exists(record_dir_)) { LOG_WARN("[RecordManager] record_dir not exist: " + record_dir_); return; } for (auto const& entry : fs::recursive_directory_iterator(record_dir_)) { if (!entry.is_regular_file()) continue; auto p = entry.path(); if (p.extension() != ".mp4") continue; auto info = parseFile(p); if (info.start_ms <= 0) continue; index_[info.stream].push_back(info); } for (auto& kv : index_) { sortStream(kv.first); } LOG_INFO("[RecordManager] scanAll completed. streams=" + std::to_string(index_.size())); } // // 排序函数:按 start_ms 升序 // void RecordManager::sortStream(const std::string& stream) { auto& files = index_[stream]; std::sort(files.begin(), files.end(), [](const RecordFileInfo& a, const RecordFileInfo& b) { return a.start_ms < b.start_ms; }); } // // 解析路径得到文件时间戳 // RecordFileInfo RecordManager::parseFile(const fs::path& p) { RecordFileInfo info; info.path = p.string(); try { auto filename = p.filename().string(); // 10-23-17.mp4 auto date_dir = p.parent_path().parent_path().filename().string(); // yyyy-mm-dd auto stream_dir = p.parent_path().parent_path().parent_path().filename().string(); info.stream = stream_dir; int y = 0, mon = 0, d = 0, h = 0, m = 0, s = 0; sscanf(date_dir.c_str(), "%d-%d-%d", &y, &mon, &d); sscanf(filename.c_str(), "%d-%d-%d", &h, &m, &s); std::tm tm{}; tm.tm_year = y - 1900; tm.tm_mon = mon - 1; tm.tm_mday = d; tm.tm_hour = h; tm.tm_min = m; tm.tm_sec = s; int64_t start = std::mktime(&tm) * 1000LL; info.start_ms = start; // 读取 mp4 时长 MP4FileHandle hFile = MP4Read(info.path.c_str()); if (hFile != MP4_INVALID_FILE_HANDLE) { double durSec = MP4GetDuration(hFile) / (double)MP4GetTimeScale(hFile); MP4Close(hFile); info.end_ms = start + (int64_t)(durSec * 1000.0); } else { info.end_ms = start + 60000; // fallback } } catch (...) { info.start_ms = -1; info.end_ms = -1; } return info; } // // 查询录像段 // std::vector RecordManager::querySegments(const std::string& stream, int64_t start_ms, int64_t end_ms) { std::vector result; if (!index_.count(stream)) return result; const auto& files = index_[stream]; std::vector hits; for (const auto& f : files) { bool overlap = !(f.end_ms <= start_ms || f.start_ms >= end_ms); if (overlap) hits.push_back(f); } if (hits.empty()) return result; std::sort(hits.begin(), hits.end(), [](auto& a, auto& b) { return a.start_ms < b.start_ms; }); int idx = 1; RecordSegment seg{}; seg.index = idx; seg.start_ms = hits[0].start_ms; seg.end_ms = hits[0].end_ms; seg.files.push_back(hits[0]); for (size_t i = 1; i < hits.size(); ++i) { auto& prev = hits[i - 1]; auto& cur = hits[i]; bool continuous = (cur.start_ms - prev.end_ms) <= 2000; if (continuous) { seg.files.push_back(cur); seg.end_ms = cur.end_ms; } else { seg.segment_id = stream + "_" + std::to_string(seg.start_ms) + "_" + std::to_string(seg.end_ms); result.push_back(seg); idx++; seg = RecordSegment{}; seg.index = idx; seg.start_ms = cur.start_ms; seg.end_ms = cur.end_ms; seg.files.push_back(cur); } } seg.segment_id = stream + "_" + std::to_string(seg.start_ms) + "_" + std::to_string(seg.end_ms); result.push_back(seg); return result; } // // 根据 segmentId 获取 segment // RecordSegment RecordManager::getSegment(const std::string& segmentId) { auto pos1 = segmentId.find('_'); auto pos2 = segmentId.find('_', pos1 + 1); if (pos1 == std::string::npos || pos2 == std::string::npos) return {}; std::string stream = segmentId.substr(0, pos1); int64_t start_ms = std::stoll(segmentId.substr(pos1 + 1, pos2 - pos1 - 1)); int64_t end_ms = std::stoll(segmentId.substr(pos2 + 1)); auto segs = querySegments(stream, start_ms, end_ms); for (auto& s : segs) { if (s.start_ms == start_ms && s.end_ms == end_ms) return s; } return {}; } int64_t RecordManager::toMsTimestamp(const std::string& s) { std::tm tm{}; char* ret = strptime(s.c_str(), "%Y-%m-%d %H:%M:%S", &tm); if (!ret) return -1; time_t t = mktime(&tm); return (int64_t)t * 1000; } std::string RecordManager::toReadable(int64_t ms) { time_t t = ms / 1000; std::tm* tm = localtime(&t); char buf[32]; strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); return buf; }