<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*
* This trait walks a file a piece at a time. A reader that uses it need
* not hold the whole file in memory.
*/
namespace seekquarry\yioop\library\av_processing;
/**
* ByteSource walks a file a piece at a time. A reader that uses it need not
* hold the whole file in memory.
*/
trait ByteSource
{
/**
* $handle stores the open file this reader takes its bytes from. A
* reader opens
* the file once and reads pieces of it as it goes, so a large
* video never sits in memory whole.
* @var resource
*/
private $handle;
/**
* $file_size stores how many bytes the file holds. A reader checks a
* chunk's length against it before seeking, so a damaged length cannot send
* the reader past the end. Set by openSource(). Read by readBytesAt(),
* sourceSize().
* @var int
*/
private int $file_size;
/**
* openSource opens the file and remembers its size.
*
* @param string $path file to read
*/
protected function openSource(string $path): void
{
$handle = @fopen($path, 'rb');
if ($handle === false) {
throw new VideoException("cannot open: $path");
}
$this->handle = $handle;
$size = filesize($path);
$this->file_size = ($size === false) ? PHP_INT_MAX : $size;
}
/**
* __destruct closes the open file when the reader goes away, so a
* site that opens many videos does not run out of open files. PHP
* calls it by itself when nothing holds the reader any longer.
*/
public function __destruct()
{
if (isset($this->handle) && is_resource($this->handle)) {
fclose($this->handle);
}
}
/**
* readBytesAt read exactly $length bytes, or fewer if the file ends first.
*
* @param int $off how far into the file to read from
* @param int $length how many bytes to read
* @return string what was read
*/
protected function readBytesAt(int $off, int $length): string
{
if ($length <= 0 || $off < 0 || $off >= $this->file_size) {
return '';
}
if (fseek($this->handle, $off) !== 0) {
throw new VideoException("seek failed at $off");
}
$bytes = '';
while (strlen($bytes) < $length) {
$chunk = fread($this->handle, $length - strlen($bytes));
if ($chunk === false || $chunk === '') {
break;
}
$bytes .= $chunk;
}
return $bytes;
}
/**
* readExact reads the number of bytes asked for and throws where the file
* ends first. A reader uses it for a field whose length the format fixes,
* so that a cut short file is reported rather than read as though it held
* zeros.
*
* @param int $off How far into the file to start reading.
* @param int $length How many bytes must be there.
* @return string what was read
*/
protected function readExact(int $off, int $length): string
{
$bytes = $this->readBytesAt($off, $length);
if (strlen($bytes) !== $length) {
throw new VideoException("short read at $off (wanted $length)");
}
return $bytes;
}
/**
* sourceSize works out how many bytes the file holds.
*
* @return int what was read
*/
protected function sourceSize(): int
{
return $this->file_size;
}
}