#pragma once #include "types.hpp" #include "config.hpp" #include #include // ── SceneTrackerFunc ────────────────────────────────────────────────────────── // KPN node: maintains an extinction-timer state machine per identified actor. // // On each MatchedSceneFrame: // 1. Update last_seen for every matched known actor. // 2. Expire actors whose last_seen is older than extinction_sec. // 3. Emit SceneAnnotation with all currently active (non-expired) actors, // including their most recently seen bbox and best similarity score. // // Unknown faces (actor_idx == -1) are passed through per-frame but are NOT // tracked across frames — each frame reports its own unknowns independently. struct SceneTrackerFunc { static constexpr std::string_view label() { return "scene_tracker"; } explicit SceneTrackerFunc(const Config& cfg) : extinction_sec_(cfg.extinction_sec) { std::cerr << "[scene_tracker] extinction_sec=" << extinction_sec_ << "\n"; } // Runtime setter for pipeline reuse across a sweep. Also clears the active-actor // state so a re-run starts clean (no carry-over from the previous config's film). void set_extinction_sec(double s) { extinction_sec_ = s; active_.clear(); } SceneAnnotation operator()(MatchedSceneFrame mf) { if (mf.source.eof) return {0.0, {}, /*eof=*/true}; double now = mf.source.timestamp_sec; // Update known actors for (const auto& ia : mf.actors) { if (ia.actor_idx < 0) continue; // skip unknowns auto& slot = active_[ia.actor_idx]; slot.last_seen = now; slot.last_bbox = ia.bbox; slot.last_crop = ia.crop; slot.name = ia.name; slot.imdb_id = ia.imdb_id; slot.tmdb_id = ia.tmdb_id; slot.jellyfin_id = ia.jellyfin_id; // Keep the best (highest) similarity seen in this window if (ia.similarity > slot.best_similarity) slot.best_similarity = ia.similarity; } // Expire stale actors for (auto it = active_.begin(); it != active_.end(); ) { if ((now - it->second.last_seen) > extinction_sec_) it = active_.erase(it); else ++it; } // Build annotation: active known actors std::vector visible; visible.reserve(active_.size() + mf.actors.size()); for (const auto& [actor_idx, slot] : active_) { IdentifiedActor ia; ia.actor_idx = actor_idx; ia.name = slot.name; ia.imdb_id = slot.imdb_id; ia.tmdb_id = slot.tmdb_id; ia.jellyfin_id = slot.jellyfin_id; ia.similarity = slot.best_similarity; ia.bbox = slot.last_bbox; ia.crop = slot.last_crop; visible.push_back(ia); } // Append per-frame unknowns (actor_idx == -1) directly for (const auto& ia : mf.actors) { if (ia.actor_idx < 0) visible.push_back(ia); } return {now, std::move(visible)}; } private: struct Slot { double last_seen{0.0}; float best_similarity{0.f}; cv::Rect2f last_bbox; cv::Mat last_crop; std::string name; std::string imdb_id; std::string tmdb_id; std::string jellyfin_id; }; double extinction_sec_; std::map active_; // actor_idx → state };