Browse Source

improved frame extraction (including alpha)

pull/40/head
ccd0 11 years ago
parent
commit
dd3cae6f70
  1. 1
      README.md
  2. 10
      matroska.php
  3. 124
      videodata.php

1
README.md

@ -35,7 +35,6 @@ And add this to stylesheets/style.css:
float: left; float: left;
margin: 10px 20px; margin: 10px 20px;
border: none; border: none;
background: #aaa;
} }
div.post video.post-image { div.post video.post-image {
padding: 0px; padding: 0px;

10
matroska.php

@ -497,7 +497,7 @@ function readMatroska($fileHandle) {
return $root; return $root;
} }
function encodeVarInt($n) { function ebmlEncodeVarInt($n) {
$data = ''; $data = '';
$flag = 0x80; $flag = 0x80;
while ($n >= $flag) { while ($n >= $flag) {
@ -512,11 +512,11 @@ function encodeVarInt($n) {
return $data; return $data;
} }
function encodeElementName($name) { function ebmlEncodeElementName($name) {
global $EBML_ELEMENTS; global $EBML_ELEMENTS;
return encodeVarInt($EBML_ELEMENTS->id($name)); return ebmlEncodeVarInt($EBML_ELEMENTS->id($name));
} }
function encodeElement($name, $content) { function ebmlEncodeElement($name, $content) {
return encodeElementName($name) . encodeVarInt(strlen($content)) . $content; return ebmlEncodeElementName($name) . ebmlEncodeVarInt(strlen($content)) . $content;
} }

124
videodata.php

@ -1,76 +1,53 @@
<?php <?php
require dirname(__FILE__) . '/matroska.php'; require dirname(__FILE__) . '/matroska.php';
function matroskaSeekElement($name, $pos) {
return ebmlEncodeElement('Seek',
ebmlEncodeElement('SeekID', ebmlEncodeElementName($name))
. ebmlEncodeElement('SeekPosition', pack('N', $pos))
);
}
// Make video from single VPx keyframe // Make video from single VPx keyframe
function muxVPxFrame($width, $height, $codecID, $data) { function muxVPxFrame($trackNumber, $videoTrack, $frame) {
$size = strlen($data); $lenSeekHead = 73;
$lenSeekHead = 61; $lenCues = 24;
$lenCues = 18; $ebml = ebmlEncodeElement('EBML',
$ebml = encodeElement('EBML', ebmlEncodeElement('DocType', "webm")
encodeElement('EBMLVersion', "\x01") . ebmlEncodeElement('DocTypeVersion', "\x02")
. encodeElement('EBMLReadVersion', "\x01") . ebmlEncodeElement('DocTypeReadVersion', "\x02")
. encodeElement('EBMLMaxIDLength', "\x04")
. encodeElement('EBMLMaxSizeLength', "\x08")
. encodeElement('DocType', "webm")
. encodeElement('DocTypeVersion', "\x02")
. encodeElement('DocTypeReadVersion', "\x02")
); );
$info = encodeElement('Info', $info = ebmlEncodeElement('Info',
encodeElement('TimecodeScale', "\x0F\x42\x40") ebmlEncodeElement('Duration', "\x41\x20\x00\x00")
. encodeElement('Duration', "\x41\x20\x00\x00") . ebmlEncodeElement('MuxingApp', 'ccframe')
. encodeElement('MuxingApp', 'f') . ebmlEncodeElement('WritingApp', 'ccframe')
. encodeElement('WritingApp', 'f')
); );
$tracks = encodeElement('Tracks', $tracks = ebmlEncodeElement('Tracks',
encodeElement('TrackEntry', ebmlEncodeElement('TrackEntry', $videoTrack->content()->readAll())
encodeElement('TrackNumber', "\x01")
. encodeElement('TrackUID', "\x01")
. encodeElement('TrackType', "\x01")
. encodeElement('DefaultDuration', "\x98\x96\x80")
. encodeElement('CodecID', $codecID)
. encodeElement('Video',
encodeElement('PixelWidth', pack('N', $width))
. encodeElement('PixelHeight', pack('N', $height))
)
)
); );
$cues = encodeElement('Cues', $cues = ebmlEncodeElement('Cues',
encodeElement('CuePoint', ebmlEncodeElement('CuePoint',
encodeElement('CueTime', "\x00") ebmlEncodeElement('CueTime', "\x00")
. encodeElement('CueTrackPositions', . ebmlEncodeElement('CueTrackPositions',
encodeElement('CueTrack', "\x01") ebmlEncodeElement('CueTrack', pack('N', $trackNumber))
. encodeElement('CueClusterPosition', chr($lenSeekHead + strlen($info) + strlen($tracks) + $lenCues)) . ebmlEncodeElement('CueClusterPosition', pack('N', $lenSeekHead + strlen($info) + strlen($tracks) + $lenCues))
) )
) )
); );
$seekHead = encodeElement('SeekHead', if (strlen($cues) != $lenCues) throw new Exception('length of Cues element wrong');
encodeElement('Seek', $cluster = ebmlEncodeElement('Cluster',
encodeElement('SeekID', encodeElementName('Info')) ebmlEncodeElement('Timecode', "\x00")
. encodeElement('SeekPosition', chr($lenSeekHead)) . ebmlEncodeElement($frame->name(), $frame->content()->readAll())
) . ebmlEncodeElement('Void', '')
. encodeElement('Seek',
encodeElement('SeekID', encodeElementName('Tracks'))
. encodeElement('SeekPosition', chr($lenSeekHead + strlen($info)))
)
. encodeElement('Seek',
encodeElement('SeekID', encodeElementName('Cues'))
. encodeElement('SeekPosition', chr($lenSeekHead + strlen($info) + strlen($tracks)))
)
. encodeElement('Seek',
encodeElement('SeekID', encodeElementName('Cluster'))
. encodeElement('SeekPosition', chr($lenSeekHead + strlen($info) + strlen($tracks) + $lenCues))
)
);
$cluster = "\x1F\x43\xB6\x75\x08" . pack('N', $size + 13) . (
//. encodeElement('Cluster',
encodeElement('Timecode', "\x00")
. "\xA3\x08" . pack('N', $size + 4) . ("\x81\x00\x00\x80" . $data)
//. encodeElement('SimpleBlock', "\x81\x00\x00\x80" . $data)
); );
$segment = "\x18\x53\x80\x67\x08" . pack('N', $size + 173) . ( $seekHead = ebmlEncodeElement('SeekHead',
// . encodeElement('Segment', matroskaSeekElement('Info', $lenSeekHead)
$seekHead . $info . $tracks . $cues . $cluster . matroskaSeekElement('Tracks', $lenSeekHead + strlen($info))
. matroskaSeekElement('Cues', $lenSeekHead + strlen($info) + strlen($tracks))
. matroskaSeekElement('Cluster', $lenSeekHead + strlen($info) + strlen($tracks) + $lenCues)
); );
if (strlen($seekHead) != $lenSeekHead) throw new Exception('length of SeekHead element wrong');
$segment = ebmlEncodeElement('Segment', $seekHead . $info . $tracks . $cues . $cluster);
return $ebml . $segment; return $ebml . $segment;
} }
@ -79,23 +56,20 @@ function firstVPxFrame($segment, $trackNumber, $skip=0) {
foreach($segment as $x1) { foreach($segment as $x1) {
if ($x1->name() == 'Cluster') { if ($x1->name() == 'Cluster') {
$cluserTimecode = $x1->Get('Timecode'); $cluserTimecode = $x1->Get('Timecode');
foreach($x1 as $x2) { foreach($x1 as $blockGroup) {
$blockRaw = NULL; $blockRaw = NULL;
if ($x2->name() == 'SimpleBlock') { if ($blockGroup->name() == 'SimpleBlock') {
$blockRaw = $x2->value(); $blockRaw = $blockGroup->value();
} elseif ($x2->name() == 'BlockGroup') { } elseif ($blockGroup->name() == 'BlockGroup') {
$blockRaw = $x2->get('Block'); $blockRaw = $blockGroup->get('Block');
} }
if (isset($blockRaw)) { if (isset($blockRaw)) {
$block = new MatroskaBlock($blockRaw); $block = new MatroskaBlock($blockRaw);
if ($block->trackNumber == $trackNumber) { if ($block->trackNumber == $trackNumber && $block->keyframe) {
$frame = $block->frames[0]; if (!isset($cluserTimecode) || $cluserTimecode + $block->timecode >= $skip) {
if ($block->keyframe) { return $blockGroup;
if (!isset($cluserTimecode) || $cluserTimecode + $block->timecode >= $skip) { } elseif (!isset($frame1)) {
return $frame; $frame1 = $blockGroup;
} elseif (!isset($frame1)) {
$frame1 = $frame;
}
} }
} }
} }
@ -175,7 +149,7 @@ function videoData($filename) {
} }
$frame = firstVPxFrame($segment, $trackNumber, $skip); $frame = firstVPxFrame($segment, $trackNumber, $skip);
if (!isset($frame)) throw new Exception('no keyframes'); if (!isset($frame)) throw new Exception('no keyframes');
$data['frame'] = muxVPxFrame($pixelWidth, $pixelHeight, $codecID, $frame->readAll()); $data['frame'] = muxVPxFrame($trackNumber, $videoTrack, $frame);
} catch (Exception $e) { } catch (Exception $e) {
error_log($e->getMessage()); error_log($e->getMessage());

Loading…
Cancel
Save