Commit 71a063da authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat(video-clips): allow episodeNumbering text to stand in the indent of episodeTitle paragraph

parent 3af404da
Loading
Loading
Loading
Loading
+33 −32
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ class MediaClipper extends BaseConfig
    public string $wavesMask = APPPATH . 'Libraries/MediaClipper/waves-mask.png';

    /**
     * @var array<string, array<string, int|array<string, int|string>>>
     * @var array<string, array<string, int|array<string, float|int|string>>>
     */
    public array $formats = [
        'landscape' => [
@@ -34,25 +34,25 @@ class MediaClipper extends BaseConfig
                'x' => 810,
                'y' => 210,
            ],
            'podcastTitle' => [
                'fontsize' => 20,
                'x' => 150,
                'y' => 620,
                'lineWidth' => 510,
            ],
            'episodeTitle' => [
                'fontsize' => 32,
                'x' => 150,
                'y' => 660,
                'lines' => 3,
                'lineWidth' => 28,
                'leading' => 20,
            ],
            'podcastTitle' => [
                'fontsize' => 20,
                'x' => 150,
                'y' => 620,
                'lineWidth' => 510,
                'lineHeight' => 1.5,
            ],
            'episodeNumbering' => [
                'fontsize' => 18,
                'paddingX' => 10,
                'paddingY' => 5,
                'x' => 180,
                'y' => 540,
                'marginRight' => 10,
            ],
            'timestamp' => [
                'fontsize' => 32,
@@ -95,25 +95,25 @@ class MediaClipper extends BaseConfig
                'x' => 75,
                'y' => 520,
            ],
            'podcastTitle' => [
                'fontsize' => 32,
                'x' => 360,
                'y' => 55,
                'lineWidth' => 670,
            ],
            'episodeTitle' => [
                'fontsize' => 42,
                'x' => 360,
                'y' => 110,
                'lines' => 3,
                'lineWidth' => 32,
                'leading' => 20,
            ],
            'podcastTitle' => [
                'fontsize' => 32,
                'x' => 360,
                'y' => 55,
                'lineWidth' => 670,
                'lineHeight' => 1.5,
            ],
            'episodeNumbering' => [
                'fontsize' => 28,
                'paddingX' => 0,
                'paddingX' => 10,
                'paddingY' => 10,
                'x' => 50,
                'y' => 330,
                'marginRight' => 10,
            ],
            'timestamp' => [
                'fontsize' => 48,
@@ -156,25 +156,26 @@ class MediaClipper extends BaseConfig
                'x' => 85,
                'y' => 320,
            ],
            'podcastTitle' => [
                'fontsize' => 28,
                'x' => 260,
                'y' => 50,
                'lines' => 1,
                'lineWidth' => 700,
            ],
            'episodeTitle' => [
                'fontsize' => 36,
                'x' => 260,
                'y' => 90,
                'lines' => 2,
                'lineWidth' => 38,
                'leading' => 20,
            ],
            'podcastTitle' => [
                'fontsize' => 28,
                'x' => 260,
                'y' => 50,
                'lineWidth' => 700,
                'lineHeight' => 1.5,
            ],
            'episodeNumbering' => [
                'fontsize' => 20,
                'paddingX' => 0,
                'paddingY' => 10,
                'x' => 40,
                'y' => 240,
                'fontsize' => 24,
                'paddingX' => 10,
                'paddingY' => 5,
                'marginRight' => 10,
            ],
            'timestamp' => [
                'fontsize' => 48,
+149 −67
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ class VideoClip
    protected ?string $episodeNumbering = null;

    /**
     * @var array<string, int|array<string, int|string>>
     * @var array<string, mixed>
     */
    protected array $dimensions = [];

@@ -224,38 +224,63 @@ class VideoClip
            return false;
        }

        $this->addTextToImage(
            $background,
            $this->dimensions['episodeTitle']['x'],
            $this->dimensions['episodeTitle']['y'],
            $this->episode->title,
            $this->getFont('episodeTitle'),
            $this->dimensions['episodeTitle']['fontsize'],
            $this->dimensions['episodeTitle']['lines'],
            $this->dimensions['episodeTitle']['lineWidth'],
            $this->dimensions['episodeTitle']['leading'],
        );
        $this->addTextToImage(
        $this->addParagraphToImage(
            $background,
            $this->dimensions['podcastTitle']['x'],
            $this->dimensions['podcastTitle']['y'],
            $this->episode->podcast->title,
            $this->getFont('podcastTitle'),
            $this->dimensions['podcastTitle']['fontsize']
            $this->dimensions['podcastTitle']['fontsize'],
            $this->dimensions['podcastTitle']['lineWidth'],
            $this->dimensions['podcastTitle']['lines'] ?? 1,
            $this->dimensions['podcastTitle']['lineHeight'] ?? 1,
        );

        $episodeNumberingWidth = 0;
        if ($this->episodeNumbering) {
            $episodeTitleBox = $this->calculateTextBox(
                $this->dimensions['episodeTitle']['fontsize'],
                0,
                $this->getFont('episodeTitle'),
                $this->episode->title
            );
            $episodeNumberingBox = $this->calculateTextBox(
                $this->dimensions['episodeNumbering']['fontsize'],
                0,
                $this->getFont('episodeNumbering'),
                $this->episodeNumbering
            );
            if (! $episodeTitleBox || ! $episodeNumberingBox) {
                return false;
            }

            $episodeTitleCenter = (int) ($episodeTitleBox['height'] / 2);
            $episodeNumberingCenter = (int) (($episodeNumberingBox['height'] + ($this->dimensions['episodeNumbering']['paddingY'] * 2)) / 2);
            $episodeNumberingWidth = $episodeNumberingBox['width'] + ($this->dimensions['episodeNumbering']['paddingX'] * 2);

            $this->addTextWithBox(
                $background,
                $this->dimensions['episodeNumbering']['x'],
                $this->dimensions['episodeNumbering']['y'],
                $this->dimensions['episodeTitle']['x'],
                $this->dimensions['episodeTitle']['y'] + $episodeTitleCenter - $episodeNumberingCenter,
                $this->episodeNumbering,
                $this->getFont('episodeNumbering'),
                $this->dimensions['episodeNumbering']['fontsize'],
                $this->dimensions['episodeNumbering']['paddingX'],
                $this->dimensions['episodeNumbering']['paddingY'],
            );
            // dd($this->episodeNumbering);
        }
        $this->addParagraphToImage(
            $background,
            $this->dimensions['episodeTitle']['x'],
            $this->dimensions['episodeTitle']['y'],
            $this->episode->title,
            $this->getFont('episodeTitle'),
            $this->dimensions['episodeTitle']['fontsize'],
            $this->dimensions['episodeTitle']['lineWidth'],
            $this->dimensions['episodeTitle']['lines'],
            $this->dimensions['episodeTitle']['lineHeight'] ?? 1,
            $episodeNumberingWidth + ($episodeNumberingWidth === 0 ? 0 : $this->dimensions['episodeNumbering']['marginRight']),
        );

        // Add quotes for subtitles
        $quotes = imagecreatefrompng(config('MediaClipper')->quotesImage);
@@ -412,16 +437,17 @@ class VideoClip
        return imagecopy($background, $foreground, $x, $y, 0, 0, $width, $height);
    }

    private function addTextToImage(
    private function addParagraphToImage(
        GdImage $image,
        int $x,
        int $y,
        string $text,
        string $fontPath,
        int $fontsize,
        int $lineWidth,
        int $numberOfLines = 1,
        int $lineWidth = 32,
        int $leading = 5,
        float $lineHeight = 1,
        int $paragraphIndent = 0,
    ): bool {
        // Allocate A Color For The Text
        $white = imagecolorallocate($image, 255, 255, 255);
@@ -430,35 +456,60 @@ class VideoClip
            return false;
        }

        if ($numberOfLines > 1) {
            $text = wordwrap($text, $lineWidth, PHP_EOL);
            preg_match_all('~' . PHP_EOL . '~', $text, $matches, PREG_OFFSET_CAPTURE);
            if (array_key_exists($numberOfLines - 1, $matches[0])) {
                $text = substr($text, 0, (int) $matches[0][$numberOfLines - 1][1]) . '…';
        $lines = $this->textToParagraph($text, $fontPath, $fontsize, $lineWidth, $numberOfLines, $paragraphIndent);
        if (! $lines) {
            return false;
        }

            $lines = explode(PHP_EOL, $text);
        $leading = (int) ($fontsize * $lineHeight);
        foreach ($lines as $i => $line) {
            // Print line On Image
            imagettftext(
                $image,
                $fontsize,
                0,
                    $x,
                    $y + $fontsize + (($fontsize + $leading) * $i),
                $x + ($paragraphIndent * ($i === 0 ? 1 : 0)),
                $y + $fontsize + ($leading * $i),
                $white,
                $fontPath,
                $line
            );
        }
        } else {
            // Print Text On Image
            imagettftext($image, $fontsize, 0, $x, $y + $fontsize, $white, $fontPath, $text);
        }

        return true;
    }

    /**
     * Adapted from: https://www.php.net/manual/fr/function.imagettfbbox.php#105593
     *
     * @return array<string, mixed>|false
     */
    private function calculateTextBox(int $fontSize, int $fontAngle, string $fontFile, string $text): array | false
    {
        /************
        simple function that calculates the *exact* bounding box (single pixel precision).
        The function returns an associative array with these keys:
        left, top:  coordinates you will pass to imagettftext
        width, height: dimension of the image you have to create
        *************/
        $bbox = imagettfbbox($fontSize, $fontAngle, $fontFile, $text);
        if (! $bbox) {
            return false;
        }
        $minX = min([$bbox[0], $bbox[2], $bbox[4], $bbox[6]]);
        $maxX = max([$bbox[0], $bbox[2], $bbox[4], $bbox[6]]);
        $minY = min([$bbox[1], $bbox[3], $bbox[5], $bbox[7]]);
        $maxY = max([$bbox[1], $bbox[3], $bbox[5], $bbox[7]]);

        return [
            'left' => abs($minX) - 1,
            'top' => abs($minY),
            'width' => $maxX - $minX,
            'height' => $maxY - $minY,
            'box' => $bbox,
        ];
    }

    private function addTextWithBox(
        GdImage $image,
        int $x,
@@ -471,7 +522,7 @@ class VideoClip
    ): bool {
        // Create some colors
        $white = imagecolorallocate($image, 255, 255, 255);
        $bgColor = imagecolorallocate($image, 0, 86, 74);
        $bgColor = imagecolorallocate($image, 0, 61, 11);

        if ($white === false || $bgColor === false) {
            return false;
@@ -495,33 +546,64 @@ class VideoClip
    }

    /**
     * Adapted from: https://www.php.net/manual/fr/function.imagettfbbox.php#105593
     *
     * @return array<string, mixed>|false
     * @return array<int, string>|false
     */
    private function calculateTextBox(int $fontSize, int $fontAngle, string $fontFile, string $text): array | false
    {
        /************
        simple function that calculates the *exact* bounding box (single pixel precision).
        The function returns an associative array with these keys:
        left, top:  coordinates you will pass to imagettftext
        width, height: dimension of the image you have to create
        *************/
        $bbox = imagettfbbox($fontSize, $fontAngle, $fontFile, $text);
    private function textToParagraph(
        string $text,
        string $fontPath,
        int $fontsize,
        int $lineWidth,
        int $numberOfLines,
        int $paragraphIndent = 0,
    ): array | false {
        // check length of text
        $bbox = $this->calculateTextBox($fontsize, 0, $fontPath, $text);
        if (! $bbox) {
            return false;
        }
        $minX = min([$bbox[0], $bbox[2], $bbox[4], $bbox[6]]);
        $maxX = max([$bbox[0], $bbox[2], $bbox[4], $bbox[6]]);
        $minY = min([$bbox[1], $bbox[3], $bbox[5], $bbox[7]]);
        $maxY = max([$bbox[1], $bbox[3], $bbox[5], $bbox[7]]);

        return [
            'left' => abs($minX) - 1,
            'top' => abs($minY),
            'width' => $maxX - $minX,
            'height' => $maxY - $minY,
            'box' => $bbox,
        ];
        // return early if text width is less than line width
        if ($bbox['width'] <= $lineWidth) {
            return [$text];
        }

        // cut text in multiple lines based on the lineWidth property
        $lines = [''];
        $length = $paragraphIndent;
        $words = preg_split('~\b(?=\S)|(?=\s)~', $text);
        if (! $words) {
            return false;
        }

        $wordCount = count($words);
        $lineNumber = 0;
        for ($i = 0; $i < $wordCount; ++$i) {
            $word = $words[$i];
            $wordBox = $this->calculateTextBox($fontsize, 0, $fontPath, $word);
            if (! $wordBox) {
                return false;
            }
            $wordWidth = $wordBox['width'];

            if (($wordWidth + $length) > $lineWidth) {
                ++$lineNumber;
                if ($lineNumber > $numberOfLines - 1) {
                    $lines[$numberOfLines - 1] .= '…';
                    break;
                }
                $lines[$lineNumber] = '';
                $length = 0;

                // If the current word is just a space, don't bother. Skip (saves a weird-looking gap in the text).
                if ($word === ' ') {
                    continue;
                }
            }

            $lines[$lineNumber] .= $word;
            $length += $wordWidth;
        }

        return $lines;
    }
}