feat: add remote_url alternative for transcript and chapters files

This commit is contained in:
Yassine Doghri 2021-05-03 17:39:58 +00:00
parent 1296187613
commit 3143c9ad36
41 changed files with 761 additions and 403 deletions

View File

@ -1,4 +1,4 @@
image: php:7.2-fpm image: php:7.3-fpm
stages: stages:
- bundle - bundle

View File

@ -26,10 +26,10 @@ class Analytics extends AnalyticsBase
$this->gateway = config('App')->adminGateway . '/analytics'; $this->gateway = config('App')->adminGateway . '/analytics';
} }
public function getEnclosureUrl($enclosureUri) public function getAudioFileUrl($audioFilePath)
{ {
helper('media'); helper('media');
return media_base_url($enclosureUri); return media_base_url($audioFilePath);
} }
} }

View File

@ -108,11 +108,12 @@ class Episode extends BaseController
public function attemptCreate() public function attemptCreate()
{ {
$rules = [ $rules = [
'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]', 'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]',
'image' => 'image' =>
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]', 'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'transcript' => 'ext_in[transcript,txt,html,srt,json]|permit_empty', 'transcript_file' =>
'chapters' => 'ext_in[chapters,json]|permit_empty', 'ext_in[transcript,txt,html,srt,json]|permit_empty',
'chapters_file' => 'ext_in[chapters,json]|permit_empty',
]; ];
if (!$this->validate($rules)) { if (!$this->validate($rules)) {
@ -127,7 +128,7 @@ class Episode extends BaseController
'title' => $this->request->getPost('title'), 'title' => $this->request->getPost('title'),
'slug' => $this->request->getPost('slug'), 'slug' => $this->request->getPost('slug'),
'guid' => '', 'guid' => '',
'enclosure' => $this->request->getFile('enclosure'), 'audio_file' => $this->request->getFile('audio_file'),
'description_markdown' => $this->request->getPost('description'), 'description_markdown' => $this->request->getPost('description'),
'image' => $this->request->getFile('image'), 'image' => $this->request->getFile('image'),
'location' => $this->request->getPost('location_name'), 'location' => $this->request->getPost('location_name'),
@ -151,6 +152,30 @@ class Episode extends BaseController
'published_at' => null, 'published_at' => null,
]); ]);
$transcriptChoice = $this->request->getPost('transcript-choice');
if (
$transcriptChoice === 'upload-file' &&
($transcriptFile = $this->request->getFile('transcript_file'))
) {
$newEpisode->transcript_file = $transcriptFile;
} elseif ($transcriptChoice === 'remote-url') {
$newEpisode->transcript_file_remote_url = $this->request->getPost(
'transcript_file_remote_url',
);
}
$chaptersChoice = $this->request->getPost('chapters-choice');
if (
$chaptersChoice === 'upload-file' &&
($chaptersFile = $this->request->getFile('chapters_file'))
) {
$newEpisode->chapters_file = $chaptersFile;
} elseif ($chaptersChoice === 'remote-url') {
$newEpisode->chapters_file_remote_url = $this->request->getPost(
'chapters_file_remote_url',
);
}
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
if (!($newEpisodeId = $episodeModel->insert($newEpisode, true))) { if (!($newEpisodeId = $episodeModel->insert($newEpisode, true))) {
@ -201,12 +226,13 @@ class Episode extends BaseController
public function attemptEdit() public function attemptEdit()
{ {
$rules = [ $rules = [
'enclosure' => 'audio_file' =>
'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty', 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]|permit_empty',
'image' => 'image' =>
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]', 'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'transcript' => 'ext_in[transcript,txt,html,srt,json]|permit_empty', 'transcript_file' =>
'chapters' => 'ext_in[chapters,json]|permit_empty', 'ext_in[transcript_file,txt,html,srt,json]|permit_empty',
'chapters_file' => 'ext_in[chapters_file,json]|permit_empty',
]; ];
if (!$this->validate($rules)) { if (!$this->validate($rules)) {
@ -240,21 +266,61 @@ class Episode extends BaseController
$this->episode->updated_by = user()->id; $this->episode->updated_by = user()->id;
$enclosure = $this->request->getFile('enclosure'); $audioFile = $this->request->getFile('audio_file');
if ($enclosure->isValid()) { if ($audioFile) {
$this->episode->enclosure = $enclosure; $this->episode->audio_file = $audioFile;
} }
$image = $this->request->getFile('image'); $image = $this->request->getFile('image');
if ($image) { if ($image) {
$this->episode->image = $image; $this->episode->image = $image;
} }
$transcript = $this->request->getFile('transcript');
if ($transcript->isValid()) { $transcriptChoice = $this->request->getPost('transcript-choice');
$this->episode->transcript = $transcript; if ($transcriptChoice === 'upload-file') {
$transcriptFile = $this->request->getFile('transcript_file');
if ($transcriptFile->isValid()) {
$this->episode->transcript_file = $transcriptFile;
$this->episode->transcript_file_remote_url = null;
}
} elseif ($transcriptChoice === 'remote-url') {
if (
$transcriptFileRemoteUrl = $this->request->getPost(
'transcript_file_remote_url',
)
) {
if (
($transcriptFile = $this->episode->transcript_file) &&
!empty($transcriptFile)
) {
unlink($transcriptFile);
$this->episode->transcript_file_path = null;
}
}
$this->episode->transcript_file_remote_url = $transcriptFileRemoteUrl;
} }
$chapters = $this->request->getFile('chapters');
if ($chapters->isValid()) { $chaptersChoice = $this->request->getPost('chapters-choice');
$this->episode->chapters = $chapters; if ($chaptersChoice === 'upload-file') {
$chaptersFile = $this->request->getFile('chapters_file');
if ($chaptersFile->isValid()) {
$this->episode->chapters_file = $chaptersFile;
$this->episode->chapters_file_remote_url = null;
}
} elseif ($chaptersChoice === 'remote-url') {
if (
$chaptersFileRemoteUrl = $this->request->getPost(
'chapters_file_remote_url',
)
) {
if (
($chaptersFile = $this->episode->chapters_file) &&
!empty($chaptersFile)
) {
unlink($chaptersFile);
$this->episode->chapters_file_path = null;
}
}
$this->episode->chapters_file_remote_url = $chaptersFileRemoteUrl;
} }
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
@ -289,8 +355,8 @@ class Episode extends BaseController
public function transcriptDelete() public function transcriptDelete()
{ {
unlink($this->episode->transcript); unlink($this->episode->transcript_file);
$this->episode->transcript_uri = null; $this->episode->transcript_file_path = null;
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
@ -306,8 +372,8 @@ class Episode extends BaseController
public function chaptersDelete() public function chaptersDelete()
{ {
unlink($this->episode->chapters); unlink($this->episode->chapters_file);
$this->episode->chapters_uri = null; $this->episode->chapters_file_path = null;
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();

View File

@ -345,7 +345,7 @@ class PodcastImport extends BaseController
'guid' => empty($item->guid) ? null : $item->guid, 'guid' => empty($item->guid) ? null : $item->guid,
'title' => $item->title, 'title' => $item->title,
'slug' => $slug, 'slug' => $slug,
'enclosure' => download_file($item->enclosure->attributes()), 'audio_file' => download_file($item->enclosure->attributes()),
'description_markdown' => $converter->convert( 'description_markdown' => $converter->convert(
$itemDescriptionHtml, $itemDescriptionHtml,
), ),

View File

@ -41,7 +41,7 @@ class AddPodcasts extends Migration
'description_html' => [ 'description_html' => [
'type' => 'TEXT', 'type' => 'TEXT',
], ],
'image_uri' => [ 'image_path' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],

View File

@ -39,25 +39,25 @@ class AddEpisodes extends Migration
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 191, 'constraint' => 191,
], ],
'enclosure_uri' => [ 'audio_file_path' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],
'enclosure_duration' => [ 'audio_file_duration' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'comment' => 'Playtime in seconds', 'comment' => 'Playtime in seconds',
], ],
'enclosure_mimetype' => [ 'audio_file_mimetype' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],
'enclosure_filesize' => [ 'audio_file_size' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'comment' => 'File size in bytes', 'comment' => 'File size in bytes',
], ],
'enclosure_headersize' => [ 'audio_file_header_size' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'comment' => 'Header size in bytes', 'comment' => 'Header size in bytes',
@ -68,7 +68,7 @@ class AddEpisodes extends Migration
'description_html' => [ 'description_html' => [
'type' => 'TEXT', 'type' => 'TEXT',
], ],
'image_uri' => [ 'image_path' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
'null' => true, 'null' => true,
@ -80,16 +80,26 @@ class AddEpisodes extends Migration
'constraint' => 13, 'constraint' => 13,
'null' => true, 'null' => true,
], ],
'transcript_uri' => [ 'transcript_file_path' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
'null' => true, 'null' => true,
], ],
'chapters_uri' => [ 'transcript_file_remote_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
'null' => true,
],
'chapters_file_path' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
'null' => true, 'null' => true,
], ],
'chapters_file_remote_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
'null' => true,
],
'parental_advisory' => [ 'parental_advisory' => [
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['clean', 'explicit'], 'constraint' => ['clean', 'explicit'],

View File

@ -41,7 +41,7 @@ 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,
], ],
'image_uri' => [ 'image_path' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],

View File

@ -13,6 +13,8 @@ use App\Models\SoundbiteModel;
use App\Models\EpisodePersonModel; use App\Models\EpisodePersonModel;
use App\Models\NoteModel; use App\Models\NoteModel;
use CodeIgniter\Entity; use CodeIgniter\Entity;
use CodeIgniter\Files\Exceptions\FileNotFoundException;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\CommonMarkConverter;
@ -36,47 +38,37 @@ class Episode extends Entity
/** /**
* @var \CodeIgniter\Files\File * @var \CodeIgniter\Files\File
*/ */
protected $enclosure; protected $audioFile;
/** /**
* @var \CodeIgniter\Files\File * @var \CodeIgniter\Files\File
*/ */
protected $transcript; protected $transcript_file;
/** /**
* @var \CodeIgniter\Files\File * @var \CodeIgniter\Files\File
*/ */
protected $chapters; protected $chapters_file;
/** /**
* @var string * @var string
*/ */
protected $enclosure_media_path; protected $audio_file_url;
/** /**
* @var string * @var string
*/ */
protected $enclosure_url; protected $audio_file_analytics_url;
/** /**
* @var string * @var string
*/ */
protected $enclosure_web_url; protected $audio_file_web_url;
/** /**
* @var string * @var string
*/ */
protected $enclosure_opengraph_url; protected $audio_file_opengraph_url;
/**
* @var string
*/
protected $transcript_url;
/**
* @var string
*/
protected $chapters_url;
/** /**
* @var \App\Entities\EpisodePerson[] * @var \App\Entities\EpisodePerson[]
@ -132,17 +124,19 @@ class Episode extends Entity
'guid' => 'string', 'guid' => 'string',
'slug' => 'string', 'slug' => 'string',
'title' => 'string', 'title' => 'string',
'enclosure_uri' => 'string', 'audio_file_path' => 'string',
'enclosure_duration' => 'integer', 'audio_file_duration' => 'integer',
'enclosure_mimetype' => 'string', 'audio_file_mimetype' => 'string',
'enclosure_filesize' => 'integer', 'audio_file_size' => 'integer',
'enclosure_headersize' => 'integer', 'audio_file_header_size' => 'integer',
'description_markdown' => 'string', 'description_markdown' => 'string',
'description_html' => 'string', 'description_html' => 'string',
'image_uri' => '?string', 'image_path' => '?string',
'image_mimetype' => '?string', 'image_mimetype' => '?string',
'transcript_uri' => '?string', 'transcript_file_path' => '?string',
'chapters_uri' => '?string', 'transcript_file_remote_url' => '?string',
'chapters_file_path' => '?string',
'chapters_file_remote_url' => '?string',
'parental_advisory' => '?string', 'parental_advisory' => '?string',
'number' => '?integer', 'number' => '?integer',
'season_number' => '?integer', 'season_number' => '?integer',
@ -176,13 +170,13 @@ class Episode extends Entity
// check whether the user has inputted an image and store // check whether the user has inputted an image and store
$this->attributes['image_mimetype'] = $image->getMimeType(); $this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_uri'] = save_media( $this->attributes['image_path'] = save_media(
$image, $image,
'podcasts/' . $this->getPodcast()->name, 'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'], $this->attributes['slug'],
); );
$this->image = new \App\Libraries\Image( $this->image = new \App\Libraries\Image(
$this->attributes['image_uri'], $this->attributes['image_path'],
$this->attributes['image_mimetype'], $this->attributes['image_mimetype'],
); );
$this->image->saveSizes(); $this->image->saveSizes();
@ -193,9 +187,9 @@ class Episode extends Entity
public function getImage(): \App\Libraries\Image public function getImage(): \App\Libraries\Image
{ {
if ($image_uri = $this->attributes['image_uri']) { if ($imagePath = $this->attributes['image_path']) {
return new \App\Libraries\Image( return new \App\Libraries\Image(
$image_uri, $imagePath,
$this->attributes['image_mimetype'], $this->attributes['image_mimetype'],
); );
} }
@ -203,58 +197,59 @@ class Episode extends Entity
} }
/** /**
* Saves an enclosure * Saves an audio file
* *
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $enclosure * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $audioFile
* *
*/ */
public function setEnclosure($enclosure = null) public function setAudioFile($audioFile = null)
{ {
if ( if (
!empty($enclosure) && !empty($audioFile) &&
(!($enclosure instanceof \CodeIgniter\HTTP\Files\UploadedFile) || (!($audioFile instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
$enclosure->isValid()) $audioFile->isValid())
) { ) {
helper(['media', 'id3']); helper(['media', 'id3']);
$enclosure_metadata = get_file_tags($enclosure); $audio_metadata = get_file_tags($audioFile);
$this->attributes['enclosure_uri'] = save_media( $this->attributes['audio_file_path'] = save_media(
$enclosure, $audioFile,
'podcasts/' . $this->getPodcast()->name, 'podcasts/' . $this->getPodcast()->name,
$this->attributes['slug'], $this->attributes['slug'],
); );
$this->attributes['enclosure_duration'] = round( $this->attributes['audio_file_duration'] = round(
$enclosure_metadata['playtime_seconds'], $audio_metadata['playtime_seconds'],
); );
$this->attributes['enclosure_mimetype'] = $this->attributes['audio_file_mimetype'] =
$enclosure_metadata['mime_type']; $audio_metadata['mime_type'];
$this->attributes['enclosure_filesize'] = $this->attributes['audio_file_size'] = $audio_metadata['filesize'];
$enclosure_metadata['filesize']; $this->attributes['audio_file_header_size'] =
$this->attributes['enclosure_headersize'] = $audio_metadata['avdataoffset'];
$enclosure_metadata['avdataoffset'];
return $this; return $this;
} }
} }
/** /**
* Saves an episode transcript * Saves an episode transcript file
* *
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $transcript * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $transcriptFile
* *
*/ */
public function setTranscript($transcript) public function setTranscriptFile($transcriptFile)
{ {
if ( if (
!empty($transcript) && !empty($transcriptFile) &&
(!($transcript instanceof \CodeIgniter\HTTP\Files\UploadedFile) || (!(
$transcript->isValid()) $transcriptFile instanceof \CodeIgniter\HTTP\Files\UploadedFile
) ||
$transcriptFile->isValid())
) { ) {
helper('media'); helper('media');
$this->attributes['transcript_uri'] = save_media( $this->attributes['transcript_file_path'] = save_media(
$transcript, $transcriptFile,
$this->getPodcast()->name, $this->getPodcast()->name,
$this->attributes['slug'] . '-transcript', $this->attributes['slug'] . '-transcript',
); );
@ -264,22 +259,22 @@ class Episode extends Entity
} }
/** /**
* Saves an episode chapters * Saves an episode chapters file
* *
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $chapters * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $chaptersFile
* *
*/ */
public function setChapters($chapters) public function setChaptersFile($chaptersFile)
{ {
if ( if (
!empty($chapters) && !empty($chaptersFile) &&
(!($chapters instanceof \CodeIgniter\HTTP\Files\UploadedFile) || (!($chaptersFile instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
$chapters->isValid()) $chaptersFile->isValid())
) { ) {
helper('media'); helper('media');
$this->attributes['chapters_uri'] = save_media( $this->attributes['chapters_file_path'] = save_media(
$chapters, $chaptersFile,
$this->getPodcast()->name, $this->getPodcast()->name,
$this->attributes['slug'] . '-chapters', $this->attributes['slug'] . '-chapters',
); );
@ -288,87 +283,102 @@ class Episode extends Entity
return $this; return $this;
} }
public function getEnclosure() public function getAudioFile()
{
return new \CodeIgniter\Files\File($this->getEnclosureMediaPath());
}
public function getTranscript()
{
return $this->attributes['transcript_uri']
? new \CodeIgniter\Files\File($this->getTranscriptMediaPath())
: null;
}
public function getChapters()
{
return $this->attributes['chapters_uri']
? new \CodeIgniter\Files\File($this->getChaptersMediaPath())
: null;
}
public function getEnclosureMediaPath()
{ {
helper('media'); helper('media');
return media_path($this->attributes['enclosure_uri']); return new \CodeIgniter\Files\File(media_path($this->audio_file_path));
} }
public function getTranscriptMediaPath() public function getTranscriptFile()
{
if ($this->attributes['transcript_file_path']) {
helper('media');
return new \CodeIgniter\Files\File(
media_path($this->attributes['transcript_file_path']),
);
}
return null;
}
public function getChaptersFile()
{
if ($this->attributes['chapters_file_path']) {
helper('media');
return new \CodeIgniter\Files\File(
media_path($this->attributes['chapters_file_path']),
);
}
return null;
}
public function getAudioFileUrl()
{ {
helper('media'); helper('media');
return $this->attributes['transcript_uri'] return media_url($this->audio_file_path);
? media_path($this->attributes['transcript_uri'])
: null;
} }
public function getChaptersMediaPath() public function getAudioFileAnalyticsUrl()
{
helper('media');
return $this->attributes['chapters_uri']
? media_path($this->attributes['chapters_uri'])
: null;
}
public function getEnclosureUrl()
{ {
helper('analytics'); helper('analytics');
return generate_episode_analytics_url( return generate_episode_analytics_url(
$this->podcast_id, $this->podcast_id,
$this->id, $this->id,
$this->enclosure_uri, $this->audio_file_path,
$this->enclosure_duration, $this->audio_file_duration,
$this->enclosure_filesize, $this->audio_file_size,
$this->enclosure_headersize, $this->audio_file_header_size,
$this->published_at, $this->published_at,
); );
} }
public function getEnclosureWebUrl() public function getAudioFileWebUrl()
{ {
return $this->getEnclosureUrl() . '?_from=-+Website+-'; return $this->getAudioFileAnalyticsUrl() . '?_from=-+Website+-';
} }
public function getEnclosureOpengraphUrl() public function getAudioFileOpengraphUrl()
{ {
return $this->getEnclosureUrl() . '?_from=-+Open+Graph+-'; return $this->getAudioFileAnalyticsUrl() . '?_from=-+Open+Graph+-';
} }
public function getTranscriptUrl() /**
* Gets transcript url from transcript file uri if it exists
* or returns the transcript_file_remote_url which can be null.
*
* @return string|null
* @throws FileNotFoundException
* @throws HTTPException
*/
public function getTranscriptFileUrl()
{ {
return $this->attributes['transcript_uri'] if ($this->attributes['transcript_file_path']) {
? base_url($this->getTranscriptMediaPath()) return media_url($this->attributes['transcript_file_path']);
: null; } else {
return $this->attributes['transcript_file_remote_url'];
}
} }
public function getChaptersUrl() /**
* Gets chapters file url from chapters file uri if it exists
* or returns the chapters_file_remote_url which can be null.
*
* @return mixed
* @throws HTTPException
*/
public function getChaptersFileUrl()
{ {
return $this->attributes['chapters_uri'] if ($this->attributes['chapters_file_path']) {
? base_url($this->getChaptersMediaPath()) return media_url($this->attributes['chapters_file_path']);
: null; } else {
return $this->attributes['chapters_file_remote_url'];
}
} }
/** /**

View File

@ -22,7 +22,7 @@ class Person extends Entity
'full_name' => 'string', 'full_name' => 'string',
'unique_name' => 'string', 'unique_name' => 'string',
'information_url' => '?string', 'information_url' => '?string',
'image_uri' => 'string', 'image_path' => 'string',
'image_mimetype' => 'string', 'image_mimetype' => 'string',
'created_by' => 'integer', 'created_by' => 'integer',
'updated_by' => 'integer', 'updated_by' => 'integer',
@ -40,13 +40,13 @@ class Person extends Entity
helper('media'); helper('media');
$this->attributes['image_mimetype'] = $image->getMimeType(); $this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_uri'] = save_media( $this->attributes['image_path'] = save_media(
$image, $image,
'persons', 'persons',
$this->attributes['unique_name'], $this->attributes['unique_name'],
); );
$this->image = new \App\Libraries\Image( $this->image = new \App\Libraries\Image(
$this->attributes['image_uri'], $this->attributes['image_path'],
$this->attributes['image_mimetype'], $this->attributes['image_mimetype'],
); );
$this->image->saveSizes(); $this->image->saveSizes();
@ -58,7 +58,7 @@ class Person extends Entity
public function getImage() public function getImage()
{ {
return new \App\Libraries\Image( return new \App\Libraries\Image(
$this->attributes['image_uri'], $this->attributes['image_path'],
$this->attributes['image_mimetype'], $this->attributes['image_mimetype'],
); );
} }

View File

@ -100,7 +100,7 @@ class Podcast extends Entity
'title' => 'string', 'title' => 'string',
'description_markdown' => 'string', 'description_markdown' => 'string',
'description_html' => 'string', 'description_html' => 'string',
'image_uri' => 'string', 'image_path' => 'string',
'image_mimetype' => 'string', 'image_mimetype' => 'string',
'language_code' => 'string', 'language_code' => 'string',
'category_id' => 'integer', 'category_id' => 'integer',
@ -161,14 +161,14 @@ class Podcast extends Entity
helper('media'); helper('media');
$this->attributes['image_mimetype'] = $image->getMimeType(); $this->attributes['image_mimetype'] = $image->getMimeType();
$this->attributes['image_uri'] = save_media( $this->attributes['image_path'] = save_media(
$image, $image,
'podcasts/' . $this->attributes['name'], 'podcasts/' . $this->attributes['name'],
'cover', 'cover',
); );
$this->image = new \App\Libraries\Image( $this->image = new \App\Libraries\Image(
$this->attributes['image_uri'], $this->attributes['image_path'],
$this->attributes['image_mimetype'], $this->attributes['image_mimetype'],
); );
$this->image->saveSizes(); $this->image->saveSizes();
@ -180,7 +180,7 @@ class Podcast extends Entity
public function getImage() public function getImage()
{ {
return new \App\Libraries\Image( return new \App\Libraries\Image(
$this->attributes['image_uri'], $this->attributes['image_path'],
$this->attributes['image_mimetype'], $this->attributes['image_mimetype'],
); );
} }

View File

@ -36,13 +36,15 @@ function get_file_tags($file)
* *
* @return UploadedFile * @return UploadedFile
*/ */
function write_enclosure_tags($episode) function write_audio_file_tags($episode)
{ {
helper('media');
$TextEncoding = 'UTF-8'; $TextEncoding = 'UTF-8';
// Initialize getID3 tag-writing module // Initialize getID3 tag-writing module
$tagwriter = new WriteTags(); $tagwriter = new WriteTags();
$tagwriter->filename = $episode->enclosure_media_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'];

View File

@ -12,15 +12,15 @@ use CodeIgniter\HTTP\ResponseInterface;
/** /**
* Saves a file to the corresponding podcast folder in `public/media` * Saves a file to the corresponding podcast folder in `public/media`
* *
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $file * @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $filePath
* @param string $podcast_name * @param string $folder
* @param string $file_name * @param string $fileName
* *
* @return string The episode's file path in media root * @return string The episode's file path in media root
*/ */
function save_media($file, $folder, $mediaName) function save_media($filePath, $folder, $mediaName)
{ {
$file_name = $mediaName . '.' . $file->getExtension(); $fileName = $mediaName . '.' . $filePath->getExtension();
$mediaRoot = config('App')->mediaRoot . '/' . $folder; $mediaRoot = config('App')->mediaRoot . '/' . $folder;
@ -30,9 +30,9 @@ function save_media($file, $folder, $mediaName)
} }
// move to media folder and overwrite file if already existing // move to media folder and overwrite file if already existing
$file->move($mediaRoot . '/', $file_name, true); $filePath->move($mediaRoot . '/', $fileName, true);
return $folder . '/' . $file_name; return $folder . '/' . $fileName;
} }
/** /**

View File

@ -258,13 +258,13 @@ function get_rss_feed($podcast, $serviceSlug = '')
$enclosure->addAttribute( $enclosure->addAttribute(
'url', 'url',
$episode->enclosure_url . $episode->audio_file_analytics_url .
(empty($serviceSlug) (empty($serviceSlug)
? '' ? ''
: '?_from=' . urlencode($serviceSlug)), : '?_from=' . urlencode($serviceSlug)),
); );
$enclosure->addAttribute('length', $episode->enclosure_filesize); $enclosure->addAttribute('length', $episode->audio_file_size);
$enclosure->addAttribute('type', $episode->enclosure_mimetype); $enclosure->addAttribute('type', $episode->audio_file_mimetype);
$item->addChild('guid', $episode->guid); $item->addChild('guid', $episode->guid);
$item->addChild( $item->addChild(
@ -290,7 +290,7 @@ function get_rss_feed($podcast, $serviceSlug = '')
); );
$item->addChild( $item->addChild(
'duration', 'duration',
$episode->enclosure_duration, $episode->audio_file_duration,
$itunes_namespace, $itunes_namespace,
); );
$item->addChild('link', $episode->link); $item->addChild('link', $episode->link);
@ -318,17 +318,20 @@ function get_rss_feed($podcast, $serviceSlug = '')
); );
$item->addChild('episodeType', $episode->type, $itunes_namespace); $item->addChild('episodeType', $episode->type, $itunes_namespace);
if ($episode->transcript) { if ($episode->transcript_file_url) {
$transcriptElement = $item->addChild( $transcriptElement = $item->addChild(
'transcript', 'transcript',
null, null,
$podcast_namespace, $podcast_namespace,
); );
$transcriptElement->addAttribute('url', $episode->transcriptUrl); $transcriptElement->addAttribute(
'url',
$episode->transcript_file_url,
);
$transcriptElement->addAttribute( $transcriptElement->addAttribute(
'type', 'type',
Mimes::guessTypeFromExtension( Mimes::guessTypeFromExtension(
pathinfo($episode->transcript_uri, PATHINFO_EXTENSION), pathinfo($episode->transcript_file_url, PATHINFO_EXTENSION),
), ),
); );
$transcriptElement->addAttribute( $transcriptElement->addAttribute(
@ -337,13 +340,13 @@ function get_rss_feed($podcast, $serviceSlug = '')
); );
} }
if ($episode->chapters) { if ($episode->chapters_file_url) {
$chaptersElement = $item->addChild( $chaptersElement = $item->addChild(
'chapters', 'chapters',
null, null,
$podcast_namespace, $podcast_namespace,
); );
$chaptersElement->addAttribute('url', $episode->chaptersUrl); $chaptersElement->addAttribute('url', $episode->chapters_file_url);
$chaptersElement->addAttribute('type', 'application/json+chapters'); $chaptersElement->addAttribute('type', 'application/json+chapters');
} }

View File

@ -31,5 +31,7 @@ return [
], ],
'image_size_hint' => 'image_size_hint' =>
'Image must be squared with at least 1400px wide and tall.', 'Image must be squared with at least 1400px wide and tall.',
'upload_file' => 'Upload a file',
'remote_url' => 'Remote URL',
], ],
]; ];

View File

@ -45,8 +45,8 @@ return [
'form' => [ 'form' => [
'warning' => 'warning' =>
'In case of fatal error, try increasing the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server.<br />These values must be higher than the audio file you wish to upload.', 'In case of fatal error, try increasing the `memory_limit`, `upload_max_filesize` and `post_max_size` values in your php configuration file then restart your web server.<br />These values must be higher than the audio file you wish to upload.',
'enclosure' => 'Audio file', 'audio_file' => 'Audio file',
'enclosure_hint' => 'Choose an .mp3 or .m4a audio file.', 'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.',
'info_section_title' => 'Episode info', 'info_section_title' => 'Episode info',
'info_section_subtitle' => '', 'info_section_subtitle' => '',
'image' => 'Cover image', 'image' => 'Cover image',
@ -90,10 +90,14 @@ return [
'location_name_hint' => 'This can be a real or fictional location', 'location_name_hint' => 'This can be a real or fictional location',
'transcript' => 'Transcript or closed captions', 'transcript' => 'Transcript or closed captions',
'transcript_hint' => 'Allowed formats are txt, html, srt or json.', 'transcript_hint' => 'Allowed formats are txt, html, srt or json.',
'transcript_delete' => 'Delete transcript', 'transcript_file' => 'Transcript file',
'transcript_file_remote_url' => 'Remote url for transcript',
'transcript_file_delete' => 'Delete transcript file',
'chapters' => 'Chapters', 'chapters' => 'Chapters',
'chapters_hint' => 'File should be in JSON Chapters Format.', 'chapters_hint' => 'File must be in JSON Chapters format.',
'chapters_delete' => 'Delete chapters', 'chapters_file' => 'Chapters file',
'chapters_file_remote_url' => 'Remote url for chapters file',
'chapters_file_delete' => 'Delete chapters file',
'advanced_section_title' => 'Advanced Parameters', 'advanced_section_title' => 'Advanced Parameters',
'advanced_section_subtitle' => 'advanced_section_subtitle' =>
'If you need RSS tags that Castopod does not handle, set them here.', 'If you need RSS tags that Castopod does not handle, set them here.',

View File

@ -22,6 +22,6 @@ return [
'submit_edit' => 'Save', 'submit_edit' => 'Save',
], ],
'messages' => [ 'messages' => [
'createSuccess' => 'The page "{pageTitle}" was created successfully!', 'createSuccess' => 'The page “{pageTitle}” was created successfully!',
], ],
]; ];

View File

@ -31,5 +31,7 @@ return [
], ],
'image_size_hint' => 'image_size_hint' =>
'Limage doit être carrée, avec au minimum 1400px de long et de large.', 'Limage doit être carrée, avec au minimum 1400px de long et de large.',
'upload_file' => 'Téléversez un fichier',
'remote_url' => 'URL distante',
], ],
]; ];

View File

@ -45,8 +45,8 @@ return [
'form' => [ 'form' => [
'warning' => 'warning' =>
'En cas derreur fatale, essayez daugmenter les valeurs de `memory_limit`, `upload_max_filesize` et `post_max_size` dans votre fichier de configuration php puis redémarrez votre serveur web.<br />Les valeurs doivent être plus grandes que le fichier audio que vous souhaitez téléverser.', 'En cas derreur fatale, essayez daugmenter les valeurs de `memory_limit`, `upload_max_filesize` et `post_max_size` dans votre fichier de configuration php puis redémarrez votre serveur web.<br />Les valeurs doivent être plus grandes que le fichier audio que vous souhaitez téléverser.',
'enclosure' => 'Fichier audio', 'audio_file' => 'Fichier audio',
'enclosure_hint' => 'Sélectionnez un fichier audio .mp3 ou .m4a.', 'audio_file_hint' => 'Sélectionnez un fichier audio .mp3 ou .m4a.',
'info_section_title' => 'Informations épisode', 'info_section_title' => 'Informations épisode',
'info_section_subtitle' => '', 'info_section_subtitle' => '',
'image' => 'Image de couverture', 'image' => 'Image de couverture',
@ -91,10 +91,16 @@ return [
'transcript' => 'Transcription ou sous-titrage', 'transcript' => 'Transcription ou sous-titrage',
'transcript_hint' => 'transcript_hint' =>
'Les formats autorisés sont txt, html, srt ou json.', 'Les formats autorisés sont txt, html, srt ou json.',
'transcript_delete' => 'Supprimer la transcription', 'transcript_file' => 'Fichier de transcription',
'transcript_file_remote_url' =>
'URL distante pour le fichier de transcription',
'transcript_file_delete' => 'Supprimer le fichier de transcription',
'chapters' => 'Chapitrage', 'chapters' => 'Chapitrage',
'chapters_hint' => 'Le fichier doit être en "JSON Chapters Format".', 'chapters_hint' => 'Le fichier doit être en format “JSON Chapters”.',
'chapters_delete' => 'Supprimer le chapitrage', 'chapters_file' => 'Fichier de chapitrage',
'chapters_file_remote_url' =>
'URL distante pour le fichier de chapitrage',
'chapters_file_delete' => 'Supprimer le fichier de chapitrage',
'advanced_section_title' => 'Paramètres avancés', 'advanced_section_title' => 'Paramètres avancés',
'advanced_section_subtitle' => 'advanced_section_subtitle' =>
'Si vous avez besoin dune balise que Castopod ne couvre pas, définissez-la ici.', 'Si vous avez besoin dune balise que Castopod ne couvre pas, définissez-la ici.',

View File

@ -26,13 +26,13 @@ class Analytics extends BaseConfig
]; ];
/** /**
* get the full enclosure url * get the full audio file url
* *
* @param string $filename * @param string $filename
* @return string * @return string
*/ */
public function getEnclosureUrl(string $enclosureUri) public function getAudioFileUrl(string $audioFilePath)
{ {
return base_url($enclosureUri); return base_url($audioFilePath);
} }
} }

View File

@ -50,7 +50,7 @@ class EpisodeAnalyticsController extends Controller
} }
// Add one hit to this episode: // Add one hit to this episode:
public function hit($base64EpisodeData, ...$enclosureUri) public function hit($base64EpisodeData, ...$audioFilePath)
{ {
$session = \Config\Services::session(); $session = \Config\Services::session();
$session->start(); $session->start();
@ -78,6 +78,6 @@ class EpisodeAnalyticsController extends Controller
$serviceName, $serviceName,
); );
return redirect()->to($this->config->getEnclosureUrl($enclosureUri)); return redirect()->to($this->config->getAudioFileUrl($audioFilePath));
} }
} }

View File

@ -30,15 +30,15 @@ if (!function_exists('base64_url_decode')) {
if (!function_exists('generate_episode_analytics_url')) { if (!function_exists('generate_episode_analytics_url')) {
/** /**
* Builds the episode analytics url that redirects to the enclosure url * Builds the episode analytics url that redirects to the audio file url
* after analytics hit. * after analytics hit.
* *
* @param int $podcastId * @param int $podcastId
* @param int $episodeId * @param int $episodeId
* @param string $enclosureUri * @param string $audioFilePath
* @param int $enclosureDuration * @param int $audioFileDuration
* @param int $enclosureFilesize * @param int $audioFileSize
* @param int $enclosureHeadersize * @param int $audioFileHeaderSize
* @param \CodeIgniter\I18n\Time $publicationDate * @param \CodeIgniter\I18n\Time $publicationDate
* *
* @return string * @return string
@ -47,10 +47,10 @@ if (!function_exists('generate_episode_analytics_url')) {
function generate_episode_analytics_url( function generate_episode_analytics_url(
$podcastId, $podcastId,
$episodeId, $episodeId,
$enclosureUri, $audioFilePath,
$enclosureDuration, $audioFileDuration,
$enclosureFilesize, $audioFileFilesize,
$enclosureHeadersize, $audioFileHeaderSize,
$publicationDate $publicationDate
) { ) {
return url_to( return url_to(
@ -61,22 +61,22 @@ if (!function_exists('generate_episode_analytics_url')) {
$podcastId, $podcastId,
$episodeId, $episodeId,
// bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics // bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics
// - if file is shorter than 60sec, then it's enclosure_filesize // - if file is shorter than 60sec, then it's audio_file_size
// - if file is longer than 60 seconds then it's enclosure_headersize + 60 seconds // - if file is longer than 60 seconds then it's audio_file_header_size + 60 seconds
$enclosureDuration <= 60 $audioFileDuration <= 60
? $enclosureFilesize ? $audioFileFilesize
: $enclosureHeadersize + : $audioFileHeaderSize +
floor( floor(
(($enclosureFilesize - $enclosureHeadersize) / (($audioFileFilesize - $audioFileHeaderSize) /
$enclosureDuration) * $audioFileDuration) *
60, 60,
), ),
$enclosureFilesize, $audioFileFilesize,
$enclosureDuration, $audioFileDuration,
strtotime($publicationDate), strtotime($publicationDate),
), ),
), ),
$enclosureUri, $audioFilePath,
); );
} }
} }

View File

@ -70,17 +70,17 @@ class Image
*/ */
public $id3_path; public $id3_path;
public function __construct($originalUri, $mimetype) public function __construct($originalPath, $mimetype)
{ {
helper('media'); helper('media');
$originalPath = media_path($originalUri); $originalMediaPath = media_path($originalPath);
[ [
'filename' => $filename, 'filename' => $filename,
'dirname' => $dirname, 'dirname' => $dirname,
'extension' => $extension, 'extension' => $extension,
] = pathinfo($originalPath); ] = pathinfo($originalMediaPath);
// load images extensions from config // load images extensions from config
$this->config = config('Images'); $this->config = config('Images');
@ -100,8 +100,8 @@ class Image
$feed = $dirname . '/' . $filename . $feedExtension . '.' . $extension; $feed = $dirname . '/' . $filename . $feedExtension . '.' . $extension;
$id3 = $dirname . '/' . $filename . $id3Extension . '.' . $extension; $id3 = $dirname . '/' . $filename . $id3Extension . '.' . $extension;
$this->original_path = $originalPath; $this->original_path = $originalMediaPath;
$this->original_url = media_url($originalUri); $this->original_url = media_url($originalMediaPath);
$this->thumbnail_path = $thumbnail; $this->thumbnail_path = $thumbnail;
$this->thumbnail_url = base_url($thumbnail); $this->thumbnail_url = base_url($thumbnail);
$this->medium_path = $medium; $this->medium_path = $medium;

View File

@ -21,17 +21,19 @@ class EpisodeModel extends Model
'guid', 'guid',
'title', 'title',
'slug', 'slug',
'enclosure_uri', 'audio_file_path',
'enclosure_duration', 'audio_file_duration',
'enclosure_mimetype', 'audio_file_mimetype',
'enclosure_filesize', 'audio_file_size',
'enclosure_headersize', 'audio_file_header_size',
'description_markdown', 'description_markdown',
'description_html', 'description_html',
'image_uri', 'image_path',
'image_mimetype', 'image_mimetype',
'transcript_uri', 'transcript_file_path',
'chapters_uri', 'transcript_file_remote_url',
'chapters_file_path',
'chapters_file_remote_url',
'parental_advisory', 'parental_advisory',
'number', 'number',
'season_number', 'season_number',
@ -58,11 +60,13 @@ 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}$/]',
'enclosure_uri' => 'required', 'audio_file_path' => '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',
'type' => 'required', 'type' => 'required',
'transcript_file_remote_url' => 'valid_url|permit_empty',
'chapters_file_remote_url' => 'valid_url|permit_empty',
'published_at' => 'valid_date|permit_empty', 'published_at' => 'valid_date|permit_empty',
'created_by' => 'required', 'created_by' => 'required',
'updated_by' => 'required', 'updated_by' => 'required',
@ -268,7 +272,7 @@ class EpisodeModel extends Model
is_array($data['id']) ? $data['id'][0] : $data['id'], is_array($data['id']) ? $data['id'][0] : $data['id'],
); );
write_enclosure_tags($episode); write_audio_file_tags($episode);
return $data; return $data;
} }

View File

@ -20,7 +20,7 @@ class PersonModel extends Model
'full_name', 'full_name',
'unique_name', 'unique_name',
'information_url', 'information_url',
'image_uri', 'image_path',
'image_mimetype', 'image_mimetype',
'created_by', 'created_by',
'updated_by', 'updated_by',
@ -35,7 +35,7 @@ class PersonModel extends Model
'full_name' => 'required', 'full_name' => 'required',
'unique_name' => 'unique_name' =>
'required|regex_match[/^[a-z0-9\-]{1,191}$/]|is_unique[persons.unique_name,id,{id}]', 'required|regex_match[/^[a-z0-9\-]{1,191}$/]|is_unique[persons.unique_name,id,{id}]',
'image_uri' => 'required', 'image_path' => 'required',
'created_by' => 'required', 'created_by' => 'required',
'updated_by' => 'required', 'updated_by' => 'required',
]; ];

View File

@ -25,7 +25,7 @@ class PodcastModel extends Model
'description_html', 'description_html',
'episode_description_footer_markdown', 'episode_description_footer_markdown',
'episode_description_footer_html', 'episode_description_footer_html',
'image_uri', 'image_path',
'image_mimetype', 'image_mimetype',
'language_code', 'language_code',
'category_id', 'category_id',
@ -62,7 +62,7 @@ class PodcastModel extends Model
'name' => 'name' =>
'required|regex_match[/^[a-zA-Z0-9\_]{1,191}$/]|is_unique[podcasts.name,id,{id}]', 'required|regex_match[/^[a-zA-Z0-9\_]{1,191}$/]|is_unique[podcasts.name,id,{id}]',
'description_markdown' => 'required', 'description_markdown' => 'required',
'image_uri' => 'required', 'image_path' => 'required',
'language_code' => 'required', 'language_code' => 'required',
'category_id' => 'required', 'category_id' => 'required',
'owner_email' => 'required|valid_email', 'owner_email' => 'required|valid_email',

View File

@ -0,0 +1,36 @@
@layer components {
.form-input-tabs > input[type="radio"] {
@apply absolute -left-full;
}
.form-input-tabs .tab-panel {
@apply hidden;
}
/* Logic for 2 tabs at most */
.form-input-tabs
> input:first-child:checked
~ .tab-panels
> .tab-panel:first-child,
.form-input-tabs
> input:nth-child(3):checked
~ .tab-panels
> .tab-panel:nth-child(2) {
@apply block;
}
/* Styling */
.form-input-tabs > label {
@apply relative inline-block px-1 py-2 text-xs text-center cursor-pointer opacity-70 hover:opacity-100;
}
.form-input-tabs > input:checked + label::after {
@apply absolute inset-x-0 bottom-0 w-full mx-auto bg-pine-700;
content: "";
height: 0.2rem;
}
.form-input-tabs > input:checked + label {
@apply font-semibold opacity-100 text-pine-700;
}
}

View File

@ -10,3 +10,4 @@
@import "./note.css"; @import "./note.css";
@import "./tabs.css"; @import "./tabs.css";
@import "./radioToggler.css"; @import "./radioToggler.css";
@import "./formInputTabs.css";

View File

@ -7,7 +7,7 @@
@apply absolute -left-full; @apply absolute -left-full;
} }
.tab-panel { .tabset .tab-panel {
@apply hidden; @apply hidden;
} }
@ -31,7 +31,7 @@
@apply font-semibold opacity-100 text-pine-700; @apply font-semibold opacity-100 text-pine-700;
} }
.tab-panels { .tabset .tab-panels {
@apply col-span-2 p-6; @apply col-span-2 p-6;
} }
} }

View File

@ -29,14 +29,14 @@
) ?> ) ?>
<?= form_label( <?= form_label(
lang('Episode.form.enclosure'), lang('Episode.form.audio_file'),
'enclosure', 'audio_file',
[], [],
lang('Episode.form.enclosure_hint'), lang('Episode.form.audio_file_hint'),
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'enclosure', 'id' => 'audio_file',
'name' => 'enclosure', 'name' => 'audio_file',
'class' => 'form-input mb-4', 'class' => 'form-input mb-4',
'required' => 'required', 'required' => 'required',
'type' => 'file', 'type' => 'file',
@ -263,34 +263,133 @@
lang('Episode.form.additional_files_section_title'), lang('Episode.form.additional_files_section_title'),
lang('Episode.form.additional_files_section_subtitle'), lang('Episode.form.additional_files_section_subtitle'),
) ?> ) ?>
<?= form_label(
lang('Episode.form.transcript'), <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
'transcript', <legend><?= lang('Episode.form.transcript') .
[], '<small class="ml-1 lowercase">(' .
lang('Episode.form.transcript_hint'), lang('Common.optional') .
true, ')</small>' .
) ?> hint_tooltip(lang('Episode.form.transcript_hint'), 'ml-1') ?></legend>
<?= form_input([ <div class="mb-4 form-input-tabs">
'id' => 'transcript', <input type="radio" name="transcript-choice" id="transcript-file-upload-choice" aria-controls="transcript-file-upload-choice" value="upload-file" <?= old(
'name' => 'transcript', 'transcript-choice',
'class' => 'form-input mb-4', ) !== 'remote-url'
'type' => 'file', ? 'checked'
'accept' => '.txt,.html,.srt,.json', : '' ?> />
]) ?> <label for="transcript-file-upload-choice"><?= lang(
<?= form_label( 'Common.forms.upload_file',
lang('Episode.form.chapters'), ) ?></label>
'chapters',
[], <input type="radio" name="transcript-choice" id="transcript-file-remote-url-choice" aria-controls="transcript-file-remote-url-choice" value="remote-url" <?= old(
lang('Episode.form.chapters_hint'), 'transcript-choice',
true, ) === 'remote-url'
) ?> ? 'checked'
<?= form_input([ : '' ?> />
'id' => 'chapters', <label for="transcript-file-remote-url-choice"><?= lang(
'name' => 'chapters', 'Common.forms.remote_url',
'class' => 'form-input mb-4', ) ?></label>
'type' => 'file',
'accept' => '.json', <div class="py-2 tab-panels">
]) ?> <section id="transcript-file-upload" class="flex items-center tab-panel">
<?= form_label(
lang('Episode.form.transcript_file'),
'transcript_file',
['class' => 'sr-only'],
lang('Episode.form.transcript_file'),
true,
) ?>
<?= form_input([
'id' => 'transcript_file',
'name' => 'transcript_file',
'class' => 'form-input',
'type' => 'file',
'accept' => '.txt,.html,.srt,.json',
]) ?>
</section>
<section id="transcript-file-remote-url" class="tab-panel">
<?= form_label(
lang('Episode.form.transcript_file_remote_url'),
'transcript_file_remote_url',
['class' => 'sr-only'],
lang('Episode.form.transcript_file_remote_url'),
true,
) ?>
<?= form_input([
'id' => 'transcript_file_remote_url',
'name' => 'transcript_file_remote_url',
'class' => 'form-input w-full',
'type' => 'url',
'placeholder' => 'https://...',
'value' => old('transcript_file_remote_url'),
]) ?>
</section>
</div>
</div>
<?= form_fieldset_close() ?>
<?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
<legend><?= lang('Episode.form.chapters') .
'<small class="ml-1 lowercase">(' .
lang('Common.optional') .
')</small>' .
hint_tooltip(lang('Episode.form.chapters_hint'), 'ml-1') ?></legend>
<div class="mb-4 form-input-tabs">
<input type="radio" name="chapters-choice" id="chapters-file-upload-choice" aria-controls="chapters-file-upload-choice" value="upload-file" <?= old(
'chapters-choice',
) !== 'remote-url'
? 'checked'
: '' ?> />
<label for="chapters-file-upload-choice"><?= lang(
'Common.forms.upload_file',
) ?></label>
<input type="radio" name="chapters-choice" id="chapters-file-remote-url-choice" aria-controls="chapters-file-remote-url-choice" value="remote-url" <?= old(
'chapters-choice',
) === 'remote-url'
? 'checked'
: '' ?> />
<label for="chapters-file-remote-url-choice"><?= lang(
'Common.forms.remote_url',
) ?></label>
<div class="py-2 tab-panels">
<section id="chapters-file-upload" class="flex items-center tab-panel">
<?= form_label(
lang('Episode.form.chapters_file'),
'chapters_file',
['class' => 'sr-only'],
lang('Episode.form.chapters_file'),
true,
) ?>
<?= form_input([
'id' => 'chapters_file',
'name' => 'chapters_file',
'class' => 'form-input',
'type' => 'file',
'accept' => '.json',
]) ?>
</section>
<section id="chapters-file-remote-url" class="tab-panel">
<?= form_label(
lang('Episode.form.chapters_file_remote_url'),
'chapters_file_remote_url',
['class' => 'sr-only'],
lang('Episode.form.chapters_file_remote_url'),
true,
) ?>
<?= form_input([
'id' => 'chapters_file_remote_url',
'name' => 'chapters_file_remote_url',
'class' => 'form-input w-full',
'type' => 'url',
'placeholder' => 'https://...',
'value' => old('chapters_file_remote_url'),
]) ?>
</section>
</div>
</div>
<?= form_fieldset_close() ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_section( <?= form_section(

View File

@ -24,18 +24,26 @@
<?= form_section( <?= form_section(
lang('Episode.form.info_section_title'), lang('Episode.form.info_section_title'),
lang('Episode.form.info_section_subtitle'), '<img
src="' .
$episode->image->medium_url .
'"
alt="' .
$episode->title .
'"
class="w-48"
/>',
) ?> ) ?>
<?= form_label( <?= form_label(
lang('Episode.form.enclosure'), lang('Episode.form.audio_file'),
'enclosure', 'audio_file',
[], [],
lang('Episode.form.enclosure_hint'), lang('Episode.form.audio_file_hint'),
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'enclosure', 'id' => 'audio_file',
'name' => 'enclosure', 'name' => 'audio_file',
'class' => 'form-input mb-4', 'class' => 'form-input mb-4',
'type' => 'file', 'type' => 'file',
'accept' => '.mp3,.m4a', 'accept' => '.mp3,.m4a',
@ -48,11 +56,7 @@
lang('Episode.form.image_hint'), lang('Episode.form.image_hint'),
true, true,
) ?> ) ?>
<img
src="<?= $episode->image->thumbnail_url ?>"
alt="<?= $episode->title ?>"
class="object-cover w-32 h-32"
/>
<?= form_input([ <?= form_input([
'id' => 'image', 'id' => 'image',
'name' => 'image', 'name' => 'image',
@ -272,86 +276,192 @@
'“<a href="https://github.com/Podcastindex-org/podcast-namespace" target="_blank" rel="noreferrer noopener" style="text-decoration: underline;">podcast namespace</a>”', '“<a href="https://github.com/Podcastindex-org/podcast-namespace" target="_blank" rel="noreferrer noopener" style="text-decoration: underline;">podcast namespace</a>”',
]), ]),
) ?> ) ?>
<div class="flex flex-col flex-1">
<?= form_label( <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
lang('Episode.form.transcript'), <legend><?= lang('Episode.form.transcript') .
'transcript', '<small class="ml-1 lowercase">(' .
[], lang('Common.optional') .
lang('Episode.form.transcript_hint'), ')</small>' .
true, hint_tooltip(lang('Episode.form.transcript_hint'), 'ml-1') ?></legend>
) ?> <div class="mb-4 form-input-tabs">
<?php if ($episode->transcript): ?> <input type="radio" name="transcript-choice" id="transcript-file-upload-choice" aria-controls="transcript-file-upload-choice" value="upload-file" <?= !$episode->transcript_file_remote_url
<div class="flex justify-between"> ? 'checked'
<?= anchor( : '' ?> />
$episode->transcriptUrl, <label for="transcript-file-upload-choice"><?= lang(
icon('file', 'mr-2') . $episode->transcript, 'Common.forms.upload_file',
[ ) ?></label>
'class' => 'inline-flex items-center text-xs',
'target' => '_blank', <input type="radio" name="transcript-choice" id="transcript-file-remote-url-choice" aria-controls="transcript-file-remote-url-choice" value="remote-url" <?= $episode->transcript_file_remote_url
'rel' => 'noreferrer noopener', ? 'checked'
], : '' ?> />
) . <label for="transcript-file-remote-url-choice"><?= lang(
anchor( 'Common.forms.remote_url',
route_to('transcript-delete', $podcast->id, $episode->id), ) ?></label>
icon('delete-bin', 'mx-auto'),
[ <div class="py-2 tab-panels">
'class' => <section id="transcript-file-upload" class="flex items-center tab-panel">
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900', <?php if ($episode->transcript_file): ?>
'data-toggle' => 'tooltip', <div class="flex justify-between">
'data-placement' => 'bottom', <?= anchor(
'title' => lang('Episode.form.transcript_delete'), $episode->transcript_file_url,
], icon('file', 'mr-2 text-gray-500') .
$episode->transcript_file,
[
'class' => 'inline-flex items-center text-xs',
'target' => '_blank',
'rel' => 'noreferrer noopener',
],
) .
anchor(
route_to(
'transcript-delete',
$podcast->id,
$episode->id,
),
icon('delete-bin', 'mx-auto'),
[
'class' =>
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
'data-toggle' => 'tooltip',
'data-placement' => 'bottom',
'title' => lang(
'Episode.form.transcript_file_delete',
),
],
) ?>
</div>
<?php endif; ?>
<?= form_label(
lang('Episode.form.transcript_file'),
'transcript_file',
['class' => 'sr-only'],
lang('Episode.form.transcript_file'),
true,
) ?> ) ?>
</div> <?= form_input([
<?php endif; ?> 'id' => 'transcript_file',
<?= form_input([ 'name' => 'transcript_file',
'id' => 'transcript', 'class' => 'form-input',
'name' => 'transcript', 'type' => 'file',
'class' => 'form-input mb-4', 'accept' => '.txt,.html,.srt,.json',
'type' => 'file', ]) ?>
'accept' => '.txt,.html,.srt,.json', </section>
]) ?> <section id="transcript-file-remote-url" class="tab-panel">
</div> <?= form_label(
<div class="flex flex-col flex-1"> lang('Episode.form.transcript_file_remote_url'),
<?= form_label( 'transcript_file_remote_url',
lang('Episode.form.chapters'), ['class' => 'sr-only'],
'chapters', lang('Episode.form.transcript_file_remote_url'),
[], true,
lang('Episode.form.chapters_hint'),
true,
) ?>
<?php if ($episode->chapters): ?>
<div class="flex justify-between">
<?= anchor(
$episode->chaptersUrl,
icon('file', 'mr-2') . $episode->chapters,
[
'class' => 'inline-flex items-center text-xs',
'target' => '_blank',
'rel' => 'noreferrer noopener',
],
) .
anchor(
route_to('chapters-delete', $podcast->id, $episode->id),
icon('delete-bin', 'mx-auto'),
[
'class' =>
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
'data-toggle' => 'tooltip',
'data-placement' => 'bottom',
'title' => lang('Episode.form.chapters_delete'),
],
) ?> ) ?>
<?= form_input([
'id' => 'transcript_file_remote_url',
'name' => 'transcript_file_remote_url',
'class' => 'form-input w-full',
'type' => 'url',
'placeholder' => 'https://...',
'value' => old(
'transcript_file_remote_url',
$episode->transcript_file_remote_url,
),
]) ?>
</section>
</div>
</div> </div>
<?php endif; ?> <?= form_fieldset_close() ?>
<?= form_input([
'id' => 'chapters', <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
'name' => 'chapters', <legend><?= lang('Episode.form.chapters') .
'class' => 'form-input mb-4', '<small class="ml-1 lowercase">(' .
'type' => 'file', lang('Common.optional') .
'accept' => '.json', ')</small>' .
]) ?> hint_tooltip(lang('Episode.form.chapters_hint'), 'ml-1') ?></legend>
</div> <div class="mb-4 form-input-tabs">
<input type="radio" name="chapters-choice" id="chapters-file-upload-choice" aria-controls="chapters-file-upload-choice" value="upload-file" <?= !$episode->chapters_file_remote_url
? 'checked'
: '' ?> />
<label for="chapters-file-upload-choice"><?= lang(
'Common.forms.upload_file',
) ?></label>
<input type="radio" name="chapters-choice" id="chapters-file-remote-url-choice" aria-controls="chapters-file-remote-url-choice" value="remote-url" <?= $episode->chapters_file_remote_url
? 'checked'
: '' ?> />
<label for="chapters-file-remote-url-choice"><?= lang(
'Common.forms.remote_url',
) ?></label>
<div class="py-2 tab-panels">
<section id="chapters-file-upload" class="flex items-center tab-panel">
<?php if ($episode->chapters_file): ?>
<div class="flex justify-between">
<?= anchor(
$episode->chapters_file_url,
icon('file', 'mr-2') . $episode->chapters_file,
[
'class' => 'inline-flex items-center text-xs',
'target' => '_blank',
'rel' => 'noreferrer noopener',
],
) .
anchor(
route_to(
'chapters-delete',
$podcast->id,
$episode->id,
),
icon('delete-bin', 'mx-auto'),
[
'class' =>
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
'data-toggle' => 'tooltip',
'data-placement' => 'bottom',
'title' => lang(
'Episode.form.chapters_file_delete',
),
],
) ?>
</div>
<?php endif; ?>
<?= form_label(
lang('Episode.form.chapters_file'),
'chapters_file',
['class' => 'sr-only'],
lang('Episode.form.chapters_file'),
true,
) ?>
<?= form_input([
'id' => 'chapters_file',
'name' => 'chapters_file',
'class' => 'form-input',
'type' => 'file',
'accept' => '.json',
]) ?>
</section>
<section id="chapters-file-remote-url" class="tab-panel">
<?= form_label(
lang('Episode.form.chapters_file_remote_url'),
'chapters_file_remote_url',
['class' => 'sr-only'],
lang('Episode.form.chapters_file_remote_url'),
true,
) ?>
<?= form_input([
'id' => 'chapters_file_remote_url',
'name' => 'chapters_file_remote_url',
'class' => 'form-input w-full',
'type' => 'url',
'placeholder' => 'https://...',
'value' => old(
'chapters_file_remote_url',
$episode->chapters_file_remote_url,
),
]) ?>
</section>
</div>
</div>
<?= form_fieldset_close() ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_section( <?= form_section(

View File

@ -107,12 +107,14 @@
$episode->publication_status, $episode->publication_status,
) ?> ) ?>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration($episode->enclosure_duration) ?> <?= format_duration(
$episode->audio_file_duration,
) ?>
</time> </time>
</div> </div>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source src="/<?= $episode->enclosure_media_path ?>" type="<?= $episode->enclosure_type ?>"> <source src="<?= $episode->audio_file_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</div> </div>

View File

@ -62,15 +62,15 @@
) ?> ) ?>
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration($episode->enclosure_duration) ?> <?= format_duration($episode->audio_file_duration) ?>
</time> </time>
</div> </div>
</a> </a>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source <source
src="<?= $episode->enclosure_web_url ?>" src="<?= $episode->audio_file_url ?>"
type="<?= $episode->enclosure_mimetype ?>"> type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</div> </div>

View File

@ -78,13 +78,13 @@
]) ?> ]) ?>
</time> </time>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration($episode->enclosure_duration) ?> <?= format_duration($episode->audio_file_duration) ?>
</time> </time>
</div> </div>
</a> </a>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_mimetype ?>"> <source src="<?= $episode->audio_file_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</div> </div>

View File

@ -13,13 +13,13 @@
<?= form_open_multipart( <?= form_open_multipart(
route_to('episode-soundbites-edit', $podcast->id, $episode->id), route_to('episode-soundbites-edit', $podcast->id, $episode->id),
['method' => 'post', 'class' => 'flex flex-col'] ['method' => 'post', 'class' => 'flex flex-col'],
) ?> ) ?>
<?= csrf_field() ?> <?= csrf_field() ?>
<?= form_section( <?= form_section(
lang('Episode.soundbites_form.info_section_title'), lang('Episode.soundbites_form.info_section_title'),
lang('Episode.soundbites_form.info_section_subtitle') lang('Episode.soundbites_form.info_section_subtitle'),
) ?> ) ?>
<table class="w-full table-fixed"> <table class="w-full table-fixed">
@ -30,20 +30,20 @@
lang('Episode.soundbites_form.start_time'), lang('Episode.soundbites_form.start_time'),
'start_time', 'start_time',
[], [],
lang('Episode.soundbites_form.start_time_hint') lang('Episode.soundbites_form.start_time_hint'),
) ?></th> ) ?></th>
<th class="w-3/12 px-1 py-2"><?= form_label( <th class="w-3/12 px-1 py-2"><?= form_label(
lang('Episode.soundbites_form.duration'), lang('Episode.soundbites_form.duration'),
'duration', 'duration',
[], [],
lang('Episode.soundbites_form.duration_hint') lang('Episode.soundbites_form.duration_hint'),
) ?></th> ) ?></th>
<th class="w-7/12 px-1 py-2"><?= form_label( <th class="w-7/12 px-1 py-2"><?= form_label(
lang('Episode.soundbites_form.label'), lang('Episode.soundbites_form.label'),
'label', 'label',
[], [],
lang('Episode.soundbites_form.label_hint'), lang('Episode.soundbites_form.label_hint'),
true true,
) ?></th> ) ?></th>
<th class="w-1/12 px-1 py-2"></th> <th class="w-1/12 px-1 py-2"></th>
</tr> </tr>
@ -62,7 +62,7 @@
'data-soundbite-id' => $soundbite->id, 'data-soundbite-id' => $soundbite->id,
'required' => 'required', 'required' => 'required',
'min' => '0', 'min' => '0',
] ],
) ?></td> ) ?></td>
<td class="px-1 py-2 font-medium bg-white border border-light-blue-500"><?= form_input( <td class="px-1 py-2 font-medium bg-white border border-light-blue-500"><?= form_input(
[ [
@ -75,7 +75,7 @@
'data-soundbite-id' => $soundbite->id, 'data-soundbite-id' => $soundbite->id,
'required' => 'required', 'required' => 'required',
'min' => '0', 'min' => '0',
] ],
) ?></td> ) ?></td>
<td class="px-1 py-2 font-medium bg-white border border-light-blue-500"><?= form_input( <td class="px-1 py-2 font-medium bg-white border border-light-blue-500"><?= form_input(
[ [
@ -83,7 +83,7 @@
'name' => "soundbites_array[{$soundbite->id}][label]", 'name' => "soundbites_array[{$soundbite->id}][label]",
'class' => 'form-input w-full border-none', 'class' => 'form-input w-full border-none',
'value' => $soundbite->label, 'value' => $soundbite->label,
] ],
) ?></td> ) ?></td>
<td class="px-4 py-2"><?= icon_button( <td class="px-4 py-2"><?= icon_button(
'play', 'play',
@ -96,7 +96,7 @@
'data-soundbite-id' => $soundbite->id, 'data-soundbite-id' => $soundbite->id,
'data-soundbite-start-time' => $soundbite->start_time, 'data-soundbite-start-time' => $soundbite->start_time,
'data-soundbite-duration' => $soundbite->duration, 'data-soundbite-duration' => $soundbite->duration,
] ],
) ?> ) ?>
<?= icon_button( <?= icon_button(
'delete-bin', 'delete-bin',
@ -105,10 +105,10 @@
'soundbite-delete', 'soundbite-delete',
$podcast->id, $podcast->id,
$episode->id, $episode->id,
$soundbite->id $soundbite->id,
), ),
['variant' => 'danger'], ['variant' => 'danger'],
[] [],
) ?> ) ?>
</td> </td>
</tr> </tr>
@ -124,7 +124,7 @@
'data-type' => 'soundbite-field', 'data-type' => 'soundbite-field',
'data-field-type' => 'start-time', 'data-field-type' => 'start-time',
'min' => '0', 'min' => '0',
] ],
) ?></td> ) ?></td>
<td class="px-1 py-4 font-medium bg-white border border-light-blue-500"><?= form_input( <td class="px-1 py-4 font-medium bg-white border border-light-blue-500"><?= form_input(
[ [
@ -136,7 +136,7 @@
'data-type' => 'soundbite-field', 'data-type' => 'soundbite-field',
'data-field-type' => 'duration', 'data-field-type' => 'duration',
'min' => '0', 'min' => '0',
] ],
) ?></td> ) ?></td>
<td class="px-1 py-4 font-medium bg-white border border-light-blue-500"><?= form_input( <td class="px-1 py-4 font-medium bg-white border border-light-blue-500"><?= form_input(
[ [
@ -144,7 +144,7 @@
'name' => 'soundbites_array[0][label]', 'name' => 'soundbites_array[0][label]',
'class' => 'form-input w-full border-none', 'class' => 'form-input w-full border-none',
'value' => old('label'), 'value' => old('label'),
] ],
) ?></td> ) ?></td>
<td class="px-4 py-2"><?= icon_button( <td class="px-4 py-2"><?= icon_button(
'play', 'play',
@ -156,7 +156,7 @@
'data-soundbite-id' => 0, 'data-soundbite-id' => 0,
'data-soundbite-start-time' => 0, 'data-soundbite-start-time' => 0,
'data-soundbite-duration' => 0, 'data-soundbite-duration' => 0,
] ],
) ?> ) ?>
@ -164,7 +164,7 @@
</tr> </tr>
<tr><td colspan="3"> <tr><td colspan="3">
<audio controls preload="auto" class="w-full"> <audio controls preload="auto" class="w-full">
<source src="/<?= $episode->enclosure_media_path ?>" type="<?= $episode->enclosure_type ?>"> <source src="<?= $episode->audio_file_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</td><td class="px-4 py-2"><?= icon_button( </td><td class="px-4 py-2"><?= icon_button(
@ -177,7 +177,7 @@
'data-start-time-field-name' => 'data-start-time-field-name' =>
'soundbites_array[0][start_time]', 'soundbites_array[0][start_time]',
'data-duration-field-name' => 'soundbites_array[0][duration]', 'data-duration-field-name' => 'soundbites_array[0][duration]',
] ],
) ?></td></tr> ) ?></td></tr>
</tbody> </tbody>
</table> </table>
@ -189,7 +189,7 @@
lang('Episode.soundbites_form.submit_edit'), lang('Episode.soundbites_form.submit_edit'),
null, null,
['variant' => 'primary'], ['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end'] ['type' => 'submit', 'class' => 'self-end'],
) ?> ) ?>
<?= form_close() ?> <?= form_close() ?>

View File

@ -39,7 +39,7 @@
class="object-cover w-full" class="object-cover w-full"
/> />
<audio controls preload="auto" class="w-full mb-6"> <audio controls preload="auto" class="w-full mb-6">
<source src="/<?= $episode->enclosure_media_path ?>" type="<?= $episode->enclosure_type ?>"> <source src="<?= $episode->audio_file_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>

View File

@ -50,11 +50,11 @@
) ?> ) ?>
</a> </a>
<audio controls preload="none" class="flex w-full mt-auto"> <audio controls preload="none" class="flex w-full mt-auto">
<source src="<?= $episode->enclosure_url . <source src="<?= $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)
: '') ?>" type="<?= $episode->enclosure_type ?>" /> : '') ?>" type="<?= $episode->audio_file_mimetype ?>" />
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</div> </div>

View File

@ -21,15 +21,15 @@
<?= lang('Common.mediumDate', [$episode->published_at]) ?> <?= lang('Common.mediumDate', [$episode->published_at]) ?>
</time> </time>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration($episode->enclosure_duration) ?> <?= format_duration($episode->audio_file_duration) ?>
</time> </time>
</div> </div>
</a> </a>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source <source
src="<?= $episode->enclosure_web_url ?>" src="<?= $episode->audio_file_web_url ?>"
type="<?= $episode->enclosure_mimetype ?>"> type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</div> </div>

View File

@ -17,8 +17,8 @@
<meta property="og:description" content="$description" /> <meta property="og:description" content="$description" />
<meta property="article:published_time" content="<?= $episode->published_at ?>" /> <meta property="article:published_time" content="<?= $episode->published_at ?>" />
<meta property="article:modified_time" content="<?= $episode->updated_at ?>" /> <meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
<meta property="og:audio" content="<?= $episode->enclosure_opengraph_url ?>" /> <meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
<meta property="og:audio:type" content="<?= $episode->enclosure_mimetype ?>" /> <meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
<link rel="alternate" type="application/json+oembed" href="<?= base_url( <link rel="alternate" type="application/json+oembed" href="<?= base_url(
route_to('episode-oembed-json', $podcast->name, $episode->slug), route_to('episode-oembed-json', $podcast->name, $episode->slug),
) ?>" title="<?= $episode->title ?> oEmbed json" /> ) ?>" title="<?= $episode->title ?> oEmbed json" />
@ -67,8 +67,8 @@
]) ?> ]) ?>
</time> </time>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration($episode->enclosure_duration) ?> <?= format_duration($episode->audio_file_duration) ?>
</time> </time>
</div> </div>
<div class="mb-2 space-x-4 text-sm"> <div class="mb-2 space-x-4 text-sm">
@ -150,7 +150,7 @@ data-placement="bottom" title="[<?= $person['full_name'] ?>] <?= $person[
</div> </div>
</div> </div>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_type ?>"> <source src="<?= $episode->audio_file_web_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</header> </header>

View File

@ -17,8 +17,8 @@
<meta property="og:description" content="$description" /> <meta property="og:description" content="$description" />
<meta property="article:published_time" content="<?= $episode->published_at ?>" /> <meta property="article:published_time" content="<?= $episode->published_at ?>" />
<meta property="article:modified_time" content="<?= $episode->updated_at ?>" /> <meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
<meta property="og:audio" content="<?= $episode->enclosure_opengraph_url ?>" /> <meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
<meta property="og:audio:type" content="<?= $episode->enclosure_mimetype ?>" /> <meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
<link rel="alternate" type="application/json+oembed" href="<?= base_url( <link rel="alternate" type="application/json+oembed" href="<?= base_url(
route_to('episode-oembed-json', $podcast->name, $episode->slug), route_to('episode-oembed-json', $podcast->name, $episode->slug),
) ?>" title="<?= $episode->title ?> oEmbed json" /> ) ?>" title="<?= $episode->title ?> oEmbed json" />
@ -67,8 +67,8 @@
]) ?> ]) ?>
</time> </time>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration($episode->enclosure_duration) ?> <?= format_duration($episode->audio_file_duration) ?>
</time> </time>
</div> </div>
<div class="mb-2 space-x-4 text-sm"> <div class="mb-2 space-x-4 text-sm">
@ -148,7 +148,7 @@
</div> </div>
</div> </div>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_type ?>"> <source src="<?= $episode->audio_file_web_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</header> </header>

View File

@ -105,14 +105,14 @@
]) ?> ]) ?>
</time> </time>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration( <?= format_duration(
$episode->enclosure_duration, $episode->audio_file_duration,
) ?> ) ?>
</time> </time>
</div> </div>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_mimetype ?>"> <source src="<?= $episode->audio_file_web_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</div> </div>
@ -168,4 +168,5 @@
<?php endif; ?> <?php endif; ?>
</section> </section>
<?= $this->endSection() ?> <?= $this->endSection()
?>

View File

@ -105,14 +105,14 @@
]) ?> ]) ?>
</time> </time>
<span class="mx-1"></span> <span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S"> <time datetime="PT<?= $episode->audio_file_duration ?>S">
<?= format_duration( <?= format_duration(
$episode->enclosure_duration, $episode->audio_file_duration,
) ?> ) ?>
</time> </time>
</div> </div>
<audio controls preload="none" class="w-full mt-auto"> <audio controls preload="none" class="w-full mt-auto">
<source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_mimetype ?>"> <source src="<?= $episode->audio_file_web_url ?>" type="<?= $episode->audio_file_mimetype ?>">
Your browser does not support the audio tag. Your browser does not support the audio tag.
</audio> </audio>
</div> </div>