69 lines
3.7 KiB
PHP
69 lines
3.7 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Tests\Support;
|
||
|
|
|
||
|
|
use Illuminate\Http\UploadedFile;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Синтетические файлы медиа под требования кабинета МТС.
|
||
|
|
*
|
||
|
|
* 🪤 Почему классом, а не функциями в тесте: в Pest помощники из разных файлов живут
|
||
|
|
* в ОДНОМ пространстве имён, и одноимённые функции в двух наборах роняют прогон
|
||
|
|
* «Cannot redeclare» — на этом уже спотыкались при сведении веток 01.08.2026.
|
||
|
|
* Класс автозагружается (Tests\ → tests/) и столкнуться не может.
|
||
|
|
*
|
||
|
|
* Настоящих пикселей внутри нет — только те поля заголовка, которые читает
|
||
|
|
* App\Support\Mp4Probe: длительность (mvhd) и размер кадра (tkhd).
|
||
|
|
*/
|
||
|
|
final class MtsMediaFixture
|
||
|
|
{
|
||
|
|
/** Один бокс контейнера ISO-BMFF: длина (4 байта) + тип (4 байта) + содержимое. */
|
||
|
|
private static function box(string $type, string $payload): string
|
||
|
|
{
|
||
|
|
return pack('N', 8 + strlen($payload)).$type.$payload;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Байты mp4 с настоящими mvhd (длительность) и tkhd (размер кадра). */
|
||
|
|
public static function mp4Bytes(float $seconds = 10.0, int $width = 1280, int $height = 720): string
|
||
|
|
{
|
||
|
|
$timescale = 1000;
|
||
|
|
$duration = (int) round($seconds * $timescale);
|
||
|
|
|
||
|
|
$mvhd = "\x00\x00\x00\x00" // version 0 + flags
|
||
|
|
.pack('N', 0).pack('N', 0) // создан / изменён
|
||
|
|
.pack('N', $timescale).pack('N', $duration) // масштаб времени и длительность
|
||
|
|
.pack('N', 0x00010000) // rate
|
||
|
|
.pack('n', 0x0100) // volume
|
||
|
|
.str_repeat("\x00", 2 + 8 + 36 + 24) // reserved + matrix + pre_defined
|
||
|
|
.pack('N', 2); // next_track_ID
|
||
|
|
|
||
|
|
$tkhd = "\x00\x00\x00\x07" // version 0 + флаги «дорожка включена»
|
||
|
|
.pack('N', 0).pack('N', 0) // создан / изменён
|
||
|
|
.pack('N', 1) // track_ID
|
||
|
|
.pack('N', 0) // reserved
|
||
|
|
.pack('N', $duration) // длительность дорожки
|
||
|
|
.str_repeat("\x00", 8) // reserved
|
||
|
|
.pack('n', 0).pack('n', 0) // layer, alternate_group
|
||
|
|
.pack('n', 0).pack('n', 0) // volume, reserved
|
||
|
|
.str_repeat("\x00", 36) // matrix
|
||
|
|
.pack('N', $width << 16).pack('N', $height << 16); // ширина/высота 16.16
|
||
|
|
|
||
|
|
return self::box('ftyp', 'isom'.pack('N', 512).'isomiso2mp41')
|
||
|
|
.self::box('moov', self::box('mvhd', $mvhd).self::box('trak', self::box('tkhd', $tkhd)));
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Годное видео файлом — ровно то, что кабинет принимает. */
|
||
|
|
public static function video(float $seconds = 10.0, int $width = 1280, int $height = 720): UploadedFile
|
||
|
|
{
|
||
|
|
return UploadedFile::fake()->createWithContent('ad.mp4', self::mp4Bytes($seconds, $width, $height));
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Годная картинка файлом. */
|
||
|
|
public static function kartinka(int $width = 1280, int $height = 720): UploadedFile
|
||
|
|
{
|
||
|
|
return UploadedFile::fake()->image('ad.jpg', $width, $height);
|
||
|
|
}
|
||
|
|
}
|