Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
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
39 changes: 39 additions & 0 deletions docs/plugins/subtitles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Subtitles
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated

subtitle plugin allows you to fetch and parse subtitle file from a URL and you an read text from the parsed file

## Usage

If you want to access Subtitles in your App code directly, import the *Subtitles* plugin from the Lightning SDK:

```js
import { Subtitles } from '@lightningjs/sdk'
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
```


## Available methods

### fetchAndParseSubs

`fetchAndParseSubs` method expects a valid file URL as an argument.
This method will fetch a file from the URL and parse it to create a list of objects. created subtitles list is stored in the plugin.
This method returns a promise that resolves to parsed subtitles as a list of objects containing {start, end, payload}.
```js
Subtitles.fetchAndParseSubs(URL)
```

### getSubtitleByTimeIndex
From the stored subtitles you can get subtitles as text, when you pass currentTime(in seconds) as an argument to the method.

```js
Subtitles.getSubtitleByTimeIndex(currentTime)
```

### clearCurrentSubtitle

Parsed subtitles will be stored in the plugin, `clearCurrentSubtitle` clears all the stored subtitles in the plugin.

```js
Subtitles.clearCurrentSubtitle()
```

1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export { default as TV, initTV } from './src/TV'
export { default as Pin, initPin } from './src/Pin'
export { default as VideoPlayer, initVideoPlayer, mediaUrl } from './src/VideoPlayer'
export { initLightningSdkPlugin } from './src/LightningSdkPlugins'
export { default as Subtitles } from './src/Subtitles'
38 changes: 38 additions & 0 deletions src/Subtitles/SubtitleAsset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2020 Metrological
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export default class SubtitleAsset {
constructor(obj) {
this._start = obj.start // start time to show subtitle in sec
this._end = obj.end // end time of showing subtitle in sec
this._payload = obj.payload ? obj.payload.replace(/<(.*?)>/g, '') : '' // Remove <v- >, etc tags in subtitle text
}

get start() {
return this._start
}

get end() {
return this._end
}

get payload() {
return this._payload
}
}
174 changes: 174 additions & 0 deletions src/Subtitles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2020 Metrological
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import SubtitleAsset from './SubtitleAsset.js'
export default class Subtitles {
// @ params url: subtitle file URL
// @return parsed subtitles as list of objects
// also stores parsed data
static fetchAndParseSubs(url) {
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
if(url && typeof url === 'string' && url.includes('https://') || url.includes('http://')){
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
console.log('invalid URL')
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
return Promise.reject(new Error('invalid URL'))
}
return fetch(url)
.then(data => data.text())
.then(subtitleData => {
this.clearCurrentSubtitle()
return this.parseSubtitles(subtitleData)
}).catch((error)=> {
console.log('Fetching file Failed:', error)
this.clearCurrentSubtitle()
})
}

// clears stored subtitles data
static clearCurrentSubtitle() {
this._currentSubtitle = null
this._nextSubtitle = null
}

// @params currentTime: time as seconds
// @return subtitle as text at passed currentTime
static getSubtitleByTimeIndex(currentTime) {
if(!currentTime || isNaN(currentTime) ) {
console.log('invalid currentTime')
return
}
let self = this
Comment thread
gvamshi-metrological marked this conversation as resolved.
Outdated
if (
this._captions &&
this._captions.length &&
this._currentSubtitle &&
this._nextSubtitle &&
Number(currentTime.toFixed(0)) < Number(this._nextSubtitle.end.toFixed(0)) &&
Number(currentTime.toFixed(0)) >= Number(this._currentSubtitle.start.toFixed(0))
) {
if (
Number(currentTime.toFixed(0)) >= Number(this._currentSubtitle.start.toFixed(0)) &&
Number(currentTime.toFixed(0)) < Number(this._currentSubtitle.end.toFixed(0))
) {
return this._currentSubtitle.payload
} else if (
Number(currentTime.toFixed(0)) >= Number(this._nextSubtitle.start.toFixed(0)) &&
Number(currentTime.toFixed(0)) < Number(this._nextSubtitle.end.toFixed(0))
) {
return this._nextSubtitle.payload
} else {
return ''
}
} else {
updateSubtitles()
}

function updateSubtitles() {
// updates current and next subtitle text values
if (self._captions && self._captions.length) {
if (
Number(currentTime.toFixed(0)) <=
Number(self._captions[self._captions.length - 1].start.toFixed(0))
) {
if (Number(currentTime.toFixed(0)) < Number(self._captions[0].end.toFixed(0))) {
if (self._captions[1] && self._captions[1].payload) {
self._nextSubtitle = self._captions[1]
}
self._currentSubtitle = self._captions[0]
} else {
for (let i = 0; i < self._captions.length; i++) {
if (Number(self._captions[i].start.toFixed(0)) >= Number(currentTime.toFixed(0))) {
self._captions[i + 1] && self._captions[i + 1].payload
? (self._nextSubtitle = self._captions[i + 1])
: { payload: '' }
self._currentSubtitle = self._captions[i]
break
}
}
}
}
}
}
}

// parses subtitle file and returns list of time, text objects
static parseSubtitles(plainSub) {
let linesArray = plainSub
.trim()
.replace('\r\n', '\n')
.split(/[\r\n]/)
.map(line => {
return line.trim()
})
let cues = []
let start = null
let end = null
let payload = null
let lines = linesArray.filter(item => item !== '' && isNaN(item))
linesArray = []
for (let i = 0; i < lines.length; i++) {
if (lines[i].indexOf('-->') >= 0) {
let splitted = lines[i].split(/[ \t]+-->[ \t]+/)

start = Subtitles.parseTimeStamp(splitted[0])
end = Subtitles.parseTimeStamp(splitted[1])
} else if (lines[i] !== '') {
if (start && end) {
if (i + 1 < lines.length && lines[i + 1].indexOf('-->') >= 0) {
let cue = new SubtitleAsset({
start,
end,
payload: payload ? payload + ' ' + lines[i] : lines[i],
})
Comment thread
gvamshi-metrological marked this conversation as resolved.
Outdated
cues.push(cue)
start = null
end = null
payload = null
} else {
payload = payload ? payload + ' ' + lines[i] : lines[i]
}
}
} else if (start && end) {
if (payload == null) {
payload = lines[i]
} else {
payload += ' ' + lines[i]
}
}
}
if (start && end) {
let match = /<(.*?)>/g
if (payload) {
payload.replace(match, '')
}
let cue = new SubtitleAsset({ start, end, payload })
cues.push(cue)
}
this._captions = cues
return this._captions
}

// parses timestamp in subtitle file into seconds
static parseTimeStamp(s) {
const match = s.match(/^(?:([0-9]+):)?([0-5][0-9]):([0-5][0-9](?:[.,][0-9]{0,3})?)/)

const hours = parseInt(match[1], 10) || '0'
const minutes = parseInt(match[2], 10)
const seconds = parseFloat(match[3].replace(',', '.'))
return seconds + 60 * minutes + 60 * 60 * hours
}
}