diff --git a/app/Actions/Discord/DiscordVideoNotificationAction.php b/app/Actions/Discord/DiscordVideoNotificationAction.php index 04383bda0..ce5f7fdda 100644 --- a/app/Actions/Discord/DiscordVideoNotificationAction.php +++ b/app/Actions/Discord/DiscordVideoNotificationAction.php @@ -4,6 +4,8 @@ namespace App\Actions\Discord; +use App\Enums\Actions\Models\Wiki\Video\NotificationType; +use App\Enums\Actions\Models\Wiki\Video\ShouldForceThread; use App\Models\Wiki\Video; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Arr; @@ -24,8 +26,8 @@ class DiscordVideoNotificationAction */ public function handle(Collection $videos, array $fields): void { - $type = Arr::get($fields, 'notification-type'); - $shouldForce = Arr::get($fields, 'should-force-thread'); + $type = Arr::get($fields, NotificationType::getFieldKey()); + $shouldForce = ShouldForceThread::from(intval(Arr::get($fields, ShouldForceThread::getFieldKey()))); $newVideos = []; @@ -42,7 +44,7 @@ public function handle(Collection $videos, array $fields): void $anime = $theme->anime; if ($anime->discordthread === null) { - if ($shouldForce === 'no') return; + if ($shouldForce === ShouldForceThread::NO) return; $threadAction = new DiscordThreadAction(); diff --git a/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php b/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php index 16ae9eaa0..79286aab0 100644 --- a/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php +++ b/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php @@ -6,11 +6,13 @@ use App\Actions\ActionResult; use App\Actions\Models\BackfillAction; +use App\Actions\Storage\Wiki\Audio\MoveAudioAction; use App\Actions\Storage\Wiki\Audio\UploadAudioAction; use App\Constants\Config\VideoConstants; use App\Enums\Actions\ActionStatus; use App\Enums\Actions\Models\Wiki\Video\DeriveSourceVideo; use App\Enums\Actions\Models\Wiki\Video\OverwriteAudio; +use App\Enums\Actions\Models\Wiki\Video\ReplaceRelatedAudio; use App\Models\Wiki\Anime; use App\Models\Wiki\Anime\AnimeTheme; use App\Models\Wiki\Anime\Theme\AnimeThemeEntry; @@ -47,7 +49,8 @@ class BackfillAudioAction extends BackfillAction public function __construct( Video $video, protected readonly DeriveSourceVideo $deriveSourceVideo = DeriveSourceVideo::YES, - protected readonly OverwriteAudio $overwriteAudio = OverwriteAudio::NO + protected readonly OverwriteAudio $overwriteAudio = OverwriteAudio::NO, + protected readonly ReplaceRelatedAudio $replaceRelatedAudio = ReplaceRelatedAudio::NO, ) { parent::__construct($video); } @@ -137,6 +140,16 @@ protected function overwriteAudio(): bool return OverwriteAudio::YES === $this->overwriteAudio; } + /** + * Determine if the new audio should replace a related one. + * + * @return bool + */ + protected function replaceRelatedAudio(): bool + { + return ReplaceRelatedAudio::YES === $this->replaceRelatedAudio; + } + /** * Get or Create Audio. * @@ -148,7 +161,7 @@ protected function getAudio(): ?Audio { // Allow bypassing of source video derivation $sourceVideo = $this->deriveSourceVideo() - ? $this->getSourceVideo() + ? $this->getSourceVideo($this->replaceRelatedAudio() ? '<' : '>') : $this->getModel(); // It's possible that the video is not attached to any themes, exit early. @@ -161,6 +174,16 @@ protected function getAudio(): ?Audio // First, attempt to set audio from the source video $audio = $sourceVideo->audio; + // When uploading a BD version we should get the parent audio of a WEB version and + // move the file overwriting the content later. Therefore, the old model is not deleted. + if ($this->replaceRelatedAudio() && $audio instanceof Audio) { + $moveAction = new MoveAudioAction($audio, $this->getModel()->path()); + + $storageResults = $moveAction->handle(); + + $audio = $moveAction->then($storageResults); + } + // Anticipate audio path for FFmpeg save file $audioPath = $audio === null ? Str::replace('webm', 'ogg', $sourceVideo->path) @@ -172,7 +195,7 @@ protected function getAudio(): ?Audio } // Finally, extract audio from the source video - if ($audio === null || $this->overwriteAudio()) { + if ($audio === null || $this->overwriteAudio() || $this->replaceRelatedAudio()) { Log::info("Extracting Audio from Video '{$sourceVideo->getName()}'"); return $this->extractAudio($sourceVideo); @@ -184,16 +207,21 @@ protected function getAudio(): ?Audio /** * Get the source video for the given video. * + * @param string $operation * @return Video|null */ - protected function getSourceVideo(): ?Video + protected function getSourceVideo(string $operation = '>'): ?Video { $source = null; $sourceCandidates = $this->getAdjacentVideos(); foreach ($sourceCandidates as $sourceCandidate) { - if (! $source instanceof Video || $sourceCandidate->getSourcePriority() > $source->getSourcePriority()) { + if ($operation === '>' && (! $source instanceof Video || $sourceCandidate->getSourcePriority() > $source->getSourcePriority())) { + $source = $sourceCandidate; + } + + if ($operation === '<' && (! $source instanceof Video || $sourceCandidate->getSourcePriority() < $source->getSourcePriority())) { $source = $sourceCandidate; } } diff --git a/app/Concerns/Filament/Actions/Models/Wiki/Audio/AttachAudioToRelatedVideosActionTrait.php b/app/Concerns/Filament/Actions/Models/Wiki/Audio/AttachAudioToRelatedVideosActionTrait.php deleted file mode 100644 index cb50e2e47..000000000 --- a/app/Concerns/Filament/Actions/Models/Wiki/Audio/AttachAudioToRelatedVideosActionTrait.php +++ /dev/null @@ -1,54 +0,0 @@ -label(__('filament.actions.audio.attach_related_videos.name')); - - $this->authorize('update', Video::class); - - $this->action(fn (Audio $record) => $this->handle($record)); - } - - /** - * Perform the action on the given models. - * - * @param Audio $audio - * @return void - */ - public function handle(Audio $audio): void - { - $video = $audio->videos()->first(); - - $video->animethemeentries()->each(function (AnimeThemeEntry $firstEntry) use ($audio) { - $theme = $firstEntry->animetheme()->first(); - - $theme->animethemeentries()->each(function (AnimeThemeEntry $entry) use ($audio) { - $entry->videos()->each(function (Video $video) use ($audio) { - Log::info("Associating Audio '{$audio->filename}' with Video '{$video->filename}'"); - $video->audio()->associate($audio)->save(); - }); - }); - }); - } -} diff --git a/app/Concerns/Filament/Actions/Models/Wiki/Video/BackfillAudioActionTrait.php b/app/Concerns/Filament/Actions/Models/Wiki/Video/BackfillAudioActionTrait.php index 844bbb89e..01446a326 100644 --- a/app/Concerns/Filament/Actions/Models/Wiki/Video/BackfillAudioActionTrait.php +++ b/app/Concerns/Filament/Actions/Models/Wiki/Video/BackfillAudioActionTrait.php @@ -7,6 +7,7 @@ use App\Actions\Models\Wiki\Video\Audio\BackfillAudioAction as BackfillAudio; use App\Enums\Actions\Models\Wiki\Video\DeriveSourceVideo; use App\Enums\Actions\Models\Wiki\Video\OverwriteAudio; +use App\Enums\Actions\Models\Wiki\Video\ReplaceRelatedAudio; use App\Filament\Components\Fields\Select; use App\Models\Wiki\Audio; use App\Models\Wiki\Video; @@ -30,6 +31,7 @@ trait BackfillAudioActionTrait final public const DERIVE_SOURCE_VIDEO = 'derive_source_video'; final public const OVERWRITE_AUDIO = 'overwrite_audio'; + final public const REPLACE_RELATED_AUDIO = 'replace_related_audio'; /** * Initial setup for the action. @@ -60,8 +62,9 @@ public function handle(Video $video, array $data): void { $deriveSourceVideo = DeriveSourceVideo::from(intval(Arr::get($data, self::DERIVE_SOURCE_VIDEO))); $overwriteAudio = OverwriteAudio::from(intval(Arr::get($data, self::OVERWRITE_AUDIO))); + $replaceRelatedAudio = ReplaceRelatedAudio::from(intval(Arr::get($data, self::REPLACE_RELATED_AUDIO))); - $action = new BackfillAudio($video, $deriveSourceVideo, $overwriteAudio); + $action = new BackfillAudio($video, $deriveSourceVideo, $overwriteAudio, $replaceRelatedAudio); try { $result = $action->handle(); @@ -95,17 +98,24 @@ public function getForm(Form $form): Form ->schema([ Select::make(self::DERIVE_SOURCE_VIDEO) ->label(__('filament.actions.video.backfill.fields.derive_source.name')) + ->helperText(__('filament.actions.video.backfill.fields.derive_source.help')) ->options(DeriveSourceVideo::asSelectArray()) ->rules(['required', new Enum(DeriveSourceVideo::class)]) - ->default(DeriveSourceVideo::YES->value) - ->helperText(__('filament.actions.video.backfill.fields.derive_source.help')), + ->default(DeriveSourceVideo::YES->value), Select::make(self::OVERWRITE_AUDIO) ->label(__('filament.actions.video.backfill.fields.overwrite.name')) + ->helperText(__('filament.actions.video.backfill.fields.overwrite.help')) ->options(OverwriteAudio::asSelectArray()) ->rules(['required', new Enum(OverwriteAudio::class)]) - ->default(OverwriteAudio::NO->value) - ->helperText(__('filament.actions.video.backfill.fields.overwrite.help')), + ->default(OverwriteAudio::NO->value), + + Select::make(self::REPLACE_RELATED_AUDIO) + ->label(__('filament.actions.video.backfill.fields.replace_related.name')) + ->helperText(__('filament.actions.video.backfill.fields.replace_related.help')) + ->options(ReplaceRelatedAudio::asSelectArray()) + ->rules(['required', new Enum(ReplaceRelatedAudio::class)]) + ->default(ReplaceRelatedAudio::NO->value), ]); } } diff --git a/app/Enums/Actions/Models/Wiki/Video/NotificationType.php b/app/Enums/Actions/Models/Wiki/Video/NotificationType.php new file mode 100644 index 000000000..a4886a10a --- /dev/null +++ b/app/Enums/Actions/Models/Wiki/Video/NotificationType.php @@ -0,0 +1,28 @@ +schema([ - Select::make('notification-type') + Select::make(NotificationType::getFieldKey()) ->label(__('filament.bulk_actions.discord.notification.type.name')) ->helperText(__('filament.bulk_actions.discord.notification.type.help')) - ->options([ - 'added' => __('filament.bulk_actions.discord.notification.type.options.added'), - 'updated' => __('filament.bulk_actions.discord.notification.type.options.updated'), - ]) - ->default('added') + ->options(NotificationType::asSelectArray()) + ->default(NotificationType::ADDED->value) ->required() - ->rules(['required']), + ->rules(['required', new Enum(NotificationType::class)]), - Select::make('should-force-thread') - ->label(__('filament.bulk_actions.discord.notification.should_force.name')) - ->helperText(__('filament.bulk_actions.discord.notification.should_force.help')) - ->options([ - 'yes' => __('filament.bulk_actions.discord.notification.should_force.options.yes'), - 'no' => __('filament.bulk_actions.discord.notification.should_force.options.no'), - ]) - ->default('no') - ->required(), - ]); + Select::make(ShouldForceThread::getFieldKey()) + ->label(__('filament.bulk_actions.discord.notification.should_force.name')) + ->helperText(__('filament.bulk_actions.discord.notification.should_force.help')) + ->options(ShouldForceThread::asSelectArray()) + ->default(ShouldForceThread::NO->value) + ->required() + ->rules(['required', new Enum(ShouldForceThread::class)]), + ]); } } diff --git a/app/Filament/HeaderActions/Models/Wiki/Audio/AttachAudioToRelatedVideosHeaderAction.php b/app/Filament/HeaderActions/Models/Wiki/Audio/AttachAudioToRelatedVideosHeaderAction.php deleted file mode 100644 index 96295862e..000000000 --- a/app/Filament/HeaderActions/Models/Wiki/Audio/AttachAudioToRelatedVideosHeaderAction.php +++ /dev/null @@ -1,17 +0,0 @@ -label(__('filament.resources.singularLabel.video')) ->schema([ Section::make(__('filament.resources.singularLabel.video')) - ->schema( - array_merge( - [ - Hidden::make(AnimeThemeEntry::ATTRIBUTE_ID) - ->label(__('filament.resources.singularLabel.anime_theme_entry')) - ->default(fn (BaseRelationManager|ListVideos $livewire) => $livewire instanceof VideoEntryRelationManager ? $livewire->getOwnerRecord()->getKey() : null), - ], - parent::getForm($form)->getComponents(), - [ - Checkbox::make(Video::ATTRIBUTE_NC) - ->label(__('filament.fields.video.nc.name')) - ->helperText(__('filament.fields.video.nc.help')) - ->nullable() - ->rules(['nullable', 'boolean']), - - Checkbox::make(Video::ATTRIBUTE_SUBBED) - ->label(__('filament.fields.video.subbed.name')) - ->helperText(__('filament.fields.video.subbed.help')) - ->nullable() - ->rules(['nullable', 'boolean']), - - Checkbox::make(Video::ATTRIBUTE_LYRICS) - ->label(__('filament.fields.video.lyrics.name')) - ->helperText(__('filament.fields.video.lyrics.help')) - ->nullable() - ->rules(['nullable', 'boolean']), - - Checkbox::make(Video::ATTRIBUTE_UNCEN) - ->label(__('filament.fields.video.uncen.name')) - ->helperText(__('filament.fields.video.uncen.help')) - ->nullable() - ->rules(['nullable', 'boolean']), - - Select::make(Video::ATTRIBUTE_OVERLAP) - ->label(__('filament.fields.video.overlap.name')) - ->helperText(__('filament.fields.video.overlap.help')) - ->options(VideoOverlap::asSelectArray()) - ->required() - ->rules(['required', new Enum(VideoOverlap::class)]), - - Select::make(Video::ATTRIBUTE_SOURCE) - ->label(__('filament.fields.video.source.name')) - ->helperText(__('filament.fields.video.source.help')) - ->options(VideoSource::asSelectArray()) - ->required() - ->rules(['required', new Enum(VideoSource::class)]), - ], - ) - ), + ->schema([ + Hidden::make(AnimeThemeEntry::ATTRIBUTE_ID) + ->label(__('filament.resources.singularLabel.anime_theme_entry')) + ->default(fn (BaseRelationManager|ListVideos $livewire) => $livewire instanceof VideoEntryRelationManager ? $livewire->getOwnerRecord()->getKey() : null), + + ...parent::getForm($form)->getComponents(), + + Checkbox::make(Video::ATTRIBUTE_NC) + ->label(__('filament.fields.video.nc.name')) + ->helperText(__('filament.fields.video.nc.help')) + ->nullable() + ->rules(['nullable', 'boolean']), + + Checkbox::make(Video::ATTRIBUTE_SUBBED) + ->label(__('filament.fields.video.subbed.name')) + ->helperText(__('filament.fields.video.subbed.help')) + ->nullable() + ->rules(['nullable', 'boolean']), + + Checkbox::make(Video::ATTRIBUTE_LYRICS) + ->label(__('filament.fields.video.lyrics.name')) + ->helperText(__('filament.fields.video.lyrics.help')) + ->nullable() + ->rules(['nullable', 'boolean']), + + Checkbox::make(Video::ATTRIBUTE_UNCEN) + ->label(__('filament.fields.video.uncen.name')) + ->helperText(__('filament.fields.video.uncen.help')) + ->nullable() + ->rules(['nullable', 'boolean']), + + Select::make(Video::ATTRIBUTE_OVERLAP) + ->label(__('filament.fields.video.overlap.name')) + ->helperText(__('filament.fields.video.overlap.help')) + ->options(VideoOverlap::asSelectArray()) + ->required() + ->rules(['required', new Enum(VideoOverlap::class)]), + + Select::make(Video::ATTRIBUTE_SOURCE) + ->label(__('filament.fields.video.source.name')) + ->helperText(__('filament.fields.video.source.help')) + ->options(VideoSource::asSelectArray()) + ->required() + ->rules(['required', new Enum(VideoSource::class)]), + ]), Section::make(__('filament.resources.singularLabel.video_script')) ->schema([ @@ -154,41 +152,31 @@ public function getForm(Form $form): Form Tab::make('audio') ->label(__('filament.actions.video.backfill.name')) - ->schema( - array_merge( - [ - Select::make(self::SHOULD_BACKFILL_AUDIO) - ->label(__('filament.actions.video.backfill.fields.should.name')) - ->helperText(__('filament.actions.video.backfill.fields.should.help')) - ->options([ - 'yes' => __('filament.actions.video.backfill.fields.should.options.yes'), - 'no' => __('filament.actions.video.backfill.fields.should.options.no'), - ]) - ->required() - ->default('yes') - ], - BackfillAudioAction::make()->getForm($form)->getComponents(), - ) - ), + ->schema([ + Select::make(self::SHOULD_BACKFILL_AUDIO) + ->label(__('filament.actions.video.backfill.fields.should.name')) + ->helperText(__('filament.actions.video.backfill.fields.should.help')) + ->options(ShouldBackfillAudio::asSelectArray()) + ->required() + ->rules(['required', new Enum(ShouldBackfillAudio::class)]) + ->default(ShouldBackfillAudio::YES->value), + + ...BackfillAudioAction::make()->getForm($form)->getComponents(), + ]), Tab::make('discord') ->label(__('filament.bulk_actions.discord.notification.name')) - ->schema( - array_merge( - [ - Select::make(self::SHOULD_SEND_NOTIFICATION) - ->label(__('filament.bulk_actions.discord.notification.should_send.name')) - ->helperText(__('filament.bulk_actions.discord.notification.should_send.help')) - ->options([ - 'yes' => __('filament.bulk_actions.discord.notification.should_send.options.yes'), - 'no' => __('filament.bulk_actions.discord.notification.should_send.options.no'), - ]) - ->required() - ->default('yes') - ], - VideoDiscordNotificationBulkAction::make()->getForm($form)->getComponents(), - ) - ), + ->schema([ + Select::make(self::SHOULD_SEND_NOTIFICATION) + ->label(__('filament.bulk_actions.discord.notification.should_send.name')) + ->helperText(__('filament.bulk_actions.discord.notification.should_send.help')) + ->options(ShouldSendNotification::asSelectArray()) + ->required() + ->rules(['required', new Enum(ShouldSendNotification::class)]) + ->default(ShouldSendNotification::YES->value), + + ...VideoDiscordNotificationBulkAction::make()->getForm($form)->getComponents(), + ]), ]) ]); @@ -251,12 +239,15 @@ protected function storageAction(array $fields): UploadVideo */ protected function afterUploaded(BaseModel $video, array $data): void { - if (Arr::get($data, self::SHOULD_BACKFILL_AUDIO) === 'yes') { + $shouldBackfill = ShouldBackfillAudio::from(intval(Arr::get($data, self::SHOULD_BACKFILL_AUDIO))); + $shouldSendNot = ShouldSendNotification::from(intval(Arr::get($data, self::SHOULD_SEND_NOTIFICATION))); + + if ($shouldBackfill === ShouldBackfillAudio::YES) { $backfillAudioAction = new BackfillAudioAction('audio'); $backfillAudioAction->handle($video, $data); } - if (Arr::get($data, self::SHOULD_SEND_NOTIFICATION) === 'yes') { + if ($shouldSendNot === ShouldSendNotification::YES) { $videos = new Collection([$video]); $discordNotificationAction = new VideoDiscordNotificationBulkAction('discord'); diff --git a/lang/en/filament.php b/lang/en/filament.php index 3b645c1d1..da4d20de5 100644 --- a/lang/en/filament.php +++ b/lang/en/filament.php @@ -84,9 +84,6 @@ 'upload' => [ 'name' => 'Upload Audio', ], - 'attach_related_videos' => [ - 'name' => 'Attach to Related Videos', - ], ], 'base' => [ 'cancelButtonText' => 'Cancel', @@ -409,13 +406,13 @@ 'help' => 'If Yes, the Audio will be extracted from the Video even if the Audio already exists. If No, the Audio will only be extracted from the Video if the Audio doesn\'t exist. No should be used in most cases. Yes is useful if we are replacing Audio for a Video.', 'name' => 'Overwrite Audio', ], + 'replace_related' => [ + 'help' => 'If Yes, the engine will search a related audio to move and replace the content. Yes should be used only when uploading a BD version of an uploaded WEB version, so the extracted audio will use the same model.', + 'name' => 'Replace Related Audio', + ], 'should' => [ 'help' => 'Determines whether audio should be extracted.', 'name' => 'Should backfill?', - 'options' => [ - 'no' => 'No', - 'yes' => 'Yes', - ], ], ], 'name' => 'Backfill Audio', @@ -447,26 +444,14 @@ 'should_force' => [ 'help' => 'If yes, the thread will be created if it does not exist', 'name' => 'Should force thread?', - 'options' => [ - 'yes' => 'Yes', - 'no' => 'No', - ], ], 'should_send' => [ 'help' => 'If yes, the notification will be created.', 'name' => 'Should send notification?', - 'options' => [ - 'yes' => 'Yes', - 'no' => 'No', - ], ], 'type' => [ 'help' => 'Are they new videos or replacement?', 'name' => 'Type', - 'options' => [ - 'added' => 'Added', - 'updated' => 'Updated', - ], ], ], ],