Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
59 changes: 59 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Heavily based on https://joelhooks.com/jest-and-github-actions
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@gvamshi-metrological Could you please remove this file from your PR? Regrettably, we can't use Github actions with our Github subscription. This will not change; the repo will most likely move to a Bitbucket environment somewhere in the upcoming months. So basically; we'll never get this to work I'm afraid 😩

name: Tests CI

on:
pull_request:
paths-ignore:
- 'README.md'

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Reconfigure git to use HTTPS authentication
uses: GuillaumeFalourd/SSH-to-HTTPS@v1

- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: '17'
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated

- name: Install
run: |
npm ci

- name: Run ESLint
run: npm run lint

- name: Run Jest tests
run: npm run test:ci
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated

- name: Tests ✅
if: ${{ success() }}
run: |
curl --request POST \
--url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \
--header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
--header 'content-type: application/json' \
--data '{
"context": "tests",
"state": "success",
"description": "Tests passed",
"target_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}'

- name: Tests 🚨
if: ${{ failure() }}
run: |
curl --request POST \
--url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }} \
--header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
--header 'content-type: application/json' \
--data '{
"context": "tests",
"state": "failure",
"description": "Tests failed",
"target_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}'
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()
```

36 changes: 36 additions & 0 deletions github-actions-reporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* eslint-disable no-console */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@gvamshi-metrological Can you please remove this file, also because we can't use Github actions 😩

class GithubActionsReporter {
constructor(globalConfig, options) {
this._globalConfig = globalConfig
this._options = options
}

onRunComplete(contexts, results) {
results.testResults.forEach(testResultItem => {
const testFilePath = testResultItem.testFilePath

testResultItem.testResults.forEach(result => {
if (result.status !== 'failed') {
return
}

result.failureMessages.forEach(failureMessages => {
const newLine = '%0A'
const message = failureMessages.replace(/\n/g, newLine)
const captureGroup = message.match(/:([0-9]+):([0-9]+)/)

if (!captureGroup) {
console.log('Unable to extract line number from call stack')
return
}

const [, line, col] = captureGroup
console.log(`::error file=${testFilePath},line=${line},col=${col}::${message}`)
})
})
})
}
}

// eslint-disable-next-line no-undef
module.exports = GithubActionsReporter
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 SubtitlesParser } from './src/SubtitlesParser'
Comment thread
robbertvancaem marked this conversation as resolved.
216 changes: 216 additions & 0 deletions src/SubtitlesParser/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* 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 SubtitlesParser {
// @ params url: subtitle file URL
// @return parsed subtitles as list of objects
// also stores parsed data
Comment thread
robbertvancaem marked this conversation as resolved.
constructor() {
SubtitlesParser.removeSubtitleTextStyles = false
SubtitlesParser.clearCurrentSubtitle()
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
}
// static _currentSubtitle = null;
// static _nextSubtitle = null;
// static _captions = null;
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
static fetchAndParseSubs(url, customParser = false, ParseOptions = {}) {
const _url = new URL(url)
if (_url.protocol !== 'https:' || _url.protocol !== 'https:' || !_url.hostname) {
console.log('Invalid URL')
return Promise.reject(new Error('Invalid URL'))
}
if (ParseOptions && 'removeSubtitleTextStyles' in ParseOptions) {
this.removeSubtitleTextStyles = ParseOptions.removeSubtitleTextStyles
}
return new Promise((resolve, reject) => {
fetch(url)
.then(data => {
let subtitleData = data.text()
this.clearCurrentSubtitle()
if (customParser && typeof customParser === 'function') {
this._captions = customParser(subtitleData)
} else {
this._captions = this.parseSubtitles(subtitleData)
}
if (this._captions && this._captions.length) {
resolve(this._captions)
} else {
reject('Failed to parse subtitles: invalid subtitles length')
}
Comment thread
robbertvancaem marked this conversation as resolved.
Outdated
})
.catch(error => {
console.log('Fetching file Failed:', error)
this.clearCurrentSubtitle()
reject('Fetching file Failed')
})
})
}

// clears stored subtitles data
static clearCurrentSubtitle() {
this._currentSubtitle = null
this._nextSubtitle = null
}
static set removeSubtitleTextStyles(v) {
this._subtitleTextStyles = !v
}

// @params currentTime: time as seconds
// @return subtitle as text at passed currentTime
static getSubtitleByTimeIndex(currentTime) {
console.log('currentTime:', currentTime)
console.log('this._nextSubtitle:', this._nextSubtitle)
console.log('this._currentSubtitle:', this._currentSubtitle)
if (!currentTime || isNaN(currentTime)) {
console.log('invalid currentTime')
return
}
let self = this
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 = ''
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 = SubtitlesParser.parseTimeStamp(splitted[0])
end = SubtitlesParser.parseTimeStamp(splitted[1])
} else if (lines[i] !== '') {
if (start && end) {
if (i + 1 < lines.length && lines[i + 1].indexOf('-->') >= 0) {
let subPayload = payload ? payload + ' ' + lines[i] : lines[i]
let cue = {
start,
end,
payload: subPayload
? this._subtitleTextStyles
? subPayload
: subPayload.replace(/<(.*?)>/g, '')
: '', // Remove <v- >, etc tags in subtitle text
}
cues.push(cue)
start = null
end = null
payload = ''
subPayload = 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 = {
start,
end,
payload: payload
? this._subtitleTextStyles
? payload
: payload.replace(/<(.*?)>/g, '') // Remove <v- >, etc tags in subtitle text
: '',
}
cues.push(cue)
}
return cues
}

// 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
}
}
Loading