Loading app/Libraries/MediaClipper/Config/MediaClipper.php +112 −4 Original line number Diff line number Diff line Loading @@ -14,6 +14,8 @@ class MediaClipper extends BaseConfig public string $wavesMask = APPPATH . 'Libraries/MediaClipper/waves-mask.png'; public string $watermark = APPPATH . 'Libraries/MediaClipper/castopod-logo.png'; /** * @var array<string, array<string, int|array<string, float|int|string>>> */ Loading Loading @@ -57,9 +59,15 @@ class MediaClipper extends BaseConfig 'timestamp' => [ 'fontsize' => 32, 'padding' => 10, 'x' => 1680, 'x' => 1620, 'y' => 985, ], 'watermark' => [ 'width' => 90, 'height' => 72, 'x' => 140, 'y' => 960, ], 'progressbar' => [ 'height' => 10, ], Loading Loading @@ -117,10 +125,16 @@ class MediaClipper extends BaseConfig ], 'timestamp' => [ 'fontsize' => 48, 'padding' => 10, 'x' => 735, 'padding' => 14, 'x' => 734, 'y' => 1800, ], 'watermark' => [ 'width' => 120, 'height' => 96, 'x' => 130, 'y' => 1770, ], 'progressbar' => [ 'height' => 10, ], Loading Loading @@ -183,6 +197,12 @@ class MediaClipper extends BaseConfig 'x' => 855, 'y' => 1070, ], 'watermark' => [ 'width' => 120, 'height' => 96, 'x' => 130, 'y' => 1040, ], 'progressbar' => [ 'height' => 10, ], Loading @@ -209,6 +229,8 @@ class MediaClipper extends BaseConfig */ public array $themes = [ 'pine' => [ // Preview must be a HSL colorscheme string 'preview' => '174 100% 29%', 'background' => [0, 86, 74], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), Loading @@ -220,7 +242,93 @@ class MediaClipper extends BaseConfig 'progressbar' => '009486', 'timestampBg' => '00564A', 'timestampText' => 'FFFFFF', 'soundwaves' => 'F2FAF9', 'watermarkBg' => '00564A', 'soundwaves' => [231, 249, 228], ], 'crimson' => [ // Preview must be a HSL colorscheme string 'preview' => '350 87% 61%', 'background' => [179, 31, 57], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [242, 70, 100], 'episodeNumberingBg' => [152, 16, 43], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => 'F24664', 'timestampBg' => 'B31F39', 'timestampText' => 'FFFFFF', 'watermarkBg' => 'B31F39', 'soundwaves' => [253, 206, 215], ], 'lake' => [ // Preview must be a HSL colorscheme string 'preview' => '194 100% 44%', 'background' => [0, 86, 113], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [0, 171, 225], 'episodeNumberingBg' => [0, 43, 57], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => '00ABE1', 'timestampBg' => '005671', 'timestampText' => 'FFFFFF', 'watermarkBg' => '005671', 'soundwaves' => [214, 245, 255], ], 'amber' => [ // Preview must be a HSL colorscheme string 'preview' => '17 100% 57%', 'background' => [177, 50, 0], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [255, 96, 34], 'episodeNumberingBg' => [121, 34, 0], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => 'FF6022', 'timestampBg' => 'B13200', 'timestampText' => 'FFFFFF', 'watermarkBg' => 'B13200', 'soundwaves' => [255, 213, 197], ], 'jacaranda' => [ // Preview must be a HSL colorscheme string 'preview' => '254 72% 52%', 'background' => [47, 21, 132], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [86, 45, 221], 'episodeNumberingBg' => [30, 14, 84], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => '562DDD', 'timestampBg' => '2F1584', 'timestampText' => 'FFFFFF', 'watermarkBg' => '2F1584', 'soundwaves' => [199, 185, 244], ], 'onyx' => [ // Preview must be a HSL colorscheme string 'preview' => '240 17% 2%', 'background' => [5, 5, 7], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [38, 38, 49], 'episodeNumberingBg' => [0, 0, 0], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => 'D5D5E1', 'timestampBg' => '050507', 'timestampText' => 'FFFFFF', 'watermarkBg' => '050507', 'soundwaves' => [213, 213, 225], ], ]; } app/Libraries/MediaClipper/VideoClip.php +59 −25 Original line number Diff line number Diff line Loading @@ -13,6 +13,11 @@ namespace MediaClipper; use App\Entities\Episode; use GdImage; /** * TODO: refactor this by splitting image manipulations + video generation * * @phpstan-ignore-next-line */ class VideoClip { /** Loading Loading @@ -44,21 +49,11 @@ class VideoClip protected ?string $episodeNumbering = null; /** * @var 'landscape'|'portrait'|'squared' */ protected string $format = 'landscape'; /** * @var array<string, mixed> */ protected array $dimensions = []; /** * @var 'pine'|'crimson'|'lake'|'amber'|'jacaranda'|'onyx' */ protected string $theme = 'pine'; /** * @var array<string, mixed> */ Loading @@ -72,11 +67,10 @@ class VideoClip protected Episode $episode, protected float $start, protected float $end, string $format, string $theme, protected string $format = 'landscape', protected string $theme = 'pine', ) { $this->duration = $end - $start; $this->format = $format; $this->episodeNumbering = $this->episodeNumbering($this->episode->number, $this->episode->season_number); $this->dimensions = config('MediaClipper') ->formats[$format]; Loading @@ -95,8 +89,8 @@ class VideoClip $this->soundbiteOutput = $podcastFolder . "/{$this->episode->slug}-soundbite-{$this->start}-to-{$this->end}.mp3"; $this->subtitlesClipOutput = $podcastFolder . "/{$this->episode->slug}-subtitles-clip-{$this->start}-to-{$this->end}.srt"; $this->videoClipBgOutput = $podcastFolder . "/{$this->episode->slug}-clip-bg-{$this->format}.png"; $this->videoClipOutput = $podcastFolder . "/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}.mp4"; $this->videoClipBgOutput = $podcastFolder . "/{$this->episode->slug}-clip-bg-{$this->format}-{$this->theme}.png"; $this->videoClipOutput = $podcastFolder . "/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; } public function soundbite(): void Loading Loading @@ -132,23 +126,30 @@ class VideoClip { // @phpstan-ignore $filters = [ "[0:a]aformat=channel_layouts=mono,showwaves=s={$this->dimensions['soundwaves']['width']}x{$this->dimensions['soundwaves']['height']}:mode=cline:rate=10:colors=0xFFFFFF,format=yuva420p[waves]", "[0:a]aformat=channel_layouts=mono,showwaves=s={$this->dimensions['soundwaves']['width']}x{$this->dimensions['soundwaves']['height']}:mode=cline:rate=10:colors=white,format=rgb24[waves]", "[waves]scale={$this->dimensions['width']}:{$this->dimensions['height']}:flags=neighbor[resizedwaves]", '[resizedwaves][3:v][4:v][5:v]threshold[cleanwaves]', '[cleanwaves][2:v]alphamerge[waves_t]', '[4:v][waves_t]overlay=x=0:y=0:shortest=1[waves_t2]', '[waves_t2]split[m][a]', '[m][a]alphamerge[waves_t3]', "[waves_t3]scale={$this->dimensions['soundwaves']['rescaleWidth']}:{$this->dimensions['soundwaves']['rescaleHeight']}[waves_final]", "[waves_t3]scale={$this->dimensions['soundwaves']['rescaleWidth']}:{$this->dimensions['soundwaves']['rescaleHeight']},lutrgb=r='if(gt(val,100),{$this->colors['soundwaves'][0]},val)':g='if(gt(val,100),{$this->colors['soundwaves'][1]},val)':b='if(gt(val,100),{$this->colors['soundwaves'][2]},val)'[waves_final]", "[1:v][waves_final]overlay=x={$this->dimensions['soundwaves']['x']}:y={$this->dimensions['soundwaves']['y']}:shortest=1,drawtext=fontfile=" . $this->getFont( 'timestamp' ) . ":text='%{pts\:gmtime\:{$this->start}\:%H\\\\\\\\\\:%M\\\\\\\\\\:%S\}':x={$this->dimensions['timestamp']['x']}:y={$this->dimensions['timestamp']['y']}:fontsize={$this->dimensions['timestamp']['fontsize']}:fontcolor=0x{$this->colors['timestampText']}:box=1:boxcolor=0x{$this->colors['timestampBg']}:boxborderw={$this->dimensions['timestamp']['padding']},format=yuv420p,colormatrix=bt601:bt2020[v3]", ) . ":text='%{pts\:gmtime\:{$this->start}\:%H\\\\\\\\\\:%M\\\\\\\\\\:%S\}':x={$this->dimensions['timestamp']['x']}:y={$this->dimensions['timestamp']['y']}:fontsize={$this->dimensions['timestamp']['fontsize']}:fontcolor=0x{$this->colors['timestampText']}:box=1:boxcolor=0x{$this->colors['timestampBg']}:boxborderw={$this->dimensions['timestamp']['padding']}[v3]", "color=c=0x{$this->colors['progressbar']}:s={$this->dimensions['width']}x{$this->dimensions['progressbar']['height']}[progressbar]", "[v3][progressbar]overlay=-w+(w/{$this->duration})*t:0:shortest=1:format=rgb,subtitles={$this->subtitlesClipOutput}:fontsdir=" . config( 'MediaClipper' )->fontsFolder . ":force_style='Fontname=" . self::FONTS['subtitles'] . ",Alignment=5,Fontsize={$this->dimensions['subtitles']['fontsize']},PrimaryColour=&H{$this->colors['subtitles']}&,BorderStyle=1,Outline=0,Shadow=0,MarginL={$this->dimensions['subtitles']['marginL']},MarginR={$this->dimensions['subtitles']['marginR']},MarginV={$this->dimensions['subtitles']['marginV']}',format=yuv420p,colormatrix=bt601:bt2020[outv]", )->fontsFolder . ":force_style='Fontname=" . self::FONTS['subtitles'] . ",Alignment=5,Fontsize={$this->dimensions['subtitles']['fontsize']},PrimaryColour=&H{$this->colors['subtitles']}&,BorderStyle=1,Outline=0,Shadow=0,MarginL={$this->dimensions['subtitles']['marginL']},MarginR={$this->dimensions['subtitles']['marginR']},MarginV={$this->dimensions['subtitles']['marginV']}'[outv]", "[6:v]scale={$this->dimensions['watermark']['width']}:{$this->dimensions['watermark']['height']}[watermark]", "color=0x{$this->colors['watermarkBg']}:{$this->dimensions['watermark']['width']}x{$this->dimensions['watermark']['height']}[over]", '[over][watermark]overlay=x=0:y=0:shortest=1[watermark_box]', "[outv][watermark_box]overlay=x={$this->dimensions['watermark']['x']}:y={$this->dimensions['watermark']['y']}:shortest=1[watermarked]", ]; $watermark = config('MediaClipper') ->watermark; $videoClipCmd = [ 'ffmpeg -y', "-i {$this->soundbiteOutput}", Loading @@ -156,13 +157,13 @@ class VideoClip "-loop 1 -framerate 30 -i {$this->dimensions['soundwaves']['mask']}", "-f lavfi -i color=gray:{$this->dimensions['width']}x{$this->dimensions['height']}", "-f lavfi -i color=black:{$this->dimensions['width']}x{$this->dimensions['height']}", "-f lavfi -i color=0x{$this->colors['soundwaves']}:{$this->dimensions['width']}x{$this->dimensions['height']}", "-f lavfi -i color=white:{$this->dimensions['width']}x{$this->dimensions['height']}", "-loop 1 -framerate 1 -i {$watermark}", '-filter_complex "' . implode(';', $filters) . '"', '-map "[outv]"', '-map "[watermarked]"', '-map 0:a', '-acodec copy', '-vcodec libx264', '-pix_fmt yuv420p', '-vcodec libx264rgb', "{$this->videoClipOutput}", ]; Loading Loading @@ -306,10 +307,16 @@ class VideoClip return false; } imagefilter($quotes, IMG_FILTER_COLORIZE, ...$this->colors['quotes']); $cleanedQuotes = $this->cleanTransparency($quotes); if (! $cleanedQuotes) { return false; } imagefilter($cleanedQuotes, IMG_FILTER_CONTRAST, 255); imagefilter($cleanedQuotes, IMG_FILTER_COLORIZE, ...$this->colors['quotes']); $scaledQuotes = $this->scaleImage( $quotes, $cleanedQuotes, $this->dimensions['quotes']['width'], $this->dimensions['quotes']['height'] ); Loading @@ -318,6 +325,7 @@ class VideoClip return false; } imagesavealpha($scaledQuotes, true); $this->overlayImages( $background, $scaledQuotes, Loading Loading @@ -570,6 +578,32 @@ class VideoClip return true; } /** * This helps getting a truly transparent background for images with transparency: * https://stackoverflow.com/a/2611911 */ private function cleanTransparency(GdImage $image): GdImage | false { $imageBg = imagecolorallocate($image, 0, 0, 0); if ($imageBg === false) { return false; } // removing the black from the image imagecolortransparent($image, $imageBg); // turning off alpha blending (to ensure alpha channel information // is preserved, rather than removed (blending with the rest of the // image in the form of black)) imagealphablending($image, false); // turning on alpha channel information saving (to ensure the full range // of transparency is preserved) imagesavealpha($image, true); return $image; } /** * @return array<int, string>|false */ Loading app/Libraries/MediaClipper/castopod-logo.png 0 → 100644 +2.74 KiB Loading image diff... app/Views/Components/Forms/ColorRadioButton.php +3 −1 Original line number Diff line number Diff line Loading @@ -8,6 +8,8 @@ class ColorRadioButton extends FormComponent { protected bool $isChecked = false; protected string $style = ''; public function setIsChecked(string $value): void { $this->isChecked = $value === 'true'; Loading @@ -26,7 +28,7 @@ class ColorRadioButton extends FormComponent ); return <<<HTML <div class="{$this->class}"> <div class="{$this->class}" style="{$this->style}"> {$radioInput} <label for="{$this->value}" title="{$this->slot}" data-tooltip="bottom"></label> </div> Loading themes/cp_admin/episode/video_clips_new.php +4 −3 Original line number Diff line number Diff line Loading @@ -29,12 +29,13 @@ </fieldset> <div class="grid gap-4 grid-cols-colorButtons"> <?php foreach (config('Colors')->themes as $themeName => $color): ?> <?php foreach (config('MediaClipper')->themes as $themeName => $colors): ?> <Forms.ColorRadioButton class="theme-<?= $themeName ?> mx-auto" class="mx-auto" value="<?= $themeName ?>" name="theme" isChecked="<?= $themeName === 'pine' ? 'true' : 'false' ?>" ><?= lang('Settings.theme.' . $themeName) ?></Forms.ColorRadioButton> isChecked="<?= $themeName === 'pine' ? 'true' : 'false' ?>" style="--color-accent-base: <?= $colors['preview']?>"><?= lang('Settings.theme.' . $themeName) ?></Forms.ColorRadioButton> <?php endforeach; ?> </div> Loading Loading
app/Libraries/MediaClipper/Config/MediaClipper.php +112 −4 Original line number Diff line number Diff line Loading @@ -14,6 +14,8 @@ class MediaClipper extends BaseConfig public string $wavesMask = APPPATH . 'Libraries/MediaClipper/waves-mask.png'; public string $watermark = APPPATH . 'Libraries/MediaClipper/castopod-logo.png'; /** * @var array<string, array<string, int|array<string, float|int|string>>> */ Loading Loading @@ -57,9 +59,15 @@ class MediaClipper extends BaseConfig 'timestamp' => [ 'fontsize' => 32, 'padding' => 10, 'x' => 1680, 'x' => 1620, 'y' => 985, ], 'watermark' => [ 'width' => 90, 'height' => 72, 'x' => 140, 'y' => 960, ], 'progressbar' => [ 'height' => 10, ], Loading Loading @@ -117,10 +125,16 @@ class MediaClipper extends BaseConfig ], 'timestamp' => [ 'fontsize' => 48, 'padding' => 10, 'x' => 735, 'padding' => 14, 'x' => 734, 'y' => 1800, ], 'watermark' => [ 'width' => 120, 'height' => 96, 'x' => 130, 'y' => 1770, ], 'progressbar' => [ 'height' => 10, ], Loading Loading @@ -183,6 +197,12 @@ class MediaClipper extends BaseConfig 'x' => 855, 'y' => 1070, ], 'watermark' => [ 'width' => 120, 'height' => 96, 'x' => 130, 'y' => 1040, ], 'progressbar' => [ 'height' => 10, ], Loading @@ -209,6 +229,8 @@ class MediaClipper extends BaseConfig */ public array $themes = [ 'pine' => [ // Preview must be a HSL colorscheme string 'preview' => '174 100% 29%', 'background' => [0, 86, 74], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), Loading @@ -220,7 +242,93 @@ class MediaClipper extends BaseConfig 'progressbar' => '009486', 'timestampBg' => '00564A', 'timestampText' => 'FFFFFF', 'soundwaves' => 'F2FAF9', 'watermarkBg' => '00564A', 'soundwaves' => [231, 249, 228], ], 'crimson' => [ // Preview must be a HSL colorscheme string 'preview' => '350 87% 61%', 'background' => [179, 31, 57], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [242, 70, 100], 'episodeNumberingBg' => [152, 16, 43], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => 'F24664', 'timestampBg' => 'B31F39', 'timestampText' => 'FFFFFF', 'watermarkBg' => 'B31F39', 'soundwaves' => [253, 206, 215], ], 'lake' => [ // Preview must be a HSL colorscheme string 'preview' => '194 100% 44%', 'background' => [0, 86, 113], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [0, 171, 225], 'episodeNumberingBg' => [0, 43, 57], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => '00ABE1', 'timestampBg' => '005671', 'timestampText' => 'FFFFFF', 'watermarkBg' => '005671', 'soundwaves' => [214, 245, 255], ], 'amber' => [ // Preview must be a HSL colorscheme string 'preview' => '17 100% 57%', 'background' => [177, 50, 0], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [255, 96, 34], 'episodeNumberingBg' => [121, 34, 0], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => 'FF6022', 'timestampBg' => 'B13200', 'timestampText' => 'FFFFFF', 'watermarkBg' => 'B13200', 'soundwaves' => [255, 213, 197], ], 'jacaranda' => [ // Preview must be a HSL colorscheme string 'preview' => '254 72% 52%', 'background' => [47, 21, 132], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [86, 45, 221], 'episodeNumberingBg' => [30, 14, 84], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => '562DDD', 'timestampBg' => '2F1584', 'timestampText' => 'FFFFFF', 'watermarkBg' => '2F1584', 'soundwaves' => [199, 185, 244], ], 'onyx' => [ // Preview must be a HSL colorscheme string 'preview' => '240 17% 2%', 'background' => [5, 5, 7], 'text' => [255, 255, 255], // subtitle hex color is BGR (Blue, Green, Red), 'subtitles' => 'FFFFFF', // quotes image MUST BE black 'quotes' => [38, 38, 49], 'episodeNumberingBg' => [0, 0, 0], 'episodeNumberingText' => [255, 255, 255], 'progressbar' => 'D5D5E1', 'timestampBg' => '050507', 'timestampText' => 'FFFFFF', 'watermarkBg' => '050507', 'soundwaves' => [213, 213, 225], ], ]; }
app/Libraries/MediaClipper/VideoClip.php +59 −25 Original line number Diff line number Diff line Loading @@ -13,6 +13,11 @@ namespace MediaClipper; use App\Entities\Episode; use GdImage; /** * TODO: refactor this by splitting image manipulations + video generation * * @phpstan-ignore-next-line */ class VideoClip { /** Loading Loading @@ -44,21 +49,11 @@ class VideoClip protected ?string $episodeNumbering = null; /** * @var 'landscape'|'portrait'|'squared' */ protected string $format = 'landscape'; /** * @var array<string, mixed> */ protected array $dimensions = []; /** * @var 'pine'|'crimson'|'lake'|'amber'|'jacaranda'|'onyx' */ protected string $theme = 'pine'; /** * @var array<string, mixed> */ Loading @@ -72,11 +67,10 @@ class VideoClip protected Episode $episode, protected float $start, protected float $end, string $format, string $theme, protected string $format = 'landscape', protected string $theme = 'pine', ) { $this->duration = $end - $start; $this->format = $format; $this->episodeNumbering = $this->episodeNumbering($this->episode->number, $this->episode->season_number); $this->dimensions = config('MediaClipper') ->formats[$format]; Loading @@ -95,8 +89,8 @@ class VideoClip $this->soundbiteOutput = $podcastFolder . "/{$this->episode->slug}-soundbite-{$this->start}-to-{$this->end}.mp3"; $this->subtitlesClipOutput = $podcastFolder . "/{$this->episode->slug}-subtitles-clip-{$this->start}-to-{$this->end}.srt"; $this->videoClipBgOutput = $podcastFolder . "/{$this->episode->slug}-clip-bg-{$this->format}.png"; $this->videoClipOutput = $podcastFolder . "/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}.mp4"; $this->videoClipBgOutput = $podcastFolder . "/{$this->episode->slug}-clip-bg-{$this->format}-{$this->theme}.png"; $this->videoClipOutput = $podcastFolder . "/{$this->episode->slug}-clip-{$this->start}-to-{$this->end}-{$this->format}-{$this->theme}.mp4"; } public function soundbite(): void Loading Loading @@ -132,23 +126,30 @@ class VideoClip { // @phpstan-ignore $filters = [ "[0:a]aformat=channel_layouts=mono,showwaves=s={$this->dimensions['soundwaves']['width']}x{$this->dimensions['soundwaves']['height']}:mode=cline:rate=10:colors=0xFFFFFF,format=yuva420p[waves]", "[0:a]aformat=channel_layouts=mono,showwaves=s={$this->dimensions['soundwaves']['width']}x{$this->dimensions['soundwaves']['height']}:mode=cline:rate=10:colors=white,format=rgb24[waves]", "[waves]scale={$this->dimensions['width']}:{$this->dimensions['height']}:flags=neighbor[resizedwaves]", '[resizedwaves][3:v][4:v][5:v]threshold[cleanwaves]', '[cleanwaves][2:v]alphamerge[waves_t]', '[4:v][waves_t]overlay=x=0:y=0:shortest=1[waves_t2]', '[waves_t2]split[m][a]', '[m][a]alphamerge[waves_t3]', "[waves_t3]scale={$this->dimensions['soundwaves']['rescaleWidth']}:{$this->dimensions['soundwaves']['rescaleHeight']}[waves_final]", "[waves_t3]scale={$this->dimensions['soundwaves']['rescaleWidth']}:{$this->dimensions['soundwaves']['rescaleHeight']},lutrgb=r='if(gt(val,100),{$this->colors['soundwaves'][0]},val)':g='if(gt(val,100),{$this->colors['soundwaves'][1]},val)':b='if(gt(val,100),{$this->colors['soundwaves'][2]},val)'[waves_final]", "[1:v][waves_final]overlay=x={$this->dimensions['soundwaves']['x']}:y={$this->dimensions['soundwaves']['y']}:shortest=1,drawtext=fontfile=" . $this->getFont( 'timestamp' ) . ":text='%{pts\:gmtime\:{$this->start}\:%H\\\\\\\\\\:%M\\\\\\\\\\:%S\}':x={$this->dimensions['timestamp']['x']}:y={$this->dimensions['timestamp']['y']}:fontsize={$this->dimensions['timestamp']['fontsize']}:fontcolor=0x{$this->colors['timestampText']}:box=1:boxcolor=0x{$this->colors['timestampBg']}:boxborderw={$this->dimensions['timestamp']['padding']},format=yuv420p,colormatrix=bt601:bt2020[v3]", ) . ":text='%{pts\:gmtime\:{$this->start}\:%H\\\\\\\\\\:%M\\\\\\\\\\:%S\}':x={$this->dimensions['timestamp']['x']}:y={$this->dimensions['timestamp']['y']}:fontsize={$this->dimensions['timestamp']['fontsize']}:fontcolor=0x{$this->colors['timestampText']}:box=1:boxcolor=0x{$this->colors['timestampBg']}:boxborderw={$this->dimensions['timestamp']['padding']}[v3]", "color=c=0x{$this->colors['progressbar']}:s={$this->dimensions['width']}x{$this->dimensions['progressbar']['height']}[progressbar]", "[v3][progressbar]overlay=-w+(w/{$this->duration})*t:0:shortest=1:format=rgb,subtitles={$this->subtitlesClipOutput}:fontsdir=" . config( 'MediaClipper' )->fontsFolder . ":force_style='Fontname=" . self::FONTS['subtitles'] . ",Alignment=5,Fontsize={$this->dimensions['subtitles']['fontsize']},PrimaryColour=&H{$this->colors['subtitles']}&,BorderStyle=1,Outline=0,Shadow=0,MarginL={$this->dimensions['subtitles']['marginL']},MarginR={$this->dimensions['subtitles']['marginR']},MarginV={$this->dimensions['subtitles']['marginV']}',format=yuv420p,colormatrix=bt601:bt2020[outv]", )->fontsFolder . ":force_style='Fontname=" . self::FONTS['subtitles'] . ",Alignment=5,Fontsize={$this->dimensions['subtitles']['fontsize']},PrimaryColour=&H{$this->colors['subtitles']}&,BorderStyle=1,Outline=0,Shadow=0,MarginL={$this->dimensions['subtitles']['marginL']},MarginR={$this->dimensions['subtitles']['marginR']},MarginV={$this->dimensions['subtitles']['marginV']}'[outv]", "[6:v]scale={$this->dimensions['watermark']['width']}:{$this->dimensions['watermark']['height']}[watermark]", "color=0x{$this->colors['watermarkBg']}:{$this->dimensions['watermark']['width']}x{$this->dimensions['watermark']['height']}[over]", '[over][watermark]overlay=x=0:y=0:shortest=1[watermark_box]', "[outv][watermark_box]overlay=x={$this->dimensions['watermark']['x']}:y={$this->dimensions['watermark']['y']}:shortest=1[watermarked]", ]; $watermark = config('MediaClipper') ->watermark; $videoClipCmd = [ 'ffmpeg -y', "-i {$this->soundbiteOutput}", Loading @@ -156,13 +157,13 @@ class VideoClip "-loop 1 -framerate 30 -i {$this->dimensions['soundwaves']['mask']}", "-f lavfi -i color=gray:{$this->dimensions['width']}x{$this->dimensions['height']}", "-f lavfi -i color=black:{$this->dimensions['width']}x{$this->dimensions['height']}", "-f lavfi -i color=0x{$this->colors['soundwaves']}:{$this->dimensions['width']}x{$this->dimensions['height']}", "-f lavfi -i color=white:{$this->dimensions['width']}x{$this->dimensions['height']}", "-loop 1 -framerate 1 -i {$watermark}", '-filter_complex "' . implode(';', $filters) . '"', '-map "[outv]"', '-map "[watermarked]"', '-map 0:a', '-acodec copy', '-vcodec libx264', '-pix_fmt yuv420p', '-vcodec libx264rgb', "{$this->videoClipOutput}", ]; Loading Loading @@ -306,10 +307,16 @@ class VideoClip return false; } imagefilter($quotes, IMG_FILTER_COLORIZE, ...$this->colors['quotes']); $cleanedQuotes = $this->cleanTransparency($quotes); if (! $cleanedQuotes) { return false; } imagefilter($cleanedQuotes, IMG_FILTER_CONTRAST, 255); imagefilter($cleanedQuotes, IMG_FILTER_COLORIZE, ...$this->colors['quotes']); $scaledQuotes = $this->scaleImage( $quotes, $cleanedQuotes, $this->dimensions['quotes']['width'], $this->dimensions['quotes']['height'] ); Loading @@ -318,6 +325,7 @@ class VideoClip return false; } imagesavealpha($scaledQuotes, true); $this->overlayImages( $background, $scaledQuotes, Loading Loading @@ -570,6 +578,32 @@ class VideoClip return true; } /** * This helps getting a truly transparent background for images with transparency: * https://stackoverflow.com/a/2611911 */ private function cleanTransparency(GdImage $image): GdImage | false { $imageBg = imagecolorallocate($image, 0, 0, 0); if ($imageBg === false) { return false; } // removing the black from the image imagecolortransparent($image, $imageBg); // turning off alpha blending (to ensure alpha channel information // is preserved, rather than removed (blending with the rest of the // image in the form of black)) imagealphablending($image, false); // turning on alpha channel information saving (to ensure the full range // of transparency is preserved) imagesavealpha($image, true); return $image; } /** * @return array<int, string>|false */ Loading
app/Views/Components/Forms/ColorRadioButton.php +3 −1 Original line number Diff line number Diff line Loading @@ -8,6 +8,8 @@ class ColorRadioButton extends FormComponent { protected bool $isChecked = false; protected string $style = ''; public function setIsChecked(string $value): void { $this->isChecked = $value === 'true'; Loading @@ -26,7 +28,7 @@ class ColorRadioButton extends FormComponent ); return <<<HTML <div class="{$this->class}"> <div class="{$this->class}" style="{$this->style}"> {$radioInput} <label for="{$this->value}" title="{$this->slot}" data-tooltip="bottom"></label> </div> Loading
themes/cp_admin/episode/video_clips_new.php +4 −3 Original line number Diff line number Diff line Loading @@ -29,12 +29,13 @@ </fieldset> <div class="grid gap-4 grid-cols-colorButtons"> <?php foreach (config('Colors')->themes as $themeName => $color): ?> <?php foreach (config('MediaClipper')->themes as $themeName => $colors): ?> <Forms.ColorRadioButton class="theme-<?= $themeName ?> mx-auto" class="mx-auto" value="<?= $themeName ?>" name="theme" isChecked="<?= $themeName === 'pine' ? 'true' : 'false' ?>" ><?= lang('Settings.theme.' . $themeName) ?></Forms.ColorRadioButton> isChecked="<?= $themeName === 'pine' ? 'true' : 'false' ?>" style="--color-accent-base: <?= $colors['preview']?>"><?= lang('Settings.theme.' . $themeName) ?></Forms.ColorRadioButton> <?php endforeach; ?> </div> Loading