transcoding is now handled in php; added video duration

This commit is contained in:
overflowerror 2021-01-07 18:53:55 +01:00
parent 78b3b6be2b
commit 07f9827ed2
9 changed files with 686 additions and 180 deletions

View file

@ -13,6 +13,7 @@
"doctrine/doctrine-bundle": "^2.2",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.8",
"php-ffmpeg/php-ffmpeg": "^0.17.0",
"phpdocumentor/reflection-docblock": "^5.2",
"ramsey/uuid-doctrine": "^1.6",
"sensio/framework-extra-bundle": "^5.6",

231
composer.lock generated
View file

@ -4,8 +4,70 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7a63ec16e3e2daa0fbf514d4aedd88d5",
"content-hash": "a08b90e42235f47d768d0ce3aba9a85e",
"packages": [
{
"name": "alchemy/binary-driver",
"version": "v5.2.0",
"source": {
"type": "git",
"url": "https://github.com/alchemy-fr/BinaryDriver.git",
"reference": "e0615cdff315e6b4b05ada67906df6262a020d22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/alchemy-fr/BinaryDriver/zipball/e0615cdff315e6b4b05ada67906df6262a020d22",
"reference": "e0615cdff315e6b4b05ada67906df6262a020d22",
"shasum": ""
},
"require": {
"evenement/evenement": "^3.0|^2.0|^1.0",
"php": ">=5.5",
"psr/log": "^1.0",
"symfony/process": "^2.3|^3.0|^4.0|^5.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0|^5.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Alchemy": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Le Goff",
"email": "legoff.n@gmail.com"
},
{
"name": "Romain Neutron",
"email": "imprec@gmail.com",
"homepage": "http://www.lickmychip.com/"
},
{
"name": "Phraseanet Team",
"email": "info@alchemy.fr",
"homepage": "http://www.phraseanet.com/"
},
{
"name": "Jens Hausdorf",
"email": "mail@jens-hausdorf.de",
"homepage": "https://jens-hausdorf.de",
"role": "Maintainer"
}
],
"description": "A set of tools to build binary drivers",
"keywords": [
"binary",
"driver"
],
"time": "2020-02-12T19:35:11+00:00"
},
{
"name": "amphp/amp",
"version": "v2.5.1",
@ -2327,6 +2389,49 @@
],
"time": "2020-12-29T14:50:06+00:00"
},
{
"name": "evenement/evenement",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/igorw/evenement.git",
"reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
"reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Evenement": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
}
],
"description": "Événement is a very simple event dispatching library for PHP",
"keywords": [
"event-dispatcher",
"event-emitter"
],
"time": "2017-07-23T21:35:13+00:00"
},
{
"name": "friendsofphp/proxy-manager-lts",
"version": "v1.0.2",
@ -2944,6 +3049,130 @@
],
"time": "2020-12-14T13:15:25+00:00"
},
{
"name": "neutron/temporary-filesystem",
"version": "3.0",
"source": {
"type": "git",
"url": "https://github.com/romainneutron/Temporary-Filesystem.git",
"reference": "60e79adfd16f42f4b888e351ad49f9dcb959e3c2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/romainneutron/Temporary-Filesystem/zipball/60e79adfd16f42f4b888e351ad49f9dcb959e3c2",
"reference": "60e79adfd16f42f4b888e351ad49f9dcb959e3c2",
"shasum": ""
},
"require": {
"php": ">=5.6",
"symfony/filesystem": "^2.3 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^5.0.4"
},
"type": "library",
"autoload": {
"psr-0": {
"Neutron": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Romain Neutron",
"email": "imprec@gmail.com"
}
],
"description": "Symfony filesystem extension to handle temporary files",
"time": "2020-07-27T14:00:33+00:00"
},
{
"name": "php-ffmpeg/php-ffmpeg",
"version": "v0.17.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git",
"reference": "a5147d1ae041e78e7870bf2443d4e2dfa7635856"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/a5147d1ae041e78e7870bf2443d4e2dfa7635856",
"reference": "a5147d1ae041e78e7870bf2443d4e2dfa7635856",
"shasum": ""
},
"require": {
"alchemy/binary-driver": "^1.5 || ~2.0.0 || ^5.0",
"doctrine/cache": "^1.0",
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"neutron/temporary-filesystem": "^2.1.1 || ^3.0",
"php": ">=5.3.9"
},
"require-dev": {
"silex/silex": "~1.0",
"symfony/phpunit-bridge": "^5.0.4",
"symfony/process": "2.8 || 3.3"
},
"suggest": {
"php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.x-dev"
}
},
"autoload": {
"psr-0": {
"FFMpeg": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Romain Neutron",
"email": "imprec@gmail.com",
"homepage": "http://www.lickmychip.com/"
},
{
"name": "Phraseanet Team",
"email": "info@alchemy.fr",
"homepage": "http://www.phraseanet.com/"
},
{
"name": "Patrik Karisch",
"email": "patrik@karisch.guru",
"homepage": "http://www.karisch.guru"
},
{
"name": "Romain Biard",
"email": "romain.biard@gmail.com",
"homepage": "https://www.strime.io/"
},
{
"name": "Jens Hausdorf",
"email": "hello@jens-hausdorf.de",
"homepage": "https://jens-hausdorf.de"
}
],
"description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg",
"keywords": [
"audio",
"audio processing",
"avconv",
"avprobe",
"ffmpeg",
"ffprobe",
"video",
"video processing"
],
"time": "2020-12-18T14:31:34+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",

View file

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210107174149 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX UNIQ_8D93D6495E237E06');
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, password, name, roles FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, password VARCHAR(255) NOT NULL COLLATE BINARY, name VARCHAR(180) NOT NULL COLLATE BINARY, roles CLOB NOT NULL COLLATE BINARY --(DC2Type:json)
, PRIMARY KEY(id))');
$this->addSql('INSERT INTO user (id, password, name, roles) SELECT id, password, name, roles FROM __temp__user');
$this->addSql('DROP TABLE __temp__user');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D6495E237E06 ON user (name)');
$this->addSql('DROP INDEX IDX_7CC7DA2C16678C77');
$this->addSql('CREATE TEMPORARY TABLE __temp__video AS SELECT id, uploader_id, uploaded, name, description, tags, state FROM video');
$this->addSql('DROP TABLE video');
$this->addSql('CREATE TABLE video (id BLOB NOT NULL, uploader_id BLOB NOT NULL, uploaded DATETIME NOT NULL --(DC2Type:datetime_immutable)
, name VARCHAR(255) NOT NULL COLLATE BINARY, description VARCHAR(1024) NOT NULL COLLATE BINARY, tags CLOB NOT NULL COLLATE BINARY --(DC2Type:array)
, state INTEGER NOT NULL, length DOUBLE PRECISION DEFAULT NULL, transcoding_progress INTEGER DEFAULT NULL, PRIMARY KEY(id), CONSTRAINT FK_7CC7DA2C16678C77 FOREIGN KEY (uploader_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO video (id, uploader_id, uploaded, name, description, tags, state) SELECT id, uploader_id, uploaded, name, description, tags, state FROM __temp__video');
$this->addSql('DROP TABLE __temp__video');
$this->addSql('CREATE INDEX IDX_7CC7DA2C16678C77 ON video (uploader_id)');
$this->addSql('DROP INDEX IDX_313BC42D29C1004E');
$this->addSql('DROP INDEX IDX_313BC42D61220EA6');
$this->addSql('CREATE TEMPORARY TABLE __temp__video_link AS SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM video_link');
$this->addSql('DROP TABLE video_link');
$this->addSql('CREATE TABLE video_link (id BLOB NOT NULL, video_id BLOB NOT NULL, creator_id BLOB NOT NULL, created DATETIME NOT NULL --(DC2Type:datetime_immutable)
, max_views INTEGER DEFAULT NULL, viewable_for INTEGER DEFAULT NULL, viewable_until DATETIME DEFAULT NULL, comment VARCHAR(1024) DEFAULT NULL COLLATE BINARY, PRIMARY KEY(id), CONSTRAINT FK_313BC42D29C1004E FOREIGN KEY (video_id) REFERENCES video (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_313BC42D61220EA6 FOREIGN KEY (creator_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO video_link (id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment) SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM __temp__video_link');
$this->addSql('DROP TABLE __temp__video_link');
$this->addSql('CREATE INDEX IDX_313BC42D29C1004E ON video_link (video_id)');
$this->addSql('CREATE INDEX IDX_313BC42D61220EA6 ON video_link (creator_id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX UNIQ_8D93D6495E237E06');
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, name, roles, password FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, name VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO user (id, name, roles, password) SELECT id, name, roles, password FROM __temp__user');
$this->addSql('DROP TABLE __temp__user');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D6495E237E06 ON user (name)');
$this->addSql('DROP INDEX IDX_7CC7DA2C16678C77');
$this->addSql('CREATE TEMPORARY TABLE __temp__video AS SELECT id, uploader_id, uploaded, name, description, tags, state FROM video');
$this->addSql('DROP TABLE video');
$this->addSql('CREATE TABLE video (id BLOB NOT NULL, uploaded DATETIME NOT NULL --(DC2Type:datetime_immutable)
, name VARCHAR(255) NOT NULL, description VARCHAR(1024) NOT NULL, tags CLOB NOT NULL --(DC2Type:array)
, state INTEGER NOT NULL, uploader_id BLOB NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO video (id, uploader_id, uploaded, name, description, tags, state) SELECT id, uploader_id, uploaded, name, description, tags, state FROM __temp__video');
$this->addSql('DROP TABLE __temp__video');
$this->addSql('CREATE INDEX IDX_7CC7DA2C16678C77 ON video (uploader_id)');
$this->addSql('DROP INDEX IDX_313BC42D29C1004E');
$this->addSql('DROP INDEX IDX_313BC42D61220EA6');
$this->addSql('CREATE TEMPORARY TABLE __temp__video_link AS SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM video_link');
$this->addSql('DROP TABLE video_link');
$this->addSql('CREATE TABLE video_link (id BLOB NOT NULL, created DATETIME NOT NULL --(DC2Type:datetime_immutable)
, max_views INTEGER DEFAULT NULL, viewable_for INTEGER DEFAULT NULL, viewable_until DATETIME DEFAULT NULL, comment VARCHAR(1024) DEFAULT NULL, video_id BLOB NOT NULL, creator_id BLOB NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO video_link (id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment) SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM __temp__video_link');
$this->addSql('DROP TABLE __temp__video_link');
$this->addSql('CREATE INDEX IDX_313BC42D29C1004E ON video_link (video_id)');
$this->addSql('CREATE INDEX IDX_313BC42D61220EA6 ON video_link (creator_id)');
}
}

View file

@ -2,7 +2,7 @@
namespace App\Command;
use App\Entity\Video;
use App\Service\TranscodingService;
use App\Service\VideoService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@ -14,12 +14,17 @@ class TranscodeCommand extends Command
protected static $defaultName = "app:start-transcode";
private $videoService;
private $transcodingService;
public function __construct(VideoService $videoService, string $name = null)
public function __construct(
VideoService $videoService,
TranscodingService $transcodingService,
string $name = null
)
{
parent::__construct($name);
$this->videoService = $videoService;
$this->transcodingService = $transcodingService;
}
protected function configure()
@ -40,32 +45,6 @@ class TranscodeCommand extends Command
return $process->isSuccessful();
}
private function handleVideo(Video $video, OutputInterface $output)
{
$output->writeln("starting creation of thumbnail...");
$this->videoService->setVideoState($video, Video::PROCESSING_THUMBNAIL);
if ($this->callScript("thumbnail.sh", [$video->getId()->toString()])) {
$output->writeln("thumbnail creation successful");
} else {
$output->writeln("thumbnail creation failed");
$this->videoService->setVideoState($video, Video::FAIL);
return;
}
$output->writeln("starting transcoding...");
$this->videoService->setVideoState($video, Video::PROCESSING_TRANSCODE);
if ($this->callScript("transcode.sh", [$video->getId()->toString()])) {
$output->writeln("transcoding successful");
} else {
$output->writeln("transcoding failed");
$this->videoService->setVideoState($video, Video::FAIL);
return;
}
$this->videoService->setVideoState($video, Video::DONE);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
while (true) {
@ -73,11 +52,11 @@ class TranscodeCommand extends Command
$videos = $this->videoService->getVideosForTranscode();
foreach ($videos as $video) {
$output->writeln("New video: " . $video->getName() . ", " . $video->getUploader()->getName());
$output->writeln("new: " . $video->getName() . ", " . $video->getUploader()->getName());
$this->handleVideo($video, $output);
$this->transcodingService->doTranscode($video, $output);
$output->writeln("Done");
$output->writeln("done");
}
}
}

View file

@ -22,13 +22,14 @@ class WatchController extends AbstractController
private const IS_OWNER = 2;
public const OWNER_LINK_ID = "owner";
public const CONTENT_DIRECTORY = "../content/";
public const CONTENT_RELATIVE = "../";
public const CONTENT_DIRECTORY = "content/";
private const PLAYLIST_MIME_TYPE = "application/x-mpegURL";
private const TS_FILE_MIME_TYPE = "video/MP2T";
private const THUMBNAIL_MIME_TYPE = "image/png";
private const TS_FILE_FORMAT = "seg-%06d-ts";
public const TS_FILE_FORMAT = "seg-%06d-ts";
private $userService;
private $videoService;
@ -97,7 +98,7 @@ class WatchController extends AbstractController
{
$data = $this->checkRequestData($videoId, $linkId);
$file = self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . "playlist.m3u8";
$file = self::CONTENT_RELATIVE . self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . "playlist.m3u8";
$response = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE);
@ -112,7 +113,7 @@ class WatchController extends AbstractController
{
$data = $this->checkRequestData($videoId, $linkId);
$file = self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . $quality . "p/" . "playlist.m3u8";
$file = self::CONTENT_RELATIVE . self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . $quality . "p/" . "playlist.m3u8";
$response = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE);
@ -127,7 +128,7 @@ class WatchController extends AbstractController
{
$data = $this->checkRequestData($videoId, $linkId);
$file = self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . $quality . "p/" . sprintf(self::TS_FILE_FORMAT, $tsFileId);
$file = self::CONTENT_RELATIVE . self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . $quality . "p/" . sprintf(self::TS_FILE_FORMAT, $tsFileId);
$response = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::TS_FILE_MIME_TYPE);
@ -142,7 +143,7 @@ class WatchController extends AbstractController
{
$data = $this->checkRequestData($videoId, $linkId);
$file = self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . "thumb.png";
$file = self::CONTENT_RELATIVE . self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . "thumb.png";
$response = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::THUMBNAIL_MIME_TYPE);

View file

@ -15,10 +15,11 @@ use Ramsey\Uuid\UuidInterface;
*/
class Video
{
public const WAITING = 1;
public const PROCESSING_THUMBNAIL = 2;
public const PROCESSING_TRANSCODE = 3;
public const DONE = 4;
public const QUEUED = 1;
public const PROCESSING_META = 2;
public const PROCESSING_THUMBNAIL = 3;
public const PROCESSING_TRANSCODE = 4;
public const DONE = 5;
public const FAIL = -1;
/**
@ -59,13 +60,23 @@ class Video
/**
* @ORM\Column(type="integer")
*/
private $state = self::WAITING;
private $state = self::QUEUED;
/**
* @ORM\OneToMany(targetEntity=VideoLink::class, mappedBy="video")
*/
private $videoLinks;
/**
* @ORM\Column(type="float", nullable=true)
*/
private $length;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $transcodingProgress;
public function __construct()
{
$this->videoLinks = new ArrayCollection();
@ -149,12 +160,14 @@ class Video
public function getStateString(): string
{
switch ($this->state) {
case self::WAITING:
return "waiting";
case self::QUEUED:
return "queued";
case self::PROCESSING_META:
return "processing...";
case self::PROCESSING_THUMBNAIL:
return "thumbnail";
return "creating thumbnail...";
case self::PROCESSING_TRANSCODE:
return "transcoding";
return "transcoding...";
case self::DONE:
return "done";
case self::FAIL:
@ -204,4 +217,28 @@ class Video
$this->customId = $customId;
return $this;
}
public function getLength(): ?float
{
return $this->length;
}
public function setLength(?float $length): self
{
$this->length = $length;
return $this;
}
public function getTranscodingProgress(): ?int
{
return $this->transcodingProgress;
}
public function setTranscodingProgress(?int $transcodingProgress): self
{
$this->transcodingProgress = $transcodingProgress;
return $this;
}
}

View file

@ -0,0 +1,163 @@
<?php
namespace App\Service;
use App\Controller\WatchController;
use App\Entity\Video;
use FFMpeg\Coordinate\Dimension;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFMpeg;
use FFMpeg\FFProbe;
use FFMpeg\Filters\Video\ResizeFilter;
use FFMpeg\Format\Video\X264;
use Symfony\Component\Console\Output\OutputInterface;
class TranscodingService
{
private const QUALITY = [
[
"height" => 1080,
"crf" => 23,
"playlistResolution" => "1920x1080",
"playlistBandwidth" => "800000",
],
[
"height" => 720,
"crf" => 23,
"playlistResolution" => "1280x720",
"playlistBandwidth" => "1400000",
],
[
"height" => 480,
"crf" => 23,
"playlistResolution" => "842x480",
"playlistBandwidth" => "2800000",
],
[
"height" => 360,
"crf" => 23,
"playlistResolution" => "640x360",
"playlistBandwidth" => "5000000",
]
];
private $ffmpeg;
private $ffprobe;
private $videoService;
public function __construct(VideoService $videoService)
{
$this->ffmpeg = FFMpeg::create();
$this->ffprobe = FFProbe::create();
$this->videoService = $videoService;
}
private function rawPath($id): string
{
return VideoService::LANDINGZONE_DIRECTORY . $id . VideoService::LANDINGZONE_EXTENTION;
}
private function getLength($id): float
{
return $this->ffprobe->format($this->rawPath($id))->get("duration");
}
private function contentDir($id): string
{
return WatchController::CONTENT_DIRECTORY . $id . "/";
}
private function createDirectories($id)
{
$dir = $this->contentDir($id);
mkdir($dir);
mkdir($dir . "360p");
mkdir($dir . "480p");
mkdir($dir . "720p");
mkdir($dir . "1080p");
}
private function createThumbnail($id)
{
$video = $this->ffmpeg->open($this->rawPath($id));
$video->filters()->custom("thumbnail,scale=640:360");
$video->frame(TimeCode::fromSeconds(1))->save($this->contentDir($id) . "thumb.png");
}
private function transcode(Video $video)
{
$height = $this->ffprobe->streams($this->rawPath($video->getId()))->videos()->first()->getDimensions()->getHeight();
$countQuality = count(self::QUALITY);
$total = $countQuality;
$i = 0;
foreach (self::QUALITY as $quality) {
if ($quality["height"] > $height) {
$total--;
}
$ffvideo = $this->ffmpeg->open($this->rawPath($video->getId()));
$ffvideo->filters()->resize(new Dimension(1, $quality["height"]), ResizeFilter::RESIZEMODE_SCALE_WIDTH)->synchronize();
$format = new X264("aac");
$format->setAdditionalParameters([
"-crf", $quality["crf"],
"-hls_segment_filename", $this->contentDir($video->getId()) . $quality["height"] . "p/" . WatchController::TS_FILE_FORMAT,
"-hls_playlist_type", "vod",
"-keyint_min", "48",
"-g", "48",
"-sc_threshold", "0",
]);
$format->on('progress', function ($v, $f, $percentage) use ($i, $total, $video) {
$percentage = (($i) * 100.0 + $percentage) / ($total);
$video->setTranscodingProgress($percentage);
$this->videoService->update($video);
});
$ffvideo->save($format, $this->contentDir($video->getId()) . $quality["height"] . "p/playlist.m3u8");
$i++;
}
$globalPlaylist = "#EXTM3U\n";
$globalPlaylist .= "#EXT-X-VERSION:3\n";
for ($i = $countQuality - $total; $i < $countQuality; $i++) {
$quality = self::QUALITY[$i];
$globalPlaylist .= "#EXT-X-STREAM-INF:BANDWIDTH=" . $quality["playlistBandwidth"] . ",RESOLUTION=" . $quality["playlistResolution"] . "\n";
$globalPlaylist .= $quality["height"] . "/playlist\n";
}
file_put_contents($this->contentDir($video->getId()) . "playlist.m3u8", $globalPlaylist);
}
public function doTranscode(Video $video, OutputInterface $output)
{
$video->setTranscodingProgress(0);
$output->writeln(" meta");
$video->setState(Video::PROCESSING_META);
$this->videoService->update($video);
$video->setLength($this->getLength($video->getId()));
$this->createDirectories($video->getId());
$output->writeln(" thumbnail");
$video->setState(Video::PROCESSING_THUMBNAIL);
$this->videoService->update($video);
$this->createThumbnail($video->getId());
$output->writeln(" transcode");
$video->setState(Video::PROCESSING_TRANSCODE);
$this->videoService->update($video);
$this->transcode($video);
$video->setState(Video::DONE);
$this->videoService->update($video);
}
}

View file

@ -11,7 +11,9 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
class VideoService
{
private const LANDINGZONE_DIRECTORY = "../landingzone/";
public const LANDINGZONE_RELATIVE = "../";
public const LANDINGZONE_DIRECTORY = "landingzone/";
public const LANDINGZONE_EXTENTION = ".vid";
private $videoRepository;
private $userService;
@ -33,17 +35,16 @@ class VideoService
$video->setUploader($this->userService->getLoggedInUser());
$this->videoRepository->save($video);
$file->move(self::LANDINGZONE_DIRECTORY, $video->getId()->toString() . ".vid");
$file->move(self::LANDINGZONE_RELATIVE . self::LANDINGZONE_DIRECTORY, $video->getId()->toString() . self::LANDINGZONE_EXTENTION);
}
public function getVideosForTranscode(): array
{
return $this->videoRepository->findByState(Video::WAITING);
return $this->videoRepository->findByState(Video::QUEUED);
}
public function setVideoState(Video $video, $state)
public function update(Video $video)
{
$video->setState($state);
$this->videoRepository->update();
}

View file

@ -1,4 +1,7 @@
{
"alchemy/binary-driver": {
"version": "v5.2.0"
},
"amphp/amp": {
"version": "v2.5.1"
},
@ -141,6 +144,9 @@
"egulias/email-validator": {
"version": "2.1.25"
},
"evenement/evenement": {
"version": "v3.0.1"
},
"friendsofphp/proxy-manager-lts": {
"version": "v1.0.2"
},
@ -168,12 +174,18 @@
"monolog/monolog": {
"version": "2.2.0"
},
"neutron/temporary-filesystem": {
"version": "3.0"
},
"nikic/php-parser": {
"version": "v4.10.4"
},
"php": {
"version": "7.4"
},
"php-ffmpeg/php-ffmpeg": {
"version": "v0.17.0"
},
"phpdocumentor/reflection-common": {
"version": "2.2.0"
},