yifysubtitles
Advanced tools
Comparing version 2.1.3 to 2.1.4
@@ -1,2 +0,2 @@ | ||
const yifysubtitles = require('../index'); | ||
const yifysubtitles = require('..'); | ||
@@ -6,5 +6,5 @@ console.log('dirname', __dirname); | ||
yifysubtitles('tt1156398', {langs: ['fr', 'en', 'zh'], path: '/tmp'}) | ||
.then(res => { | ||
console.log('res', res); | ||
}) | ||
.catch(err => console.log(err)); | ||
.then(res => { | ||
console.log('res', res); | ||
}) | ||
.catch(error => console.log(error)); |
155
index.js
@@ -12,90 +12,125 @@ const fs = require('fs'); | ||
const apiUri = 'http://api.yifysubtitles.com/subs'; | ||
const uri = 'http://www.yifysubtitles.com/movie-imdb'; | ||
const downloadUri = 'http://yifysubtitles.com'; | ||
// Down const apiUri = 'http://api.yifysubtitles.com/subs'; | ||
const uri = 'https://www.yifysubtitles.com/movie-imdb'; | ||
const downloadUri = 'https://yifysubtitles.com'; | ||
const langK = Object.keys(langsFormat); | ||
const langV = langK.map(i => langsFormat[i]); | ||
const formatLangLong = lang => (langV[langK.indexOf(lang)]); | ||
const formatLangShort = lang => (langK[langV.indexOf(lang)]); | ||
const formatLangLong = lang => langV[langK.indexOf(lang)]; | ||
const formatLangShort = lang => langK[langV.indexOf(lang)]; | ||
// Since yifysubtitle api is not working anymore we scrape the site instead | ||
const scrape = imdbId => { | ||
return got(`${uri}/${imdbId}`) | ||
.then(res => cheerio.load(res.body)) | ||
.then($ => { | ||
return $('tbody tr').map((i, el) => { | ||
const $el = $(el); | ||
return { | ||
rating: $el.find('.rating-cell').text(), | ||
language: $el.find('.flag-cell .sub-lang').text().toLowerCase(), | ||
url: $el.find('.download-cell a').attr('href').replace('subtitles/', 'subtitle/') + '.zip' | ||
}; | ||
}).get(); | ||
}) | ||
return got(`${uri}/${imdbId}`) | ||
.then(res => cheerio.load(res.body)) | ||
.then($ => { | ||
return $('tbody tr') | ||
.map((i, el) => { | ||
const $el = $(el); | ||
return { | ||
rating: $el.find('.rating-cell').text(), | ||
language: $el | ||
.find('.flag-cell .sub-lang') | ||
.text() | ||
.toLowerCase(), | ||
url: | ||
$el | ||
.find('.download-cell a') | ||
.attr('href') | ||
.replace('subtitles/', 'subtitle/') + '.zip' | ||
}; | ||
}) | ||
.get(); | ||
}); | ||
}; | ||
const langFilter = (subs, langs) => { | ||
const data = langs.reduce((acc, l) => { | ||
const lang = subs.filter(s => s.language === l).sort((a, b) => b.rating - a.rating); | ||
if (lang.length) { | ||
acc[l] = lang[0]; | ||
} | ||
return acc; | ||
}, {}); | ||
return data | ||
const data = langs.reduce((acc, l) => { | ||
const lang = subs | ||
.filter(s => s.language === l) | ||
.sort((a, b) => b.rating - a.rating); | ||
if (lang.length > 0) { | ||
acc[l] = lang[0]; | ||
} | ||
return acc; | ||
}, {}); | ||
return data; | ||
}; | ||
const downloadFormat = format => (lang, url, link) => { | ||
let writed = ''; | ||
let fullPath = ''; | ||
let writed = ''; | ||
let fullPath = ''; | ||
return got.stream(downloadUri + url) | ||
.pipe(unzipper()) | ||
.pipe(streamz(entry => { | ||
const parsedPath = path.parse(entry.path); | ||
if (parsedPath.dir === '' && parsedPath.ext === '.srt') { | ||
writed = format === 'srt' ? entry.path : entry.path.replace('srt', 'vtt'); | ||
fullPath = path.join(link, writed); | ||
return format === 'srt' | ||
? entry.pipe(fs.createWriteStream(fullPath)) | ||
: entry.pipe(srt2vtt()).pipe(fs.createWriteStream(fullPath)); | ||
} | ||
entry.autodrain(); | ||
})) | ||
.promise() | ||
.then(() => ({lang: lang, langShort: formatLangShort(lang), path: fullPath, fileName: writed})); | ||
return got | ||
.stream(downloadUri + url) | ||
.pipe(unzipper()) | ||
.pipe( | ||
streamz(entry => { | ||
const parsedPath = path.parse(entry.path); | ||
// Add Language to subtitle name and deete spaces | ||
const escapedLang = lang.replace('/', '-'); | ||
entry.path = entry.path | ||
.replace(/\s+/g, '.') | ||
.replace(parsedPath.ext, `_${escapedLang}_${parsedPath.ext}`); | ||
if (parsedPath.dir === '' && parsedPath.ext === '.srt') { | ||
writed = | ||
format === 'srt' ? entry.path : entry.path.replace('srt', 'vtt'); | ||
fullPath = path.join(link, writed); | ||
return format === 'srt' ? | ||
entry.pipe(fs.createWriteStream(fullPath)) : | ||
entry.pipe(srt2vtt()).pipe(fs.createWriteStream(fullPath)); | ||
} | ||
entry.autodrain(); | ||
}) | ||
) | ||
.promise() | ||
.then(() => ({ | ||
lang, | ||
langShort: formatLangShort(lang), | ||
path: fullPath, | ||
fileName: writed | ||
})); | ||
}; | ||
const downloads = (res, opts) => { | ||
const download = downloadFormat(opts.format); | ||
const {concurrency, path} = opts; | ||
const download = downloadFormat(opts.format); | ||
const {concurrency, path} = opts; | ||
return pMap(Object.keys(res), lang => download(lang, res[lang].url, path), concurrency); | ||
return pMap( | ||
Object.keys(res), | ||
lang => download(lang, res[lang].url, path), | ||
concurrency | ||
); | ||
}; | ||
const runConditional = (imdbId, opts, res) => { | ||
return Promise.resolve(langFilter(res, opts.langs.map(formatLangLong))) | ||
.then(res => downloads(res, opts)); | ||
return Promise.resolve( | ||
langFilter(res, opts.langs.map(formatLangLong)) | ||
).then(res => downloads(res, opts)); | ||
}; | ||
const yifysubtitles = (imdbId, opts) => { | ||
opts = Object.assign({ | ||
path: __dirname, | ||
langs: ['en'], | ||
concurrency: Infinity, | ||
format: 'vtt' | ||
}, opts); | ||
opts = Object.assign( | ||
{ | ||
path: __dirname, | ||
langs: ['en'], | ||
concurrency: Infinity, | ||
format: 'vtt' | ||
}, | ||
opts | ||
); | ||
if (opts.langs.constructor !== Array) { | ||
throw new TypeError('Expected `langs` to be an array'); | ||
} else if (opts.langs.some(lang => langK.indexOf(lang) === -1)) { | ||
throw new TypeError(`Expected \`langs\` members to be in ${langK}`); | ||
} | ||
if (opts.langs.constructor !== Array) { | ||
throw new TypeError('Expected `langs` to be an array'); | ||
} else if (opts.langs.some(lang => langK.indexOf(lang) === -1)) { | ||
throw new TypeError(`Expected \`langs\` members to be in ${langK}`); | ||
} | ||
return scrape(imdbId) | ||
.then(res => res.length ? runConditional(imdbId, opts, res) : []); | ||
return scrape(imdbId).then(res => | ||
(res.length > 0) ? runConditional(imdbId, opts, res) : [] | ||
); | ||
}; | ||
module.exports = yifysubtitles; |
{ | ||
"name": "yifysubtitles", | ||
"version": "2.1.3", | ||
"version": "2.1.4", | ||
"description": "A simple wrapper to download subtitles from the yifysubtitles website.", | ||
@@ -8,11 +8,12 @@ "license": "MIT", | ||
"type": "git", | ||
"url": "https://github.com/MRdotB/yifysubtitles" | ||
"url": "https://github.com/mrdotb/yifysubtitles" | ||
}, | ||
"author": { | ||
"name": "mrdotb", | ||
"email": "baptiste.chaleil@gmail.com" | ||
"email": "mrdotb@protonmail.com" | ||
}, | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "ava" | ||
"test": "ava", | ||
"lint": "xo --fix" | ||
}, | ||
@@ -23,2 +24,5 @@ "ava": { | ||
}, | ||
"xo": { | ||
"space": true | ||
}, | ||
"dependencies": { | ||
@@ -35,2 +39,3 @@ "cheerio": "^1.0.0-rc.2", | ||
"ava": "^0.25.0", | ||
"xo": "^0.24.0", | ||
"pify": "^4.0.0" | ||
@@ -37,0 +42,0 @@ }, |
import test from 'ava'; | ||
import got from 'got'; | ||
test(`http://yifysubtitles.com should be alive`, async t => { | ||
const res = await got(`http://yifysubtitles.com`); | ||
test('http://yifysubtitles.com should be alive', async t => { | ||
const res = await got('http://yifysubtitles.com'); | ||
t.is(res.statusCode, 200); | ||
t.is(res.statusCode, 200); | ||
}); |
@@ -7,55 +7,55 @@ import fs from 'fs'; | ||
import yifysubtitles from '../'; | ||
import yifysubtitles from '..'; | ||
const downloadDir = path.join(__dirname, 'tmp'); | ||
test.before('setup: create tmp dir', async t => { | ||
await pify(fs.mkdir)(downloadDir); | ||
test.before('setup: create tmp dir', async () => { | ||
await pify(fs.mkdir)(downloadDir); | ||
}); | ||
test('with imdbid not in api', async t => { | ||
const subtitles = await yifysubtitles('tt1234567', {path: downloadDir, langs: ['fr', 'en', 'nl']}); | ||
const subtitles = await yifysubtitles('tt1234567', {path: downloadDir, langs: ['fr', 'en', 'nl']}); | ||
t.is(subtitles.length, 0); | ||
t.is(subtitles.length, 0); | ||
}); | ||
test('opts.langs bad args', t => { | ||
t.throws(() => yifysubtitles('tt1156398', {langs: 'lol string'})); | ||
t.throws(() => yifysubtitles('tt1156398', {langs: ['zz']})); | ||
t.throws(() => yifysubtitles('tt1156398', {langs: 'lol string'})); | ||
t.throws(() => yifysubtitles('tt1156398', {langs: ['zz']})); | ||
}); | ||
test('download zombieland subtitles in fr, en, nl + transform in vtt', async t => { | ||
const subtitles = await yifysubtitles('tt1156398', {path: downloadDir, langs: ['fr', 'en', 'nl']}); | ||
const subtitles = await yifysubtitles('tt1156398', {path: downloadDir, langs: ['fr', 'en', 'nl']}); | ||
t.is(subtitles.length, 3, 'results length should be 3'); | ||
subtitles.forEach(subtitle => t.regex(subtitle.path, /(\.vtt)$/, 'extension should be vtt')); | ||
t.is(subtitles.length, 3, 'results length should be 3'); | ||
subtitles.forEach(subtitle => t.regex(subtitle.path, /(\.vtt)$/, 'extension should be vtt')); | ||
const paths = subtitles.map(subtitle => subtitle.path); | ||
await pMap(paths, path => t.notThrows(pify(fs.access)(path), 'file should exist')); | ||
await pMap(paths, path => pify(fs.unlink)(path)); | ||
const paths = subtitles.map(subtitle => subtitle.path); | ||
await pMap(paths, path => t.notThrows(pify(fs.access)(path), 'file should exist')); | ||
await pMap(paths, path => pify(fs.unlink)(path)); | ||
}); | ||
test('download zombieland subtitles in fr, en, nl', async t => { | ||
const subtitles = await yifysubtitles('tt1156398', {path: downloadDir, langs: ['fr', 'en', 'nl'], format: 'srt'}); | ||
const subtitles = await yifysubtitles('tt1156398', {path: downloadDir, langs: ['fr', 'en', 'nl'], format: 'srt'}); | ||
t.is(subtitles.length, 3, 'results length should be 3'); | ||
subtitles.forEach(subtitle => t.regex(subtitle.path, /(\.srt)$/, 'extension should be srt')); | ||
t.is(subtitles.length, 3, 'results length should be 3'); | ||
subtitles.forEach(subtitle => t.regex(subtitle.path, /(\.srt)$/, 'extension should be srt')); | ||
const paths = subtitles.map(subtitle => subtitle.path); | ||
await pMap(paths, path => t.notThrows(pify(fs.access)(path), 'file should exist')); | ||
await pMap(paths, path => pify(fs.unlink)(path)); | ||
const paths = subtitles.map(subtitle => subtitle.path); | ||
await pMap(paths, path => t.notThrows(pify(fs.access)(path), 'file should exist')); | ||
await pMap(paths, path => pify(fs.unlink)(path)); | ||
}); | ||
test('download rango subtitles in ir', async t => { | ||
const subtitles = await yifysubtitles('tt1192628', {path: downloadDir, langs: ['ir']}); | ||
const subtitles = await yifysubtitles('tt1192628', {path: downloadDir, langs: ['ir']}); | ||
t.is(subtitles.length, 1, 'results length should be 1'); | ||
t.is(subtitles.length, 1, 'results length should be 1'); | ||
const paths = subtitles.map(subtitle => subtitle.path); | ||
await pMap(paths, path => t.notThrows(pify(fs.access)(path), 'file should exist')); | ||
await pMap(paths, path => pify(fs.unlink)(path)); | ||
const paths = subtitles.map(subtitle => subtitle.path); | ||
await pMap(paths, path => t.notThrows(pify(fs.access)(path), 'file should exist')); | ||
await pMap(paths, path => pify(fs.unlink)(path)); | ||
}); | ||
test.after.always('cleanup: delete tmp dir', async t => { | ||
await pify(fs.rmdir)(downloadDir); | ||
test.after.always('cleanup: delete tmp dir', async () => { | ||
await pify(fs.rmdir)(downloadDir); | ||
}); |
10878
218
3