<?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
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\library\av_processing\WebmExtractor;
use seekquarry\yioop\library\UnitTest;
/**
* WebmExtractorTest checks that WebmExtractor reads a WebM file
* correctly: how long the video runs, how large its frames are, what it
* was coded with, which frames can be decoded on their own, and where in
* the file each frame's bytes sit. A WebM keeps its frames in clusters
* rather than in one table, so the reader walks those clusters to answer
* any of these. Each case compares what the reader says against what
* ffmpeg wrote.
*
* @author Chris Pollett
*/
class WebmExtractorTest extends UnitTest
{
/**
* VIDEO_SECONDS is how many seconds the test video runs, as ffmpeg
* wrote it. The reader's answer is compared against this.
* @var float
*/
const VIDEO_SECONDS = 5.01;
/**
* VIDEO_WIDTH is how many pixels across each frame of the test video
* is, as ffmpeg wrote it.
* @var int
*/
const VIDEO_WIDTH = 320;
/**
* VIDEO_HEIGHT is how many pixels down each frame of the test video
* is, as ffmpeg wrote it.
* @var int
*/
const VIDEO_HEIGHT = 240;
/**
* $video_path stores where setUp wrote the WebM file that the cases
* read. Each case opens a reader on this path.
* @var string
*/
public $video_path;
/**
* $reader stores the WebmExtractor under test, opened on the file at
* $video_path.
* @var WebmExtractor
*/
public $reader;
/**
* setUp writes the test video out of the base64 it is kept in and
* opens a reader on it. Every case here needs a real file, since the
* reader walks the file's clusters. The file is small enough that
* writing it for each case costs under a hundredth of a second.
*/
public function setUp()
{
$this->video_path = C\WORK_DIRECTORY . "/temp/webmread" .
getmypid() . ".webm";
$held = file_get_contents(C\PARENT_DIR .
"/tests/test_files/video_movie_webm.txt");
file_put_contents($this->video_path, base64_decode(trim($held)));
@chmod($this->video_path, 0777);
$this->reader = new WebmExtractor($this->video_path);
}
/**
* tearDown removes the video file that setUp wrote, so a run leaves
* nothing behind in the work directory.
*/
public function tearDown()
{
if (file_exists($this->video_path)) {
unlink($this->video_path);
}
}
/**
* videosShapeIsReadTestCase checks that the reader gives back how
* long the video runs, how large its frames are, and which container
* it came out of. A wiki page sizes its player from these three, so
* each is compared against what ffmpeg wrote.
*/
public function videosShapeIsReadTestCase()
{
$this->assertTrue(abs($this->reader->durationSeconds() -
self::VIDEO_SECONDS) < 0.05, "the video runs " .
self::VIDEO_SECONDS . " seconds, and the reader says " .
round($this->reader->durationSeconds(), 2));
$this->assertEqual(self::VIDEO_WIDTH, $this->reader->frameWidth(),
"each frame is " . self::VIDEO_WIDTH . " pixels across");
$this->assertEqual(self::VIDEO_HEIGHT,
$this->reader->frameHeight(),
"and " . self::VIDEO_HEIGHT . " pixels down");
$this->assertEqual("WebM", $this->reader->containerName(),
"and the file says it is a WebM");
}
/**
* whatTheVideoWasCodedWithIsNamedTestCase checks that the reader
* names what the video was coded with. A caller picks a decoder from
* that name, so a wrong one sends the frames to a decoder that
* cannot read them.
*/
public function whatTheVideoWasCodedWithIsNamedTestCase()
{
$this->assertEqual("vp9", $this->reader->codecKind(),
"the video was coded with vp9");
$this->assertEqual("V_VP9", $this->reader->codecName(),
"which a WebM names V_VP9 in its own table");
}
/**
* framesThatStandAloneAreFoundTestCase checks that the reader finds
* the frames which can be decoded on their own, and hands back the
* bytes of one. A thumbnail is taken from such a frame, since any
* other frame needs the frames before it.
*/
public function framesThatStandAloneAreFoundTestCase()
{
$syncs = $this->reader->syncSamples();
$this->assertTrue(count($syncs) > 0,
"the video holds at least one frame that stands alone, and " .
"holds " . count($syncs));
$first = $syncs[0];
$this->assertTrue($this->reader->sampleTime($first) <
self::VIDEO_SECONDS,
"the first such frame sits inside the video's length");
$bytes = $this->reader->sampleData($first);
$this->assertTrue(strlen($bytes) > 0,
"and its bytes can be read back, which came to " .
strlen($bytes));
}
/**
* frameForAMomentIsAtOrBeforeItTestCase checks that asking for
* the frame at a moment gives one at or before that moment, never
* after it. A thumbnail asked for two seconds in must not come from
* the third second.
*/
public function frameForAMomentIsAtOrBeforeItTestCase()
{
$syncs = $this->reader->syncSamples();
$wanted = self::VIDEO_SECONDS / 2;
$found = -1;
foreach ($syncs as $one) {
if ($this->reader->sampleTime($one) <= $wanted) {
$found = $one;
}
}
$this->assertTrue($found >= 0,
"a frame at or before the middle of the video is found");
$this->assertTrue($this->reader->sampleTime($found) <= $wanted,
"and it does not sit after the moment asked for");
}
/**
* fileThatIsNotAWebmIsRefusedTestCase checks that a file which is
* not a WebM is refused rather than read as one. A reader that
* carried on would hand its caller frames made of whatever bytes
* happened to follow.
*/
public function fileThatIsNotAWebmIsRefusedTestCase()
{
$where = C\WORK_DIRECTORY . "/temp/notwebm" . getmypid() . ".webm";
file_put_contents($where, str_repeat("not a video at all", 20));
@chmod($where, 0777);
$refused = false;
try {
$reader = new WebmExtractor($where);
$reader->durationSeconds();
} catch (\Exception $trouble) {
$refused = true;
}
if (file_exists($where)) {
unlink($where);
}
$this->assertTrue($refused,
"a file of ordinary text is refused rather than read");
}
}