mirror of
https://github.com/zoriya/srt-webvtt.git
synced 2025-12-06 05:56:10 +00:00
13
.babelrc
13
.babelrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": {
|
||||
"browsers": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
}
|
||||
}],
|
||||
"stage-2"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "5"
|
||||
- "12.14.1"
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
- npm run build
|
||||
|
||||
58
README.md
58
README.md
@@ -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
18
index.d.ts
vendored
@@ -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;
|
||||
}
|
||||
53
index.html
53
index.html
@@ -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>
|
||||
90
index.js
90
index.js
@@ -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
36
index.spec.ts
Normal 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
76
index.ts
Normal 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
4
jest.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jsdom',
|
||||
};
|
||||
6257
package-lock.json
generated
Normal file
6257
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -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
15
tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
@@ -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',
|
||||
};
|
||||
Reference in New Issue
Block a user