Saltar a contenido

Guía de Base de Datos del SDK Fingerprinting para C++

Esta guía demuestra cómo integrar el SDK Video Fingerprinting para C++ con varios sistemas de bases de datos para almacenamiento y recuperación eficiente de huellas.

Descripción General

A diferencia del SDK .NET que proporciona integración integrada con MongoDB, el SDK C++ ofrece flexibilidad para integrarse con cualquier sistema de base de datos. Esta guía proporciona patrones de implementación y ejemplos para bases de datos comunes.

Consideraciones de Diseño de Base de Datos

Requisitos de Almacenamiento de Huellas

  • Datos Binarios: Las huellas son datos binarios (típicamente 10-100KB por video)
  • Indexación: Los metadatos de video deben indexarse para consultas rápidas
  • Escalabilidad: El diseño debe soportar millones de huellas
  • Rendimiento: Optimizar tanto para operaciones de escritura como de lectura

Esquema Recomendado

CREATE TABLE video_fingerprints (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    video_path TEXT NOT NULL,
    video_hash VARCHAR(64),
    fingerprint_type VARCHAR(10), -- 'search' or 'compare'
    fingerprint_data BLOB NOT NULL,
    duration_ms INTEGER,
    frame_count INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    metadata JSON,
    INDEX idx_video_path (video_path),
    INDEX idx_video_hash (video_hash),
    INDEX idx_created_at (created_at)
);

Implementación SQLite

SQLite es ideal para aplicaciones embebidas y despliegues en una sola máquina.

Integración Básica con SQLite

#include <sqlite3.h>
#include "VisioForge_VFP.h"
#include "VisioForge_VFP_Types.h"
#include <vector>
#include <string>

class SQLiteFingerprintDB {
private:
    sqlite3* db;

public:
    SQLiteFingerprintDB(const std::string& dbPath) {
        int rc = sqlite3_open(dbPath.c_str(), &db);
        if (rc != SQLITE_OK) {
            throw std::runtime_error("Cannot open database");
        }
        InitializeSchema();
    }

    ~SQLiteFingerprintDB() {
        if (db) {
            sqlite3_close(db);
        }
    }

    void InitializeSchema() {
        const char* sql = R"(
            CREATE TABLE IF NOT EXISTS fingerprints (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                video_path TEXT NOT NULL,
                fingerprint_type TEXT NOT NULL,
                fingerprint_data BLOB NOT NULL,
                duration_ms INTEGER,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
            CREATE INDEX IF NOT EXISTS idx_video_path ON fingerprints(video_path);
        )";

        char* errMsg = nullptr;
        int rc = sqlite3_exec(db, sql, nullptr, nullptr, &errMsg);
        if (rc != SQLITE_OK) {
            std::string error = errMsg;
            sqlite3_free(errMsg);
            throw std::runtime_error("SQL error: " + error);
        }
    }

    bool StoreFingerprint(const std::string& videoPath, 
                         const VFPFingerPrint& fingerprint,
                         const std::string& type = "search") {
        const char* sql = R"(
            INSERT INTO fingerprints (video_path, fingerprint_type, fingerprint_data, duration_ms)
            VALUES (?, ?, ?, ?)
        )";

        sqlite3_stmt* stmt;
        int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
        if (rc != SQLITE_OK) return false;

        sqlite3_bind_text(stmt, 1, videoPath.c_str(), -1, SQLITE_STATIC);
        sqlite3_bind_text(stmt, 2, type.c_str(), -1, SQLITE_STATIC);
        sqlite3_bind_blob(stmt, 3, fingerprint.Data, fingerprint.DataSize, SQLITE_STATIC);
        sqlite3_bind_int64(stmt, 4, fingerprint.Duration);  // Duration is INT64

        rc = sqlite3_step(stmt);
        sqlite3_finalize(stmt);

        return rc == SQLITE_DONE;
    }

    VFPFingerPrint* LoadFingerprint(const std::string& videoPath) {
        const char* sql = "SELECT fingerprint_data, duration_ms FROM fingerprints WHERE video_path = ?";

        sqlite3_stmt* stmt;
        int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
        if (rc != SQLITE_OK) return nullptr;

        sqlite3_bind_text(stmt, 1, videoPath.c_str(), -1, SQLITE_STATIC);

        VFPFingerPrint* result = nullptr;
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            result = new VFPFingerPrint();

            const void* data = sqlite3_column_blob(stmt, 0);
            int dataSize = sqlite3_column_bytes(stmt, 0);

            result->Data = new char[dataSize];  // VFPFingerPrint uses char*
            memcpy(result->Data, data, dataSize);
            result->DataSize = dataSize;
            result->Duration = sqlite3_column_int64(stmt, 1);  // Duration is INT64
        }

        sqlite3_finalize(stmt);
        return result;
    }

    std::vector<std::string> FindSimilarVideos(const VFPFingerPrint& queryFp, 
                                               int maxResults = 10) {
        std::vector<std::string> results;
        const char* sql = "SELECT video_path, fingerprint_data FROM fingerprints";

        sqlite3_stmt* stmt;
        if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
            return results;
        }

        std::vector<std::pair<std::string, int>> similarities;

        while (sqlite3_step(stmt) == SQLITE_ROW) {
            std::string path = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));

            const void* data = sqlite3_column_blob(stmt, 1);
            int dataSize = sqlite3_column_bytes(stmt, 1);

            // Compare fingerprints using actual SDK function
            double difference = VFPCompare_Compare(
                queryFp.Data, queryFp.DataSize,
                static_cast<const char*>(data), dataSize,
                1000  // maxDifference
            );

            if (difference < 1000) {  // Lower difference = more similar
                similarities.push_back({path, static_cast<int>(difference)});
            }
        }

        sqlite3_finalize(stmt);

        // Sort by difference (lower is better)
        std::sort(similarities.begin(), similarities.end(),
                 [](const auto& a, const auto& b) { return a.second < b.second; });

        // Return top results
        for (int i = 0; i < std::min(maxResults, (int)similarities.size()); i++) {
            results.push_back(similarities[i].first);
        }

        return results;
    }
};

Ejemplo de Uso

int main() {
    // Inicializar la base de datos
    SQLiteFingerprintDB db("fingerprints.db");

    // Establecer la licencia
    VFPSetLicenseKey(L"SU-CLAVE-DE-LICENCIA");

    // Generar y almacenar la huella
    // Generar la huella mediante la API de alto nivel
    VFPFingerprintSource src{};
    VFPFillSource(L"video.mp4", &src);

    VFPFingerPrint fingerprint{};
    VFPSearch_GetFingerprintForVideoFile(src, &fingerprint);

    db.StoreFingerprint("video.mp4", fingerprint);
    std::cout << "Huella almacenada correctamente" << std::endl;

    // Guardar también la huella en un archivo
    VFPFingerprintSave(&fingerprint, L"video.vfp");

    // Buscar videos similares
    auto similar = db.FindSimilarVideos(fingerprint, 5);
    for (const auto& path : similar) {
        std::cout << "Video similar: " << path << std::endl;
    }

    return 0;
}

Implementación PostgreSQL

Para despliegues más grandes que requieran acceso concurrente y características avanzadas.

Integración PostgreSQL con libpq

#include <libpq-fe.h>
#include "VisioForge_VFP.h"
#include "VisioForge_VFP_Types.h"

class PostgreSQLFingerprintDB {
private:
    PGconn* conn;

public:
    PostgreSQLFingerprintDB(const std::string& connStr) {
        conn = PQconnectdb(connStr.c_str());

        if (PQstatus(conn) != CONNECTION_OK) {
            std::string error = PQerrorMessage(conn);
            PQfinish(conn);
            throw std::runtime_error("Connection failed: " + error);
        }

        InitializeSchema();
    }

    ~PostgreSQLFingerprintDB() {
        if (conn) {
            PQfinish(conn);
        }
    }

    void InitializeSchema() {
        const char* sql = R"(
            CREATE TABLE IF NOT EXISTS fingerprints (
                id SERIAL PRIMARY KEY,
                video_path TEXT NOT NULL,
                video_hash VARCHAR(64),
                fingerprint_type VARCHAR(10),
                fingerprint_data BYTEA NOT NULL,
                duration_ms INTEGER,
                metadata JSONB,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
            CREATE INDEX IF NOT EXISTS idx_video_path ON fingerprints(video_path);
            CREATE INDEX IF NOT EXISTS idx_metadata ON fingerprints USING GIN(metadata);
        )";

        PGresult* res = PQexec(conn, sql);
        if (PQresultStatus(res) != PGRES_COMMAND_OK) {
            std::string error = PQresultErrorMessage(res);
            PQclear(res);
            throw std::runtime_error("Schema creation failed: " + error);
        }
        PQclear(res);
    }

    bool StoreFingerprint(const std::string& videoPath,
                         const VFPFingerPrint& fingerprint,
                         const std::string& metadata = "{}") {
        // Escape binary data for PostgreSQL
        size_t escapedLen;
        unsigned char* escapedData = PQescapeByteaConn(
            conn, 
            reinterpret_cast<const unsigned char*>(fingerprint.Data), 
            fingerprint.DataSize, 
            &escapedLen
        );

        std::string sql = "INSERT INTO fingerprints "
                         "(video_path, fingerprint_data, duration_ms, metadata) "
                         "VALUES ($1, $2, $3, $4::jsonb)";

        const char* paramValues[4];
        paramValues[0] = videoPath.c_str();
        paramValues[1] = reinterpret_cast<const char*>(escapedData);
        paramValues[2] = std::to_string(fingerprint.Duration).c_str();
        paramValues[3] = metadata.c_str();

        PGresult* res = PQexecParams(conn, sql.c_str(), 4, nullptr,
                                     paramValues, nullptr, nullptr, 0);

        bool success = (PQresultStatus(res) == PGRES_COMMAND_OK);

        PQclear(res);
        PQfreemem(escapedData);

        return success;
    }

    // Batch insert for better performance
    bool StoreFingerprintsBatch(const std::vector<std::pair<std::string, VFPFingerPrint>>& batch) {
        PQexec(conn, "BEGIN");

        for (const auto& [path, fp] : batch) {
            if (!StoreFingerprint(path, fp)) {
                PQexec(conn, "ROLLBACK");
                return false;
            }
        }

        PGresult* res = PQexec(conn, "COMMIT");
        bool success = (PQresultStatus(res) == PGRES_COMMAND_OK);
        PQclear(res);

        return success;
    }
};

Implementación Redis

Para caché de alto rendimiento y procesamiento en tiempo real.

#include <hiredis/hiredis.h>
#include "VisioForge_VFP.h"
#include "VisioForge_VFP_Types.h"

class RedisFingerprintCache {
private:
    redisContext* redis;

public:
    RedisFingerprintCache(const std::string& host = "127.0.0.1", int port = 6379) {
        redis = redisConnect(host.c_str(), port);
        if (redis == nullptr || redis->err) {
            throw std::runtime_error("Redis connection failed");
        }
    }

    ~RedisFingerprintCache() {
        if (redis) {
            redisFree(redis);
        }
    }

    bool CacheFingerprint(const std::string& key, 
                         const VFPFingerPrint& fingerprint,
                         int ttlSeconds = 3600) {
        redisReply* reply = (redisReply*)redisCommand(
            redis,
            "SETEX %s %d %b",
            key.c_str(),
            ttlSeconds,
            fingerprint.Data,
            (size_t)fingerprint.DataSize
        );

        bool success = (reply && reply->type == REDIS_REPLY_STATUS);
        if (reply) freeReplyObject(reply);

        return success;
    }

    VFPFingerPrint* GetCachedFingerprint(const std::string& key) {
        redisReply* reply = (redisReply*)redisCommand(redis, "GET %s", key.c_str());

        if (reply && reply->type == REDIS_REPLY_STRING) {
            VFPFingerPrint* fp = new VFPFingerPrint();
            fp->DataSize = reply->len;
            fp->Data = new char[reply->len];  // VFPFingerPrint uses char*
            memcpy(fp->Data, reply->str, reply->len);

            freeReplyObject(reply);
            return fp;
        }

        if (reply) freeReplyObject(reply);
        return nullptr;
    }
};

Optimización de Rendimiento

Pool de conexiones

class DatabaseConnectionPool {
private:
    std::queue<std::unique_ptr<SQLiteFingerprintDB>> connections;
    std::mutex poolMutex;
    std::condition_variable poolCV;
    std::string dbPath;
    size_t maxConnections;

public:
    DatabaseConnectionPool(const std::string& path, size_t maxConn = 10)
        : dbPath(path), maxConnections(maxConn) {
        // Crear previamente las conexiones
        for (size_t i = 0; i < maxConnections; i++) {
            connections.push(std::make_unique<SQLiteFingerprintDB>(dbPath));
        }
    }

    std::unique_ptr<SQLiteFingerprintDB> GetConnection() {
        std::unique_lock<std::mutex> lock(poolMutex);
        poolCV.wait(lock, [this] { return !connections.empty(); });

        auto conn = std::move(connections.front());
        connections.pop();
        return conn;
    }

    void ReturnConnection(std::unique_ptr<SQLiteFingerprintDB> conn) {
        std::lock_guard<std::mutex> lock(poolMutex);
        connections.push(std::move(conn));
        poolCV.notify_one();
    }
};

Batch Processing

class BatchFingerprintProcessor {
private:
    DatabaseConnectionPool& pool;
    std::vector<std::pair<std::string, VFPFingerPrint>> batch;
    std::mutex batchMutex;
    size_t batchSize;

public:
    BatchFingerprintProcessor(DatabaseConnectionPool& p, size_t size = 100)
        : pool(p), batchSize(size) {}

    void AddFingerprint(const std::string& path, const VFPFingerPrint& fp) {
        std::lock_guard<std::mutex> lock(batchMutex);
        batch.push_back({path, fp});

        if (batch.size() >= batchSize) {
            FlushBatch();
        }
    }

    void FlushBatch() {
        if (batch.empty()) return;

        auto conn = pool.GetConnection();

        // Iniciar la transacción para la inserción por lotes
        for (const auto& [path, fp] : batch) {
            conn->StoreFingerprint(path, fp);
        }

        pool.ReturnConnection(std::move(conn));
        batch.clear();
    }
};

Mejores Prácticas

1. Optimización de índices

  • Cree índices en los campos consultados con frecuencia (video_path, hash)
  • Use índices parciales para consultas filtradas
  • Considere índices compuestos para consultas complejas

2. Compresión de datos

// Ejemplo: guardar y cargar huellas usando funciones del SDK
void SaveFingerprintToDB(const std::string& path) {
    // Generar la huella mediante la API de alto nivel
    VFPFingerprintSource src{};
    VFPFillSource(std::wstring(path.begin(), path.end()).c_str(), &src);

    VFPFingerPrint fp{};
    VFPSearch_GetFingerprintForVideoFile(src, &fp);

    // Guardar en formato de archivo
    VFPFingerprintSave(&fp, L"temp.vfp");

    // O guardar en formato heredado para compatibilidad
    VFPFingerprintSaveLegacy(&fp, L"temp_legacy.vfp");

    // Almacenar en la base de datos...
}

3. Estrategia de sharding

Para bases de datos muy grandes, implemente sharding:

class ShardedFingerprintDB {
private:
    std::vector<std::unique_ptr<SQLiteFingerprintDB>> shards;

    int GetShardIndex(const std::string& key) {
        std::hash<std::string> hasher;
        return hasher(key) % shards.size();
    }

public:
    void StoreFingerprint(const std::string& path, const VFPFingerPrint& fp) {
        int shard = GetShardIndex(path);
        shards[shard]->StoreFingerprint(path, fp);
    }
};

Comparación con la Integración MongoDB de .NET

Característica Implementación personalizada en C++ Integración MongoDB en .NET
Complejidad de configuración Mayor (esquema manual) Menor (automática)
Flexibilidad Control total Específica de MongoDB
Rendimiento Se puede optimizar Bueno desde el inicio
Opciones de base de datos Cualquier base de datos Solo MongoDB
Código requerido Más código repetitivo Mínimo
Mantenimiento Actualizaciones manuales Actualizaciones del SDK

Próximos Pasos

Recursos Adicionales