Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add support for video captions
Generate HTML5 subtitle tracks dynamically based on the contents of the folder
where the video is located.

For example: given a movie 'video.mkv', the player will look for VTT subtitle
files named 'video.XX.vtt' and '.video.XX.vtt', where XX is the two-letter
code for the language.

Signed-off-by: Frederic Ruget <[email protected]>
  • Loading branch information
douzebis authored and beardhatcode committed May 25, 2021
commit 9cb64088cc1ea61cd5880bec7c1354ae2747ae8f
4 changes: 2 additions & 2 deletions js/viewer-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/viewer-main.js.map

Large diffs are not rendered by default.

234 changes: 233 additions & 1 deletion src/components/Videos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<!-- Plyr currently replaces the parent. Wrapping to prevent this
https://github.com/redxtech/vue-plyr/issues/259 -->
<div v-if="davPath">
<div v-html="fetchTracks()" />
<VuePlyr
ref="plyr"
:options="options"
Expand All @@ -40,7 +41,8 @@
preload="metadata"
@ended="donePlaying"
@canplay="doneLoading"
@loadedmetadata="onLoadedMetadata">
@loadedmetadata="onLoadedMetadata"
v-html="tracks">

<!-- Omitting `type` on purpose because most of the
browsers auto detect the appropriate codec.
Expand All @@ -58,6 +60,8 @@
import Vue from 'vue'
import VuePlyr from '@skjnldsv/vue-plyr'
import '@skjnldsv/vue-plyr/dist/vue-plyr.css'
import { extractFilePaths } from '../utils/fileUtils'
import getFileList from '../services/FileList'

const liveExt = ['jpg', 'jpeg', 'png']
const liveExtRegex = new RegExp(`\\.(${liveExt.join('|')})$`, 'i')
Expand Down Expand Up @@ -85,8 +89,10 @@ export default {
options() {
return {
autoplay: this.active === true,
captions: { active: false, language: 'auto', update: true },
controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'fullscreen'],
loadSprite: false,

}
},
},
Expand Down Expand Up @@ -121,6 +127,232 @@ export default {
onLoadedMetadata() {
this.updateVideoSize()
},

async fetchFolder(dirPath) {
// Fetching the contents of the video folder must be asynchronous
const fileList = await getFileList(dirPath)
return fileList
},

fetchTracks() {
const dirPath = extractFilePaths(this.filename)[0]
this.fetchFolder(dirPath).then(folder => {
// ISO code for languages
// See https://www.loc.gov/standards/iso639-2/php/code_list.php
const languages = {
aa: 'Afar',
ab: 'Abkhazian',
ae: 'Avestan',
af: 'Afrikaans',
ak: 'Akan',
am: 'Amharic',
an: 'Aragonese',
ar: 'Arabic',
as: 'Assamese',
av: 'Avaric',
ay: 'Aymara',
az: 'Azerbaijani',
ba: 'Bashkir',
be: 'Belarusian',
bg: 'Bulgarian',
bh: 'Bihari languages',
bi: 'Bislama',
bm: 'Bambara',
bn: 'Bengali',
bo: 'Tibetan',
br: 'Breton',
bs: 'Bosnian',
ca: 'Catalan',
ce: 'Chechen',
ch: 'Chamorro',
co: 'Corsican',
cr: 'Cree',
cs: 'Czech',
cu: 'Church Slavic',
cv: 'Chuvash',
cy: 'Welsh',
da: 'Danish',
de: 'German',
dv: 'Divehi',
dz: 'Dzongkha',
ee: 'Ewe',
el: 'Greek',
en: 'English',
eo: 'Esperanto',
es: 'Spanish',
et: 'Estonian',
eu: 'Basque',
fa: 'Persian',
ff: 'Fulah',
fi: 'Finnish',
fj: 'Fijian',
fo: 'Faroese',
fr: 'French',
fy: 'Western Frisian',
ga: 'Irish',
gd: 'Gaelic',
gl: 'Galician',
gn: 'Guarani',
gu: 'Gujarati',
gv: 'Manx',
ha: 'Hausa',
he: 'Hebrew',
hi: 'Hindi',
ho: 'Hiri Motu',
hr: 'Croatian',
ht: 'Haitian',
hu: 'Hungarian',
hy: 'Armenian',
hz: 'Herero',
ia: 'Interlingua',
id: 'Indonesian',
ie: 'Interlingue',
ig: 'Igbo',
ii: 'Sichuan Yi',
ik: 'Inupiaq',
io: 'Ido',
is: 'Icelandic',
it: 'Italian',
iu: 'Inuktitut',
ja: 'Japanese',
jv: 'Javanese',
ka: 'Georgian',
kg: 'Kongo',
ki: 'Kikuyu',
kj: 'Kuanyama',
kk: 'Kazakh',
kl: 'Kalaallisut',
km: 'Central Khmer',
kn: 'Kannada',
ko: 'Korean',
kr: 'Kanuri',
ks: 'Kashmiri',
ku: 'Kurdish',
kv: 'Komi',
kw: 'Cornish',
ky: 'Kirghiz',
la: 'Latin',
lb: 'Luxembourgish',
lg: 'Ganda',
li: 'Limburgan',
ln: 'Lingala',
lo: 'Lao',
lt: 'Lithuanian',
lu: 'Luba-Katanga',
lv: 'Latvian',
mg: 'Malagasy',
mh: 'Marshallese',
mi: 'Maori',
mk: 'Macedonian',
ml: 'Malayalam',
mn: 'Mongolian',
mr: 'Marathi',
ms: 'Malay',
mt: 'Maltese',
my: 'Burmese',
na: 'Nauru',
nb: 'Norwegian Bokmål',
nd: 'Ndebele, North',
ne: 'Nepali',
ng: 'Ndonga',
nl: 'Dutch',
nn: 'Norwegian Nynorsk',
no: 'Norwegian',
nr: 'Ndebele, South',
nv: 'Navajo',
ny: 'Nyanja',
oc: 'Occitan',
oj: 'Ojibwa',
om: 'Oromo',
or: 'Oriya',
os: 'Ossetian',
pa: 'Panjabi',
pi: 'Pali',
pl: 'Polish',
ps: 'Pushto',
pt: 'Portuguese',
qu: 'Quechua',
rm: 'Romansh',
rn: 'Rundi',
ro: 'Romanian',
ru: 'Russian',
rw: 'Kinyarwanda',
sa: 'Sanskrit',
sc: 'Sardinian',
sd: 'Sindhi',
se: 'Northern Sami',
sg: 'Sango',
si: 'Sinhala',
sk: 'Slovak',
sl: 'Slovenian',
sm: 'Samoan',
sn: 'Shona',
so: 'Somali',
sq: 'Albanian',
sr: 'Serbian',
ss: 'Swati',
st: 'Sotho',
su: 'Sundanese',
sv: 'Swedish',
sw: 'Swahili',
ta: 'Tamil',
te: 'Telugu',
tg: 'Tajik',
th: 'Thai',
ti: 'Tigrinya',
tk: 'Turkmen',
tl: 'Tagalog',
tn: 'Tswana',
to: 'Tonga',
tr: 'Turkish',
ts: 'Tsonga',
tt: 'Tatar',
tw: 'Twi',
ty: 'Tahitian',
ug: 'Uighur',
uk: 'Ukrainian',
ur: 'Urdu',
uz: 'Uzbek',
ve: 'Venda',
vi: 'Vietnamese',
vo: 'Volapük',
wa: 'Walloon',
wo: 'Wolof',
xh: 'Xhosa',
yi: 'Yiddish',
yo: 'Yoruba',
za: 'Zhuang',
zh: 'Chinese',
zu: 'Zulu',
Comment on lines +148 to +332
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This most likely already exists, but I can see where maintaining this list could be an issue.
We already maintain such list here: https://github.com/nextcloud/server/blob/9de329a4c2327767d86bd7f594b232eb56af0d01/resources/locales.json

We have a dedicated php method to get those ressources.
I'll suggest we use that instead (you can either have this injected in the page from a InitialState or maybe it's already available
https://github.com/nextcloud/server/blob/9de329a4c2327767d86bd7f594b232eb56af0d01/lib/public/L10N/IFactory.php#L88

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @skjnldsv... so I would like to invoke L10N's php function 'findLanguageFromLocale()' from 'fetchTracks()'.
Please would you provide guidance as to how to import this function into Videos.vue?

public function findLanguageFromLocale(string $app = 'core', string $locale = null)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need the findLanguageFromLocale ?
All of the locale computation should be done on the php side

}
const davDir = this.davPath.replace(/[^/]*$/, '')
const videoRoot = this.basename.replace(/[.][^.]+$/, '')
// Create caption tracks for the HTML5 player
// E.g.: <file>.mkv: look for <file>.xx.vtt or .<file>.xx.vtt
let capTracks = '' // '<source src="' + this.davPath + '" />\n'
for (let i = 0; i < folder.length; ++i) {
const basename = folder[i].basename
const index = basename.indexOf(videoRoot)
// Consider only file... or .file...
if (!(index === 0 || (index === 1 || basename[0] === '.'))) {
continue
}
const suffix = basename.slice(videoRoot.length + index)
// Consider only ...xx.vtt
if (suffix.search(/^[.]..[.]vtt$/) !== 0) {
continue
}
const lang = suffix.slice(1, 3)
const language = languages[lang] || lang
capTracks += '<track src="' + davDir + basename + '"'
+ ' label="' + language + '"'
+ ' kind="captions"'
+ ' srclang="' + lang + '"'
+ ' />\n'
}
this.tracks = capTracks
})
},
},
}
</script>
Expand Down