Support multiple cuts, for removing of ads

This commit is contained in:
Daniel Siepmann 2020-09-30 13:48:25 +02:00
parent 86a7dbef19
commit 8b8f7fcab1
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
5 changed files with 254 additions and 74 deletions

View file

@ -40,8 +40,8 @@ class Command
$videoInfo = new VideoInfo(
$input->getArgument('file'),
$input->getArgument('start'),
$input->getArgument('end')
// $input->getOption('ad')
$input->getArgument('end'),
$input->getOption('ad')
);
$cutting = new Cutting($videoInfo);

View file

@ -42,17 +42,24 @@ class Cutting
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())
);
$start = $this->video->getStart();
$end = $this->video->getEnd();
// TODO: Generate multiple once ads are in
if ($this->video->getAds() === []) {
yield $this->getCuttingCommand($start, $end);
return;
}
yield Process::fromShellCommandline($command);
// Each video starts with end of last ad, and end at next ad start.
foreach ($this->video->getAds() as [$adStart, $adEnd]) {
$end = $adStart;
yield $this->getCuttingCommand($start, $end);
$start = $adEnd;
}
yield $this->getCuttingCommand($start, $this->video->getEnd());
}
public function getCommandForGeneratingMetadata(): Process
@ -75,16 +82,12 @@ class Cutting
);
}
$command = sprintf(
'echo "file %s" > %s',
escapeshellarg($this->tempFilenames[0]),
escapeshellarg($this->getConcatFilename())
);
// if $ads ; then
// echo "file '$result2'" >> $resultTxt
// fi
$lines = [];
foreach ($this->tempFilenames as $filename) {
$lines[] = 'file ' . $filename;
}
$command = 'printf ' . escapeshellarg(implode('\n', $lines)) . ' > ' . $this->getConcatFilename();
return Process::fromShellCommandline($command);
}
@ -117,6 +120,19 @@ class Cutting
return Process::fromShellCommandline($command);
}
private function getCuttingCommand(string $start, string $end): Process
{
$command = sprintf(
'ffmpeg -y -ss %s -to %s -i %s -c copy %s',
escapeshellarg($start),
escapeshellarg($end),
escapeshellarg($this->video->getOriginalFilename()),
escapeshellarg($this->getNextTempFilename())
);
return Process::fromShellCommandline($command);
}
private function getNextTempFilename(): string
{
$count = count($this->tempFilenames) + 1;

View file

@ -26,15 +26,18 @@ class VideoInfo
private $filename = '';
private $start = '';
private $end = '';
private $ads = [];
public function __construct(
string $filename,
string $start,
string $end
string $end,
array $ads
) {
$this->filename = $filename;
$this->start = $start;
$this->end = $end;
$this->ads = $this->getParsedAds($ads);
}
public function getStart(): string
@ -47,53 +50,9 @@ class VideoInfo
return $this->end;
}
public function getSeries(): string
public function getAds(): array
{
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);
return $this->ads;
}
public function getTitle(): string
@ -140,6 +99,62 @@ class VideoInfo
);
}
private function getParsedAds(array $ads): array
{
return array_map(function (string $ad) {
return explode('/', $ad, 2);
}, $ads);
}
private function getSeries(): string
{
return $this->getFilenameParts()[0];
}
private function hasSeason(): bool
{
foreach ($this->getFilenameParts() as $part) {
if (stripos($part, 'season-') === 0) {
return true;
}
}
return false;
}
private 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);
}
private function hasEpisode(): bool
{
foreach ($this->getFilenameParts() as $part) {
if (stripos($part, 'episode-') === 0) {
return true;
}
}
return false;
}
private 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);
}
private function getExtension(): string
{
$file = new \SplFileInfo($this->filename);

View file

@ -50,11 +50,12 @@ class CuttingTest extends TestCase
/**
* @test
*/
public function generatesCommandsForCutting(): void
public function generatesCommandsForCuttingWithoutAds(): void
{
$video = $this->prophesize(VideoInfo::class);
$video->getStart()->willReturn('10.05.20');
$video->getEnd()->willReturn('13.10.25');
$video->getAds()->willReturn([]);
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
$video->getOriginalFilenameWithSuffix('cut-1')->willReturn('Some-Video-cut-1.mp4');
@ -74,6 +75,47 @@ class CuttingTest extends TestCase
);
}
/**
* @test
*/
public function generatesCommandsForCuttingWithAds(): void
{
$video = $this->prophesize(VideoInfo::class);
$video->getStart()->willReturn('1:05.20');
$video->getEnd()->willReturn('13:10.25');
$video->getAds()->willReturn([
['2:10', '3:05'],
['10:07', '11:10'],
]);
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
$video->getOriginalFilenameWithSuffix('cut-1')->willReturn('Some-Video-cut-1.mp4');
$video->getOriginalFilenameWithSuffix('cut-2')->willReturn('Some-Video-cut-2.mp4');
$video->getOriginalFilenameWithSuffix('cut-3')->willReturn('Some-Video-cut-3.mp4');
$subject = new Cutting(
$video->reveal()
);
$commands = [];
foreach ($subject->getCommandsForCutting() as $command) {
$commands[] = $command;
}
static::assertCount(3, $commands, 'Did not get expected number of cutting commands');
static::assertSame(
"ffmpeg -y -ss '1:05.20' -to '2:10' -i 'Some-Video.mp4' -c copy '/tmp/Some-Video-cut-1.mp4'",
$commands[0]->getCommandLine()
);
static::assertSame(
"ffmpeg -y -ss '3:05' -to '10:07' -i 'Some-Video.mp4' -c copy '/tmp/Some-Video-cut-2.mp4'",
$commands[1]->getCommandLine()
);
static::assertSame(
"ffmpeg -y -ss '11:10' -to '13:10.25' -i 'Some-Video.mp4' -c copy '/tmp/Some-Video-cut-3.mp4'",
$commands[2]->getCommandLine()
);
}
/**
* @test
*/
@ -116,11 +158,12 @@ class CuttingTest extends TestCase
/**
* @test
*/
public function generatesCommandsForGeneratingConcatInputFile(): void
public function generatesCommandsForGeneratingConcatInputFileWithNoAds(): void
{
$video = $this->prophesize(VideoInfo::class);
$video->getStart()->willReturn('10.05.20');
$video->getEnd()->willReturn('13.10.25');
$video->getAds()->willReturn([]);
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
$video->getOriginalFilenameWithSuffix('cut-1')->willReturn('Some-Video-cut-1.mp4');
$video->getOriginalFilenameWithSuffix('concat')->willReturn('Some-Video-concat.mp4');
@ -136,7 +179,42 @@ class CuttingTest extends TestCase
$command = $subject->getCommandForGeneratingConcatInputFile();
static::assertSame(
'echo "file \'/tmp/Some-Video-cut-1.mp4\'" > \'/tmp/Some-Video-concat.txt\'',
"printf 'file /tmp/Some-Video-cut-1.mp4' > /tmp/Some-Video-concat.txt",
$command->getCommandLine()
);
}
/**
* @test
*/
public function generatesCommandsForGeneratingConcatInputFileWithAds(): void
{
$video = $this->prophesize(VideoInfo::class);
$video->getStart()->willReturn('10.05.20');
$video->getEnd()->willReturn('13.10.25');
$video->getAds()->willReturn([
['2:10', '3:05'],
['10:07', '11:10'],
]);
$video->getOriginalFilename()->willReturn('Some-Video.mp4');
$video->getOriginalFilenameWithSuffix('cut-1')->willReturn('Some-Video-cut-1.mp4');
$video->getOriginalFilenameWithSuffix('cut-2')->willReturn('Some-Video-cut-2.mp4');
$video->getOriginalFilenameWithSuffix('cut-3')->willReturn('Some-Video-cut-3.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(
'printf \'file /tmp/Some-Video-cut-1.mp4\nfile /tmp/Some-Video-cut-2.mp4\nfile /tmp/Some-Video-cut-3.mp4\''
. ' > /tmp/Some-Video-concat.txt',
$command->getCommandLine()
);
}
@ -196,6 +274,7 @@ class CuttingTest extends TestCase
$video = $this->prophesize(VideoInfo::class);
$video->getStart()->willReturn('10.05.20');
$video->getEnd()->willReturn('13.10.25');
$video->getAds()->willReturn([]);
$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');

View file

@ -37,12 +37,79 @@ class VideoInfoTest extends TestCase
$subject = new VideoInfo(
'Example-Video-File.mp4',
'05:20.1',
'32:30.30'
'32:30.30',
[]
);
static::assertInstanceOf(VideoInfo::class, $subject);
}
/**
* @test
*/
public function getInitialSetStart()
{
$subject = new VideoInfo(
'Example-Video-File.mp4',
'05:20.1',
'32:30.30',
[]
);
static::assertSame('05:20.1', $subject->getStart());
}
/**
* @test
*/
public function getInitialSetEnd()
{
$subject = new VideoInfo(
'Example-Video-File.mp4',
'05:20.1',
'32:30.30',
[]
);
static::assertSame('32:30.30', $subject->getEnd());
}
/**
* @test
*/
public function getInitialEmptySetAds()
{
$subject = new VideoInfo(
'Example-Video-File.mp4',
'05:20.1',
'32:30.30',
[]
);
static::assertSame([], $subject->getAds());
}
/**
* @test
*/
public function getInitialSetAds()
{
$subject = new VideoInfo(
'Example-Video-File.mp4',
'05:20.1',
'32:30.30',
[
'10:35/15:10',
'21:38/31:02',
]
);
static::assertSame([
['10:35', '15:10'],
['21:38', '31:02'],
], $subject->getAds());
}
/**
* @test
* @dataProvider possibleFilenames
@ -54,7 +121,8 @@ class VideoInfoTest extends TestCase
$subject = new VideoInfo(
$filename,
'05:20.1',
'32:30.30'
'32:30.30',
[]
);
static::assertSame(
@ -71,7 +139,8 @@ class VideoInfoTest extends TestCase
$subject = new VideoInfo(
'Example-Video-File.mp4',
'05:20.1',
'32:30.30'
'32:30.30',
[]
);
static::assertSame(
@ -88,7 +157,8 @@ class VideoInfoTest extends TestCase
$subject = new VideoInfo(
'Example-Video-File.mp4',
'05:20.1',
'32:30.30'
'32:30.30',
[]
);
static::assertSame(