sweeper_video/src/record_manager.cpp

304 lines
7.8 KiB
C++
Raw Normal View History

2025-11-14 09:42:17 +08:00
#include "record_manager.hpp"
#include <algorithm>
#include <ctime>
2025-11-14 14:21:06 +08:00
#include <fstream>
2025-11-14 09:42:17 +08:00
#include <iostream>
namespace fs = std::filesystem;
2025-11-14 14:13:14 +08:00
//
// 构造函数:自动加载 SRS 配置
//
RecordManager::RecordManager(const std::string& srs_record_cfg_path) : srs_record_cfg_path_(srs_record_cfg_path)
{
std::cout << "[RecordManager] Loading SRS config: " << srs_record_cfg_path_ << "\n";
bool ok = loadSrsConfig();
if (!ok)
{
std::cout << "[RecordManager] ERROR loading SRS config\n";
}
else
{
std::cout << "[RecordManager] SRS config loaded successfully.\n";
std::cout << " record_dir_ = " << record_dir_ << "\n";
std::cout << " dvr_duration_sec = " << dvr_duration_sec_ << "\n";
}
}
//
// 解析 SRS DVR 配置,提取 record_dir_ 与 dvr_duration_sec_
//
bool RecordManager::loadSrsConfig()
{
std::ifstream ifs(srs_record_cfg_path_);
if (!ifs.is_open())
{
std::cerr << "[RecordManager] Failed to open SRS config: " << srs_record_cfg_path_ << "\n";
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;
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);
}
}
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())
{
// 例子:/sata/record/[stream]/[2006]-[01]-[02]/...
// → record_dir_ = /sata/record/
auto pos = dvr_path.find("[stream]");
if (pos != std::string::npos)
{
record_dir_ = dvr_path.substr(0, pos);
}
}
if (record_dir_.empty())
{
std::cerr << "[RecordManager] ERROR: cannot parse dvr_path.\n";
return false;
}
return true;
}
2025-11-14 09:42:17 +08:00
//
// 扫描所有文件
//
void RecordManager::scanAll()
{
index_.clear();
2025-11-14 14:21:06 +08:00
if (!fs::exists(record_dir_)) return;
2025-11-14 09:42:17 +08:00
2025-11-14 14:21:06 +08:00
for (auto const& entry : fs::recursive_directory_iterator(record_dir_))
2025-11-14 09:42:17 +08:00
{
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);
}
// 各 stream 排序
for (auto& kv : index_)
{
sortStream(kv.first);
}
}
//
// 排序函数:按 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; });
}
//
// 解析路径得到文件时间戳
//
// 格式:
/// sata/record/AHD1_main/2025-11-13/10/10-23-17.mp4
//
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(); // 2025-11-13
auto stream_dir = p.parent_path().parent_path().parent_path().filename().string();
info.stream = stream_dir;
2025-11-14 11:09:01 +08:00
int y = 0, mon = 0, d = 0, h = 0, m = 0, s = 0;
2025-11-14 09:42:17 +08:00
sscanf(date_dir.c_str(), "%d-%d-%d", &y, &mon, &d);
sscanf(filename.c_str(), "%d-%d-%d", &h, &m, &s);
2025-11-14 11:09:01 +08:00
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;
// ====== 加入 mp4v2 读取真实 duration ======
MP4FileHandle hFile = MP4Read(info.path.c_str());
if (hFile != MP4_INVALID_FILE_HANDLE)
{
double durSec = MP4GetDuration(hFile) / (double)MP4GetTimeScale(hFile);
MP4Close(hFile);
int64_t dur_ms = (int64_t)(durSec * 1000.0);
info.end_ms = start + dur_ms;
}
else
{
// 出错 fallback
info.end_ms = start + 60000;
}
2025-11-14 09:42:17 +08:00
}
catch (...)
{
info.start_ms = -1;
info.end_ms = -1;
}
return info;
}
//
// 查询某段时间的录像段
//
std::vector<RecordSegment> RecordManager::querySegments(const std::string& stream, int64_t start_ms, int64_t end_ms)
{
std::vector<RecordSegment> result;
if (!index_.count(stream)) return result;
const auto& files = index_[stream];
std::vector<RecordFileInfo> 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.files.push_back(hits[0]);
seg.end_ms = hits[0].end_ms;
seg.segment_id = hits[0].stream + "_" + std::to_string(seg.start_ms) + "_" + std::to_string(seg.end_ms);
for (size_t i = 1; i < hits.size(); ++i)
{
auto& prev = hits[i - 1];
auto& cur = hits[i];
2025-11-14 11:09:01 +08:00
bool continuous = (cur.start_ms - prev.end_ms) <= 2000;
2025-11-14 09:42:17 +08:00
if (continuous)
{
seg.files.push_back(cur);
seg.end_ms = cur.end_ms;
}
else
{
// 关闭上一段
seg.segment_id =
seg.files[0].stream + "_" + std::to_string(seg.start_ms) + "_" + std::to_string(seg.end_ms);
result.push_back(seg);
// 开新段
idx++;
seg = RecordSegment{};
seg.index = idx;
seg.files.push_back(cur);
seg.start_ms = cur.start_ms;
seg.end_ms = cur.end_ms;
}
}
// 最后一段补进去
seg.segment_id = seg.files[0].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)
{
// segmentId 格式:
// AHD1_main_1731465600000_1731467400000
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 {};
}
2025-11-14 09:45:39 +08:00
2025-11-14 09:55:53 +08:00
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); // local time (UTC+8)
return static_cast<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;
}