summaryrefslogtreecommitdiff
path: root/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Theora.php
blob: b801ea26378d698fad87d06a5db1db699f423c97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------------+
// | File_Ogg PEAR Package for Accessing Ogg Bitstreams                         |
// | Copyright (c) 2005-2007                                                    |
// | David Grant <david@grant.org.uk>                                           |
// | Tim Starling <tstarling@wikimedia.org>                                     |
// +----------------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or              |
// | modify it under the terms of the GNU Lesser General Public                 |
// | License as published by the Free Software Foundation; either               |
// | version 2.1 of the License, or (at your option) any later version.         |
// |                                                                            |
// | This library is distributed in the hope that it will be useful,            |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of             |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU          |
// | Lesser General Public License for more details.                            |
// |                                                                            |
// | You should have received a copy of the GNU Lesser General Public           |
// | License along with this library; if not, write to the Free Software        |
// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA |
// +----------------------------------------------------------------------------+


define( 'OGG_THEORA_IDENTIFICATION_HEADER', 0x80 );
define( 'OGG_THEORA_COMMENTS_HEADER', 0x81 );
define( 'OGG_THEORA_IDENTIFICATION_PAGE_OFFSET', 0 );
define( 'OGG_THEORA_COMMENTS_PAGE_OFFSET', 1 );


/**
 * @author      David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org>
 * @category    File
 * @copyright   David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org>
 * @license     http://www.gnu.org/copyleft/lesser.html GNU LGPL
 * @link        http://pear.php.net/package/File_Ogg
 * @link        http://www.xiph.org/theora/
 * @package     File_Ogg
 * @version     CVS: $Id: Theora.php,v 1.9 2005/11/16 20:43:27 djg Exp $
 */
class File_Ogg_Theora extends File_Ogg_Media
{
    /**
     * @access  private
     */
    function __construct($streamSerial, $streamData, $filePointer)
    {
        parent::__construct($streamSerial, $streamData, $filePointer);
        $this->_decodeIdentificationHeader();
        $this->_decodeCommentsHeader();
      	$endSec = $this->getSecondsFromGranulePos( $this->_lastGranulePos );

        $startSec =  $this->getSecondsFromGranulePos( $this->_firstGranulePos );

        //make sure the offset is worth taking into account oggz_chop related hack
	    if( $startSec > 1){
            $this->_streamLength = $endSec - $startSec;
            $this->_startOffset = $startSec;
	    }else{
            $this->_streamLength = $endSec;
	    }

        $this->_avgBitrate = $this->_streamLength ? ($this->_streamSize * 8) / $this->_streamLength : 0;
    }
	function getSecondsFromGranulePos($granulePos){
		// Calculate GranulePos seconds
        // First make some "numeric strings"
        // These might not fit into PHP's integer type, but they will fit into
        // the 53-bit mantissa of a double-precision number
        $topWord = floatval( base_convert( substr( $granulePos, 0, 8 ), 16, 10 ) );
        $bottomWord = floatval( base_convert( substr( $granulePos, 8, 8 ), 16, 10 ) );
        // Calculate the keyframe position by shifting right by KFGSHIFT
        // We don't use PHP's shift operators because they're terribly broken
        // This is made slightly simpler by the fact that KFGSHIFT < 32
        $keyFramePos = $topWord / pow(2, $this->_kfgShift - 32) +
            floor( $bottomWord / pow(2, $this->_kfgShift) );
        // Calculate the frame offset by masking off the top 64-KFGSHIFT bits
        // This requires a bit of floating point trickery
        $offset = fmod( $bottomWord, pow(2, $this->_kfgShift) );
        // They didn't teach you that one at school did they?
        // Now put it together with the frame rate to calculate time in seconds
       	return  ( $keyFramePos + $offset ) / $this->_frameRate;
	}
    /**
     * Get the 6-byte identification string expected in the common header
     */
    function getIdentificationString()
    {
        return OGG_STREAM_CAPTURE_THEORA;
    }

    /**
     * Parse the identification header in a Theora stream.
     * @access  private
     */
    function _decodeIdentificationHeader()
    {
        $this->_decodeCommonHeader(OGG_THEORA_IDENTIFICATION_HEADER, OGG_THEORA_IDENTIFICATION_PAGE_OFFSET);
        $h = File_Ogg::_readBigEndian( $this->_filePointer, array(
            'VMAJ' => 8,
            'VMIN' => 8,
            'VREV' => 8,
            'FMBW' => 16,
            'FMBH' => 16,
            'PICW' => 24,
            'PICH' => 24,
            'PICX' => 8,
            'PICY' => 8,
            'FRN'  => 32,
            'FRD'  => 32,
            'PARN' => 24,
            'PARD' => 24,
            'CS'   => 8,
            'NOMBR' => 24,
            'QUAL' => 6,
            'KFGSHIFT' => 5,
            'PF' => 2));
        if ( !$h ) {
            throw new OggException("Stream is undecodable due to a truncated header.", OGG_ERROR_UNDECODABLE);
        }

        // Theora version
        // Seems overly strict but this is what the spec says
        // VREV is for backwards-compatible changes, apparently
        if ( $h['VMAJ'] != 3 || $h['VMIN'] != 2 ) {
            throw new OggException("Stream is undecodable due to an invalid theora version.", OGG_ERROR_UNDECODABLE);
        }
        $this->_theoraVersion = "{$h['VMAJ']}.{$h['VMIN']}.{$h['VREV']}";

        // Frame height/width
        if ( !$h['FMBW'] || !$h['FMBH'] ) {
            throw new OggException("Stream is undecodable because it has frame size of zero.", OGG_ERROR_UNDECODABLE);
        }
        $this->_frameWidth = $h['FMBW'] * 16;
        $this->_frameHeight = $h['FMBH'] * 16;

        // Picture height/width
        if ( $h['PICW'] > $this->_frameWidth || $h['PICH'] > $this->_frameHeight ) {
            throw new OggException("Stream is undecodable because the picture width is greater than the frame width.", OGG_ERROR_UNDECODABLE);
        }
        $this->_pictureWidth = $h['PICW'];
        $this->_pictureHeight = $h['PICH'];

        // Picture offset
        $this->_offsetX = $h['PICX'];
        $this->_offsetY = $h['PICY'];
        // Frame rate
        $this->_frameRate = $h['FRD'] == 0 ? 0 : $h['FRN'] / $h['FRD'];
        // Physical aspect ratio
        if ( !$h['PARN'] || !$h['PARD'] ) {
            $this->_physicalAspectRatio = 1;
        } else {
            $this->_physicalAspectRatio = $h['PARN'] / $h['PARD'];
        }

        // Color space
        $colorSpaces = array(
            0 => 'Undefined',
            1 => 'Rec. 470M',
            2 => 'Rec. 470BG',
        );
        if ( isset( $colorSpaces[$h['CS']] ) ) {
            $this->_colorSpace = $colorSpaces[$h['CS']];
        } else {
            $this->_colorSpace = 'Unknown (reserved)';
        }

        $this->_nomBitrate = $h['NOMBR'];

        $this->_quality = $h['QUAL'];
        $this->_kfgShift = $h['KFGSHIFT'];

        $pixelFormats = array(
            0 => '4:2:0',
            1 => 'Unknown (reserved)',
            2 => '4:2:2',
            3 => '4:4:4',
        );
        $this->_pixelFormat = $pixelFormats[$h['PF']];

        switch ( $h['PF'] ) {
            case 0:
                $h['NSBS'] =
                    floor( ($h['FMBW'] + 1) / 2 ) *
                    floor( ($h['FMBH'] + 1) / 2 ) + 2 *
                    floor( ($h['FMBW'] + 3) / 4 ) *
                    floor( ($h['FMBH'] + 3) / 4 );
                $h['NBS'] = 6 * $h['FMBW'] * $h['FMBH'];
                break;
            case 2:
                $h['NSBS'] =
                    floor( ($h['FMBW'] + 1) / 2 ) *
                    floor( ($h['FMBH'] + 1) / 2 ) + 2 *
                    floor( ($h['FMBW'] + 3) / 4 ) *
                    floor( ($h['FMBH'] + 1) / 2 );
                $h['NBS'] = 8 * $h['FMBW'] * $h['FMBH'];
                break;
            case 3:
                $h['NSBS'] =
                    3 * floor( ($h['FMBW'] + 1) / 2 ) *
                        floor( ($h['FMBH'] + 1) / 2 );
                $h['NBS'] = 12 * $h['FMBW'] * $h['FMBH'];
                break;
            default:
                $h['NSBS'] = $h['NBS'] = 0;
        }
        $h['NMBS'] = $h['FMBW'] * $h['FMBH'];

        $this->_idHeader = $h;
    }

    /**
     * Get an associative array containing header information about the stream
     * @access  public
     * @return  array
     */
    function getHeader() {
        return $this->_idHeader;
    }

    /**
     * Get a short string describing the type of the stream
     * @return string
     */
    function getType() {
        return 'Theora';
    }

    /**
     * Decode the comments header
     * @access private
     */
    function _decodeCommentsHeader()
    {
        $this->_decodeCommonHeader(OGG_THEORA_COMMENTS_HEADER, OGG_THEORA_COMMENTS_PAGE_OFFSET);
        $this->_decodeBareCommentsHeader();
    }

}
?>