sylvainjule / subtitler
Synchronize content with audio/video files within Kirby
Installs: 37
Dependents: 0
Suggesters: 0
Security: 0
Stars: 38
Watchers: 3
Forks: 2
Open Issues: 0
Language:SCSS
Type:kirby-plugin
Requires
This package is auto-updated.
Last update: 2024-11-07 00:32:57 UTC
README
This plugin allows you to sync any content with audio or video files.
Overview
This plugin is completely free and published under the MIT license. However, if you are using it in a commercial project and want to help me keep up with maintenance, please consider making a donation of your choice or purchasing your license(s) through my affiliate link.
TLDR – Just get me started 👀
1. Installation
If you are looking to use this field with Kirby 2, please switch to the
kirby-2
branch.
Download and copy this repository to /site/plugins/subtitler
Alternatively, you can install it with composer: composer require sylvainjule/subtitler
2. Blueprint usage
The subtitler is a section which doesn't store any information itself. Instead, it provides an interface to manipulate content from other fields. Here's a basic setup of the plugin within your blueprint:
2.1. Example
columns: - width: 2/3 sections: subtitler: type: subtitler timelines: mytimeline: id: mytimeline label: My timeline color: purple storage: src: src subs: subs - width: 1/3 sections: myfields: type: fields fields: src: type: files max: 1 subs: type: structure sortable: false fields: note: label: Note type: text
Notice the sortable: false
for the structure field, else the section will easily be lost with the incrementation.
You should also ensure that the structure field will only contain subtitles. To do so, you can hide the Add +
button of the field. This way, there will be no alternative to populate it other than the subtitler section.
This should be set in a custom panel.css
:
.k-field-subs .k-field-header button { display: none; }
2.2. Usage within a file page
You can use this plugin within a file page by setting it like stated above, but skipping the src
option within the storage
settings. The plugin will automatically detect the image of the given page.
3. Options
3.1. Timelines
This new version handles multiple timelines for a given video / audio file, instead of having to duplicate the field.
Timelines have to be specified within the timelines
option, an given 3 attributes :
- An
id
(will be used to divide the structure entries) - A
label
(will be displayed within the editor) - A
color
(will be displayed within the editor, and have to be eitherblue
,green
,red
,orange
orpurple
)
Here's an example with multiple timelines:
timelines: chapters: id: chapters label: Chapters color: purple links: id: links label: Links color: orange images: id: images label: Images color: blue
Labels also support multi-language syntax:
timelines: chapters: id: chapters label: en: Chapters fr: Chapitres color: purple
3.2. Display options
• Theme
type:
string
, default:light
You have two themes available, a dark and a light one. (doesn't work for now, but you'll have them pretty soon)
• Debug
type:
boolean
, default:false
When set to true
, timecodes and coordinates based on cursor position will be shown in real-time in the toolbar. Not needed unless you're trying to extend some functionality.
3.3. Storage options
• Audio / video file
The section needs to be synced with a field returning a file object to work with.
Using a files
field is required. Not only does it look nicer than a select field, but most importantly it returns both an absolute url and an id of the file:
# subtitler section storage: src: src # fields section src: type: files max: 1
Note: You don’t need to explicitly set a
max
value, though it may look clearer. When confronted to a files field containing multiple files, the plugin will always use the first one.
• Subtitles structure
The plugin needs an associated structure field to store the subtitles informations. It has 5 reserved fields that shouldn't be used for any other purpose: timeline
, start
, startprop
,end
and endprop
. Those will be automatically set and don't need to be explicitely specified unless you want to show them within the panel:
# subtitler section storage: subs: subs # fields section subs: type: structure fields: timeline: label: 'Timeline' type: text start: label: 'Start' type: text startprop: label: 'Start (%)' type: text end: label: 'End' type: text endprop: label: 'End (%)' type: text
Otherwise, you can directly start adding fields you'd like to sync content with:
# subtitler section storage: subs: subs # fields section subs: type: structure fields: mynote: label: 'Note' type: text
4. Template usage
There are different ways to use the subtitles, depending on your use case. You could, for example, send them as json
to a Javascript app. Or, you could make use of some simple helpers shipped with the plugin to use them directly in your templates:
4.1. How are the informations stored?
Each entry will have a start
, startprop
, end
and endprop
value, formatted like this:
start / end : 364.58745 # (number of seconds) startprop / endprop : 0.35 # (the progress relative to the file's duration, between 0 and 1) # + all your custom field
4.2. Create a subtitles collections
Subtitles are stored in a structure field.
If you have a single timeline, you can create a collection with the toStructure()
method to access them:
foreach($page->mysubtitles()->toStructure() as $subtitle) { // do something }
When dealing with multiple timelines, you either need to filter them manually or call the getTimeline
method, which is a toStructure
wrapper providing filtering by timeline. Use it this way:
foreach($page->mysubtitles()->getTimeline('timelinekey') as $subtitle) { // do something }
4.3. Generate and use a track file
• Creating the tracks
The plugin provides a simple way to generate WebVTT files, which can later be added in your html <track>
tags.
First, make sure that the field used to get the subtitles' content is called subtitle
, else the plugin won't know where to look for the text:
subs:
label: Subtitles
type: structure
fields:
subtitle:
label: Content
type: text
Then, include the tracksbuilder
field on your page, which is basically a button generating the .vtt
files on request (stored in the page's folder).
mytracks: label: Generate tracks type: tracksbuilder src: subs # (key of the structure field containing the subtitles)
The output will look like:
# if single language ${fieldname}-${timeline}.vtt # (content/my/page/subs-notes.vtt) # if multi-language ${fieldname}-${timeline}-${languageCode}.vtt # (content/my/page/subs-notes-en.vtt)
You are now able to get them easily by calling ->vtt('timeline')
on the subtitles structure field (example below).
• Single language setup
Once your .vtt
files have been created, you can include them in your video
tag.
Here is a basic example, using a timeline called notes
:
<?php // we make sure that the video file exists if($video = $page->src()->toFile()): ?> <video id="video" controls preload="metadata"> <source src="<?php echo $video->url() ?>" type="<?php echo $video->mime() ?>"> <?php // we make sure that the .vtt file exists if($track = $page->subs()->vtt('notes')): ?> <track label="English" kind="subtitles" srclang="en" src="<?php echo $track->url() ?>" default> <?php endif; ?> </video> <?php endif; ?>
• Multi-language setup
Multi-language setup is pretty much the same, except we need to put the track
tag within a loop and set the desired languages explicitely:
<?php // we make sure that the video file exists if($video = $page->src()->toFile()): ?> <video id="video" controls preload="metadata"> <source src="<?php echo $video->url() ?>" type="<?php echo $video->mime() ?>"> <?php // we loop through every language foreach($kirby->languages() as $language): ?> <?php // we make sure that the .vtt file exists if($track = $page->subs()->vtt('notes', $language->code())): ?> <track label="<?php echo $language->name() ?>" kind="subtitles" srclang="<?php echo $language->code() ?>" src="<?php echo $track->url() ?>" default> <?php endif; ?> <?php endforeach; ?> </video> <?php endif; ?>
5. License
MIT
6. Credits
- The fields synchronization has been taken from @rasteiner's map section. 🙏