Initial first working version
This commit is contained in:
parent
0d4dd5181e
commit
86a7dbef19
10 changed files with 3601 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/vendor/
|
27
composer.json
Normal file
27
composer.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "danielsiepmann/videocutting",
|
||||||
|
"description": "Allows to cut videos using ffmpeg.",
|
||||||
|
"type": "project",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Daniel Siepmann",
|
||||||
|
"email": "coding@daniel-siepmann.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"DanielSiepmann\\Videcutting\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "7.3.*",
|
||||||
|
"symfony/console": "^5.1",
|
||||||
|
"symfony/process": "^5.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.3",
|
||||||
|
"phpspec/prophecy": "^1.12",
|
||||||
|
"phpspec/prophecy-phpunit": "^2.0"
|
||||||
|
}
|
||||||
|
}
|
2785
composer.lock
generated
Normal file
2785
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
21
cutvideo
Executable file
21
cutvideo
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
use DanielSiepmann\Videcutting\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\SingleCommandApplication;
|
||||||
|
|
||||||
|
(new SingleCommandApplication())
|
||||||
|
->setName('Cutvideo')
|
||||||
|
->setHelp('Allows to cut a given video multiple times. Uses ffmpeg under the hood.')
|
||||||
|
->setVersion('0.1.0')
|
||||||
|
->addArgument('file', InputArgument::REQUIRED, 'The video file to cut.')
|
||||||
|
->addArgument('start', InputArgument::REQUIRED, 'The starting point of the video')
|
||||||
|
->addArgument('end', InputArgument::REQUIRED, 'The end point of the video')
|
||||||
|
->addOption('ad', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Start and end of ads, separate start and end via "/".', [])
|
||||||
|
->setCode(new Command())
|
||||||
|
->run();
|
30
phpunit.xml.dist
Normal file
30
phpunit.xml.dist
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<phpunit
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||||
|
backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
forceCoversAnnotation="false"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnError="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
stopOnIncomplete="false"
|
||||||
|
stopOnSkipped="false"
|
||||||
|
verbose="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="unit">
|
||||||
|
<directory>tests/Unit/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<coverage>
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">src</directory>
|
||||||
|
</include>
|
||||||
|
</coverage>
|
||||||
|
</phpunit>
|
102
src/Command.php
Normal file
102
src/Command.php
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DanielSiepmann\Videcutting;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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 2
|
||||||
|
* 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, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SymfonyStyle
|
||||||
|
*/
|
||||||
|
private $io;
|
||||||
|
|
||||||
|
public function __invoke(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$this->io = new SymfonyStyle($input, $output);
|
||||||
|
$videoInfo = new VideoInfo(
|
||||||
|
$input->getArgument('file'),
|
||||||
|
$input->getArgument('start'),
|
||||||
|
$input->getArgument('end')
|
||||||
|
// $input->getOption('ad')
|
||||||
|
);
|
||||||
|
|
||||||
|
$cutting = new Cutting($videoInfo);
|
||||||
|
|
||||||
|
$this->io->title('Generating temp cutted video files.');
|
||||||
|
foreach ($cutting->getCommandsForCutting() as $command) {
|
||||||
|
$this->runCommand($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->io->title('Generating Metadata');
|
||||||
|
$this->runCommand($cutting->getCommandForGeneratingMetadata());
|
||||||
|
|
||||||
|
$this->io->title('Generating Concat input file');
|
||||||
|
$this->runCommand($cutting->getCommandForGeneratingConcatInputFile());
|
||||||
|
|
||||||
|
$this->io->title('Creating target folder');
|
||||||
|
$this->createTargetFolder($videoInfo);
|
||||||
|
|
||||||
|
$this->io->title('Generating final video');
|
||||||
|
$this->runCommand($cutting->getCommandForGeneratingVideo());
|
||||||
|
|
||||||
|
$this->io->title('Cleanup');
|
||||||
|
$this->runCommand($cutting->getCommandForCleanup());
|
||||||
|
|
||||||
|
$this->io->success(sprintf(
|
||||||
|
'Generated video file "%s".',
|
||||||
|
$videoInfo->getTargetFilePath()
|
||||||
|
));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runCommand(Process $command): void
|
||||||
|
{
|
||||||
|
if ($this->io->isVeryVerbose()) {
|
||||||
|
$this->io->comment('Executing command');
|
||||||
|
$this->io->comment($command->getCommandLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
$command->run(function ($type, $buffer) {
|
||||||
|
$this->io->writeln($buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!$command->isSuccessful()) {
|
||||||
|
throw new ProcessFailedException($command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createTargetFolder(VideoInfo $videoInfo): void
|
||||||
|
{
|
||||||
|
$target = dirname($videoInfo->getTargetFilePath());
|
||||||
|
if (file_exists($target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir($target, 0775, true);
|
||||||
|
}
|
||||||
|
}
|
144
src/Cutting.php
Normal file
144
src/Cutting.php
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DanielSiepmann\Videcutting;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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 2
|
||||||
|
* 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, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class Cutting
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var VideoInfo
|
||||||
|
*/
|
||||||
|
private $video;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $tempFilenames = [];
|
||||||
|
|
||||||
|
public function __construct(VideoInfo $video)
|
||||||
|
{
|
||||||
|
$this->video = $video;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommandsForCutting(): \Generator
|
||||||
|
{
|
||||||
|
$command = sprintf(
|
||||||
|
'ffmpeg -y -ss %s -to %s -i %s -c copy %s',
|
||||||
|
escapeshellarg($this->video->getStart()),
|
||||||
|
escapeshellarg($this->video->getEnd()),
|
||||||
|
escapeshellarg($this->video->getOriginalFilename()),
|
||||||
|
escapeshellarg($this->getNextTempFilename())
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Generate multiple once ads are in
|
||||||
|
|
||||||
|
yield Process::fromShellCommandline($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommandForGeneratingMetadata(): Process
|
||||||
|
{
|
||||||
|
$command = sprintf(
|
||||||
|
'ffmpeg -i %s -f ffmetadata %s',
|
||||||
|
escapeshellarg($this->video->getOriginalFilename()),
|
||||||
|
escapeshellarg($this->getMetadataFilename())
|
||||||
|
);
|
||||||
|
|
||||||
|
return Process::fromShellCommandline($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommandForGeneratingConcatInputFile(): Process
|
||||||
|
{
|
||||||
|
if ($this->tempFilenames === []) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'No temp files for concat available. Did you call "getCommandsForCutting()" before?',
|
||||||
|
1601455216
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = sprintf(
|
||||||
|
'echo "file %s" > %s',
|
||||||
|
escapeshellarg($this->tempFilenames[0]),
|
||||||
|
escapeshellarg($this->getConcatFilename())
|
||||||
|
);
|
||||||
|
|
||||||
|
// if $ads ; then
|
||||||
|
// echo "file '$result2'" >> $resultTxt
|
||||||
|
// fi
|
||||||
|
|
||||||
|
return Process::fromShellCommandline($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommandForGeneratingVideo(): Process
|
||||||
|
{
|
||||||
|
$command = sprintf(
|
||||||
|
'ffmpeg -f concat -safe 0 -i %s -c copy'
|
||||||
|
. ' -f ffmetadata -i %s -c copy -map_metadata 1'
|
||||||
|
// . ' -metadata Title="$episodeTitle"'
|
||||||
|
. ' %s',
|
||||||
|
escapeshellarg($this->getConcatFilename()),
|
||||||
|
escapeshellarg($this->getMetadataFilename()),
|
||||||
|
escapeshellarg($this->video->getTargetFilePath())
|
||||||
|
);
|
||||||
|
return Process::fromShellCommandline($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCommandForCleanup(): Process
|
||||||
|
{
|
||||||
|
$tempfiles = implode(' ', array_map(function (string $filename) {
|
||||||
|
return escapeshellarg($filename);
|
||||||
|
}, $this->tempFilenames));
|
||||||
|
|
||||||
|
$command = sprintf(
|
||||||
|
'rm %s %s %s',
|
||||||
|
$tempfiles,
|
||||||
|
escapeshellarg($this->getConcatFilename()),
|
||||||
|
escapeshellarg($this->getMetadataFilename())
|
||||||
|
);
|
||||||
|
return Process::fromShellCommandline($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNextTempFilename(): string
|
||||||
|
{
|
||||||
|
$count = count($this->tempFilenames) + 1;
|
||||||
|
$nextName = sys_get_temp_dir()
|
||||||
|
. DIRECTORY_SEPARATOR
|
||||||
|
. $this->video->getOriginalFilenameWithSuffix('cut-' . $count);
|
||||||
|
|
||||||
|
$this->tempFilenames[] = $nextName;
|
||||||
|
return $nextName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMetadataFilename(): string
|
||||||
|
{
|
||||||
|
$file = new \SplFileInfo($this->video->getOriginalFilenameWithSuffix('metadata'));
|
||||||
|
|
||||||
|
return sys_get_temp_dir() . DIRECTORY_SEPARATOR . $file->getBasename('.' . $file->getExtension()) . '.txt';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getConcatFilename(): string
|
||||||
|
{
|
||||||
|
$file = new \SplFileInfo($this->video->getOriginalFilenameWithSuffix('concat'));
|
||||||
|
|
||||||
|
return sys_get_temp_dir() . DIRECTORY_SEPARATOR . $file->getBasename('.' . $file->getExtension()) . '.txt';
|
||||||
|
}
|
||||||
|
}
|
154
src/VideoInfo.php
Normal file
154
src/VideoInfo.php
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DanielSiepmann\Videcutting;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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 2
|
||||||
|
* 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, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class VideoInfo
|
||||||
|
{
|
||||||
|
private $filename = '';
|
||||||
|
private $start = '';
|
||||||
|
private $end = '';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $filename,
|
||||||
|
string $start,
|
||||||
|
string $end
|
||||||
|
) {
|
||||||
|
$this->filename = $filename;
|
||||||
|
$this->start = $start;
|
||||||
|
$this->end = $end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStart(): string
|
||||||
|
{
|
||||||
|
return $this->start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEnd(): string
|
||||||
|
{
|
||||||
|
return $this->end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSeries(): string
|
||||||
|
{
|
||||||
|
return $this->getFilenameParts()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasSeason(): bool
|
||||||
|
{
|
||||||
|
foreach ($this->getFilenameParts() as $part) {
|
||||||
|
if (stripos($part, 'season-') === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSeasonNumber(): int
|
||||||
|
{
|
||||||
|
foreach ($this->getFilenameParts() as $part) {
|
||||||
|
if (stripos($part, 'season-') === 0) {
|
||||||
|
return (int) str_replace('season-', '', $part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Exception('No season number detected.', 1601458223);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasEpisode(): bool
|
||||||
|
{
|
||||||
|
foreach ($this->getFilenameParts() as $part) {
|
||||||
|
if (stripos($part, 'episode-') === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEpisodeNumber(): int
|
||||||
|
{
|
||||||
|
foreach ($this->getFilenameParts() as $part) {
|
||||||
|
if (stripos($part, 'episode-') === 0) {
|
||||||
|
return (int) str_replace('episode-', '', $part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Exception('No episode number detected.', 1601458223);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
$parts = $this->getFilenameParts();
|
||||||
|
// Remove also the quality indicator
|
||||||
|
return $parts[count($parts) - 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTargetFilePath(): string
|
||||||
|
{
|
||||||
|
$folderParts = [];
|
||||||
|
$filenameParts = [];
|
||||||
|
$folderParts[] = $this->getSeries();
|
||||||
|
|
||||||
|
if ($this->hasSeason()) {
|
||||||
|
$folderParts[] = 'Staffel-' . str_pad($this->getSeasonNumber(), 2, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
if ($this->hasEpisode()) {
|
||||||
|
$filenameParts[] = str_pad($this->getEpisodeNumber(), 2, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
$filenameParts[] = ucwords(str_replace('-', ' ', $this->getTitle()));
|
||||||
|
|
||||||
|
return implode(DIRECTORY_SEPARATOR, $folderParts)
|
||||||
|
. DIRECTORY_SEPARATOR
|
||||||
|
. implode('-', $filenameParts)
|
||||||
|
. '.' . $this->getExtension();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOriginalFilename(): string
|
||||||
|
{
|
||||||
|
return $this->filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOriginalFilenameWithSuffix(string $suffix): string
|
||||||
|
{
|
||||||
|
$file = new \SplFileInfo($this->filename);
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'%s-%s.%s',
|
||||||
|
$file->getBasename('.' . $this->getExtension()),
|
||||||
|
$suffix,
|
||||||
|
$this->getExtension()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getExtension(): string
|
||||||
|
{
|
||||||
|
$file = new \SplFileInfo($this->filename);
|
||||||
|
|
||||||
|
return $file->getExtension();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFilenameParts(): array
|
||||||
|
{
|
||||||
|
return explode('_', $this->filename);
|
||||||
|
}
|
||||||
|
}
|
220
tests/Unit/CuttingTest.php
Normal file
220
tests/Unit/CuttingTest.php
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DanielSiepmann\Videcutting\Tests\Unit;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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 2
|
||||||
|
* 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, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use DanielSiepmann\Videcutting\Cutting;
|
||||||
|
use DanielSiepmann\Videcutting\VideoInfo;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers DanielSiepmann\Videcutting\Cutting
|
||||||
|
*/
|
||||||
|
class CuttingTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function instanceCanBeCreated(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
static::assertInstanceOf(Cutting::class, $subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function generatesCommandsForCutting(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
$video->getStart()->willReturn('10.05.20');
|
||||||
|
$video->getEnd()->willReturn('13.10.25');
|
||||||
|
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('cut-1')->willReturn('Some-Video-cut-1.mp4');
|
||||||
|
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$commands = [];
|
||||||
|
foreach ($subject->getCommandsForCutting() as $command) {
|
||||||
|
$commands[] = $command;
|
||||||
|
}
|
||||||
|
|
||||||
|
static::assertCount(1, $commands);
|
||||||
|
static::assertSame(
|
||||||
|
"ffmpeg -y -ss '10.05.20' -to '13.10.25' -i 'Some-Video.mp4' -c copy '/tmp/Some-Video-cut-1.mp4'",
|
||||||
|
$commands[0]->getCommandLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function generatesCommandForGeneratingMetadata(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('metadata')->willReturn('Some-Video-metadata.mp4');
|
||||||
|
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$command = $subject->getCommandForGeneratingMetadata();
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
"ffmpeg -i 'Some-Video.mp4' -f ffmetadata '/tmp/Some-Video-metadata.txt'",
|
||||||
|
$command->getCommandLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function throwsExceptionForGeneratingConcatInputFileIfNoConcatIsAvailable(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('No temp files for concat available. Did you call "getCommandsForCutting()" before?');
|
||||||
|
$this->expectExceptionCode(1601455216);
|
||||||
|
|
||||||
|
$subject->getCommandForGeneratingConcatInputFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function generatesCommandsForGeneratingConcatInputFile(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
$video->getStart()->willReturn('10.05.20');
|
||||||
|
$video->getEnd()->willReturn('13.10.25');
|
||||||
|
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('cut-1')->willReturn('Some-Video-cut-1.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('concat')->willReturn('Some-Video-concat.mp4');
|
||||||
|
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($subject->getCommandsForCutting() as $command) {
|
||||||
|
// Just generate all commands to have proper internal state
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = $subject->getCommandForGeneratingConcatInputFile();
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
'echo "file \'/tmp/Some-Video-cut-1.mp4\'" > \'/tmp/Some-Video-concat.txt\'',
|
||||||
|
$command->getCommandLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function generatesCommandForGeneratingVideo(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
$video->getTargetFilePath()->willReturn('Series-Name/Series-01/10-episode title.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('concat')->willReturn('Series-Name_Series-01_episode-title-01-concat.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('metadata')->willReturn('Series-Name_Series-01_episode-title-01-metadata.mp4');
|
||||||
|
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$command = $subject->getCommandForGeneratingVideo();
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
'ffmpeg -f concat -safe 0 -i \'/tmp/Series-Name_Series-01_episode-title-01-concat.txt\' -c copy'
|
||||||
|
. ' -f ffmetadata -i \'/tmp/Series-Name_Series-01_episode-title-01-metadata.txt\' -c copy -map_metadata 1'
|
||||||
|
// . ' -metadata Title="$episodeTitle"'
|
||||||
|
. ' \'Series-Name/Series-01/10-episode title.mp4\'',
|
||||||
|
$command->getCommandLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function generatesCommandForCleanup(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
$video->getTargetFilePath()->willReturn('Series-Name/Series-01/10-episode title.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('concat')->willReturn('Series-Name_Series-01_episode-title-01-concat.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('metadata')->willReturn('Series-Name_Series-01_episode-title-01-metadata.mp4');
|
||||||
|
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$command = $subject->getCommandForCleanup();
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
'rm \'/tmp/Series-Name_Series-01_episode-title-01-concat.txt\' \'/tmp/Series-Name_Series-01_episode-title-01-metadata.txt\'',
|
||||||
|
$command->getCommandLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function generatesCommandForCleanupWithSingleTempFile(): void
|
||||||
|
{
|
||||||
|
$video = $this->prophesize(VideoInfo::class);
|
||||||
|
$video->getStart()->willReturn('10.05.20');
|
||||||
|
$video->getEnd()->willReturn('13.10.25');
|
||||||
|
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
|
||||||
|
$video->getTargetFilePath()->willReturn('Series-Name/Series-01/10-episode title.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('cut-1')->willReturn('Some-Video-cut-1.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('concat')->willReturn('Series-Name_Series-01_episode-title-01-concat.mp4');
|
||||||
|
$video->getOriginalFilenameWithSuffix('metadata')->willReturn('Series-Name_Series-01_episode-title-01-metadata.mp4');
|
||||||
|
|
||||||
|
$subject = new Cutting(
|
||||||
|
$video->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($subject->getCommandsForCutting() as $command) {
|
||||||
|
// Just generate all commands to have proper internal state
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = $subject->getCommandForCleanup();
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
'rm \'/tmp/Some-Video-cut-1.mp4\' \'/tmp/Series-Name_Series-01_episode-title-01-concat.txt\' \'/tmp/Series-Name_Series-01_episode-title-01-metadata.txt\'',
|
||||||
|
$command->getCommandLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
117
tests/Unit/VideoInfoTest.php
Normal file
117
tests/Unit/VideoInfoTest.php
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DanielSiepmann\Videcutting\Tests\Unit;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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 2
|
||||||
|
* 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, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use DanielSiepmann\Videcutting\VideoInfo;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers DanielSiepmann\Videcutting\VideoInfo
|
||||||
|
*/
|
||||||
|
class VideoInfoTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function instanceCanBeCreated(): void
|
||||||
|
{
|
||||||
|
$subject = new VideoInfo(
|
||||||
|
'Example-Video-File.mp4',
|
||||||
|
'05:20.1',
|
||||||
|
'32:30.30'
|
||||||
|
);
|
||||||
|
|
||||||
|
static::assertInstanceOf(VideoInfo::class, $subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @dataProvider possibleFilenames
|
||||||
|
*/
|
||||||
|
public function returnsTargetFilePath(
|
||||||
|
string $filename,
|
||||||
|
string $expectedTargetFilePath
|
||||||
|
): void {
|
||||||
|
$subject = new VideoInfo(
|
||||||
|
$filename,
|
||||||
|
'05:20.1',
|
||||||
|
'32:30.30'
|
||||||
|
);
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
$expectedTargetFilePath,
|
||||||
|
$subject->getTargetFilePath()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function returnsOriginalFilename(): void
|
||||||
|
{
|
||||||
|
$subject = new VideoInfo(
|
||||||
|
'Example-Video-File.mp4',
|
||||||
|
'05:20.1',
|
||||||
|
'32:30.30'
|
||||||
|
);
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
'Example-Video-File.mp4',
|
||||||
|
$subject->getOriginalFilename()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function returnsOriginalFilenameWithProvidedSuffix(): void
|
||||||
|
{
|
||||||
|
$subject = new VideoInfo(
|
||||||
|
'Example-Video-File.mp4',
|
||||||
|
'05:20.1',
|
||||||
|
'32:30.30'
|
||||||
|
);
|
||||||
|
|
||||||
|
static::assertSame(
|
||||||
|
'Example-Video-File-some-suffix.mp4',
|
||||||
|
$subject->getOriginalFilenameWithSuffix('some-suffix')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function possibleFilenames(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Movie' => [
|
||||||
|
'filename' => 'Sherlock-Holmes_Der-Vampir-Von-Whitechapel_hq.mp4',
|
||||||
|
'expectedTargetFilePath' => 'Sherlock-Holmes/Der Vampir Von Whitechapel.mp4',
|
||||||
|
],
|
||||||
|
'Series no seasons' => [
|
||||||
|
'filename' => 'Storage-Hunters_episode-2_trucker-auktion_hq.mp4',
|
||||||
|
'expectedTargetFilePath' => 'Storage-Hunters/02-Trucker Auktion.mp4',
|
||||||
|
],
|
||||||
|
'Series with seasons' => [
|
||||||
|
'filename' => 'The-Big-Bang-Theory_season-1_episode-12_das-jerusalem-projekt_hq.mp4',
|
||||||
|
'expectedTargetFilePath' => 'The-Big-Bang-Theory/Staffel-01/12-Das Jerusalem Projekt.mp4',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue