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,21 +13,22 @@
"doctrine/doctrine-bundle": "^2.2", "doctrine/doctrine-bundle": "^2.2",
"doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.8", "doctrine/orm": "^2.8",
"php-ffmpeg/php-ffmpeg": "^0.17.0",
"phpdocumentor/reflection-docblock": "^5.2", "phpdocumentor/reflection-docblock": "^5.2",
"ramsey/uuid-doctrine": "^1.6", "ramsey/uuid-doctrine": "^1.6",
"sensio/framework-extra-bundle": "^5.6", "sensio/framework-extra-bundle": "^5.6",
"symfony/asset": "5.2.*", "symfony/asset": "5.2.*",
"symfony/console": "5.2.*", "symfony/console": "5.2.*",
"symfony/dotenv": "5.2.*", "symfony/dotenv": "5.2.*",
"symfony/expression-language": "5.2.*", "symfony/expression-language": "5.2.*",
"symfony/flex": "^1.3.1", "symfony/flex": "^1.3.1",
"symfony/form": "5.2.*", "symfony/form": "5.2.*",
"symfony/framework-bundle": "5.2.*", "symfony/framework-bundle": "5.2.*",
"symfony/http-client": "5.2.*", "symfony/http-client": "5.2.*",
"symfony/intl": "5.2.*", "symfony/intl": "5.2.*",
"symfony/mailer": "5.2.*", "symfony/mailer": "5.2.*",
"symfony/mime": "5.2.*", "symfony/mime": "5.2.*",
"symfony/monolog-bundle": "^3.1", "symfony/monolog-bundle": "^3.1",
"symfony/notifier": "5.2.*", "symfony/notifier": "5.2.*",
"symfony/process": "5.2.*", "symfony/process": "5.2.*",
"symfony/property-access": "5.2.*", "symfony/property-access": "5.2.*",

465
composer.lock generated
View file

@ -4,8 +4,70 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7a63ec16e3e2daa0fbf514d4aedd88d5", "content-hash": "a08b90e42235f47d768d0ce3aba9a85e",
"packages": [ "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", "name": "amphp/amp",
"version": "v2.5.1", "version": "v2.5.1",
@ -853,16 +915,16 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/283a40c901101e66de7061bd359252c013dcc43c", "url": "https://api.github.com/repos/brick/math/zipball/283a40c901101e66de7061bd359252c013dcc43c",
"reference": "283a40c901101e66de7061bd359252c013dcc43c", "reference": "283a40c901101e66de7061bd359252c013dcc43c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"php": "^7.1|^8.0" "php": "^7.1|^8.0"
}, },
"require-dev": { "require-dev": {
"php-coveralls/php-coveralls": "^2.2", "php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^7.5.15|^8.5", "phpunit/phpunit": "^7.5.15|^8.5",
"vimeo/psalm": "^3.5" "vimeo/psalm": "^3.5"
}, },
"type": "library", "type": "library",
@ -948,14 +1010,14 @@
], ],
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"funding": [ "funding": [
{ {
"url": "https://packagist.com", "url": "https://packagist.com",
"type": "custom" "type": "custom"
}, },
{ {
"url": "https://github.com/composer", "url": "https://github.com/composer",
"type": "github" "type": "github"
}, },
{ {
"url": "https://tidelift.com/funding/github/packagist/composer/composer", "url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift" "type": "tidelift"
@ -1014,16 +1076,16 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/annotations/zipball/ce77a7ba1770462cd705a91a151b6c3746f9c6ad", "url": "https://api.github.com/repos/doctrine/annotations/zipball/ce77a7ba1770462cd705a91a151b6c3746f9c6ad",
"reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad", "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/lexer": "1.*", "doctrine/lexer": "1.*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"php": "^7.1 || ^8.0" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/cache": "1.*", "doctrine/cache": "1.*",
"doctrine/coding-standard": "^6.0 || ^8.1", "doctrine/coding-standard": "^6.0 || ^8.1",
"phpstan/phpstan": "^0.12.20", "phpstan/phpstan": "^0.12.20",
"phpunit/phpunit": "^7.5 || ^9.1.5" "phpunit/phpunit": "^7.5 || ^9.1.5"
@ -2319,25 +2381,68 @@
"validation", "validation",
"validator" "validator"
], ],
"funding": [ "funding": [
{ {
"url": "https://github.com/egulias", "url": "https://github.com/egulias",
"type": "github" "type": "github"
} }
], ],
"time": "2020-12-29T14:50:06+00:00" "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": "friendsofphp/proxy-manager-lts", "name": "Igor Wiedler",
"version": "v1.0.2", "email": "igor@wiedler.ch"
"source": { }
"type": "git", ],
"url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", "description": "Événement is a very simple event dispatching library for PHP",
"reference": "4a66e4e0d3279d3bb3722963b4294331fabe15bc" "keywords": [
}, "event-dispatcher",
"dist": { "event-emitter"
"type": "zip", ],
"url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/4a66e4e0d3279d3bb3722963b4294331fabe15bc", "time": "2017-07-23T21:35:13+00:00"
},
{
"name": "friendsofphp/proxy-manager-lts",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git",
"reference": "4a66e4e0d3279d3bb3722963b4294331fabe15bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/4a66e4e0d3279d3bb3722963b4294331fabe15bc",
"reference": "4a66e4e0d3279d3bb3722963b4294331fabe15bc", "reference": "4a66e4e0d3279d3bb3722963b4294331fabe15bc",
"shasum": "" "shasum": ""
}, },
@ -2394,17 +2499,17 @@
"service proxies" "service proxies"
], ],
"funding": [ "funding": [
{ {
"url": "https://github.com/Ocramius", "url": "https://github.com/Ocramius",
"type": "github" "type": "github"
}, },
{ {
"url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-01-04T11:21:26+00:00" "time": "2021-01-04T11:21:26+00:00"
}, },
{ {
"name": "kelunik/certificate", "name": "kelunik/certificate",
"version": "v1.1.2", "version": "v1.1.2",
@ -2465,16 +2570,16 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-code/zipball/28a6d70ea8b8bca687d7163300e611ae33baf82a", "url": "https://api.github.com/repos/laminas/laminas-code/zipball/28a6d70ea8b8bca687d7163300e611ae33baf82a",
"reference": "28a6d70ea8b8bca687d7163300e611ae33baf82a", "reference": "28a6d70ea8b8bca687d7163300e611ae33baf82a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"laminas/laminas-eventmanager": "^3.3", "laminas/laminas-eventmanager": "^3.3",
"php": "^7.4 || ~8.0.0" "php": "^7.4 || ~8.0.0"
}, },
"conflict": { "conflict": {
"phpspec/prophecy": "<1.9.0" "phpspec/prophecy": "<1.9.0"
}, },
"replace": { "replace": {
"zendframework/zend-code": "self.version" "zendframework/zend-code": "self.version"
}, },
@ -2617,16 +2722,16 @@
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"BSD-3-Clause" "BSD-3-Clause"
], ],
"description": "Alias legacy ZF class names to Laminas Project equivalents.", "description": "Alias legacy ZF class names to Laminas Project equivalents.",
"keywords": [ "keywords": [
"ZendFramework", "ZendFramework",
"autoloading", "autoloading",
"laminas", "laminas",
"zf" "zf"
], ],
"funding": [ "funding": [
{ {
"url": "https://funding.communitybridge.org/projects/laminas-project", "url": "https://funding.communitybridge.org/projects/laminas-project",
@ -2863,16 +2968,16 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2", "php": ">=7.2",
"psr/log": "^1.0.1" "psr/log": "^1.0.1"
}, },
"provide": { "provide": {
"psr/log-implementation": "1.0.0" "psr/log-implementation": "1.0.0"
}, },
"require-dev": { "require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0", "aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev", "doctrine/couchdb": "~1.0@dev",
@ -2935,26 +3040,150 @@
"funding": [ "funding": [
{ {
"url": "https://github.com/Seldaek", "url": "https://github.com/Seldaek",
"type": "github" "type": "github"
}, },
{ {
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog", "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-14T13:15:25+00:00" "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": "phpdocumentor/reflection-common", "name": "Phraseanet Team",
"version": "2.2.0", "email": "info@alchemy.fr",
"source": { "homepage": "http://www.phraseanet.com/"
"type": "git", },
"url": "https://github.com/phpDocumentor/ReflectionCommon.git", {
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" "name": "Patrik Karisch",
}, "email": "patrik@karisch.guru",
"dist": { "homepage": "http://www.karisch.guru"
"type": "zip", },
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", {
"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",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"shasum": "" "shasum": ""
}, },
@ -3213,16 +3442,16 @@
"Psr\\EventDispatcher\\": "src/" "Psr\\EventDispatcher\\": "src/"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"authors": [ "authors": [
{ {
"name": "PHP-FIG", "name": "PHP-FIG",
"homepage": "http://www.php-fig.org/" "homepage": "http://www.php-fig.org/"
} }
], ],
"description": "Standard interfaces for event handling.", "description": "Standard interfaces for event handling.",
"keywords": [ "keywords": [
"events", "events",
@ -3292,16 +3521,16 @@
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/link/zipball/eea8e8662d5cd3ae4517c9b864493f59fca95562", "url": "https://api.github.com/repos/php-fig/link/zipball/eea8e8662d5cd3ae4517c9b864493f59fca95562",
"reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562", "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.0" "php": ">=5.3.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0.x-dev" "dev-master": "1.0.x-dev"
} }
}, },
"autoload": { "autoload": {

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; namespace App\Command;
use App\Entity\Video; use App\Service\TranscodingService;
use App\Service\VideoService; use App\Service\VideoService;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@ -14,12 +14,17 @@ class TranscodeCommand extends Command
protected static $defaultName = "app:start-transcode"; protected static $defaultName = "app:start-transcode";
private $videoService; 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); parent::__construct($name);
$this->videoService = $videoService; $this->videoService = $videoService;
$this->transcodingService = $transcodingService;
} }
protected function configure() protected function configure()
@ -40,32 +45,6 @@ class TranscodeCommand extends Command
return $process->isSuccessful(); 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 protected function execute(InputInterface $input, OutputInterface $output): int
{ {
while (true) { while (true) {
@ -73,11 +52,11 @@ class TranscodeCommand extends Command
$videos = $this->videoService->getVideosForTranscode(); $videos = $this->videoService->getVideosForTranscode();
foreach ($videos as $video) { 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; private const IS_OWNER = 2;
public const OWNER_LINK_ID = "owner"; 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 PLAYLIST_MIME_TYPE = "application/x-mpegURL";
private const TS_FILE_MIME_TYPE = "video/MP2T"; private const TS_FILE_MIME_TYPE = "video/MP2T";
private const THUMBNAIL_MIME_TYPE = "image/png"; 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 $userService;
private $videoService; private $videoService;
@ -97,7 +98,7 @@ class WatchController extends AbstractController
{ {
$data = $this->checkRequestData($videoId, $linkId); $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 = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE); $response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE);
@ -112,7 +113,7 @@ class WatchController extends AbstractController
{ {
$data = $this->checkRequestData($videoId, $linkId); $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 = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE); $response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE);
@ -127,7 +128,7 @@ class WatchController extends AbstractController
{ {
$data = $this->checkRequestData($videoId, $linkId); $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 = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::TS_FILE_MIME_TYPE); $response->headers->set("Content-Type", self::TS_FILE_MIME_TYPE);
@ -142,7 +143,7 @@ class WatchController extends AbstractController
{ {
$data = $this->checkRequestData($videoId, $linkId); $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 = new BinaryFileResponse($file);
$response->headers->set("Content-Type", self::THUMBNAIL_MIME_TYPE); $response->headers->set("Content-Type", self::THUMBNAIL_MIME_TYPE);

View file

@ -15,10 +15,11 @@ use Ramsey\Uuid\UuidInterface;
*/ */
class Video class Video
{ {
public const WAITING = 1; public const QUEUED = 1;
public const PROCESSING_THUMBNAIL = 2; public const PROCESSING_META = 2;
public const PROCESSING_TRANSCODE = 3; public const PROCESSING_THUMBNAIL = 3;
public const DONE = 4; public const PROCESSING_TRANSCODE = 4;
public const DONE = 5;
public const FAIL = -1; public const FAIL = -1;
/** /**
@ -59,13 +60,23 @@ class Video
/** /**
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
*/ */
private $state = self::WAITING; private $state = self::QUEUED;
/** /**
* @ORM\OneToMany(targetEntity=VideoLink::class, mappedBy="video") * @ORM\OneToMany(targetEntity=VideoLink::class, mappedBy="video")
*/ */
private $videoLinks; private $videoLinks;
/**
* @ORM\Column(type="float", nullable=true)
*/
private $length;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $transcodingProgress;
public function __construct() public function __construct()
{ {
$this->videoLinks = new ArrayCollection(); $this->videoLinks = new ArrayCollection();
@ -149,12 +160,14 @@ class Video
public function getStateString(): string public function getStateString(): string
{ {
switch ($this->state) { switch ($this->state) {
case self::WAITING: case self::QUEUED:
return "waiting"; return "queued";
case self::PROCESSING_META:
return "processing...";
case self::PROCESSING_THUMBNAIL: case self::PROCESSING_THUMBNAIL:
return "thumbnail"; return "creating thumbnail...";
case self::PROCESSING_TRANSCODE: case self::PROCESSING_TRANSCODE:
return "transcoding"; return "transcoding...";
case self::DONE: case self::DONE:
return "done"; return "done";
case self::FAIL: case self::FAIL:
@ -204,4 +217,28 @@ class Video
$this->customId = $customId; $this->customId = $customId;
return $this; 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 class VideoService
{ {
private const LANDINGZONE_DIRECTORY = "../landingzone/"; public const LANDINGZONE_RELATIVE = "../";
public const LANDINGZONE_DIRECTORY = "landingzone/";
public const LANDINGZONE_EXTENTION = ".vid";
private $videoRepository; private $videoRepository;
private $userService; private $userService;
@ -33,17 +35,16 @@ class VideoService
$video->setUploader($this->userService->getLoggedInUser()); $video->setUploader($this->userService->getLoggedInUser());
$this->videoRepository->save($video); $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 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(); $this->videoRepository->update();
} }

View file

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