mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 13:01:19 +00:00
parent
b9c8008062
commit
e769d83a93
@ -307,7 +307,7 @@ class Mimes
|
||||
],
|
||||
'svg' => ['image/svg+xml', 'application/xml', 'text/xml'],
|
||||
'vcf' => 'text/x-vcard',
|
||||
'srt' => ['text/srt', 'text/plain'],
|
||||
'srt' => ['text/srt', 'text/plain', 'application/octet-stream'],
|
||||
'vtt' => ['text/vtt', 'text/plain'],
|
||||
'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
|
||||
];
|
||||
|
@ -237,6 +237,22 @@ $routes->group(
|
||||
'as' => 'episode-delete',
|
||||
'filter' => 'permission:podcast_episodes-delete',
|
||||
]);
|
||||
$routes->get(
|
||||
'transcript-delete',
|
||||
'Episode::transcriptDelete/$1/$2',
|
||||
[
|
||||
'as' => 'transcript-delete',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
]
|
||||
);
|
||||
$routes->get(
|
||||
'chapters-delete',
|
||||
'Episode::chaptersDelete/$1/$2',
|
||||
[
|
||||
'as' => 'chapters-delete',
|
||||
'filter' => 'permission:podcast_episodes-edit',
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -96,6 +96,8 @@ class Episode extends BaseController
|
||||
'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
|
||||
'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]',
|
||||
'chapters' => 'ext_in[chapters,json]',
|
||||
'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
|
||||
];
|
||||
|
||||
@ -114,6 +116,8 @@ class Episode extends BaseController
|
||||
'enclosure' => $this->request->getFile('enclosure'),
|
||||
'description_markdown' => $this->request->getPost('description'),
|
||||
'image' => $this->request->getFile('image'),
|
||||
'transcript' => $this->request->getFile('transcript'),
|
||||
'chapters' => $this->request->getFile('chapters'),
|
||||
'parental_advisory' =>
|
||||
$this->request->getPost('parental_advisory') !== 'undefined'
|
||||
? $this->request->getPost('parental_advisory')
|
||||
@ -189,6 +193,8 @@ class Episode extends BaseController
|
||||
'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
|
||||
'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]',
|
||||
'chapters' => 'ext_in[chapters,json]',
|
||||
'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
|
||||
];
|
||||
|
||||
@ -231,6 +237,14 @@ class Episode extends BaseController
|
||||
if ($image) {
|
||||
$this->episode->image = $image;
|
||||
}
|
||||
$transcript = $this->request->getFile('transcript');
|
||||
if ($transcript->isValid()) {
|
||||
$this->episode->transcript = $transcript;
|
||||
}
|
||||
$chapters = $this->request->getFile('chapters');
|
||||
if ($chapters->isValid()) {
|
||||
$this->episode->chapters = $chapters;
|
||||
}
|
||||
|
||||
$episodeModel = new EpisodeModel();
|
||||
|
||||
@ -262,6 +276,40 @@ class Episode extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
public function transcriptDelete()
|
||||
{
|
||||
unlink($this->episode->transcript);
|
||||
$this->episode->transcript_uri = null;
|
||||
|
||||
$episodeModel = new EpisodeModel();
|
||||
|
||||
if (!$episodeModel->update($this->episode->id, $this->episode)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episodeModel->errors());
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function chaptersDelete()
|
||||
{
|
||||
unlink($this->episode->chapters);
|
||||
$this->episode->chapters_uri = null;
|
||||
|
||||
$episodeModel = new EpisodeModel();
|
||||
|
||||
if (!$episodeModel->update($this->episode->id, $this->episode)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episodeModel->errors());
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
(new EpisodeModel())->delete($this->episode->id);
|
||||
|
@ -73,6 +73,16 @@ class AddEpisodes extends Migration
|
||||
'constraint' => 255,
|
||||
'null' => true,
|
||||
],
|
||||
'transcript_uri' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => true,
|
||||
],
|
||||
'chapters_uri' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => true,
|
||||
],
|
||||
'parental_advisory' => [
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['clean', 'explicit'],
|
||||
|
@ -35,6 +35,16 @@ class Episode extends Entity
|
||||
*/
|
||||
protected $enclosure;
|
||||
|
||||
/**
|
||||
* @var \CodeIgniter\Files\File
|
||||
*/
|
||||
protected $transcript;
|
||||
|
||||
/**
|
||||
* @var \CodeIgniter\Files\File
|
||||
*/
|
||||
protected $chapters;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -55,6 +65,16 @@ class Episode extends Entity
|
||||
*/
|
||||
protected $enclosure_opengraph_url;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $transcript_url;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $chapters_url;
|
||||
|
||||
/**
|
||||
* Holds text only description, striped of any markdown or html special characters
|
||||
*
|
||||
@ -86,6 +106,8 @@ class Episode extends Entity
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'image_uri' => '?string',
|
||||
'transcript_uri' => '?string',
|
||||
'chapters_uri' => '?string',
|
||||
'parental_advisory' => '?string',
|
||||
'number' => '?integer',
|
||||
'season_number' => '?integer',
|
||||
@ -170,11 +192,75 @@ class Episode extends Entity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an episode transcript
|
||||
*
|
||||
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $transcript
|
||||
*
|
||||
*/
|
||||
public function setTranscript($transcript)
|
||||
{
|
||||
if (
|
||||
!empty($transcript) &&
|
||||
(!($transcript instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
|
||||
$transcript->isValid())
|
||||
) {
|
||||
helper('media');
|
||||
|
||||
$this->attributes['transcript_uri'] = save_podcast_media(
|
||||
$transcript,
|
||||
$this->getPodcast()->name,
|
||||
$this->attributes['slug'] . '-transcript'
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an episode chapters
|
||||
*
|
||||
* @param \CodeIgniter\HTTP\Files\UploadedFile|\CodeIgniter\Files\File $chapters
|
||||
*
|
||||
*/
|
||||
public function setChapters($chapters)
|
||||
{
|
||||
if (
|
||||
!empty($chapters) &&
|
||||
(!($chapters instanceof \CodeIgniter\HTTP\Files\UploadedFile) ||
|
||||
$chapters->isValid())
|
||||
) {
|
||||
helper('media');
|
||||
|
||||
$this->attributes['chapters_uri'] = save_podcast_media(
|
||||
$chapters,
|
||||
$this->getPodcast()->name,
|
||||
$this->attributes['slug'] . '-chapters'
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEnclosure()
|
||||
{
|
||||
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');
|
||||
@ -182,6 +268,24 @@ class Episode extends Entity
|
||||
return media_path($this->attributes['enclosure_uri']);
|
||||
}
|
||||
|
||||
public function getTranscriptMediaPath()
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return $this->attributes['transcript_uri']
|
||||
? media_path($this->attributes['transcript_uri'])
|
||||
: null;
|
||||
}
|
||||
|
||||
public function getChaptersMediaPath()
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return $this->attributes['chapters_uri']
|
||||
? media_path($this->attributes['chapters_uri'])
|
||||
: null;
|
||||
}
|
||||
|
||||
public function getEnclosureUrl()
|
||||
{
|
||||
helper('analytics');
|
||||
@ -230,6 +334,20 @@ class Episode extends Entity
|
||||
return $this->getEnclosureUrl() . '?_from=-+Open+Graph+-';
|
||||
}
|
||||
|
||||
public function getTranscriptUrl()
|
||||
{
|
||||
return $this->attributes['transcript_uri']
|
||||
? base_url($this->getTranscriptMediaPath())
|
||||
: null;
|
||||
}
|
||||
|
||||
public function getChaptersUrl()
|
||||
{
|
||||
return $this->attributes['chapters_uri']
|
||||
? base_url($this->getChaptersMediaPath())
|
||||
: null;
|
||||
}
|
||||
|
||||
public function getLink()
|
||||
{
|
||||
return base_url(
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Mimes;
|
||||
|
||||
/**
|
||||
* Generates the rss feed for a given podcast entity
|
||||
@ -217,6 +218,35 @@ function get_rss_feed($podcast, $serviceName = '')
|
||||
);
|
||||
$item->addChild('episodeType', $episode->type, $itunes_namespace);
|
||||
|
||||
if ($episode->transcript) {
|
||||
$transcriptElement = $item->addChild(
|
||||
'transcript',
|
||||
null,
|
||||
$podcast_namespace
|
||||
);
|
||||
$transcriptElement->addAttribute('url', $episode->transcriptUrl);
|
||||
$transcriptElement->addAttribute(
|
||||
'type',
|
||||
Mimes::guessTypeFromExtension(
|
||||
pathinfo($episode->transcript_uri, PATHINFO_EXTENSION)
|
||||
)
|
||||
);
|
||||
$transcriptElement->addAttribute(
|
||||
'language',
|
||||
$podcast->language_code
|
||||
);
|
||||
}
|
||||
|
||||
if ($episode->chapters) {
|
||||
$chaptersElement = $item->addChild(
|
||||
'chapters',
|
||||
null,
|
||||
$podcast_namespace
|
||||
);
|
||||
$chaptersElement->addAttribute('url', $episode->chaptersUrl);
|
||||
$chaptersElement->addAttribute('type', 'application/json+chapters');
|
||||
}
|
||||
|
||||
$episode->is_blocked &&
|
||||
$item->addChild('block', 'Yes', $itunes_namespace);
|
||||
}
|
||||
|
@ -70,6 +70,15 @@ return [
|
||||
'block' => 'Episode should be hidden from all platforms',
|
||||
'block_hint' =>
|
||||
'The episode show or hide status. If you want this episode removed from the Apple directory, toggle this on.',
|
||||
'additional_files_section_title' => 'Additional files',
|
||||
'additional_files_section_subtitle' =>
|
||||
'These files may be used by other platforms to provide better experience to your audience.<br />See the {podcastNamespaceLink} for more information.',
|
||||
'transcript' => 'Transcript or closed captions',
|
||||
'transcript_hint' => 'Allowed formats are txt, html, srt or json.',
|
||||
'transcript_delete' => 'Delete transcript',
|
||||
'chapters' => 'Chapters',
|
||||
'chapters_hint' => 'File should be in JSON Chapters Format.',
|
||||
'chapters_delete' => 'Delete chapters',
|
||||
'submit_create' => 'Create episode',
|
||||
'submit_edit' => 'Save episode',
|
||||
],
|
||||
|
@ -70,6 +70,16 @@ return [
|
||||
'block' => 'L’épisode doit être masqué de toutes les plateformes',
|
||||
'block_hint' =>
|
||||
'La visibilité de l’épisode. Si vous souhaitez retirer cet épisode de l’index Apple, activez ce champ.',
|
||||
'additional_files_section_title' => 'Fichiers additionels',
|
||||
'additional_files_section_subtitle' =>
|
||||
'Ces fichiers pourront être utilisées par d’autres plate-formes pour procurer une meilleure expérience à vos auditeurs.<br />Consulter le {podcastNamespaceLink} pour plus d’informations.',
|
||||
'transcript' => 'Transcription ou sous-titrage',
|
||||
'transcript_hint' =>
|
||||
'Les formats autorisés sont txt, html, srt ou json.',
|
||||
'transcript_delete' => 'Supprimer la transcription',
|
||||
'chapters' => 'Chapitrage',
|
||||
'chapters_hint' => 'Le fichier doit être en "JSON Chapters Format".',
|
||||
'chapters_delete' => 'Supprimer le chaptrage',
|
||||
'submit_create' => 'Créer l’épisode',
|
||||
'submit_edit' => 'Enregistrer l’épisode',
|
||||
],
|
||||
|
@ -28,6 +28,8 @@ class EpisodeModel extends Model
|
||||
'description_markdown',
|
||||
'description_html',
|
||||
'image_uri',
|
||||
'transcript_uri',
|
||||
'chapters_uri',
|
||||
'parental_advisory',
|
||||
'number',
|
||||
'season_number',
|
||||
|
1
app/Views/_assets/icons/file.svg
Normal file
1
app/Views/_assets/icons/file.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M19 22H5a3 3 0 0 1-3-3V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v12h4v4a3 3 0 0 1-3 3zm-1-5v2a1 1 0 0 0 2 0v-2h-2zm-2 3V4H4v15a1 1 0 0 0 1 1h11zM6 7h8v2H6V7zm0 4h8v2H6v-2zm0 4h5v2H6v-2z"/></svg>
|
After Width: | Height: | Size: 314 B |
@ -264,6 +264,40 @@
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= form_section(
|
||||
lang('Episode.form.additional_files_section_title'),
|
||||
lang('Episode.form.additional_files_section_subtitle')
|
||||
) ?>
|
||||
<?= form_label(
|
||||
lang('Episode.form.transcript'),
|
||||
'transcript',
|
||||
[],
|
||||
lang('Episode.form.transcript_hint'),
|
||||
true
|
||||
) ?>
|
||||
<?= form_input([
|
||||
'id' => 'transcript',
|
||||
'name' => 'transcript',
|
||||
'class' => 'form-input mb-4',
|
||||
'type' => 'file',
|
||||
'accept' => '.txt,.html,.srt,.json',
|
||||
]) ?>
|
||||
<?= form_label(
|
||||
lang('Episode.form.chapters'),
|
||||
'chapters',
|
||||
[],
|
||||
lang('Episode.form.chapters_hint'),
|
||||
true
|
||||
) ?>
|
||||
<?= form_input([
|
||||
'id' => 'chapters',
|
||||
'name' => 'chapters',
|
||||
'class' => 'form-input mb-4',
|
||||
'type' => 'file',
|
||||
'accept' => '.json',
|
||||
]) ?>
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= button(
|
||||
lang('Episode.form.submit_create'),
|
||||
null,
|
||||
|
@ -136,7 +136,6 @@
|
||||
</label>
|
||||
<?= form_radio(
|
||||
['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio-btn'],
|
||||
|
||||
'bonus',
|
||||
old('type') ? old('type') === 'bonus' : $episode->type === 'bonus'
|
||||
) ?>
|
||||
@ -273,6 +272,91 @@
|
||||
old('block', $episode->is_blocked)
|
||||
) ?>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
<?= form_section(
|
||||
lang('Episode.form.additional_files_section_title'),
|
||||
lang('Episode.form.additional_files_section_subtitle')
|
||||
) ?>
|
||||
<div class="flex flex-col flex-1">
|
||||
<?= form_label(
|
||||
lang('Episode.form.transcript'),
|
||||
'transcript',
|
||||
[],
|
||||
lang('Episode.form.transcript_hint'),
|
||||
true
|
||||
) ?>
|
||||
<?php if ($episode->transcript): ?>
|
||||
<div class="flex justify-between">
|
||||
<?= anchor(
|
||||
$episode->transcriptUrl,
|
||||
icon('file', 'mr-2') . $episode->transcript,
|
||||
[
|
||||
'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_delete'),
|
||||
]
|
||||
) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?= form_input([
|
||||
'id' => 'transcript',
|
||||
'name' => 'transcript',
|
||||
'class' => 'form-input mb-4',
|
||||
'type' => 'file',
|
||||
'accept' => '.txt,.html,.srt,.json',
|
||||
]) ?>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1">
|
||||
<?= form_label(
|
||||
lang('Episode.form.chapters'),
|
||||
'chapters',
|
||||
[],
|
||||
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'),
|
||||
]
|
||||
) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?= form_input([
|
||||
'id' => 'chapters',
|
||||
'name' => 'chapters',
|
||||
'class' => 'form-input mb-4',
|
||||
'type' => 'file',
|
||||
'accept' => '.json',
|
||||
]) ?>
|
||||
</div>
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= button(
|
||||
|
Loading…
x
Reference in New Issue
Block a user