Compare commits
1 Commits
v0.13.0
...
m/mkv-subt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6656b75c1b |
@@ -1,4 +1,4 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 'node'
|
- 'node'
|
||||||
install: npm install standard depcheck
|
install: npm install standard
|
||||||
|
|||||||
108
bin/check-deps.js
Executable file
108
bin/check-deps.js
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var fs = require('fs')
|
||||||
|
var cp = require('child_process')
|
||||||
|
|
||||||
|
// We can't use `builtin-modules` here since our TravisCI
|
||||||
|
// setup expects this file to run with no dependencies
|
||||||
|
var BUILT_IN_NODE_MODULES = [
|
||||||
|
'assert',
|
||||||
|
'buffer',
|
||||||
|
'child_process',
|
||||||
|
'cluster',
|
||||||
|
'console',
|
||||||
|
'constants',
|
||||||
|
'crypto',
|
||||||
|
'dgram',
|
||||||
|
'dns',
|
||||||
|
'domain',
|
||||||
|
'events',
|
||||||
|
'fs',
|
||||||
|
'http',
|
||||||
|
'https',
|
||||||
|
'module',
|
||||||
|
'net',
|
||||||
|
'os',
|
||||||
|
'path',
|
||||||
|
'process',
|
||||||
|
'punycode',
|
||||||
|
'querystring',
|
||||||
|
'readline',
|
||||||
|
'repl',
|
||||||
|
'stream',
|
||||||
|
'string_decoder',
|
||||||
|
'timers',
|
||||||
|
'tls',
|
||||||
|
'tty',
|
||||||
|
'url',
|
||||||
|
'util',
|
||||||
|
'v8',
|
||||||
|
'vm',
|
||||||
|
'zlib'
|
||||||
|
]
|
||||||
|
|
||||||
|
var BUILT_IN_ELECTRON_MODULES = [ 'electron' ]
|
||||||
|
|
||||||
|
var BUILT_IN_DEPS = [].concat(BUILT_IN_NODE_MODULES, BUILT_IN_ELECTRON_MODULES)
|
||||||
|
|
||||||
|
var EXECUTABLE_DEPS = [
|
||||||
|
'babel-cli',
|
||||||
|
'babel-plugin-syntax-jsx',
|
||||||
|
'babel-plugin-transform-es2015-destructuring',
|
||||||
|
'babel-plugin-transform-object-rest-spread',
|
||||||
|
'babel-plugin-transform-react-jsx',
|
||||||
|
'gh-release',
|
||||||
|
'nodemon',
|
||||||
|
'standard'
|
||||||
|
]
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
// Scans codebase for missing or unused dependencies. Exits with code 0 on success.
|
||||||
|
function main () {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
console.error('Sorry, check-deps only works on Mac and Linux')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var usedDeps = findUsedDeps()
|
||||||
|
var packageDeps = findPackageDeps()
|
||||||
|
|
||||||
|
var missingDeps = usedDeps.filter(
|
||||||
|
(dep) => !includes(packageDeps, dep) && !includes(BUILT_IN_DEPS, dep)
|
||||||
|
)
|
||||||
|
var unusedDeps = packageDeps.filter(
|
||||||
|
(dep) => !includes(usedDeps, dep) && !includes(EXECUTABLE_DEPS, dep)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (missingDeps.length > 0) {
|
||||||
|
console.error('Missing package dependencies: ' + missingDeps)
|
||||||
|
}
|
||||||
|
if (unusedDeps.length > 0) {
|
||||||
|
console.error('Unused package dependencies: ' + unusedDeps)
|
||||||
|
}
|
||||||
|
if (missingDeps.length + unusedDeps.length > 0) {
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds all dependencies specified in `package.json`
|
||||||
|
function findPackageDeps () {
|
||||||
|
var pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
||||||
|
|
||||||
|
var deps = Object.keys(pkg.dependencies)
|
||||||
|
var devDeps = Object.keys(pkg.devDependencies)
|
||||||
|
var optionalDeps = Object.keys(pkg.optionalDependencies)
|
||||||
|
|
||||||
|
return [].concat(deps, devDeps, optionalDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds all dependencies that used with `require()`
|
||||||
|
function findUsedDeps () {
|
||||||
|
var stdout = cp.execSync('./bin/list-deps.sh')
|
||||||
|
return stdout.toString().trim().split('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function includes (arr, elem) {
|
||||||
|
return arr.indexOf(elem) >= 0
|
||||||
|
}
|
||||||
10
bin/list-deps.sh
Executable file
10
bin/list-deps.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# This is a truly heinous hack, but it works pretty nicely.
|
||||||
|
# Find all modules we're requiring---even conditional requires.
|
||||||
|
|
||||||
|
grep "require('" src/ bin/ -R |
|
||||||
|
grep '.js:' |
|
||||||
|
sed "s/.*require('\([^'\/]*\).*/\1/" |
|
||||||
|
grep -v '^\.' |
|
||||||
|
sort |
|
||||||
|
uniq
|
||||||
@@ -7,4 +7,4 @@ npm run package -- --sign
|
|||||||
git push
|
git push
|
||||||
git push --tags
|
git push --tags
|
||||||
npm publish
|
npm publish
|
||||||
npm run gh-release
|
./node_modules/.bin/gh-release
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "webtorrent-desktop",
|
"name": "webtorrent-desktop",
|
||||||
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
|
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
|
||||||
"version": "0.13.0",
|
"version": "0.12.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "WebTorrent, LLC",
|
"name": "WebTorrent, LLC",
|
||||||
"email": "feross@webtorrent.io",
|
"email": "feross@webtorrent.io",
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"languagedetect": "^1.1.1",
|
"languagedetect": "^1.1.1",
|
||||||
"location-history": "^1.0.0",
|
"location-history": "^1.0.0",
|
||||||
"material-ui": "^0.15.4",
|
"material-ui": "^0.15.4",
|
||||||
|
"matroska-subtitles": "^2.0.0",
|
||||||
"musicmetadata": "^2.0.2",
|
"musicmetadata": "^2.0.2",
|
||||||
"network-address": "^1.1.0",
|
"network-address": "^1.1.0",
|
||||||
"parse-torrent": "^5.7.3",
|
"parse-torrent": "^5.7.3",
|
||||||
@@ -52,7 +53,6 @@
|
|||||||
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
||||||
"babel-plugin-transform-react-jsx": "^6.8.0",
|
"babel-plugin-transform-react-jsx": "^6.8.0",
|
||||||
"cross-zip": "^2.0.1",
|
"cross-zip": "^2.0.1",
|
||||||
"depcheck": "^0.6.4",
|
|
||||||
"electron-osx-sign": "^0.3.0",
|
"electron-osx-sign": "^0.3.0",
|
||||||
"electron-packager": "^7.0.0",
|
"electron-packager": "^7.0.0",
|
||||||
"electron-winstaller": "^2.3.0",
|
"electron-winstaller": "^2.3.0",
|
||||||
@@ -98,8 +98,7 @@
|
|||||||
"package": "node ./bin/package.js",
|
"package": "node ./bin/package.js",
|
||||||
"prepublish": "npm run build",
|
"prepublish": "npm run build",
|
||||||
"start": "npm run build && electron .",
|
"start": "npm run build && electron .",
|
||||||
"test": "standard && depcheck --ignores=babel-cli,nodemon,gh-release --ignore-dirs=build,dist",
|
"test": "standard && node ./bin/check-deps.js",
|
||||||
"gh-release": "gh-release",
|
|
||||||
"update-authors": "./bin/update-authors.sh",
|
"update-authors": "./bin/update-authors.sh",
|
||||||
"watch": "nodemon --exec 'npm run start' --ext js,pug,css"
|
"watch": "nodemon --exec 'npm run start' --ext js,pug,css"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,14 @@ class ShowMore extends React.Component {
|
|||||||
? this.props.hideLabel
|
? this.props.hideLabel
|
||||||
: this.props.showLabel
|
: this.props.showLabel
|
||||||
return (
|
return (
|
||||||
<div style={this.props.style}>
|
<div
|
||||||
|
style={this.props.style}
|
||||||
|
>
|
||||||
{this.state.expanded ? this.props.children : null}
|
{this.state.expanded ? this.props.children : null}
|
||||||
<FlatButton
|
<FlatButton
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
label={label} />
|
label={label}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ const electron = require('electron')
|
|||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const parallel = require('run-parallel')
|
const parallel = require('run-parallel')
|
||||||
|
const zeroFill = require('zero-fill')
|
||||||
|
|
||||||
const remote = electron.remote
|
const remote = electron.remote
|
||||||
|
const ipcRenderer = electron.ipcRenderer
|
||||||
|
|
||||||
const {dispatch} = require('../lib/dispatcher')
|
const {dispatch} = require('../lib/dispatcher')
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ module.exports = class SubtitlesController {
|
|||||||
torrentSummary.progress.files.forEach((fp, ix) => {
|
torrentSummary.progress.files.forEach((fp, ix) => {
|
||||||
if (fp.numPieces !== fp.numPiecesPresent) return // ignore incomplete files
|
if (fp.numPieces !== fp.numPiecesPresent) return // ignore incomplete files
|
||||||
var file = torrentSummary.files[ix]
|
var file = torrentSummary.files[ix]
|
||||||
|
if (this.state.playing.fileIndex === ix) return this.checkForEmbeddedMKVSubtitles(file)
|
||||||
if (!this.isSubtitle(file.name)) return
|
if (!this.isSubtitle(file.name)) return
|
||||||
var filePath = path.join(torrentSummary.path, file.path)
|
var filePath = path.join(torrentSummary.path, file.path)
|
||||||
this.addSubtitles([filePath], false)
|
this.addSubtitles([filePath], false)
|
||||||
@@ -85,12 +88,31 @@ module.exports = class SubtitlesController {
|
|||||||
var ext = path.extname(name).toLowerCase()
|
var ext = path.extname(name).toLowerCase()
|
||||||
return ext === '.srt' || ext === '.vtt'
|
return ext === '.srt' || ext === '.vtt'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkForEmbeddedMKVSubtitles (file) {
|
||||||
|
var playing = this.state.playing
|
||||||
|
// var playingFile = this.state.getPlayingFileSummary()
|
||||||
|
// var playingPath = path.join(torrentSummary.path, playingFile.path)
|
||||||
|
|
||||||
|
if (path.extname(file.name).toLowerCase() === '.mkv') {
|
||||||
|
ipcRenderer.send('wt-get-mkv-subtitles', playing.infoHash, playing.fileIndex)
|
||||||
|
|
||||||
|
ipcRenderer.once('wt-mkv-subtitles', function (e, tracks) {
|
||||||
|
tracks.forEach(function (trackEntry) {
|
||||||
|
var track = loadEmbeddedSubtitle(trackEntry)
|
||||||
|
console.log('loaded emb subs', track)
|
||||||
|
playing.subtitles.tracks.push(track)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (tracks.length > 0) relabelSubtitles(playing.subtitles)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSubtitle (file, cb) {
|
function loadSubtitle (file, cb) {
|
||||||
// Lazy load to keep startup fast
|
// Lazy load to keep startup fast
|
||||||
var concat = require('simple-concat')
|
var concat = require('simple-concat')
|
||||||
var LanguageDetect = require('languagedetect')
|
|
||||||
var srtToVtt = require('srt-to-vtt')
|
var srtToVtt = require('srt-to-vtt')
|
||||||
|
|
||||||
// Read the .SRT or .VTT file, parse it, add subtitle track
|
// Read the .SRT or .VTT file, parse it, add subtitle track
|
||||||
@@ -101,11 +123,7 @@ function loadSubtitle (file, cb) {
|
|||||||
concat(vttStream, function (err, buf) {
|
concat(vttStream, function (err, buf) {
|
||||||
if (err) return dispatch('error', 'Can\'t parse subtitles file.')
|
if (err) return dispatch('error', 'Can\'t parse subtitles file.')
|
||||||
|
|
||||||
// Detect what language the subtitles are in
|
var langDetected = detectVTTLanguage(buf)
|
||||||
var vttContents = buf.toString().replace(/(.*-->.*)/g, '')
|
|
||||||
var langDetected = (new LanguageDetect()).detect(vttContents, 2)
|
|
||||||
langDetected = langDetected.length ? langDetected[0][0] : 'subtitle'
|
|
||||||
langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1)
|
|
||||||
|
|
||||||
var track = {
|
var track = {
|
||||||
buffer: 'data:text/vtt;base64,' + buf.toString('base64'),
|
buffer: 'data:text/vtt;base64,' + buf.toString('base64'),
|
||||||
@@ -137,3 +155,49 @@ function relabelSubtitles (subtitles) {
|
|||||||
track.label = counts[lang] > 1 ? (lang + ' ' + counts[lang]) : lang
|
track.label = counts[lang] > 1 ? (lang + ' ' + counts[lang]) : lang
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function detectVTTLanguage (buffer) {
|
||||||
|
var LanguageDetect = require('languagedetect')
|
||||||
|
|
||||||
|
// Detect what language the subtitles are in
|
||||||
|
var vttContents = buffer.toString().replace(/(.*-->.*)/g, '') // remove numbers?
|
||||||
|
var langDetected = (new LanguageDetect()).detect(vttContents, 2)
|
||||||
|
langDetected = langDetected.length ? langDetected[0][0] : 'subtitle'
|
||||||
|
langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1)
|
||||||
|
|
||||||
|
return langDetected
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEmbeddedSubtitle (trackEntry) {
|
||||||
|
// convert to .vtt format
|
||||||
|
var vtt = 'WEBVTT FILE\r\n\r\n'
|
||||||
|
trackEntry.subtitles.forEach(function (sub, i) {
|
||||||
|
vtt += `${i + 1}\r\n`
|
||||||
|
vtt += `${msToTime(sub.time)} --> ${msToTime(sub.time + sub.duration)}\r\n`
|
||||||
|
vtt += `${sub.text}\r\n\r\n`
|
||||||
|
})
|
||||||
|
|
||||||
|
function msToTime (s) {
|
||||||
|
var ms = s % 1000
|
||||||
|
s = (s - ms) / 1000
|
||||||
|
var secs = s % 60
|
||||||
|
s = (s - secs) / 60
|
||||||
|
var mins = s % 60
|
||||||
|
var hrs = (s - mins) / 60
|
||||||
|
|
||||||
|
var z = zeroFill
|
||||||
|
return z(2, hrs) + ':' + z(2, mins) + ':' + z(2, secs) + '.' + z(3, ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = new Buffer(vtt)
|
||||||
|
var langDetected = detectVTTLanguage(buf)
|
||||||
|
|
||||||
|
var track = {
|
||||||
|
buffer: 'data:text/vtt;base64,' + buf.toString('base64'),
|
||||||
|
language: langDetected,
|
||||||
|
label: langDetected,
|
||||||
|
filePath: null
|
||||||
|
}
|
||||||
|
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,9 +126,7 @@ function logUncaughtError (procName, e) {
|
|||||||
|
|
||||||
var message
|
var message
|
||||||
var stack = ''
|
var stack = ''
|
||||||
if (e == null) {
|
if (e.message) {
|
||||||
message = 'Unexpected undefined error'
|
|
||||||
} else if (e.message) {
|
|
||||||
// err is either an Error or a plain object {message, stack}
|
// err is either an Error or a plain object {message, stack}
|
||||||
message = e.message
|
message = e.message
|
||||||
stack = e.stack
|
stack = e.stack
|
||||||
@@ -151,13 +149,11 @@ function logUncaughtError (procName, e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof stack !== 'string') stack = 'Unexpected stack: ' + stack
|
|
||||||
if (typeof message !== 'string') message = 'Unexpected message: ' + message
|
|
||||||
|
|
||||||
// Remove the first part of each file path in the stack trace.
|
// Remove the first part of each file path in the stack trace.
|
||||||
// - Privacy: remove personal info like C:\Users\<full name>
|
// - Privacy: remove personal info like C:\Users\<full name>
|
||||||
// - Aggregation: this lets us find which stacktraces occur often
|
// - Aggregation: this lets us find which stacktraces occur often
|
||||||
stack = stack.replace(/\(.*app.asar/g, '(...')
|
if (stack && typeof stack === 'string') stack = stack.replace(/\(.*app.asar/g, '(...')
|
||||||
|
else if (stack) stack = 'Unexpected stack: ' + stack
|
||||||
|
|
||||||
// We need to POST the telemetry object, make sure it stays < 100kb
|
// We need to POST the telemetry object, make sure it stays < 100kb
|
||||||
if (telemetry.uncaughtErrors.length > 20) return
|
if (telemetry.uncaughtErrors.length > 20) return
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ const {dispatch, dispatcher} = require('../lib/dispatcher')
|
|||||||
const FlatButton = require('material-ui/FlatButton').default
|
const FlatButton = require('material-ui/FlatButton').default
|
||||||
const RaisedButton = require('material-ui/RaisedButton').default
|
const RaisedButton = require('material-ui/RaisedButton').default
|
||||||
const TextField = require('material-ui/TextField').default
|
const TextField = require('material-ui/TextField').default
|
||||||
const Checkbox = require('material-ui/Checkbox').default
|
|
||||||
|
|
||||||
const CreateTorrentErrorPage = require('../components/create-torrent-error-page')
|
const CreateTorrentErrorPage = require('../components/create-torrent-error-page')
|
||||||
const Heading = require('../components/Heading')
|
const Heading = require('../components/Heading')
|
||||||
@@ -21,20 +20,27 @@ class CreateTorrentPage extends React.Component {
|
|||||||
var state = this.props.state
|
var state = this.props.state
|
||||||
var info = state.location.current()
|
var info = state.location.current()
|
||||||
|
|
||||||
|
// Preprocess: exclude .DS_Store and other dotfiles
|
||||||
|
var files = info.files
|
||||||
|
.filter((f) => !f.name.startsWith('.'))
|
||||||
|
.map((f) => ({name: f.name, path: f.path, size: f.size}))
|
||||||
|
if (files.length === 0) return (<CreateTorrentErrorPage state={state} />)
|
||||||
|
|
||||||
// First, extract the base folder that the files are all in
|
// First, extract the base folder that the files are all in
|
||||||
var pathPrefix = info.folderPath
|
var pathPrefix = info.folderPath
|
||||||
if (!pathPrefix) {
|
if (!pathPrefix) {
|
||||||
pathPrefix = info.files.map((x) => x.path).reduce(findCommonPrefix)
|
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
|
||||||
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
|
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
|
||||||
pathPrefix = path.dirname(pathPrefix)
|
pathPrefix = path.dirname(pathPrefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then, exclude .DS_Store and other dotfiles
|
// Sanity check: show the number of files and total size
|
||||||
var files = info.files
|
var numFiles = files.length
|
||||||
.filter((f) => !containsDots(f.path, pathPrefix))
|
var totalBytes = files
|
||||||
.map((f) => ({name: f.name, path: f.path, size: f.size}))
|
.map((f) => f.size)
|
||||||
if (files.length === 0) return (<CreateTorrentErrorPage state={state} />)
|
.reduce((a, b) => a + b, 0)
|
||||||
|
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
|
||||||
|
|
||||||
// Then, use the name of the base folder (or sole file, for a single file torrent)
|
// Then, use the name of the base folder (or sole file, for a single file torrent)
|
||||||
// as the default name. Show all files relative to the base folder.
|
// as the default name. Show all files relative to the base folder.
|
||||||
@@ -48,70 +54,90 @@ class CreateTorrentPage extends React.Component {
|
|||||||
defaultName = path.basename(pathPrefix)
|
defaultName = path.basename(pathPrefix)
|
||||||
basePath = path.dirname(pathPrefix)
|
basePath = path.dirname(pathPrefix)
|
||||||
}
|
}
|
||||||
|
var maxFileElems = 100
|
||||||
// Default trackers
|
var fileElems = files.slice(0, maxFileElems).map(function (file, i) {
|
||||||
|
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
|
||||||
|
return (<div key={i}>{relativePath}</div>)
|
||||||
|
})
|
||||||
|
if (files.length > maxFileElems) {
|
||||||
|
fileElems.push(<div key='more'>+ {maxFileElems - files.length} more</div>)
|
||||||
|
}
|
||||||
var trackers = createTorrent.announceList.join('\n')
|
var trackers = createTorrent.announceList.join('\n')
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
comment: '',
|
|
||||||
isPrivate: false,
|
|
||||||
pathPrefix,
|
|
||||||
basePath,
|
basePath,
|
||||||
defaultName,
|
defaultName,
|
||||||
files,
|
fileElems,
|
||||||
|
torrentInfo,
|
||||||
trackers
|
trackers
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create React event handlers only once
|
|
||||||
this.setIsPrivate = (_, isPrivate) => this.setState({isPrivate})
|
|
||||||
this.setComment = (_, comment) => this.setState({comment})
|
|
||||||
this.setTrackers = (_, trackers) => this.setState({trackers})
|
|
||||||
this.handleSubmit = () => this.handleSubmit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit () {
|
handleSubmit () {
|
||||||
var announceList = this.state.trackers
|
var announceList = document.querySelector('.torrent-trackers').value
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter((s) => s !== '')
|
.filter((s) => s !== '')
|
||||||
|
var isPrivate = document.querySelector('.torrent-is-private').checked
|
||||||
|
var comment = document.querySelector('.torrent-comment').value.trim()
|
||||||
var options = {
|
var options = {
|
||||||
// We can't let the user choose their own name if we want WebTorrent
|
// We can't let the user choose their own name if we want WebTorrent
|
||||||
// to use the files in place rather than creating a new folder.
|
// to use the files in place rather than creating a new folder.
|
||||||
|
// If we ever want to add support for that:
|
||||||
|
// name: document.querySelector('.torrent-name').value
|
||||||
name: this.state.defaultName,
|
name: this.state.defaultName,
|
||||||
path: this.state.basePath,
|
path: this.state.basePath,
|
||||||
files: this.state.files,
|
files: this.state.files,
|
||||||
announce: announceList,
|
announce: announceList,
|
||||||
private: this.state.isPrivate,
|
private: isPrivate,
|
||||||
comment: this.state.comment.trim()
|
comment: comment
|
||||||
}
|
}
|
||||||
dispatch('createTorrent', options)
|
dispatch('createTorrent', options)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
var files = this.state.files
|
|
||||||
|
|
||||||
// Sanity check: show the number of files and total size
|
|
||||||
var numFiles = files.length
|
|
||||||
var totalBytes = files
|
|
||||||
.map((f) => f.size)
|
|
||||||
.reduce((a, b) => a + b, 0)
|
|
||||||
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='create-torrent'>
|
<div className='create-torrent'>
|
||||||
<Heading level={1}>Create torrent {this.state.defaultName}</Heading>
|
<Heading level={1}>
|
||||||
<div className='torrent-info'>{torrentInfo}</div>
|
Create torrent "{this.state.defaultName}"
|
||||||
|
</Heading>
|
||||||
|
<div className='torrent-info'>
|
||||||
|
{this.state.torrentInfo}
|
||||||
|
</div>
|
||||||
<div className='torrent-attribute'>
|
<div className='torrent-attribute'>
|
||||||
<label>Path:</label>
|
<label>Path:</label>
|
||||||
<div>{this.state.pathPrefix}</div>
|
<div className='torrent-attribute'>{this.state.pathPrefix}</div>
|
||||||
</div>
|
</div>
|
||||||
<ShowMore
|
<ShowMore
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 10
|
marginBottom: 10
|
||||||
}}
|
}}
|
||||||
hideLabel='Hide advanced settings...'
|
hideLabel='Hide advanced settings...'
|
||||||
showLabel='Show advanced settings...' >
|
showLabel='Show advanced settings...'
|
||||||
{this.renderAdvanced()}
|
>
|
||||||
|
<div key='advanced' className='create-torrent-advanced'>
|
||||||
|
<div key='private' className='torrent-attribute'>
|
||||||
|
<label>Private:</label>
|
||||||
|
<input type='checkbox' className='torrent-is-private' value='torrent-is-private' />
|
||||||
|
</div>
|
||||||
|
<Heading level={2}>Trackers:</Heading>
|
||||||
|
<TextField
|
||||||
|
className='torrent-trackers'
|
||||||
|
hintText='Tracker'
|
||||||
|
multiLine
|
||||||
|
rows={2}
|
||||||
|
rowsMax={10}
|
||||||
|
defaultValue={this.state.trackers}
|
||||||
|
/>
|
||||||
|
<div key='comment' className='torrent-attribute'>
|
||||||
|
<label>Comment:</label>
|
||||||
|
<textarea className='torrent-attribute torrent-comment' />
|
||||||
|
</div>
|
||||||
|
<div key='files' className='torrent-attribute'>
|
||||||
|
<label>Files:</label>
|
||||||
|
<div>{this.state.fileElems}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ShowMore>
|
</ShowMore>
|
||||||
<div className='float-right'>
|
<div className='float-right'>
|
||||||
<FlatButton
|
<FlatButton
|
||||||
@@ -130,65 +156,6 @@ class CreateTorrentPage extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAdvanced () {
|
|
||||||
// Create file list
|
|
||||||
var maxFileElems = 100
|
|
||||||
var files = this.state.files
|
|
||||||
var fileElems = files.slice(0, maxFileElems).map((file, i) => {
|
|
||||||
var relativePath = path.relative(this.state.pathPrefix, file.path)
|
|
||||||
return (<div key={i}>{relativePath}</div>)
|
|
||||||
})
|
|
||||||
if (files.length > maxFileElems) {
|
|
||||||
fileElems.push(<div key='more'>+ {files.length - maxFileElems} more</div>)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align the text fields
|
|
||||||
var textFieldStyle = { width: '' }
|
|
||||||
var textareaStyle = { margin: 0 }
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key='advanced' className='create-torrent-advanced'>
|
|
||||||
<div key='private' className='torrent-attribute'>
|
|
||||||
<label>Private:</label>
|
|
||||||
<Checkbox
|
|
||||||
className='torrent-is-private'
|
|
||||||
style={{display: ''}}
|
|
||||||
value={this.state.isPrivate}
|
|
||||||
onChange={this.setIsPrivate} />
|
|
||||||
</div>
|
|
||||||
<div key='trackers' className='torrent-attribute'>
|
|
||||||
<label>Trackers:</label>
|
|
||||||
<TextField
|
|
||||||
className='torrent-trackers'
|
|
||||||
style={textFieldStyle}
|
|
||||||
textareaStyle={textareaStyle}
|
|
||||||
multiLine
|
|
||||||
rows={2}
|
|
||||||
rowsMax={10}
|
|
||||||
value={this.state.trackers}
|
|
||||||
onChange={this.setTrackers} />
|
|
||||||
</div>
|
|
||||||
<div key='comment' className='torrent-attribute'>
|
|
||||||
<label>Comment:</label>
|
|
||||||
<TextField
|
|
||||||
className='torrent-comment'
|
|
||||||
style={textFieldStyle}
|
|
||||||
textareaStyle={textareaStyle}
|
|
||||||
hintText='Optionally describe your torrent...'
|
|
||||||
multiLine
|
|
||||||
rows={2}
|
|
||||||
rowsMax={10}
|
|
||||||
value={this.state.comment}
|
|
||||||
onChange={this.setComment} />
|
|
||||||
</div>
|
|
||||||
<div key='files' className='torrent-attribute'>
|
|
||||||
<label>Files:</label>
|
|
||||||
<div>{fileElems}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds the longest common prefix
|
// Finds the longest common prefix
|
||||||
@@ -201,10 +168,4 @@ function findCommonPrefix (a, b) {
|
|||||||
return a.substring(0, i)
|
return a.substring(0, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
function containsDots (path, pathPrefix) {
|
|
||||||
var suffix = path.substring(pathPrefix.length).replace(/\\/g, '/')
|
|
||||||
console.log('SUFFIX ' + suffix)
|
|
||||||
return ('/' + suffix).includes('/.')
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CreateTorrentPage
|
module.exports = CreateTorrentPage
|
||||||
|
|||||||
@@ -96,14 +96,6 @@ class PreferencesPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setDefaultAppButton () {
|
setDefaultAppButton () {
|
||||||
var isFileHandler = this.props.state.unsaved.prefs.isFileHandler
|
|
||||||
if (isFileHandler) {
|
|
||||||
return (
|
|
||||||
<Preference>
|
|
||||||
<p>WebTorrent is your default torrent app. Hooray!</p>
|
|
||||||
</Preference>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Preference>
|
<Preference>
|
||||||
<p>WebTorrent is not currently the default torrent app.</p>
|
<p>WebTorrent is not currently the default torrent app.</p>
|
||||||
@@ -117,17 +109,20 @@ class PreferencesPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSetDefaultApp () {
|
handleSetDefaultApp () {
|
||||||
dispatch('updatePreferences', 'isFileHandler', true)
|
window.alert('TODO')
|
||||||
|
// var isFileHandler = state.unsaved.prefs.isFileHandler
|
||||||
|
// dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
var style = {
|
|
||||||
color: colors.grey400,
|
|
||||||
marginLeft: 25,
|
|
||||||
marginRight: 25
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div style={style} >
|
<div
|
||||||
|
style={{
|
||||||
|
color: colors.grey400,
|
||||||
|
marginLeft: 25,
|
||||||
|
marginRight: 25
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PreferencesSection title='Downloads'>
|
<PreferencesSection title='Downloads'>
|
||||||
{this.downloadPathSelector()}
|
{this.downloadPathSelector()}
|
||||||
</PreferencesSection>
|
</PreferencesSection>
|
||||||
@@ -151,12 +146,13 @@ class PreferencesSection extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
var style = {
|
|
||||||
marginBottom: 25,
|
|
||||||
marginTop: 25
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 25,
|
||||||
|
marginTop: 25
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Heading level={2}>{this.props.title}</Heading>
|
<Heading level={2}>{this.props.title}</Heading>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
@@ -166,8 +162,15 @@ class PreferencesSection extends React.Component {
|
|||||||
|
|
||||||
class Preference extends React.Component {
|
class Preference extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
var style = { marginBottom: 10 }
|
return (
|
||||||
return (<div style={style}>{this.props.children}</div>)
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ function init () {
|
|||||||
generateTorrentPoster(torrentKey))
|
generateTorrentPoster(torrentKey))
|
||||||
ipc.on('wt-get-audio-metadata', (e, infoHash, index) =>
|
ipc.on('wt-get-audio-metadata', (e, infoHash, index) =>
|
||||||
getAudioMetadata(infoHash, index))
|
getAudioMetadata(infoHash, index))
|
||||||
|
ipc.on('wt-get-mkv-subtitles', (e, infoHash, index) =>
|
||||||
|
getMKVSubtitles(infoHash, index))
|
||||||
ipc.on('wt-start-server', (e, infoHash, index) =>
|
ipc.on('wt-start-server', (e, infoHash, index) =>
|
||||||
startServer(infoHash, index))
|
startServer(infoHash, index))
|
||||||
ipc.on('wt-stop-server', (e) =>
|
ipc.on('wt-stop-server', (e) =>
|
||||||
@@ -342,6 +344,32 @@ function getAudioMetadata (infoHash, index) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMKVSubtitles (infoHash, index) {
|
||||||
|
var torrent = client.get(infoHash)
|
||||||
|
var file = torrent.files[index]
|
||||||
|
|
||||||
|
var MatroskaSubtitles = require('matroska-subtitles')
|
||||||
|
var subtitleParser = new MatroskaSubtitles()
|
||||||
|
|
||||||
|
var textTracks = new Map()
|
||||||
|
|
||||||
|
subtitleParser.once('tracks', function (tracks) {
|
||||||
|
tracks.forEach(function (track) {
|
||||||
|
textTracks.set(track.number, { track: track, subtitles: [] })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
subtitleParser.on('subtitle', function (subtitle, trackNumber) {
|
||||||
|
textTracks.get(trackNumber).subtitles.push(subtitle)
|
||||||
|
})
|
||||||
|
|
||||||
|
subtitleParser.on('finish', function () {
|
||||||
|
ipc.send('wt-mkv-subtitles', Array.from(textTracks.values()))
|
||||||
|
})
|
||||||
|
|
||||||
|
file.createReadStream().pipe(subtitleParser)
|
||||||
|
}
|
||||||
|
|
||||||
function selectFiles (torrentOrInfoHash, selections) {
|
function selectFiles (torrentOrInfoHash, selections) {
|
||||||
// Get the torrent object
|
// Get the torrent object
|
||||||
var torrent
|
var torrent
|
||||||
|
|||||||
@@ -272,12 +272,10 @@ table {
|
|||||||
.create-torrent {
|
.create-torrent {
|
||||||
padding: 10px 25px;
|
padding: 10px 25px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font: 16px/24px BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent .torrent-attribute {
|
.create-torrent .torrent-attribute {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin: 8px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent .torrent-attribute>* {
|
.create-torrent .torrent-attribute>* {
|
||||||
@@ -285,12 +283,13 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent .torrent-attribute label {
|
.create-torrent .torrent-attribute label {
|
||||||
width: 100px;
|
width: 60px;
|
||||||
|
margin-right: 10px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent .torrent-attribute>div {
|
.create-torrent .torrent-attribute>div {
|
||||||
width: calc(100% - 100px);
|
width: calc(100% - 90px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent .torrent-attribute div {
|
.create-torrent .torrent-attribute div {
|
||||||
@@ -299,6 +298,18 @@ table {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.create-torrent .torrent-attribute textarea {
|
||||||
|
width: calc(100% - 80px);
|
||||||
|
height: 80px;
|
||||||
|
color: #eee;
|
||||||
|
background-color: transparent;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BUTTONS
|
* BUTTONS
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user