Commit 2065ebbe authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat(video-clips): add route for scheduled video clips + list video clips with status

parent 2f6fdf90
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -85,7 +85,6 @@ class AddClips extends Migration
        ]);

        $this->forge->addKey('id', true);
        $this->forge->addUniqueKey(['episode_id', 'start_time', 'duration', 'type']);
        $this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
        $this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
        $this->forge->addForeignKey('media_id', 'media', 'id', '', 'CASCADE');
+20 −6
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@ use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\MediaModel;
use App\Models\PodcastModel;
use App\Models\UserModel;
use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use Modules\Auth\Entities\User;

/**
 * @property int $id
@@ -33,8 +35,10 @@ use CodeIgniter\Files\File;
 * @property string $type
 * @property int $media_id
 * @property Video|Audio $media
 * @property array|null $metadata
 * @property string $status
 * @property string $logs
 * @property User $user
 * @property int $created_by
 * @property int $updated_by
 */
@@ -52,14 +56,17 @@ class BaseClip extends Entity
        'duration' => 'double',
        'type' => 'string',
        'media_id' => '?integer',
        'metadata' => 'json-array',
        'metadata' => '?json-array',
        'status' => 'string',
        'logs' => 'string',
        'created_by' => 'integer',
        'updated_by' => 'integer',
    ];

    public function __construct($data)
    /**
     * @param array<string, mixed>|null $data
     */
    public function __construct(array $data = null)
    {
        parent::__construct($data);

@@ -80,13 +87,20 @@ class BaseClip extends Entity
        return (new EpisodeModel())->getEpisodeById($this->episode_id);
    }

    public function getUser(): ?User
    {
        return (new UserModel())->find($this->created_by);
    }

    public function setMedia(string $filePath = null): static
    {
        if ($filePath === null || ($file = new File($filePath)) === null) {
        if ($filePath === null) {
            return $this;
        }

        if ($this->media_id !== 0) {
        $file = new File($filePath);

        if ($this->media_id !== null) {
            $this->getMedia()
                ->setFile($file);
            $this->getMedia()
@@ -97,8 +111,8 @@ class BaseClip extends Entity
                'file_path' => $filePath,
                'language_code' => $this->getPodcast()
                    ->language_code,
                'uploaded_by' => user_id(),
                'updated_by' => user_id(),
                'uploaded_by' => $this->attributes['created_by'],
                'updated_by' => $this->attributes['created_by'],
            ]);
            $media->setFile($file);

+60 −2
Original line number Diff line number Diff line
@@ -10,20 +10,78 @@ declare(strict_types=1);

namespace App\Entities\Clip;

use App\Entities\Media\Video;
use App\Models\MediaModel;
use CodeIgniter\Files\File;

/**
 * @property string $theme
 * @property array $theme
 * @property string $format
 */
class VideoClip extends BaseClip
{
    protected string $type = 'video';

    /**
     * @param array<string, mixed>|null $data
     */
    public function __construct(array $data = null)
    {
        parent::__construct($data);

        if ($this->metadata !== null) {
        if ($this->metadata !== null && $this->metadata !== []) {
            $this->theme = $this->metadata['theme'];
            $this->format = $this->metadata['format'];
        }
    }

    /**
     * @param array<string, string> $theme
     */
    public function setTheme(array $theme): self
    {
        // TODO: change?
        $this->attributes['metadata'] = json_decode($this->attributes['metadata'] ?? '[]', true);

        $this->attributes['theme'] = $theme;
        $this->attributes['metadata']['theme'] = $theme;

        $this->attributes['metadata'] = json_encode($this->attributes['metadata']);

        return $this;
    }

    public function setFormat(string $format): self
    {
        $this->attributes['metadata'] = json_decode($this->attributes['metadata'], true);

        $this->attributes['format'] = $format;
        $this->attributes['metadata']['format'] = $format;

        $this->attributes['metadata'] = json_encode($this->attributes['metadata']);

        return $this;
    }

    public function setMedia(string $filePath = null): static
    {
        if ($filePath === null) {
            return $this;
        }

        $file = new File($filePath);

        $video = new Video([
            'file_path' => $filePath,
            'language_code' => $this->getPodcast()
                ->language_code,
            'uploaded_by' => $this->attributes['created_by'],
            'updated_by' => $this->attributes['created_by'],
        ]);
        $video->setFile($file);

        $this->attributes['media_id'] = (new MediaModel())->saveMedia($video);

        return $this;
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ declare(strict_types=1);

namespace App\Entities;

use App\Entities\Clip\BaseClip;
use App\Entities\Clip\Soundbite;
use App\Entities\Media\Audio;
use App\Entities\Media\Chapters;
use App\Entities\Media\Image;
@@ -75,7 +75,7 @@ use RuntimeException;
 * @property Time|null $deleted_at;
 *
 * @property Person[] $persons;
 * @property Soundbites[] $soundbites;
 * @property Soundbite[] $soundbites;
 * @property string $embed_url;
 */
class Episode extends Entity
@@ -110,7 +110,7 @@ class Episode extends Entity
    protected ?array $persons = null;

    /**
     * @var Soundbites[]|null
     * @var Soundbite[]|null
     */
    protected ?array $soundbites = null;

@@ -407,7 +407,7 @@ class Episode extends Entity
    /**
     * Returns the episode’s clips
     *
     * @return BaseClip[]|\App\Entities\Soundbites[]
     * @return Soundbite[]
     */
    public function getSoundbites(): array
    {
+56 −5
Original line number Diff line number Diff line
@@ -31,6 +31,12 @@ class VideoClipper
        'timestamp' => 'NotoSansMono-Regular.ttf',
    ];

    public ?string $logs = null;

    public bool $error = false;

    public string $videoClipOutput;

    protected float $duration;

    protected string $audioInput;
@@ -45,8 +51,6 @@ class VideoClipper

    protected string $videoClipBgOutput;

    protected string $videoClipOutput;

    protected ?string $episodeNumbering = null;

    /**
@@ -107,7 +111,10 @@ class VideoClipper
        }
    }

    public function generate(): string
    /**
     * @return int 0 for success, else error
     */
    public function generate(): int
    {
        $this->soundbite();
        $this->subtitlesClip();
@@ -119,7 +126,7 @@ class VideoClipper

        $generateCmd = $this->getCmd();

        return shell_exec($generateCmd . ' 2>&1');
        return $this->cmd_exec($generateCmd);
    }

    public function getCmd(): string
@@ -205,7 +212,7 @@ class VideoClipper
            return false;
        }

        $episodeCover = imagecreatefromjpeg($this->episodeCoverPath);
        $episodeCover = $this->createCoverImage();
        if (! $episodeCover) {
            return false;
        }
@@ -340,6 +347,41 @@ class VideoClipper
        return true;
    }

    /**
     * @return int 0 (success), 1 - 2 - 254 - 255 (error)
     */
    private function cmd_exec(string $cmd): int
    {
        $outFile = tempnam(WRITEPATH . 'logs', 'cmd-out-');

        if (! $outFile) {
            return 254;
        }

        $descriptorSpec = [
            0 => ['pipe', 'r'],
            1 => ['pipe', 'w'],
            2 => ['file', $outFile, 'w'],
            // FFmpeg outputs to stderr by default
        ];
        $proc = proc_open($cmd, $descriptorSpec, $pipes);

        if (! is_resource($proc)) {
            return 255;
        }

        fclose($pipes[0]);    //Don't really want to give any input

        $exit = proc_close($proc);

        $this->logs = (string) file_get_contents($outFile);

        // remove temporary files
        unlink($outFile);

        return $exit;
    }

    private function getFont(string $name): string
    {
        return config('MediaClipper')->fontsFolder . self::FONTS[$name];
@@ -364,6 +406,15 @@ class VideoClipper
        return $background;
    }

    private function createCoverImage(): GdImage | false
    {
        return match ($this->episode->cover->file_mimetype) {
            'image/jpeg' => imagecreatefromjpeg($this->episodeCoverPath),
            'image/png' => imagecreatefrompng($this->episodeCoverPath),
            default => imagecreate(1400, 1400),
        };
    }

    private function scaleImage(GdImage $image, int $width, int $height): GdImage | false
    {
        return imagescale($image, $width, $height);
Loading