> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt
> Use this file to discover all available pages before exploring further.

# Sincronización de video multicámara con ISOBMFF

> Extrae las marcas de tiempo de milisegundos de Rhombus embebidas en contenedores de video ISOBMFF para alinear fotogramas con precisión entre varias cámaras en análisis forense.

<Note>
  Esta página fue traducida automáticamente. Si encuentra errores o tiene sugerencias, [contáctenos](mailto:support@rhombus.com).
</Note>

Al trabajar con segmentos de video—especialmente en sistemas que dependen de cámaras distribuidas, almacenamiento en la nube y múltiples flujos de datos—la **sincronización temporal** se convierte en uno de los retos más críticos. Sin información de temporización precisa, alinear eventos entre varios flujos (por ejemplo, video, audio y datos de sensores) se vuelve propenso a errores.

Rhombus ha implementado una estrategia de **embebido de marcas de tiempo personalizadas** que ofrece precisión de milisegundos dentro de los segmentos de video `.mp4`/`.m4v`, yendo más allá de los campos de temporización gruesos que se encuentran tradicionalmente en los contenedores ISOBMFF (ISO Base Media File Format) estándar.

<Info>
  Esta guía explica qué significa este enfoque, por qué es importante y cómo los desarrolladores pueden recuperar y usar esta marca de tiempo para crear aplicaciones alineadas temporalmente.
</Info>

## Comprender ISOBMFF y el átomo "free"

El estándar **ISOBMFF** (ISO/IEC 14496-12) es el formato de contenedor que subyace a `.mp4`, `.m4v`, `.mov` y muchos formatos de segmentos de streaming como `.fMP4`. Su estructura se basa en **cajas (átomos)**—unidades de datos autocontenidas identificadas por un código de 4 caracteres (por ejemplo, `moov`, `mdat`, `free`).

<Warning>
  Las **marcas de tiempo estándar** en ISOBMFF (por ejemplo, `creation_time` en la caja `mvhd`) normalmente tienen **resolución de segundos**. Esto es suficiente para algunos flujos de trabajo multimedia, pero insuficiente para la **sincronización multicámara** o la **correlación de eventos de alta velocidad**.
</Warning>

Rhombus resuelve esto **embebiendo una marca de tiempo con precisión de milisegundos** dentro de un átomo `free`. Este es un método no estándar, pero totalmente compatible con ISOBMFF.

## La marca de tiempo personalizada de Rhombus

Cuando Rhombus segmenta el video (y si se obtienen segmentos reales, no un flujo de transporte en vivo), se escribe una **firma de metadatos personalizada** en la caja `free`:

<Steps>
  <Step title="Tipo de caja: free">
    En ISOBMFF, `free` normalmente es una caja de relleno que contiene espacio sin usar. Rhombus la reutiliza para transportar metadatos de marca de tiempo.
  </Step>

  <Step title="Firma: rhom">
    Los primeros 4 bytes son la cadena ASCII `rhom`—que la identifica como **datos específicos de Rhombus**.
  </Step>

  <Step title="Marca de tiempo en milisegundos">
    Los siguientes **8 bytes** son un **entero de 64 bits** que representa la hora de inicio del contenido de video, medida en **milisegundos desde la época Unix** (UTC).
  </Step>
</Steps>

**Ejemplo de disposición binaria:**

```text theme={null}
[ free box length ][ 'free' ][ 'rhom' ][ 8-byte timestamp (ms since epoch) ]
```

## Por qué esto es importante para los desarrolladores

Esta decisión de diseño desbloquea capacidades de **sincronización precisa** en múltiples casos de uso:

<CardGroup cols={2}>
  <Card title="Alineación multicámara" icon="video">
    Alinea las transmisiones de video de diferentes cámaras con una precisión de 1 ms para un monitoreo coordinado
  </Card>

  <Card title="Fusión de sensores" icon="microchip">
    Combina video con datos de sensores IoT (eventos de control de acceso, lecturas ambientales)
  </Card>

  <Card title="Precisión forense" icon="magnifying-glass">
    Reconstruye eventos hasta intervalos de subsegundos en investigaciones
  </Card>

  <Card title="Menor deriva" icon="clock">
    Evita los errores que se acumulan al depender únicamente de los relojes del sistema cliente o de la sincronización NTP
  </Card>
</CardGroup>

<Tip>
  Para los socios del ecosistema y de integración, esto hace que las transmisiones de video de Rhombus sean **altamente interoperables** con análisis de terceros, canalizaciones de IA/ML y sistemas de monitoreo en tiempo real.
</Tip>

## Recuperar la marca de tiempo

### Analizar archivos ISOBMFF

Puedes usar bibliotecas de código abierto para leer la caja `free` de un segmento `.mp4`/`.m4v` y comprobar la firma `rhom`.

<CardGroup cols={2}>
  <Card title="Analizador ISOBMFF (C++/Swift)" icon="github" href="https://github.com/DigiDNA/ISOBMFF">
    Biblioteca profesional para analizar el ISO Base Media File Format
  </Card>

  <Card title="Visor interactivo MP4Box.js" icon="browser" href="https://gpac.github.io/mp4box.js/test/filereader.html">
    Herramienta en línea para inspeccionar visualmente las estructuras de cajas MP4
  </Card>
</CardGroup>

## Ejemplos de implementación

<Tabs>
  <Tab title="Python">
    ```python Python Implementation theme={null}
    import struct
    import datetime

    def extract_rhombus_timestamp(file_path):
        """
        Extrae la marca de tiempo con precisión de milisegundos de un segmento de video de Rhombus.

        Args:
            file_path: Ruta al archivo .mp4 o .m4v

        Returns:
            tuple: (timestamp_ms, objeto datetime) o (None, None) si no se encuentra
        """
        with open(file_path, "rb") as f:
            data = f.read()

        # Buscar la caja 'free'
        idx = data.find(b'free')
        if idx == -1:
            return None, None

        # Buscar la firma 'rhom' después de 'free'
        rhom_idx = data.find(b'rhom', idx)
        if rhom_idx == -1:
            return None, None

        # Leer los siguientes 8 bytes después de 'rhom' (entero de 64 bits big-endian)
        timestamp_bytes = data[rhom_idx + 4 : rhom_idx + 12]
        timestamp_ms = int.from_bytes(timestamp_bytes, byteorder="big")

        # Convertir a hora UTC legible para humanos
        timestamp_dt = datetime.datetime.fromtimestamp(timestamp_ms / 1000.0, tz=datetime.timezone.utc)

        return timestamp_ms, timestamp_dt

    # Ejemplo de uso:
    timestamp_ms, timestamp_dt = extract_rhombus_timestamp("video_segment.mp4")

    if timestamp_ms:
        print(f"Timestamp (ms since epoch): {timestamp_ms}")
        print(f"UTC Time: {timestamp_dt}")
        print(f"ISO Format: {timestamp_dt.isoformat()}")
    else:
        print("Rhombus timestamp not found in file")

    # Salida de ejemplo:
    # Timestamp (ms since epoch): 1722945678123
    # UTC Time: 2024-08-06 15:21:18.123000
    # ISO Format: 2024-08-06T15:21:18.123000
    ```

    ### Uso avanzado en Python

    ```python Multi-Segment Processing theme={null}
    import os
    import glob
    from datetime import datetime

    class RhombusTimestampExtractor:
        """Extrae y administra marcas de tiempo de múltiples segmentos de video."""

        def __init__(self):
            self.timestamps = []

        def process_directory(self, directory_path, pattern="*.mp4"):
            """Procesa todos los archivos de video en un directorio."""
            files = glob.glob(os.path.join(directory_path, pattern))

            for file_path in sorted(files):
                timestamp_ms, timestamp_dt = extract_rhombus_timestamp(file_path)

                if timestamp_ms:
                    self.timestamps.append({
                        'file': os.path.basename(file_path),
                        'timestamp_ms': timestamp_ms,
                        'datetime': timestamp_dt
                    })

        def get_timeline(self):
            """Obtiene una lista de segmentos ordenada cronológicamente."""
            return sorted(self.timestamps, key=lambda x: x['timestamp_ms'])

        def find_segment_at_time(self, target_datetime):
            """Encuentra el segmento de video que contiene una hora específica."""
            target_ms = int(target_datetime.timestamp() * 1000)

            for segment in self.get_timeline():
                if segment['timestamp_ms'] <= target_ms:
                    closest = segment
                else:
                    break

            return closest

    # Ejemplo de uso
    extractor = RhombusTimestampExtractor()
    extractor.process_directory("/path/to/video/segments")

    # Encontrar el segmento en una hora específica
    target = datetime(2024, 8, 6, 15, 21, 0)
    segment = extractor.find_segment_at_time(target)
    print(f"Segment at {target}: {segment['file']}")
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript JavaScript Implementation theme={null}
    /**
     * Extrae la marca de tiempo de Rhombus de un ArrayBuffer de segmento de video
     * @param {ArrayBuffer} arrayBuffer - El archivo de video como ArrayBuffer
     * @returns {Object|null} Objeto con timestampMs y utcTime, o null si no se encuentra
     */
    function extractRhombusTimestamp(arrayBuffer) {
        const data = new Uint8Array(arrayBuffer);

        // Buscar la caja 'free'
        const freePattern = new Uint8Array([0x66, 0x72, 0x65, 0x65]); // 'free'
        let freeIndex = -1;

        for (let i = 0; i <= data.length - 4; i++) {
            if (data.subarray(i, i + 4).every((val, idx) => val === freePattern[idx])) {
                freeIndex = i;
                break;
            }
        }

        if (freeIndex === -1) return null;

        // Buscar la firma 'rhom'
        const rhomPattern = new Uint8Array([0x72, 0x68, 0x6f, 0x6d]); // 'rhom'
        let rhomIndex = -1;

        for (let i = freeIndex; i <= data.length - 4; i++) {
            if (data.subarray(i, i + 4).every((val, idx) => val === rhomPattern[idx])) {
                rhomIndex = i;
                break;
            }
        }

        if (rhomIndex === -1) return null;

        // Extraer la marca de tiempo de 8 bytes (big-endian)
        const timestampBytes = data.subarray(rhomIndex + 4, rhomIndex + 12);
        let timestamp = 0;

        for (let i = 0; i < 8; i++) {
            timestamp = timestamp * 256 + timestampBytes[i];
        }

        return {
            timestampMs: timestamp,
            utcTime: new Date(timestamp),
            isoString: new Date(timestamp).toISOString()
        };
    }

    // Ejemplo de uso con la File API
    async function processVideoFile(file) {
        const arrayBuffer = await file.arrayBuffer();
        const result = extractRhombusTimestamp(arrayBuffer);

        if (result) {
            console.log('Timestamp (ms):', result.timestampMs);
            console.log('UTC Time:', result.utcTime);
            console.log('ISO String:', result.isoString);
        } else {
            console.log('Rhombus timestamp not found');
        }
    }

    // Uso con un elemento input
    document.querySelector('#fileInput').addEventListener('change', async (e) => {
        const file = e.target.files[0];
        if (file) {
            await processVideoFile(file);
        }
    });
    ```

    ### Uso avanzado en JavaScript

    ```javascript Multi-Camera Synchronization theme={null}
    class RhombusVideoSync {
        constructor() {
            this.cameras = new Map();
        }

        /**
         * Agrega el segmento de video de una cámara con su marca de tiempo
         */
        async addCameraSegment(cameraId, videoFile) {
            const arrayBuffer = await videoFile.arrayBuffer();
            const timestamp = extractRhombusTimestamp(arrayBuffer);

            if (timestamp) {
                if (!this.cameras.has(cameraId)) {
                    this.cameras.set(cameraId, []);
                }

                this.cameras.get(cameraId).push({
                    timestamp: timestamp.timestampMs,
                    datetime: timestamp.utcTime,
                    file: videoFile
                });
            }
        }

        /**
         * Encuentra segmentos de todas las cámaras en una hora específica
         */
        findSegmentsAtTime(targetTime) {
            const targetMs = targetTime.getTime();
            const results = {};

            for (const [cameraId, segments] of this.cameras.entries()) {
                // Encontrar el segmento más cercano antes o en la hora objetivo
                const sorted = segments.sort((a, b) => a.timestamp - b.timestamp);
                const segment = sorted.find(s => s.timestamp <= targetMs);

                if (segment) {
                    results[cameraId] = {
                        timestamp: segment.timestamp,
                        datetime: segment.datetime,
                        offset: targetMs - segment.timestamp
                    };
                }
            }

            return results;
        }

        /**
         * Obtiene el rango de tiempo cubierto por todas las cámaras
         */
        getCommonTimeRange() {
            let earliestEnd = Infinity;
            let latestStart = 0;

            for (const segments of this.cameras.values()) {
                if (segments.length === 0) continue;

                const start = Math.min(...segments.map(s => s.timestamp));
                const end = Math.max(...segments.map(s => s.timestamp));

                latestStart = Math.max(latestStart, start);
                earliestEnd = Math.min(earliestEnd, end);
            }

            return {
                start: new Date(latestStart),
                end: new Date(earliestEnd),
                duration: earliestEnd - latestStart
            };
        }
    }

    // Uso
    const sync = new RhombusVideoSync();
    await sync.addCameraSegment('camera1', file1);
    await sync.addCameraSegment('camera2', file2);
    await sync.addCameraSegment('camera3', file3);

    // Encontrar qué estaba sucediendo en todas las cámaras en una hora específica
    const target = new Date('2024-08-06T15:21:18.123Z');
    const segments = sync.findSegmentsAtTime(target);
    console.log('Synchronized segments:', segments);
    ```
  </Tab>

  <Tab title="C++">
    ```cpp C++ Implementation theme={null}
    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <cstdint>
    #include <chrono>
    #include <optional>
    #include <algorithm>

    struct RhombusTimestamp {
        uint64_t timestampMs;
        std::chrono::system_clock::time_point utcTime;

        // Convertir a cadena ISO 8601
        std::string toISO8601() const {
            auto timeT = std::chrono::system_clock::to_time_t(utcTime);
            auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                utcTime.time_since_epoch()
            ).count() % 1000;

            char buffer[32];
            std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S",
                         std::gmtime(&timeT));

            return std::string(buffer) + "." +
                   std::to_string(ms) + "Z";
        }
    };

    /**
     * Extrae la marca de tiempo de Rhombus de un archivo de video
     */
    std::optional<RhombusTimestamp> extractRhombusTimestamp(
        const std::string& filePath
    ) {
        std::ifstream file(filePath, std::ios::binary);
        if (!file) {
            return std::nullopt;
        }

        // Leer el archivo completo en memoria
        std::vector<uint8_t> data(
            (std::istreambuf_iterator<char>(file)),
            std::istreambuf_iterator<char>()
        );

        // Buscar la caja 'free'
        const std::vector<uint8_t> freePattern = {0x66, 0x72, 0x65, 0x65};
        auto freeIt = std::search(
            data.begin(), data.end(),
            freePattern.begin(), freePattern.end()
        );

        if (freeIt == data.end()) {
            return std::nullopt;
        }

        // Buscar la firma 'rhom'
        const std::vector<uint8_t> rhomPattern = {0x72, 0x68, 0x6f, 0x6d};
        auto rhomIt = std::search(
            freeIt, data.end(),
            rhomPattern.begin(), rhomPattern.end()
        );

        if (rhomIt == data.end()) {
            return std::nullopt;
        }

        // Extraer la marca de tiempo de 8 bytes (big-endian)
        uint64_t timestamp = 0;
        for (int i = 0; i < 8; i++) {
            timestamp = (timestamp << 8) | *(rhomIt + 4 + i);
        }

        // Convertir a time_point
        auto utcTime = std::chrono::system_clock::from_time_t(
            timestamp / 1000
        );
        utcTime += std::chrono::milliseconds(timestamp % 1000);

        return RhombusTimestamp{timestamp, utcTime};
    }

    // Ejemplo de uso
    int main() {
        auto result = extractRhombusTimestamp("video_segment.mp4");

        if (result) {
            std::cout << "Timestamp (ms): " << result->timestampMs << std::endl;
            std::cout << "ISO 8601: " << result->toISO8601() << std::endl;
        } else {
            std::cout << "Rhombus timestamp not found" << std::endl;
        }

        return 0;
    }
    ```

    ### Uso avanzado en C++

    ```cpp Multi-Segment Timeline theme={null}
    #include <filesystem>
    #include <map>

    class RhombusTimeline {
    private:
        struct Segment {
            std::filesystem::path filePath;
            RhombusTimestamp timestamp;
        };

        std::vector<Segment> segments;

    public:
        /**
         * Carga todos los segmentos de video de un directorio
         */
        void loadDirectory(const std::filesystem::path& directory) {
            for (const auto& entry :
                 std::filesystem::directory_iterator(directory)) {

                if (entry.path().extension() == ".mp4" ||
                    entry.path().extension() == ".m4v") {

                    auto timestamp = extractRhombusTimestamp(
                        entry.path().string()
                    );

                    if (timestamp) {
                        segments.push_back({entry.path(), *timestamp});
                    }
                }
            }

            // Ordenar por marca de tiempo
            std::sort(segments.begin(), segments.end(),
                [](const Segment& a, const Segment& b) {
                    return a.timestamp.timestampMs < b.timestamp.timestampMs;
                }
            );
        }

        /**
         * Encuentra el segmento que contiene una hora específica
         */
        std::optional<Segment> findSegmentAtTime(
            const std::chrono::system_clock::time_point& targetTime
        ) const {
            auto targetMs = std::chrono::duration_cast<
                std::chrono::milliseconds
            >(targetTime.time_since_epoch()).count();

            Segment* closest = nullptr;

            for (const auto& segment : segments) {
                if (segment.timestamp.timestampMs <= targetMs) {
                    closest = const_cast<Segment*>(&segment);
                } else {
                    break;
                }
            }

            return closest ? std::optional<Segment>(*closest) : std::nullopt;
        }

        /**
         * Obtiene la lista cronológica de todos los segmentos
         */
        const std::vector<Segment>& getTimeline() const {
            return segments;
        }
    };
    ```
  </Tab>
</Tabs>

## Casos de uso del mundo real

### Reconstrucción de eventos multicámara

Sincroniza el material de varias cámaras para reconstruir incidentes de seguridad:

```python Event Timeline Reconstruction theme={null}
from datetime import datetime, timedelta

class EventReconstructor:
    def __init__(self):
        self.camera_segments = {}  # cameraId -> lista de segmentos

    def add_camera_footage(self, camera_id, segment_files):
        """Agrega segmentos de video de una cámara específica."""
        segments = []

        for file_path in segment_files:
            timestamp_ms, timestamp_dt = extract_rhombus_timestamp(file_path)
            if timestamp_ms:
                segments.append({
                    'file': file_path,
                    'start_time': timestamp_dt,
                    'timestamp_ms': timestamp_ms
                })

        self.camera_segments[camera_id] = sorted(
            segments,
            key=lambda x: x['timestamp_ms']
        )

    def reconstruct_event(self, event_time, window_seconds=30):
        """
        Encuentra todos los segmentos de cámara dentro de la ventana de tiempo de un evento.

        Args:
            event_time: datetime del evento
            window_seconds: segundos antes/después del evento a incluir
        """
        window = timedelta(seconds=window_seconds)
        start_time = event_time - window
        end_time = event_time + window

        relevant_footage = {}

        for camera_id, segments in self.camera_segments.items():
            camera_clips = []

            for segment in segments:
                # Comprobar si el segmento se superpone con la ventana del evento
                if start_time <= segment['start_time'] <= end_time:
                    offset = (segment['start_time'] - event_time).total_seconds()
                    camera_clips.append({
                        'file': segment['file'],
                        'start_time': segment['start_time'],
                        'offset_from_event': offset
                    })

            if camera_clips:
                relevant_footage[camera_id] = camera_clips

        return relevant_footage

# Uso
reconstructor = EventReconstructor()
reconstructor.add_camera_footage('entrance', entrance_files)
reconstructor.add_camera_footage('lobby', lobby_files)
reconstructor.add_camera_footage('parking', parking_files)

# Reconstruir el evento en una hora específica
event_time = datetime(2024, 8, 6, 15, 21, 18)
footage = reconstructor.reconstruct_event(event_time, window_seconds=30)

print(f"Footage for event at {event_time}:")
for camera_id, clips in footage.items():
    print(f"\n{camera_id}:")
    for clip in clips:
        print(f"  - {clip['file']}")
        print(f"    Offset: {clip['offset_from_event']:.2f}s")
```

### Correlación de datos de sensores

Alinea el video con eventos de control de acceso o de sensores ambientales:

```javascript Sensor-Video Correlation theme={null}
class SensorVideoCorrelator {
    constructor() {
        this.videoTimestamps = [];
        this.sensorEvents = [];
    }

    /**
     * Agrega un segmento de video con su marca de tiempo
     */
    addVideoSegment(cameraId, timestamp, videoUrl) {
        this.videoTimestamps.push({
            cameraId,
            timestamp: timestamp.timestampMs,
            datetime: timestamp.utcTime,
            url: videoUrl
        });
    }

    /**
     * Agrega un evento de sensor con su marca de tiempo
     */
    addSensorEvent(sensorId, eventType, timestampMs, data) {
        this.sensorEvents.push({
            sensorId,
            eventType,
            timestamp: timestampMs,
            datetime: new Date(timestampMs),
            data
        });
    }

    /**
     * Encuentra la cobertura de video para un evento de sensor
     */
    findVideoForEvent(eventTimestampMs, cameras = null) {
        const relevantVideos = this.videoTimestamps.filter(video => {
            // El segmento de video comienza antes o en la hora del evento
            const inTimeRange = video.timestamp <= eventTimestampMs;

            // Filtrar por cámara si se especifica
            const inCameraList = !cameras || cameras.includes(video.cameraId);

            return inTimeRange && inCameraList;
        });

        // Obtener el video más reciente antes del evento para cada cámara
        const latestByCamera = {};

        relevantVideos.forEach(video => {
            if (!latestByCamera[video.cameraId] ||
                video.timestamp > latestByCamera[video.cameraId].timestamp) {
                latestByCamera[video.cameraId] = video;
            }
        });

        return Object.values(latestByCamera);
    }

    /**
     * Genera un informe de correlación
     */
    generateCorrelationReport() {
        return this.sensorEvents.map(event => {
            const videos = this.findVideoForEvent(event.timestamp);

            return {
                event: {
                    type: event.eventType,
                    sensor: event.sensorId,
                    time: event.datetime.toISOString(),
                    data: event.data
                },
                associatedVideos: videos.map(v => ({
                    camera: v.cameraId,
                    url: v.url,
                    offset: event.timestamp - v.timestamp
                }))
            };
        });
    }
}

// Ejemplo de uso
const correlator = new SensorVideoCorrelator();

// Agregar un evento de acceso a una puerta
correlator.addSensorEvent(
    'door-entrance-1',
    'ACCESS_GRANTED',
    1722945678123,
    { userId: 'user123', cardId: '12345' }
);

// Agregar el video correspondiente
correlator.addVideoSegment(
    'camera-entrance',
    { timestampMs: 1722945670000, utcTime: new Date(1722945670000) },
    'https://media.rhombussystems.com/segment1.mp4'
);

// Generar informe
const report = correlator.generateCorrelationReport();
console.log(JSON.stringify(report, null, 2));
```

## Mejores prácticas para la integración

<AccordionGroup>
  <Accordion title="Validar la firma" icon="shield-check">
    Confirma siempre la etiqueta `rhom` antes de interpretar los bytes siguientes como una marca de tiempo. Esto evita la mala interpretación de datos no relacionados.

    ```python theme={null}
    def is_valid_rhombus_timestamp(data, rhom_index):
        # Verificar la firma 'rhom'
        if data[rhom_index:rhom_index + 4] != b'rhom':
            return False

        # Verificar que haya datos suficientes para la marca de tiempo
        if len(data) < rhom_index + 12:
            return False

        # Extraer y validar el rango de la marca de tiempo
        timestamp_bytes = data[rhom_index + 4:rhom_index + 12]
        timestamp_ms = int.from_bytes(timestamp_bytes, byteorder="big")

        # Verificación de coherencia: la marca de tiempo debe ser razonable
        # (entre 2015 y 2050)
        min_timestamp = 1420070400000  # 1 de enero de 2015
        max_timestamp = 2524608000000  # 1 de enero de 2050

        return min_timestamp <= timestamp_ms <= max_timestamp
    ```
  </Accordion>

  <Accordion title="Manejar las zonas horarias correctamente" icon="globe">
    La marca de tiempo está basada en UTC. Conviértela apropiadamente si tu aplicación necesita la hora local.

    ```python theme={null}
    import datetime as dt
    import pytz

    def convert_to_local_time(timestamp_ms, timezone='America/New_York'):
        # Crear un datetime UTC
        utc_dt = dt.datetime.fromtimestamp(timestamp_ms / 1000.0, tz=dt.timezone.utc)
        utc_dt = pytz.utc.localize(utc_dt)

        # Convertir a la zona horaria local
        local_tz = pytz.timezone(timezone)
        local_dt = utc_dt.astimezone(local_tz)

        return local_dt

    # Uso
    timestamp_ms = 1722945678123
    local_time = convert_to_local_time(timestamp_ms, 'America/Los_Angeles')
    print(f"Local time: {local_time}")
    ```
  </Accordion>

  <Accordion title="Usar como reloj de referencia" icon="clock">
    Combina la marca de tiempo de inicio del segmento con las marcas de tiempo de los fotogramas para una alineación con precisión de fotograma.

    ```javascript theme={null}
    class FrameTimestampCalculator {
        constructor(segmentStartMs, frameRate) {
            this.segmentStartMs = segmentStartMs;
            this.frameRate = frameRate;
            this.frameDurationMs = 1000 / frameRate;
        }

        /**
         * Calcula la marca de tiempo absoluta para un fotograma específico
         */
        getFrameTimestamp(frameNumber) {
            const offsetMs = frameNumber * this.frameDurationMs;
            return this.segmentStartMs + offsetMs;
        }

        /**
         * Encuentra el número de fotograma para una marca de tiempo específica
         */
        getFrameAtTimestamp(targetTimestampMs) {
            const offsetMs = targetTimestampMs - this.segmentStartMs;
            return Math.floor(offsetMs / this.frameDurationMs);
        }
    }

    // Uso
    const segmentTimestamp = 1722945678123;
    const calculator = new FrameTimestampCalculator(segmentTimestamp, 30);

    // Obtener la marca de tiempo del fotograma 150
    const frameTime = calculator.getFrameTimestamp(150);
    console.log('Frame 150 timestamp:', new Date(frameTime));

    // Encontrar el fotograma en una hora específica
    const targetTime = 1722945683123;
    const frameNum = calculator.getFrameAtTimestamp(targetTime);
    console.log('Frame at target time:', frameNum);
    ```
  </Accordion>

  <Accordion title="Versiona para prepararte a futuro" icon="code-branch">
    Almacena tu lógica de análisis de forma modular por si Rhombus agrega nuevos formatos de metadatos.

    ```python theme={null}
    class RhombusMetadataParser:
        """Analizador extensible para los formatos de metadatos de Rhombus."""

        VERSION = "1.0"

        def __init__(self):
            self.parsers = {
                b'rhom': self._parse_v1_timestamp
            }

        def parse(self, file_path):
            """Analiza los metadatos de un archivo de video."""
            with open(file_path, "rb") as f:
                data = f.read()

            # Buscar la caja 'free'
            free_idx = data.find(b'free')
            if free_idx == -1:
                return None

            # Comprobar todas las firmas conocidas
            for signature, parser_func in self.parsers.items():
                sig_idx = data.find(signature, free_idx)
                if sig_idx != -1:
                    return parser_func(data, sig_idx)

            return None

        def _parse_v1_timestamp(self, data, rhom_idx):
            """Analiza el formato de marca de tiempo v1."""
            timestamp_bytes = data[rhom_idx + 4:rhom_idx + 12]
            timestamp_ms = int.from_bytes(timestamp_bytes, byteorder="big")

            return {
                'version': 1,
                'type': 'timestamp',
                'timestamp_ms': timestamp_ms,
                'datetime': datetime.fromtimestamp(timestamp_ms / 1000.0, tz=datetime.timezone.utc)
            }

        def add_parser(self, signature, parser_func):
            """Agrega un analizador personalizado para nuevos formatos de metadatos."""
            self.parsers[signature] = parser_func

    # Uso
    parser = RhombusMetadataParser()
    metadata = parser.parse("video_segment.mp4")

    if metadata:
        print(f"Version: {metadata['version']}")
        print(f"Type: {metadata['type']}")
        print(f"Timestamp: {metadata['datetime']}")
    ```
  </Accordion>
</AccordionGroup>

## Consideraciones de rendimiento

<CardGroup cols={2}>
  <Card title="Lectura eficiente de archivos" icon="gauge-high">
    Para archivos grandes, lee solo la parte del encabezado en lugar del archivo completo
  </Card>

  <Card title="Estrategia de caché" icon="database">
    Almacena en caché las marcas de tiempo extraídas para evitar volver a analizar los mismos archivos
  </Card>

  <Card title="Procesamiento por lotes" icon="layer-group">
    Procesa varios archivos en paralelo al construir líneas de tiempo
  </Card>

  <Card title="Administración de memoria" icon="memory">
    Usa analizadores de streaming para archivos de video muy grandes
  </Card>
</CardGroup>

### Lectura optimizada de archivos

```python Optimized Parser theme={null}
def extract_rhombus_timestamp_optimized(file_path, max_search_bytes=100_000):
    """
    Versión optimizada que lee solo el comienzo del archivo.
    La mayoría de los metadatos están en la primera parte de los archivos MP4.
    """
    with open(file_path, "rb") as f:
        # Leer solo la parte del encabezado
        data = f.read(max_search_bytes)

    # Buscar la firma 'rhom'
    rhom_idx = data.find(b'rhom')
    if rhom_idx == -1:
        return None, None

    # Verificar que tenemos datos suficientes para la marca de tiempo
    if len(data) < rhom_idx + 12:
        return None, None

    timestamp_bytes = data[rhom_idx + 4:rhom_idx + 12]
    timestamp_ms = int.from_bytes(timestamp_bytes, byteorder="big")
    timestamp_dt = datetime.fromtimestamp(timestamp_ms / 1000.0, tz=datetime.timezone.utc)

    return timestamp_ms, timestamp_dt
```

## Conclusión

El método de Rhombus de embeber una **`marca de tiempo UTC con precisión de milisegundos en el átomo free`** de los segmentos ISOBMFF proporciona a los desarrolladores una herramienta poderosa para la **alineación precisa de eventos** en entornos de múltiples flujos.

Este enfoque preserva la compatibilidad con las herramientas de video existentes a la vez que desbloquea **precisión de subsegundos** para análisis, IA y monitoreo en tiempo real—algo crítico para integraciones avanzadas en el ecosistema de Rhombus.

<Tip>
  **Próximos pasos para los desarrolladores:**

  * Experimenta con la [biblioteca ISOBMFF de GitHub](https://github.com/DigiDNA/ISOBMFF) para analizar segmentos de Rhombus
  * Usa el [visor en línea MP4Box.js](https://gpac.github.io/mp4box.js/test/filereader.html) para inspeccionar visualmente las estructuras de cajas
  * Incorpora la extracción de marcas de tiempo en tu canalización de ingesta para conjuntos de datos multiorigen perfectamente sincronizados
</Tip>

## Recursos adicionales

<CardGroup cols={2}>
  <Card title="Guía del reproductor de video" icon="circle-play" href="/es/implementations/video-player">
    Aprende a implementar streaming de video en vivo con DashJS
  </Card>

  <Card title="Referencia de la API" icon="book" href="/api-reference/">
    Explora los endpoints de la API de cámara y medios, así como otras opciones de video
  </Card>

  <Card title="Especificación ISOBMFF" icon="file-lines" href="https://standards.iso.org/ittf/PubliclyAvailableStandards/">
    Lee la especificación oficial del ISO Base Media File Format
  </Card>

  <Card title="Comunidad de desarrolladores" icon="users" href="https://rhombus.community">
    Obtén ayuda y comparte implementaciones en la Comunidad de Desarrolladores
  </Card>
</CardGroup>

## Soporte

¿Necesitas ayuda con la extracción de marcas de tiempo o la sincronización de video?

* **Correo electrónico**: [support@rhombus.com](mailto:support@rhombus.com)
* **Comunidad**: [rhombus.community](https://rhombus.community)
* **Documentación**: Explora nuestra referencia completa de la API

<Note>
  Esta guía de implementación avanzada se actualiza periódicamente para reflejar las mejores prácticas más recientes para trabajar con segmentos de video de Rhombus.
</Note>
