Cheetah
Moovrelocator.class.php
Go to the documentation of this file.
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3 
74 // get absolute path to this lib
75 define('PATH_MOOVRELOCATOR', CH_DIRECTORY_PATH_PLUGINS . 'moovrelocator/lib/');
76 
77 // atom poc class
78 require_once PATH_MOOVRELOCATOR.'atom'.DIRECTORY_SEPARATOR.'Atom.class.php';
79 
80 // bytearray to operate direct on bytes in memory just to simulate Adobe's AS3 Bytearray
81 require_once PATH_MOOVRELOCATOR.'bytes'.DIRECTORY_SEPARATOR.'Bytearray.class.php';
82 
83 // transform (taken from php-reader)
84 require_once PATH_MOOVRELOCATOR.'bytes'.DIRECTORY_SEPARATOR.'Transform.class.php';
85 
104 {
111  private static $_instance = null;
112 
119  private $_outputFile = false;
120 
127  private $_filesize = 0;
128 
135  private $_fp = false;
136 
144  private $_ftypBytes;
145 
153  private $_moovBytes;
154 
162  private $_middleBytes;
163 
170  private $_fileAtoms = array();
171 
178  private $_lastAtom = null;
179 
186  private $_successfullyParsed = false;
187 
194  private $_fileValid = null;
195 
196 
210  public function setInput($filename = null)
211  {
212  // reset previously retrieved results
213  $this->_reset();
214 
215  // check and open file
216  if (!file_exists($filename) || !($this->_fp = fopen($filename, 'rb'))) {
217  return 'cannot open file: '.$filename;
218  }
219 
220  // get size of file
221  $this->_filesize = filesize($filename);
222 
223  // empty file?
224  if ($this->_filesize == 0) {
225  return 'file '.$filename.' seems to be empty!';
226  }
227 
228  // parse file's atoms
229  while (!feof($this->_fp) && ftell($this->_fp) < $this->_filesize) {
230  $this->_fileAtoms[] = $this->_parseAtomsFromInput();
231  }
232 
233  $moovEOFCheck = $this->_moovAtomAtEOF();
234  if ($moovEOFCheck !== true) {
235  return $moovEOFCheck;
236  } else {
237  // successful parsed the file!
238  $this->_successfullyParsed = true;
239  }
240 
241  // success
242  return true;
243  }
244 
245 
257  private function _parseAtomsFromInput()
258  {
259  // reset file stream pointer to position of the lastAtom.offset + size
260  if ((isset($this->_fileAtoms[count($this->_fileAtoms) - 1]))) {
261  $lastAtom = $this->_fileAtoms[count($this->_fileAtoms) - 1];
262  $position = $lastAtom->getSize() + $lastAtom->getOffset();
263  if (ftell($this->_fp) <= $position) {
264  fseek($this->_fp, $position);
265  }
266  }
267 
268  // get 8 bytes -> get totalSize and boxType
269  $atomData = unpack('NtotalSize/NboxType', fread($this->_fp, 8));
270  // store offset, size and type
271  $offset = ftell($this->_fp) - 8;
272  $size = $atomData['totalSize'];
273  $type = pack('N', $atomData['boxType']);
274 
275  // check 64 bit size (files > 4GB)
276  if ($size == 1) {
277  $highInt = $atomData['totalSize'];
278  $size = ($highInt << 32) | $atomData['totalSize'];
279  }
280 
281  // positioning of file-pointer
282  fseek($this->_fp, ($size + $offset));
283 
284  // give back atom
285  return self::factory($offset, $size, $type);
286  }
287 
288 
300  private function _isValidQTFile()
301  {
302  // check if file was succesfully parsed
303  if (!$this->_successfullyParsed) {
304  return 'please open a file first!';
305  }
306 
307  // get first atom
308  $firstAtom = $this->_fileAtoms[0];
309 
310  // check first for being valid (first atom MUST be ftyp)
311  if ($firstAtom->getType() != Atom::FTYP_ATOM) {
312  $this->_fileValid = false;
313  return 'encountered non-QT top-level atom (is this a Quicktime file?)';
314  }
315 
316  // check last for being valid
317  $lastAtom = $this->_fileAtoms[count($this->_fileAtoms) - 1];
318 
319  if (!Atom::isValidAtom($lastAtom->getType())) {
320  $this->_fileValid = false;
321  return 'encountered non-QT top-level atom (is this a Quicktime file?';
322  }
323 
324  // store validity
325  return $this->_fileValid = true;
326  }
327 
328 
340  private function _moovAtomAtEOF()
341  {
342  if ($this->_fileAtoms[count($this->_fileAtoms) - 1]->getType() != Atom::MOOV_ATOM) {
343  return 'The moov-atom isn\'t located at the end of the file, the file is allready ready for progressive download or it is a invalid file';
344  }
345 
346  // moov is at end of file...
347  return true;
348  }
349 
350 
364  public function setOutput($filename)
365  {
366  // store filename
367  $this->_outputFile = $filename;
368 
369  // success
370  return true;
371  }
372 
373 
389  public function relocateMoovAtom($filename, $outputFilename = null, $overwrite = false)
390  {
391  // read file, preprocess (parse atoms/boxes)
392  $result = $this->setInput($filename);
393  if ($result !== true) {
394  return $result;
395  }
396 
397  if (is_null($outputFilename) && $overwrite === true) {
398  $fileResult = $filename;
399  } else {
400  $fileResult = $outputFilename;
401  }
402 
403  // set the output filename and path
404  $result = $this->setOutput($fileResult);
405  if ($result !== true) {
406  return $result;
407  }
408 
409  // moov positioning fix
410  $result = $this->fix();
411  if ($result !== true) {
412  return $result;
413  }
414 
415  // success
416  return true;
417  }
418 
419 
431  public function fix()
432  {
433  // check if file was succesfully parsed
434  if (!$this->_successfullyParsed) {
435  return 'please open a file first! (syntax: '.__CLASS__.'->setInput($filename);)';
436  }
437 
438  if (!$this->_outputFile) {
439  return 'please set an outputfile first! (syntax: '.__CLASS__.'->setOutput($file);)';
440  }
441 
442  // check if moov atom is allready at beginning of file
443  if (!$this->_moovAtomAtEOF()) {
444  return 'nothing to do! moov allready at begin of file!';
445  }
446 
447  // Bytearray's holding bytes from file
448  $this->_ftypBytes = new Bytearray();
449  $this->_middleBytes = new Bytearray();
450  $this->_moovBytes = new Bytearray();
451 
452  // read in file's bytes
453  $result = $this->_readBytes();
454  if ($result !== true) {
455  return $result;
456  }
457 
458  // now start swapping
459  $result = $this->_swapIndex();
460  if ($result !== true) {
461  return $result;
462  }
463 
464  // write new file
465  $result = $this->_writeFile();
466  if ($result !== true) {
467  return $result;
468  }
469 
470  // everythings fine!
471  return true;
472  }
473 
474 
486  private function _readBytes()
487  {
488  // read bytes from all found atoms
489  for ($atom = 0; $atom < count($this->_fileAtoms); $atom++) {
490 
491  // get the current atom/box
492  $currentAtom = $this->_fileAtoms[$atom];
493  $currentAtomType = $currentAtom->getType();
494 
495  // keep ftyp atom
496  if ($currentAtomType == Atom::FTYP_ATOM) {
497  // set file pointer to begin of file
498  fseek($this->_fp, 0);
499  $bytes = fread($this->_fp, $currentAtom->getSize());
500  $this->_ftypBytes->writeBytes($bytes);
501 
502  } else if ($currentAtomType == Atom::MOOV_ATOM) {
503  $bytes = fread($this->_fp, $currentAtom->getSize());
504  $this->_moovBytes->writeBytes($bytes);
505 
506  } else {
507  $bytes = fread($this->_fp, $currentAtom->getSize());
508  $this->_middleBytes->writeBytes($bytes);
509  }
510  }
511 
512  return true;
513  }
514 
515 
527  private function _swapIndex()
528  {
529  $moovSize = $this->_moovBytes->bytesAvailable();
530 
531  $moovAType = '';
532  $moovASize = 0;
533  $offsetCount = 0;
534 
535  $compressionCheck = $this->_moovBytes->readBytes(12, 4);
536 
537  if ($compressionCheck == Atom::CMOV_ATOM) {
538  throw new Exception('compressed MP4/QT-file can\'t do this file: '.$file);
539  }
540 
541  // begin of metadata
542  $metaDataOffsets = array();
543  $metaDataStrings = array();
544  $metaDataCurrentLevel = 0;
545 
546  $moovStartOffset = 12;
547 
548  for ($i = $moovStartOffset; $i < $moovSize - $moovStartOffset; $i++) {
549  $moovAType = $this->_moovBytes->readUTFBytes($i, 4);
550 
551  if (Atom::isValidAtom($moovAType)) {
552 
553  $moovASize = $this->_moovBytes->readUnsignedInt($i - 4);
554 
555  if (($moovASize > 8) && ($moovASize + $i < ($moovSize - $moovStartOffset))) {
556 
557  try {
558  $containerLength = 0;
559  $containerString = $moovAType;
560 
561  for ($mi = count($metaDataOffsets) - 1; $mi > - 1; $mi--) {
562 
563  $containerLength = $metaDataOffsets[$mi];
564 
565  if ($i - $moovStartOffset < $containerLength && $i - $moovStartOffset + $moovASize > $containerLength) {
566  throw new Exception('bad atom nested size');
567  }
568 
569  if ($i - $moovStartOffset == $containerLength) {
570  array_pop($metaDataOffsets);
571  array_pop($metaDataStrings);
572  } else {
573  $containerString = $metaDataStrings[$mi].".".$containerString;
574  }
575  }
576 
577  if (($i - $moovStartOffset) <= $containerLength) {
578  array_push($metaDataOffsets, ($i - $moovStartOffset + $moovASize));
579  array_push($metaDataStrings, $moovAType);
580  }
581 
582  if ($moovAType != Atom::STCO_ATOM && $moovAType != Atom::CO64_ATOM) {
583  $i += 4;
584  } elseif ($moovAType == Atom::URL_ATOM || $moovAType == Atom::XML_ATOM) {
585  $i += $moovASize - 4;
586  }
587  }
588  catch(Exception $e) {
589  echo 'EXCEPTION: '.$e->getMessage();
590  }
591  }
592  }
593 
594 
595  if ($moovAType == Atom::STCO_ATOM) {
596  $moovASize = $this->_moovBytes->readUnsignedInt($i - 4);
597 
598  if ($i + $moovASize - $moovStartOffset > $moovSize) {
599  throw new Exception('bad atom size');
600  return;
601  }
602 
603  $offsetCount = $this->_moovBytes->readUnsignedInt($i + 8);
604 
605  for ($j = 0; $j < $offsetCount; $j++) {
606  $position = ($i + 12 + $j * 4);
607 
608  $currentOffset = $this->_moovBytes->readUnsignedInt($position);
609 
610  // cause of mooving the moov-atom right before the rest of data
611  // (behind ftyp) the new offset is caluclated:
612  // current-offset + size of moov atom (box) = new offset
613  $currentOffset += $moovSize;
614 
615  $this->_moovBytes->writeBytes(Transform::toUInt32BE($currentOffset), $position + 1);
616  }
617  $i += $moovASize - 4;
618 
619  } else if ($moovAType == Atom::CO64_ATOM) {
620  $moovASize = $this->_moovBytes->readUnsignedInt($i - 4);
621 
622  if ($i + $moovASize - $moovStartOffset > $moovSize) {
623  throw new Exception('bad atom size');
624  return;
625  }
626 
627  $offsetCount = $this->_moovBytes->readDouble($i + 8);
628 
629  for ($j2 = 0; $j2 < $offsetCount; $j2++) {
630  $position = ($i + 12 + $j * 8);
631 
632  $currentOffset = $this->_moovBytes->readUnsignedInt($position);
633 
634  // cause of mooving the moov-atom right before the rest of data
635  // (behind ftyp) the new offset is caluclated:
636  // current-offset + size of moov atom (box) = new offset
637  $currentOffset += $moovSize;
638 
639  // TODO implement!
640  //$this->_moovBytes->writeBytes(Transform::toUInt64BE($currentOffset), $position+1);
641  }
642  $i += $moovASize - 4;
643  }
644  }
645 
646  return true;
647  }
648 
649 
661  private function _writeFile()
662  {
663  // check if we need to unlink exisiting outputfile
664  if (file_exists($this->_outputFile)) {
665 
666  // close handle
667  if ($this->_fp) {
668  @fclose($this->_fp);
669  $this->_fp = null;
670  }
671 
672  if (!@unlink($this->_outputFile)) {
673  return 'error deleting file: '.$this->_outputFile.' outputfile "'.$this->_outputFile.'" exists (overwite = true)!';
674  }
675  }
676 
677  // open predefined output file
678  if (!$fh = fopen($this->_outputFile, 'wb+')) {
679  return 'error opening outputfile: '.$this->_outputFile.' for wb+ access!';
680  }
681 
682  // put ftyp atom/box in
683  if (!fwrite($fh, $this->_ftypBytes->readAllBytes())) {
684  return 'error writing ftyp-atom to outputfile: '.$this->_outputFile;
685  }
686 
687  // put moov atom in
688  if (!fwrite($fh, $this->_moovBytes->readAllBytes())) {
689  return 'error writing moov-atom to outputfile: '.$this->_outputFile;
690  }
691 
692  // put rest data in
693  if (!fwrite($fh, $this->_middleBytes->readAllBytes())) {
694  return 'error writing other atom(s) to outputfile: '.$this->_outputFile;
695  }
696 
697  // close handle
698  fclose($fh);
699 
700  // everything's fine
701  return true;
702  }
703 
704 
720  public static function factory($offset, $size, $type)
721  {
722  // instanciate a new atom-object
723  $atom = new Atom();
724 
725  // setup ...
726  $atom->setOffset($offset);
727  $atom->setSize($size);
728  $atom->setType($type);
729 
730  // and give back ...
731  return $atom;
732  }
733 
745  private function _reset()
746  {
747  $this->_fileAtoms = array();
748  $this->_lastAtom = null;
749  $this->_successfullyParsed = false;
750  $this->_fileValid = null;
751 
752  if ($this->_fp) {
753  @fclose($this->_fp);
754  }
755  }
756 
757 
769  public static function getInstance()
770  {
771  if (is_null(self::$_instance)) {
772  self::$_instance = new self();
773  }
774  return self::$_instance;
775  }
776 
777 
790  private function __clone()
791  {
792  // empty container
793  }
794 
795 
809  public function __destruct()
810  {
811  // close handle
812  $this->_reset();
813  }
814 }
815 
816 ?>
PATH_MOOVRELOCATOR
const PATH_MOOVRELOCATOR
Definition: Moovrelocator.class.php:75
Moovrelocator\fix
fix()
Definition: Moovrelocator.class.php:431
Transform\toUInt32BE
static toUInt32BE($value)
Definition: Transform.class.php:247
Atom\MOOV_ATOM
const MOOV_ATOM
Definition: Atom.class.php:103
Atom\FTYP_ATOM
const FTYP_ATOM
Definition: Atom.class.php:102
Bytearray
Definition: Bytearray.class.php:66
php
Atom\XML_ATOM
const XML_ATOM
Definition: Atom.class.php:108
Atom\URL_ATOM
const URL_ATOM
Definition: Atom.class.php:107
Moovrelocator\relocateMoovAtom
relocateMoovAtom($filename, $outputFilename=null, $overwrite=false)
Definition: Moovrelocator.class.php:389
Atom\CMOV_ATOM
const CMOV_ATOM
Definition: Atom.class.php:104
Moovrelocator\setInput
setInput($filename=null)
Definition: Moovrelocator.class.php:210
Moovrelocator\setOutput
setOutput($filename)
Definition: Moovrelocator.class.php:364
Moovrelocator\__destruct
__destruct()
Definition: Moovrelocator.class.php:809
Atom\CO64_ATOM
const CO64_ATOM
Definition: Atom.class.php:106
Atom
Definition: Atom.class.php:66
Moovrelocator
Definition: Moovrelocator.class.php:104
Atom\STCO_ATOM
const STCO_ATOM
Definition: Atom.class.php:105
Atom\isValidAtom
static isValidAtom($fourByteString)
Definition: Atom.class.php:187
Moovrelocator\getInstance
static getInstance()
Definition: Moovrelocator.class.php:769
Moovrelocator\factory
static factory($offset, $size, $type)
Definition: Moovrelocator.class.php:720