diff --git a/app/Database/Migrations/2021-12-09-130000_add_clips.php b/app/Database/Migrations/2021-12-09-130000_add_clips.php
index 8e1f710404c7c7ee31bf74f000f85117dfb1e14d..0c42a92299a420bec187489db0a34b6ee832c470 100644
--- a/app/Database/Migrations/2021-12-09-130000_add_clips.php
+++ b/app/Database/Migrations/2021-12-09-130000_add_clips.php
@@ -72,15 +72,19 @@ class AddClips extends Migration
                 'type' => 'INT',
                 'unsigned' => true,
             ],
-            'created_at' => [
+            'job_started_at' => [
                 'type' => 'DATETIME',
+                'null' => true,
             ],
-            'updated_at' => [
+            'job_ended_at' => [
                 'type' => 'DATETIME',
+                'null' => true,
             ],
-            'deleted_at' => [
+            'created_at' => [
+                'type' => 'DATETIME',
+            ],
+            'updated_at' => [
                 'type' => 'DATETIME',
-                'null' => true,
             ],
         ]);
 
diff --git a/app/Entities/Clip/BaseClip.php b/app/Entities/Clip/BaseClip.php
index 77d4c0dae2454a52175b2be3e8f13b9989f58348..00057c2ed291b157c9ec56e378adf4029cc925df 100644
--- a/app/Entities/Clip/BaseClip.php
+++ b/app/Entities/Clip/BaseClip.php
@@ -12,7 +12,6 @@ namespace App\Entities\Clip;
 
 use App\Entities\Episode;
 use App\Entities\Media\Audio;
-use App\Entities\Media\BaseMedia;
 use App\Entities\Media\Video;
 use App\Entities\Podcast;
 use App\Models\EpisodeModel;
@@ -21,6 +20,7 @@ use App\Models\PodcastModel;
 use App\Models\UserModel;
 use CodeIgniter\Entity\Entity;
 use CodeIgniter\Files\File;
+use CodeIgniter\I18n\Time;
 use Modules\Auth\Entities\User;
 
 /**
@@ -34,21 +34,32 @@ use Modules\Auth\Entities\User;
  * @property double $end_time
  * @property double $duration
  * @property string $type
- * @property int $media_id
- * @property Video|Audio $media
+ * @property int|null $media_id
+ * @property Video|Audio|null $media
  * @property array|null $metadata
  * @property string $status
  * @property string $logs
  * @property User $user
  * @property int $created_by
  * @property int $updated_by
+ * @property Time|null $job_started_at
+ * @property Time|null $job_ended_at
  */
 class BaseClip extends Entity
 {
     /**
-     * @var BaseMedia
+     * @var Video|Audio|null
      */
-    protected $media = null;
+    protected $media;
+
+    protected ?int $job_duration = null;
+
+    protected ?float $end_time = null;
+
+    /**
+     * @var string[]
+     */
+    protected $dates = ['created_at', 'updated_at', 'job_started_at', 'job_ended_at'];
 
     /**
      * @var array<string, string>
@@ -75,12 +86,25 @@ class BaseClip extends Entity
     public function __construct(array $data = null)
     {
         parent::__construct($data);
+    }
 
-        if ($this->start_time && $this->duration) {
+    public function getJobDuration(): ?int
+    {
+        if ($this->job_duration === null && $this->job_started_at && $this->job_ended_at) {
+            $this->job_duration = ($this->job_started_at->difference($this->job_ended_at))
+                ->getSeconds();
+        }
+
+        return $this->job_duration;
+    }
+
+    public function getEndTime(): float
+    {
+        if ($this->end_time === null) {
             $this->end_time = $this->start_time + $this->duration;
-        } elseif ($this->start_time && $this->end_time) {
-            $this->duration = $this->end_time - $this->duration;
         }
+
+        return $this->end_time;
     }
 
     public function getPodcast(): ?Podcast
@@ -128,16 +152,12 @@ class BaseClip extends Entity
         return $this;
     }
 
-    /**
-     * @noRector ReturnTypeDeclarationRector
-     */
     public function getMedia(): Audio | Video | null
     {
         if ($this->media_id !== null && $this->media === null) {
             $this->media = (new MediaModel($this->type))->getMediaById($this->media_id);
         }
 
-        // @phpstan-ignore-next-line
         return $this->media;
     }
 }
diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php
index a608e03ba5fc6d5d050f574aa55d6694e9e2771f..89c5a121a757e95258b653bbe994567e0cca5dae 100644
--- a/app/Helpers/components_helper.php
+++ b/app/Helpers/components_helper.php
@@ -26,7 +26,7 @@ if (! function_exists('hint_tooltip')) {
         $tooltip =
             '<span data-tooltip="bottom" tabindex="0" title="' .
             $hintText .
-            '" class="inline-block align-middle text-skin-muted focus:ring-accent';
+            '" class="inline-block align-middle opacity-75 focus:ring-accent';
 
         if ($class !== '') {
             $tooltip .= ' ' . $class;
diff --git a/app/Helpers/misc_helper.php b/app/Helpers/misc_helper.php
index 99a7a75b08d50c91b7b777b04c3b947a84bc5fe8..a270890745b01a1eb0e1e25887c97f28ad0deb64 100644
--- a/app/Helpers/misc_helper.php
+++ b/app/Helpers/misc_helper.php
@@ -136,16 +136,20 @@ if (! function_exists('slugify')) {
 
 if (! function_exists('format_duration')) {
     /**
-     * Formats duration in seconds to an hh:mm:ss string. Doesn't show leading zeros if any.
+     * Formats duration in seconds to an hh:mm:ss string.
      *
      * ⚠️ This uses php's gmdate function so any duration > 86000 seconds (24 hours) will not be formatted properly.
      *
      * @param int $seconds seconds to format
      */
-    function format_duration(int $seconds): string
+    function format_duration(int $seconds, bool $showLeadingZeros = false): string
     {
+        if ($showLeadingZeros) {
+            return gmdate('H:i:s', $seconds);
+        }
+
         if ($seconds < 60) {
-            return '0:' . $seconds;
+            return '0:' . sprintf('%02d', $seconds);
         }
         if ($seconds < 3600) {
             // < 1 hour: returns MM:SS
@@ -153,9 +157,9 @@ if (! function_exists('format_duration')) {
         }
         if ($seconds < 36000) {
             // < 10 hours: returns H:MM:SS
-            return ltrim(gmdate('h:i:s', $seconds), '0');
+            return ltrim(gmdate('H:i:s', $seconds), '0');
         }
-        return gmdate('h:i:s', $seconds);
+        return gmdate('H:i:s', $seconds);
     }
 }
 
diff --git a/app/Libraries/MediaClipper/Config/MediaClipper.php b/app/Libraries/MediaClipper/Config/MediaClipper.php
index f4274d6de256761f601f4dfc9e23fa3e43e6d661..e300f24db0e613ecff4a01c59e4b5504d9fbc77a 100644
--- a/app/Libraries/MediaClipper/Config/MediaClipper.php
+++ b/app/Libraries/MediaClipper/Config/MediaClipper.php
@@ -213,7 +213,7 @@ class MediaClipper extends BaseConfig
                 'rescaleHeight' => 1200,
                 'x' => 0,
                 'y' => 600,
-                'mask' => APPPATH . 'Libraries/MediaClipper/soundwaves-mask-squared.png',
+                'mask' => APPPATH . 'Libraries/MediaClipper/soundwaves-mask-square.png',
             ],
             'subtitles' => [
                 'fontsize' => 20,
diff --git a/app/Models/ClipModel.php b/app/Models/ClipModel.php
index e61e6fc5f4dbd380d86b0c4d9a677243f2476517..94364cd209484a1bb6dbfdd6b68ad45f7f6c4e72 100644
--- a/app/Models/ClipModel.php
+++ b/app/Models/ClipModel.php
@@ -49,6 +49,8 @@ class ClipModel extends Model
         'logs',
         'created_by',
         'updated_by',
+        'job_started_at',
+        'job_ended_at',
     ];
 
     /**
diff --git a/app/Resources/icons/calendar.svg b/app/Resources/icons/calendar.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fe9bf8d9fb3da0271f27e36eff86704546a255af
--- /dev/null
+++ b/app/Resources/icons/calendar.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="M2 11h20v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-9zm15-8h4a1 1 0 0 1 1 1v5H2V4a1 1 0 0 1 1-1h4V1h2v2h6V1h2v2z"/>
+    </g>
+</svg>
diff --git a/app/Resources/icons/loader.svg b/app/Resources/icons/loader.svg
new file mode 100644
index 0000000000000000000000000000000000000000..55da7bdb0318d1400395ec8dea16e9831f210e31
--- /dev/null
+++ b/app/Resources/icons/loader.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="M18.364 5.636L16.95 7.05A7 7 0 1 0 19 12h2a9 9 0 1 1-2.636-6.364z"/>
+    </g>
+</svg>
diff --git a/app/Resources/styles/radioBtn.css b/app/Resources/styles/radioBtn.css
index 509ec4c650e3bab354bc9efae9f64a8cd6732bdd..5a044cd98687d192bcd0e2248c97e597c6dab0b1 100644
--- a/app/Resources/styles/radioBtn.css
+++ b/app/Resources/styles/radioBtn.css
@@ -15,7 +15,7 @@
     }
 
     & + label {
-      @apply inline-block py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3;
+      @apply inline-flex items-center py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3;
       color: hsl(var(--color-text-muted));
     }
   }
diff --git a/app/Views/Components/Forms/RadioButton.php b/app/Views/Components/Forms/RadioButton.php
index 656375d984de6c5017dbb7bcaeaa0087ecd607d6..f7d7015a111004f34766b5809afaa5ed282301a4 100644
--- a/app/Views/Components/Forms/RadioButton.php
+++ b/app/Views/Components/Forms/RadioButton.php
@@ -8,6 +8,8 @@ class RadioButton extends FormComponent
 {
     protected bool $isChecked = false;
 
+    protected ?string $hint = null;
+
     public function setIsChecked(string $value): void
     {
         $this->isChecked = $value === 'true';
@@ -25,10 +27,12 @@ class RadioButton extends FormComponent
             old($this->name) ? old($this->name) === $this->value : $this->isChecked,
         );
 
+        $hint = $this->hint ? hint_tooltip($this->hint, 'ml-1 text-base') : '';
+
         return <<<HTML
             <div>
                 {$radioInput}
-                <label for="{$this->value}">{$this->slot}</label>
+                <label for="{$this->value}">{$this->slot}{$hint}</label>
             </div>
         HTML;
     }
diff --git a/app/Views/Components/Pill.php b/app/Views/Components/Pill.php
index b5927e658e62f6f9c5e07984eaad36375b5eeaec..a6ff6ffa40a1be5ca454915655c56a67c9c2fbfc 100644
--- a/app/Views/Components/Pill.php
+++ b/app/Views/Components/Pill.php
@@ -17,6 +17,10 @@ class Pill extends Component
 
     public ?string $icon = null;
 
+    public ?string $iconClass = '';
+
+    protected ?string $hint = null;
+
     public function render(): string
     {
         $variantClasses = [
@@ -27,10 +31,11 @@ class Pill extends Component
             'warning' => 'text-yellow-900 bg-yellow-100 border-yellow-300',
         ];
 
-        $icon = $this->icon ? icon($this->icon) : '';
+        $icon = $this->icon ? icon($this->icon, $this->iconClass) : '';
+        $hint = $this->hint ? 'data-tooltip="bottom" title="' . $this->hint . '"' : '';
 
         return <<<HTML
-            <span class="inline-flex items-center gap-x-1 px-1 font-semibold text-sm border rounded {$variantClasses[$this->variant]}">{$icon}{$this->slot}</span>
+            <span class="inline-flex items-center gap-x-1 px-1 font-semibold text-sm border rounded {$variantClasses[$this->variant]} {$this->class}" {$hint}>{$icon}{$this->slot}</span>
         HTML;
     }
 }
diff --git a/crontab b/crontab
index fc44ff63d30e0836f967dd6c259766cf508109a0..3ad6aeb4c33a617ca889eee105886d40ef83c03d 100644
--- a/crontab
+++ b/crontab
@@ -1 +1,2 @@
-* * * * * /usr/local/bin/php /castopod/public/index.php scheduled-activities
+* * * * * /usr/local/bin/php /castopod-host/public/index.php scheduled-activities
+* * * * * /usr/local/bin/php /castopod-host/public/index.php scheduled-video-clips
diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php
index b32a81748d399143c6f7869ac71013e62e62d292..a41bdb5d0a4c2c4e0880f0ab78164e045d063ada 100644
--- a/modules/Admin/Config/Routes.php
+++ b/modules/Admin/Config/Routes.php
@@ -365,17 +365,17 @@ $routes->group(
                         );
                         $routes->get(
                             'video-clips/new',
-                            'VideoClipsController::generate/$1/$2',
+                            'VideoClipsController::create/$1/$2',
                             [
-                                'as' => 'video-clips-generate',
+                                'as' => 'video-clips-create',
                                 'filter' => 'permission:podcast_episodes-edit',
                             ],
                         );
                         $routes->post(
                             'video-clips/new',
-                            'VideoClipsController::attemptGenerate/$1/$2',
+                            'VideoClipsController::attemptCreate/$1/$2',
                             [
-                                'as' => 'video-clips-generate',
+                                'as' => 'video-clips-create',
                                 'filter' => 'permission:podcast_episodes-edit',
                             ],
                         );
@@ -387,6 +387,14 @@ $routes->group(
                                 'filter' => 'permission:podcast_episodes-edit',
                             ],
                         );
+                        $routes->get(
+                            'video-clips/(:num)/delete',
+                            'VideoClipsController::delete/$1/$2/$3',
+                            [
+                                'as' => 'video-clip-delete',
+                                'filter' => 'permission:podcast_episodes-edit',
+                            ],
+                        );
                         $routes->get(
                             'embed',
                             'EpisodeController::embed/$1/$2',
diff --git a/modules/Admin/Controllers/SchedulerController.php b/modules/Admin/Controllers/SchedulerController.php
index 043451798558493c0ac2e4e59b80615c9702cb2b..e3f9cfd5997ea1af5b1af5aed5b835676043b8ce 100644
--- a/modules/Admin/Controllers/SchedulerController.php
+++ b/modules/Admin/Controllers/SchedulerController.php
@@ -12,6 +12,7 @@ namespace Modules\Admin\Controllers;
 
 use App\Models\ClipModel;
 use CodeIgniter\Controller;
+use CodeIgniter\I18n\Time;
 use MediaClipper\VideoClipper;
 
 class SchedulerController extends Controller
@@ -41,6 +42,7 @@ class SchedulerController extends Controller
             (new ClipModel())
                 ->update($scheduledClip->id, [
                     'status' => 'running',
+                    'job_started_at' => Time::now(),
                 ]);
             $clipper = new VideoClipper(
                 $scheduledClip->episode,
@@ -58,12 +60,14 @@ class SchedulerController extends Controller
                     'media_id' => $scheduledClip->media_id,
                     'status' => 'passed',
                     'logs' => $clipper->logs,
+                    'job_ended_at' => Time::now(),
                 ]);
             } else {
                 // error
                 (new ClipModel())->update($scheduledClip->id, [
                     'status' => 'failed',
                     'logs' => $clipper->logs,
+                    'job_ended_at' => Time::now(),
                 ]);
             }
         }
diff --git a/modules/Admin/Controllers/VideoClipsController.php b/modules/Admin/Controllers/VideoClipsController.php
index f75849bdea7a232a20e4aac9703df99ffcd4cbe2..ea6a329c7e51dd4829e251d225579d6b080813f7 100644
--- a/modules/Admin/Controllers/VideoClipsController.php
+++ b/modules/Admin/Controllers/VideoClipsController.php
@@ -15,6 +15,7 @@ use App\Entities\Episode;
 use App\Entities\Podcast;
 use App\Models\ClipModel;
 use App\Models\EpisodeModel;
+use App\Models\MediaModel;
 use App\Models\PodcastModel;
 use CodeIgniter\Exceptions\PageNotFoundException;
 use CodeIgniter\HTTP\RedirectResponse;
@@ -105,7 +106,7 @@ class VideoClipsController extends BaseController
         return view('episode/video_clip', $data);
     }
 
-    public function generate(): string
+    public function create(): string
     {
         helper('form');
 
@@ -121,12 +122,12 @@ class VideoClipsController extends BaseController
         return view('episode/video_clips_new', $data);
     }
 
-    public function attemptGenerate(): RedirectResponse
+    public function attemptCreate(): RedirectResponse
     {
-        // TODO: add end_time greater than start_time, with minimum ?
         $rules = [
+            'label' => 'required',
             'start_time' => 'required|numeric',
-            'end_time' => 'required|numeric|differs[start_time]',
+            'duration' => 'required|greater_than[0]',
             'format' => 'required|in_list[' . implode(',', array_keys(config('MediaClipper')->formats)) . ']',
             'theme' => 'required|in_list[' . implode(',', array_keys(config('Colors')->themes)) . ']',
         ];
@@ -147,9 +148,9 @@ class VideoClipsController extends BaseController
         ];
 
         $videoClip = new VideoClip([
-            'label' => 'NEW CLIP',
+            'label' => $this->request->getPost('label'),
             'start_time' => (float) $this->request->getPost('start_time'),
-            'end_time' => (float) $this->request->getPost('end_time',),
+            'duration' => (float) $this->request->getPost('duration',),
             'theme' => $theme,
             'format' => $this->request->getPost('format'),
             'type' => 'video',
@@ -162,9 +163,33 @@ class VideoClipsController extends BaseController
 
         (new ClipModel())->insert($videoClip);
 
-        return redirect()->route('video-clips-generate', [$this->podcast->id, $this->episode->id])->with(
+        return redirect()->route('video-clips-list', [$this->podcast->id, $this->episode->id])->with(
             'message',
             lang('Settings.images.regenerationSuccess')
         );
     }
+
+    public function delete(string $videoClipId): RedirectResponse
+    {
+        $videoClip = (new ClipModel())->getVideoClipById((int) $videoClipId);
+
+        if ($videoClip === null) {
+            throw PageNotFoundException::forPageNotFound();
+        }
+
+        if ($videoClip->media === null) {
+            // delete Clip directly
+            (new ClipModel())->delete($videoClipId);
+        } else {
+            $mediaModel = new MediaModel();
+            if (! $mediaModel->deleteMedia($videoClip->media)) {
+                return redirect()
+                    ->back()
+                    ->withInput()
+                    ->with('errors', $mediaModel->errors());
+            }
+        }
+
+        return redirect()->back();
+    }
 }
diff --git a/modules/Admin/Language/en/EpisodeNavigation.php b/modules/Admin/Language/en/EpisodeNavigation.php
index 8e2df5beb441b92df4fed86cf405bb611834c031..6511ff5c00ce509d93000d9ed2ad1f25339decc3 100644
--- a/modules/Admin/Language/en/EpisodeNavigation.php
+++ b/modules/Admin/Language/en/EpisodeNavigation.php
@@ -18,5 +18,5 @@ return [
     'clips' => 'Clips',
     'soundbites-edit' => 'Soundbites',
     'video-clips-list' => 'Video clips',
-    'video-clips-generate' => 'New video clip',
+    'video-clips-create' => 'New video clip',
 ];
diff --git a/modules/Admin/Language/en/VideoClip.php b/modules/Admin/Language/en/VideoClip.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd13c061838231bc72d0dac0b8a56da41b398127
--- /dev/null
+++ b/modules/Admin/Language/en/VideoClip.php
@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright  2021 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+return [
+    'list' => [
+        'title' => 'Video clips',
+        'status' => [
+            'label' => 'Status',
+            'queued' => 'queued',
+            'queued_hint' => 'Clip is waiting to be processed.',
+            'pending' => 'pending',
+            'pending_hint' => 'Clip will be generated shortly.',
+            'running' => 'running',
+            'running_hint' => 'Clip is being generated.',
+            'failed' => 'failed',
+            'failed_hint' => 'Clip could not be generated: script failure.',
+            'passed' => 'passed',
+            'passed_hint' => 'Clip was generated successfully!',
+        ],
+        'clip' => 'Clip',
+        'duration' => 'Duration',
+    ],
+    'title' => 'Video clip: {videoClipLabel}',
+    'download_clip' => 'Download clip',
+    'go_to_page' => 'Go to clip page',
+    'delete' => 'Delete clip',
+    'logs' => 'Job logs',
+    'form' => [
+        'title' => 'New video clip',
+        'params_section_title' => 'Video clip parameters',
+        'clip_title' => 'Clip title',
+        'format' => [
+            'label' => 'Choose a format',
+            'landscape' => 'Landscape',
+            'landscape_hint' => 'With a 16:9 ratio, landscape videos are great for PeerTube, Youtube and Vimeo.',
+            'portrait' => 'Portrait',
+            'portrait_hint' => 'With a 9:16 ratio, portrait videos are great for TikTok, Youtube shorts and Instagram stories.',
+            'squared' => 'Squared',
+            'squared_hint' => 'With a 1:1 ratio, squared videos are great for Mastodon, Facebook, Twitter and LinkedIn.',
+        ],
+        'theme' => 'Select a theme',
+        'start_time' => 'Start at',
+        'duration' => 'Duration',
+        'submit' => 'Create video clip',
+    ],
+];
diff --git a/modules/Admin/Language/fr/EpisodeNavigation.php b/modules/Admin/Language/fr/EpisodeNavigation.php
index 288b9203dda79567a3ddde381af044d9d1f5e42e..b8c82ff7ad5a82b99ffdde3fde5d49aa875cd512 100644
--- a/modules/Admin/Language/fr/EpisodeNavigation.php
+++ b/modules/Admin/Language/fr/EpisodeNavigation.php
@@ -18,5 +18,5 @@ return [
     'clips' => 'Extraits',
     'soundbites-edit' => 'Extraits sonores',
     'video-clips-list' => 'Extraits video',
-    'video-clips-generate' => 'Nouvel extrait video',
+    'video-clips-create' => 'Nouvel extrait video',
 ];
diff --git a/modules/Admin/Language/fr/VideoClip.php b/modules/Admin/Language/fr/VideoClip.php
new file mode 100644
index 0000000000000000000000000000000000000000..bf686ed31695d8dbeb18a8d010e1a6ed0bc281e8
--- /dev/null
+++ b/modules/Admin/Language/fr/VideoClip.php
@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright  2021 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+return [
+    'list' => [
+        'title' => 'Extraits vidéos',
+        'status' => [
+            'label' => 'Statut',
+            'queued' => 'en file d’attente',
+            'queued_hint' => 'L’extrait est dans la file d’attente.',
+            'pending' => 'en attente',
+            'pending_hint' => 'L’extrait va être généré prochainement.',
+            'running' => 'en cours',
+            'running_hint' => 'L’extrait est en cours de génération.',
+            'failed' => 'échec',
+            'failed_hint' => 'L’extrait n’a pas pu être généré : erreur du programme.',
+            'passed' => 'réussite',
+            'passed_hint' => 'L’extrait a été généré avec succès !',
+        ],
+        'clip' => 'Extrait',
+        'duration' => 'Durée',
+    ],
+    'title' => 'Extrait vidéo : {videoClipLabel}',
+    'download_clip' => 'Télécharger l’extrait',
+    'go_to_page' => 'Aller à la page de l’extrait',
+    'delete' => 'Supprimer l’extrait',
+    'logs' => 'Historique d’exécution',
+    'form' => [
+        'title' => 'Nouvel extrait vidéo',
+        'params_section_title' => 'Paramètres de l’extrait vidéo',
+        'clip_title' => 'Titre de l’extrait',
+        'format' => [
+            'label' => 'Choisissez un format',
+            'landscape' => 'Paysage',
+            'landscape_hint' => 'Avec un ratio de 16/9, les vidéos en paysage sont adaptées pour PeerTube, Youtube et Vimeo.',
+            'portrait' => 'Portrait',
+            'portrait_hint' => 'Avec un ratio de 9/16, les vidéos en portrait sont adaptées pour TikTok, les Youtube shorts and les stories Instagram.',
+            'squared' => 'Carré',
+            'squared_hint' => 'Avec un ratio de 1/1,  les vidéos carrées sont adaptées pour Mastodon, Facebook, Twitter et LinkedIn.',
+        ],
+        'theme' => 'Sélectionnez un thème',
+        'start_time' => 'Démarrer à',
+        'duration' => 'Durée',
+        'submit' => 'Créer un extrait vidéo',
+    ],
+];
diff --git a/themes/cp_admin/episode/_sidebar.php b/themes/cp_admin/episode/_sidebar.php
index 9fbcf49333ea7c7f3509b9bd105dfb6b731b87d3..10c0affe9dbc26c070554188e1323a3a675649ba 100644
--- a/themes/cp_admin/episode/_sidebar.php
+++ b/themes/cp_admin/episode/_sidebar.php
@@ -7,7 +7,7 @@ $podcastNavigation = [
     ],
     'clips' => [
         'icon' => 'clapperboard',
-        'items' => ['video-clips-list', 'video-clips-generate', 'soundbites-edit'],
+        'items' => ['video-clips-list', 'video-clips-create', 'soundbites-edit'],
     ],
 ]; ?>
 
diff --git a/themes/cp_admin/episode/edit.php b/themes/cp_admin/episode/edit.php
index 6e62156348ad678834e3778028393679577a4bb0..3b1e6873e0f632eaed4982fd88cebbb448412ace 100644
--- a/themes/cp_admin/episode/edit.php
+++ b/themes/cp_admin/episode/edit.php
@@ -162,7 +162,7 @@
     <div class="py-2 tab-panels">
         <section id="transcript-file-upload" class="flex items-center tab-panel">
             <?php if ($episode->transcript) : ?>
-                <div class="flex mb-1 gap-x-2">
+                <div class="flex items-center mb-1 gap-x-2">
                     <?= anchor(
                 $episode->transcript->file_url,
                 icon('file', 'mr-2 text-skin-muted') .
diff --git a/themes/cp_admin/episode/video_clip.php b/themes/cp_admin/episode/video_clip.php
index dad7a8ce7569cd9cd32cec79c94aaab85b004f1c..0504104c250c71c3b9d8aa8ac085144d2d464976 100644
--- a/themes/cp_admin/episode/video_clip.php
+++ b/themes/cp_admin/episode/video_clip.php
@@ -1,13 +1,13 @@
 <?= $this->extend('_layout') ?>
 
 <?= $this->section('title') ?>
-<?= lang('Episode.video_clips.title', [
+<?= lang('VideoClip.title', [
     'videoClipLabel' => $videoClip->label,
 ]) ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('pageTitle') ?>
-<?= lang('Episode.video_clips.title', [
+<?= lang('VideoClip.title', [
     'videoClipLabel' => $videoClip->label,
 ]) ?>
 <?= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/video_clips_list.php b/themes/cp_admin/episode/video_clips_list.php
index 71a5d0f65d782678c34059b6959a4b4b8d280e1f..c718c3930ffcbbb3b74b861998f229f8f801a32f 100644
--- a/themes/cp_admin/episode/video_clips_list.php
+++ b/themes/cp_admin/episode/video_clips_list.php
@@ -1,18 +1,24 @@
+<?php declare(strict_types=1);
+
+use App\Entities\Clip\VideoClip;
+use CodeIgniter\I18n\Time;
+
+?>
 <?= $this->extend('_layout') ?>
 
 <?= $this->section('title') ?>
-<?= lang('Episode.video_clips.title') ?>
+<?= lang('VideoClip.list.title') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('pageTitle') ?>
-<?= lang('Episode.video_clips.title') ?>
+<?= lang('VideoClip.list.title') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
 <?= data_table(
     [
         [
-            'header' => lang('VideoClip.list.status'),
+            'header' => lang('VideoClip.list.status.label'),
             'cell' => function ($videoClip): string {
                 $pillVariantMap = [
                     'queued' => 'default',
@@ -26,36 +32,84 @@
                 $pillIconMap = [
                     'queued' => 'timer',
                     'pending' => 'pause',
-                    'running' => 'play',
+                    'running' => 'loader',
                     'canceled' => 'forbid',
                     'failed' => 'close',
                     'passed' => 'check',
                 ];
 
-                return '<Pill variant="' . $pillVariantMap[$videoClip->status] . '" icon="' . $pillIconMap[$videoClip->status] . '">' . $videoClip->status . '</Pill>';
+                $pillIconClassMap = [
+                    'queued' => '',
+                    'pending' => '',
+                    'running' => 'animate-spin',
+                    'canceled' => '',
+                    'failed' => '',
+                    'passed' => '',
+                ];
+
+                return '<Pill variant="' . $pillVariantMap[$videoClip->status] . '" icon="' . $pillIconMap[$videoClip->status] . '" iconClass="' . $pillIconClassMap[$videoClip->status] . '" hint="' . lang('VideoClip.list.status.' . $videoClip->status . '_hint') . '">' . lang('VideoClip.list.status.' . $videoClip->status) . '</Pill>';
             },
         ],
         [
-            'header' => lang('VideoClip.list.label'),
+            'header' => lang('VideoClip.list.clip'),
             'cell' => function ($videoClip): string {
                 $formatClass = [
                     'landscape' => 'aspect-video',
                     'portrait' => 'aspect-[9/16]',
                     'squared' => 'aspect-square',
                 ];
-                return '<a href="' . route_to('video-clip', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id) . '" class="inline-flex items-center font-semibold hover:underline gap-x-2 focus:ring-accent"><div class="relative"><span class="absolute block w-3 h-3 rounded-full -bottom-1 -left-1" data-tooltip="bottom" title="' . $videoClip->theme['name'] . '" style="background-color:hsl(' . $videoClip->theme['preview'] . ')"></span><div class="flex items-center justify-center h-6 overflow-hidden bg-black rounded-sm aspect-video" data-tooltip="bottom" title="' . $videoClip->format . '"><span class="flex items-center justify-center h-full text-white bg-gray-400 ' . $formatClass[$videoClip->format] . '"><Icon glyph="play"/></span></div></div>' . $videoClip->label . '</a>';
+                return '<a href="' . route_to('video-clip', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id) . '" class="inline-flex items-center w-full group gap-x-2 focus:ring-accent"><div class="relative"><span class="absolute block w-3 h-3 rounded-full ring-2 ring-white -bottom-1 -left-1" data-tooltip="bottom" title="' . lang('Settings.theme.' . $videoClip->theme['name']) . '" style="background-color:hsl(' . $videoClip->theme['preview'] . ')"></span><div class="flex items-center justify-center h-6 overflow-hidden bg-black rounded-sm aspect-video" data-tooltip="bottom" title="' . $videoClip->format . '"><span class="flex items-center justify-center h-full text-white bg-gray-400 ' . $formatClass[$videoClip->format] . '"><Icon glyph="play"/></span></div></div><div class="flex flex-col"><div class="text-sm">#' . $videoClip->id . ' – <span class="font-semibold group-hover:underline">' . $videoClip->label . '</span><span class="ml-1 text-sm">by ' . $videoClip->user->username . '</span></div><span class="text-xs">' . format_duration((int) $videoClip->duration) . '</span></div></a>';
             },
         ],
         [
-            'header' => lang('VideoClip.list.clip_id'),
-            'cell' => function ($videoClip): string {
-                return '<a href="' . route_to('video-clip', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id) . '" class="font-semibold hover:underline focus:ring-accent">#' . $videoClip->id . '</a><span class="ml-1 text-sm">by ' . $videoClip->user->username . '</span>';
+            'header' => lang('VideoClip.list.duration'),
+            'cell' => function (VideoClip $videoClip): string {
+                $duration = '';
+                if ($videoClip->job_started_at !== null) {
+                    if ($videoClip->job_ended_at !== null) {
+                        $duration = '<div class="flex flex-col text-xs gap-y-1">' .
+                        '<div class="inline-flex items-center gap-x-1"><Icon glyph="timer" class="text-sm text-gray-400" />' . format_duration($videoClip->job_duration, true) . '</div>' .
+                        '<div class="inline-flex items-center gap-x-1"><Icon glyph="calendar" class="text-sm text-gray-400" />' . relative_time($videoClip->job_ended_at) . '</div>' .
+                        '</div>';
+                    } else {
+                        $duration = '<div class="inline-flex items-center text-xs gap-x-1"><Icon glyph="timer" class="text-sm text-gray-400" />' . format_duration(($videoClip->job_started_at->difference(Time::now()))->getSeconds(), true) . '</div>';
+                    }
+                }
+
+                return $duration;
             },
         ],
         [
             'header' => lang('Common.actions'),
             'cell' => function ($videoClip): string {
-                return '…';
+                $downloadButton = '';
+                if ($videoClip->media) {
+                    helper('misc');
+                    $filename = 'clip-' . slugify($videoClip->label) . "-{$videoClip->start_time}-{$videoClip->end_time}";
+                    $downloadButton = '<IconButton glyph="download" uri="' . $videoClip->media->file_url . '" download="' . $filename . '">' . lang('VideoClip.download_clip') . '</IconButton>';
+                }
+
+                return '<div class="inline-flex items-center gap-x-2">' . $downloadButton .
+                '<button id="more-dropdown-' . $videoClip->id . '" type="button" class="inline-flex items-center p-1 rounded-full focus:ring-accent" data-dropdown="button" data-dropdown-target="more-dropdown-' . $videoClip->id . '-menu" aria-haspopup="true" aria-expanded="false">' .
+                        icon('more') .
+                        '</button>' .
+                        '<DropdownMenu id="more-dropdown-' . $videoClip->id . '-menu" labelledby="more-dropdown-' . $videoClip->id . '" offsetY="-24" items="' . esc(json_encode([
+                            [
+                                'type' => 'link',
+                                'title' => lang('VideoClip.go_to_page'),
+                                'uri' => route_to('video-clip', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id),
+                            ],
+                            [
+                                'type' => 'separator',
+                            ],
+                            [
+                                'type' => 'link',
+                                'title' => lang('VideoClip.delete'),
+                                'uri' => route_to('video-clip-delete', $videoClip->podcast_id, $videoClip->episode_id, $videoClip->id),
+                                'class' => 'font-semibold text-red-600',
+                            ],
+                        ])) . '" />' .
+                        '</div>';
             },
         ],
     ],
diff --git a/themes/cp_admin/episode/video_clips_new.php b/themes/cp_admin/episode/video_clips_new.php
index 93ce42a0f2e8aa76f2fdb692591d6b2ef8f5e736..442df6f4655aa9c047edf9ab277dddd6e0ea34be 100644
--- a/themes/cp_admin/episode/video_clips_new.php
+++ b/themes/cp_admin/episode/video_clips_new.php
@@ -1,60 +1,74 @@
 <?= $this->extend('_layout') ?>
 
 <?= $this->section('title') ?>
-<?= lang('Episode.video_clips.title') ?>
+<?= lang('VideoClip.form.title') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('pageTitle') ?>
-<?= lang('Episode.video_clips.title') ?>
+<?= lang('VideoClip.form.title') ?>
 <?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
 
-<form action="<?= route_to('video-clips-generate', $podcast->id, $episode->id) ?>" method="POST" class="flex flex-col max-w-sm gap-y-4">
+<form action="<?= route_to('video-clips-create', $podcast->id, $episode->id) ?>" method="POST" class="flex flex-col gap-y-4">
 
-<fieldset>
-<legend>Format</legend>
-<div class="mx-auto">
-    <input type="radio" name="format" value="landscape" id="landscape" checked="checked"/>
-    <label for="landscape">Landscape - 16:9</label>
-</div>
-<div class="mx-auto">
-    <input type="radio" name="format" value="portrait" id="portrait"/>
-    <label for="portrait">Portrait - 9:16</label>
-</div>
-<div class="mx-auto">
-    <input type="radio" name="format" value="squared" id="square"/>
-    <label for="square">Square - 1:1</label>
-</div>
+<Forms.Section title="<?= lang('VideoClip.form.params_section_title') ?>" >
+ 
+<Forms.Field
+    name="label"
+    label="<?= lang('VideoClip.form.clip_title') ?>"
+    required="true"
+/>
+
+<fieldset class="flex gap-1">
+<legend><?= lang('VideoClip.form.format.label') ?></legend>
+<Forms.RadioButton
+    value="landscape"
+    name="format"
+    hint="<?= lang('VideoClip.form.format.landscape_hint') ?>"><?= lang('VideoClip.form.format.landscape') ?></Forms.RadioButton>
+<Forms.RadioButton
+    value="portrait"
+    name="format"
+    hint="<?= lang('VideoClip.form.format.portrait_hint') ?>"><?= lang('VideoClip.form.format.portrait') ?></Forms.RadioButton>
+<Forms.RadioButton
+    value="squared"
+    name="format"
+    hint="<?= lang('VideoClip.form.format.squared_hint') ?>"><?= lang('VideoClip.form.format.squared') ?></Forms.RadioButton>
 </fieldset>
 
+<fieldset>
+<legend><?= lang('VideoClip.form.theme') ?></legend>
 <div class="grid gap-4 grid-cols-colorButtons">
     <?php foreach (config('MediaClipper')->themes as $themeName => $colors): ?>
         <Forms.ColorRadioButton
         class="mx-auto"
         value="<?= $themeName ?>"
         name="theme"
-        isChecked="<?= $themeName === 'pine' ? 'true' : 'false' ?>"
         style="--color-accent-base: <?= $colors['preview']?>"><?= lang('Settings.theme.' . $themeName) ?></Forms.ColorRadioButton>
     <?php endforeach; ?>
 </div>
+</fieldset>
 
-<Forms.Field
-    type="number"
-    name="start_time"
-    label="START"
-    required="true"
-    value="5"
-/>
-<Forms.Field
-    type="number"
-    name="end_time"
-    label="END"
-    required="true"
-    value="10"
-/>
+<div class="flex flex-col gap-x-2 gap-y-4 md:flex-row">
+    <Forms.Field
+        type="number"
+        name="start_time"
+        label="<?= lang('VideoClip.form.start_time') ?>"
+        required="true"
+        step="0.001"
+    />
+    <Forms.Field
+        type="number"
+        name="duration"
+        label="<?= lang('VideoClip.form.duration') ?>"
+        required="true"
+        step="0.001"
+    />
+</div>
+
+<Button variant="primary" type="submit" iconRight="arrow-right" class="self-end"><?= lang('VideoClip.form.submit') ?></Button>
 
-<Button variant="primary" type="submit"><?= lang('Episode.video_clips.submit') ?></Button>
+</Forms.Section>
 
 </form>