chore: first commit
This commit is contained in:
7
.babelrc
Normal file
7
.babelrc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["env", { "targets": { "node": "6" } }],
|
||||||
|
"flow",
|
||||||
|
"stage-0"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.eslintrc.js
Normal file
3
.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: 'algolia/flowtype'
|
||||||
|
}
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Youtube Captions Scrapper
|
||||||
|
|
||||||
|
> Scrap youtube user defined and auto-generated captions
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
* `> npm install -S youtube-captions-scrapper` OR
|
||||||
|
* `> yarn add youtube-captions-scrapper`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ES6 / TypeScript
|
||||||
|
import { getSubtitles } from 'youtube-captions-scrapper';
|
||||||
|
|
||||||
|
getSubtitles({
|
||||||
|
videoID: 'XXXXX', // youtube video id
|
||||||
|
lang: 'fr' // default: `en`
|
||||||
|
}).then(captions => {
|
||||||
|
console.log(captions);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ES5
|
||||||
|
var getSubtitles = require('youtube-captions-scrapper').getSubtitles;
|
||||||
|
|
||||||
|
getSubtitles({
|
||||||
|
videoID: 'XXXXX', // youtube video id
|
||||||
|
lang: 'fr' // default: `en`
|
||||||
|
}).then(function(captions) {
|
||||||
|
console.log(captions);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Captions will be an array of object of this format:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
"start": Number,
|
||||||
|
"dur": Number,
|
||||||
|
"text": String
|
||||||
|
}
|
||||||
|
```
|
||||||
45
package.json
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "youtube-captions-scrapper",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Scrap youtube auto-generated captions",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"author": {
|
||||||
|
"name": "Algolia, Inc.",
|
||||||
|
"url": "https://www.algolia.com"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/algolia/youtube-captions-scrapper.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/algolia/youtube-captions-scrapper",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/algolia/youtube-captions-scrapper/issues"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rm -rf dist && babel src -d dist",
|
||||||
|
"prepublish": "npm run build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
|
"babel-eslint": "^8.0.2",
|
||||||
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"babel-preset-flow": "^6.23.0",
|
||||||
|
"babel-preset-stage-0": "^6.24.1",
|
||||||
|
"babel-watch": "^2.0.7",
|
||||||
|
"eslint": "^4.11.0",
|
||||||
|
"eslint-config-algolia": "^12.0.0",
|
||||||
|
"eslint-config-prettier": "^2.8.0",
|
||||||
|
"eslint-plugin-flowtype": "^2.39.1",
|
||||||
|
"eslint-plugin-import": "^2.8.0",
|
||||||
|
"eslint-plugin-prettier": "^2.3.1",
|
||||||
|
"flow-bin": "^0.59.0",
|
||||||
|
"flow-typed": "^2.2.3",
|
||||||
|
"prettier": "^1.8.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.17.1",
|
||||||
|
"he": "^1.1.1",
|
||||||
|
"lodash": "^4.17.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/index.js
Normal file
60
src/index.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import he from 'he';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { find } from 'lodash';
|
||||||
|
|
||||||
|
export async function getSubtitles({
|
||||||
|
videoID,
|
||||||
|
lang = 'en',
|
||||||
|
}: {
|
||||||
|
videoID: string,
|
||||||
|
lang: 'en',
|
||||||
|
}) {
|
||||||
|
const { data } = await axios.get(
|
||||||
|
`https://youtube.com/get_video_info?video_id=${videoID}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const decodedData = decodeURIComponent(data);
|
||||||
|
|
||||||
|
// * ensure we have access to captions data
|
||||||
|
if (!decodedData.includes('captionTracks'))
|
||||||
|
throw new Error(`Could not find captions for video: ${videoID}`);
|
||||||
|
|
||||||
|
const regex = /({"captionTracks":.*isTranslatable":(true|false)}])/;
|
||||||
|
const [match] = regex.exec(decodedData);
|
||||||
|
const { captionTracks } = JSON.parse(`${match}}`);
|
||||||
|
|
||||||
|
const subtitle =
|
||||||
|
find(captionTracks, { vssId: `.${lang}` }) ||
|
||||||
|
find(captionTracks, { vssId: `a.${lang}` });
|
||||||
|
|
||||||
|
// * ensure we have found the correct subtitle lang
|
||||||
|
if (!subtitle || (subtitle && !subtitle.baseUrl))
|
||||||
|
throw new Error(`Could not find ${lang} captions for ${videoID}`);
|
||||||
|
|
||||||
|
const { data: transcript } = await axios.get(subtitle.baseUrl);
|
||||||
|
const lines = transcript
|
||||||
|
.replace('<?xml version="1.0" encoding="utf-8" ?><transcript>', '')
|
||||||
|
.replace('</transcript>', '')
|
||||||
|
.split('</text>')
|
||||||
|
.filter(line => line && line.trim())
|
||||||
|
.map(line => {
|
||||||
|
const startRegex = /start="([\d.]+)"/;
|
||||||
|
const durRegex = /dur="([\d.]+)"/;
|
||||||
|
|
||||||
|
const [, start] = startRegex.exec(line);
|
||||||
|
const [, dur] = durRegex.exec(line);
|
||||||
|
|
||||||
|
const htmlText = line
|
||||||
|
.replace(/<text.+>/, '')
|
||||||
|
.replace(/&/gi, '&')
|
||||||
|
.replace(/<\/?[^>]+(>|$)/g, '');
|
||||||
|
|
||||||
|
const text = he.decode(htmlText);
|
||||||
|
|
||||||
|
return { start, dur, text };
|
||||||
|
});
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user