feat(video-clips): generate subtitles clip using transcript json to have subtitles accross video

This commit is contained in:
Yassine Doghri 2022-01-10 14:22:55 +00:00
parent 958c1213ed
commit 3ce07e455d
3 changed files with 87 additions and 14 deletions

View File

@ -15,12 +15,12 @@ use CodeIgniter\Files\File;
class Transcript extends BaseMedia
{
public ?string $json_path = null;
public ?string $json_url = null;
protected string $type = 'transcript';
protected ?string $json_path = null;
protected ?string $json_url = null;
public function initFileProperties(): void
{
parent::initFileProperties();

View File

@ -11,10 +11,12 @@ declare(strict_types=1);
namespace MediaClipper;
use App\Entities\Episode;
use Exception;
use GdImage;
/**
* TODO: refactor this by splitting image manipulations + video generation
* TODO: refactor this by splitting process modules into different classes (image generation, subtitles clip, video
* generation)
*
* @phpstan-ignore-next-line
*/
@ -45,8 +47,6 @@ class VideoClipper
protected string $episodeCoverPath;
protected ?string $subtitlesInput = null;
protected string $soundbiteOutput;
protected string $subtitlesClipOutput;
@ -87,9 +87,6 @@ class VideoClipper
$this->audioInput = media_path($this->episode->audio->file_path);
$this->episodeCoverPath = media_path($this->episode->cover->file_path);
if ($this->episode->transcript !== null) {
$this->subtitlesInput = media_path($this->episode->transcript->file_path);
}
$podcastFolder = media_path("podcasts/{$this->episode->podcast->handle}");
@ -108,12 +105,84 @@ class VideoClipper
public function subtitlesClip(): void
{
if ($this->subtitlesInput) {
$subtitleClipCmd = "ffmpeg -y -i {$this->subtitlesInput} -ss {$this->start} -t {$this->duration} {$this->subtitlesClipOutput}";
if ($this->episode->transcript === null) {
throw new Exception('Episode does not have a transcript!');
}
if ($this->episode->transcript->json_path) {
$this->generateSubtitlesClipFromJson($this->episode->transcript->json_path);
} else {
$subtitlesInput = media_path($this->episode->transcript->file_path);
$subtitleClipCmd = "ffmpeg -y -i {$subtitlesInput} -ss {$this->start} -t {$this->duration} {$this->subtitlesClipOutput}";
exec($subtitleClipCmd);
}
}
public function generateSubtitlesClipFromJson(string $jsonFileInput): void
{
$jsonTranscriptString = file_get_contents($jsonFileInput);
if ($jsonTranscriptString === false) {
throw new Exception('Cannot get transcript json contents.');
}
$jsonTranscript = json_decode($jsonTranscriptString, true);
if ($jsonTranscript === null) {
throw new Exception('Transcript json is invalid.');
}
$srtClip = '';
$segmentIndex = 1;
foreach ($jsonTranscript as $segment) {
$startTime = null;
$endTime = null;
if ($segment['startTime'] < $this->end && $segment['endTime'] > $this->start) {
$startTime = $segment['startTime'] - $this->start;
$endTime = $segment['endTime'] - $this->start;
}
if ($segment['startTime'] < $this->start && $this->start < $segment['endTime']) {
$startTime = 0;
}
if ($segment['startTime'] < $this->end && $segment['endTime'] >= $this->end) {
$endTime = $this->duration;
}
if ($startTime !== null && $endTime !== null) {
$formattedStartTime = $this->formatSeconds($startTime);
$formattedEndTime = $this->formatSeconds($endTime);
$srtClip .= <<<CODE_SAMPLE
{$segmentIndex}
{$formattedStartTime} --> {$formattedEndTime}
{$segment['text']}
CODE_SAMPLE;
++$segmentIndex;
}
}
// create srt clip file
file_put_contents($this->subtitlesClipOutput, $srtClip);
}
public function formatSeconds(float $seconds): string
{
$milliseconds = str_replace('0.', '', (string) (round($seconds - floor($seconds), 3)));
return gmdate('H:i:s', (int) floor($seconds)) . ',' . str_pad($milliseconds, 3, '0', STR_PAD_RIGHT);
}
public function cleanTempFiles(): void
{
// delete generated video background image, soundbite & subtitlesClip
unlink($this->soundbiteOutput);
unlink($this->subtitlesClipOutput);
unlink($this->videoClipBgOutput);
}
/**
* @return int 0 for success, else error
*/
@ -129,7 +198,11 @@ class VideoClipper
$generateCmd = $this->getCmd();
return $this->cmd_exec($generateCmd);
$cmdResult = $this->cmd_exec($generateCmd);
$this->cleanTempFiles();
return $cmdResult;
}
public function getCmd(): string

View File

@ -13,7 +13,7 @@ const VideoClipBuilder = (): void => {
) as NodeListOf<HTMLInputElement>;
const titleInput = form.querySelector(
'input[name="label"]'
'input[name="title"]'
) as HTMLInputElement;
if (titleInput) {
videoClipPreviewer.setAttribute("title", titleInput.value || "");