<?php
require dirname(__FILE__) . '/matroska.php';

// Header for single VPx keyframe
function vpxFrameHeader($size, $width, $height, $codecID) {
    return "\x1A\x45\xDF\xA3\x9F\x42\x86\x81\x01\x42\xF7\x81\x01\x42\xF2\x81"
        . "\x04\x42\xF3\x81\x08\x42\x82\x84\x77\x65\x62\x6D\x42\x87\x81\x02"
        . "\x42\x85\x81\x02\x18\x53\x80\x67\x08" . pack('N', $size + 173) . "\x11\x4D\x9B"
        . "\x74\xB8\x4D\xBB\x8B\x53\xAB\x84\x15\x49\xA9\x66\x53\xAC\x81\x3D"
        . "\x4D\xBB\x8B\x53\xAB\x84\x16\x54\xAE\x6B\x53\xAC\x81\x58\x4D\xBB"
        . "\x8B\x53\xAB\x84\x1C\x53\xBB\x6B\x53\xAC\x81\x85\x4D\xBB\x8B\x53"
        . "\xAB\x84\x1F\x43\xB6\x75\x53\xAC\x81\x97\x15\x49\xA9\x66\x96\x2A"
        . "\xD7\xB1\x83\x0F\x42\x40\x44\x89\x84\x41\x20\x00\x00\x4D\x80\x81"
        . "\x66\x57\x41\x81\x66\x16\x54\xAE\x6B\xA8\xAE\xA6\xD7\x81\x01\x73"
        . "\xC5\x81\x01\x83\x81\x01\x23\xE3\x83\x83\x98\x96\x80\x86\x85" . $codecID
        . "\xE0\x8C\xB0\x84" . pack('N', $width) . "\xBA\x84" . pack('N', $height)
        . "\x1C\x53\xBB\x6B\x8D\xBB\x8B\xB3\x81\x00\xB7\x86\xF7\x81"
        . "\x01\xF1\x81\x97\x1F\x43\xB6\x75\x08" . pack('N', $size + 13) . "\xE7\x81\x00"
        . "\xA3\x08" . pack('N', $size + 4) . "\x81\x00\x00\x80";
}

// Locate first VPx keyframe of track $trackNumber after timecode $skip
function firstVPxFrame($segment, $trackNumber, $skip=0) {
    foreach($segment as $x1) {
        if ($x1->name() == 'Cluster') {
            $cluserTimecode = $x1->Get('Timecode');
            foreach($x1 as $x2) {
                $blockRaw = NULL;
                if ($x2->name() == 'SimpleBlock') {
                    $blockRaw = $x2->value();
                } elseif ($x2->name() == 'BlockGroup') {
                    $blockRaw = $x2->get('Block');
                }
                if (isset($blockRaw)) {
                    $block = new MatroskaBlock($blockRaw);
                    if ($block->trackNumber == $trackNumber) {
                        $frame = $block->frames[0];
                        if ($block->keyframe) {
                            if (!isset($cluserTimecode) || $cluserTimecode + $block->timecode >= $skip) {
                                return $frame;
                            } elseif (!isset($frame1)) {
                                $frame1 = $frame;
                            }
                        }
                    }
                }
            }
        }
    }
    return isset($frame1) ? $frame1 : NULL;
}

function videoData($filename) {
    $data = array();

    // Open file
    $fileHandle = fopen($filename, 'rb');
    if (!$fileHandle) {
        error_log('could not open file');
        return $data;
    }

    try {
        $root = readMatroska($fileHandle);

        // Locate segment information and tracks
        $segment = $root->get('Segment');
        if (!isset($segment)) throw new Exception('missing Segment element');

        // Get segment information
        $info = $segment->get('Info');
        if (isset($info)) {
            $timecodeScale = $info->get('TimecodeScale');
            $duration = $info->get('Duration');
            if (isset($timecodeScale) && isset($duration)) {
                $data['duration'] = 1e-9 * $timecodeScale * $duration;
            }
        }

        // Locate video track
        $tracks = $segment->get('Tracks');
        if (!isset($tracks)) throw new Exception('missing Tracks element');
        foreach($tracks as $trackEntry) {
            if ($trackEntry->name() == 'TrackEntry' && $trackEntry->get('TrackType') == 1) {
                $videoTrack = $trackEntry;
                break;
            }
        }
        if (!isset($videoTrack)) throw new Exception('no video track');

        // Get track information
        $videoAttr = $videoTrack->get('Video');
        if (isset($videoAttr)) {
            $pixelWidth = $videoAttr->get('PixelWidth');
            $pixelHeight = $videoAttr->get('PixelHeight');
            if ($pixelWidth == 0 || $pixelHeight == 0) {
                error_log('bad PixelWidth/PixelHeight');
                $pixelWidth = NULL;
                $pixelHeight = NULL;
            }
            $data['width'] = $videoAttr->get('DisplayWidth', $pixelWidth);
            $data['height'] = $videoAttr->get('DisplayHeight', $pixelHeight);
            if ($data['width'] == 0 || $data['height'] == 0) {
                error_log('bad DisplayWidth/DisplayHeight');
                $data['width'] = $pixelWidth;
                $data['height'] = $pixelHeight;
            }
        }

        // Extract frame to use as thumbnail
        $trackNumber = $videoTrack->get('TrackNumber');
        if (!isset($trackNumber)) throw new Exception('missing track number');
        $codecID = $videoTrack->get('CodecID');
        if ($codecID != 'V_VP8' && $codecID != 'V_VP9') throw new Exception('codec is not VP8 or VP9');
        if (!isset($pixelWidth) || !isset($pixelHeight)) throw new Exception('no width or height');
        if (isset($data['duration']) && $data['duration'] >= 5) {
            $skip = 1e9 / $timecodeScale;
        } else {
            $skip = 0;
        }
        $frame = firstVPxFrame($segment, $trackNumber, $skip);
        if (!isset($frame)) throw new Exception('no keyframes');
        $data['frame'] = vpxFrameHeader($frame->size(), $pixelWidth, $pixelHeight, $codecID) . $frame->readAll();

    } catch (Exception $e) {
        error_log($e->getMessage());
    }

    fclose($fileHandle);
    return $data;
}