From e462abf6d660e41d2170c52caf45704008de58e9 Mon Sep 17 00:00:00 2001 From: Yassine Doghri <yassine@doghri.fr> Date: Tue, 7 Dec 2021 16:58:12 +0000 Subject: [PATCH] feat(video-clips): replace hardcoded colors with config's theme colors --- .../MediaClipper/Config/MediaClipper.php | 20 ++++++ app/Libraries/MediaClipper/VideoClip.php | 61 ++++++++++++------ app/Libraries/MediaClipper/quotes.png | Bin 4247 -> 2822 bytes app/Resources/icons/clapperboard.svg | 6 ++ modules/Admin/Config/Routes.php | 16 +++-- ...ontroller.php => VideoClipsController.php} | 30 +++++++-- .../Admin/Language/en/EpisodeNavigation.php | 4 +- .../Admin/Language/fr/EpisodeNavigation.php | 3 + themes/cp_admin/episode/_sidebar.php | 6 +- themes/cp_admin/episode/video_clips_list.php | 13 ++++ .../{video_clips.php => video_clips_new.php} | 16 +++-- 11 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 app/Resources/icons/clapperboard.svg rename modules/Admin/Controllers/{ClipsController.php => VideoClipsController.php} (71%) create mode 100644 themes/cp_admin/episode/video_clips_list.php rename themes/cp_admin/episode/{video_clips.php => video_clips_new.php} (73%) diff --git a/app/Libraries/MediaClipper/Config/MediaClipper.php b/app/Libraries/MediaClipper/Config/MediaClipper.php index 52702da64b..c457185199 100644 --- a/app/Libraries/MediaClipper/Config/MediaClipper.php +++ b/app/Libraries/MediaClipper/Config/MediaClipper.php @@ -203,4 +203,24 @@ class MediaClipper extends BaseConfig ], ], ]; + + /** + * @var array<string, array<string, string|int[]>> + */ + public array $themes = [ + 'pine' => [ + 'background' => [0, 86, 74], + 'text' => [255, 255, 255], + // subtitle hex color is BGR (Blue, Green, Red), + 'subtitles' => 'FFFFFF', + // quotes image MUST BE black + 'quotes' => [0, 148, 134], + 'episodeNumberingBg' => [0, 61, 11], + 'episodeNumberingText' => [255, 255, 255], + 'progressbar' => '009486', + 'timestampBg' => '00564A', + 'timestampText' => 'FFFFFF', + 'soundwaves' => 'F2FAF9', + ], + ]; } diff --git a/app/Libraries/MediaClipper/VideoClip.php b/app/Libraries/MediaClipper/VideoClip.php index 9be8e42f37..909bd13e82 100644 --- a/app/Libraries/MediaClipper/VideoClip.php +++ b/app/Libraries/MediaClipper/VideoClip.php @@ -44,32 +44,46 @@ class VideoClip protected ?string $episodeNumbering = null; + /** + * @var 'landscape'|'portrait'|'squared' + */ + protected string $format = 'landscape'; + /** * @var array<string, mixed> */ protected array $dimensions = []; /** - * @var 'landscape'|'portrait'|'squared' + * @var 'pine'|'crimson'|'lake'|'amber'|'jacaranda'|'onyx' */ - protected string $format = 'landscape'; + protected string $theme = 'pine'; + + /** + * @var array<string, mixed> + */ + protected array $colors = []; /** * @param 'landscape'|'portrait'|'squared' $format + * @param 'pine'|'crimson'|'lake'|'amber'|'jacaranda'|'onyx' $theme */ public function __construct( protected Episode $episode, protected float $start, protected float $end, string $format, + string $theme, ) { $this->duration = $end - $start; $this->format = $format; $this->episodeNumbering = $this->episodeNumbering($this->episode->number, $this->episode->season_number); $this->dimensions = config('MediaClipper') ->formats[$format]; + $this->colors = config('MediaClipper') + ->themes[$theme]; - helper('media'); + helper(['media']); $this->audioInput = media_path($this->episode->audio_file_path); $this->episodeCoverPath = media_path($this->episode->cover->path); @@ -118,7 +132,7 @@ 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=white,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=0xFFFFFF,format=yuva420p[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]', @@ -128,11 +142,11 @@ class VideoClip "[waves_t3]scale={$this->dimensions['soundwaves']['rescaleWidth']}:{$this->dimensions['soundwaves']['rescaleHeight']}[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=white:box=1:boxcolor=0x00564A:boxborderw={$this->dimensions['timestamp']['padding']}[v3]", - "color=c=0x009486:s={$this->dimensions['width']}x{$this->dimensions['progressbar']['height']}[progressbar]", + ) . ":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]", + "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']},BorderStyle=1,Outline=0,Shadow=0,MarginL={$this->dimensions['subtitles']['marginL']},MarginR={$this->dimensions['subtitles']['marginR']},MarginV={$this->dimensions['subtitles']['marginV']}'[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']}',format=yuv420p,colormatrix=bt601:bt2020[outv]", ]; $videoClipCmd = [ @@ -142,12 +156,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=white:{$this->dimensions['width']}x{$this->dimensions['height']}", + "-f lavfi -i color=0x{$this->colors['soundwaves']}:{$this->dimensions['width']}x{$this->dimensions['height']}", '-filter_complex "' . implode(';', $filters) . '"', '-map "[outv]"', '-map 0:a', '-acodec copy', '-vcodec libx264', + '-pix_fmt yuv420p', "{$this->videoClipOutput}", ]; @@ -184,7 +199,7 @@ class VideoClip private function generateVideoClipBg(): bool { - $background = $this->generateColouredBg($this->dimensions['width'], $this->dimensions['height']); + $background = $this->generateBackground($this->dimensions['width'], $this->dimensions['height']); if ($background === null) { return false; @@ -265,6 +280,8 @@ class VideoClip $this->episodeNumbering, $this->getFont('episodeNumbering'), $this->dimensions['episodeNumbering']['fontsize'], + $this->colors['episodeNumberingText'], + $this->colors['episodeNumberingBg'], $this->dimensions['episodeNumbering']['paddingX'], $this->dimensions['episodeNumbering']['paddingY'], ); @@ -289,6 +306,8 @@ class VideoClip return false; } + imagefilter($quotes, IMG_FILTER_COLORIZE, ...$this->colors['quotes']); + $scaledQuotes = $this->scaleImage( $quotes, $this->dimensions['quotes']['width'], @@ -319,7 +338,7 @@ class VideoClip return config('MediaClipper')->fontsFolder . self::FONTS[$name]; } - private function generateColouredBg(int $width, int $height): ?GdImage + private function generateBackground(int $width, int $height): ?GdImage { $background = imagecreatetruecolor($width, $height); @@ -327,7 +346,7 @@ class VideoClip return null; } - $coloredBackground = imagecolorallocate($background, 0, 86, 74); + $coloredBackground = imagecolorallocate($background, ...$this->colors['background']); if ($coloredBackground === false) { return null; @@ -450,9 +469,9 @@ class VideoClip int $paragraphIndent = 0, ): bool { // Allocate A Color For The Text - $white = imagecolorallocate($image, 255, 255, 255); + $textColor = imagecolorallocate($image, ...$this->colors['text']); - if ($white === false) { + if ($textColor === false) { return false; } @@ -470,7 +489,7 @@ class VideoClip 0, $x + ($paragraphIndent * ($i === 0 ? 1 : 0)), $y + $fontsize + ($leading * $i), - $white, + $textColor, $fontPath, $line ); @@ -510,6 +529,10 @@ class VideoClip ]; } + /** + * @param int[] $boxTextColor + * @param int[] $boxBgColor + */ private function addTextWithBox( GdImage $image, int $x, @@ -517,14 +540,16 @@ class VideoClip string $text, string $fontPath, int $fontsize, + array $boxTextColor, + array $boxBgColor, int $paddingX = 0, int $paddingY = 0, ): bool { // Create some colors - $white = imagecolorallocate($image, 255, 255, 255); - $bgColor = imagecolorallocate($image, 0, 61, 11); + $textColor = imagecolorallocate($image, ...$boxTextColor); + $bgColor = imagecolorallocate($image, ...$boxBgColor); - if ($white === false || $bgColor === false) { + if ($textColor === false || $bgColor === false) { return false; } @@ -540,7 +565,7 @@ class VideoClip $y2 = $y + $bbox['height'] + ($paddingY * 2); imagefilledrectangle($image, $x, $y, $x2, $y2, $bgColor); - imagettftext($image, $fontsize, 0, $x1, $y1, $white, $fontPath, $text); + imagettftext($image, $fontsize, 0, $x1, $y1, $textColor, $fontPath, $text); return true; } diff --git a/app/Libraries/MediaClipper/quotes.png b/app/Libraries/MediaClipper/quotes.png index 2c65242817398e386b3c512453968dc34e2db5ec..41f22cacf9d154f9629ab61c9f60fe516fcd4367 100644 GIT binary patch delta 2615 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 delta 4050 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 W`BrwH$`J+z1_n=8KbLh*2~7Y}yYdSF diff --git a/app/Resources/icons/clapperboard.svg b/app/Resources/icons/clapperboard.svg new file mode 100644 index 0000000000..c5d7d1215e --- /dev/null +++ b/app/Resources/icons/clapperboard.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <g> + <path fill="none" d="M0 0h24v24H0z"/> + <path d="M17.998 7l2.31-4h.7c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h3.006l-2.31 4h2.31l2.31-4h3.69l-2.31 4h2.31l2.31-4h3.69l-2.31 4h2.31z"/> + </g> +</svg> diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php index 469859db1a..59233f6198 100644 --- a/modules/Admin/Config/Routes.php +++ b/modules/Admin/Config/Routes.php @@ -352,15 +352,23 @@ $routes->group( ); $routes->get( 'video-clips', - 'ClipsController::videoClips/$1/$2', + 'VideoClipsController::list/$1/$2', [ - 'as' => 'video-clips', + 'as' => 'video-clips-list', + 'filter' => 'permission:podcast_episodes-edit', + ], + ); + $routes->get( + 'video-clips/new', + 'VideoClipsController::generate/$1/$2', + [ + 'as' => 'video-clips-generate', 'filter' => 'permission:podcast_episodes-edit', ], ); $routes->post( - 'video-clips', - 'ClipsController::generateVideoClip/$1/$2', + 'video-clips/new', + 'VideoClipsController::attemptGenerate/$1/$2', [ 'as' => 'video-clips-generate', 'filter' => 'permission:podcast_episodes-edit', diff --git a/modules/Admin/Controllers/ClipsController.php b/modules/Admin/Controllers/VideoClipsController.php similarity index 71% rename from modules/Admin/Controllers/ClipsController.php rename to modules/Admin/Controllers/VideoClipsController.php index cfb9eb4d2d..3520e2862d 100644 --- a/modules/Admin/Controllers/ClipsController.php +++ b/modules/Admin/Controllers/VideoClipsController.php @@ -18,7 +18,7 @@ use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use MediaClipper\VideoClip; -class ClipsController extends BaseController +class VideoClipsController extends BaseController { protected Podcast $podcast; @@ -55,7 +55,21 @@ class ClipsController extends BaseController return $this->{$method}(...$params); } - public function videoClips(): string + public function list(): string + { + $data = [ + 'podcast' => $this->podcast, + 'episode' => $this->episode, + ]; + + replace_breadcrumb_params([ + 0 => $this->podcast->title, + 1 => $this->episode->title, + ]); + return view('episode/video_clips_list', $data); + } + + public function generate(): string { helper('form'); @@ -66,18 +80,19 @@ class ClipsController extends BaseController replace_breadcrumb_params([ 0 => $this->podcast->title, - 1 => $this->episode->slug, + 1 => $this->episode->title, ]); - return view('episode/video_clips', $data); + return view('episode/video_clips_new', $data); } - public function generateVideoClip(): RedirectResponse + public function attemptGenerate(): RedirectResponse { // TODO: add end_time greater than start_time, with minimum ? $rules = [ - 'format' => 'required|in_list[landscape,portrait,squared]', 'start_time' => 'required|numeric', 'end_time' => 'required|numeric|differs[start_time]', + 'format' => 'required|in_list[' . implode(',', array_keys(config('MediaClipper')->formats)) . ']', + 'theme' => 'required|in_list[' . implode(',', array_keys(config('Colors')->themes)) . ']', ]; if (! $this->validate($rules)) { @@ -92,10 +107,11 @@ class ClipsController extends BaseController (float) $this->request->getPost('start_time'), (float) $this->request->getPost('end_time',), $this->request->getPost('format'), + $this->request->getPost('theme'), ); $clipper->generate(); - return redirect()->route('video-clips', [$this->podcast->id, $this->episode->id])->with( + return redirect()->route('video-clips-generate', [$this->podcast->id, $this->episode->id])->with( 'message', lang('Settings.images.regenerationSuccess') ); diff --git a/modules/Admin/Language/en/EpisodeNavigation.php b/modules/Admin/Language/en/EpisodeNavigation.php index c7f7dba3ea..8e2df5beb4 100644 --- a/modules/Admin/Language/en/EpisodeNavigation.php +++ b/modules/Admin/Language/en/EpisodeNavigation.php @@ -15,6 +15,8 @@ return [ 'episode-edit' => 'Edit episode', 'episode-persons-manage' => 'Manage persons', 'embed-add' => 'Embeddable player', + 'clips' => 'Clips', 'soundbites-edit' => 'Soundbites', - 'video-clips' => 'Video clips', + 'video-clips-list' => 'Video clips', + 'video-clips-generate' => 'New video clip', ]; diff --git a/modules/Admin/Language/fr/EpisodeNavigation.php b/modules/Admin/Language/fr/EpisodeNavigation.php index d85c74d2c1..288b9203dd 100644 --- a/modules/Admin/Language/fr/EpisodeNavigation.php +++ b/modules/Admin/Language/fr/EpisodeNavigation.php @@ -15,5 +15,8 @@ return [ 'episode-edit' => 'Modifier l’épisode', 'episode-persons-manage' => 'Gestion des intervenants', 'embed' => 'Lecteur intégré', + 'clips' => 'Extraits', 'soundbites-edit' => 'Extraits sonores', + 'video-clips-list' => 'Extraits video', + 'video-clips-generate' => 'Nouvel extrait video', ]; diff --git a/themes/cp_admin/episode/_sidebar.php b/themes/cp_admin/episode/_sidebar.php index 32332db025..9fbcf49333 100644 --- a/themes/cp_admin/episode/_sidebar.php +++ b/themes/cp_admin/episode/_sidebar.php @@ -3,7 +3,11 @@ $podcastNavigation = [ 'dashboard' => [ 'icon' => 'dashboard', - 'items' => ['episode-view', 'episode-edit', 'episode-persons-manage', 'embed-add', 'soundbites-edit', 'video-clips'], + 'items' => ['episode-view', 'episode-edit', 'episode-persons-manage', 'embed-add'], + ], + 'clips' => [ + 'icon' => 'clapperboard', + 'items' => ['video-clips-list', 'video-clips-generate', 'soundbites-edit'], ], ]; ?> diff --git a/themes/cp_admin/episode/video_clips_list.php b/themes/cp_admin/episode/video_clips_list.php new file mode 100644 index 0000000000..f357a62267 --- /dev/null +++ b/themes/cp_admin/episode/video_clips_list.php @@ -0,0 +1,13 @@ +<?= $this->extend('_layout') ?> + +<?= $this->section('title') ?> +<?= lang('Episode.video_clips.title') ?> +<?= $this->endSection() ?> + +<?= $this->section('pageTitle') ?> +<?= lang('Episode.video_clips.title') ?> +<?= $this->endSection() ?> + +<?= $this->section('content') ?> + +<?= $this->endSection() ?> diff --git a/themes/cp_admin/episode/video_clips.php b/themes/cp_admin/episode/video_clips_new.php similarity index 73% rename from themes/cp_admin/episode/video_clips.php rename to themes/cp_admin/episode/video_clips_new.php index 314b060cbe..abcb48b22a 100644 --- a/themes/cp_admin/episode/video_clips.php +++ b/themes/cp_admin/episode/video_clips_new.php @@ -28,23 +28,31 @@ </div> </fieldset> +<div class="grid gap-4 grid-cols-colorButtons"> + <?php foreach (config('Colors')->themes as $themeName => $color): ?> + <Forms.ColorRadioButton + class="theme-<?= $themeName ?> mx-auto" + value="<?= $themeName ?>" + name="theme" + isChecked="<?= $themeName === 'pine' ? 'true' : 'false' ?>" ><?= lang('Settings.theme.' . $themeName) ?></Forms.ColorRadioButton> + <?php endforeach; ?> +</div> + <Forms.Field type="number" name="start_time" label="START" required="true" - value="0" + value="5" /> <Forms.Field type="number" name="end_time" label="END" required="true" - value="15" + value="10" /> -<audio></audio> - <Button variant="primary" type="submit"><?= lang('Episode.video_clips.submit') ?></Button> </form> -- GitLab