diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 4f875095..f8ad08ec 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -43,23 +43,24 @@ class Autoload extends AutoloadConfig */ public $psr4 = [ APP_NAMESPACE => APPPATH, + 'Config' => APPPATH . 'Config/', 'Modules' => ROOTPATH . 'modules/', 'Modules\Admin' => ROOTPATH . 'modules/Admin/', - 'Modules\Auth' => ROOTPATH . 'modules/Auth/', 'Modules\Analytics' => ROOTPATH . 'modules/Analytics/', - 'Modules\Install' => ROOTPATH . 'modules/Install/', - 'Modules\Update' => ROOTPATH . 'modules/Update/', - 'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/', - 'Modules\Media' => ROOTPATH . 'modules/Media/', - 'Modules\WebSub' => ROOTPATH . 'modules/WebSub/', 'Modules\Api\Rest\V1' => ROOTPATH . 'modules/Api/Rest/V1', + 'Modules\Auth' => ROOTPATH . 'modules/Auth/', + 'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/', + 'Modules\Install' => ROOTPATH . 'modules/Install/', + 'Modules\Media' => ROOTPATH . 'modules/Media/', + 'Modules\MediaClipper' => ROOTPATH . 'modules/MediaClipper/', + 'Modules\PodcastImport' => ROOTPATH . 'modules/PodcastImport/', 'Modules\PremiumPodcasts' => ROOTPATH . 'modules/PremiumPodcasts/', - 'Config' => APPPATH . 'Config/', + 'Modules\Update' => ROOTPATH . 'modules/Update/', + 'Modules\WebSub' => ROOTPATH . 'modules/WebSub/', + 'Themes' => ROOTPATH . 'themes', 'ViewComponents' => APPPATH . 'Libraries/ViewComponents/', 'ViewThemes' => APPPATH . 'Libraries/ViewThemes/', - 'MediaClipper' => APPPATH . 'Libraries/MediaClipper/', 'Vite' => APPPATH . 'Libraries/Vite/', - 'Themes' => ROOTPATH . 'themes', ]; /** diff --git a/app/Config/Tasks.php b/app/Config/Tasks.php new file mode 100644 index 00000000..06dd531c --- /dev/null +++ b/app/Config/Tasks.php @@ -0,0 +1,55 @@ +command('fediverse:broadcast') + ->everyMinute() + ->named('fediverse-broadcast'); + + $schedule->command('websub:publish') + ->everyMinute() + ->named('websub-publish'); + + $schedule->command('video-clips:generate') + ->everyMinute() + ->named('video-clips-generate'); + + $schedule->command('podcast:import') + ->everyMinute() + ->named('podcast-import'); + } +} diff --git a/app/Controllers/WebmanifestController.php b/app/Controllers/WebmanifestController.php index d42da053..742709a3 100644 --- a/app/Controllers/WebmanifestController.php +++ b/app/Controllers/WebmanifestController.php @@ -89,11 +89,11 @@ class WebmanifestController extends Controller $webmanifest = [ 'name' => esc($podcast->title), - 'short_name' => '@' . esc($podcast->handle), + 'short_name' => $podcast->at_handle, 'description' => $podcast->description, 'lang' => $podcast->language_code, 'start_url' => $podcast->link, - 'scope' => '/@' . esc($podcast->handle), + 'scope' => '/' . $podcast->at_handle, 'display' => 'standalone', 'orientation' => 'portrait', 'theme_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['theme'], diff --git a/app/Entities/Clip/BaseClip.php b/app/Entities/Clip/BaseClip.php index 4a2186a3..3528bfe8 100644 --- a/app/Entities/Clip/BaseClip.php +++ b/app/Entities/Clip/BaseClip.php @@ -129,15 +129,15 @@ class BaseClip extends Entity $this->getMedia() ->setFile($file); $this->getMedia() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('audio'))->updateMedia($this->getMedia()); } else { $media = new Audio([ 'file_key' => $fileKey, 'language_code' => $this->getPodcast() ->language_code, - 'uploaded_by' => $this->attributes['created_by'], - 'updated_by' => $this->attributes['created_by'], + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $media->setFile($file); diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 3108afcc..e16fc81c 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -188,15 +188,15 @@ class Episode extends Entity $this->getCover() ->setFile($file); $this->getCover() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('image'))->updateMedia($this->getCover()); } else { $cover = new Image([ 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '.' . $file->getExtension(), 'sizes' => config('Images') ->podcastCoverSizes, - 'uploaded_by' => user_id(), - 'updated_by' => user_id(), + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $cover->setFile($file); @@ -234,7 +234,7 @@ class Episode extends Entity $this->getAudio() ->setFile($file); $this->getAudio() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('audio'))->updateMedia($this->getAudio()); } else { $audio = new Audio([ @@ -244,8 +244,8 @@ class Episode extends Entity ) . '.' . $file->getExtension(), 'language_code' => $this->getPodcast() ->language_code, - 'uploaded_by' => user_id(), - 'updated_by' => user_id(), + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $audio->setFile($file); @@ -274,15 +274,15 @@ class Episode extends Entity $this->getTranscript() ->setFile($file); $this->getTranscript() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('transcript'))->updateMedia($this->getTranscript()); } else { $transcript = new Transcript([ 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-transcript.' . $file->getExtension(), 'language_code' => $this->getPodcast() ->language_code, - 'uploaded_by' => user_id(), - 'updated_by' => user_id(), + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $transcript->setFile($file); @@ -311,15 +311,15 @@ class Episode extends Entity $this->getChapters() ->setFile($file); $this->getChapters() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('chapters'))->updateMedia($this->getChapters()); } else { $chapters = new Chapters([ 'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-chapters' . '.' . $file->getExtension(), 'language_code' => $this->getPodcast() ->language_code, - 'uploaded_by' => user_id(), - 'updated_by' => user_id(), + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $chapters->setFile($file); diff --git a/app/Entities/Person.php b/app/Entities/Person.php index b09aa6b6..961f5603 100644 --- a/app/Entities/Person.php +++ b/app/Entities/Person.php @@ -66,15 +66,15 @@ class Person extends Entity $this->getAvatar() ->setFile($file); $this->getAvatar() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('image'))->updateMedia($this->getAvatar()); } else { $avatar = new Image([ 'file_key' => 'persons/' . $this->attributes['unique_name'] . '.' . $file->getExtension(), 'sizes' => config('Images') ->personAvatarSizes, - 'uploaded_by' => user_id(), - 'updated_by' => user_id(), + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $avatar->setFile($file); diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index 174e50a7..3aff5e15 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -41,6 +41,7 @@ use RuntimeException; * @property int $actor_id * @property Actor|null $actor * @property string $handle + * @property string $at_handle * @property string $link * @property string $feed_url * @property string $title @@ -240,15 +241,15 @@ class Podcast extends Entity $this->getCover() ->setFile($file); $this->getCover() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('image'))->updateMedia($this->getCover()); } else { $cover = new Image([ 'file_key' => 'podcasts/' . $this->attributes['handle'] . '/cover.' . $file->getExtension(), 'sizes' => config('Images') ->podcastCoverSizes, - 'uploaded_by' => user_id(), - 'updated_by' => user_id(), + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $cover->setFile($file); @@ -283,15 +284,15 @@ class Podcast extends Entity $this->getBanner() ->setFile($file); $this->getBanner() - ->updated_by = (int) user_id(); + ->updated_by = $this->attributes['updated_by']; (new MediaModel('image'))->updateMedia($this->getBanner()); } else { $banner = new Image([ 'file_key' => 'podcasts/' . $this->attributes['handle'] . '/banner.' . $file->getExtension(), 'sizes' => config('Images') ->podcastBannerSizes, - 'uploaded_by' => user_id(), - 'updated_by' => user_id(), + 'uploaded_by' => $this->attributes['updated_by'], + 'updated_by' => $this->attributes['updated_by'], ]); $banner->setFile($file); diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php index 773f6360..351d2ec0 100644 --- a/app/Helpers/components_helper.php +++ b/app/Helpers/components_helper.php @@ -218,8 +218,8 @@ if (! function_exists('publication_status_banner')) { } return << -

+

- publication_status !== 'published'): ?> + + get('Import.current') === $podcast->handle): ?> + + + publication_status !== 'published'): ?> published_at, $podcast->id, $podcast->publication_status) ?> - + +
renderSection('content') ?> diff --git a/themes/cp_admin/_sidebar.php b/themes/cp_admin/_sidebar.php index 2d1a9deb..e5a8d271 100644 --- a/themes/cp_admin/_sidebar.php +++ b/themes/cp_admin/_sidebar.php @@ -7,7 +7,7 @@ $navigation = [ ], 'podcasts' => [ 'icon' => 'mic', - 'items' => ['podcast-list', 'podcast-create', 'podcast-import'], + 'items' => ['podcast-list', 'podcast-create', 'all-podcast-imports', 'podcast-imports-add'], ], 'persons' => [ 'icon' => 'folder-user', diff --git a/themes/cp_admin/episode/list.php b/themes/cp_admin/episode/list.php index a0e5d275..2e603a59 100644 --- a/themes/cp_admin/episode/list.php +++ b/themes/cp_admin/episode/list.php @@ -27,7 +27,7 @@

- + diff --git a/themes/cp_admin/import/_queue_table.php b/themes/cp_admin/import/_queue_table.php new file mode 100644 index 00000000..fe51a3b1 --- /dev/null +++ b/themes/cp_admin/import/_queue_table.php @@ -0,0 +1,131 @@ + + + lang('PodcastImport.queue.status.label'), + 'cell' => function (PodcastImportTask $importTask) { + $pillVariantMap = [ + 'queued' => 'default', + 'pending' => 'warning', + 'running' => 'primary', + 'canceled' => 'default', + 'failed' => 'danger', + 'passed' => 'success', + ]; + + $pillIconMap = [ + 'queued' => 'timer', + 'pending' => 'pause', + 'running' => 'loader', + 'canceled' => 'forbid', + 'failed' => 'close', + 'passed' => 'check', + ]; + + $pillIconClassMap = [ + 'queued' => '', + 'pending' => '', + 'running' => 'animate-spin', + 'canceled' => '', + 'failed' => '', + 'passed' => '', + ]; + + $errorHint = $importTask->status === TaskStatus::Failed ? hint_tooltip($importTask->error, 'ml-1') : ''; + + return '
' . lang('PodcastImport.queue.status.' . $importTask->status->value) . '' . $errorHint . '
'; + }, + ], + [ + 'header' => lang('PodcastImport.queue.feed'), + 'cell' => function (PodcastImportTask $importTask) { + return << + {$importTask->feed_url} + @{$importTask->handle} +
+ HTML; + }, + ], + [ + 'header' => lang('PodcastImport.queue.duration'), + 'cell' => function (PodcastImportTask $importTask) { + $duration = '-'; + if ($importTask->started_at !== null) { + if ($importTask->ended_at !== null) { + $duration = '
' . + '
' . format_duration((int) $importTask->getDuration(), true) . '
' . + '
' . relative_time($importTask->ended_at) . '
' . + '
'; + } else { + $duration = '
' . format_duration(($importTask->started_at->difference(Time::now()))->getSeconds(), true) . '
'; + } + } + + return $duration; + }, + ], + [ + 'header' => lang('PodcastImport.queue.imported_episodes'), + 'cell' => function (PodcastImportTask $importTask) { + if ($importTask->episodes_count) { + $progressPercentage = (int) ($importTask->getProgress() * 100) . '%'; + $moreInfoHelper = hint_tooltip(lang('PodcastImport.queue.imported_episodes_hint', [ + 'newlyImportedCount' => $importTask->episodes_newly_imported, + 'alreadyImportedCount' => $importTask->episodes_already_imported, + ]), 'ml-1'); + return << + {$progressPercentage} +

+ {$importTask->episodes_imported} out of {$importTask->episodes_count} + {$moreInfoHelper} +

+
+ HTML; + } + + return '-'; + }, + ], + [ + 'header' => lang('Common.list.actions'), + 'cell' => function (PodcastImportTask $importTask) { + return '
' . + '' . + '' . + '
'; + }, + ], + ], + $podcastImportsQueue +) ?> diff --git a/themes/cp_admin/import/add_to_queue.php b/themes/cp_admin/import/add_to_queue.php new file mode 100644 index 00000000..377b2062 --- /dev/null +++ b/themes/cp_admin/import/add_to_queue.php @@ -0,0 +1,62 @@ +extend('_layout') ?> + +section('title') ?> + +endSection() ?> + +section('pageTitle') ?> + +endSection() ?> + +section('content') ?> + + + + + + + + + + + + +
+ +
+ + +
+
+ + + + + +
+ + + + + + +endSection() ?> diff --git a/themes/cp_admin/import/podcast_queue.php b/themes/cp_admin/import/podcast_queue.php new file mode 100644 index 00000000..9c38a5f0 --- /dev/null +++ b/themes/cp_admin/import/podcast_queue.php @@ -0,0 +1,20 @@ +extend('_layout') ?> + +section('title') ?> + +endSection() ?> + +section('pageTitle') ?> + +endSection() ?> + +section('headerRight') ?> + +endSection() ?> + + +section('content') ?> + +include('import/_queue_table'); ?> + +endSection() ?> diff --git a/themes/cp_admin/import/queue.php b/themes/cp_admin/import/queue.php new file mode 100644 index 00000000..6182c6f6 --- /dev/null +++ b/themes/cp_admin/import/queue.php @@ -0,0 +1,23 @@ + +extend('_layout') ?> + +section('title') ?> + +endSection() ?> + +section('pageTitle') ?> + +endSection() ?> + +section('headerRight') ?> + +endSection() ?> + + +section('content') ?> + +include('import/_queue_table'); ?> + +endSection() ?> diff --git a/themes/cp_admin/podcast/_sidebar.php b/themes/cp_admin/podcast/_sidebar.php index 35db7c54..3558cd51 100644 --- a/themes/cp_admin/podcast/_sidebar.php +++ b/themes/cp_admin/podcast/_sidebar.php @@ -3,7 +3,7 @@ $podcastNavigation = [ 'dashboard' => [ 'icon' => 'dashboard', - 'items' => ['podcast-view', 'podcast-edit', 'podcast-persons-manage'], + 'items' => ['podcast-view', 'podcast-edit', 'podcast-persons-manage', 'podcast-imports'], ], 'episodes' => [ 'icon' => 'play-circle', diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php index 3b05c882..d4b9b283 100644 --- a/themes/cp_admin/podcast/edit.php +++ b/themes/cp_admin/podcast/edit.php @@ -248,7 +248,7 @@ - + diff --git a/themes/cp_admin/podcast/import.php b/themes/cp_admin/podcast/import.php deleted file mode 100644 index bd2ee775..00000000 --- a/themes/cp_admin/podcast/import.php +++ /dev/null @@ -1,99 +0,0 @@ -extend('_layout') ?> - -section('title') ?> - -endSection() ?> - -section('pageTitle') ?> - -endSection() ?> - -section('content') ?> - - - -
- - - - - - - - - -
- -
- - -
-
- - - - - -
- - - -
- - <title> - <link> -
- -
- - <description> - <itunes:summary> - <itunes:subtitle> + <itunes:summary> - <content:encoded> -
- - - - - - - -
- - - -
- - -endSection() ?> diff --git a/themes/cp_admin/podcast/list.php b/themes/cp_admin/podcast/list.php index 28cbd916..ae6567c4 100644 --- a/themes/cp_admin/podcast/list.php +++ b/themes/cp_admin/podcast/list.php @@ -9,7 +9,7 @@ endSection() ?> section('headerRight') ?> - + endSection() ?>