mirror of
https://code.castopod.org/adaures/castopod
synced 2025-06-06 18:31:05 +00:00
feat: add media entity and link documents, images and audio files to it
This commit is contained in:
parent
1d1490b06a
commit
6ecf2866cf
@ -50,6 +50,8 @@ RUN apt-get update \
|
|||||||
# gd for image processing
|
# gd for image processing
|
||||||
&& docker-php-ext-configure gd --with-webp --with-jpeg --with-freetype \
|
&& docker-php-ext-configure gd --with-webp --with-jpeg --with-freetype \
|
||||||
&& docker-php-ext-install gd \
|
&& docker-php-ext-install gd \
|
||||||
|
&& docker-php-ext-install exif \
|
||||||
|
&& docker-php-ext-enable exif \
|
||||||
# redis extension for cache
|
# redis extension for cache
|
||||||
&& pecl install -o -f redis \
|
&& pecl install -o -f redis \
|
||||||
&& rm -rf /tmp/pear \
|
&& rm -rf /tmp/pear \
|
||||||
|
@ -46,7 +46,7 @@ class MapController extends BaseController
|
|||||||
'location_url' => $episode->location->url,
|
'location_url' => $episode->location->url,
|
||||||
'episode_link' => $episode->link,
|
'episode_link' => $episode->link,
|
||||||
'podcast_link' => $episode->podcast->link,
|
'podcast_link' => $episode->podcast->link,
|
||||||
'cover_path' => $episode->cover->thumbnail_url,
|
'cover_url' => $episode->cover->thumbnail_url,
|
||||||
'podcast_title' => $episode->podcast->title,
|
'podcast_title' => $episode->podcast->title,
|
||||||
'episode_title' => $episode->title,
|
'episode_title' => $episode->title,
|
||||||
];
|
];
|
||||||
|
83
app/Database/Migrations/2020-05-29-120000_add_media.php
Normal file
83
app/Database/Migrations/2020-05-29-120000_add_media.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2021 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class AddMedia extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'file_path' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
'file_size' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'comment' => 'File size in bytes',
|
||||||
|
],
|
||||||
|
'file_content_type' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 45,
|
||||||
|
],
|
||||||
|
'file_metadata' => [
|
||||||
|
'type' => 'JSON',
|
||||||
|
'nullable' => true,
|
||||||
|
],
|
||||||
|
'type' => [
|
||||||
|
'type' => 'ENUM',
|
||||||
|
'constraint' => ['image', 'audio', 'video', 'transcript', 'chapters', 'document'],
|
||||||
|
],
|
||||||
|
'description' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
|
'language_code' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 2,
|
||||||
|
],
|
||||||
|
'uploaded_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'updated_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'uploaded_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'deleted_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->addKey('id', true);
|
||||||
|
$this->forge->addForeignKey('uploaded_by', 'users', 'id');
|
||||||
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
|
$this->forge->createTable('media');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('media');
|
||||||
|
}
|
||||||
|
}
|
@ -46,25 +46,13 @@ class AddPodcasts extends Migration
|
|||||||
'description_html' => [
|
'description_html' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'TEXT',
|
||||||
],
|
],
|
||||||
'cover_path' => [
|
'cover_id' => [
|
||||||
'type' => 'VARCHAR',
|
'type' => 'INT',
|
||||||
'constraint' => 255,
|
'unsigned' => true,
|
||||||
],
|
],
|
||||||
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
'banner_id' => [
|
||||||
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
'type' => 'INT',
|
||||||
'cover_mimetype' => [
|
'unsigned' => true,
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 13,
|
|
||||||
],
|
|
||||||
'banner_path' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
'null' => true,
|
|
||||||
'default' => null,
|
|
||||||
],
|
|
||||||
'banner_mimetype' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 13,
|
|
||||||
'null' => true,
|
'null' => true,
|
||||||
'default' => null,
|
'default' => null,
|
||||||
],
|
],
|
||||||
@ -209,6 +197,8 @@ class AddPodcasts extends Migration
|
|||||||
$this->forge->addUniqueKey('guid');
|
$this->forge->addUniqueKey('guid');
|
||||||
$this->forge->addUniqueKey('actor_id');
|
$this->forge->addUniqueKey('actor_id');
|
||||||
$this->forge->addForeignKey('actor_id', config('Fediverse')->tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
$this->forge->addForeignKey('actor_id', config('Fediverse')->tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('cover_id', 'media', 'id');
|
||||||
|
$this->forge->addForeignKey('banner_id', 'media', 'id');
|
||||||
$this->forge->addForeignKey('category_id', 'categories', 'id');
|
$this->forge->addForeignKey('category_id', 'categories', 'id');
|
||||||
$this->forge->addForeignKey('language_code', 'languages', 'code');
|
$this->forge->addForeignKey('language_code', 'languages', 'code');
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
|
@ -40,29 +40,9 @@ class AddEpisodes extends Migration
|
|||||||
'type' => 'VARCHAR',
|
'type' => 'VARCHAR',
|
||||||
'constraint' => 128,
|
'constraint' => 128,
|
||||||
],
|
],
|
||||||
'audio_file_path' => [
|
'audio_id' => [
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
],
|
|
||||||
'audio_file_duration' => [
|
|
||||||
// exact value for duration with max 99999,999 ~ 27.7 hours
|
|
||||||
'type' => 'DECIMAL(8,3)',
|
|
||||||
'unsigned' => true,
|
|
||||||
'comment' => 'Playtime in seconds',
|
|
||||||
],
|
|
||||||
'audio_file_mimetype' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
],
|
|
||||||
'audio_file_size' => [
|
|
||||||
'type' => 'INT',
|
'type' => 'INT',
|
||||||
'unsigned' => true,
|
'unsigned' => true,
|
||||||
'comment' => 'File size in bytes',
|
|
||||||
],
|
|
||||||
'audio_file_header_size' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'comment' => 'Header size in bytes',
|
|
||||||
],
|
],
|
||||||
'description_markdown' => [
|
'description_markdown' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'TEXT',
|
||||||
@ -70,34 +50,27 @@ class AddEpisodes extends Migration
|
|||||||
'description_html' => [
|
'description_html' => [
|
||||||
'type' => 'TEXT',
|
'type' => 'TEXT',
|
||||||
],
|
],
|
||||||
'cover_path' => [
|
'cover_id' => [
|
||||||
'type' => 'VARCHAR',
|
'type' => 'INT',
|
||||||
'constraint' => 255,
|
'unsigned' => true,
|
||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
'transcript_id' => [
|
||||||
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
'type' => 'INT',
|
||||||
'cover_mimetype' => [
|
'unsigned' => true,
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 13,
|
|
||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
'transcript_file_path' => [
|
'transcript_remote_url' => [
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'transcript_file_remote_url' => [
|
|
||||||
'type' => 'VARCHAR',
|
'type' => 'VARCHAR',
|
||||||
'constraint' => 512,
|
'constraint' => 512,
|
||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
'chapters_file_path' => [
|
'chapters_id' => [
|
||||||
'type' => 'VARCHAR',
|
'type' => 'INT',
|
||||||
'constraint' => 255,
|
'unsigned' => true,
|
||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
'chapters_file_remote_url' => [
|
'chapters_remote_url' => [
|
||||||
'type' => 'VARCHAR',
|
'type' => 'VARCHAR',
|
||||||
'constraint' => 512,
|
'constraint' => 512,
|
||||||
'null' => true,
|
'null' => true,
|
||||||
@ -183,6 +156,10 @@ class AddEpisodes extends Migration
|
|||||||
$this->forge->addPrimaryKey('id');
|
$this->forge->addPrimaryKey('id');
|
||||||
$this->forge->addUniqueKey(['podcast_id', 'slug']);
|
$this->forge->addUniqueKey(['podcast_id', 'slug']);
|
||||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('audio_id', 'media', 'id');
|
||||||
|
$this->forge->addForeignKey('cover_id', 'media', 'id');
|
||||||
|
$this->forge->addForeignKey('transcript_id', 'media', 'id');
|
||||||
|
$this->forge->addForeignKey('chapters_id', 'media', 'id');
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
$this->forge->createTable('episodes');
|
$this->forge->createTable('episodes');
|
||||||
|
@ -42,16 +42,9 @@ class AddPersons extends Migration
|
|||||||
'The url to a relevant resource of information about the person, such as a homepage or third-party profile platform.',
|
'The url to a relevant resource of information about the person, such as a homepage or third-party profile platform.',
|
||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
'avatar_path' => [
|
'avatar_id' => [
|
||||||
'type' => 'VARCHAR',
|
'type' => 'INT',
|
||||||
'constraint' => 255,
|
'unsigned' => true,
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
|
||||||
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
|
||||||
'avatar_mimetype' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 13,
|
|
||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
'created_by' => [
|
'created_by' => [
|
||||||
@ -71,6 +64,7 @@ class AddPersons extends Migration
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->forge->addKey('id', true);
|
$this->forge->addKey('id', true);
|
||||||
|
$this->forge->addForeignKey('avatar_id', 'media', 'id');
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
$this->forge->createTable('persons');
|
$this->forge->createTable('persons');
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AddSoundbites Creates soundbites table in database
|
* @copyright 2021 Podlibre
|
||||||
*
|
|
||||||
* @copyright 2020 Podlibre
|
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
@ -14,7 +12,7 @@ namespace App\Database\Migrations;
|
|||||||
|
|
||||||
use CodeIgniter\Database\Migration;
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
class AddSoundbites extends Migration
|
class AddClips extends Migration
|
||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
@ -37,7 +35,7 @@ class AddSoundbites extends Migration
|
|||||||
'unsigned' => true,
|
'unsigned' => true,
|
||||||
],
|
],
|
||||||
'duration' => [
|
'duration' => [
|
||||||
// soundbite duration cannot be higher than 9999,999 seconds ~ 2.77 hours
|
// clip duration cannot be higher than 9999,999 seconds ~ 2.77 hours
|
||||||
'type' => 'DECIMAL(7,3)',
|
'type' => 'DECIMAL(7,3)',
|
||||||
'unsigned' => true,
|
'unsigned' => true,
|
||||||
],
|
],
|
||||||
@ -46,6 +44,21 @@ class AddSoundbites extends Migration
|
|||||||
'constraint' => 128,
|
'constraint' => 128,
|
||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
|
'type' => [
|
||||||
|
'type' => 'ENUM',
|
||||||
|
'constraint' => ['audio', 'video'],
|
||||||
|
],
|
||||||
|
'media_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'status' => [
|
||||||
|
'type' => 'ENUM',
|
||||||
|
'constraint' => ['queued', 'pending', 'generating', 'passed', 'failed'],
|
||||||
|
],
|
||||||
|
'logs' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
'created_by' => [
|
'created_by' => [
|
||||||
'type' => 'INT',
|
'type' => 'INT',
|
||||||
'unsigned' => true,
|
'unsigned' => true,
|
||||||
@ -65,17 +78,19 @@ class AddSoundbites extends Migration
|
|||||||
'null' => true,
|
'null' => true,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->forge->addKey('id', true);
|
$this->forge->addKey('id', true);
|
||||||
$this->forge->addUniqueKey(['episode_id', 'start_time', 'duration']);
|
$this->forge->addUniqueKey(['episode_id', 'start_time', 'duration', 'type']);
|
||||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('media_id', 'media', 'id', '', 'CASCADE');
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
$this->forge->createTable('soundbites');
|
$this->forge->createTable('clips');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
$this->forge->dropTable('soundbites');
|
$this->forge->dropTable('clips');
|
||||||
}
|
}
|
||||||
}
|
}
|
51
app/Entities/Audio.php
Normal file
51
app/Entities/Audio.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2021 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entities;
|
||||||
|
|
||||||
|
use CodeIgniter\Files\File;
|
||||||
|
use JamesHeinrich\GetID3\GetID3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property float $duration
|
||||||
|
* @property int $header_size
|
||||||
|
*/
|
||||||
|
class Audio extends Media
|
||||||
|
{
|
||||||
|
protected string $type = 'audio';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed>|null $data
|
||||||
|
*/
|
||||||
|
public function __construct(array $data = null)
|
||||||
|
{
|
||||||
|
parent::__construct($data);
|
||||||
|
|
||||||
|
if ($this->file_metadata) {
|
||||||
|
$this->duration = (float) $this->file_metadata['playtime_seconds'];
|
||||||
|
$this->header_size = (int) $this->file_metadata['avdataoffset'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFile(File $file): self
|
||||||
|
{
|
||||||
|
parent::setFile($file);
|
||||||
|
|
||||||
|
$getID3 = new GetID3();
|
||||||
|
$audioMetadata = $getID3->analyze((string) $file);
|
||||||
|
|
||||||
|
$this->attributes['file_content_type'] = $audioMetadata['mimetype'];
|
||||||
|
$this->attributes['file_size'] = $audioMetadata['filesize'];
|
||||||
|
$this->attributes['description'] = $audioMetadata['comments']['comment'];
|
||||||
|
$this->attributes['file_metadata'] = $audioMetadata;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ use CodeIgniter\Entity\Entity;
|
|||||||
* @property int $created_by
|
* @property int $created_by
|
||||||
* @property int $updated_by
|
* @property int $updated_by
|
||||||
*/
|
*/
|
||||||
class Soundbite extends Entity
|
class Clip extends Entity
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
@ -33,7 +33,11 @@ class Soundbite extends Entity
|
|||||||
'episode_id' => 'integer',
|
'episode_id' => 'integer',
|
||||||
'start_time' => 'double',
|
'start_time' => 'double',
|
||||||
'duration' => 'double',
|
'duration' => 'double',
|
||||||
|
'type' => 'string',
|
||||||
'label' => '?string',
|
'label' => '?string',
|
||||||
|
'media_id' => 'integer',
|
||||||
|
'status' => 'string',
|
||||||
|
'logs' => 'string',
|
||||||
'created_by' => 'integer',
|
'created_by' => 'integer',
|
||||||
'updated_by' => 'integer',
|
'updated_by' => 'integer',
|
||||||
];
|
];
|
@ -11,14 +11,14 @@ declare(strict_types=1);
|
|||||||
namespace App\Entities;
|
namespace App\Entities;
|
||||||
|
|
||||||
use App\Libraries\SimpleRSSElement;
|
use App\Libraries\SimpleRSSElement;
|
||||||
|
use App\Models\ClipsModel;
|
||||||
use App\Models\EpisodeCommentModel;
|
use App\Models\EpisodeCommentModel;
|
||||||
|
use App\Models\MediaModel;
|
||||||
use App\Models\PersonModel;
|
use App\Models\PersonModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use App\Models\PostModel;
|
use App\Models\PostModel;
|
||||||
use App\Models\SoundbiteModel;
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
use CodeIgniter\Files\File;
|
use CodeIgniter\Files\File;
|
||||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
|
||||||
use CodeIgniter\I18n\Time;
|
use CodeIgniter\I18n\Time;
|
||||||
use League\CommonMark\CommonMarkConverter;
|
use League\CommonMark\CommonMarkConverter;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
@ -31,30 +31,22 @@ use RuntimeException;
|
|||||||
* @property string $guid
|
* @property string $guid
|
||||||
* @property string $slug
|
* @property string $slug
|
||||||
* @property string $title
|
* @property string $title
|
||||||
* @property File $audio_file
|
* @property int $audio_id
|
||||||
* @property string $audio_file_url
|
* @property Audio $audio
|
||||||
* @property string $audio_file_analytics_url
|
* @property string $audio_file_analytics_url
|
||||||
* @property string $audio_file_web_url
|
* @property string $audio_file_web_url
|
||||||
* @property string $audio_file_opengraph_url
|
* @property string $audio_file_opengraph_url
|
||||||
* @property string $audio_file_path
|
|
||||||
* @property double $audio_file_duration
|
|
||||||
* @property string $audio_file_mimetype
|
|
||||||
* @property int $audio_file_size
|
|
||||||
* @property int $audio_file_header_size
|
|
||||||
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
||||||
* @property string $description_markdown
|
* @property string $description_markdown
|
||||||
* @property string $description_html
|
* @property string $description_html
|
||||||
|
* @property int $cover_id
|
||||||
* @property Image $cover
|
* @property Image $cover
|
||||||
* @property string|null $cover_path
|
* @property int|null $transcript_id
|
||||||
* @property string|null $cover_mimetype
|
* @property Media|null $transcript
|
||||||
* @property File|null $transcript_file
|
* @property string|null $transcript_remote_url
|
||||||
* @property string|null $transcript_file_url
|
* @property int|null $chapters_id
|
||||||
* @property string|null $transcript_file_path
|
* @property Media|null $chapters
|
||||||
* @property string|null $transcript_file_remote_url
|
* @property string|null $chapters_remote_url
|
||||||
* @property File|null $chapters_file
|
|
||||||
* @property string|null $chapters_file_url
|
|
||||||
* @property string|null $chapters_file_path
|
|
||||||
* @property string|null $chapters_file_remote_url
|
|
||||||
* @property string|null $parental_advisory
|
* @property string|null $parental_advisory
|
||||||
* @property int $number
|
* @property int $number
|
||||||
* @property int $season_number
|
* @property int $season_number
|
||||||
@ -86,15 +78,15 @@ class Episode extends Entity
|
|||||||
|
|
||||||
protected string $link;
|
protected string $link;
|
||||||
|
|
||||||
protected File $audio_file;
|
protected Audio $audio;
|
||||||
|
|
||||||
protected string $audio_file_url;
|
protected string $audio_url;
|
||||||
|
|
||||||
protected string $audio_file_analytics_url;
|
protected string $audio_analytics_url;
|
||||||
|
|
||||||
protected string $audio_file_web_url;
|
protected string $audio_web_url;
|
||||||
|
|
||||||
protected string $audio_file_opengraph_url;
|
protected string $audio_opengraph_url;
|
||||||
|
|
||||||
protected string $embed_url;
|
protected string $embed_url;
|
||||||
|
|
||||||
@ -102,9 +94,9 @@ class Episode extends Entity
|
|||||||
|
|
||||||
protected ?string $description = null;
|
protected ?string $description = null;
|
||||||
|
|
||||||
protected File $transcript_file;
|
protected ?Media $transcript;
|
||||||
|
|
||||||
protected File $chapters_file;
|
protected ?Media $chapters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Person[]|null
|
* @var Person[]|null
|
||||||
@ -112,9 +104,9 @@ class Episode extends Entity
|
|||||||
protected ?array $persons = null;
|
protected ?array $persons = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Soundbite[]|null
|
* @var Clip[]|null
|
||||||
*/
|
*/
|
||||||
protected ?array $soundbites = null;
|
protected ?array $clips = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Post[]|null
|
* @var Post[]|null
|
||||||
@ -146,19 +138,14 @@ class Episode extends Entity
|
|||||||
'guid' => 'string',
|
'guid' => 'string',
|
||||||
'slug' => 'string',
|
'slug' => 'string',
|
||||||
'title' => 'string',
|
'title' => 'string',
|
||||||
'audio_file_path' => 'string',
|
'audio_id' => 'integer',
|
||||||
'audio_file_duration' => 'double',
|
|
||||||
'audio_file_mimetype' => 'string',
|
|
||||||
'audio_file_size' => 'integer',
|
|
||||||
'audio_file_header_size' => 'integer',
|
|
||||||
'description_markdown' => 'string',
|
'description_markdown' => 'string',
|
||||||
'description_html' => 'string',
|
'description_html' => 'string',
|
||||||
'cover_path' => '?string',
|
'cover_id' => '?integer',
|
||||||
'cover_mimetype' => '?string',
|
'transcript_id' => '?integer',
|
||||||
'transcript_file_path' => '?string',
|
'transcript_remote_url' => '?string',
|
||||||
'transcript_file_remote_url' => '?string',
|
'chapters_id' => '?integer',
|
||||||
'chapters_file_path' => '?string',
|
'chapters_remote_url' => '?string',
|
||||||
'chapters_file_remote_url' => '?string',
|
|
||||||
'parental_advisory' => '?string',
|
'parental_advisory' => '?string',
|
||||||
'number' => '?integer',
|
'number' => '?integer',
|
||||||
'season_number' => '?integer',
|
'season_number' => '?integer',
|
||||||
@ -199,108 +186,45 @@ class Episode extends Entity
|
|||||||
|
|
||||||
public function getCover(): Image
|
public function getCover(): Image
|
||||||
{
|
{
|
||||||
if ($coverPath = $this->attributes['cover_path']) {
|
if (! $this->cover instanceof Image) {
|
||||||
return new Image(null, $coverPath, $this->attributes['cover_mimetype'], config(
|
$this->cover = (new MediaModel('image'))->getMediaById($this->cover_id);
|
||||||
'Images'
|
|
||||||
)->podcastCoverSizes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getPodcast()
|
return $this->cover;
|
||||||
->cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getAudio(): Audio
|
||||||
* Saves an audio file
|
|
||||||
*/
|
|
||||||
public function setAudioFile(UploadedFile | File $audioFile): static
|
|
||||||
{
|
{
|
||||||
helper(['media', 'id3']);
|
if (! $this->audio) {
|
||||||
|
$this->audio = (new MediaModel('audio'))->getMediaById($this->audio_id);
|
||||||
$audioMetadata = get_file_tags($audioFile);
|
|
||||||
|
|
||||||
$this->attributes['audio_file_path'] = save_media(
|
|
||||||
$audioFile,
|
|
||||||
'podcasts/' . $this->getPodcast()->handle,
|
|
||||||
$this->attributes['slug'],
|
|
||||||
);
|
|
||||||
$this->attributes['audio_file_duration'] =
|
|
||||||
$audioMetadata['playtime_seconds'];
|
|
||||||
$this->attributes['audio_file_mimetype'] = $audioMetadata['mime_type'];
|
|
||||||
$this->attributes['audio_file_size'] = $audioMetadata['filesize'];
|
|
||||||
$this->attributes['audio_file_header_size'] =
|
|
||||||
$audioMetadata['avdataoffset'];
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves an episode transcript file
|
|
||||||
*/
|
|
||||||
public function setTranscriptFile(UploadedFile | File $transcriptFile): static
|
|
||||||
{
|
|
||||||
helper('media');
|
|
||||||
|
|
||||||
$this->attributes['transcript_file_path'] = save_media(
|
|
||||||
$transcriptFile,
|
|
||||||
'podcasts/' . $this->getPodcast()
|
|
||||||
->handle,
|
|
||||||
$this->attributes['slug'] . '-transcript',
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves an episode chapters file
|
|
||||||
*/
|
|
||||||
public function setChaptersFile(UploadedFile | File $chaptersFile): static
|
|
||||||
{
|
|
||||||
helper('media');
|
|
||||||
|
|
||||||
$this->attributes['chapters_file_path'] = save_media(
|
|
||||||
$chaptersFile,
|
|
||||||
'podcasts/' . $this->getPodcast()
|
|
||||||
->handle,
|
|
||||||
$this->attributes['slug'] . '-chapters',
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAudioFile(): File
|
|
||||||
{
|
|
||||||
helper('media');
|
|
||||||
|
|
||||||
return new File(media_path($this->audio_file_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTranscriptFile(): ?File
|
|
||||||
{
|
|
||||||
if ($this->attributes['transcript_file_path']) {
|
|
||||||
helper('media');
|
|
||||||
|
|
||||||
return new File(media_path($this->attributes['transcript_file_path']));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return $this->audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getChaptersFile(): ?File
|
public function getTranscript(): ?Media
|
||||||
{
|
{
|
||||||
if ($this->attributes['chapters_file_path']) {
|
if ($this->transcript_id !== null && $this->transcript === null) {
|
||||||
helper('media');
|
$this->transcript = (new MediaModel('document'))->getMediaById($this->transcript_id);
|
||||||
|
|
||||||
return new File(media_path($this->attributes['chapters_file_path']));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return $this->transcript;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChaptersFile(): ?Media
|
||||||
|
{
|
||||||
|
if ($this->chapters_id !== null && $this->chapters === null) {
|
||||||
|
$this->chapters = (new MediaModel('document'))->getMediaById($this->chapters_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->chapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAudioFileUrl(): string
|
public function getAudioFileUrl(): string
|
||||||
{
|
{
|
||||||
helper('media');
|
helper('media');
|
||||||
|
|
||||||
return media_base_url($this->audio_file_path);
|
return media_base_url($this->audio->file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAudioFileAnalyticsUrl(): string
|
public function getAudioFileAnalyticsUrl(): string
|
||||||
@ -308,15 +232,15 @@ class Episode extends Entity
|
|||||||
helper('analytics');
|
helper('analytics');
|
||||||
|
|
||||||
// remove 'podcasts/' from audio file path
|
// remove 'podcasts/' from audio file path
|
||||||
$strippedAudioFilePath = substr($this->audio_file_path, 9);
|
$strippedAudioFilePath = substr($this->audio->file_path, 9);
|
||||||
|
|
||||||
return generate_episode_analytics_url(
|
return generate_episode_analytics_url(
|
||||||
$this->podcast_id,
|
$this->podcast_id,
|
||||||
$this->id,
|
$this->id,
|
||||||
$strippedAudioFilePath,
|
$strippedAudioFilePath,
|
||||||
$this->audio_file_duration,
|
$this->audio->duration,
|
||||||
$this->audio_file_size,
|
$this->audio->file_size,
|
||||||
$this->audio_file_header_size,
|
$this->audio->header_size,
|
||||||
$this->published_at,
|
$this->published_at,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -332,28 +256,26 @@ class Episode extends Entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets transcript url from transcript file uri if it exists or returns the transcript_file_remote_url which can be
|
* Gets transcript url from transcript file uri if it exists or returns the transcript_remote_url which can be null.
|
||||||
* null.
|
|
||||||
*/
|
*/
|
||||||
public function getTranscriptFileUrl(): ?string
|
public function getTranscriptUrl(): ?string
|
||||||
{
|
{
|
||||||
if ($this->attributes['transcript_file_path']) {
|
if ($this->transcript !== null) {
|
||||||
return media_base_url($this->attributes['transcript_file_path']);
|
return $this->transcript->url;
|
||||||
}
|
}
|
||||||
return $this->attributes['transcript_file_remote_url'];
|
return $this->transcript_remote_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets chapters file url from chapters file uri if it exists or returns the chapters_file_remote_url which can be
|
* Gets chapters file url from chapters file uri if it exists or returns the chapters_remote_url which can be null.
|
||||||
* null.
|
|
||||||
*/
|
*/
|
||||||
public function getChaptersFileUrl(): ?string
|
public function getChaptersFileUrl(): ?string
|
||||||
{
|
{
|
||||||
if ($this->chapters_file_path) {
|
if ($this->chapters) {
|
||||||
return media_base_url($this->chapters_file_path);
|
return $this->chapters->url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->chapters_file_remote_url;
|
return $this->chapters_remote_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -375,21 +297,21 @@ class Episode extends Entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the episode’s soundbites
|
* Returns the episode’s clips
|
||||||
*
|
*
|
||||||
* @return Soundbite[]
|
* @return Clip[]
|
||||||
*/
|
*/
|
||||||
public function getSoundbites(): array
|
public function getClips(): array
|
||||||
{
|
{
|
||||||
if ($this->id === null) {
|
if ($this->id === null) {
|
||||||
throw new RuntimeException('Episode must be created before getting soundbites.');
|
throw new RuntimeException('Episode must be created before getting clips.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->soundbites === null) {
|
if ($this->clips === null) {
|
||||||
$this->soundbites = (new SoundbiteModel())->getEpisodeSoundbites($this->getPodcast() ->id, $this->id);
|
$this->clips = (new ClipsModel())->getEpisodeClips($this->getPodcast() ->id, $this->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->soundbites;
|
return $this->clips;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,176 +10,68 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entities;
|
namespace App\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
|
||||||
use CodeIgniter\Files\File;
|
use CodeIgniter\Files\File;
|
||||||
use Config\Images;
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
class Image extends Media
|
||||||
* @property File|null $file
|
|
||||||
* @property string $dirname
|
|
||||||
* @property string $filename
|
|
||||||
* @property string $extension
|
|
||||||
* @property string $mimetype
|
|
||||||
* @property string $path
|
|
||||||
* @property string $url
|
|
||||||
*/
|
|
||||||
class Image extends Entity
|
|
||||||
{
|
{
|
||||||
protected Images $config;
|
protected string $type = 'image';
|
||||||
|
|
||||||
protected File $file;
|
|
||||||
|
|
||||||
protected string $dirname;
|
|
||||||
|
|
||||||
protected string $filename;
|
|
||||||
|
|
||||||
protected string $extension;
|
|
||||||
|
|
||||||
protected string $mimetype;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, array<string, int|string>>
|
* @param array<string, mixed>|null $data
|
||||||
*/
|
*/
|
||||||
protected array $sizes = [];
|
public function __construct(array $data = null)
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, array<string, int|string>> $sizes
|
|
||||||
* @param File $file
|
|
||||||
*/
|
|
||||||
public function __construct(?File $file, string $path = '', string $mimetype = '', array $sizes = [])
|
|
||||||
{
|
{
|
||||||
if ($file === null && $path === '') {
|
parent::__construct($data);
|
||||||
throw new RuntimeException('File or path must be set to create an Image.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$dirname = '';
|
if ($this->file_path && $this->file_metadata) {
|
||||||
$filename = '';
|
$this->sizes = $this->file_metadata['sizes'];
|
||||||
$extension = '';
|
$this->initSizeProperties();
|
||||||
|
|
||||||
if ($file !== null) {
|
|
||||||
$dirname = $file->getPath();
|
|
||||||
$filename = $file->getBasename();
|
|
||||||
$extension = $file->getExtension();
|
|
||||||
$mimetype = $file->getMimeType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($path !== '') {
|
|
||||||
[
|
|
||||||
'filename' => $filename,
|
|
||||||
'dirname' => $dirname,
|
|
||||||
'extension' => $extension,
|
|
||||||
] = pathinfo($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($file === null) {
|
|
||||||
helper('media');
|
|
||||||
$file = new File(media_path($path));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->file = $file;
|
|
||||||
$this->dirname = $dirname;
|
|
||||||
$this->filename = $filename;
|
|
||||||
$this->extension = $extension;
|
|
||||||
$this->mimetype = $mimetype;
|
|
||||||
$this->sizes = $sizes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __get($property)
|
|
||||||
{
|
|
||||||
// Convert to CamelCase for the method
|
|
||||||
$method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $property)));
|
|
||||||
|
|
||||||
// if a get* method exists for this property,
|
|
||||||
// call that method to get this value.
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
if (method_exists($this, $method)) {
|
|
||||||
return $this->{$method}();
|
|
||||||
}
|
|
||||||
|
|
||||||
$fileSuffix = '';
|
|
||||||
if ($lastUnderscorePosition = strrpos($property, '_')) {
|
|
||||||
$fileSuffix = '_' . substr($property, 0, $lastUnderscorePosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = '';
|
|
||||||
if ($this->dirname !== '.') {
|
|
||||||
$path .= $this->dirname . '/';
|
|
||||||
}
|
|
||||||
$path .= $this->filename . $fileSuffix;
|
|
||||||
|
|
||||||
$extension = '.' . $this->extension;
|
|
||||||
$mimetype = $this->mimetype;
|
|
||||||
if ($fileSuffix !== '') {
|
|
||||||
$sizeName = substr($fileSuffix, 1);
|
|
||||||
if (array_key_exists('extension', $this->sizes[$sizeName])) {
|
|
||||||
$extension = '.' . $this->sizes[$sizeName]['extension'];
|
|
||||||
}
|
|
||||||
if (array_key_exists('mimetype', $this->sizes[$sizeName])) {
|
|
||||||
$mimetype = $this->sizes[$sizeName]['mimetype'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$path .= $extension;
|
|
||||||
|
|
||||||
if (str_ends_with($property, 'mimetype')) {
|
|
||||||
return $mimetype;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str_ends_with($property, 'url')) {
|
|
||||||
helper('media');
|
|
||||||
|
|
||||||
return media_base_url($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str_ends_with($property, 'path')) {
|
|
||||||
return $path;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMimetype(): string
|
public function initSizeProperties(): bool
|
||||||
{
|
|
||||||
return $this->mimetype;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFile(): File
|
|
||||||
{
|
|
||||||
return $this->file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, array<string, int|string>> $sizes
|
|
||||||
*/
|
|
||||||
public function saveImage(array $sizes, string $dirname, string $filename): void
|
|
||||||
{
|
{
|
||||||
helper('media');
|
helper('media');
|
||||||
|
|
||||||
$this->dirname = $dirname;
|
$extension = $this->file_extension;
|
||||||
$this->filename = $filename;
|
$mimetype = $this->mimetype;
|
||||||
$this->sizes = $sizes;
|
foreach ($this->sizes as $name => $size) {
|
||||||
|
if (array_key_exists('extension', $size)) {
|
||||||
|
$extension = $size['extension'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('mimetype', $size)) {
|
||||||
|
$mimetype = $size['mimetype'];
|
||||||
|
}
|
||||||
|
$this->{$name . '_path'} = $this->file_directory . '/' . $this->file_name . '_' . $name . '.' . $extension;
|
||||||
|
$this->{$name . '_url'} = media_base_url($this->{$name . '_path'});
|
||||||
|
$this->{$name . '_mimetype'} = $mimetype;
|
||||||
|
}
|
||||||
|
|
||||||
save_media($this->file, $this->dirname, $this->filename);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFile(File $file): self
|
||||||
|
{
|
||||||
|
parent::setFile($file);
|
||||||
|
|
||||||
|
$metadata = exif_read_data(media_path($this->file_path), null, true);
|
||||||
|
|
||||||
|
if ($metadata) {
|
||||||
|
$metadata['sizes'] = $this->sizes;
|
||||||
|
$this->attributes['file_size'] = $metadata['FILE']['FileSize'];
|
||||||
|
$this->attributes['file_metadata'] = json_encode($metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// save derived sizes
|
||||||
$imageService = service('image');
|
$imageService = service('image');
|
||||||
|
foreach ($this->sizes as $name => $size) {
|
||||||
foreach ($sizes as $name => $size) {
|
|
||||||
$pathProperty = $name . '_path';
|
$pathProperty = $name . '_path';
|
||||||
$imageService
|
$imageService
|
||||||
->withFile(media_path($this->path))
|
->withFile(media_path($this->file_path))
|
||||||
->resize($size['width'], $size['height']);
|
->resize($size['width'], $size['height']);
|
||||||
$imageService->save(media_path($this->{$pathProperty}));
|
$imageService->save(media_path($this->{$pathProperty}));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return $this;
|
||||||
* @param array<string, int[]> $sizes
|
|
||||||
*/
|
|
||||||
public function delete(array $sizes): void
|
|
||||||
{
|
|
||||||
helper('media');
|
|
||||||
|
|
||||||
foreach (array_keys($sizes) as $name) {
|
|
||||||
$pathProperty = $name . '_path';
|
|
||||||
unlink(media_path($this->{$pathProperty}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
123
app/Entities/ImageOLD.php
Normal file
123
app/Entities/ImageOLD.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2021 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entities;
|
||||||
|
|
||||||
|
use CodeIgniter\Files\File;
|
||||||
|
use Config\Images;
|
||||||
|
|
||||||
|
class Image extends Media
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<string, array<string, int|string>>
|
||||||
|
*/
|
||||||
|
public array $sizes = [];
|
||||||
|
|
||||||
|
protected Images $config;
|
||||||
|
|
||||||
|
protected string $type = 'image';
|
||||||
|
|
||||||
|
public function __get($property)
|
||||||
|
{
|
||||||
|
if (str_ends_with($property, '_url') || str_ends_with($property, '_path') || str_ends_with(
|
||||||
|
$property,
|
||||||
|
'_mimetype'
|
||||||
|
)) {
|
||||||
|
$this->initSizeProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__get($property);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFileMetadata(string $metadata): self
|
||||||
|
{
|
||||||
|
$this->attributes['file_metadata'] = $metadata;
|
||||||
|
|
||||||
|
$metadataArray = json_decode($metadata, true);
|
||||||
|
if (! array_key_exists('sizes', $metadataArray)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sizes = $metadataArray['sizes'];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initSizeProperties(): bool
|
||||||
|
{
|
||||||
|
if ($this->file_path === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->sizes === []) {
|
||||||
|
$this->sizes = $this->file_metadata['sizes'];
|
||||||
|
}
|
||||||
|
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
$extension = $this->file_extension;
|
||||||
|
$mimetype = $this->mimetype;
|
||||||
|
foreach ($this->sizes as $name => $size) {
|
||||||
|
if (array_key_exists('extension', $size)) {
|
||||||
|
$extension = $size['extension'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('mimetype', $size)) {
|
||||||
|
$mimetype = $size['mimetype'];
|
||||||
|
}
|
||||||
|
$this->{$name . '_path'} = $this->file_directory . '/' . $this->file_name . '_' . $name . '.' . $extension;
|
||||||
|
$this->{$name . '_url'} = media_base_url($this->{$name . '_path'});
|
||||||
|
$this->{$name . '_mimetype'} = $mimetype;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveInDisk(File $file, string $dirname, string $filename): void
|
||||||
|
{
|
||||||
|
// save original
|
||||||
|
parent::saveInDisk($file, $dirname, $filename);
|
||||||
|
|
||||||
|
$this->initSizeProperties();
|
||||||
|
|
||||||
|
// save derived sizes
|
||||||
|
$imageService = service('image');
|
||||||
|
foreach ($this->sizes as $name => $size) {
|
||||||
|
$pathProperty = $name . '_path';
|
||||||
|
$imageService
|
||||||
|
->withFile(media_path($this->file_path))
|
||||||
|
->resize($size['width'], $size['height']);
|
||||||
|
$imageService->save(media_path($this->{$pathProperty}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function injectFileData(File $file): void
|
||||||
|
{
|
||||||
|
$metadata = exif_read_data(media_path($this->file_path), null, true);
|
||||||
|
|
||||||
|
if ($metadata) {
|
||||||
|
$metadata['sizes'] = $this->sizes;
|
||||||
|
$this->file_size = $metadata['FILE']['FileSize'];
|
||||||
|
$this->file_metadata = $metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, int[]> $sizes
|
||||||
|
*/
|
||||||
|
public function delete(array $sizes): void
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
foreach (array_keys($sizes) as $name) {
|
||||||
|
$pathProperty = $name . '_path';
|
||||||
|
unlink(media_path($this->{$pathProperty}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
app/Entities/Media.php
Normal file
95
app/Entities/Media.php
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2021 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entities;
|
||||||
|
|
||||||
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\Files\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property string $file_path
|
||||||
|
* @property string $file_directory
|
||||||
|
* @property string $file_extension
|
||||||
|
* @property string $file_name
|
||||||
|
* @property int $file_size
|
||||||
|
* @property string $file_content_type
|
||||||
|
* @property array $file_metadata
|
||||||
|
* @property 'image'|'audio'|'video'|'document' $type
|
||||||
|
* @property string $description
|
||||||
|
* @property string|null $language_code
|
||||||
|
* @property int $uploaded_by
|
||||||
|
* @property int $updated_by
|
||||||
|
*/
|
||||||
|
class Media extends Entity
|
||||||
|
{
|
||||||
|
protected File $file;
|
||||||
|
|
||||||
|
protected string $type = 'document';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $dates = ['uploaded_at', 'updated_at', 'deleted_at'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'id' => 'integer',
|
||||||
|
'file_path' => 'string',
|
||||||
|
'file_size' => 'int',
|
||||||
|
'file_content_type' => 'string',
|
||||||
|
'file_metadata' => 'json-array',
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => 'string',
|
||||||
|
'language_code' => '?string',
|
||||||
|
'uploaded_by' => 'integer',
|
||||||
|
'updated_by' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed>|null $data
|
||||||
|
*/
|
||||||
|
public function __construct(array $data = null)
|
||||||
|
{
|
||||||
|
parent::__construct($data);
|
||||||
|
|
||||||
|
if ($this->file_path) {
|
||||||
|
[
|
||||||
|
'filename' => $filename,
|
||||||
|
'dirname' => $dirname,
|
||||||
|
'extension' => $extension,
|
||||||
|
] = pathinfo($this->file_path);
|
||||||
|
|
||||||
|
$this->file_name = $filename;
|
||||||
|
$this->file_directory = $dirname;
|
||||||
|
$this->file_extension = $extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFile(File $file): self
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
$this->attributes['file_content_type'] = $file->getMimeType();
|
||||||
|
$this->attributes['file_metadata'] = json_encode(lstat((string) $file));
|
||||||
|
$this->attributes['file_path'] = save_media(
|
||||||
|
$file,
|
||||||
|
$this->attributes['file_directory'],
|
||||||
|
$this->attributes['file_name']
|
||||||
|
);
|
||||||
|
if ($filesize = filesize(media_path($this->file_path))) {
|
||||||
|
$this->attributes['file_size'] = $filesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
93
app/Entities/MediaOLD.php
Normal file
93
app/Entities/MediaOLD.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2021 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entities;
|
||||||
|
|
||||||
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\Files\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property string $file_path
|
||||||
|
* @property string $file_directory
|
||||||
|
* @property string $file_extension
|
||||||
|
* @property string $file_name
|
||||||
|
* @property int $file_size
|
||||||
|
* @property string $file_content_type
|
||||||
|
* @property array $file_metadata
|
||||||
|
* @property 'image'|'audio'|'video'|'document' $type
|
||||||
|
* @property string $description
|
||||||
|
* @property string|null $language_code
|
||||||
|
* @property int $uploaded_by
|
||||||
|
* @property int $updated_by
|
||||||
|
*/
|
||||||
|
class Media extends Entity
|
||||||
|
{
|
||||||
|
protected File $file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $dates = ['uploaded_at', 'updated_at', 'deleted_at'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'id' => 'integer',
|
||||||
|
'file_path' => 'string',
|
||||||
|
'file_size' => 'string',
|
||||||
|
'file_content_type' => 'string',
|
||||||
|
'file_metadata' => 'json-array',
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => 'string',
|
||||||
|
'language_code' => '?string',
|
||||||
|
'uploaded_by' => 'integer',
|
||||||
|
'updated_by' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function setFilePath(string $path): self
|
||||||
|
{
|
||||||
|
$this->attributes['file_path'] = $path;
|
||||||
|
|
||||||
|
[
|
||||||
|
'filename' => $filename,
|
||||||
|
'dirname' => $dirname,
|
||||||
|
'extension' => $extension,
|
||||||
|
] = pathinfo($path);
|
||||||
|
|
||||||
|
$this->file_name = $filename;
|
||||||
|
$this->file_directory = $dirname;
|
||||||
|
$this->file_extension = $extension;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveInDisk(File $file, string $dirname, string $filename): void
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
$this->file_content_type = $file->getMimeType();
|
||||||
|
|
||||||
|
$filePath = save_media($file, $dirname, $filename);
|
||||||
|
|
||||||
|
$this->file_path = $filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function injectFileData(File $file): void
|
||||||
|
{
|
||||||
|
$this->file_content_type = $file->getMimeType();
|
||||||
|
$this->type = 'document';
|
||||||
|
|
||||||
|
if ($filesize = filesize(media_path($this->file_path))) {
|
||||||
|
$this->file_size = $filesize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,20 +19,15 @@ use RuntimeException;
|
|||||||
* @property string $full_name
|
* @property string $full_name
|
||||||
* @property string $unique_name
|
* @property string $unique_name
|
||||||
* @property string|null $information_url
|
* @property string|null $information_url
|
||||||
|
* @property int $avatar_id
|
||||||
* @property Image $avatar
|
* @property Image $avatar
|
||||||
* @property string $avatar_path
|
|
||||||
* @property string $avatar_mimetype
|
|
||||||
* @property int $created_by
|
* @property int $created_by
|
||||||
* @property int $updated_by
|
* @property int $updated_by
|
||||||
* @property object[]|null $roles
|
* @property object[]|null $roles
|
||||||
*/
|
*/
|
||||||
class Person extends Entity
|
class Person extends Entity
|
||||||
{
|
{
|
||||||
protected Image $avatar;
|
protected ?Image $avatar = null;
|
||||||
|
|
||||||
protected ?int $podcast_id = null;
|
|
||||||
|
|
||||||
protected ?int $episode_id = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var object[]|null
|
* @var object[]|null
|
||||||
@ -47,8 +42,7 @@ class Person extends Entity
|
|||||||
'full_name' => 'string',
|
'full_name' => 'string',
|
||||||
'unique_name' => 'string',
|
'unique_name' => 'string',
|
||||||
'information_url' => '?string',
|
'information_url' => '?string',
|
||||||
'avatar_path' => '?string',
|
'avatar_id' => '?int',
|
||||||
'avatar_mimetype' => '?string',
|
|
||||||
'podcast_id' => '?integer',
|
'podcast_id' => '?integer',
|
||||||
'episode_id' => '?integer',
|
'episode_id' => '?integer',
|
||||||
'created_by' => 'integer',
|
'created_by' => 'integer',
|
||||||
|
@ -13,6 +13,7 @@ namespace App\Entities;
|
|||||||
use App\Libraries\SimpleRSSElement;
|
use App\Libraries\SimpleRSSElement;
|
||||||
use App\Models\CategoryModel;
|
use App\Models\CategoryModel;
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
|
use App\Models\MediaModel;
|
||||||
use App\Models\PersonModel;
|
use App\Models\PersonModel;
|
||||||
use App\Models\PlatformModel;
|
use App\Models\PlatformModel;
|
||||||
use App\Models\UserModel;
|
use App\Models\UserModel;
|
||||||
@ -34,12 +35,10 @@ use RuntimeException;
|
|||||||
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
||||||
* @property string $description_markdown
|
* @property string $description_markdown
|
||||||
* @property string $description_html
|
* @property string $description_html
|
||||||
|
* @property int $cover_id
|
||||||
* @property Image $cover
|
* @property Image $cover
|
||||||
* @property string $cover_path
|
* @property int|null $banner_id
|
||||||
* @property string $cover_mimetype
|
|
||||||
* @property Image|null $banner
|
* @property Image|null $banner
|
||||||
* @property string|null $banner_path
|
|
||||||
* @property string|null $banner_mimetype
|
|
||||||
* @property string $language_code
|
* @property string $language_code
|
||||||
* @property int $category_id
|
* @property int $category_id
|
||||||
* @property Category|null $category
|
* @property Category|null $category
|
||||||
@ -87,9 +86,9 @@ class Podcast extends Entity
|
|||||||
|
|
||||||
protected ?Actor $actor = null;
|
protected ?Actor $actor = null;
|
||||||
|
|
||||||
protected Image $cover;
|
protected ?Image $cover = null;
|
||||||
|
|
||||||
protected ?Image $banner;
|
protected ?Image $banner = null;
|
||||||
|
|
||||||
protected ?string $description = null;
|
protected ?string $description = null;
|
||||||
|
|
||||||
@ -150,10 +149,8 @@ class Podcast extends Entity
|
|||||||
'title' => 'string',
|
'title' => 'string',
|
||||||
'description_markdown' => 'string',
|
'description_markdown' => 'string',
|
||||||
'description_html' => 'string',
|
'description_html' => 'string',
|
||||||
'cover_path' => 'string',
|
'cover_id' => 'int',
|
||||||
'cover_mimetype' => 'string',
|
'banner_id' => '?int',
|
||||||
'banner_path' => '?string',
|
|
||||||
'banner_mimetype' => '?string',
|
|
||||||
'language_code' => 'string',
|
'language_code' => 'string',
|
||||||
'category_id' => 'integer',
|
'category_id' => 'integer',
|
||||||
'parental_advisory' => '?string',
|
'parental_advisory' => '?string',
|
||||||
@ -195,66 +192,36 @@ class Podcast extends Entity
|
|||||||
return $this->actor;
|
return $this->actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves a podcast cover to the corresponding podcast folder in `public/media/podcast_name/`
|
|
||||||
*/
|
|
||||||
public function setCover(Image $cover): static
|
|
||||||
{
|
|
||||||
// Save image
|
|
||||||
$cover->saveImage(config('Images')->podcastCoverSizes, 'podcasts/' . $this->attributes['handle'], 'cover');
|
|
||||||
|
|
||||||
$this->attributes['cover_path'] = $cover->path;
|
|
||||||
$this->attributes['cover_mimetype'] = $cover->mimetype;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCover(): Image
|
public function getCover(): Image
|
||||||
{
|
{
|
||||||
return new Image(null, $this->cover_path, $this->cover_mimetype, config('Images')->podcastCoverSizes);
|
if (! $this->cover instanceof Image) {
|
||||||
}
|
$this->cover = (new MediaModel('image'))->getMediaById($this->cover_id);
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves a podcast cover to the corresponding podcast folder in `public/media/podcast_name/`
|
|
||||||
*/
|
|
||||||
public function setBanner(?Image $banner): static
|
|
||||||
{
|
|
||||||
if ($banner === null) {
|
|
||||||
$this->attributes['banner_path'] = null;
|
|
||||||
$this->attributes['banner_mimetype'] = null;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save image
|
return $this->cover;
|
||||||
$banner->saveImage(
|
|
||||||
config('Images')
|
|
||||||
->podcastBannerSizes,
|
|
||||||
'podcasts/' . $this->attributes['handle'],
|
|
||||||
'banner'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->attributes['banner_path'] = $banner->path;
|
|
||||||
$this->attributes['banner_mimetype'] = $banner->mimetype;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBanner(): Image
|
public function getBanner(): Image
|
||||||
{
|
{
|
||||||
if ($this->attributes['banner_path'] === null) {
|
if ($this->banner_id === null) {
|
||||||
return new Image(
|
return new Image([
|
||||||
null,
|
'file_path' => config('Images')
|
||||||
config('Images')
|
|
||||||
->podcastBannerDefaultPath,
|
->podcastBannerDefaultPath,
|
||||||
config('Images')
|
'file_mimetype' => config('Images')
|
||||||
->podcastBannerDefaultMimeType,
|
->podcastBannerDefaultMimeType,
|
||||||
config('Images')
|
'file_size' => 0,
|
||||||
->podcastBannerSizes
|
'file_metadata' => [
|
||||||
);
|
'sizes' => config('Images')
|
||||||
|
->podcastBannerSizes,
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Image(null, $this->banner_path, $this->banner_mimetype, config('Images') ->podcastBannerSizes);
|
if (! $this->banner instanceof Image) {
|
||||||
|
$this->banner = (new MediaModel('image'))->getMediaById($this->banner_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->banner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLink(): string
|
public function getLink(): string
|
||||||
|
@ -10,29 +10,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
use App\Entities\Episode;
|
use App\Entities\Episode;
|
||||||
use CodeIgniter\Files\File;
|
use CodeIgniter\Files\File;
|
||||||
use JamesHeinrich\GetID3\GetID3;
|
|
||||||
use JamesHeinrich\GetID3\WriteTags;
|
use JamesHeinrich\GetID3\WriteTags;
|
||||||
|
|
||||||
if (! function_exists('get_file_tags')) {
|
|
||||||
/**
|
|
||||||
* Gets audio file metadata and ID3 info
|
|
||||||
*
|
|
||||||
* @return array<string, string|double|int>
|
|
||||||
*/
|
|
||||||
function get_file_tags(File $file): array
|
|
||||||
{
|
|
||||||
$getID3 = new GetID3();
|
|
||||||
$FileInfo = $getID3->analyze((string) $file);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'filesize' => $FileInfo['filesize'],
|
|
||||||
'mime_type' => $FileInfo['mime_type'],
|
|
||||||
'avdataoffset' => $FileInfo['avdataoffset'],
|
|
||||||
'playtime_seconds' => $FileInfo['playtime_seconds'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! function_exists('write_audio_file_tags')) {
|
if (! function_exists('write_audio_file_tags')) {
|
||||||
/**
|
/**
|
||||||
* Write audio file metadata / ID3 tags
|
* Write audio file metadata / ID3 tags
|
||||||
@ -45,7 +24,7 @@ if (! function_exists('write_audio_file_tags')) {
|
|||||||
|
|
||||||
// Initialize getID3 tag-writing module
|
// Initialize getID3 tag-writing module
|
||||||
$tagwriter = new WriteTags();
|
$tagwriter = new WriteTags();
|
||||||
$tagwriter->filename = media_path($episode->audio_file_path);
|
$tagwriter->filename = media_path($episode->audio->file_path);
|
||||||
|
|
||||||
// set various options (optional)
|
// set various options (optional)
|
||||||
$tagwriter->tagformats = ['id3v2.4'];
|
$tagwriter->tagformats = ['id3v2.4'];
|
||||||
|
@ -211,8 +211,8 @@ if (! function_exists('get_rss_feed')) {
|
|||||||
? ''
|
? ''
|
||||||
: '?_from=' . urlencode($serviceSlug)),
|
: '?_from=' . urlencode($serviceSlug)),
|
||||||
);
|
);
|
||||||
$enclosure->addAttribute('length', (string) $episode->audio_file_size);
|
$enclosure->addAttribute('length', (string) $episode->audio->file_size);
|
||||||
$enclosure->addAttribute('type', $episode->audio_file_mimetype);
|
$enclosure->addAttribute('type', $episode->audio->file_content_type);
|
||||||
|
|
||||||
$item->addChild('guid', $episode->guid);
|
$item->addChild('guid', $episode->guid);
|
||||||
$item->addChild('pubDate', $episode->published_at->format(DATE_RFC1123));
|
$item->addChild('pubDate', $episode->published_at->format(DATE_RFC1123));
|
||||||
@ -230,7 +230,7 @@ if (! function_exists('get_rss_feed')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$item->addChildWithCDATA('description', $episode->getDescriptionHtml($serviceSlug));
|
$item->addChildWithCDATA('description', $episode->getDescriptionHtml($serviceSlug));
|
||||||
$item->addChild('duration', (string) $episode->audio_file_duration, $itunesNamespace);
|
$item->addChild('duration', (string) $episode->audio->duration, $itunesNamespace);
|
||||||
$item->addChild('link', $episode->link);
|
$item->addChild('link', $episode->link);
|
||||||
$episodeItunesImage = $item->addChild('image', null, $itunesNamespace);
|
$episodeItunesImage = $item->addChild('image', null, $itunesNamespace);
|
||||||
$episodeItunesImage->addAttribute('href', $episode->cover->feed_url);
|
$episodeItunesImage->addAttribute('href', $episode->cover->feed_url);
|
||||||
@ -255,7 +255,7 @@ if (! function_exists('get_rss_feed')) {
|
|||||||
$comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug));
|
$comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug));
|
||||||
$comments->addAttribute('contentType', 'application/podcast-activity+json');
|
$comments->addAttribute('contentType', 'application/podcast-activity+json');
|
||||||
|
|
||||||
if ($episode->transcript_file_url) {
|
if ($episode->transcript->file_url) {
|
||||||
$transcriptElement = $item->addChild('transcript', null, $podcastNamespace);
|
$transcriptElement = $item->addChild('transcript', null, $podcastNamespace);
|
||||||
$transcriptElement->addAttribute('url', $episode->transcript_file_url);
|
$transcriptElement->addAttribute('url', $episode->transcript_file_url);
|
||||||
$transcriptElement->addAttribute(
|
$transcriptElement->addAttribute(
|
||||||
@ -267,16 +267,17 @@ if (! function_exists('get_rss_feed')) {
|
|||||||
$transcriptElement->addAttribute('language', $podcast->language_code);
|
$transcriptElement->addAttribute('language', $podcast->language_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($episode->chapters_file_url) {
|
if ($episode->chapters->file_url) {
|
||||||
$chaptersElement = $item->addChild('chapters', null, $podcastNamespace);
|
$chaptersElement = $item->addChild('chapters', null, $podcastNamespace);
|
||||||
$chaptersElement->addAttribute('url', $episode->chapters_file_url);
|
$chaptersElement->addAttribute('url', $episode->chapters_file_url);
|
||||||
$chaptersElement->addAttribute('type', 'application/json+chapters');
|
$chaptersElement->addAttribute('type', 'application/json+chapters');
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($episode->soundbites as $soundbite) {
|
foreach ($episode->clip as $clip) {
|
||||||
$soundbiteElement = $item->addChild('soundbite', $soundbite->label, $podcastNamespace);
|
// TODO: differentiate video from soundbites?
|
||||||
$soundbiteElement->addAttribute('start_time', (string) $soundbite->start_time);
|
$soundbiteElement = $item->addChild('soundbite', $clip->label, $podcastNamespace);
|
||||||
$soundbiteElement->addAttribute('duration', (string) $soundbite->duration);
|
$soundbiteElement->addAttribute('start_time', (string) $clip->start_time);
|
||||||
|
$soundbiteElement->addAttribute('duration', (string) $clip->duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($episode->persons as $person) {
|
foreach ($episode->persons as $person) {
|
||||||
|
@ -64,9 +64,9 @@ if (! function_exists('get_episode_metatags')) {
|
|||||||
'image' => $episode->cover->feed_url,
|
'image' => $episode->cover->feed_url,
|
||||||
'description' => $episode->description,
|
'description' => $episode->description,
|
||||||
'datePublished' => $episode->published_at->format(DATE_ISO8601),
|
'datePublished' => $episode->published_at->format(DATE_ISO8601),
|
||||||
'timeRequired' => iso8601_duration($episode->audio_file_duration),
|
'timeRequired' => iso8601_duration($episode->audio->duration),
|
||||||
'associatedMedia' => new Thing('MediaObject', [
|
'associatedMedia' => new Thing('MediaObject', [
|
||||||
'contentUrl' => $episode->audio_file_url,
|
'contentUrl' => $episode->audio->file_url,
|
||||||
]),
|
]),
|
||||||
'partOfSeries' => new Thing('PodcastSeries', [
|
'partOfSeries' => new Thing('PodcastSeries', [
|
||||||
'name' => $episode->podcast->title,
|
'name' => $episode->podcast->title,
|
||||||
@ -87,7 +87,7 @@ if (! function_exists('get_episode_metatags')) {
|
|||||||
->og('image:height', (string) config('Images')->podcastCoverSizes['large']['height'])
|
->og('image:height', (string) config('Images')->podcastCoverSizes['large']['height'])
|
||||||
->og('locale', $episode->podcast->language_code)
|
->og('locale', $episode->podcast->language_code)
|
||||||
->og('audio', $episode->audio_file_opengraph_url)
|
->og('audio', $episode->audio_file_opengraph_url)
|
||||||
->og('audio:type', $episode->audio_file_mimetype)
|
->og('audio:type', $episode->audio->file_content_type)
|
||||||
->meta('article:published_time', $episode->published_at->format(DATE_ISO8601))
|
->meta('article:published_time', $episode->published_at->format(DATE_ISO8601))
|
||||||
->meta('article:modified_time', $episode->updated_at->format(DATE_ISO8601))
|
->meta('article:modified_time', $episode->updated_at->format(DATE_ISO8601))
|
||||||
->twitter('audio:partner', $episode->podcast->publisher ?? '')
|
->twitter('audio:partner', $episode->podcast->publisher ?? '')
|
||||||
|
@ -79,10 +79,10 @@ class VideoClip
|
|||||||
|
|
||||||
helper(['media']);
|
helper(['media']);
|
||||||
|
|
||||||
$this->audioInput = media_path($this->episode->audio_file_path);
|
$this->audioInput = media_path($this->episode->audio->file_path);
|
||||||
$this->episodeCoverPath = media_path($this->episode->cover->path);
|
$this->episodeCoverPath = media_path($this->episode->cover->path);
|
||||||
if ($this->episode->transcript_file_path !== null) {
|
if ($this->episode->transcript !== null) {
|
||||||
$this->subtitlesInput = media_path($this->episode->transcript_file_path);
|
$this->subtitlesInput = media_path($this->episode->transcript->file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
$podcastFolder = media_path("podcasts/{$this->episode->podcast->handle}");
|
$podcastFolder = media_path("podcasts/{$this->episode->podcast->handle}");
|
||||||
@ -167,7 +167,6 @@ class VideoClip
|
|||||||
"{$this->videoClipOutput}",
|
"{$this->videoClipOutput}",
|
||||||
];
|
];
|
||||||
|
|
||||||
// dd(implode(' ', $videoClipCmd));
|
|
||||||
return implode(' ', $videoClipCmd);
|
return implode(' ', $videoClipCmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,24 +52,24 @@ class PodcastEpisode extends ObjectType
|
|||||||
|
|
||||||
$this->image = [
|
$this->image = [
|
||||||
'type' => 'Image',
|
'type' => 'Image',
|
||||||
'mediaType' => $episode->cover_mimetype,
|
'mediaType' => $episode->cover->file_content_type,
|
||||||
'url' => $episode->cover->feed_url,
|
'url' => $episode->cover->feed_url,
|
||||||
];
|
];
|
||||||
|
|
||||||
// add audio file
|
// add audio file
|
||||||
$this->audio = [
|
$this->audio = [
|
||||||
'id' => $episode->audio_file_url,
|
'id' => $episode->audio->file_url,
|
||||||
'type' => 'Audio',
|
'type' => 'Audio',
|
||||||
'name' => $episode->title,
|
'name' => $episode->title,
|
||||||
'size' => $episode->audio_file_size,
|
'size' => $episode->audio->file_size,
|
||||||
'duration' => $episode->audio_file_duration,
|
'duration' => $episode->audio->duration,
|
||||||
'url' => [
|
'url' => [
|
||||||
'href' => $episode->audio_file_url,
|
'href' => $episode->audio->file_url,
|
||||||
'type' => 'Link',
|
'type' => 'Link',
|
||||||
'mediaType' => $episode->audio_file_mimetype,
|
'mediaType' => $episode->audio->file_content_type,
|
||||||
],
|
],
|
||||||
'transcript' => $episode->transcript_file_url,
|
'transcript' => $episode->transcript->file_url,
|
||||||
'chapters' => $episode->chapters_file_url,
|
'chapters' => $episode->chapters->file_url,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug);
|
$this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug);
|
||||||
|
@ -12,16 +12,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Entities\Soundbite;
|
use App\Entities\Clip;
|
||||||
use CodeIgniter\Database\BaseResult;
|
use CodeIgniter\Database\BaseResult;
|
||||||
use CodeIgniter\Model;
|
use CodeIgniter\Model;
|
||||||
|
|
||||||
class SoundbiteModel extends Model
|
class ClipsModel extends Model
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $table = 'soundbites';
|
protected $table = 'clips';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -35,6 +35,7 @@ class SoundbiteModel extends Model
|
|||||||
'podcast_id',
|
'podcast_id',
|
||||||
'episode_id',
|
'episode_id',
|
||||||
'label',
|
'label',
|
||||||
|
'type',
|
||||||
'start_time',
|
'start_time',
|
||||||
'duration',
|
'duration',
|
||||||
'created_by',
|
'created_by',
|
||||||
@ -44,7 +45,7 @@ class SoundbiteModel extends Model
|
|||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $returnType = Soundbite::class;
|
protected $returnType = Clip::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
@ -71,23 +72,23 @@ class SoundbiteModel extends Model
|
|||||||
*/
|
*/
|
||||||
protected $beforeDelete = ['clearCache'];
|
protected $beforeDelete = ['clearCache'];
|
||||||
|
|
||||||
public function deleteSoundbite(int $podcastId, int $episodeId, int $soundbiteId): BaseResult | bool
|
public function deleteClip(int $podcastId, int $episodeId, int $clipId): BaseResult | bool
|
||||||
{
|
{
|
||||||
return $this->delete([
|
return $this->delete([
|
||||||
'podcast_id' => $podcastId,
|
'podcast_id' => $podcastId,
|
||||||
'episode_id' => $episodeId,
|
'episode_id' => $episodeId,
|
||||||
'id' => $soundbiteId,
|
'id' => $clipId,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all soundbites for an episode
|
* Gets all clips for an episode
|
||||||
*
|
*
|
||||||
* @return Soundbite[]
|
* @return Clip[]
|
||||||
*/
|
*/
|
||||||
public function getEpisodeSoundbites(int $podcastId, int $episodeId): array
|
public function getEpisodeClips(int $podcastId, int $episodeId): array
|
||||||
{
|
{
|
||||||
$cacheName = "podcast#{$podcastId}_episode#{$episodeId}_soundbites";
|
$cacheName = "podcast#{$podcastId}_episode#{$episodeId}_clips";
|
||||||
if (! ($found = cache($cacheName))) {
|
if (! ($found = cache($cacheName))) {
|
||||||
$found = $this->where([
|
$found = $this->where([
|
||||||
'episode_id' => $episodeId,
|
'episode_id' => $episodeId,
|
||||||
@ -114,7 +115,7 @@ class SoundbiteModel extends Model
|
|||||||
);
|
);
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->delete("podcast#{$episode->podcast_id}_episode#{$episode->id}_soundbites");
|
->delete("podcast#{$episode->podcast_id}_episode#{$episode->id}_clips");
|
||||||
|
|
||||||
// delete cache for rss feed
|
// delete cache for rss feed
|
||||||
cache()
|
cache()
|
@ -68,18 +68,13 @@ class EpisodeModel extends Model
|
|||||||
'guid',
|
'guid',
|
||||||
'title',
|
'title',
|
||||||
'slug',
|
'slug',
|
||||||
'audio_file_path',
|
'audio_file_id',
|
||||||
'audio_file_duration',
|
|
||||||
'audio_file_mimetype',
|
|
||||||
'audio_file_size',
|
|
||||||
'audio_file_header_size',
|
|
||||||
'description_markdown',
|
'description_markdown',
|
||||||
'description_html',
|
'description_html',
|
||||||
'cover_path',
|
'cover_id',
|
||||||
'cover_mimetype',
|
'transcript_file_id',
|
||||||
'transcript_file_path',
|
|
||||||
'transcript_file_remote_url',
|
'transcript_file_remote_url',
|
||||||
'chapters_file_path',
|
'chapters_file_id',
|
||||||
'chapters_file_remote_url',
|
'chapters_file_remote_url',
|
||||||
'parental_advisory',
|
'parental_advisory',
|
||||||
'number',
|
'number',
|
||||||
@ -119,7 +114,7 @@ class EpisodeModel extends Model
|
|||||||
'podcast_id' => 'required',
|
'podcast_id' => 'required',
|
||||||
'title' => 'required',
|
'title' => 'required',
|
||||||
'slug' => 'required|regex_match[/^[a-zA-Z0-9\-]{1,191}$/]',
|
'slug' => 'required|regex_match[/^[a-zA-Z0-9\-]{1,191}$/]',
|
||||||
'audio_file_path' => 'required',
|
'audio_file_id' => 'required',
|
||||||
'description_markdown' => 'required',
|
'description_markdown' => 'required',
|
||||||
'number' => 'is_natural_no_zero|permit_empty',
|
'number' => 'is_natural_no_zero|permit_empty',
|
||||||
'season_number' => 'is_natural_no_zero|permit_empty',
|
'season_number' => 'is_natural_no_zero|permit_empty',
|
||||||
|
109
app/Models/MediaModel.php
Normal file
109
app/Models/MediaModel.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Entities\Audio;
|
||||||
|
use App\Entities\Image;
|
||||||
|
use App\Entities\Media;
|
||||||
|
use CodeIgniter\Database\ConnectionInterface;
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
use CodeIgniter\Validation\ValidationInterface;
|
||||||
|
|
||||||
|
class MediaModel extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $table = 'media';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $returnType = Media::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $allowedFields = [
|
||||||
|
'id',
|
||||||
|
'file_path',
|
||||||
|
'file_size',
|
||||||
|
'file_content_type',
|
||||||
|
'file_metadata',
|
||||||
|
'type',
|
||||||
|
'description',
|
||||||
|
'language_code',
|
||||||
|
'uploaded_by',
|
||||||
|
'updated_by',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model constructor.
|
||||||
|
*
|
||||||
|
* @param ConnectionInterface|null $db DB Connection
|
||||||
|
* @param ValidationInterface|null $validation Validation
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
protected string $fileType,
|
||||||
|
ConnectionInterface &$db = null,
|
||||||
|
ValidationInterface $validation = null
|
||||||
|
) {
|
||||||
|
switch ($fileType) {
|
||||||
|
case 'audio':
|
||||||
|
$this->returnType = Audio::class;
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
$this->returnType = Image::class;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing, keep Media class as default
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($db, $validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Media|Image|Audio
|
||||||
|
*/
|
||||||
|
public function getMediaById(int $mediaId): object
|
||||||
|
{
|
||||||
|
$cacheName = "media#{$mediaId}";
|
||||||
|
if (! ($found = cache($cacheName))) {
|
||||||
|
$builder = $this->where([
|
||||||
|
'id' => $mediaId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = $builder->first();
|
||||||
|
$mediaClass = $this->returnType;
|
||||||
|
$found = new $mediaClass($result->toArray(false, true));
|
||||||
|
|
||||||
|
cache()
|
||||||
|
->save($cacheName, $found, DECADE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Media|Image|Audio $media
|
||||||
|
*/
|
||||||
|
public function saveMedia(object $media): int | false
|
||||||
|
{
|
||||||
|
// insert record in database
|
||||||
|
if (! $mediaId = $this->insert($media, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
return $mediaId;
|
||||||
|
}
|
||||||
|
}
|
112
app/Models/MediaModelOLD.php
Normal file
112
app/Models/MediaModelOLD.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Entities\Audio;
|
||||||
|
use App\Entities\Image;
|
||||||
|
use App\Entities\Media;
|
||||||
|
use CodeIgniter\Database\ConnectionInterface;
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
use CodeIgniter\Validation\ValidationInterface;
|
||||||
|
|
||||||
|
class MediaModel extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $table = 'media';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $returnType = Media::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $allowedFields = [
|
||||||
|
'id',
|
||||||
|
'file_path',
|
||||||
|
'file_size',
|
||||||
|
'file_content_type',
|
||||||
|
'file_metadata',
|
||||||
|
'type',
|
||||||
|
'description',
|
||||||
|
'language_code',
|
||||||
|
'uploaded_by',
|
||||||
|
'updated_by',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model constructor.
|
||||||
|
*
|
||||||
|
* @param ConnectionInterface|null $db DB Connection
|
||||||
|
* @param ValidationInterface|null $validation Validation
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
protected string $fileType,
|
||||||
|
ConnectionInterface &$db = null,
|
||||||
|
ValidationInterface $validation = null
|
||||||
|
) {
|
||||||
|
switch ($fileType) {
|
||||||
|
case 'audio':
|
||||||
|
$this->returnType = Audio::class;
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
$this->returnType = Image::class;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing, keep Media class as default
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($db, $validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Media|Image|Audio
|
||||||
|
*/
|
||||||
|
public function getMediaById(int $mediaId): object
|
||||||
|
{
|
||||||
|
$cacheName = "media#{$mediaId}";
|
||||||
|
if (! ($found = cache($cacheName))) {
|
||||||
|
$builder = $this->where([
|
||||||
|
'id' => $mediaId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$found = $builder->first();
|
||||||
|
|
||||||
|
cache()
|
||||||
|
->save($cacheName, $found, DECADE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Media|Image $media
|
||||||
|
*/
|
||||||
|
public function saveMedia(object $media): int | false
|
||||||
|
{
|
||||||
|
// insert record in database
|
||||||
|
if (! $mediaId = $this->insert($media, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
return $mediaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteFile(int $mediaId): void
|
||||||
|
{
|
||||||
|
// TODO: get file, delete it from disk & from database
|
||||||
|
}
|
||||||
|
}
|
@ -35,8 +35,7 @@ class PersonModel extends Model
|
|||||||
'full_name',
|
'full_name',
|
||||||
'unique_name',
|
'unique_name',
|
||||||
'information_url',
|
'information_url',
|
||||||
'avatar_path',
|
'avatar_id',
|
||||||
'avatar_mimetype',
|
|
||||||
'created_by',
|
'created_by',
|
||||||
'updated_by',
|
'updated_by',
|
||||||
];
|
];
|
||||||
|
@ -40,10 +40,8 @@ class PodcastModel extends Model
|
|||||||
'description_html',
|
'description_html',
|
||||||
'episode_description_footer_markdown',
|
'episode_description_footer_markdown',
|
||||||
'episode_description_footer_html',
|
'episode_description_footer_html',
|
||||||
'cover_path',
|
'cover_id',
|
||||||
'cover_mimetype',
|
'banner_id',
|
||||||
'banner_path',
|
|
||||||
'banner_mimetype',
|
|
||||||
'language_code',
|
'language_code',
|
||||||
'category_id',
|
'category_id',
|
||||||
'parental_advisory',
|
'parental_advisory',
|
||||||
@ -92,7 +90,7 @@ class PodcastModel extends Model
|
|||||||
'handle' =>
|
'handle' =>
|
||||||
'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]|is_unique[podcasts.handle,id,{id}]',
|
'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]|is_unique[podcasts.handle,id,{id}]',
|
||||||
'description_markdown' => 'required',
|
'description_markdown' => 'required',
|
||||||
'cover_path' => 'required',
|
'cover_id' => 'required',
|
||||||
'language_code' => 'required',
|
'language_code' => 'required',
|
||||||
'category_id' => 'required',
|
'category_id' => 'required',
|
||||||
'owner_email' => 'required|valid_email',
|
'owner_email' => 'required|valid_email',
|
||||||
@ -460,7 +458,7 @@ class PodcastModel extends Model
|
|||||||
|
|
||||||
if ($podcastActor) {
|
if ($podcastActor) {
|
||||||
$podcastActor->avatar_image_url = $podcast->cover->thumbnail_url;
|
$podcastActor->avatar_image_url = $podcast->cover->thumbnail_url;
|
||||||
$podcastActor->avatar_image_mimetype = $podcast->cover_mimetype;
|
$podcastActor->avatar_image_mimetype = $podcast->cover->thumbnail_mimetype;
|
||||||
|
|
||||||
(new ActorModel())->update($podcast->actor_id, $podcastActor);
|
(new ActorModel())->update($podcast->actor_id, $podcastActor);
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ const drawEpisodesMap = async (mapDivId: string, dataUrl: string) => {
|
|||||||
data[i].longitude,
|
data[i].longitude,
|
||||||
]).bindPopup(
|
]).bindPopup(
|
||||||
'<div class="flex min-w-max w-full gap-x-2"><img src="' +
|
'<div class="flex min-w-max w-full gap-x-2"><img src="' +
|
||||||
data[i].cover_path +
|
data[i].cover_url +
|
||||||
'" alt="' +
|
'" alt="' +
|
||||||
data[i].episode_title +
|
data[i].episode_title +
|
||||||
'" class="rounded w-16 h-16" /><div class="flex flex-col flex-1"><h2 class="leading-tight text-sm w-56 line-clamp-2 font-bold"><a href="' +
|
'" class="rounded w-16 h-16" /><div class="flex flex-col flex-1"><h2 class="leading-tight text-sm w-56 line-clamp-2 font-bold"><a href="' +
|
||||||
|
@ -14,13 +14,15 @@ use App\Entities\Episode;
|
|||||||
use App\Entities\EpisodeComment;
|
use App\Entities\EpisodeComment;
|
||||||
use App\Entities\Image;
|
use App\Entities\Image;
|
||||||
use App\Entities\Location;
|
use App\Entities\Location;
|
||||||
|
use App\Entities\Media;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
use App\Entities\Post;
|
use App\Entities\Post;
|
||||||
|
use App\Models\ClipsModel;
|
||||||
use App\Models\EpisodeCommentModel;
|
use App\Models\EpisodeCommentModel;
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
|
use App\Models\MediaModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use App\Models\PostModel;
|
use App\Models\PostModel;
|
||||||
use App\Models\SoundbiteModel;
|
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
use CodeIgniter\HTTP\RedirectResponse;
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
use CodeIgniter\I18n\Time;
|
use CodeIgniter\I18n\Time;
|
||||||
@ -156,9 +158,30 @@ class EpisodeController extends BaseController
|
|||||||
'published_at' => null,
|
'published_at' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
$coverFile = $this->request->getFile('cover');
|
$coverFile = $this->request->getFile('cover');
|
||||||
if ($coverFile !== null && $coverFile->isValid()) {
|
if ($coverFile !== null && $coverFile->isValid()) {
|
||||||
$newEpisode->cover = new Image($coverFile);
|
$cover = new Image([
|
||||||
|
'file_name' => $newEpisode->slug,
|
||||||
|
'file_directory' => 'podcasts/' . $this->podcast->handle,
|
||||||
|
'sizes' => config('Images')
|
||||||
|
->podcastBannerSizes,
|
||||||
|
'file' => $this->request->getFile('banner'),
|
||||||
|
'uploaded_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
$mediaModel = new MediaModel('image');
|
||||||
|
if (! ($newCoverId = $mediaModel->saveMedia($cover))) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $mediaModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$newEpisode->cover_id = $newCoverId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$transcriptChoice = $this->request->getPost('transcript-choice');
|
$transcriptChoice = $this->request->getPost('transcript-choice');
|
||||||
@ -167,10 +190,26 @@ class EpisodeController extends BaseController
|
|||||||
&& ($transcriptFile = $this->request->getFile('transcript_file'))
|
&& ($transcriptFile = $this->request->getFile('transcript_file'))
|
||||||
&& $transcriptFile->isValid()
|
&& $transcriptFile->isValid()
|
||||||
) {
|
) {
|
||||||
$newEpisode->transcript_file = $transcriptFile;
|
$transcript = new Media([
|
||||||
|
'file_name' => $newEpisode->slug . '-transcript',
|
||||||
|
'file_directory' => 'podcasts/' . $this->podcast->handle,
|
||||||
|
'file' => $transcriptFile,
|
||||||
|
'uploaded_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
$mediaModel = new MediaModel('image');
|
||||||
|
if (! ($newTranscriptId = $mediaModel->saveMedia($transcript))) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $mediaModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$newEpisode->transcript_id = $newTranscriptId;
|
||||||
} elseif ($transcriptChoice === 'remote-url') {
|
} elseif ($transcriptChoice === 'remote-url') {
|
||||||
$newEpisode->transcript_file_remote_url = $this->request->getPost(
|
$newEpisode->transcript_remote_url = $this->request->getPost(
|
||||||
'transcript_file_remote_url'
|
'transcript_remote_url'
|
||||||
) === '' ? null : $this->request->getPost('transcript_file_remote_url');
|
) === '' ? null : $this->request->getPost('transcript_file_remote_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,11 +852,11 @@ class EpisodeController extends BaseController
|
|||||||
return redirect()->route('soundbites-edit', [$this->podcast->id, $this->episode->id]);
|
return redirect()->route('soundbites-edit', [$this->podcast->id, $this->episode->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function soundbiteDelete(string $soundbiteId): RedirectResponse
|
public function soundbiteDelete(string $clipId): RedirectResponse
|
||||||
{
|
{
|
||||||
(new SoundbiteModel())->deleteSoundbite($this->podcast->id, $this->episode->id, (int) $soundbiteId);
|
(new ClipsModel())->deleteClip($this->podcast->id, $this->episode->id, (int) $clipId);
|
||||||
|
|
||||||
return redirect()->route('soundbites-edit', [$this->podcast->id, $this->episode->id]);
|
return redirect()->route('clips-edit', [$this->podcast->id, $this->episode->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function embed(): string
|
public function embed(): string
|
||||||
|
@ -16,6 +16,7 @@ use App\Entities\Podcast;
|
|||||||
use App\Models\CategoryModel;
|
use App\Models\CategoryModel;
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
use App\Models\LanguageModel;
|
use App\Models\LanguageModel;
|
||||||
|
use App\Models\MediaModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
use CodeIgniter\HTTP\RedirectResponse;
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
@ -192,11 +193,10 @@ class PodcastController extends BaseController
|
|||||||
$partnerImageUrl = null;
|
$partnerImageUrl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$podcast = new Podcast([
|
$newPodcast = new Podcast([
|
||||||
'title' => $this->request->getPost('title'),
|
'title' => $this->request->getPost('title'),
|
||||||
'handle' => $this->request->getPost('handle'),
|
'handle' => $this->request->getPost('handle'),
|
||||||
'description_markdown' => $this->request->getPost('description'),
|
'description_markdown' => $this->request->getPost('description'),
|
||||||
'cover' => new Image($this->request->getFile('cover')),
|
|
||||||
'language_code' => $this->request->getPost('language'),
|
'language_code' => $this->request->getPost('language'),
|
||||||
'category_id' => $this->request->getPost('category'),
|
'category_id' => $this->request->getPost('category'),
|
||||||
'parental_advisory' =>
|
'parental_advisory' =>
|
||||||
@ -225,17 +225,53 @@ class PodcastController extends BaseController
|
|||||||
'updated_by' => user_id(),
|
'updated_by' => user_id(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$cover = new Image([
|
||||||
|
'file_name' => 'cover',
|
||||||
|
'file_directory' => 'podcasts/' . $newPodcast->handle,
|
||||||
|
'sizes' => config('Images')
|
||||||
|
->podcastCoverSizes,
|
||||||
|
'file' => $this->request->getFile('cover'),
|
||||||
|
'uploaded_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
$mediaModel = new MediaModel('image');
|
||||||
|
if (! ($newCoverId = $mediaModel->saveMedia($cover))) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $mediaModel->errors());
|
||||||
|
}
|
||||||
|
$newPodcast->cover_id = $newCoverId;
|
||||||
|
|
||||||
$bannerFile = $this->request->getFile('banner');
|
$bannerFile = $this->request->getFile('banner');
|
||||||
if ($bannerFile !== null && $bannerFile->isValid()) {
|
if ($bannerFile !== null && $bannerFile->isValid()) {
|
||||||
$podcast->banner = new Image($bannerFile);
|
$banner = new Image([
|
||||||
|
'file_name' => 'banner',
|
||||||
|
'file_directory' => 'podcasts/' . $newPodcast->handle,
|
||||||
|
'sizes' => config('Images')
|
||||||
|
->podcastBannerSizes,
|
||||||
|
'file' => $this->request->getFile('banner'),
|
||||||
|
'uploaded_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
$mediaModel = new MediaModel('image');
|
||||||
|
if (! ($newBannerId = $mediaModel->saveMedia($banner))) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $mediaModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPodcast->banner_id = $newBannerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$podcastModel = new PodcastModel();
|
$podcastModel = new PodcastModel();
|
||||||
$db = db_connect();
|
if (! ($newPodcastId = $podcastModel->insert($newPodcast, true))) {
|
||||||
|
|
||||||
$db->transStart();
|
|
||||||
|
|
||||||
if (! ($newPodcastId = $podcastModel->insert($podcast, true))) {
|
|
||||||
$db->transRollback();
|
$db->transRollback();
|
||||||
return redirect()
|
return redirect()
|
||||||
->back()
|
->back()
|
||||||
@ -311,7 +347,7 @@ class PodcastController extends BaseController
|
|||||||
|
|
||||||
$coverFile = $this->request->getFile('cover');
|
$coverFile = $this->request->getFile('cover');
|
||||||
if ($coverFile !== null && $coverFile->isValid()) {
|
if ($coverFile !== null && $coverFile->isValid()) {
|
||||||
$this->podcast->cover = new Image($coverFile);
|
$this->podcast->cover->setFile($coverFile);
|
||||||
}
|
}
|
||||||
$bannerFile = $this->request->getFile('banner');
|
$bannerFile = $this->request->getFile('banner');
|
||||||
if ($bannerFile !== null && $bannerFile->isValid()) {
|
if ($bannerFile !== null && $bannerFile->isValid()) {
|
||||||
|
@ -36,7 +36,7 @@ class SchedulerController extends Controller
|
|||||||
// set activity post to delivered
|
// set activity post to delivered
|
||||||
model('ActivityModel')
|
model('ActivityModel')
|
||||||
->update($scheduledActivity->id, [
|
->update($scheduledActivity->id, [
|
||||||
'task_status' => 'delivered',
|
'status' => 'delivered',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class AddActivities extends Migration
|
|||||||
'payload' => [
|
'payload' => [
|
||||||
'type' => 'JSON',
|
'type' => 'JSON',
|
||||||
],
|
],
|
||||||
'task_status' => [
|
'status' => [
|
||||||
'type' => 'ENUM',
|
'type' => 'ENUM',
|
||||||
'constraint' => ['queued', 'delivered'],
|
'constraint' => ['queued', 'delivered'],
|
||||||
'null' => true,
|
'null' => true,
|
||||||
|
@ -23,7 +23,7 @@ use RuntimeException;
|
|||||||
* @property Post $post
|
* @property Post $post
|
||||||
* @property string $type
|
* @property string $type
|
||||||
* @property object $payload
|
* @property object $payload
|
||||||
* @property string|null $task_status
|
* @property string|null $status
|
||||||
* @property Time|null $scheduled_at
|
* @property Time|null $scheduled_at
|
||||||
* @property Time $created_at
|
* @property Time $created_at
|
||||||
*/
|
*/
|
||||||
@ -55,7 +55,7 @@ class Activity extends UuidEntity
|
|||||||
'post_id' => '?string',
|
'post_id' => '?string',
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'payload' => 'json',
|
'payload' => 'json',
|
||||||
'task_status' => '?string',
|
'status' => '?string',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getActor(): Actor
|
public function getActor(): Actor
|
||||||
|
@ -42,7 +42,7 @@ class ActivityModel extends BaseUuidModel
|
|||||||
'post_id',
|
'post_id',
|
||||||
'type',
|
'type',
|
||||||
'payload',
|
'payload',
|
||||||
'task_status',
|
'status',
|
||||||
'scheduled_at',
|
'scheduled_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ class ActivityModel extends BaseUuidModel
|
|||||||
'type' => $type,
|
'type' => $type,
|
||||||
'payload' => $payload,
|
'payload' => $payload,
|
||||||
'scheduled_at' => $scheduledAt,
|
'scheduled_at' => $scheduledAt,
|
||||||
'task_status' => $taskStatus,
|
'status' => $taskStatus,
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
@ -112,7 +112,7 @@ class ActivityModel extends BaseUuidModel
|
|||||||
public function getScheduledActivities(): array
|
public function getScheduledActivities(): array
|
||||||
{
|
{
|
||||||
return $this->where('`scheduled_at` <= NOW()', null, false)
|
return $this->where('`scheduled_at` <= NOW()', null, false)
|
||||||
->where('task_status', 'queued')
|
->where('status', 'queued')
|
||||||
->orderBy('scheduled_at', 'ASC')
|
->orderBy('scheduled_at', 'ASC')
|
||||||
->findAll();
|
->findAll();
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ Options All -Indexes
|
|||||||
Options +FollowSymlinks
|
Options +FollowSymlinks
|
||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
|
|
||||||
# If you installed CodeIgniter in a subfolder, you will need to
|
# If you installed Castopod Host in a subfolder, you will need to
|
||||||
# change the following line to match the subfolder you need.
|
# change the following line to match the subfolder you need.
|
||||||
# http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
|
# http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
|
||||||
# RewriteBase /
|
# RewriteBase /
|
||||||
|
@ -29,9 +29,9 @@
|
|||||||
'cell' => function ($episode, $podcast) {
|
'cell' => function ($episode, $podcast) {
|
||||||
return '<div class="flex">' .
|
return '<div class="flex">' .
|
||||||
'<div class="relative flex-shrink-0 mr-2">' .
|
'<div class="relative flex-shrink-0 mr-2">' .
|
||||||
'<time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/50" datetime="PT<?= $episode->audio_file_duration ?>S">' .
|
'<time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/50" datetime="PT<?= $episode->audio->duration ?>S">' .
|
||||||
format_duration(
|
format_duration(
|
||||||
$episode->audio_file_duration,
|
$episode->audio->duration,
|
||||||
) .
|
) .
|
||||||
'</time>' .
|
'</time>' .
|
||||||
'<img loading="lazy" src="' . $episode->cover->thumbnail_url . '" alt="' . $episode->title . '" class="object-cover w-20 rounded-lg shadow-inner aspect-square" />' .
|
'<img loading="lazy" src="' . $episode->cover->thumbnail_url . '" alt="' . $episode->title . '" class="object-cover w-20 rounded-lg shadow-inner aspect-square" />' .
|
||||||
|
@ -54,12 +54,12 @@
|
|||||||
) ?>
|
) ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-skin-muted">
|
<div class="text-xs text-skin-muted">
|
||||||
<time datetime="PT<?= $episode->audio_file_duration ?>S">
|
<time datetime="PT<?= $episode->audio->duration ?>S">
|
||||||
<?= format_duration($episode->audio_file_duration) ?>
|
<?= format_duration($episode->audio->duration) ?>
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<?= audio_player($episode->audio_file_url, $episode->audio_file_mimetype, 'mt-auto') ?>
|
<?= audio_player($episode->audio->file_url, $episode->audio->file_content_type, 'mt-auto') ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer class="flex justify-around px-6 py-3">
|
<footer class="flex justify-around px-6 py-3">
|
||||||
|
@ -58,12 +58,12 @@
|
|||||||
<div class="text-xs text-skin-muted">
|
<div class="text-xs text-skin-muted">
|
||||||
<?= relative_time($episode->published_at) ?>
|
<?= relative_time($episode->published_at) ?>
|
||||||
<span class="mx-1">•</span>
|
<span class="mx-1">•</span>
|
||||||
<time datetime="PT<?= $episode->audio_file_duration ?>S">
|
<time datetime="PT<?= $episode->audio->duration ?>S">
|
||||||
<?= format_duration($episode->audio_file_duration) ?>
|
<?= format_duration($episode->audio->duration) ?>
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<?= audio_player($episode->audio_file_url, $episode->audio_file_mimetype, 'mt-auto') ?>
|
<?= audio_player($episode->audio->file_url, $episode->audio->file_content_type, 'mt-auto') ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer class="flex justify-around px-6 py-3">
|
<footer class="flex justify-around px-6 py-3">
|
||||||
|
@ -35,8 +35,8 @@
|
|||||||
|
|
||||||
foreach ($episode->soundbites as $soundbite) {
|
foreach ($episode->soundbites as $soundbite) {
|
||||||
$table->addRow(
|
$table->addRow(
|
||||||
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio_file_duration}' name='soundbites[{$soundbite->id}][start_time]' value='{$soundbite->start_time}' data-type='soundbite-field' data-field-type='start_time' required='true' />",
|
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio->duration}' name='soundbites[{$soundbite->id}][start_time]' value='{$soundbite->start_time}' data-type='soundbite-field' data-field-type='start_time' required='true' />",
|
||||||
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio_file_duration}' name='soundbites[{$soundbite->id}][duration]' value='{$soundbite->duration}' data-type='soundbite-field' data-field-type='duration' required='true' />",
|
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio->duration}' name='soundbites[{$soundbite->id}][duration]' value='{$soundbite->duration}' data-type='soundbite-field' data-field-type='duration' required='true' />",
|
||||||
"<Forms.Input class='flex-1' name='soundbites[{$soundbite->id}][label]' value='{$soundbite->label}' />",
|
"<Forms.Input class='flex-1' name='soundbites[{$soundbite->id}][label]' value='{$soundbite->label}' />",
|
||||||
"<IconButton variant='primary' glyph='play' data-type='play-soundbite' data-soundbite-id='{$soundbite->id}'>" . lang('Episode.soundbites_form.play') . '</IconButton>',
|
"<IconButton variant='primary' glyph='play' data-type='play-soundbite' data-soundbite-id='{$soundbite->id}'>" . lang('Episode.soundbites_form.play') . '</IconButton>',
|
||||||
'<IconButton uri=' . route_to(
|
'<IconButton uri=' . route_to(
|
||||||
@ -49,8 +49,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$table->addRow(
|
$table->addRow(
|
||||||
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio_file_duration}' name='soundbites[0][start_time]' data-type='soundbite-field' data-field-type='start_time' />",
|
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio->duration}' name='soundbites[0][start_time]' data-type='soundbite-field' data-field-type='start_time' />",
|
||||||
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio_file_duration}' name='soundbites[0][duration]' data-type='soundbite-field' data-field-type='duration' />",
|
"<Forms.Input class='w-24' type='number' step='any' min='0' max='{$episode->audio->duration}' name='soundbites[0][duration]' data-type='soundbite-field' data-field-type='duration' />",
|
||||||
"<Forms.Input class='flex-1' name='soundbites[0][label]' />",
|
"<Forms.Input class='flex-1' name='soundbites[0][label]' />",
|
||||||
"<IconButton variant='primary' glyph='play' data-type='play-soundbite' data-soundbite-id='0'>" . lang('Episode.soundbites_form.play') . '</IconButton>',
|
"<IconButton variant='primary' glyph='play' data-type='play-soundbite' data-soundbite-id='0'>" . lang('Episode.soundbites_form.play') . '</IconButton>',
|
||||||
);
|
);
|
||||||
@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
<div class="flex items-center gap-x-2">
|
<div class="flex items-center gap-x-2">
|
||||||
<audio controls preload="auto" class="flex-1 w-full">
|
<audio controls preload="auto" class="flex-1 w-full">
|
||||||
<source src="<?= $episode->audio_file_url ?>" type="<?= $episode->audio_file_mimetype ?>">
|
<source src="<?= $episode->audio->file_url ?>" type="<?= $episode->audio->file_content_type ?>">
|
||||||
Your browser does not support the audio tag.
|
Your browser does not support the audio tag.
|
||||||
</audio>
|
</audio>
|
||||||
<IconButton glyph="timer" variant="info" data-type="get-soundbite" data-start-time-field-name="soundbites[0][start_time]" data-duration-field-name="soundbites[0][duration]" ><?= lang('Episode.soundbites_form.bookmark') ?></IconButton>
|
<IconButton glyph="timer" variant="info" data-type="get-soundbite" data-start-time-field-name="soundbites[0][start_time]" data-duration-field-name="soundbites[0][duration]" ><?= lang('Episode.soundbites_form.bookmark') ?></IconButton>
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<?= $this->section('content') ?>
|
<?= $this->section('content') ?>
|
||||||
|
|
||||||
<div class="mb-12">
|
<div class="mb-12">
|
||||||
<?= audio_player($episode->audio_file_url, $episode->audio_file_mimetype) ?>
|
<?= audio_player($episode->audio->file_url, $episode->audio->file_content_type) ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
|
|
||||||
<div class="sticky z-40 flex flex-col w-full max-w-xs overflow-hidden shadow-sm bg-elevated border-3 border-subtle top-24 rounded-xl">
|
<div class="sticky z-40 flex flex-col w-full max-w-xs overflow-hidden shadow-sm bg-elevated border-3 border-subtle top-24 rounded-xl">
|
||||||
<?php if ($podcast->banner_path !== null): ?>
|
<?php if ($podcast->banner_id !== null): ?>
|
||||||
<a href="<?= route_to('podcast-banner-delete', $podcast->id) ?>" class="absolute p-1 text-red-700 bg-red-100 border-2 rounded-full hover:text-red-900 border-contrast focus:ring-accent top-2 right-2" title="<?= lang('Podcast.form.banner_delete') ?>" data-tooltip="bottom"><?= icon('delete-bin') ?></a>
|
<a href="<?= route_to('podcast-banner-delete', $podcast->id) ?>" class="absolute p-1 text-red-700 bg-red-100 border-2 rounded-full hover:text-red-900 border-contrast focus:ring-accent top-2 right-2" title="<?= lang('Podcast.form.banner_delete') ?>" data-tooltip="bottom"><?= icon('delete-bin') ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<img src="<?= $podcast->banner->small_url ?>" alt="" class="object-cover w-full aspect-[3/1] bg-header" />
|
<img src="<?= $podcast->banner->small_url ?>" alt="" class="object-cover w-full aspect-[3/1] bg-header" />
|
||||||
|
@ -41,12 +41,12 @@
|
|||||||
style="--vm-player-box-shadow:0; --vm-player-theme: hsl(var(--color-accent-base)); --vm-control-focus-color: hsl(var(--color-accent-contrast)); --vm-control-spacing: 4px; --vm-menu-item-focus-bg: hsl(var(--color-background-highlight)); --vm-control-icon-size: 24px; <?= str_ends_with($theme, 'transparent') ? '--vm-controls-bg: transparent;' : '' ?>"
|
style="--vm-player-box-shadow:0; --vm-player-theme: hsl(var(--color-accent-base)); --vm-control-focus-color: hsl(var(--color-accent-contrast)); --vm-control-spacing: 4px; --vm-menu-item-focus-bg: hsl(var(--color-background-highlight)); --vm-control-icon-size: 24px; <?= str_ends_with($theme, 'transparent') ? '--vm-controls-bg: transparent;' : '' ?>"
|
||||||
>
|
>
|
||||||
<vm-audio preload="none">
|
<vm-audio preload="none">
|
||||||
<?php $source = logged_in() ? $episode->audio_file_url : $episode->audio_file_analytics_url .
|
<?php $source = logged_in() ? $episode->audio->file_url : $episode->audio_file_analytics_url .
|
||||||
(isset($_SERVER['HTTP_REFERER'])
|
(isset($_SERVER['HTTP_REFERER'])
|
||||||
? '?_from=' .
|
? '?_from=' .
|
||||||
parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST)
|
parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST)
|
||||||
: '') ?>
|
: '') ?>
|
||||||
<source src="<?= $source ?>" type="<?= $episode->audio_file_mimetype ?>" />
|
<source src="<?= $source ?>" type="<?= $episode->audio->file_content_type ?>" />
|
||||||
</vm-audio>
|
</vm-audio>
|
||||||
<vm-ui>
|
<vm-ui>
|
||||||
<vm-icon-library name="castopod-icons"></vm-icon-library>
|
<vm-icon-library name="castopod-icons"></vm-icon-library>
|
||||||
|
@ -115,14 +115,14 @@
|
|||||||
title="<?= $episode->title ?>"
|
title="<?= $episode->title ?>"
|
||||||
podcast="<?= $episode->podcast->title ?>"
|
podcast="<?= $episode->podcast->title ?>"
|
||||||
src="<?= $episode->audio_file_web_url ?>"
|
src="<?= $episode->audio_file_web_url ?>"
|
||||||
mediaType="<?= $episode->audio_file_mimetype ?>"
|
mediaType="<?= $episode->audio->file_content_type ?>"
|
||||||
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
||||||
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
||||||
<div class="text-xs">
|
<div class="text-xs">
|
||||||
<?= relative_time($episode->published_at) ?>
|
<?= relative_time($episode->published_at) ?>
|
||||||
<span class="mx-1">•</span>
|
<span class="mx-1">•</span>
|
||||||
<time datetime="PT<?= $episode->audio_file_duration ?>S">
|
<time datetime="PT<?= $episode->audio->duration ?>S">
|
||||||
<?= format_duration_symbol($episode->audio_file_duration) ?>
|
<?= format_duration_symbol($episode->audio->duration) ?>
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<article class="flex w-full p-4 shadow bg-elevated rounded-conditional-2xl gap-x-2">
|
<article class="flex w-full p-4 shadow bg-elevated rounded-conditional-2xl gap-x-2">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/75" datetime="PT<?= $episode->audio_file_duration ?>S">
|
<time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/75" datetime="PT<?= $episode->audio->duration ?>S">
|
||||||
<?= format_duration($episode->audio_file_duration) ?>
|
<?= format_duration($episode->audio->duration) ?>
|
||||||
</time>
|
</time>
|
||||||
<img loading="lazy" src="<?= $episode->cover
|
<img loading="lazy" src="<?= $episode->cover
|
||||||
->thumbnail_url ?>" alt="<?= $episode->title ?>" class="object-cover w-20 rounded-lg shadow-inner aspect-square" />
|
->thumbnail_url ?>" alt="<?= $episode->title ?>" class="object-cover w-20 rounded-lg shadow-inner aspect-square" />
|
||||||
@ -20,7 +20,7 @@
|
|||||||
title="<?= $episode->title ?>"
|
title="<?= $episode->title ?>"
|
||||||
podcast="<?= $episode->podcast->title ?>"
|
podcast="<?= $episode->podcast->title ?>"
|
||||||
src="<?= $episode->audio_file_web_url ?>"
|
src="<?= $episode->audio_file_web_url ?>"
|
||||||
mediaType="<?= $episode->audio_file_mimetype ?>"
|
mediaType="<?= $episode->audio->file_content_type ?>"
|
||||||
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
||||||
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div class="flex items-center border-y border-subtle">
|
<div class="flex items-center border-y border-subtle">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<time class="absolute px-1 text-sm font-semibold text-white rounded bg-black/75 bottom-2 right-2" datetime="PT<?= $episode->audio_file_duration ?>S">
|
<time class="absolute px-1 text-sm font-semibold text-white rounded bg-black/75 bottom-2 right-2" datetime="PT<?= $episode->audio->duration ?>S">
|
||||||
<?= format_duration($episode->audio_file_duration) ?>
|
<?= format_duration($episode->audio->duration) ?>
|
||||||
</time>
|
</time>
|
||||||
<img
|
<img
|
||||||
src="<?= $episode->cover->thumbnail_url ?>"
|
src="<?= $episode->cover->thumbnail_url ?>"
|
||||||
@ -21,7 +21,7 @@
|
|||||||
title="<?= $episode->title ?>"
|
title="<?= $episode->title ?>"
|
||||||
podcast="<?= $episode->podcast->title ?>"
|
podcast="<?= $episode->podcast->title ?>"
|
||||||
src="<?= $episode->audio_file_web_url ?>"
|
src="<?= $episode->audio_file_web_url ?>"
|
||||||
mediaType="<?= $episode->audio_file_mimetype ?>"
|
mediaType="<?= $episode->audio->file_content_type ?>"
|
||||||
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
||||||
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
||||||
</div>
|
</div>
|
Loading…
x
Reference in New Issue
Block a user