Merge pull request #3 from imshaikot/dev

Dev v2.0
This commit is contained in:
Shariar Shaikot
2021-10-23 00:38:38 +02:00
committed by GitHub
13 changed files with 6421 additions and 253 deletions

View File

@@ -1,13 +0,0 @@
{
"presets": [
["env", {
"targets": {
"browsers": [
"> 1%",
"last 2 versions"
]
}
}],
"stage-2"
]
}

View File

@@ -1,5 +1,7 @@
language: node_js
node_js:
- "5"
- "12.14.1"
script:
- npm run lint
- npm test
- npm run build

View File

@@ -1,8 +1,7 @@
<h2 align="left"> Convert and generate URL of WebVTT from .srt subtitle file on the fly over Browser/HTML5 environment</h2>
<h2 align="left"> Convert and generate ObjectURL of WebVTT from srt format subtitle on the fly over HTML5 environment</h2>
HTMLMediaElement/Video doesn't support ```.srt``` (SubRip Track) format subtitle as its ```<track>``` source - in order to show captions of your video track either you have to convert the SRT file to WebVTT or write it on your own. Because ```<track src="VALID URL SCHEME">``` requires a valid URL of ```.vtt``` (Web Video Text Track) formated subtitle track.
This library will let you do this on the fly and will give you an URL to set the source of caption track.
HTMLMediaElement/Video doesn't support `.srt` (SubRip Track) format subtitle as its `<track>` source - in order to show captions of your video track either you have to convert the SRT file to WebVTT or write it on your own. Because `<track src="VALID URL SCHEME">` requires a valid URL of `.vtt` (Web Video Text Track) formated subtitle track.
This library will let you do this on the fly and will give you an URL to set the source of caption track.
<p align="center">
<a href="https://www.npmjs.org/package/srt-webvtt"><img src="https://img.shields.io/npm/v/srt-webvtt.svg?style=flat-square" /></a>
@@ -26,10 +25,6 @@
```bash
$ npm install srt-webvtt --save
```
OR umd builds are also available
```
<script src="https://unpkg.com/srt-webvtt/umd/index.min.js"></script>
```
## Getting Started
@@ -37,53 +32,44 @@ Using it very easy but a little tricky indeed.
To getting started:
```js
// Using ES6
import VTTConverter from 'srt-webvtt'; // This is a default export, so you don't have to worry about the import name
// Using ES6 es-module
import toWebVTT from "srt-webvtt"; // This is a default export, so you don't have to worry about the import name
// Not using ES6
var VTTConverter = require('srt-webvtt');
// Not using ES6??
var toWebVTT = require("srt-webvtt");
```
## Example and API
When you're about to use ```HTMLMediaElement``` (example: ```<video>```) and you want to show caption on your video player - there's a native feature that will allow you to do that.
See the official MDN article and tutorial of this ```<track>``` feature
When you're using `HTMLMediaElement` (example: `<video>`) and you want to show caption on your video player - there's a native HTML Element that will allow us to do that.
See the official MDN article and tutorial of this `<track>` feature
<a href="https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video"> Adding captions and subtitles to HTML5 video</a>
But this feature is limited to WebVTT format and won't allow you to use SRT (very commonly used subtitle)
So, this tiny library will take your ```.srt``` subtitle file or a ```Blob``` object and will give you converted ```.vtt``` file's valid Object URL that you can set as ```<track>```'s source.
So, this tiny library will take your `.srt` subtitle file or a `Blob` object and will give you converted `.vtt` file's valid Object URL that you can set as `<track>`'s source.
<h4>See the Example below:</h4>
```js
import VTTConverter from 'srt-webvtt';
import { default as toWebVTT } from "srt-webvtt";
const vttConverter = new VTTConverter(input.files[0]); // the constructor accepts a parameer of SRT subtitle blob/file object
vttConverter
.getURL()
.then(function(url) { // Its a valid url that can be used further
var track = document.getElementById('my-sub-track'); // Track element (which is child of a video element)
var video = document.getElementById('my-video'); // Main video element
track.src = url; // Set the converted URL to track's source
video.textTracks[0].mode = 'show'; // Start showing subtitle to your track
})
.catch(function(err) {
console.error(err);
})
try {
const textTrackUrl = await toWebVTT(input.files[0]); // this function accepts a parameer of SRT subtitle blob/file object
// It is a valid url that can be used as text track URL
var track = document.getElementById("my-sub-track"); // Track element (which is child of a video element)
var video = document.getElementById("my-video"); // Main video element
track.src = textTrackUrl; // Set the converted URL to track's source
video.textTracks[0].mode = "show"; // Start showing subtitle to your track
} catch (e) {
console.error(e.message);
}
```
<br />
```Constructor(Blob)``` The constructor must a valid Blob reference to a valid srt file
```getURL()``` resolves a promise with the converted subtitle's Object URL or rejects with appropriate error.
```release()``` as long as you're done with the URL and subtitle you call this instance method to release the memory.
`toWebVTT(file: Blob): Promise<string>` and this is it :)
## LICENSE

18
index.d.ts vendored
View File

@@ -1,18 +0,0 @@
declare module "srt-webvtt" {
class WebVTTConverter {
constructor(resource: Blob);
public blobToBuffer(): Promise<Uint8Array | string>;
public static blobToString(
blob: Blob,
success: (result: string) => void,
fail: () => void
): void;
public static toVTT(utf8str: string): string;
public static toTypedArray(str: string): Uint8Array;
public getURL(): Promise<string>;
public release(): void;
}
export default WebVTTConverter;
}

View File

@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Gloria+Hallelujah" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<script>
function convert(event) {
const el = document.getElementById('process');
el.innerHTML = '<p>Processing...</p>';
const file = event.files[0];
if (!file.name.includes('.srt')) {
el.innerHTML = 'Process Error: you must select a subtitle file that ends with .srt';
return;
}
const webvtt = new WebVTTConverter(file);
webvtt
.getURL()
.then(url => {
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = file.name.substr(0, file.name.lastIndexOf('.')) + '.vtt';
anchor.innerText = 'Download';
anchor.addEventListener('click', () => webvtt.release());
el.innerHTML = anchor.outerHTML;
})
.catch(err => {
el.innerHTML = 'Process Error: ' +err;
});
}
</script>
<title>SRT to WebVTT Converter</title>
<style>
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
</style>
</head>
<body>
<div style="margin: 0 auto;">
<div style="width: 500px; margin: 0 auto;">
<h3>Select a SRT format subtitle in order to convert it to WebVTT format</h3>
<input type="file" onchange="convert(this)" />
<div style="margin-top: 10px;" id="process"></div>
</div>
</div>
</body>
</html>

View File

@@ -1,90 +0,0 @@
class WebVTTConverter {
constructor(resource) {
this.resource = resource;
}
blobToBuffer() {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('loadend', (event) => {
const buf = event.target.result;
resolve(new Uint8Array(buf));
});
reader.addEventListener('error', () => reject('Error while reading the Blob object'));
reader.readAsArrayBuffer(this.resource);
});
}
/**
* @param {*} blob
* @param {*} success
* @param {*} fail
*/
static blobToString(blob, success, fail) {
const reader = new FileReader();
reader.addEventListener('loadend', (event) => {
const text = event.target.result;
success(text);
});
reader.addEventListener('error', () => fail());
reader.readAsText(blob);
}
/**
* @param {*} utf8str
*/
static toVTT(utf8str) {
return utf8str
.replace(/\{\\([ibu])\}/g, '</$1>')
.replace(/\{\\([ibu])1\}/g, '<$1>')
.replace(/\{([ibu])\}/g, '<$1>')
.replace(/\{\/([ibu])\}/g, '</$1>')
.replace(/(\d\d:\d\d:\d\d),(\d\d\d)/g, '$1.$2')
.concat('\r\n\r\n');
}
/**
* @param {*} str
*/
static toTypedArray(str) {
const result = [];
str.split('').forEach((each) => {
result.push(parseInt(each.charCodeAt(), 16));
});
return Uint8Array.from(result);
}
getURL() {
return new Promise((resolve, reject) => {
if (!(this.resource instanceof Blob)) return reject('Expecting resource to be a Blob but something else found.');
if (!(FileReader)) return reject('No FileReader constructor found');
if (!TextDecoder) return reject('No TextDecoder constructor found');
return WebVTTConverter.blobToString(
this.resource,
(decoded) => {
const vttString = 'WEBVTT FILE\r\n\r\n';
const text = vttString.concat(WebVTTConverter.toVTT(decoded));
const blob = new Blob([text], { type: 'text/vtt' });
this.objectURL = URL.createObjectURL(blob);
return resolve(this.objectURL);
},
() => {
this.blobToBuffer()
.then((buffer) => {
const utf8str = new TextDecoder('utf-8').decode(buffer);
const vttString = 'WEBVTT FILE\r\n\r\n';
const text = vttString.concat(WebVTTConverter.toVTT(utf8str));
const blob = new Blob([text], { type: 'text/vtt' });
this.objectURL = URL.createObjectURL(blob);
return resolve(this.objectURL);
});
},
);
});
}
release() {
URL.createObjectURL(this.objectURL);
}
}
window.WebVTTConverter = WebVTTConverter;
export default WebVTTConverter;

36
index.spec.ts Normal file
View File

@@ -0,0 +1,36 @@
import { TextDecoder } from 'util'
import { default as WebVTT, moduleName } from './index'
describe('WebVTT', () => {
let file: Blob;
beforeEach(() => {
file = new Blob([new Uint8Array([])], { type: 'text/plain' });
(global as any).TextDecoder = TextDecoder;
(global.URL as any).createObjectURL = jest.fn().mockImplementation((text) => 'blob:some-valid-url');
})
it('should return a promise', () => {
expect(WebVTT(file)).toBeInstanceOf(Promise);
});
it('should return a valid return', async () => {
expect(await WebVTT(file)).toBe('blob:some-valid-url');
});
it('should throw if undefined or wrong resource provided', async () => {
try {
const webVtt = await WebVTT(undefined as any);
} catch (e: any) {
expect(e.message).toContain(moduleName);
expect(e.message).toContain('Blob');
}
});
it('should throw TextDecoder is not defined', async () => {
(global as any).TextDecoder = undefined;
try {
const webVtt = await WebVTT(file);
} catch (e: any) {
expect(e.message).toContain(moduleName);
expect(e.message).toContain('TextDecoder');
}
});
});

76
index.ts Normal file
View File

@@ -0,0 +1,76 @@
export const moduleName = 'toWebVTT';
/**
* @param blob
* @param readAs
* @returns Promise<ArrayBuffer>
*/
const blobToBufferOrString = (blob: Blob, readAs: 'string' | 'buffer'): Promise<Uint8Array | String> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
/**
* @param event
*/
const loadedCb = (event: Event) => {
const buf = (event.target as any).result;
reader.removeEventListener('loadend', loadedCb);
resolve(readAs !== 'string' ? new Uint8Array(buf) : buf);
};
const errorCb = () => {
reader.removeEventListener('error', errorCb);
reject(new Error(`${moduleName}: Error while reading the Blob object`));
};
reader.addEventListener('loadend', loadedCb);
reader.addEventListener('error', errorCb);
if (readAs !== 'string') {
reader.readAsArrayBuffer(blob);
} else {
reader.readAsText(blob);
}
});
/**
* @param text
* @returns ObjectURL
*/
const blobToURL = (text: string): string => URL
.createObjectURL(new Blob([text], { type: 'text/vtt' }));
/**
* @param utf8str
* @returns string
*/
const toVTT = (utf8str: string) => utf8str
.replace(/\{\\([ibu])\}/g, '</$1>')
.replace(/\{\\([ibu])1\}/g, '<$1>')
.replace(/\{([ibu])\}/g, '<$1>')
.replace(/\{\/([ibu])\}/g, '</$1>')
.replace(/(\d\d:\d\d:\d\d),(\d\d\d)/g, '$1.$2')
.concat('\r\n\r\n');
/**
* @param resource
* @returns Promise<string>
*/
const toWebVTT = async (resource: Blob): Promise<string> => {
if (!(FileReader)) {
throw (new Error(`${moduleName}: No FileReader constructor found`));
}
if (!TextDecoder) {
throw (new Error(`${moduleName}: No TextDecoder constructor found`));
}
if (!(resource instanceof Blob)) {
throw new Error(`${moduleName}: Expecting resource to be a Blob but something else found.`);
}
let text;
const vttString = 'WEBVTT FILE\r\n\r\n'; // leading text
try {
const buffer = await blobToBufferOrString(resource, 'string');
text = vttString.concat(toVTT(buffer as string));
} catch (e) {
const buffer = await blobToBufferOrString(resource, 'buffer');
const decode = new TextDecoder('utf-8').decode(buffer as Uint8Array);
text = vttString.concat(toVTT(decode));
}
return Promise.resolve(blobToURL(text));
};
export default toWebVTT;

4
jest.config.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
};

6257
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "srt-webvtt",
"version": "1.3.1",
"version": "1.3.2",
"description": "Convert SRT format subtitle to WebVTT on the fly over HTML5 environment",
"main": "lib/index.js",
"files": [
@@ -8,9 +8,9 @@
"lib/index.d.ts"
],
"scripts": {
"test": "./node_modules/.bin/eslint ./index.js",
"start": "webpack-dev-server",
"build": "babel ./index.js --stage 2 -d ./lib/ && webpack -p ./index.js ./umd/index.min.js && cp index.d.ts lib/index.d.ts",
"test": "./node_modules/.bin/jest",
"lint": "./node_modules/.bin/eslint ./index.js",
"build": "rm -rf lib && tsc",
"minor": "npm version minor && npm publish",
"major": "npm version major && npm publish",
"patch": "npm version patch && npm publish"
@@ -46,19 +46,14 @@
},
"homepage": "https://github.com/imshaikot/srt-webvtt#readme",
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"@types/jest": "^27.0.2",
"eslint": "^4.15.0",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.5.1",
"html-webpack-plugin": "^2.30.1",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.11.0"
"jest": "^27.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.4.4"
}
}

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es2017", "es7", "es6", "dom"],
"declaration": true,
"outDir": "lib",
"strict": true,
"esModuleInterop": true
},
"exclude": [
"node_modules",
"lib"
]
}

View File

@@ -1,29 +0,0 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './index.js',
output: {
library: 'VideoToThumb',
libraryTarget: 'umd',
},
resolve: {
extensions: ['.js'],
},
module: {
rules: [
{
test: /\.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: `${__dirname}/index.html`,
filename: 'index.html',
inject: 'body',
}),
],
devtool: 'cheap-module-eval-source-map',
};