Skip to content

Instantly share code, notes, and snippets.

@Thinkscape
Created September 20, 2012 16:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Thinkscape/3756836 to your computer and use it in GitHub Desktop.
Save Thinkscape/3756836 to your computer and use it in GitHub Desktop.
<?php
/*
* qtfaststart.php v0.1 by Valentin Schmidt
* based on qt-faststart.c v0.2 by Mike Melanson (melanson@pcisys.net)
*
* This file is placed in the public domain. Use the program however you
* see fit.
*
* This utility rearranges a Quicktime file such that the moov atom
* is in front of the data, thus facilitating network streaming.
*
* Notes: Quicktime files can come in many configurations of top-level
* atoms. This utility stipulates that the very last atom in the file needs
* to be a moov atom. When given such a file, this utility will rearrange
* the top-level atoms by shifting the moov atom from the back of the file
* to the front, and patch the chunk offsets along the way. This utility
* presently only operates on uncompressed moov atoms.
*/
define('MOOV_ATOM', 'moov');
define('FTYP_ATOM', 'ftyp');
define('CMOV_ATOM', 'cmov');
define('STCO_ATOM', 'stco');
define('CO64_ATOM', 'co64');
define('ATOM_PREAMBLE_SIZE', 8);
define('COPY_BUFFER_SIZE', 1024);
function
BE_32($x, $offset=0){
return
(ord($x[$offset+0]) << 24) |
(ord($x[$offset+1]) << 16) |
(ord($x[$offset+2]) << 8) |
(ord($x[$offset+3]));
}
function
BE_64($x, $offset=0){
return
(ord($x[$offset+0]) << 56) |
(ord($x[$offset+1]) << 48) |
(ord($x[$offset+2]) << 40) |
(ord($x[$offset+3]) << 32) |
(ord($x[$offset+4]) << 24) |
(ord($x[$offset+5]) << 16) |
(ord($x[$offset+6]) << 8) |
(ord($x[$offset+7]));
}
function
status($s){
echo $s.'<br />';
}
function
qtfaststart($inputVideoFile, $outputVideoFile){
$atom_offset = 0;
$atom_type = '';
$infile = fopen($inputVideoFile, "rb");
if (!$infile) {
return 0;
}
/* traverse through the atoms in the file to make sure that 'moov' is at the end */
while (!feof($infile)) {
if (($atom_bytes = fread($infile, ATOM_PREAMBLE_SIZE)) == FALSE) {
break;
}
$atom_size = BE_32($atom_bytes, 0);
$atom_type = substr($atom_bytes, 4, 4);
/* keep ftyp atom */
if ($atom_type == FTYP_ATOM) {
$ftyp_atom_size = $atom_size;
fseek($infile, -ATOM_PREAMBLE_SIZE, SEEK_CUR);
$ftyp_atom = fread($infile, $ftyp_atom_size);
$start_offset = ftell($infile);
} else {
/* 64-bit special case */
if ($atom_size == 1) {
if (($atom_bytes = fread($infile, ATOM_PREAMBLE_SIZE)) == FALSE) {
break;
}
$atom_size = BE_64($atom_bytes, 0);
fseek($infile, $atom_size - ATOM_PREAMBLE_SIZE * 2, SEEK_CUR);
} else {
fseek($infile, $atom_size - ATOM_PREAMBLE_SIZE, SEEK_CUR);
}
}
$atom_offset += $atom_size;
/* The atom header is 8 (or 16 bytes), if the atom size (which
* includes these 8 or 16 bytes) is less than that, we won't be
* able to continue scanning sensibly after this atom, so break. */
if ($atom_size < 8) break;
}
if (
$atom_type != MOOV_ATOM) {
status("last atom in file was not a moov atom");
fclose($infile);
return 0;
}
/* moov atom was, in fact, the last atom in the chunk; load the whole moov atom */
fseek($infile, -$atom_size, SEEK_END);
$last_offset = ftell($infile);
$moov_atom_size = $atom_size;
if (($moov_atom = fread($infile, $moov_atom_size)) == FALSE) {
fclose($infile);
return 0;
}
/* this utility does not support compressed atoms yet, so disqualify
* files with compressed QT atoms */
if (substr($moov_atom, 12, 4) == CMOV_ATOM) {
status("this utility does not support compressed moov atoms yet");
fclose($infile);
return 0;
}
/* close; will be re-opened later */
fclose($infile);
/* crawl through the moov chunk in search of stco or co64 atoms */
for ($i = 4; $i < $moov_atom_size - 4; $i++) {
$atom_type = substr($moov_atom, $i, 4);
if ($atom_type == STCO_ATOM) {
status(" patching stco atom");
$atom_size = BE_32($moov_atom, $i - 4);
if ($i + $atom_size - 4 > $moov_atom_size) {
status(" bad atom size");
return 0;
}
$offset_count = BE_32($moov_atom, $i + 8);
for ($j = 0; $j < $offset_count; $j++) {
$current_offset = BE_32($moov_atom, $i + 12 + $j * 4);
$current_offset += $moov_atom_size;
$moov_atom[$i + 12 + $j * 4 + 0] = chr( ($current_offset >> 24) & 0xFF);
$moov_atom[$i + 12 + $j * 4 + 1] = chr( ($current_offset >> 16) & 0xFF);
$moov_atom[$i + 12 + $j * 4 + 2] = chr( ($current_offset >> 8) & 0xFF);
$moov_atom[$i + 12 + $j * 4 + 3] = chr( ($current_offset >> 0) & 0xFF);
}
$i += $atom_size - 4;
} else if ($atom_type == CO64_ATOM) {
status(" patching co64 atom");
$atom_size = BE_32($moov_atom, $i - 4);
if ($i + $atom_size - 4 > $moov_atom_size) {
status(" bad atom size");
return 0;
}
$offset_count = BE_32($moov_atom, $i + 8);
for ($j = 0; $j < $offset_count; $j++) {
$current_offset = BE_64($moov_atom, $i + 12 + $j * 8);
$current_offset += $moov_atom_size;
$moov_atom[$i + 12 + $j * 8 + 0] = chr( ($current_offset >> 56) & 0xFF);
$moov_atom[$i + 12 + $j * 8 + 1] = chr( ($current_offset >> 48) & 0xFF);
$moov_atom[$i + 12 + $j * 8 + 2] = chr( ($current_offset >> 40) & 0xFF);
$moov_atom[$i + 12 + $j * 8 + 3] = chr( ($current_offset >> 32) & 0xFF);
$moov_atom[$i + 12 + $j * 8 + 4] = chr( ($current_offset >> 24) & 0xFF);
$moov_atom[$i + 12 + $j * 8 + 5] = chr( ($current_offset >> 16) & 0xFF);
$moov_atom[$i + 12 + $j * 8 + 6] = chr( ($current_offset >> 8) & 0xFF);
$moov_atom[$i + 12 + $j * 8 + 7] = chr( ($current_offset >> 0) & 0xFF);
}
$i += $atom_size - 4;
}
}
/* re-open the input file and open the output file */
$infile = fopen($inputVideoFile, "rb");
if (!$infile) {
return 0;
}
if (
$start_offset > 0) { /* seek after ftyp atom */
fseek($infile, $start_offset, SEEK_SET);
$last_offset -= $start_offset;
}
$outfile = fopen($outputVideoFile, "wb");
if (!$outfile) {
fclose($infile);
return 0;
}
/* dump the same ftyp atom */
if ($ftyp_atom_size > 0) {
status(" writing ftyp atom");
if ((fwrite($outfile, $ftyp_atom, $ftyp_atom_size)) == FALSE) {
fclose($infile);
fclose($outfile);
return 0;
}
}
/* dump the new moov atom */
status(" writing moov atom");
if ((fwrite($outfile, $moov_atom, $moov_atom_size)) == FALSE) {
fclose($infile);
fclose($outfile);
return 0;
}
/* copy the remainder of the infile, from offset 0 -> last_offset - 1 */
status(" copying rest of file");
while ($last_offset) {
if ($last_offset > COPY_BUFFER_SIZE)
$bytes_to_copy = COPY_BUFFER_SIZE;
else
$bytes_to_copy = $last_offset;
if (($copy_buffer = fread($infile, $bytes_to_copy)) == FALSE) {
fclose($infile);
fclose($outfile);
return 0;
}
if ((fwrite($outfile, $copy_buffer, $bytes_to_copy)) == FALSE) {
fclose($infile);
fclose($outfile);
return 0;
}
$last_offset -= $bytes_to_copy;
}
fclose($infile);
fclose($outfile);
return 1;
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment