diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ea7dc9183a..380ac8175e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -17,9 +17,9 @@ jobs:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
with:
- fetch-depth: 1
+ fetch-depth: 0
- name: Prepare NSIS
if: matrix.os == 'windows-latest'
diff --git a/.jshintrc b/.jshintrc
index 438cc7edc8..efa8eff54e 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -21,13 +21,12 @@
"quotmark" : "single",
"trailing" : true,
"sub" : true,
- "trailing" : true,
"undef" : true,
"laxbreak" : true,
"loopfunc" : true,
"indent" : 4,
"newcap" : false,
- "esversion" : 6,
+ "esversion" : 8,
// Globals
"globals": {
@@ -56,7 +55,7 @@
"inherits": true,
"Q": true,
"os": true,
- "moment": true,
+ "dayjs": true,
"crypt": true,
"semver": true,
"fs": true,
@@ -75,7 +74,6 @@
"url": true,
"tls": true,
"http": true,
- "request": true,
"querystring": true,
"URI": true,
"child": true,
@@ -91,7 +89,6 @@
"Backbone": true,
"Marionette": true,
"Mousetrap": true,
- "_": true,
"request": true,
"videojs": true,
"vjs": true,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd5d5e9385..7634dcc18c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,32 @@
-## 0.4.5 - The next wave - 21 June 2021
+## 0.4.6 - The Good Variant - 11 October 2021
+
+New Features:
+- Add Localization support
+- Add multiple audio language support
+- Add maximum Download/Upload speed options
+- Add ability to minimize the native media player
+- Add Source, Release Info and Parental Guide links for content where data exists
+- Add a Magnet Link button in the loading screen
+- Add a Rebuild bookmarks database function/button in the settings
+- Add support for fetching the Genres list from the API
+- Update WebTorrent to 1.5.5 also adding PE/MSE support
+
+Bug Fixes:
+- Fix issue with peers not being resolved when restarting canceled stream/download
+- Fix wrong file selection on some instances where torrents contain multiple video files
+- Fix issue where the subtitles and cover image weren't being downloaded when using the Download function
+- Fix file/directory selection on Windows
+- Remove non-working TVShow Time support since their API service has been terminated
+
+Other:
+- Optimize app closing time
+- Settings page UI changes/updates
+- Better unreachable API error message displaying all APIs tried
+- Update torrent trackers
+- Update various modules/dependencies
+- Various other small fixes and optimizations
+
+## 0.4.5 - The Next Wave - 21 June 2021
New Features:
- Update NWJS to 0.44.5 (https://github.com/nwjs/nw.js/blob/nw44/CHANGELOG.md)
diff --git a/README.md b/README.md
index 66902c6f35..112ceba3e6 100644
--- a/README.md
+++ b/README.md
@@ -36,19 +36,17 @@ Download and install:
### MacOS:
-Easily install Popcorn Time via _[Homebrew](https://brew.sh) ([Cask](https://github.com/Homebrew/homebrew-cask#homebrew-cask)):_
- * **Latest release**:
- `brew tap popcorn-official/popcorn-desktop https://github.com/popcorn-official/popcorn-desktop.git`
- `brew install --cask popcorn-time`
- * Or **latest dev build (for testers)**:
- `brew tap popcorn-official/popcorn-desktop https://github.com/popcorn-official/popcorn-desktop.git`
- `brew install --cask popcorn-time-beta`
-
+Easily install Popcorn Time via _[Homebrew](https://brew.sh) ([Cask](https://docs.brew.sh/Cask-Cookbook)):_
+ ~~~ rb
+ brew tap popcorn-official/popcorn-desktop https://github.com/popcorn-official/popcorn-desktop.git
+ brew install --cask popcorn-time
+ ~~~
+
Also, if you keep a [_Brewfile_](https://github.com/Homebrew/homebrew-bundle#usage), you can add something like this:
~~~ rb
- repo = 'popcorn-official/popcorn-desktop'
+ repo = "popcorn-official/popcorn-desktop"
tap repo, "https://github.com/#{repo}.git"
- cask 'popcorn-time'
+ cask "popcorn-time"
~~~
Update from _zip_ file:
@@ -68,17 +66,17 @@ Download and install:
Via archive and command line (tested on ubuntu 18.04 and 20.04):
1. Download Popcorn Time archive:
* For the **latest release**:
- `wget -c https://get.popcorntime.app/repo/build/Popcorn-Time-0.4.5-linux64.zip`
+ `wget -c https://get.popcorntime.app/repo/build/Popcorn-Time-0.4.6-linux64.zip`
_if eventually you get issue with popcorntime.app website you can try to download from the github repo
- `wget -c https://github.com/popcorn-official/popcorn-desktop/releases/download/v0.4.5/Popcorn-Time-0.4.5-linux64.zip`_
+ `wget -c https://github.com/popcorn-official/popcorn-desktop/releases/download/v0.4.6/Popcorn-Time-0.4.6-linux64.zip`_
* Or for the **latest dev build (for testers)**:
- `wget -c https://ci.popcorntime.app/job/Popcorn-Time-Desktop/lastSuccessfulBuild/artifact/build/Popcorn-Time-0.4.5_linux64.zip -O Popcorn-Time-0.4.5-linux64.zip`
+ `wget -c https://ci.popcorntime.app/job/Popcorn-Time-Desktop/lastSuccessfulBuild/artifact/build/Popcorn-Time-0.4.6_linux64.zip -O Popcorn-Time-0.4.6-linux64.zip`
2. Create popcorn-time folder in /opt/:
`sudo mkdir /opt/popcorn-time`
3. Install unzip && dependencies (they should not be always required but some users needed them to make Popcorn Time working):
`sudo apt update && sudo apt install unzip libcanberra-gtk-module libgconf-2-4 libatomic1`
4. Extract the zip in /opt/popcorn-time:
- `sudo unzip Popcorn-Time-0.4.5-linux64.zip -d /opt/popcorn-time`
+ `sudo unzip Popcorn-Time-0.4.6-linux64.zip -d /opt/popcorn-time`
5. Create symlink of Popcorn-Time in /usr/bin:
`sudo ln -sf /opt/popcorn-time/Popcorn-Time /usr/bin/popcorn-time`
6. Create .desktop file (so the launcher):
diff --git a/casks/popcorn-time-beta.rb b/casks/popcorn-time-beta.rb
deleted file mode 100644
index b806d4f516..0000000000
--- a/casks/popcorn-time-beta.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-cask "popcorn-time-beta" do
- version :latest
- sha256 :no_check
-
- ci = "https://ci.popcorntime.app/job/Popcorn-Time-Desktop"
- url "#{ci}/lastSuccessfulBuild/artifact/build/Popcorn-Time-0.4.5_osx64.zip"
- appcast ci, configuration: "Latest successful build"
- name "Popcorn Time"
- desc "Watch movies and TV shows instantly"
- homepage "https://popcorntime.app/"
-
- auto_updates true
- conflicts_with cask: "popcorn-time"
-
- app "Popcorn-Time.app"
-
- bundle_id = "com.nw-builder.popcorn-time"
- uninstall quit: bundle_id
-
- zap trash: [
- "~/Library/Preferences/#{bundle_id}.plist",
- "~/Library/Application Support/Popcorn-Time",
- "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/#{bundle_id}.sfl*",
- "~/Library/Application Support/configstore/popcorn-time.json",
- "~/Library/Saved Application State/#{bundle_id}.savedState",
- "~/Library/Caches/Popcorn-Time",
- ]
-end
diff --git a/casks/popcorn-time.rb b/casks/popcorn-time.rb
index 757e2e2693..5dea12a31f 100644
--- a/casks/popcorn-time.rb
+++ b/casks/popcorn-time.rb
@@ -1,27 +1,54 @@
cask "popcorn-time" do
- version "0.4.5"
- sha256 "045dbe37d06e24ed7129dddd922648caaba712dee24685fb3cb1f4782f03ead5"
+ version "0.4.6"
+ sha256 "cacf8ed13b427bceb481ba88ff97ff297f7e9e0487f1411f8d20ff87dd674ddb"
- url "https://get.popcorntime.app/build/Popcorn-Time-#{version}.pkg"
- appcast "https://github.com/popcorn-official/popcorn-desktop/releases.atom"
- name "Popcorn Time"
- desc "Watch movies and TV shows instantly"
- homepage "https://popcorntime.app/"
+ server = "popcorn-ru.tk"
+ homepage = "http://#{server}"
+ zip = "Popcorn-Time-#{version}-Mac.zip"
+
+ url "#{homepage}/build/#{zip}"
+ name token.titlecase
+ desc "BitTorrent client that includes an integrated media player"
+ homepage homepage
+
+ livecheck do
+ url "#{homepage}/build"
+ strategy :page_match
+ regex Regexp.new zip.sub version, "([0-9]+(?:\\.[0-9]+)+)"
+ end
auto_updates true
- conflicts_with cask: "popcorn-time-beta"
- pkg "Popcorn-Time-#{version}.pkg"
+ app "Popcorn-Time.app"
+
+ app_support = "#{Dir.home}/Library/Application Support"
+
+ postflight do
+ require "securerandom"
+
+ db = "#{app_support}/Popcorn-Time/Default/data/settings.db"
+
+ %w[Movies Series].each do |medium|
+ setting = {
+ key: "custom#{medium}Server",
+ value: "https://#{server}/",
+ _id: SecureRandom.alphanumeric,
+ }
+ settings = File.read(db).lines
+
+ next if settings.grep(/#{setting[:key]}/).any?
+
+ `echo '#{setting.to_json}' >> '#{db}'`
+ end
+ end
- bundle_id = "com.nw-builder.popcorn-time"
- uninstall quit: bundle_id,
- delete: "#{appdir}/Popcorn-Time.app"
+ uninstall quit: bundle_id = "com.nw-builder.popcorn-time"
zap trash: [
+ "#{app_support}/Popcorn-Time",
"~/Library/Preferences/#{bundle_id}.plist",
- "~/Library/Application Support/Popcorn-Time",
- "~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/#{bundle_id}.sfl*",
- "~/Library/Application Support/configstore/popcorn-time.json",
+ "#{app_support}/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/#{bundle_id}.sfl*",
+ "#{app_support}/configstore/popcorn-time.json",
"~/Library/Saved Application State/#{bundle_id}.savedState",
"~/Library/Caches/Popcorn-Time",
]
diff --git a/dist/linux/exec_basefile.sh b/dist/linux/exec_basefile.sh
index 2e61e0006c..5d709eb37f 100644
--- a/dist/linux/exec_basefile.sh
+++ b/dist/linux/exec_basefile.sh
@@ -55,7 +55,7 @@ current="1: Copy files"
echo "
- Copying files to ~/.Butter"
mkdir -p "$HOME/.Butter"
-cp -r locales node_modules src .git.json CHANGELOG.md icudtl.dat libffmpegsumo.so LICENSE.txt nw.pak package.nw package.json Butter README.md "$HOME/.Butter" &> /dev/null && error=0 || error=1
+cp -r locales node_modules src git.json CHANGELOG.md icudtl.dat libffmpegsumo.so LICENSE.txt nw.pak package.nw package.json Butter README.md "$HOME/.Butter" &> /dev/null && error=0 || error=1
#move icon
mkdir -p "$HOME/.local/share/icons"
diff --git a/dist/windows/updater_makensis.nsi b/dist/windows/updater_makensis.nsi
index 455cc9e0ce..9bf6dafd2f 100644
--- a/dist/windows/updater_makensis.nsi
+++ b/dist/windows/updater_makensis.nsi
@@ -348,7 +348,7 @@ Section
File "..\..\package.json"
File "..\..\build\${APP_NAME}\${ARCH}\${APP_LAUNCHER}"
File "..\..\CHANGELOG.md"
- File /nonfatal "..\..\.git.json"
+ File /nonfatal "..\..\git.json"
;Set output path to InstallDir
SetOutPath "\\?\$INSTDIR\node_modules"
diff --git a/dist/windows/updater_package.sh b/dist/windows/updater_package.sh
index fae2e9ef64..09ce4437a0 100644
--- a/dist/windows/updater_package.sh
+++ b/dist/windows/updater_package.sh
@@ -27,7 +27,7 @@ if [ "${POP_NEW_NW}" = "TRUE" ]; then
fi
cp "${basedir}/package.json" "${outdir}"
-cp "${basedir}/.git.json" "${outdir}"
+cp "${basedir}/git.json" "${outdir}"
cd ${outdir}
vers=$(sed -n "s|\s*\"version\"\:\ \"\(.*\)\"\,|\1|p" "${basedir}/package.json")
diff --git a/gulpfile.js b/gulpfile.js
index 9238d7634d..352a0f6820 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -19,7 +19,7 @@ const gulp = require('gulp'),
currentPlatform = require('nw-builder/lib/detectCurrentPlatform.js'),
yargs = require('yargs'),
nib = require('nib'),
- git = require('git-rev'),
+ git = require('git-describe'),
zip = require('gulp-zip'),
fs = require('fs'),
path = require('path'),
@@ -95,6 +95,15 @@ const parseReqDeps = () => {
});
};
+const curVersion = () => {
+ if (fs.existsSync('./git.json')) {
+ const gitData = require('./git.json');
+ return gitData.semver;
+ } else {
+ return pkJson.version;
+ }
+};
+
// console.log for thenable promises
const log = () => {
console.log.apply(console, arguments);
@@ -245,7 +254,7 @@ gulp.task('compresszip', () => {
return gulp
.src(sources + '/**')
.pipe(
- zip(pkJson.name + '-' + pkJson.version + '_' + platform + '.zip')
+ zip(pkJson.name + '-' + curVersion() + '_' + platform + '.zip')
)
.pipe(gulp.dest(releasesDir))
.on('end', () => {
@@ -275,7 +284,7 @@ gulp.task('compressUpdater', () => {
console.log('Packaging updater for: %s', platform);
return gulp
.src(path.join('build', updateFile))
- .pipe(zip('update-' + pkJson.version + '_' + platform + '.zip'))
+ .pipe(zip('update-' + curVersion() + '_' + platform + '.zip'))
.pipe(gulp.dest(releasesDir))
.on('end', () => {
console.log(
@@ -418,7 +427,7 @@ gulp.task('nwjs', () => {
'./README.md',
'./CHANGELOG.md',
'./LICENSE.txt',
- './.git.json'
+ './git.json'
];
// add node_modules
nw.options.files = nw.options.files.concat(requiredDeps);
@@ -444,17 +453,17 @@ gulp.task('nwjs', () => {
});
});
-// create .git.json (used in 'About')
+// create git.json (used in 'About')
gulp.task('injectgit', () => {
- return Promise.all([promiseCallback(git.branch), promiseCallback(git.long)])
+ return git.gitDescribe()
.then(
(gitInfo) =>
new Promise((resolve, reject) => {
fs.writeFile(
- '.git.json',
+ 'git.json',
JSON.stringify({
- branch: gitInfo[0],
- commit: gitInfo[1]
+ commit: gitInfo.hash.substr(1),
+ semver: gitInfo.semverString,
}),
(error) => {
return error ? reject(error) : resolve(gitInfo);
@@ -463,8 +472,8 @@ gulp.task('injectgit', () => {
})
)
.then((gitInfo) => {
- console.log('Branch:', gitInfo[0]);
- console.log('Commit:', gitInfo[1].substr(0, 8));
+ console.log('Hash:', gitInfo.hash.substr(1));
+ console.log('Raw:', gitInfo.raw);
})
.catch((error) => {
console.log(error);
@@ -572,7 +581,7 @@ gulp.task('deb', () => {
nwVersion,
platform,
pkJson.name,
- pkJson.version,
+ curVersion(),
releasesDir
]);
@@ -686,7 +695,7 @@ gulp.task('prepareUpdater:win', () => {
)
.pipe(gulpRename('update.exe'))
.pipe(gulp.dest(path.join(process.cwd(), releasesDir)))
- .pipe(zip('update-' + pkJson.version + '_' + platform + '.zip'))
+ .pipe(zip('update-' + curVersion() + '_' + platform + '.zip'))
.pipe(gulp.dest(releasesDir))
.on('end', () => {
console.log(
diff --git a/package.json b/package.json
index f602c9683e..59e2c1f090 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,8 @@
},
"license": "GPL-3.0",
"main": "src/app/index.html",
- "version": "0.4.5",
- "releaseName": "The next wave",
+ "version": "0.4.6",
+ "releaseName": "The Good Variant",
"scripts": {
"build": "gulp build",
"clean": "gulp clean",
@@ -56,12 +56,11 @@
"bootstrap": "^3.4.1",
"butter-provider": "0.11.0",
"butter-sanitize": "^0.1.1",
- "butter-settings-popcorntime.app": "0.0.5",
+ "butter-settings-popcorntime.app": "0.0.6",
"chromecast-api": "0.3.4",
- "defer-request": "0.0.3",
+ "dayjs": "^1.10.6",
"dlnacasts2": "0.2.0",
"edit-json-file": "^1.4.1",
- "es6-object-assign": "^1.0.1",
"flag-icon-css": "^3.5.0",
"i18n": "0.x.x",
"iconv-lite": "0.x.x",
@@ -71,38 +70,37 @@
"lodash": "^4.17.19",
"memoizee": "0.x.x",
"mkdirp": "*",
- "moment": ">=2.22.2",
"mousetrap": "~1.6.2",
"mv": "2.x.x",
- "nedb": "1.8.0",
+ "nedb-promises": "^5.0.0",
"node-tvdb": "^4.1.0",
"opensubtitles-api": "^5.1.2",
"q": "2.0.3",
"querystring": "^0.2.0",
"readdirp": "2.x.x",
"request": "2.88.x",
- "rimraf": "2.x.x",
+ "rimraf": "^3.0.0",
"sanitizer": "0.x.x",
- "semver": "5.x.x",
- "send": "0.16.x",
- "socks-proxy-agent": "^5.0.0",
+ "semver": "^5.7.1",
+ "send": "^0.17.1",
+ "socks-proxy-agent": "^6.0.0",
"srt-to-vtt": "^1.1",
- "tar": "4.4.8",
+ "tar": "4.4.18",
"torrentcollection4": "0.0.9",
"trakt.tv": "7.x.x",
"trakt.tv-images": "5.x.x",
"trakt.tv-matcher": "7.x.x",
"trakt.tv-ondeck": "7.x.x",
- "underscore": "1.12.1",
- "urijs": "1.19.6",
+ "underscore": "^1.13.0",
+ "urijs": "^1.19.7",
"video.js": "4.11.4",
"videojs-youtube": "1.2.10",
- "webtorrent": "git+https://github.com/popcorn-time-ru/webtorrent/#pt-fork",
+ "webtorrent": "^1.5.5",
"webtorrent-health": "1.x.x"
},
"devDependencies": {
"del": "^3.x.x",
- "git-rev": "^0.2.1",
+ "git-describe": "^4.0",
"gulp": "^4.0.2",
"gulp-filter": "^5.1.0",
"gulp-gzip": "^1.4.2",
diff --git a/src/app/.jshintrc b/src/app/.jshintrc
index d3ff22f0e6..2dc7935a45 100644
--- a/src/app/.jshintrc
+++ b/src/app/.jshintrc
@@ -54,7 +54,7 @@
"inherits": true,
"Q": true,
"os": true,
- "moment": true,
+ "dayjs": true,
"crypt": true,
"semver": true,
"fs": true,
diff --git a/src/app/app.js b/src/app/app.js
index 5078512037..b678c8834b 100644
--- a/src/app/app.js
+++ b/src/app/app.js
@@ -94,16 +94,19 @@ App.db = Database;
App.advsettings = AdvSettings;
App.settings = Settings;
App.WebTorrent = new WebTorrent({
- maxConns: parseInt(Settings.connectionLimit, 10) || 55,
- tracker: {
- announce: Settings.trackers.forced
- },
- dht: true,
+ maxConns : parseInt(Settings.connectionLimit, 10) || 55,
+ downloadLimit: parseInt(parseFloat(Settings.downloadLimit, 10) * parseInt(Settings.maxLimitMult, 10)) || -1,
+ uploadLimit : parseInt(parseFloat(Settings.uploadLimit, 10) * parseInt(Settings.maxLimitMult, 10)) || -1,
+ dht : true,
+ secure : Settings.protocolEncryption || false,
+ tracker : {
+ announce: Settings.trackers.forced
+ }
});
App.plugins = {};
-fs.readFile('./.git.json', 'utf8', function (err, json) {
+fs.readFile('./git.json', 'utf8', function (err, json) {
if (!err) {
App.git = JSON.parse(json);
}
@@ -306,7 +309,7 @@ win.on('restore', function () {
// Now this function is used via global keys (cmd+q and alt+f4)
function close() {
- $('.spinner').show();
+ win.hide();
// If the WebTorrent is destroyed, that means the user has already clicked the close button.
// Try to let the WebTorrent destroy from that closure. Even if it fails, the window will close.
@@ -702,7 +705,7 @@ var handleVideoFile = function (file) {
App.vent.trigger('stream:ready', localVideo); // start stream
App.Device.Collection.setDevice(tmpPlayer);
- $('.eye-info-player').hide();
+ $('.eye-info-player, .maximize-icon #maxdllb').hide();
$('.vjs-load-progress').css('width', '100%');
});
};
diff --git a/src/app/bootstrap.js b/src/app/bootstrap.js
index d59fc9511f..cbcb70a0c8 100644
--- a/src/app/bootstrap.js
+++ b/src/app/bootstrap.js
@@ -14,43 +14,7 @@
return files
.map(function(file) {
- if (!file.match(/\.js$/) || file.match(/generic.js$/) || file.match(/tvshowtime.js$/)) {
- return null;
- }
-
- win.info('loading local provider', file);
-
- var q = Q.defer();
-
- var head = document.getElementsByTagName('head')[0];
- var script = document.createElement('script');
-
- script.type = 'text/javascript';
- script.src = 'lib/providers/' + file;
-
- script.onload = function() {
- win.info('loaded', file);
- q.resolve(file);
- };
-
- head.appendChild(script);
-
- return q.promise;
- })
- .filter(function(q) {
- return q;
- });
- }
-
- function loadLocalProvidersDelayed() {
- var appPath = '';
- var providerPath = './src/app/lib/providers/';
-
- var files = fs.readdirSync(providerPath);
-
- return files
- .map(function(file) {
- if (!file.match(/tvshowtime.js$/)) {
+ if (!file.match(/\.js$/) || file.match(/generic.js$/)) {
return null;
}
@@ -85,7 +49,7 @@
function loadProvidersJSON(fn) {
return pkJson.providers.map(function(providerPath) {
- win.info('loading npm', providerPath);
+ win.info('loading json', providerPath);
return loadFromNPM(`./${providerPath}`, fn);
});
}
@@ -125,9 +89,7 @@
function loadProvidersDelayed() {
return Q.all(
- loadLocalProvidersDelayed()
- .concat(loadNpmProviders())
- .concat(loadLegacyNpmProviders())
+ loadNpmProviders().concat(loadLegacyNpmProviders())
);
}
diff --git a/src/app/butter-provider/anime.js b/src/app/butter-provider/anime.js
index 1363a5c729..bb8fea7453 100644
--- a/src/app/butter-provider/anime.js
+++ b/src/app/butter-provider/anime.js
@@ -1,7 +1,8 @@
'use strict';
-var Generic = require('./generic');
-var sanitize = require('butter-sanitize');
+const Generic = require('./generic');
+const sanitize = require('butter-sanitize');
+const i18n = require('i18n');
class AnimeApi extends Generic {
constructor(args) {
@@ -96,6 +97,72 @@ class AnimeApi extends Generic {
return sanitize(result);
});
}
+
+ filters() {
+ const data = {
+ genres: [
+ 'All',
+ 'Action',
+ 'Adventure',
+ 'Cars',
+ 'Comedy',
+ 'Dementia',
+ 'Demons',
+ 'Drama',
+ 'Ecchi',
+ 'Fantasy',
+ 'Game',
+ 'Harem',
+ 'Historical',
+ 'Horror',
+ 'Josei',
+ 'Kids',
+ 'Magic',
+ 'Martial Arts',
+ 'Mecha',
+ 'Military',
+ 'Music',
+ 'Mystery',
+ 'Parody',
+ 'Police',
+ 'Psychological',
+ 'Romance',
+ 'Samurai',
+ 'School',
+ 'Sci-Fi',
+ 'Seinen',
+ 'Shoujo',
+ 'Shoujo Ai',
+ 'Shounen',
+ 'Shounen Ai',
+ 'Slice of Life',
+ 'Space',
+ 'Sports',
+ 'Super Power',
+ 'Supernatural',
+ 'Thriller',
+ 'Vampire'
+ ],
+ sorters: ['popularity', 'name', 'year'],
+ types: ['All', 'Movies', 'TV', 'OVA', 'ONA']
+ };
+ let filters = {
+ genres: {},
+ sorters: {},
+ types: {},
+ };
+ for (const genre of data.genres) {
+ filters.genres[genre] = i18n.__(genre.capitalizeEach());
+ }
+ for (const sorter of data.sorters) {
+ filters.sorters[sorter] = i18n.__(sorter.capitalizeEach());
+ }
+ for (const type of data.types) {
+ filters.types[type] = i18n.__(type);
+ }
+
+ return Promise.resolve(filters);
+ }
}
AnimeApi.prototype.config = {
diff --git a/src/app/butter-provider/generic.js b/src/app/butter-provider/generic.js
index a124c388e7..174fab2e42 100644
--- a/src/app/butter-provider/generic.js
+++ b/src/app/butter-provider/generic.js
@@ -1,7 +1,14 @@
var memoize = require('memoizee');
var _ = require('lodash');
+const i18n = require('i18n');
const socksProxyAgent = require( 'socks-proxy-agent' );
+String.prototype.capitalizeEach = function () {
+ return this.replace(/\w*/g, function (txt) {
+ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
+ });
+};
+
var processArgs = function(config, args) {
var newArgs = {};
Object.keys(config.args).map(function(k) {
@@ -119,7 +126,29 @@ class Provider {
urls = urls.split(',').map((x) => x.trim()).filter((x) => !!x);
}
this.apiURL = _.shuffle(urls);
+ }
+
+ filters() {return Promise.resolve({});}
+
+ formatFiltersFromServer(sorters, data)
+ {
+ let filters = {
+ genres: {},
+ sorters: {},
+ };
+ for (const genre of sorters) {
+ filters.sorters[genre] = i18n.__(genre.capitalizeEach());
+ }
+
+ filters.genres = {
+ 'All': data.all.title + ' (' + data.all.count + ')',
+ };
+ delete data.all;
+ for (const key in data) {
+ filters.genres[key] = data[key].title + ' (' + data[key].count + ')'
+ }
+ return filters;
}
}
diff --git a/src/app/butter-provider/movie.js b/src/app/butter-provider/movie.js
index cc5b2e3594..9088103c6b 100644
--- a/src/app/butter-provider/movie.js
+++ b/src/app/butter-provider/movie.js
@@ -2,12 +2,15 @@
const Generic = require('./generic');
const sanitize = require('butter-sanitize');
+const i18n = require('i18n');
class MovieApi extends Generic {
constructor(args) {
super(args);
this.language = args.language;
+ this.contentLanguage = args.contentLanguage || this.language;
+ this.contentLangOnly = args.contentLangOnly || false;
}
_formatForPopcorn(movies) {
@@ -31,11 +34,10 @@ class MovieApi extends Generic {
synopsis: movie.synopsis,
trailer: movie.trailer !== null ? movie.trailer : false,
certification: movie.certification,
- torrents:
- movie.torrents['en'] !== null
- ? movie.torrents['en']
- : movie.torrents[Object.keys(movie.torrents)[0]],
- langs: movie.torrents
+ torrents: movie.torrents[movie.contextLocale],
+ langs: movie.torrents,
+ defaultAudio: movie.contextLocale,
+ locale: movie.locale || null,
});
}
});
@@ -56,6 +58,12 @@ class MovieApi extends Generic {
limit: '50'
};
+ params.locale = this.language;
+ params.contentLocale = this.contentLanguage;
+ if (!this.contentLangOnly) {
+ params.showAll = 1;
+ }
+
if (filters.keywords) {
params.keywords = this.apiURL[0].includes('popcorn-ru') ? filters.keywords.trim() : filters.keywords.trim().replace(/[^a-zA-Z0-9]|\s/g, '% ');
}
@@ -78,6 +86,62 @@ class MovieApi extends Generic {
detail(torrent_id, old_data, debug) {
return new Promise((resolve, reject) => resolve(old_data));
}
+
+ filters() {
+ const params = {
+ contentLocale: this.contentLanguage,
+ };
+ if (!this.contentLangOnly) {
+ params.showAll = 1;
+ }
+ return this._get(0, 'movies/stat?' + new URLSearchParams(params))
+ .then((result) => this.formatFiltersFromServer(
+ ['trending', 'popularity', 'last added', 'year', 'title', 'rating'],
+ result
+ )).catch(() => {
+ const data = {
+ genres: [
+ 'All',
+ 'Action',
+ 'Adventure',
+ 'Animation',
+ 'Biography',
+ 'Comedy',
+ 'Crime',
+ 'Documentary',
+ 'Drama',
+ 'Family',
+ 'Fantasy',
+ 'Film-Noir',
+ 'History',
+ 'Horror',
+ 'Music',
+ 'Musical',
+ 'Mystery',
+ 'Romance',
+ 'Sci-Fi',
+ 'Short',
+ 'Sport',
+ 'Thriller',
+ 'War',
+ 'Western'
+ ],
+ sorters: ['trending', 'popularity', 'last added', 'year', 'title', 'rating'],
+ };
+ let filters = {
+ genres: {},
+ sorters: {},
+ };
+ for (const genre of data.genres) {
+ filters.genres[genre] = i18n.__(genre.capitalizeEach());
+ }
+ for (const sorter of data.sorters) {
+ filters.sorters[sorter] = i18n.__(sorter.capitalizeEach());
+ }
+
+ return Promise.resolve(filters);
+ });
+ }
}
MovieApi.prototype.config = {
diff --git a/src/app/butter-provider/tv.js b/src/app/butter-provider/tv.js
index f5423f7d9f..c4ced21428 100644
--- a/src/app/butter-provider/tv.js
+++ b/src/app/butter-provider/tv.js
@@ -2,23 +2,15 @@
const Generic = require('./generic');
const sanitize = require('butter-sanitize');
-const TVDB = require('node-tvdb');
+const i18n = require('i18n');
class TVApi extends Generic {
constructor(args) {
super(args);
this.language = args.language;
-
- try {
- this.tvdb = new TVDB('7B95D15E1BE1D75A');
- this.tvdb.getLanguages().then(langlist => (this.TVDBLangs = langlist));
- } catch (err) {
- this.TVDBLangs = false;
- console.warn(
- 'Something went wrong with TVDB, overviews can\'t be translated.'
- );
- }
+ this.contentLanguage = args.contentLanguage || this.language;
+ this.contentLangOnly = args.contentLangOnly || false;
}
extractIds(items) {
@@ -31,6 +23,12 @@ class TVApi extends Generic {
limit: '50'
};
+ params.locale = this.language;
+ params.contentLocale = this.contentLanguage;
+ if (!this.contentLangOnly) {
+ params.showAll = 1;
+ }
+
if (filters.keywords) {
params.keywords = this.apiURL[0].includes('popcorn-ru') ? filters.keywords.trim() : filters.keywords.trim().replace(/[^a-zA-Z0-9]|\s/g, '% ');
}
@@ -56,58 +54,83 @@ class TVApi extends Generic {
}
detail(torrent_id, old_data, debug) {
- const uri = `show/${torrent_id}`;
+ return this.contentOnLang(torrent_id, old_data.contextLocale);
+ }
+
+ contentOnLang(torrent_id, lang) {
+ const params = {};
+ if (this.language) {
+ params.locale = this.language;
+ }
+ if (this.language !== lang) {
+ params.contentLocale = lang;
+ }
+ const uri = `show/${torrent_id}?` + new URLSearchParams(params);
return this._get(0, uri).then(data => {
- console.log(data._id);
- if (this.translate && this.language !== 'en') {
- let langAvailable;
- for (let x = 0; x < this.TVDBLangs.length; x++) {
- if (this.TVDBLangs[x].abbreviation.indexOf(this.language) > -1) {
- langAvailable = true;
- break;
- }
- }
-
- if (!langAvailable) {
- return sanitize(data);
- } else {
- const reqTimeout = setTimeout(() => sanitize(data), 2000);
-
- console.info(
- `Request to TVApi: '${old_data.title}' - ${this.language}`
- );
- return this.tvdb
- .getSeriesAllById(old_data.tvdb_id)
- .then(localization => {
- clearTimeout(reqTimeout);
-
- data = Object.assign(data, {
- synopsis: localization.Overview
- });
-
- for (let i = 0; i < localization.Episodes.length; i++) {
- for (let j = 0; j < data.episodes.length; j++) {
- if (
- localization.Episodes[i].id.toString() ===
- data.episodes[j].tvdb_id.toString()
- ) {
- data.episodes[j].overview =
- localization.Episodes[i].Overview;
- break;
- }
- }
- }
-
- return sanitize(data);
- })
- .catch(err => sanitize(data));
- }
- } else {
- return sanitize(data);
- }
+ return data;
+ return sanitize(data);
});
}
+
+ filters() {
+ const params = {
+ contentLocale: this.contentLanguage,
+ };
+ if (!this.contentLangOnly) {
+ params.showAll = 1;
+ }
+ return this._get(0, 'shows/stat?' + new URLSearchParams(params))
+ .then((result) => this.formatFiltersFromServer(
+ ['trending', 'popularity', 'updated', 'year', 'name', 'rating'],
+ result
+ )).catch(() => {
+ const data = {
+ genres: [
+ 'All',
+ 'Action',
+ 'Adventure',
+ 'Animation',
+ 'Children',
+ 'Comedy',
+ 'Crime',
+ 'Documentary',
+ 'Drama',
+ 'Family',
+ 'Fantasy',
+ 'Game Show',
+ 'Home and Garden',
+ 'Horror',
+ 'Mini Series',
+ 'Mystery',
+ 'News',
+ 'Reality',
+ 'Romance',
+ 'Science Fiction',
+ 'Soap',
+ 'Special Interest',
+ 'Sport',
+ 'Suspense',
+ 'Talk Show',
+ 'Thriller',
+ 'Western'
+ ],
+ sorters: ['trending', 'popularity', 'updated', 'year', 'name', 'rating'],
+ };
+ let filters = {
+ genres: {},
+ sorters: {},
+ };
+ for (const genre of data.genres) {
+ filters.genres[genre] = i18n.__(genre.capitalizeEach());
+ }
+ for (const sorter of data.sorters) {
+ filters.sorters[sorter] = i18n.__(sorter.capitalizeEach());
+ }
+
+ return Promise.resolve(filters);
+ });
+ }
}
TVApi.prototype.config = {
diff --git a/src/app/butter-provider/yts.js b/src/app/butter-provider/yts.js
index 3a29c12eed..b63c08d2c6 100644
--- a/src/app/butter-provider/yts.js
+++ b/src/app/butter-provider/yts.js
@@ -2,6 +2,7 @@
const Generic = require('./generic');
const sanitize = require('butter-sanitize');
+const i18n = require('i18n');
class YTSApi extends Generic {
constructor(args) {
@@ -15,6 +16,19 @@ class YTSApi extends Generic {
if (movies) {
movies.forEach(movie => {
if (movie.torrents) {
+ let torrents = movie.torrents.reduceRight(function (torrents, torrent) {
+ torrents[torrent.quality] = {
+ url: torrent.url,
+ magnet: `magnet:?xt=urn:btih:${torrent.hash}`,
+ source: movie.url,
+ size: torrent.size_bytes,
+ filesize: torrent.size,
+ seed: torrent.seeds,
+ peer: torrent.peers
+ };
+ return torrents;
+ }, {});
+ let curLang = movie.language.replace('cn', 'zh-cn');
results.push({
type: 'movie',
imdb_id: movie.imdb_code,
@@ -31,18 +45,9 @@ class YTSApi extends Generic {
synopsis: movie.description_full,
trailer: 'https://www.youtube.com/watch?v=' + movie.yt_trailer_code || false,
certification: movie.mpa_rating,
- torrents: movie.torrents.reduceRight(function (torrents, torrent) {
- torrents[torrent.quality] = {
- url: torrent.url,
- magnet: `magnet:?xt=urn:btih:${torrent.hash}`,
- size: torrent.size_bytes,
- filesize: torrent.size,
- seed: torrent.seeds,
- peer: torrent.peers
- };
- return torrents;
- }, {}),
- langs: {[movie.language.replace('cn', 'zh-cn')]: {}}
+ torrents: torrents,
+ defaultAudio: curLang,
+ langs: {[curLang]: torrents}
});
}
});
@@ -90,7 +95,7 @@ class YTSApi extends Generic {
}
}
if (filters.rating && filters.rating !== 'All') {
- params.minimum_rating = filters.rating;
+ params.minimum_rating = filters.rating.replace('r', '');
}
const uri = `api/v2/list_movies.json?` + new URLSearchParams(params);
@@ -102,6 +107,71 @@ class YTSApi extends Generic {
detail(torrent_id, old_data, debug) {
return new Promise((resolve, reject) => resolve(old_data));
}
+
+ filters() {
+ const data = {
+ genres: [
+ 'All',
+ 'Action',
+ 'Adventure',
+ 'Animation',
+ 'Biography',
+ 'Comedy',
+ 'Crime',
+ 'Documentary',
+ 'Drama',
+ 'Family',
+ 'Fantasy',
+ 'Film-Noir',
+ 'History',
+ 'Horror',
+ 'Music',
+ 'Musical',
+ 'Mystery',
+ 'Romance',
+ 'Sci-Fi',
+ 'Short',
+ 'Sport',
+ 'Thriller',
+ 'War',
+ 'Western'
+ ],
+ sorters: [
+ 'trending',
+ 'popularity',
+ 'last added',
+ 'year',
+ 'title',
+ 'rating'
+ ],
+ types: ['All', '720p', '1080p', '2160p', '3D'],
+ ratings: ['All', '9', '8', '7', '6', '5', '4', '3', '2', '1']
+ };
+ let filters = {
+ genres: {},
+ sorters: {},
+ types: {},
+ ratings: {},
+ };
+ for (const genre of data.genres) {
+ filters.genres[genre] = i18n.__(genre.capitalizeEach());
+ }
+ for (const sorter of data.sorters) {
+ filters.sorters[sorter] = i18n.__(sorter.capitalizeEach());
+ }
+ for (const type of data.types) {
+ filters.types[type] = i18n.__(type);
+ }
+ for (const rating of data.ratings) {
+ if (rating === 'All') {
+ filters.ratings[rating] = i18n.__(rating);
+ } else {
+ filters.ratings['r' + rating] = rating + '+';
+ }
+ }
+
+ return Promise.resolve(filters);
+ }
}
YTSApi.prototype.config = {
diff --git a/src/app/common.js b/src/app/common.js
index 265135acba..08f19ea040 100644
--- a/src/app/common.js
+++ b/src/app/common.js
@@ -291,6 +291,27 @@ Common.normalize = (function () {
};
})();
+Common.loadImage = function(img) {
+ return new Promise(function(resolve, reject) {
+ let cache = new Image();
+ cache.onload = () => {
+ if (img.indexOf('.gif') !== -1) {
+ // freeze gifs
+ let c = document.createElement('canvas');
+ let w = (c.width = img.width);
+ let h = (c.height = img.height);
+
+ c.getContext('2d').drawImage(cache, 0, 0, w, h);
+ img = c.toDataURL();
+ }
+ resolve(img);
+ };
+
+ cache.onerror = () => resolve(null);
+ cache.src = img;
+ });
+};
+
Common.Promises = {
allSettled: function (promises) {
var wrappedPromises = promises.map(
@@ -302,3 +323,5 @@ Common.Promises = {
};
Common.getTorrentUri = torrent => torrent.magnet || torrent.url || torrent;
+
+Common.qualityCollator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
diff --git a/src/app/database.js b/src/app/database.js
index 427ef0dbed..e25f9f3554 100644
--- a/src/app/database.js
+++ b/src/app/database.js
@@ -1,12 +1,10 @@
-var Datastore = require('nedb'),
+var Datastore = require('nedb-promises'),
db = {},
TTL = 1000 * 60 * 60 * 24;
var startupTime = window.performance.now();
console.debug('Database path: ' + data_path);
-process.env.TZ = 'America/New_York'; // set same api tz
-
db.bookmarks = new Datastore({
filename: path.join(data_path, 'data/bookmarks.db'),
autoload: true
@@ -28,18 +26,6 @@ db.watched = new Datastore({
autoload: true
});
-function promisifyDatastore(datastore) {
- datastore.insert = Q.denodeify(datastore.insert, datastore);
- datastore.update = Q.denodeify(datastore.update, datastore);
- datastore.remove = Q.denodeify(datastore.remove, datastore);
-}
-
-promisifyDatastore(db.bookmarks);
-promisifyDatastore(db.settings);
-promisifyDatastore(db.tvshows);
-promisifyDatastore(db.movies);
-promisifyDatastore(db.watched);
-
// Create unique indexes for the various id's for shows and movies
db.tvshows.ensureIndex({
fieldName: 'imdb_id',
@@ -74,19 +60,6 @@ var extractMovieIds = function (items) {
return _.pluck(items, 'movie_id');
};
-// This utilizes the exec function on nedb to turn function calls into promises
-var promisifyDb = function (obj) {
- return Q.Promise(function (resolve, reject) {
- obj.exec(function (error, result) {
- if (error) {
- return reject(error);
- } else {
- return resolve(result);
- }
- });
- });
-};
-
var Database = {
addMovie: function (data) {
return db.movies.insert(data);
@@ -99,9 +72,9 @@ var Database = {
},
getMovie: function (imdb_id) {
- return promisifyDb(db.movies.findOne({
+ return db.movies.findOne({
imdb_id: imdb_id
- }));
+ });
},
addBookmark: function (imdb_id, type) {
@@ -142,19 +115,11 @@ var Database = {
query.type = data.type;
}
- return promisifyDb(db.bookmarks.find(query).skip(offset).limit(byPage));
+ return db.bookmarks.find(query).skip(offset).limit(byPage);
},
getAllBookmarks: function () {
- return promisifyDb(db.bookmarks.find({}))
- .then(function (data) {
- var bookmarks = [];
- if (data) {
- bookmarks = extractIds(data);
- }
-
- return bookmarks;
- });
+ return db.bookmarks.find({});
},
markMoviesWatched: function (data) {
@@ -174,7 +139,7 @@ var Database = {
win.warn('This shouldn\'t be called');
- return Q();
+ return Promise.resolve();
},
markMovieAsNotWatched: function (data) {
@@ -187,9 +152,9 @@ var Database = {
},
getMoviesWatched: function () {
- return promisifyDb(db.watched.find({
+ return db.watched.find({
type: 'movie'
- }));
+ });
},
/*******************************
@@ -206,9 +171,9 @@ var Database = {
},
markEpisodeAsWatched: function (data) {
- return promisifyDb(db.watched.find({
+ return db.watched.find({
tvdb_id: data.tvdb_id.toString()
- }))
+ })
.then(function (response) {
if (response.length === 0) {
App.watchedShows.push(data.imdb_id.toString());
@@ -235,9 +200,9 @@ var Database = {
},
markEpisodeAsNotWatched: function (data) {
- return promisifyDb(db.watched.find({
+ return db.watched.find({
tvdb_id: data.tvdb_id.toString()
- }))
+ })
.then(function (response) {
if (response.length === 1) {
App.watchedShows.splice(App.watchedShows.indexOf(data.imdb_id.toString()), 1);
@@ -257,12 +222,12 @@ var Database = {
},
checkEpisodeWatched: function (data) {
- return promisifyDb(db.watched.find({
+ return db.watched.find({
tvdb_id: data.tvdb_id.toString(),
imdb_id: data.imdb_id.toString(),
season: data.season.toString(),
episode: data.episode.toString()
- }))
+ })
.then(function (data) {
return (data !== null && data.length > 0);
});
@@ -271,15 +236,15 @@ var Database = {
// return an array of watched episode for this
// tvshow
getEpisodesWatched: function (tvdb_id) {
- return promisifyDb(db.watched.find({
+ return db.watched.find({
tvdb_id: tvdb_id.toString()
- }));
+ });
},
getAllEpisodesWatched: function () {
- return promisifyDb(db.watched.find({
+ return db.watched.find({
type: 'episode'
- }));
+ });
},
// Used in bookmarks
@@ -293,32 +258,32 @@ var Database = {
getTVShow: function (data) {
win.warn('this isn\'t used anywhere');
- return promisifyDb(db.tvshows.findOne({
+ return db.tvshows.findOne({
_id: data.tvdb_id
- }));
+ });
},
// Used in bookmarks
getTVShowByImdb: function (imdb_id) {
- return promisifyDb(db.tvshows.findOne({
+ return db.tvshows.findOne({
imdb_id: imdb_id
- }));
+ });
},
getSetting: function (data) {
- return promisifyDb(db.settings.findOne({
+ return db.settings.findOne({
key: data.key
- }));
+ });
},
getSettings: function () {
- return promisifyDb(db.settings.find({}));
+ return db.settings.find({});
},
getUserInfo: function () {
var bookmarks = Database.getAllBookmarks()
.then(function (data) {
- App.userBookmarks = data;
+ App.userBookmarks = extractIds(data);
});
var movies = Database.getMoviesWatched()
@@ -331,7 +296,7 @@ var Database = {
App.watchedShows = extractIds(data);
});
- return Q.all([bookmarks, movies, episodes]);
+ return Promise.all([bookmarks, movies, episodes]);
},
// format: {key: key_name, value: settings_value}
@@ -372,7 +337,7 @@ var Database = {
fs.unlinkSync(path.join(data_path, 'data/settings.db'));
- return Q.Promise(function (resolve, reject) {
+ return new Promise(function (resolve, reject) {
var req = indexedDB.deleteDatabase(App.Config.cache.name);
req.onsuccess = function () {
resolve();
@@ -397,7 +362,7 @@ var Database = {
.then(Database.getSettings)
.then(function (data) {
if (data !== null) {
- for (var key in data) {
+ for (let key in data) {
Settings[data[key].key] = data[key].value;
}
} else {
@@ -424,15 +389,14 @@ var Database = {
.then(function () {
// set app language
window.setLanguage(Settings.language);
+ // set content language
+ App.Providers.updateLanguage(Settings.language, Settings.contentLanguage || Settings.language, Settings.contentLangOnly);
// set hardware settings and usefull stuff
return AdvSettings.setup();
})
.then(function () {
App.Trakt = App.Config.getProviderForType('metadata');
- App.TVShowTime = App.Config.getProviderForType('tvst');
- App.TVShowTime.restoreToken();
-
// check update
var updater = new App.Updater();
@@ -447,6 +411,9 @@ var Database = {
// enable secure after load options
require('webtorrent/lib/peer.js').enableSecure();
}
+ App.WebTorrent.throttleDownload(parseInt(parseFloat(Settings.downloadLimit, 10) * parseInt(Settings.maxLimitMult, 10)) || -1);
+ App.WebTorrent.throttleUpload(parseInt(parseFloat(Settings.uploadLimit, 10) * parseInt(Settings.maxLimitMult, 10)) || -1);
+ App.WebTorrent.maxConns = parseInt(Settings.connectionLimit, 10) || 55;
})
.catch(function (err) {
win.error('Error starting up', err);
diff --git a/src/app/global.js b/src/app/global.js
index 23aab7d922..912d980d64 100644
--- a/src/app/global.js
+++ b/src/app/global.js
@@ -5,7 +5,7 @@ var _ = require('underscore'),
Q = require('q'),
// Machine readable
os = require('os'),
- moment = require('moment'),
+ dayjs = require('dayjs'),
crypt = require('crypto'),
semver = require('semver'),
// Files
@@ -47,3 +47,6 @@ var _ = require('underscore'),
extPlayerlst = '',
// setting default filters status
curSetDefaultFilters = false;
+
+dayjs.extend(require('dayjs/plugin/relativeTime'));
+dayjs.extend(require('dayjs/plugin/localizedFormat'));
diff --git a/src/app/language.js b/src/app/language.js
index 2948582eba..01e65697a7 100644
--- a/src/app/language.js
+++ b/src/app/language.js
@@ -6,6 +6,7 @@ var setLanguage = function (preferredLanguage) {
var lang = App.Localization.detectLocale();
i18n.setLocale(lang);
AdvSettings.set('language', lang);
+ Settings.language = lang;
} else {
i18n.setLocale(preferredLanguage);
}
@@ -21,6 +22,9 @@ var setLanguage = function (preferredLanguage) {
$el.text(i18n.__(key));
}
});
+
+ require('dayjs/locale/' + Settings.language);
+ dayjs.locale(Settings.language);
};
App.Localization.nativeName = function (lang) {
diff --git a/src/app/language/en.json b/src/app/language/en.json
index 13b3133bbb..7cc7dda7b8 100644
--- a/src/app/language/en.json
+++ b/src/app/language/en.json
@@ -464,7 +464,7 @@
"Cache Folder Button": "Cache Folder Button",
"Enable remote control": "Enable remote control",
"Server": "Server",
- "API Server": "API Server",
+ "API Server(s)": "API Server(s)",
"Proxy Server": "Proxy Server",
"Remember to Export your Database before updating in case its necessary to restore your Favorites, marked as watched or settings": "Remember to Export your Database before updating in case its necessary to restore your Favorites, marked as watched or settings",
"Update Now": "Update Now",
@@ -515,9 +515,9 @@
"No anime found...": "No anime found...",
"Search in %s": "Search in %s",
"Show 'Search on Torrent Collection' in search": "Show 'Search on Torrent Collection' in search",
- "Custom Movies Server": "Custom Movies Server",
- "Custom Series Server": "Custom Series Server",
- "Custom Anime Server": "Custom Anime Server",
+ "Movies API Server": "Movies API Server",
+ "Series API Server": "Series API Server",
+ "Anime API Server": "Anime API Server",
"The image url was copied to the clipboard": "The image url was copied to the clipboard",
"Popcorn Time currently supports": "Popcorn Time currently supports",
"There is also support for Chromecast, AirPlay & DLNA devices.": "There is also support for Chromecast, AirPlay & DLNA devices.",
@@ -554,5 +554,24 @@
"Allows connecting to peers that use PE/MSE. Will in most cases increase the number of connectable peers but might also result in increased CPU usage": "Allows connecting to peers that use PE/MSE. Will in most cases increase the number of connectable peers but might also result in increased CPU usage",
"Show the Seedbox when adding a new download": "Show the Seedbox when adding a new download",
"Download added": "Download added",
- "Change API Server": "Change API Server"
+ "Change API Server": "Change API Server",
+ "Localisation": "Localisation",
+ "Preferred Content Language": "Preferred Content Language",
+ "Same as interface": "Same as interface",
+ "Title translation": "Title translation",
+ "Translated - Original": "Translated - Original",
+ "Original - Translated": "Original - Translated",
+ "Translated only": "Translated only",
+ "Original only": "Original only",
+ "Translate Posters": "Translate Posters",
+ "Translate Episode Titles": "Translate Episode Titles",
+ "Only show content available in the preferred language": "Only show content available in the preferred language",
+ "Translations depend on availability. Some options also might not be supported by all API servers": "Translations depend on availability. Some options also might not be supported by all API servers",
+ "added": "added",
+ "The source link was copied to the clipboard": "The source link was copied to the clipboard",
+ "Max. Down / Up Speed": "Max. Down / Up Speed",
+ "Show Release Info": "Show Release Info",
+ "Parental Guide": "Parental Guide",
+ "Rebuild bookmarks database": "Rebuild bookmarks database",
+ "Rebuilding bookmarks...": "Rebuilding bookmarks..."
}
diff --git a/src/app/lib/config.js b/src/app/lib/config.js
index 859ab01a6c..c421a54166 100644
--- a/src/app/lib/config.js
+++ b/src/app/lib/config.js
@@ -4,6 +4,8 @@
var Config = {
title: Settings.projectName,
platform: process.platform,
+
+ // TODO: remote api usage - need rewrite
genres: [
'All',
'Action',
@@ -128,35 +130,6 @@
'Western'
],
- genres_indie: [
- 'All',
- 'Action',
- 'Adventure',
- 'Animation',
- 'Biography',
- 'Comedy',
- 'Crime',
- 'Documentary',
- 'Drama',
- 'Family',
- 'Fantasy',
- 'Film-Noir',
- 'History',
- 'Horror',
- 'Music',
- 'Musical',
- 'Mystery',
- 'Romance',
- 'Sci-Fi',
- 'Short',
- 'Sport',
- 'Thriller',
- 'War',
- 'Western'
- ],
- sorters_indie: ['popularity', 'updated', 'year', 'alphabet', 'rating'],
- types_indie: [],
-
cache: {
name: 'cachedb',
version: '1.7',
@@ -185,8 +158,7 @@
if (
(p.name === 'Movies' && !Settings.moviesTabEnable) ||
(p.name === 'Series' && !Settings.seriesTabEnable) ||
- (p.name === 'Anime' && !Settings.animeTabEnable) ||
- p.name === 'Indie'
+ (p.name === 'Anime' && !Settings.animeTabEnable)
) {
return false;
}
@@ -199,10 +171,6 @@
},
getProviderForType: function(type) {
- if (type === 'indie') {
- return null;
- }
-
var provider = Settings.providers[type];
if (typeof provider !== 'string') {
if (provider && provider.uri) {
diff --git a/src/app/lib/models/filter.js b/src/app/lib/models/filter.js
index 45e020d9fc..f5bc5016cb 100644
--- a/src/app/lib/models/filter.js
+++ b/src/app/lib/models/filter.js
@@ -2,20 +2,33 @@
'use strict';
var Filter = Backbone.Model.extend({
- defaults: {
- genres: [],
- sorters: [],
- types: [],
- order: -1,
- ratings: []
- },
initialize: function () {
- this.set('sorter', this.get('sorter') || this.get('sorters')[0]);
- this.set('genre', this.get('genre') || this.get('genres')[0]);
- this.set('type', this.get('type') || this.get('types')[0]);
+ this.set('load', false);
+ this.set('genres', []);
+ this.set('sorters', []);
+ this.set('types', []);
+ this.set('ratings', []);
+ this.init();
+
+ this.get('provider').filters().then((filters) => {
+ this.set('genres', filters.genres || []);
+ this.set('sorters', filters.sorters || []);
+ this.set('types', filters.types || []);
+ this.set('ratings', filters.ratings || []);
+
+ this.init();
+ this.set('load', true);
+ App.vent.trigger('filter-bar:render');
+ });
+ },
+
+ init() {
+ this.set('sorter', this.get('sorter') || Object.keys(this.get('sorters'))[0]);
+ this.set('genre', this.get('genre') || Object.keys(this.get('genres'))[0]);
+ this.set('type', this.get('type') || Object.keys(this.get('types'))[0]);
this.set('order', this.get('order') || -1);
- this.set('rating', this.get('rating') || this.get('ratings')[0]);
+ this.set('rating', this.get('rating') || Object.keys(this.get('ratings'))[0]);
}
});
diff --git a/src/app/lib/models/indie_collection.js b/src/app/lib/models/indie_collection.js
deleted file mode 100644
index 963ed0a4fe..0000000000
--- a/src/app/lib/models/indie_collection.js
+++ /dev/null
@@ -1,16 +0,0 @@
-(function (App) {
- 'use strict';
-
- var IndieCollection = App.Model.Collection.extend({
- model: App.Model.Movie,
- popid: 'imdb_id',
- type: 'indies',
- getProviders: function () {
- return {
- torrents: App.Config.getProviderForType('indie')
- };
- },
- });
-
- App.Model.IndieCollection = IndieCollection;
-})(window.App);
diff --git a/src/app/lib/providers/favorites.js b/src/app/lib/providers/favorites.js
index b41f14e735..26b0f35a2c 100644
--- a/src/app/lib/providers/favorites.js
+++ b/src/app/lib/providers/favorites.js
@@ -1,6 +1,8 @@
(function (App) {
'use strict';
+ const i18n = require('i18n');
+
var Favorites = function () {};
Favorites.prototype.constructor = Favorites;
Favorites.prototype.config = {
@@ -186,6 +188,25 @@
});
};
+ Favorites.prototype.filters = function () {
+ const data = {
+ types: ['All', 'Movies', 'Series', 'Anime'],
+ sorters: ['watched items', 'year', 'title', 'rating']
+ };
+ let filters = {
+ types: {},
+ sorters: {},
+ };
+ for (const sorter of data.sorters) {
+ filters.sorters[sorter] = i18n.__(sorter.capitalizeEach());
+ }
+ for (const type of data.types) {
+ filters.types[type] = i18n.__(type);
+ }
+
+ return Promise.resolve(filters);
+ };
+
App.Providers.install(Favorites);
})(window.App);
diff --git a/src/app/lib/providers/generic.js b/src/app/lib/providers/generic.js
index ba2a2c19eb..b2327efda6 100644
--- a/src/app/lib/providers/generic.js
+++ b/src/app/lib/providers/generic.js
@@ -9,12 +9,7 @@
if (moviesServer && moviesServer.includes('://yts')) {
var MovieBrowser = App.View.PCTBrowser.extend({
collectionModel: App.Model.MovieCollection,
- filters: {
- genres: App.Config.genres,
- sorters: App.Config.sorters,
- types: App.Config.types_yts,
- ratings: App.Config.ratings_yts
- }
+ provider: 'YTSApi',
});
App.View.MovieBrowser = MovieBrowser;
cache[Object.keys(App.Providers._cache)[0]] = App.Providers.get('YTSApi');
@@ -27,6 +22,20 @@
}
}
+ function updateProviderLanguage (language, contentLanguage, contentLangOnly = false) {
+ for (let provider in cache) {
+ if (cache[provider] && cache[provider].hasOwnProperty('language')) {
+ cache[provider].language = language;
+ }
+ if (cache[provider] && cache[provider].hasOwnProperty('contentLanguage')) {
+ cache[provider].contentLanguage = contentLanguage;
+ }
+ if (cache[provider] && cache[provider].hasOwnProperty('contentLangOnly')) {
+ cache[provider].contentLangOnly = contentLangOnly;
+ }
+ }
+ }
+
function delProvider(name) {
if (cache[name]) {
win.info('Delete provider cache', name);
@@ -109,6 +118,7 @@
App.Providers.delete = delProvider;
App.Providers.install = installProvider;
App.Providers.updateConnection = updateProviderConnection;
+ App.Providers.updateLanguage = updateProviderLanguage;
App.Providers.getFromRegistry = getProviderFromRegistry;
})(window.App);
diff --git a/src/app/lib/providers/opensubtitles.js b/src/app/lib/providers/opensubtitles.js
index c19681eddf..2d8a5906c4 100644
--- a/src/app/lib/providers/opensubtitles.js
+++ b/src/app/lib/providers/opensubtitles.js
@@ -52,7 +52,7 @@
data[lang] = data[lang].url;
}
- console.info(Object.keys(data).length + ' subtitles found');
+ win.info(Object.keys(data).length + ' subtitles found');
return Common.sanitize(data);
};
diff --git a/src/app/lib/providers/tvshowtime.js b/src/app/lib/providers/tvshowtime.js
deleted file mode 100644
index d8f94ce7d4..0000000000
--- a/src/app/lib/providers/tvshowtime.js
+++ /dev/null
@@ -1,144 +0,0 @@
-(function (App) {
- 'use strict';
-
- var PT_VERSION = Settings.version,
- API_ENDPOINT = URI('https://api.tvtime.com/v1'),
- API_CLIENT_ID = Settings.tvshowtime.client_id,
- API_CLIENT_SECRET = Settings.tvshowtime.client_secret;
-
- function TVShowTime() {
- App.Providers.CacheProviderV2.call(this, 'tvst');
- this.restoreToken();
- }
- // Inherit the Cache Provider
- inherits(TVShowTime, App.Providers.CacheProviderV2);
-
- TVShowTime.prototype.config = {
- name: 'TVShowTime'
- };
-
- // Try to restore token from settings and auth to tvst api
- TVShowTime.prototype.restoreToken = function () {
- var tvstAccessToken = AdvSettings.get('tvstAccessToken');
-
- if (tvstAccessToken !== '') {
- this.authenticated = true;
- App.vent.trigger('system:tvstAuthenticated');
- this._credentials = {
- token: tvstAccessToken
- };
-
- } else {
- this.authenticated = false;
- this._credentials = {
- token: ''
- };
- }
- };
-
- TVShowTime.prototype.post = function (endpoint, postVariables) {
- var defer = Q.defer();
-
- postVariables = postVariables || {};
-
- var requestUri = API_ENDPOINT.clone()
- .segment(endpoint);
-
- request.post(requestUri.toString(), {
- form: postVariables
- }, function (err, res, body) {
- if (err || !body || res.statusCode >= 400) {
- defer.reject(err);
- } else {
- defer.resolve(body);
- }
- });
-
- return defer.promise;
- };
-
- TVShowTime.prototype.authenticate = function (callback) {
- var self = this;
- this
- .post('oauth/device/code', {
- 'client_id': API_CLIENT_ID
- })
- .then(function (data) {
- data = Common.sanitize(JSON.parse(data));
- if (data.result === 'OK') {
- var activateUri = data.verification_url + '?user_code=' + data.user_code;
- self.oauthAuthorizing = setInterval(function () {
- self.post('oauth/access_token', {
- 'client_id': API_CLIENT_ID,
- 'client_secret': API_CLIENT_SECRET,
- 'code': data.device_code
- }).then(function (data) {
- data = JSON.parse(data);
- if (data.result === 'OK') {
- clearInterval(self.oauthAuthorizing);
- self._credentials.token = data.access_token;
- self.authenticated = true;
- App.vent.trigger('system:tvstAuthenticated');
- // Store the credentials (hashed ofc)
- AdvSettings.set('tvstAccessToken', data.access_token);
- }
- });
- }, (data.interval + 1) * 1000);
- callback(activateUri);
- }
- });
- };
-
-
- TVShowTime.prototype.disconnect = function (callback) {
- this.authenticated = false;
- AdvSettings.set('tvstAccessToken', '');
- callback();
- };
-
-
- TVShowTime.prototype.checkin = function (show) {
- this
- .post('checkin', {
- 'show_id': show.tvdb_id,
- 'season_number': show.season,
- 'number': show.episode,
- 'access_token': this._credentials.token
- })
- .then(function (data) {
- //console.log(data);
- });
- };
-
- TVShowTime.prototype.checkout = function (show) {
- this
- .post('checkout', {
- 'show_id': show.tvdb_id,
- 'season_number': show.season,
- 'number': show.episode,
- 'access_token': this._credentials.token
- })
- .then(function (data) {
- //console.log(data);
- });
- };
-
- function onShowWatched(show, channel) {
- if (App.TVShowTime.authenticated) {
- App.TVShowTime.checkin(show);
- }
- }
-
- function onShowUnWatched(show, channel) {
- if (App.TVShowTime.authenticated) {
- App.TVShowTime.checkout(show);
- }
- }
-
- App.vent.on('show:watched', onShowWatched);
- App.vent.on('show:unwatched', onShowUnWatched);
-
- App.Providers.TVShowTime = TVShowTime;
- App.Providers.install(TVShowTime);
-
-})(window.App);
diff --git a/src/app/lib/providers/watchlist.js b/src/app/lib/providers/watchlist.js
index de9ea79aa8..a0679bce92 100644
--- a/src/app/lib/providers/watchlist.js
+++ b/src/app/lib/providers/watchlist.js
@@ -61,60 +61,32 @@
return Promise.all(
items.map(function(item) {
if (item.next_episode) {
- if (
- moment(item.next_episode.first_aired)
- .fromNow()
- .indexOf('in') !== -1
- ) {
- console.log(
- '"%s" is not released yet, not showing',
- item.show.title +
- ' ' +
- item.next_episode.season +
- 'x' +
- item.next_episode.number
- );
- } else {
- var show = item.show;
- show.type = 'show';
- show.episode = item.next_episode.number;
- show.season = item.next_episode.season;
- show.episode_title = item.next_episode.title;
- show.episode_id = item.next_episode.ids.tvdb;
- show.episode_aired = item.next_episode.first_aired;
- show.imdb_id = item.show.ids.imdb;
- show.tvdb_id = item.show.ids.tvdb;
- show.rating = item.show.rating;
- show.title = item.show.title;
- show.trailer = item.show.trailer;
- show.unseen = item.unseen;
-
- itemList.push(show);
- }
- } else {
- if (item.movie) {
- if (
- moment(item.movie.released)
- .fromNow()
- .indexOf('in') !== -1
- ) {
- console.log(
- '"%s" is not released yet, not showing',
- item.movie.title
- );
- } else {
- var movie = item.movie;
- movie.type = 'movie';
- movie.listed_at = item.listed_at;
- movie.imdb_id = item.movie.ids.imdb;
- movie.rating = item.movie.rating;
- movie.title = item.movie.title;
- movie.trailer = item.movie.trailer;
- movie.year = item.movie.year;
-
- itemList.push(movie);
- }
- }
+ var show = item.show;
+ show.type = 'show';
+ show.episode = item.next_episode.number;
+ show.season = item.next_episode.season;
+ show.episode_title = item.next_episode.title;
+ show.episode_id = item.next_episode.ids.tvdb;
+ show.episode_aired = item.next_episode.first_aired;
+ show.imdb_id = item.show.ids.imdb;
+ show.tvdb_id = item.show.ids.tvdb;
+ show.rating = item.show.rating;
+ show.title = item.show.title;
+ show.trailer = item.show.trailer;
+ show.unseen = item.unseen;
+
+ itemList.push(show);
+ } else if (item.movie) {
+ var movie = item.movie;
+ movie.type = 'movie';
+ movie.listed_at = item.listed_at;
+ movie.imdb_id = item.movie.ids.imdb;
+ movie.rating = item.movie.rating;
+ movie.title = item.movie.title;
+ movie.trailer = item.movie.trailer;
+ movie.year = item.movie.year;
+
+ itemList.push(movie);
}
})
)
diff --git a/src/app/lib/streamer.js b/src/app/lib/streamer.js
index 0b03979827..8c094d68bb 100644
--- a/src/app/lib/streamer.js
+++ b/src/app/lib/streamer.js
@@ -7,28 +7,24 @@
this.torrent = null;
// Torrent Backbone Model
this.torrentModel = null;
-
// State Backbone Model
this.stateModel = null;
-
- // Stream Info Backbone Model, which keeps showing ratio/download/upload info.
- // See models/stream_info.js
+ // Stream Info Backbone Model, which keeps showing ratio/download/upload info - See models/stream_info.js
this.streamInfo = null;
- // Interval controller for StreamInfo view, which keeps showing ratio/download/upload info.
- // See models/stream_info.js
+ // Interval controller for StreamInfo view, which keeps showing ratio/download/upload info - See models/stream_info.js
this.updateStatsInterval = null;
// video dummy element
this.video = null;
-
// Boolean to indicate if subtitles are already downloaded and ready to use
this.subtitleReady = false;
-
// Boolean to indicate if the video file is ready
this.canPlay = false;
-
-
// Boolean to indicate if the process was interrupted
this.stopped = true;
+ // Boolean to indicate if Watch now or just Download
+ this.downloadOnly = false;
+ // Boolean to indicate preload episode state
+ this.preload = false;
};
WebTorrentStreamer.prototype = {
@@ -109,52 +105,34 @@
});
},
- // wrapper for handling a torrent
- start: function(model) {
- // if webtorrent is created/running, we stop/destroy it
- if (App.WebTorrent.destroyed) {
- this.stop();
- }
-
- this.setModels(model);
-
- this.fetchTorrent(this.torrentModel.get('torrent'), App.settings.tmpLocation).then(function (torrent) {
- this.torrentModel.set('torrent', this.torrent = torrent);
- this.linkTransferStatus();
- this.handleTorrent(torrent);
- this.handleStreamInfo();
- this.watchState();
- this.saveCoverToFile();
- return this.createServer();
- }.bind(this)).then(this.waitForBuffer.bind(this)).catch(this.handleErrors.bind(this));
- },
-
- download: function(torrent, mediaName = '', fileName = '') {
- // if webtorrent is created/running, we stop/destroy it
+ start: function(model, state) {
if (App.WebTorrent.destroyed) {
this.stop();
}
- // handles magnet and hosted torrents
- const uri = Common.getTorrentUri(torrent);
- const parseTorrent = require('parse-torrent');
- var infoHash = '';
- try { infoHash = parseTorrent(uri).infoHash; } catch (err) {}
-
- if (this.torrent && this.torrent.infoHash === infoHash) {
- return;
- }
+ this.setModels(model, state);
+ const location = this.downloadOnly && App.settings.separateDownloadsDir ? App.settings.downloadsLocation : App.settings.tmpLocation;
- if (mediaName) {
- App.plugins.mediaName.setMediaName(infoHash, mediaName);
+ if (!this.downloadOnly && !this.preload) {
+ this.fetchTorrent(this.torrentModel.get('torrent'), location, model.get('title')).then(function (torrent) {
+ this.torrentModel.set('torrent', this.torrent = torrent);
+ this.linkTransferStatus();
+ this.handleTorrent(torrent);
+ this.handleStreamInfo();
+ this.watchState();
+ this.saveCoverToFile(location);
+ return this.createServer();
+ }.bind(this)).then(this.waitForBuffer.bind(this)).catch(this.handleErrors.bind(this));
+ } else {
+ this.fetchTorrent(this.torrentModel.get('torrent'), location, model.get('title')).then(function (torrent) {
+ this.torrentModel.set('torrent', torrent);
+ this.handleTorrent(torrent);
+ this.saveCoverToFile(location);
+ return;
+ }.bind(this));
}
- const location = App.settings.separateDownloadsDir ? App.settings.downloadsLocation : App.settings.tmpLocation;
- this.fetchTorrent(uri, location).then(function (torrent) {
- this.selectFile(torrent, fileName);
- }.bind(this));
},
- // kill the streamer
stop: function() {
if (this.torrent) {
// update ratio
@@ -162,15 +140,15 @@
AdvSettings.set('totalUploaded', Settings.totalUploaded + this.torrent.uploaded);
if (Settings.activateSeedbox) {
- this.torrent.pause();
- // complete pause torrent, stop download data
const removedPeers = [];
+ this.torrent.pause();
+
for (const id in this.torrent._peers) {
// collect peers, need to do this before calling removePeer!
removedPeers.push(this.torrent._peers[id].addr);
-
this.torrent.removePeer(id);
}
+
if(removedPeers.length > 0) {
// store removed peers, so we can re-add them when resuming
this.torrent.pctRemovedPeers = removedPeers;
@@ -182,7 +160,17 @@
});
}
} else {
- this.torrent.destroy();
+ App.WebTorrent.destroy();
+ App.WebTorrent = new WebTorrent({
+ maxConns : parseInt(Settings.connectionLimit, 10) || 55,
+ downloadLimit: parseInt(parseFloat(Settings.downloadLimit, 10) * parseInt(Settings.maxLimitMult, 10)) || -1,
+ uploadLimit : parseInt(parseFloat(Settings.uploadLimit, 10) * parseInt(Settings.maxLimitMult, 10)) || -1,
+ dht : true,
+ secure : Settings.protocolEncryption || false,
+ tracker : {
+ announce: Settings.trackers.forced
+ }
+ });
}
}
@@ -214,7 +202,25 @@
AdvSettings.set('totalDownloaded', Settings.totalDownloaded + this.torrent.downloaded);
AdvSettings.set('totalUploaded', Settings.totalUploaded + this.torrent.uploaded);
- this.torrent.destroy();
+ const removedPeers = [];
+ this.torrent.pause();
+
+ for (const id in this.torrent._peers) {
+ // collect peers, need to do this before calling removePeer!
+ removedPeers.push(this.torrent._peers[id].addr);
+ this.torrent.removePeer(id);
+ }
+
+ if(removedPeers.length > 0) {
+ // store removed peers, so we can re-add them when resuming
+ this.torrent.pctRemovedPeers = removedPeers;
+ }
+
+ if (this.torrent._xsRequests) {
+ this.torrent._xsRequests.forEach(req => {
+ req.abort();
+ });
+ }
}
if (this.video) {
@@ -246,7 +252,7 @@
},
// fire webtorrent and resolve the torrent
- fetchTorrent: function(torrentInfo, path) {
+ fetchTorrent: function(torrentInfo, path, mediaName) {
return new Promise(function (resolve, reject) {
// handles magnet and hosted torrents
@@ -256,6 +262,8 @@
try { infoHash = parseTorrent(uri).infoHash; } catch (err) {}
var torrent;
+ App.plugins.mediaName.setMediaName(infoHash, mediaName);
+
for(const t of App.WebTorrent.torrents) {
if (t.infoHash === infoHash) {
torrent = t;
@@ -273,9 +281,10 @@
if (!torrent) {
torrent = App.WebTorrent.add(uri, {
- path: path,
+ path : path,
maxConns : 10,
dht : true,
+ secure : Settings.protocolEncryption || false,
announce : Settings.trackers.forced,
tracker : Settings.trackers.forced
});
@@ -436,18 +445,23 @@
if (!fileName) {
for (let i in torrent.files) {
if (fileSize < torrent.files[i].length) {
+ fileIndex = i;
fileSize = torrent.files[i].length;
fileName = torrent.files[i].path;
}
}
}
- for (var f in torrent.files) { // Add selection
- var file = torrent.files[f];
+ for (let f in torrent.files) { // Add selection
+ let file = torrent.files[f];
+ // windows specific fix
+ let path = file.path.replace(/\\/g, '/');
+ let name = fileName.replace(/\\/g, '/');
// we use endsWith, not equals because from server may return without first directory
- if (file.path.endsWith(fileName)) {
+ if (path.endsWith(name)) {
fileIndex = f;
fileSize = file.length;
+ fileName = file.path;
file.select();
} else {
// file.deselect();
@@ -455,10 +469,10 @@
}
return {
- name: path.basename(torrent.files[fileIndex].path),
+ name: path.basename(fileName),
size: fileSize,
index: fileIndex,
- path: path.join(torrent.path, torrent.files[fileIndex].path)
+ path: path.join(torrent.path, fileName)
};
},
@@ -544,11 +558,12 @@
}.bind(this));
},
- setModels: function (model) {
+ setModels: function (model, state) {
this.stopped = false;
+ this.downloadOnly = state === 'downloadOnly' ? true : false;
+ this.preload = state === 'preload' ? true : false;
this.torrentModel = model;
this.streamInfo = new App.Model.StreamInfo();
-
this.stateModel = new Backbone.Model({
state: 'connecting',
backdrop: this.torrentModel.get('backdrop'),
@@ -557,11 +572,15 @@
show_controls: false,
streamInfo: this.streamInfo
});
- App.vent.trigger('stream:started', this.stateModel);
+ if (!this.downloadOnly && !this.preload) {
+ App.vent.trigger('stream:started', this.stateModel);
+ } else {
+ this.stopped = true;
+ }
},
watchState: function () {
- if (this.stopped) {
+ if (this.stopped) {
return;
}
if (!this.torrent) {
@@ -603,7 +622,7 @@
}
},
- saveCoverToFile: function () {
+ saveCoverToFile: function (location) {
if (this.torrentModel && this.torrentModel.get('type') === 'movie' && this.torrentModel.get('cover') && this.torrentModel.get('torrent').name) {
const request = require('request');
let url = this.torrentModel.get('cover');
@@ -611,13 +630,13 @@
if (err || buffer.length < 1000) {
return;
}
- fs.writeFileSync(path.join(App.settings.tmpLocation, this.torrentModel.get('torrent').name) + '/cover.jpg', buffer);
+ fs.writeFileSync(path.join(location, this.torrentModel.get('torrent').name) + '/cover.jpg', buffer);
});
}
},
onSubtitlesFound: function (subs) {
- if (this.stopped) {
+ if (this.stopped && !this.downloadOnly && !this.preload) {
return;
}
@@ -676,7 +695,8 @@
// download the subtitle
App.vent.trigger('subtitle:download', {
url: subtitles[defaultSubtitle],
- path: this.torrentModel.get('video_file').path
+ path: this.torrentModel.get('video_file').path,
+ lang: this.torrentModel.get('defaultSubtitle')
});
}
} else {
@@ -706,7 +726,7 @@
},
handleSubtitles: function () {
- if (this.stopped) {
+ if (this.stopped && !this.downloadOnly && !this.preload) {
return;
}
// set default subtitle language (passed by a view or settings)
@@ -737,7 +757,7 @@
},
buildSubtitleQuery: function () {
- if (this.stopped) {
+ if (this.stopped && !this.downloadOnly && !this.preload) {
return;
}
@@ -780,6 +800,5 @@
App.vent.on('stream:start', streamer.start.bind(streamer));
App.vent.on('stream:stop', streamer.stop.bind(streamer));
App.vent.on('stream:stopFS', streamer.stopFS.bind(streamer));
- App.vent.on('stream:download', streamer.download.bind(streamer));
App.vent.on('stream:serve_subtitles', streamer.serveSubtitles.bind(streamer));
})(window.App);
diff --git a/src/app/lib/subtitle/generic.js b/src/app/lib/subtitle/generic.js
index 0350f224eb..3bf73a464f 100644
--- a/src/app/lib/subtitle/generic.js
+++ b/src/app/lib/subtitle/generic.js
@@ -23,13 +23,12 @@
var downloadFromUrl = function (data) {
return new Promise(function (resolve, reject) {
- var streamInfo = App.LoadingView.model.get('streamInfo');
var vpath = data.path; // video file path
var vext = path.extname(vpath); // video extension
var vname = path.basename(vpath).substring(0, path.basename(vpath).lastIndexOf(vext)); // video file name
var folder = path.dirname(vpath); // cwd
var furl = data.url; // subtitle url
- var fpath = path.join(folder, vname + '.' + streamInfo.get('defaultSubtitle').substr(0,2)); // subtitle local path, no extension
+ var fpath = path.join(folder, vname + '.' + data.lang); // subtitle local path, no extension
request.get(furl).on('response', function (response) {
var rtype = (response.headers['content-type'] || '').split(';')[0].trim(); // response type
diff --git a/src/app/lib/views/browser/anime_browser.js b/src/app/lib/views/browser/anime_browser.js
index 858084a937..1f3d0ca6c8 100644
--- a/src/app/lib/views/browser/anime_browser.js
+++ b/src/app/lib/views/browser/anime_browser.js
@@ -3,11 +3,7 @@
var AnimeBrowser = App.View.PCTBrowser.extend({
collectionModel: App.Model.AnimeCollection,
- filters: {
- genres: App.Config.genres_anime,
- sorters: App.Config.sorters_anime,
- types: App.Config.types_anime
- }
+ providerType: 'anime',
});
App.View.AnimeBrowser = AnimeBrowser;
diff --git a/src/app/lib/views/browser/favorite_browser.js b/src/app/lib/views/browser/favorite_browser.js
index 68189623ad..ea507036a3 100644
--- a/src/app/lib/views/browser/favorite_browser.js
+++ b/src/app/lib/views/browser/favorite_browser.js
@@ -3,10 +3,7 @@
var FavoriteBrowser = App.View.PCTBrowser.extend({
collectionModel: App.Model.FavoriteCollection,
- filters: {
- types: App.Config.types_fav,
- sorters: App.Config.sorters_fav
- }
+ provider: 'Favorites',
});
App.View.FavoriteBrowser = FavoriteBrowser;
diff --git a/src/app/lib/views/browser/filter_bar.js b/src/app/lib/views/browser/filter_bar.js
index e5f4727e8c..f2e818ef61 100644
--- a/src/app/lib/views/browser/filter_bar.js
+++ b/src/app/lib/views/browser/filter_bar.js
@@ -2,6 +2,7 @@
'use strict';
App.View.FilterBar = Marionette.View.extend({
+ template: '#filter-bar-tpl',
className: 'filter-bar',
ui: {
searchForm: '.search form',
@@ -38,7 +39,10 @@
},
initialize: function(e) {
- App.vent.on('filter-bar:render', this.render);
+ App.vent.on('filter-bar:render', () => {
+ this.render();
+ this.setActive(App.currentview);
+ });
if (VPNht.isInstalled()) {
VPNht.isConnected().then(isConnected => {
@@ -126,9 +130,13 @@
break;
}
- try {
- this.fixFilter();
- } catch (e) {}
+ $('#nav-filters .filter').each(function(i, item) {
+ $(item).find('.active').removeClass('active');
+ const value = $(item).find('.value');
+ const li = $(item).find('li a[data-value="' + value.data('value') + '"]');
+ li.addClass('active');
+ value.text(li.text());
+ });
},
rightclick_search: function(e) {
e.preventDefault();
@@ -224,30 +232,6 @@
focusSearch: function() {
this.$('.search input').focus();
},
- fixFilter: function() {
- $('.genres .active').removeClass('active');
- $('.sorters .active').removeClass('active');
- $('.types .active').removeClass('active');
- $('.ratings .active').removeClass('active');
-
- var genre = $('.genres .value').data('value');
- var sorter = $('.sorters .value').data('value');
- var type = $('.types .value').data('value');
- var rating = $('.ratings .value').data('value');
-
- $('.genres li')
- .find('[data-value="' + genre + '"]')
- .addClass('active');
- $('.sorters li')
- .find('[data-value="' + sorter + '"]')
- .addClass('active');
- $('.types li')
- .find('[data-value="' + type + '"]')
- .addClass('active');
- $('.ratings li')
- .find('[data-value="' + rating + '"]')
- .addClass('active');
- },
search: function(e) {
App.vent.trigger('about:close');
App.vent.trigger('torrentCollection:close');
@@ -325,7 +309,7 @@
});
}
- this.ui.sorterValue.text(i18n.__(sorter.capitalizeEach()));
+ this.ui.sorterValue.text($(e.target).text());
this.previousSort = sorter;
},
@@ -337,7 +321,7 @@
$(e.target).addClass('active');
var type = $(e.target).attr('data-value');
- this.ui.typeValue.text(i18n.__(type));
+ this.ui.typeValue.text($(e.target).text());
this.model.set({
keyword: '',
@@ -352,7 +336,7 @@
var genre = $(e.target).attr('data-value');
- this.ui.genreValue.text(i18n.__(genre.capitalizeEach()));
+ this.ui.genreValue.text($(e.target).text());
this.model.set({
keyword: '',
@@ -366,9 +350,7 @@
$(e.target).addClass('active');
const rating = $(e.target).attr('data-value');
- const ratingLabel = rating === 'All' ? rating : `${rating}+`;
-
- this.ui.ratingValue.text(i18n.__(ratingLabel.capitalizeEach()));
+ this.ui.ratingValue.text($(e.target).text());
this.model.set({
keyword: '',
@@ -508,8 +490,4 @@
randomMovie: function() {}
});
-
- App.View.FilterBar = App.View.FilterBar.extend({
- template: '#filter-bar-tpl'
- });
})(window.App);
diff --git a/src/app/lib/views/browser/generic_browser.js b/src/app/lib/views/browser/generic_browser.js
index 5901f25284..f3fc34932d 100644
--- a/src/app/lib/views/browser/generic_browser.js
+++ b/src/app/lib/views/browser/generic_browser.js
@@ -26,17 +26,20 @@
},
initialize: function () {
- this.filter = new App.Model.Filter(this.filters);
-
+ const provider = this.provider ? App.Providers.get(this.provider) : App.Config.getProviderForType(this.providerType)[0];
+ let initFilter = {};
if (Settings.defaultFilters === 'custom' || Settings.defaultFilters === 'remember') {
- this.filter.set(this.getSavedFilter());
+ initFilter = this.getSavedFilter();
}
+ initFilter.provider = provider;
- this.collection = new this.collectionModel([], {
- filter: this.filter
- });
+ this.filter = new App.Model.Filter(initFilter);
- this.collection.fetch();
+ // this.collection = new this.collectionModel([], {
+ // filter: this.filter
+ // });
+ //
+ // this.collection.fetch();
this.listenTo(this.filter, 'change', this.onFilterChange);
@@ -49,9 +52,9 @@
this.showChildView('FilterBar', this.bar);
- this.showChildView('ItemList', new App.View.List({
- collection: this.collection
- }));
+ // this.showChildView('ItemList', new App.View.List({
+ // collection: this.collection
+ // }));
if (!isNaN(startupTime)) {
win.debug('Butter %s startup time: %sms', Settings.version, (window.performance.now() - startupTime).toFixed(3)); // started in database.js;
@@ -76,6 +79,9 @@
},
onFilterChange: function () {
+ if (!this.filter.get('load')) {
+ return;
+ }
if (Settings.defaultFilters === 'remember' || curSetDefaultFilters) {
this.saveFilter();
}
@@ -166,7 +172,7 @@
getSavedFilter: function () {
var filters = AdvSettings.get('filters') || {};
- return filters[this.currentView()] || this.filter.pick('sorter', 'genre', 'type', 'order', 'rating');
+ return filters[this.currentView()] || {};
}
});
diff --git a/src/app/lib/views/browser/indie_browser.js b/src/app/lib/views/browser/indie_browser.js
deleted file mode 100644
index 7056fef77f..0000000000
--- a/src/app/lib/views/browser/indie_browser.js
+++ /dev/null
@@ -1,14 +0,0 @@
-(function (App) {
- 'use strict';
-
- var IndieBrowser = App.View.PCTBrowser.extend({
- collectionModel: App.Model.IndieCollection,
- filters: {
- genres: App.Config.genres_indie,
- sorters: App.Config.sorters_indie
- //types: App.Config.types_indie
- }
- });
-
- App.View.IndieBrowser = IndieBrowser;
-})(window.App);
diff --git a/src/app/lib/views/browser/item.js b/src/app/lib/views/browser/item.js
index 33dec578f2..a84fd3fb94 100644
--- a/src/app/lib/views/browser/item.js
+++ b/src/app/lib/views/browser/item.js
@@ -33,6 +33,8 @@
initialize: function () {
this.setModelStates();
this.isAprilFools();
+ this.localizeTexts();
+ this.setQualityDisplayed();
},
onAttach: function () {
@@ -48,6 +50,40 @@
});
},
+ setQualityDisplayed: function() {
+ let torrents = this.model.get('torrents');
+ if (!App.settings.moviesShowQuality || !torrents) {
+ this.model.set('qualityList', '');
+ return;
+ }
+ let keys = Object.keys(torrents).sort(Common.qualityCollator.compare);
+ keys = keys.filter((key) => key !== '480p' && key !== '3D');
+ this.model.set('qualityList', keys.length ? keys.join('/') : 'HDRip');
+ },
+
+ localizeTexts: function () {
+ var title = this.model.get('title');
+ var locale = this.model.get('locale');
+
+ let title1 = title;
+ let title2;
+ if (locale && locale.title) {
+ if (Settings.translateTitle === 'translated-origin') {
+ title1 = locale.title;
+ title2 = title;
+ }
+ if (Settings.translateTitle === 'origin-translated') {
+ title2 = locale.title;
+ }
+ if (Settings.translateTitle === 'translated') {
+ title1 = locale.title;
+ }
+ }
+
+ this.model.set('title1', title1);
+ this.model.set('title2', title2);
+ },
+
hoverItem: function (e) {
if (e.pageX !== prevX || e.pageY !== prevY) {
$('.item.selected').removeClass('selected');
@@ -166,29 +202,18 @@
this.model.set('getmetarunned', true);
}
- var setImage = function (img) {
- if (this.ui.cover.css) {
- this.ui.cover.css('background-image', 'url(' + img + ')').addClass('fadein');
+ if (Settings.translatePosters) {
+ var locale = this.model.get('locale');
+ if (locale && locale.poster) {
+ poster = locale.poster;
}
- }.bind(this);
-
- var posterCache = new Image();
- posterCache.src = poster;
-
- posterCache.onload = function () {
- if (poster.indexOf('.gif') !== -1) { // freeze gifs
- var c = document.createElement('canvas');
- var w = c.width = posterCache.width;
- var h = c.height = posterCache.height;
+ }
- c.getContext('2d').drawImage(posterCache, 0, 0, w, h);
- poster = c.toDataURL();
+ Common.loadImage(poster).then((img) => {
+ if (this.ui.cover.css) {
+ this.ui.cover.css('background-image', 'url(' + (img || noimg) + ')').addClass('fadein');
}
- setImage(poster);
- };
- posterCache.onerror = function (e) {
- setImage(noimg);
- };
+ });
},
setTooltips: function () {
diff --git a/src/app/lib/views/browser/list.js b/src/app/lib/views/browser/list.js
index fe97dbad38..2fe42bdfce 100644
--- a/src/app/lib/views/browser/list.js
+++ b/src/app/lib/views/browser/list.js
@@ -108,21 +108,24 @@
var errorURL;
switch (App.currentview) {
case 'movies':
- errorURL = App.Config.getProviderForType('movie')[0].apiURL[0];
+ errorURL = App.Config.getProviderForType('movie')[0].apiURL.slice(0);
break;
case 'shows':
- errorURL = App.Config.getProviderForType('tvshow')[0].apiURL[0];
+ errorURL = App.Config.getProviderForType('tvshow')[0].apiURL.slice(0);
break;
case 'anime':
- errorURL = App.Config.getProviderForType('anime')[0].apiURL[0];
+ errorURL = App.Config.getProviderForType('anime')[0].apiURL.slice(0);
break;
default:
errorURL = '';
}
- var dspURL = errorURL.slice(-1) === '/' ? errorURL.replace(/http:\/\/|https:\/\//g, '').slice(0, -1) : errorURL.replace(/http:\/\/|https:\/\//g, '');
+ errorURL.forEach(function(e, index) {
+ errorURL[index] = '' + e.replace(/http:\/\/|https:\/\/|\/$/g, '') + '';
+ });
+ errorURL = errorURL.join(', ').replace(/,(?=[^,]*$)/, ' &');
return ErrorView.extend({
retry: true,
- error: i18n.__('The remote ' + App.currentview + ' API failed to respond, please check %s and try again later', '' + dspURL + '')
+ error: i18n.__('The remote ' + App.currentview + ' API failed to respond, please check %s and try again later', errorURL)
});
} else if (this.collection.state !== 'loading') {
return ErrorView.extend({
diff --git a/src/app/lib/views/browser/movie_browser.js b/src/app/lib/views/browser/movie_browser.js
index e56c490ebf..283ed9b034 100644
--- a/src/app/lib/views/browser/movie_browser.js
+++ b/src/app/lib/views/browser/movie_browser.js
@@ -3,10 +3,7 @@
var MovieBrowser = App.View.PCTBrowser.extend({
collectionModel: App.Model.MovieCollection,
- filters: {
- genres: App.Config.genres,
- sorters: App.Config.sorters
- }
+ providerType: 'movie',
});
App.View.MovieBrowser = MovieBrowser;
diff --git a/src/app/lib/views/browser/show_browser.js b/src/app/lib/views/browser/show_browser.js
index 3b7e72b208..3282e06b53 100644
--- a/src/app/lib/views/browser/show_browser.js
+++ b/src/app/lib/views/browser/show_browser.js
@@ -3,10 +3,7 @@
var ShowBrowser = App.View.PCTBrowser.extend({
collectionModel: App.Model.ShowCollection,
- filters: {
- genres: App.Config.genres_tv,
- sorters: App.Config.sorters_tv
- }
+ providerType: 'tvshow',
});
App.View.ShowBrowser = ShowBrowser;
diff --git a/src/app/lib/views/lang_dropdown.js b/src/app/lib/views/lang_dropdown.js
index 85160ac037..1950d0747c 100644
--- a/src/app/lib/views/lang_dropdown.js
+++ b/src/app/lib/views/lang_dropdown.js
@@ -57,11 +57,14 @@
setLang: function (value) {
this.model.set('selected', value);
- if (value !== 'none') {
- this.ui.selected.removeClass().addClass('flag toggle selected-lang').addClass(value.substr(0,2));
- } else {
- this.ui.selected.removeClass().addClass('flag toggle selected-lang').addClass(value);
+ const langClass = value === 'none' ? value : value.substr(0,2);
+ this.ui.selected.removeClass().addClass('flag toggle selected-lang').addClass(langClass);
+ let title = App.Localization.nativeName(value);
+ if (langClass !== 'none' && langClass !== 'en') {
+ title += ' (' + App.Localization.name(value).replace(/\(|\)/g, '') + ')';
}
+ this.ui.selected.attr('title', title)
+ .tooltip({delay: {show: 800, hide: 100}, html: true}).tooltip('fixTitle');
App.vent.trigger(this.type + ':lang', value);
},
diff --git a/src/app/lib/views/main_window.js b/src/app/lib/views/main_window.js
index 1af76aa6cf..fb3c227cc4 100644
--- a/src/app/lib/views/main_window.js
+++ b/src/app/lib/views/main_window.js
@@ -170,11 +170,6 @@
)
);
- App.vent.on(
- 'system:tvstAuthenticated',
- _.bind(this.tvstAuthenticated, this)
- );
-
// Stream events
App.vent.on('stream:started', _.bind(this.streamStarted, this));
App.vent.on('stream:ready', _.bind(this.streamReady, this));
@@ -558,10 +553,6 @@
);
},
- tvstAuthenticated: function() {
- win.info('TVShow Time: authenticated');
- },
-
streamStarted: function(stateModel) {
// People wanted to keep the active
// modal (tvshow/movie) detail open when
@@ -590,9 +581,11 @@
model: streamModel
})
);
- this.getRegion('Content').$el.hide();
- if (this.getRegion('MovieDetail').$el !== undefined) {
- this.getRegion('MovieDetail').$el.hide();
+ if ($('.loading .maximize-icon').is(':hidden')) {
+ this.getRegion('Content').$el.hide();
+ if (this.getRegion('MovieDetail').$el !== undefined) {
+ this.getRegion('MovieDetail').$el.hide();
+ }
}
},
diff --git a/src/app/lib/views/movie_detail.js b/src/app/lib/views/movie_detail.js
index 13b0f83aed..15d1929f7c 100644
--- a/src/app/lib/views/movie_detail.js
+++ b/src/app/lib/views/movie_detail.js
@@ -16,21 +16,18 @@
watchedIcon: '.watched-toggle',
backdrop: '.backdrop',
poster: '.mcover-image',
- q2160p: '.q2160',
- q1080p: '.q1080',
- q720p: '.q720'
},
events: {
'click .close-icon': 'closeDetails',
+ 'click .year': 'openRelInfo',
+ 'click .certification': 'openCert',
'click .movie-imdb-link': 'openIMDb',
'mousedown .magnet-link': 'openMagnet',
+ 'mousedown .source-link': 'openSource',
'click .rating-container': 'switchRating',
'click .show-cast': 'showCast',
'click .showall-cast': 'showallCast',
- 'click .q2160': 'toggleShowQuality',
- 'click .q1080': 'toggleShowQuality',
- 'click .q720': 'toggleShowQuality',
'click .health-icon': 'resetTorrentHealth',
'mousedown .mcover-image': 'clickPoster'
},
@@ -70,80 +67,65 @@
App.vent.on('shortcuts:movies', _this.initKeyboardShortcuts);
- App.vent.on('change:quality', healthButton.render, this);
+ App.vent.on('change:quality', _this.onChangeQuality.bind(_this));
+ // init fields in model
+ this.model.set('displayTitle', '');
+ this.model.set('displaySynopsis', '');
+ this.localizeTexts();
},
- toggleShowQuality: function(e) {
- if ($(e.currentTarget).hasClass('disabled')) {
- return;
- }
- var quality = $(e.currentTarget);
- var currentQuality = quality.text();
- if (currentQuality === '4K') {
- currentQuality = '2160p';
- }
- this.updateQuality(currentQuality);
- },
-
- updateQuality: function(quality) {
+ onChangeQuality: function (quality) {
this.model.set('quality', quality);
- this.refreshUiQuality();
+ this.toggleSourceLink(quality);
+ win.debug('about to render health button');
+ healthButton.render();
},
- refreshUiQuality: function() {
- win.debug('quality changed');
- const quality = this.model.get('quality');
- const torrents = this.model.get('torrents');
-
- if (torrents['2160p'] === undefined) {
- $('.q2160').addClass('disabled');
- }
-
- if (torrents['1080p'] === undefined) {
- $('.q1080').addClass('disabled');
- }
-
- if (torrents['720p'] === undefined) {
- $('.q720').addClass('disabled');
- }
-
- if (quality === '2160p') {
- $('.q2160').addClass('active');
-
- $('.q1080').removeClass('active');
- $('.q720').removeClass('active');
- } else if (quality === '1080p') {
- $('.q1080').addClass('active');
-
- $('.q2160').removeClass('active');
- $('.q720').removeClass('active');
+ toggleSourceLink: function(quality) {
+ const sourceURL = this.model.get('torrents')[quality].source;
+ if (sourceURL) {
+ $('.source-link').show().attr('data-original-title', sourceURL.split('//').pop().split('/')[0]);
} else {
- $('.q720').addClass('active');
-
- $('.q2160').removeClass('active');
- $('.q1080').removeClass('active');
+ $('.source-link').hide();
}
-
- win.debug('about to render health button');
- healthButton.render();
},
onAttach: function() {
- win.info('Show movie detail (' + this.model.get('imdb_id') + ')');
+ win.info('Show movie details (' + this.model.get('imdb_id') + ')');
App.MovieDetailView = this;
+ this.localizeTexts();
this.hideUnused();
this.loadImages();
this.loadComponents();
this.initKeyboardShortcuts();
healthButton.render();
- this.refreshUiQuality();
-
if (curSynopsis.vstatus !== null && curSynopsis.cast === '') {
this.showCast();
}
+
+ $('[data-toggle="tooltip"]').tooltip({
+ html: true
+ });
+ },
+ localizeTexts: function() {
+ const locale = this.model.get('locale');
+ let title = this.model.get('title');
+ if (Settings.translateTitle === 'translated-origin' || Settings.translateTitle === 'translated') {
+ if (locale && locale.title) {
+ title = locale.title;
+ }
+ }
+ let synopsis = this.model.get('synopsis');
+ if (Settings.translateSynopsis) {
+ if (locale && locale.synopsis) {
+ synopsis = locale.synopsis;
+ }
+ }
+ this.model.set('displayTitle', title);
+ this.model.set('displaySynopsis', synopsis);
},
loadComponents: function() {
// play control
@@ -158,38 +140,6 @@
var noimg = 'images/posterholder.png';
var nobg = 'images/bg-header.jpg';
- var setImage = {
- poster: function(img) {
- this.ui.poster.attr('src', img || noimg).addClass('fadein');
- }.bind(this),
- backdrop: function(img) {
- this.ui.backdrop
- .css('background-image', 'url(' + (img || nobg) + ')')
- .addClass('fadein');
- }.bind(this)
- };
-
- var loadImage = function(img, type) {
- var cache = new Image();
- cache.src = img;
-
- cache.onload = function() {
- if (img.indexOf('.gif') !== -1) {
- // freeze gifs
- var c = document.createElement('canvas');
- var w = (c.width = img.width);
- var h = (c.height = img.height);
-
- c.getContext('2d').drawImage(cache, 0, 0, w, h);
- img = c.toDataURL();
- }
- setImage[type](img);
- };
-
- cache.onerror = function(e) {
- setImage[type](null);
- };
- };
var images = this.model.get('images');
var p =
this.model.get('image') ||
@@ -201,17 +151,31 @@
this.model.get('backdrop') ||
this.model.get('poster') ||
nobg;
- loadImage(p, 'poster');
- loadImage(b, 'backdrop');
+
+ if (Settings.translatePosters) {
+ var locale = this.model.get('locale');
+ if (locale) {
+ p = locale.poster ? locale.poster : p;
+ b = locale.backdrop ? locale.backdrop : b;
+ }
+ }
+
+ Common.loadImage(p).then((img) => {
+ this.ui.poster.attr('src', img || noimg).addClass('fadein');
+ });
+ Common.loadImage(b).then((img) => {
+ this.ui.backdrop
+ .css('background-image', 'url(' + (img || nobg) + ')')
+ .addClass('fadein');
+ });
},
hideUnused: function() {
var id = this.model.get('imdb_id');
- win.info('hideunused (' + this.model.get('imdb_id') + ')');
if (!this.model.get('torrents')) {
// no torrents
- $('.magnet-link, .health-icon').hide();
+ $('.magnet-link, .health-icon, .source-link').hide();
}
if (!this.model.get('rating')) {
@@ -225,18 +189,6 @@
}
},
- handleAnime: function() {
- var id = this.model.get('imdb_id');
- if (id && id.indexOf('mal') === -1) {
- return;
- }
-
- $(
- '.movie-imdb-link, .rating-container, .magnet-link, .health-icon'
- ).hide();
- $('.dot').css('opacity', 0);
- },
-
getMetaData: function () {
curSynopsis.vstatus = false;
var imdb = this.model.get('imdb_id'),
@@ -360,10 +312,16 @@
healthButton.render();
},
- openIMDb: function() {
- nw.Shell.openExternal(
- 'https://www.imdb.com/title/' + this.model.get('imdb_id')
- );
+ openRelInfo: function () {
+ nw.Shell.openExternal('https://www.imdb.com/title/' + this.model.get('imdb_id') + '/releaseinfo');
+ },
+
+ openCert: function () {
+ nw.Shell.openExternal('https://www.imdb.com/title/' + this.model.get('imdb_id') + '/parentalguide');
+ },
+
+ openIMDb: function () {
+ nw.Shell.openExternal('https://www.imdb.com/title/' + this.model.get('imdb_id'));
},
openMagnet: function(e) {
@@ -390,6 +348,31 @@
} else {
nw.Shell.openExternal(magnetLink);
}
+ },
+
+ openSource: function(e) {
+ var torrent = this.model.get('torrents')[this.model.get('quality')],
+ sourceLink;
+
+ if (torrent.source) {
+ // Movies
+ sourceLink = torrent.source.replace(/\&/g, '&');
+ } else {
+ return;
+ }
+ if (e.button === 2) {
+ //if right click on magnet link
+ var clipboard = nw.Clipboard.get();
+ clipboard.set(sourceLink, 'text'); //copy link to clipboard
+ $('.notification_alert')
+ .text(i18n.__('The source link was copied to the clipboard'))
+ .fadeIn('fast')
+ .delay(2500)
+ .fadeOut('fast');
+ } else {
+ nw.Shell.openExternal(sourceLink);
+ }
}
+
});
})(window.App);
diff --git a/src/app/lib/views/play_control.js b/src/app/lib/views/play_control.js
index e74511d9bf..11e6932420 100644
--- a/src/app/lib/views/play_control.js
+++ b/src/app/lib/views/play_control.js
@@ -17,7 +17,8 @@
'click .playerchoicehelp': 'showPlayerList',
'click .watched-toggle': 'toggleWatched',
'click #subs-dropdown': 'hideTooltips',
- 'click #audio-dropdown': 'hideTooltips'
+ 'click #audio-dropdown': 'hideTooltips',
+ 'click #quality-selector': 'hideTooltips'
},
regions: {
subDropdown: '#subs-dropdown',
@@ -34,7 +35,9 @@
this.model.get('title')
);
if (!this.model.get('langs')) {
- this.model.set('langs', { en: undefined });
+ this.model.set('langs', { en: this.model.get('torrents') });
+ } else {
+ this.model.set('torrents', this.model.get('langs')[this.model.get('defaultAudio')]);
}
App.vent.on('sub:lang', this.switchSubtitle.bind(this));
@@ -62,7 +65,7 @@
this.model.on('change:langs', this.loadAudioDropdown.bind(this));
this.model.on('change:subtitle', this.loadSubDropdown.bind(this));
- if ($('.loading .maximize-icon').is(':visible')) {
+ if ($('.loading .maximize-icon').is(':visible') || $('.player .maximize-icon').is(':visible')) {
$('.button:not(#download-torrent)').addClass('disabled');
$('#watch-now, #watch-trailer, .playerchoice').prop('disabled', true);
}
@@ -70,6 +73,7 @@
setQuality: function(torrent, key) {
_this.model.set('quality', key);
+ App.vent.trigger('change:quality', key);
},
hideUnused: function() {
@@ -132,7 +136,7 @@
},
setUiStates: function() {
- $('.star-container,.movie-imdb-link,.q720,input,.magnet-link,.show-cast').tooltip({
+ $('.star-container,.movie-imdb-link,.q720,input,.magnet-link,.source-link,.show-cast').tooltip({
html: true
});
@@ -182,7 +186,7 @@
},
hideTooltips: function () {
- $('#subs-dropdown .flag.toggle, #audio-dropdown .flag.toggle').tooltip('hide');
+ $('#subs-dropdown .flag.toggle, #audio-dropdown .flag.toggle, #quality-selector .qselect').tooltip('hide');
},
switchSubtitle: function(lang) {
@@ -191,12 +195,6 @@
lang = 'none';
}
this.subtitle_selected = lang;
- if (lang === 'en') {
- $('#subs-dropdown .flag.toggle').attr('title', App.Localization.nativeName(lang)).tooltip({delay: {show: 800, hide: 100}, html: true}).tooltip('fixTitle');
- } else {
- $('#subs-dropdown .flag.toggle').attr('title', App.Localization.nativeName(lang) + '
(' + App.Localization.name(lang).replace(/\(|\)/g, '') + ')').tooltip({delay: {show: 800, hide: 100}, html: true}).tooltip('fixTitle');
- }
- console.info('Subtitles: ' + this.subtitle_selected);
},
switchAudio: function(lang) {
@@ -205,33 +203,15 @@
lang = 'none';
}
this.audio_selected = lang;
- if (lang === 'en') {
- $('#audio-dropdown .flag.toggle').attr('title', App.Localization.nativeName(lang)).tooltip({delay: {show: 800, hide: 100}, html: true}).tooltip('fixTitle');
- } else {
- $('#audio-dropdown .flag.toggle').attr('title', App.Localization.nativeName(lang) + '
(' + App.Localization.name(lang).replace(/\(|\)/g, '') + ')').tooltip({delay: {show: 800, hide: 100}, html: true}).tooltip('fixTitle');
+
+ if (this.getRegion('qualitySelector').currentView) {
+ this.model.set('torrents', audios[lang]);
+ this.getRegion('qualitySelector').currentView.updateTorrents(audios[lang]);
}
- console.info('Audios: ' + lang);
},
downloadTorrent: function() {
- var providers = this.model.get('providers');
- var quality = this.model.get('quality');
- var defaultTorrent = this.model.get('torrents')[quality];
-
- var filters = {
- quality: quality,
- lang: this.audio_selected
- };
-
- const torrent = providers.torrent.resolveStream
- ? providers.torrent.resolveStream(
- defaultTorrent,
- filters,
- this.model.attributes
- )
- : defaultTorrent;
-
- App.vent.trigger('stream:download', torrent, this.model.get('title') /*mediaName*/);
+ this.startStreaming('downloadOnly');
if (Settings.showSeedboxOnDlInit) {
App.previousview = App.currentview;
App.currentview = 'Seedbox';
@@ -244,7 +224,7 @@
}
},
- startStreaming: function() {
+ startStreaming: function(state) {
var providers = this.model.get('providers');
var quality = this.model.get('quality');
var defaultTorrent = this.model.get('torrents')[quality];
@@ -276,7 +256,7 @@
cover: this.model.get('cover')
});
- App.vent.trigger('stream:start', torrentStart);
+ App.vent.trigger('stream:start', torrentStart, state);
},
playTrailer: function() {
diff --git a/src/app/lib/views/player/loading.js b/src/app/lib/views/player/loading.js
index 4005417abd..d880a19010 100644
--- a/src/app/lib/views/player/loading.js
+++ b/src/app/lib/views/player/loading.js
@@ -52,10 +52,12 @@
'click .backward': 'backwardStreaming',
'click .minimize-icon': 'minDetails',
'click .maximize-icon': 'minDetails',
+ 'click #max_play_ctrl': 'maxPlayCtrl',
'click .show-pcontrols': 'showpcontrols',
'mousedown .title': 'copytoclip',
'mousedown .text_filename': 'copytoclip',
'mousedown .text_streamurl': 'copytoclip',
+ 'mousedown .magnet-icon': 'openMagnet',
'click .playing-progressbar': 'seekStreaming'
},
@@ -156,12 +158,18 @@
}
},
+ maxPlayCtrl: function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ $('.vjs-play-control').click();
+ },
+
onAttach: function() {
$('.filter-bar').hide();
$('#header').addClass('header-shadow');
App.LoadingView = this;
this.initKeyboardShortcuts();
- $('.minimize-icon,#maxic,.open-button,.title,.text_filename,.text_streamurl,.show-pcontrols').tooltip({
+ $('.minimize-icon,#maxic,.open-button,.title,.text_filename,.text_streamurl,.show-pcontrols,.magnet-icon').tooltip({
html: true,
delay: {
'show': 800,
@@ -199,6 +207,7 @@
if (streamInfo.get('device') && streamInfo.get('device').get('type') !== 'local') {
this.ui.player.text(streamInfo.get('device').get('name'));
this.ui.streaming.css('visibility', 'visible');
+ $('#max_play_ctrl').removeClass('fa-play').removeClass('play').addClass('fa-pause').addClass('pause');
}
this.ui.stateTextDownload.text(i18n.__('Downloading'));
this.ui.progressbar.parent().css('visibility', 'hidden');
@@ -337,6 +346,25 @@
App.settings.os === 'windows' ? nw.Shell.openExternal(Settings.tmpLocation) : nw.Shell.openItem(Settings.tmpLocation);
},
+ openMagnet: function (e) {
+ const torrent = this.model.get('streamInfo').attributes.torrentModel.attributes.torrent;
+ if (torrent.magnetURI) {
+ var magnetLink = torrent.magnetURI.replace(/\&/g, '&');
+ magnetLink = magnetLink.split('&tr=')[0] + _.union(decodeURIComponent(magnetLink).replace(/\/announce/g, '').split('&tr=').slice(1), Settings.trackers.forced.toString().replace(/\/announce/g, '').split(',')).map(t => `&tr=${t}/announce`).join('');
+ if (e.button === 2) {
+ var clipboard = nw.Clipboard.get();
+ clipboard.set(magnetLink, 'text'); //copy link to clipboard
+ $('.notification_alert')
+ .text(i18n.__('Copied to clipboard'))
+ .fadeIn('fast')
+ .delay(2500)
+ .fadeOut('fast');
+ } else {
+ nw.Shell.openExternal(magnetLink);
+ }
+ }
+ },
+
remainingTime: function () {
var streamInfo = this.model.get('streamInfo');
var timeLeft = streamInfo.get('time_left');
@@ -361,13 +389,12 @@
pauseStreaming: function() {
App.vent.trigger('device:pause');
- $('.pause').removeClass('fa-pause').removeClass('pause').addClass('fa-play').addClass('play');
+ $('.pause, #max_play_ctrl').removeClass('fa-pause').removeClass('pause').addClass('fa-play').addClass('play');
},
resumeStreaming: function() {
- win.debug('Play triggered');
App.vent.trigger('device:unpause');
- $('.play').removeClass('fa-play').removeClass('play').addClass('fa-pause').addClass('pause');
+ $('.play, #max_play_ctrl').removeClass('fa-play').removeClass('play').addClass('fa-pause').addClass('pause');
},
stopStreaming: function() {
diff --git a/src/app/lib/views/player/player.js b/src/app/lib/views/player/player.js
index 526c4e3ada..b5efb00cb1 100644
--- a/src/app/lib/views/player/player.js
+++ b/src/app/lib/views/player/player.js
@@ -7,6 +7,8 @@
className: 'player',
player: null,
prevSub: null,
+ wasFullscreen: false,
+ wasMinimized: false,
ui: {
eyeInfo: '.eye-info-player',
@@ -14,8 +16,12 @@
uploadSpeed: '.upload_speed_player',
activePeers: '.active_peers_player',
downloaded: '.downloaded_player',
- pause: '.fa-pause',
- play: '.fa-play'
+ downloadedPercent: '.downloadedPercent_player',
+ pause: '#osd_pause',
+ play: '#osd_play',
+ minimizeIcon: '.minimize-icon',
+ maximizeIcon: '.maximize-icon',
+ maxPlayCtrlIcon: '#max_play_ctrl'
},
events: {
@@ -27,11 +33,19 @@
'click .vjs-subtitles-button': 'toggleSubtitles',
'click .vjs-text-track': 'moveSubtitles',
'mousedown .eye-info-player': 'filenametoclip',
+ 'click .minimize-icon': 'minDetails',
+ 'click .maximize-icon': 'minDetails',
+ 'click #max_play_ctrl': 'maxPlayCtrl',
'click .vjs-play-control': 'togglePlay'
},
initialize: function () {
_this = this;
+
+ if ($('.loading .maximize-icon').is(':visible')) {
+ this.wasMinimized = true;
+ }
+
this.listenTo(this.model, 'change:downloadSpeed', this.updateDownloadSpeed);
this.listenTo(this.model, 'change:uploadSpeed', this.updateUploadSpeed);
this.listenTo(this.model, 'change:active_peers', this.updateActivePeers);
@@ -39,18 +53,16 @@
this.inFullscreen = win.isFullscreen;
this.playerWasReady = false;
-
this.remaining = false;
this.createdRemaining = false;
this.firstPlay = true;
-
this.boundedMouseScroll = this.mouseScroll.bind(this);
//If a child was removed from above this view
App.vent.on('viewstack:pop', function() {
- if (_.last(App.ViewStack) === 'app-overlay') {
- _this.bindKeyboardShortcuts();
- }
+ if (_.last(App.ViewStack) === 'app-overlay') {
+ _this.bindKeyboardShortcuts();
+ }
});
},
@@ -78,13 +90,61 @@
this.ui.activePeers.text(this.model.get('active_peers'));
},
+ minDetails: function () {
+ if (this.ui.minimizeIcon.is(':visible')) {
+ if (win.isFullscreen) {
+ this.toggleFullscreen();
+ this.wasFullscreen = true;
+ }
+ $('.player').css({'height': '0', 'width': '0'});
+ $('.player .video-js').css('display', 'none');
+ $('#content').show();
+ if ($('#movie-detail') !== undefined) {
+ $('#movie-detail').show();
+ }
+ $('#player_drag').hide();
+ $('#header').show();
+ this.ui.minimizeIcon.hide();
+ this.ui.maximizeIcon.show();
+ this.unbindKeyboardShortcuts();
+ Mousetrap.bind(['esc', 'backspace'], function (e) {
+ App.vent.trigger('show:closeDetail');
+ App.vent.trigger('movie:closeDetail');
+ });
+ } else {
+ $('.player, .player .video-js').removeAttr('style');
+ $('#content').hide();
+ if ($('#movie-detail') !== undefined) {
+ $('#movie-detail').hide();
+ }
+ $('#player_drag').show();
+ $('#header').removeClass('header-shadow').hide();
+ this.ui.maximizeIcon.hide();
+ this.ui.minimizeIcon.show();
+ if (this.wasFullscreen) {
+ this.toggleFullscreen();
+ this.wasFullscreen = false;
+ }
+ this.bindKeyboardShortcuts();
+ }
+ },
+
+ maxPlayCtrl: function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ $('.vjs-play-control').click();
+ },
+
updateDownloaded: function () {
if (this.model.get('downloadedPercent').toFixed(0) < 100 || this.model.get('size') === 0) {
if (this.model.get('size') !== 0) {
this.ui.downloaded.html(this.model.get('downloadedPercent').toFixed(0) + '% (' + this.model.get('downloadedFormatted') + ' / ' + Common.fileSize(this.model.get('size')) + ')');
+ this.ui.downloadedPercent.html(this.model.get('downloadedPercent').toFixed(0) + '%');
} else {
this.ui.downloaded.html('(' + this.model.get('downloadedFormatted') + ' / ' + i18n.__('Unknown') + ')');
+ this.ui.downloadedPercent.html(i18n.__('Unknown') + ' %');
}
+
$('.vjs-load-progress').css('width', this.model.get('downloadedPercent').toFixed(0) + '%');
this.remaining = true;
@@ -97,8 +157,10 @@
} else {
$('.details-info-player #sstatel').text(i18n.__('Downloaded'));
this.ui.downloaded.html(this.model.get('downloadedPercent').toFixed(0) + '% (' + Common.fileSize(this.model.get('size')) + ')
');
- $('.details-info-player #dloaddd, .download_speed_player, .details-info-player #apeersss, .active_peers_player').hide();
+ this.ui.downloadedPercent.html(this.model.get('downloadedPercent').toFixed(0) + '%');
+ $('.details-info-player #dloaddd, .download_speed_player, .details-info-player #apeersss, .active_peers_player, .maximize-icon #maxdllb').hide();
$('.vjs-load-progress').css('width', '100%');
+ this.ui.maximizeIcon.addClass('done');
this.remaining = false;
}
@@ -202,7 +264,7 @@
var playingNext = $('.playing_next');
if (!this.precachestarted) {
- App.vent.trigger('preload:start', this.next_episode_model);
+ App.vent.trigger('stream:start', this.next_episode_model, 'preload');
this.precachestarted = true;
}
@@ -324,6 +386,7 @@
this.ui.play.show().delay(50).queue(function () {
this.ui.play.css('transform', 'scale(1.8)').fadeOut(400).dequeue();
}.bind(this));
+ this.ui.maxPlayCtrlIcon.removeClass('fa-play').addClass('fa-pause');
App.vent.trigger('player:play');
}
@@ -340,6 +403,7 @@
this.ui.pause.show().delay(50).queue(function () {
this.ui.pause.css('transform', 'scale(1.8)').fadeOut(400).dequeue();
}.bind(this));
+ this.ui.maxPlayCtrlIcon.removeClass('fa-pause').addClass('fa-play');
App.vent.trigger('player:pause');
this.sendToTrakt('pause');
}
@@ -386,6 +450,9 @@
$('#player_drag').show();
var that = this;
+ $('.button:not(#download-torrent), .show-details .sdow-watchnow, .show-details #download-torrent, .file-item, .result-item, .collection-actions').addClass('disabled');
+ $('#watch-now, #watch-trailer, .playerchoice, .file-item, .result-item').prop('disabled', true);
+
// Double Click to toggle Fullscreen
$('#video_player, .state-info-player').dblclick(function (event) {
that.toggleFullscreen();
@@ -401,6 +468,14 @@
this.processNext();
}
+ this.$('.tooltipped').tooltip({
+ html: true,
+ delay: {
+ 'show': 800,
+ 'hide': 0
+ }
+ });
+
// start videojs engine
if (this.model.get('type') === 'video/youtube') {
@@ -414,7 +489,8 @@
this.addClass('vjs-has-started');
});
this.ui.eyeInfo.hide();
- $('.player-title').text(this.model.get('title') + ' - Trailer');
+ $('.maximize-icon #maxdllb').hide();
+ $('.player-title, .player .maximize-icon .title').text(this.model.get('title') + ' - Trailer');
// XXX Sammuel86 Trailer UI Show FIX/HACK
$('.trailer_mouse_catch')
@@ -492,7 +568,6 @@
this.player.on('pause', this.onPlayerPause.bind(this));
this.player.on('error', this.onPlayerError.bind(this));
- this.bindKeyboardShortcuts();
this.metadataCheck();
$('.player-header-background').appendTo('div#video_player');
@@ -506,12 +581,35 @@
// set fullscreen state & previous state
if (Settings.alwaysFullscreen && !this.inFullscreen) {
- this.toggleFullscreen();
+ if (this.wasMinimized) {
+ this.wasFullscreen = true;
+ } else {
+ this.toggleFullscreen();
+ }
}
if (this.inFullscreen) {
win.leaveFullscreen();
this.toggleFullscreen();
}
+ if (this.wasMinimized) {
+ $('.player').css({'height': '0', 'width': '0'});
+ $('.player .video-js').css('display', 'none');
+ $('#player_drag').hide();
+ $('#header').show();
+ this.ui.minimizeIcon.hide();
+ this.ui.maximizeIcon.show();
+ this.unbindKeyboardShortcuts();
+ Mousetrap.bind(['esc', 'backspace'], function (e) {
+ App.vent.trigger('show:closeDetail');
+ App.vent.trigger('movie:closeDetail');
+ });
+ } else {
+ this.bindKeyboardShortcuts();
+ }
+
+ Mousetrap.bind('ctrl+v', function (e) {
+ e.preventDefault();
+ });
// don't hide controls when hovering following classes:
$('.vjs-menu-content, .eye-info-player, .playing_next, .verify_metadata').hover(function () {
@@ -633,7 +731,7 @@
this.model.set('tvdb_id', false);
this.model.set('episode_id', false);
this.model.set('metadataCheckRequired', false);
- $('.player-title').text(title);
+ $('.player-title, .player .maximize-icon .title').text(title);
// remove subtitles
var subs = this.model.get('subtitle');
@@ -1049,7 +1147,10 @@
if (this.inFullscreen && !win.isFullscreen) {
$('.btn-os.fullscreen').removeClass('active');
}
+ $('.button, #watch-now, .show-details .sdow-watchnow, .playerchoice, .file-item, .result-item, .trash-torrent, .collection-actions').removeClass('disabled').removeProp('disabled');
this.unbindKeyboardShortcuts();
+ Mousetrap.bind('ctrl+v', function (e) {
+ });
App.vent.trigger('player:close');
var vjsPlayer = document.getElementById('video_player');
if (vjsPlayer) {
diff --git a/src/app/lib/views/quality-selector.js b/src/app/lib/views/quality-selector.js
index 4b7a45a969..6081ed6e72 100644
--- a/src/app/lib/views/quality-selector.js
+++ b/src/app/lib/views/quality-selector.js
@@ -9,16 +9,31 @@
events: {
'click .qselect': 'selectItem',
},
- collator: new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}),
initialize: function () {
- var self = this;
+ this.updateTorrents(this.model.get('torrents'));
+ },
+
+ onAttach: function () {
+ this.initQuality();
+ },
- var required = this.model.get('required');
- var torrents = this.model.get('torrents');
+ initQuality: function() {
+ var selectedKey = null;
+ for (let [key, torrent] of Object.entries(this.model.get('sortedTorrents'))) {
+ if (!torrent) {
+ continue;
+ }
+ if (!selectedKey || Common.qualityCollator.compare(key, Settings[this.model.get('defaultQualityKey')]) <= 0) {
+ selectedKey = key;
+ }
+ }
+ this.selectQuality(selectedKey);
+ },
- let keys = Object.keys(torrents).sort(this.collator.compare);
+ updateTorrents: function (torrents) {
+ let keys = Object.keys(torrents).sort(Common.qualityCollator.compare);
let sortedTorrents = {};
- for (let key of required) {
+ for (let key of this.model.get('required')) {
sortedTorrents[key] = false;
}
for (let key of keys) {
@@ -30,19 +45,8 @@
}
this.model.set('sortedTorrents', sortedTorrents);
- },
-
- onAttach: function () {
- var selectedKey = null;
- for (let [key, torrent] of Object.entries(this.model.get('sortedTorrents'))) {
- if (!torrent) {
- continue;
- }
- if (!selectedKey || this.collator.compare(key, Settings[this.model.get('defaultQualityKey')]) <= 0) {
- selectedKey = key;
- }
- }
- this.selectQuality(selectedKey);
+ this.render();
+ this.initQuality();
},
selectNext: function () {
@@ -76,8 +80,7 @@
selectQuality: function (key) {
$(this.ui.list).find('div').removeClass('active');
$(this.ui.list).find('div:contains("'+key+'")').addClass('active');
- console.log('Select quality: ', key);
- var torrents = this.model.get('torrents');
+ var torrents = this.model.get('sortedTorrents');
var callback = this.model.get('selectCallback');
callback(torrents[key], key);
},
diff --git a/src/app/lib/views/seedbox.js b/src/app/lib/views/seedbox.js
index 08cef2c344..6f42a5e271 100644
--- a/src/app/lib/views/seedbox.js
+++ b/src/app/lib/views/seedbox.js
@@ -57,7 +57,7 @@
this.render();
this.addTorrentHooks();
- if ($('.loading .maximize-icon').is(':visible')) {
+ if ($('.loading .maximize-icon').is(':visible') || $('.player .maximize-icon').is(':visible')) {
let currentHash;
try { currentHash = App.LoadingView.model.attributes.streamInfo.attributes.torrentModel.attributes.torrent.infoHash; } catch(err) {}
currentHash && $('#trash-'+currentHash)[0] ? $('#trash-'+currentHash).addClass('disabled') : null;
@@ -377,7 +377,7 @@
torrent.name ? $('.seedbox-infos-title').text(torrent.name) : $('.seedbox-infos-title').text(i18n.__('connecting'));
$('.seedbox-downloaded').text(' ' + formatBytes(torrent.downloaded));
$('.seedbox-uploaded').text(' ' + formatBytes(torrent.uploaded));
- try { $('.seedbox-infos-date').text(stats.ctime); } catch(err) {}
+ try { $('.seedbox-infos-date').text(i18n.__('added') + ' ' + dayjs(stats.ctime).fromNow()); } catch(err) {}
$('.progress-bar').css('width', (torrent.progress * 100).toFixed(2) + '%');
$('.progress-percentage>span').text((torrent.progress * 100).toFixed(2) + '%');
if (torrent.progress >= 1) {
diff --git a/src/app/lib/views/settings_container.js b/src/app/lib/views/settings_container.js
index f5208a6759..2f24affe98 100644
--- a/src/app/lib/views/settings_container.js
+++ b/src/app/lib/views/settings_container.js
@@ -24,6 +24,7 @@
'click .close-icon': 'closeSettings',
'change select,input': 'saveSetting',
'contextmenu input': 'rightclick_field',
+ 'click .rebuild-bookmarks': 'rebuildBookmarks',
'click .flush-bookmarks': 'flushBookmarks',
'click .flush-databases': 'flushAllDatabase',
'click #faketmpLocation': 'showCacheDirectoryDialog',
@@ -39,8 +40,6 @@
'click .modal-overlay, .modal-close': 'closeModal',
'click #authTrakt': 'connectTrakt',
'click #unauthTrakt': 'disconnectTrakt',
- 'click #connect-with-tvst': 'connectWithTvst',
- 'click #disconnect-tvst': 'disconnectTvst',
'click #authOpensubtitles': 'connectOpensubtitles',
'click #unauthOpensubtitles': 'disconnectOpensubtitles',
'click .reset-tvshow': 'resettvshow',
@@ -268,10 +267,12 @@
AdvSettings.set('lastTab', App.currentview);
}
/* falls through */
+ case 'translateTitle':
case 'watchedCovers':
case 'defaultFilters':
case 'theme':
case 'delSeedboxCache':
+ case 'maxLimitMult':
value = $('option:selected', field).val();
break;
case 'poster_size':
@@ -283,6 +284,9 @@
App.vent.trigger('updatePostersSizeStylesheet');
});
break;
+ case 'contentLanguage':
+ value = $('option:selected', field).val();
+ break;
case 'language':
value = $('option:selected', field).val();
i18n.setLocale(value);
@@ -292,12 +296,15 @@
case 'separateDownloadsDir':
case 'continueSeedingOnStart':
case 'protocolEncryption':
+ case 'contentLangOnly':
case 'vpnEnabled':
case 'coversShowRating':
case 'torColSearchMore':
case 'showSeedboxOnDlInit':
case 'nativeWindowFrame':
+ case 'translatePosters':
case 'translateSynopsis':
+ case 'translateEpisodes':
case 'showAdvancedSettings':
case 'alwaysOnTop':
case 'traktSyncOnStart':
@@ -335,8 +342,17 @@
case 'maxActiveTorrents':
value = field.val();
break;
+ case 'downloadLimit':
+ case 'uploadLimit':
+ let numvalue = field.val().replace(/[^0-9|.-]/gi, '').replace(/^([^.]*\.)|\./g, '$1');
+ if (numvalue <= 0) {
+ numvalue = '';
+ }
+ field.val(numvalue);
+ value = numvalue;
+ break;
case 'bigPicture':
- var nvalue = field.val().replace(/[^0-9]/gi, '');
+ let nvalue = field.val().replace(/[^0-9]/gi, '');
if (nvalue === '') {
nvalue = AdvSettings.get('bigPicture');
} else if (nvalue < 25) {
@@ -406,6 +422,7 @@
syncSetting: function (setting, value) {
let scrollPos = that.$el.scrollTop();
+ let scrollPosOffset = 0;
switch (setting) {
case 'coversShowRating':
if (value) {
@@ -431,12 +448,31 @@
case 'protocolEncryption':
this.alertMessageSuccess(true);
break;
- case 'vpnEnabled':
+ case 'downloadLimit':
+ App.WebTorrent.throttleDownload(parseInt(parseFloat(value, 10) * parseInt(Settings.maxLimitMult, 10)) || -1);
+ break;
+ case 'uploadLimit':
+ App.WebTorrent.throttleUpload(parseInt(parseFloat(value, 10) * parseInt(Settings.maxLimitMult, 10)) || -1);
+ break;
+ case 'maxLimitMult':
+ if (Settings.downloadLimit) {
+ App.WebTorrent.throttleDownload(parseInt(parseFloat(Settings.downloadLimit, 10) * parseInt(value, 10)) || -1);
+ }
+ if (Settings.uploadLimit) {
+ App.WebTorrent.throttleUpload(parseInt(parseFloat(Settings.uploadLimit, 10) * parseInt(value, 10)) || -1);
+ }
+ break;
+ case 'contentLanguage':
+ case 'contentLangOnly':
+ App.Providers.updateLanguage(Settings.language, value || Settings.language, Settings.contentLangOnly);
+ this.alertMessageSuccess(true);
+ break;
case 'language':
- case 'watchedCovers':
- case 'defaultFilters':
$('.nav-hor.left li:first').click();
App.vent.trigger('settings:show');
+ if (!Settings.contentLanguage) {
+ this.alertMessageSuccess(true);
+ }
break;
case 'alwaysOnTop':
win.setAlwaysOnTop(value);
@@ -471,6 +507,7 @@
if (AdvSettings.get('startScreen') === 'Torrent-collection') {
$('select[name=start_screen]').change();
}
+ value ? scrollPosOffset++ : scrollPosOffset--;
break;
case 'moviesTabEnable':
App.vent.trigger('favorites:list');
@@ -510,6 +547,7 @@
if (AdvSettings.get('startScreen') === 'Seedbox') {
$('select[name=start_screen]').change();
}
+ value ? scrollPosOffset++ : scrollPosOffset--;
break;
case 'separateDownloadsDir':
if (value) {
@@ -518,11 +556,24 @@
fs.mkdir(torrent_cache_dir2, function (err) {});
}
}
- /* falls through */
+ if (Settings.deleteTmpOnClose) {
+ value ? scrollPosOffset++ : scrollPosOffset--;
+ }
+ $('.nav-hor.left li:first').click();
+ App.vent.trigger('settings:show');
+ break;
case 'deleteTmpOnClose':
+ if (!Settings.separateDownloadsDir) {
+ !value ? scrollPosOffset++ : scrollPosOffset--;
+ }
+ /* falls through */
+ case 'vpnEnabled':
+ case 'watchedCovers':
+ case 'defaultFilters':
case 'activateTempf':
case 'multipleExtSubtitles':
case 'torColSearchMore':
+ case 'httpApiEnabled':
$('.nav-hor.left li:first').click();
App.vent.trigger('settings:show');
break;
@@ -550,6 +601,9 @@
default:
}
if (that.$el.scrollTop() !== scrollPos) {
+ if (scrollPosOffset) {
+ scrollPos = scrollPos + scrollPosOffset * 40;
+ }
that.$el.scrollTop(scrollPos);
}
},
@@ -603,28 +657,6 @@
this.render();
},
- connectWithTvst: function () {
- var self = this;
-
- $('#connect-with-tvst > i').css('visibility', 'hidden');
- $('.tvst-loading-spinner').show();
-
- App.vent.on('system:tvstAuthenticated', function () {
- $('.tvst-loading-spinner').hide();
- self.render();
- });
- App.TVShowTime.authenticate(function (activateUri) {
- nw.Shell.openExternal(activateUri);
- });
- },
-
- disconnectTvst: function () {
- var self = this;
- App.TVShowTime.disconnect(function () {
- self.render();
- });
- },
-
connectOpensubtitles: function (e) {
var self = this,
usn = $('#opensubtitlesUsername').val(),
@@ -681,6 +713,45 @@
setTimeout(self.render, 200);
},
+ rebuildBookmarks: function (e) {
+ var btn = $(e.currentTarget);
+
+ if (!this.areYouSure(btn, i18n.__('Rebuilding bookmarks...'))) {
+ return;
+ }
+
+ this.alertMessageWait(i18n.__('We are rebuilding your database'));
+
+ Database.getAllBookmarks()
+ .then(function (data) {
+ let movieProvider = App.Config.getProviderForType('movie')[0];
+ let showProvider = App.Config.getProviderForType('tvshow')[0];
+ for (let item of data) {
+ if (item.type === 'movie') {
+ movieProvider.fetch({keywords: item.imdb_id}).then(function (movies) {
+ if (movies.results.length !== 1) {
+ return;
+ }
+ let movie = movies.results[0];
+ Database.deleteMovie(item.imdb_id);
+ movie.providers = [movieProvider.name];
+ Database.addMovie(movie);
+ });
+ }
+ if (item.type === 'show') {
+ showProvider.detail(item.imdb_id, {
+ contextLocale: App.settings.contextLanguage || App.settings.language
+ }).then(function (show) {
+ Database.deleteTVShow(item.imdb_id);
+ show.providers = [showProvider.name];
+ Database.addTVShow(show);
+ });
+ }
+ }
+ that.alertMessageSuccess(true);
+ });
+ },
+
flushBookmarks: function (e) {
var btn = $(e.currentTarget);
diff --git a/src/app/lib/views/show_detail.js b/src/app/lib/views/show_detail.js
index a17a57edec..814de47fe7 100644
--- a/src/app/lib/views/show_detail.js
+++ b/src/app/lib/views/show_detail.js
@@ -26,8 +26,10 @@
'click .close-icon': 'closeDetails',
'click .tab-season': 'clickSeason',
'click .tab-episode': 'clickEpisode',
+ 'click .shmi-year': 'openRelInfo',
'click .shmi-imdb': 'openIMDb',
'mousedown .magnet-icon': 'openMagnet',
+ 'mousedown .source-icon': 'openSource',
'dblclick .tab-episode': 'dblclickEpisode',
'click .playerchoicemenu li a': 'selectPlayer',
'click .shmi-rating': 'switchRating',
@@ -37,6 +39,8 @@
},
regions: {
+ subDropdown: '#subs-dropdown',
+ audioDropdown: '#audio-dropdown',
qualitySelector: '#quality-selector',
},
@@ -61,11 +65,16 @@
initialize: function () {
_this = this;
- this.renameUntitled();
+ this.views = {};
healthButton = new Common.HealthButton('.health-icon', this.retrieveTorrentHealth.bind(this));
//Handle keyboard shortcuts when other views are appended or removed
+ // init fields in model
+ this.model.set('displayTitle', '');
+ this.model.set('displaySynopsis', '');
+ this.model.set('localizeEpisode', this.localizeEpisode);
+ this.localizeTexts();
//If a child was removed from above this view
App.vent.on('viewstack:pop', function () {
@@ -89,20 +98,12 @@
_this.initKeyboardShortcuts();
});
- var torrents = {};
- _.each(this.model.get('episodes'), function (value, currentEpisode) {
- if (!torrents[value.season]) {
- torrents[value.season] = {};
- }
- torrents[value.season][value.episode] = value;
- });
- this.model.set('torrents', torrents);
- this.model.set('seasonCount', Object.keys(torrents).length);
+ App.vent.on('audio:lang', this.switchAudio.bind(this));
+ this.initTorrents(this.model.get('episodes'));
},
- renameUntitled: function () {
- var episodes = this.model.get('episodes');
- for (var i = 0; i < episodes.length; i++) {
+ initTorrents: function (episodes) {
+ for (let i = 0; i < episodes.length; i++) {
if (!episodes[i].title) {
episodes[i].title = 'Untitled';
}
@@ -113,6 +114,15 @@
episodes[i].first_aired = 'Unknown';
}
}
+ let torrents = {};
+ _.each(episodes, function (value, currentEpisode) {
+ if (!torrents[value.season]) {
+ torrents[value.season] = {};
+ }
+ torrents[value.season][value.episode] = value;
+ });
+ this.model.set('torrents', torrents);
+ this.model.set('seasonCount', Object.keys(torrents).length);
},
initKeyboardShortcuts: function () {
@@ -142,6 +152,8 @@
},
onAttach: function () {
+ win.info('Show series details (' + this.model.get('imdb_id') + ')');
+
bookmarked = App.userBookmarks.indexOf(this.model.get('imdb_id')) !== -1;
if (bookmarked) {
@@ -150,8 +162,9 @@
this.ui.bookmarkIcon.removeClass('selected');
}
+ this.loadAudioDropdown();
this.getRegion('qualitySelector').empty();
- $('.star-container-tv,.shmi-imdb,.magnet-icon').tooltip();
+ $('.star-container-tv,.shmi-year,.shmi-imdb,.magnet-icon,.source-icon').tooltip();
var noimg = 'images/posterholder.png';
var nobg = 'images/bg-header.jpg';
var images = this.model.get('images');
@@ -166,44 +179,25 @@
if (!backdrop) {
backdrop = images.banner || nobg;
}
- var posterCache = new Image();
- posterCache.src = poster;
- posterCache.onload = function () {
- try {
- $('.shp-img')
- .css('background-image', 'url(' + poster + ')')
- .addClass('fadein');
- } catch (e) {}
- posterCache = null;
- };
- posterCache.onerror = function () {
- try {
- $('.shp-img')
- .css('background-image', 'url("images/posterholder.png")')
- .addClass('fadein');
- } catch (e) {}
- posterCache = null;
- };
+ if (Settings.translatePosters) {
+ var locale = this.model.get('locale');
+ if (locale) {
+ poster = locale.poster ? locale.poster : poster;
+ backdrop = locale.backdrop ? locale.backdrop : backdrop;
+ }
+ }
- var bgCache = new Image();
- bgCache.src = backdrop;
- bgCache.onload = function () {
- try {
- $('.shb-img')
- .css('background-image', 'url(' + backdrop + ')')
- .addClass('fadein');
- } catch (e) {}
- bgCache = null;
- };
- bgCache.onerror = function () {
- try {
- $('.shb-img')
- .css('background-image', 'url("images/bg-header.jpg")')
- .addClass('fadein');
- } catch (e) {}
- bgCache = null;
- };
+ Common.loadImage(poster).then((img) => {
+ $('.shp-img')
+ .css('background-image', 'url(' + (img || noimg) + ')')
+ .addClass('fadein');
+ });
+ Common.loadImage(backdrop).then((img) => {
+ $('.shb-img')
+ .css('background-image', 'url(' + (img || nobg) + ')')
+ .addClass('fadein');
+ });
this.selectNextEpisode();
@@ -214,7 +208,7 @@
$('.number-container-tv').removeClass('hidden');
}
- if (AdvSettings.get('hideSeasons') && this.model.get('seasonCount') < 2) {
+ if (this.model.get('seasonCount') < 2) {
this.ui.seasonTab.hide();
}
@@ -224,12 +218,56 @@
App.Device.ChooserView('#player-chooser').render();
$('.spinner').hide();
- if ($('.loading .maximize-icon').is(':visible')) {
+ if ($('.loading .maximize-icon').is(':visible') || $('.player .maximize-icon').is(':visible')) {
$('.sdow-watchnow, #download-torrent').addClass('disabled');
$('#watch-now').prop('disabled', true);
}
},
-
+ localizeTexts: function () {
+ const locale = this.model.get('locale');
+ let title = this.model.get('title');
+ if (Settings.translateTitle === 'translated-origin' || Settings.translateTitle === 'translated') {
+ if (locale && locale.title) {
+ title = locale.title;
+ }
+ }
+ let synopsis = this.model.get('synopsis');
+ if (Settings.translateSynopsis) {
+ if (locale && locale.synopsis) {
+ synopsis = locale.synopsis;
+ }
+ }
+ this.model.set('displayTitle', title);
+ this.model.set('displaySynopsis', synopsis);
+ },
+ localizeEpisode: function (episode) {
+ let title = episode.title;
+ let listTitle = episode.title;
+ let overview = episode.overview;
+ if (Settings.translateEpisodes && episode.locale) {
+ if (Settings.translateSynopsis && episode.locale.overview) {
+ overview = episode.locale.overview;
+ }
+ if (episode.locale.title) {
+ if (Settings.translateTitle === 'translated-origin') {
+ title = episode.locale.title;
+ listTitle = episode.locale.title + ' (' + episode.title + ')';
+ }
+ if (Settings.translateTitle === 'origin-translated') {
+ listTitle = episode.title + ' (' + episode.locale.title + ')';
+ }
+ if (Settings.translateTitle === 'translated') {
+ title = episode.locale.title;
+ listTitle = episode.locale.title;
+ }
+ }
+ }
+ return {
+ title,
+ listTitle,
+ overview,
+ };
+ },
selectNextEpisode: function () {
var episodesSeen = [];
@@ -317,6 +355,10 @@
});
},
+ openRelInfo: function () {
+ nw.Shell.openExternal('https://www.imdb.com/title/' + this.model.get('imdb_id') + '/releaseinfo');
+ },
+
openIMDb: function () {
nw.Shell.openExternal('https://www.imdb.com/title/' + this.model.get('imdb_id'));
},
@@ -333,12 +375,64 @@
}
},
+ openSource: function (e) {
+ var torrentUrl = $('.startStreaming').attr('data-source');
+ if (e.button === 2) { //if right click on magnet link
+ var clipboard = nw.Clipboard.get();
+ clipboard.set(torrentUrl, 'text'); //copy link to clipboard
+ $('.notification_alert').text(i18n.__('The source link was copied to the clipboard')).fadeIn('fast').delay(2500).fadeOut('fast');
+ } else {
+ nw.Shell.openExternal(torrentUrl);
+ }
+ },
+
switchRating: function () {
$('.number-container-tv').toggleClass('hidden');
$('.star-container-tv').toggleClass('hidden');
AdvSettings.set('ratingStars', $('.number-container-tv').hasClass('hidden'));
},
+ switchAudio: async function(lang) {
+ if (lang === this.model.get('contextLocale')) {
+ return;
+ }
+ $('.spinner').show();
+ const provider = this.model.get('providers').torrent;
+ const data = await provider.contentOnLang(this.model.get('imdb_id'), lang);
+ this.model.set('contextLocale', data.contextLocale);
+ this.model.set('episodes', data.episodes);
+ this.initTorrents(data.episodes);
+ this.render();
+ this.onAttach();
+ },
+
+ loadDropdown: function(type, attrs) {
+ this.views[type] && this.views[type].destroy();
+ this.views[type] = new App.View.LangDropdown({
+ model: new App.Model.Lang(Object.assign({ type: type }, attrs))
+ });
+ var types = type + 'Dropdown';
+ this.getRegion(types).show(this.views[type]);
+ },
+
+ loadAudioDropdown: function() {
+ return this.loadDropdown('audio', {
+ title: i18n.__('Audio Language'),
+ selected: this.model.get('contextLocale'),
+ values: _.object(_.map(this.model.get('exist_translations'), (item) => [item, 'data'])),
+ });
+ },
+
+ // TODO: for subtitles
+ // loadSubDropdown: function() {
+ // return this.loadDropdown('sub', {
+ // title: i18n.__('Subtitle'),
+ // selected: this.model.get('defaultSubtitle'),
+ // hasNull: true,
+ // values: this.model.get('subtitle')
+ // });
+ // },
+
toggleWatched: function (e) {
var edata = e.currentTarget.id.split('-');
setTimeout(function () {
@@ -428,7 +522,7 @@
}
},
- startStreaming: function (e) {
+ startStreaming: function (e, state) {
if (e.type) {
e.preventDefault();
}
@@ -451,13 +545,12 @@
episode: episode
};
-
var episodes = [];
var episodes_data = [];
//var selected_quality = $(e.currentTarget).attr('data-quality');
var auto_play = false;
var images = this.model.get('images');
- if (AdvSettings.get('playNextEpisodeAuto') && this.model.get('imdb_id').indexOf('mal') === -1) {
+ if (state !== 'downloadOnly' && AdvSettings.get('playNextEpisodeAuto') && this.model.get('imdb_id').indexOf('mal') === -1) {
_.each(this.model.get('episodes'), function (value) {
var epaInfo = {
id: parseInt(value.season) * 100 + parseInt(value.episode),
@@ -519,25 +612,22 @@
auto_id: parseInt(season) * 100 + parseInt(episode),
auto_play_data: episodes_data
});
- console.log('Playing next episode automatically:', AdvSettings.get('playNextEpisodeAuto'));
_this.unbindKeyboardShortcuts();
- App.vent.trigger('stream:start', torrentStart);
+ App.vent.trigger('stream:start', torrentStart, state);
},
downloadTorrent: function(e) {
- const torrent = $(e.currentTarget).attr('data-torrent');
- const file = $(e.currentTarget).attr('data-file');
- App.vent.trigger('stream:download', torrent, this.model.get('title'), file);
- if (Settings.showSeedboxOnDlInit) {
- App.previousview = App.currentview;
- App.currentview = 'Seedbox';
- App.vent.trigger('seedbox:show');
- $('.filter-bar').find('.active').removeClass('active');
- $('#filterbar-seedbox').addClass('active');
- $('#nav-filters').hide();
- } else {
- $('.notification_alert').stop().text(i18n.__('Download added')).fadeIn('fast').delay(1500).fadeOut('fast');
- }
+ this.startStreaming(e, 'downloadOnly');
+ if (Settings.showSeedboxOnDlInit) {
+ App.previousview = App.currentview;
+ App.currentview = 'Seedbox';
+ App.vent.trigger('seedbox:show');
+ $('.filter-bar').find('.active').removeClass('active');
+ $('#filterbar-seedbox').addClass('active');
+ $('#nav-filters').hide();
+ } else {
+ $('.notification_alert').stop().text(i18n.__('Download added')).fadeIn('fast').delay(1500).fadeOut('fast');
+ }
},
closeDetails: function (e) {
@@ -599,16 +689,17 @@
});
_this.getRegion('qualitySelector').show(qualitySelector);
- var first_aired = selectedEpisode.first_aired ? moment.unix(selectedEpisode.first_aired).locale(Settings.language).format('LLLL') : '';
+ var first_aired = selectedEpisode.first_aired ? dayjs.unix(selectedEpisode.first_aired).locale(Settings.language).format('LLLL') : '';
var synopsis = $('.sdoi-synopsis');
var startStreaming = $('.startStreaming');
+ var localize = this.localizeEpisode(selectedEpisode);
$('.tab-episode.active').removeClass('active');
$elem.addClass('active');
$('.sdoi-number').text(i18n.__('Season %s', selectedEpisode.season) + ', ' + i18n.__('Episode %s', selectedEpisode.episode));
- $('.sdoi-title').text(selectedEpisode.title);
+ $('.sdoi-title').text(localize.title);
$('.sdoi-date').text(i18n.__('Aired Date') + ': ' + first_aired);
- synopsis.text(selectedEpisode.overview);
+ synopsis.text(localize.overview);
//pull the scroll always to top
synopsis.scrollTop(0);
@@ -628,11 +719,13 @@
var downloadButton = $('#download-torrent');
startStreaming.attr('data-file', torrent.file || '');
startStreaming.attr('data-torrent', torrent.url);
+ startStreaming.attr('data-source', torrent.source);
startStreaming.attr('data-quality', key);
downloadButton.attr('data-torrent', torrent.url);
downloadButton.attr('data-file', torrent.file || '');
_this.resetTorrentHealth();
+ _this.toggleSourceLink();
},
toggleQuality: function (e) {
@@ -651,7 +744,7 @@
$nextEpisode[0].scrollIntoView(false);
}
- if (e.type) {
+ if (e && e.type) {
e.preventDefault();
e.stopPropagation();
}
@@ -776,6 +869,15 @@
healthButton.render();
},
+ toggleSourceLink: function () {
+ const sourceURL = $('.startStreaming').attr('data-source');
+ if (sourceURL) {
+ $('.source-icon').show().attr('data-original-title', sourceURL.split('//').pop().split('/')[0]);
+ } else {
+ $('.source-icon').hide();
+ }
+ },
+
selectPlayer: function (e) {
var player = $(e.currentTarget).parent('li').attr('id').replace('player-', '');
_this.model.set('device', player);
@@ -794,6 +896,7 @@
onBeforeDestroy: function () {
this.unbindKeyboardShortcuts();
+ App.vent.off('audio:lang');
App.vent.off('show:watched:' + this.model.id);
App.vent.off('show:unwatched:' + this.model.id);
}
diff --git a/src/app/lib/views/torrent_collection.js b/src/app/lib/views/torrent_collection.js
index 91f2171597..7cac659b84 100644
--- a/src/app/lib/views/torrent_collection.js
+++ b/src/app/lib/views/torrent_collection.js
@@ -57,7 +57,7 @@
if (Settings.toggleSengines) {
this.togglesengines();
}
- if ($('.loading .maximize-icon').is(':visible')) {
+ if ($('.loading .maximize-icon').is(':visible') || $('.player .maximize-icon').is(':visible')) {
$('.file-item, .collection-actions').addClass('disabled').prop('disabled', true);
}
diff --git a/src/app/settings.js b/src/app/settings.js
index 41a53169d9..05ff501c4f 100644
--- a/src/app/settings.js
+++ b/src/app/settings.js
@@ -34,10 +34,6 @@ var Settings = {
client_secret:
'f55b0a53c63af683588b47f6de94226b7572a6f83f40bd44c58a7c83fe1f2cb1'
},
- tvshowtime: {
- client_id: 'iM2Vxlwr93imH7nwrTEZ',
- client_secret: 'ghmK6ueMJjQLHBwsaao1tw3HUF7JVp_GQTwDwhCn'
- },
fanart: {
api_key: 'ce4bba4b3cc473306c6cddb4e1cb0da4'
},
@@ -62,9 +58,14 @@ Settings.providers = {
uri: [
]
},
+ anime: {
+ order: 3,
+ name: 'Anime',
+ uri: [
+ ]
+ },
subtitle: 'OpenSubtitles',
metadata: 'Trakttv',
- tvst: 'TVShowTime',
torrentCache: 'TorrentCache'
};
@@ -75,7 +76,6 @@ Settings.trackers = {
'udp://tracker.tiny-vps.com:6969',
'udp://tracker.openbittorrent.com:1337',
'udp://tracker.leechers-paradise.org:6969',
- 'udp://p4p.arenabg.ch:1337',
'udp://p4p.arenabg.com:1337',
'udp://tracker.internetwarriors.net:1337',
'udp://9.rarbg.to:2710',
@@ -84,11 +84,12 @@ Settings.trackers = {
'udp://tracker.cyberia.is:6969',
'udp://tracker.torrent.eu.org:451',
'udp://open.stealth.si:80',
+ 'udp://opentor.org:2710',
'udp://tracker.moeking.me:6969',
'udp://tracker.zerobytes.xyz:1337',
'udp://tracker.uw0.xyz:6969',
- 'wss://tracker.openwebtorrent.com',
- 'wss://tracker.btorrent.xyz'
+ 'udp://retracker.lanta-net.ru:2710',
+ 'wss://tracker.openwebtorrent.com'
]
};
@@ -100,6 +101,8 @@ Settings.proxyServer = '';
// User interface
Settings.language = '';
+Settings.contentLanguage = '';
+Settings.contentLangOnly = false;
Settings.nativeWindowFrame = nw.App.manifest.window.frame;
Settings.translateSynopsis = true;
Settings.coversShowRating = true;
@@ -115,6 +118,12 @@ Settings.postersWidth = Settings.postersMinWidth;
Settings.postersJump = [134, 154, 174, 194, 214, 234, 254, 274, 294];
Settings.bigPicture = 100;
+//Localisation
+Settings.translateTitle = 'translated-origin';
+Settings.translatePosters = true;
+Settings.translateSynopsis = true;
+Settings.translateEpisodes = true;
+
//Playback
Settings.alwaysFullscreen = false;
Settings.playNextEpisodeAuto = false;
@@ -125,7 +134,6 @@ Settings.chosenPlayer = 'local';
Settings.alwaysOnTop = false;
Settings.theme = 'Official_-_Dark_theme';
Settings.ratingStars = true; //trigger on click in details
-Settings.hideSeasons = true;
Settings.startScreen = 'Movies';
Settings.lastTab = '';
Settings.defaultFilters = 'default';
@@ -150,8 +158,8 @@ Settings.multipleExtSubtitles = false;
// More options
Settings.httpApiEnabled = false;
Settings.httpApiPort = 8008;
-Settings.httpApiUsername = 'butter';
-Settings.httpApiPassword = 'butter';
+Settings.httpApiUsername = 'popcorn';
+Settings.httpApiPassword = 'popcorn';
// Trakt.tv
Settings.traktStatus = false;
@@ -160,9 +168,6 @@ Settings.traktLastActivities = false;
Settings.traktSyncOnStart = true;
Settings.traktPlayback = true;
-// TVShow Time
-Settings.tvstAccessToken = '';
-
// OpenSubtitles
Settings.opensubtitlesAutoUpload = true;
Settings.opensubtitlesAuthenticated = false;
@@ -171,6 +176,9 @@ Settings.opensubtitlesPassword = '';
// Advanced options
Settings.connectionLimit = 55;
+Settings.downloadLimit = '';
+Settings.uploadLimit = '';
+Settings.maxLimitMult = 1024;
Settings.streamPort = 0; // 0 = Random
Settings.protocolEncryption = false;
Settings.tmpLocation = path.join(os.tmpdir(), Settings.projectName);
@@ -180,7 +188,7 @@ Settings.deleteTmpOnClose = true;
Settings.separateDownloadsDir = false;
Settings.delSeedboxCache = 'ask';
Settings.continueSeedingOnStart = false;
-Settings.vpnEnabled = true;
+Settings.vpnEnabled = false;
Settings.maxActiveTorrents = 5;
Settings.automaticUpdating = true;
Settings.UpdateSeed = false;
diff --git a/src/app/styl/views/browser/item.styl b/src/app/styl/views/browser/item.styl
index 177818be7f..c494fbac82 100644
--- a/src/app/styl/views/browser/item.styl
+++ b/src/app/styl/views/browser/item.styl
@@ -146,6 +146,16 @@
white-space: nowrap
padding-bottom: 2px
+ .title2
+ font-size: 0.85em
+ color: $Text1
+ height: auto;
+ margin: -2px 0 3px;
+ overflow: hidden
+ text-overflow: ellipsis
+ white-space: nowrap
+ padding-bottom: 2px
+
.year
font-size: 0.85em
color: $Text4
@@ -156,9 +166,7 @@
color: $Text4
display: inline
float: right
-
- &.quality
- display: none
+ margin-top: 2px
@keyframes fadeBd {
from { border-color: $PosterHoverOverlayOpq; }
diff --git a/src/app/styl/views/loading.styl b/src/app/styl/views/loading.styl
index 60478bfdac..28a78be559 100644
--- a/src/app/styl/views/loading.styl
+++ b/src/app/styl/views/loading.styl
@@ -282,6 +282,21 @@
background: rgba(0, 0, 0, .6)
display inline-block
+ &:hover > .magnet-icon
+ opacity 0.4
+
+ &:hover
+ opacity 1
+
+ &:active
+ opacity 1
+
+ .magnet-icon
+ float right
+ cursor pointer
+ transition opacity .2s
+ opacity 0
+
span
position relative
color #fff
diff --git a/src/app/styl/views/movie_detail.styl b/src/app/styl/views/movie_detail.styl
index e6fdaca2b5..b1e3650dab 100644
--- a/src/app/styl/views/movie_detail.styl
+++ b/src/app/styl/views/movie_detail.styl
@@ -117,6 +117,16 @@
font-smoothing: antialiased
cursor: pointer
+ .year, .certification
+ color: #f8f8f8
+ font-size: 12px
+ position: relative
+ font-family: $MainFontBold
+ text-stroke: 1px rgba(0,0,0,0.1)
+ float: left
+ font-smoothing: antialiased
+ cursor: pointer
+
.movie-imdb-link
margin-top: -3px
padding-top: 3px
@@ -208,6 +218,17 @@
color: #FFF
transition: all .5s
+ .source-link
+ position: relative
+ font-size: 13px
+ float: right
+ margin-right: 12px
+ color: #DDD
+ cursor: pointer
+ &:hover
+ color: #FFF
+ transition: all .5s
+
.overview
position: relative
height: 55%
@@ -478,7 +499,7 @@
cursor: pointer
float: left
font-family: $MainFont;
- -webkit-font-smoothing: antialiased;
+ font-smoothing: antialiased
font-size: 11px
color: $ShowText1
transition: all .2s ease-in
@@ -487,32 +508,6 @@
.tooltip
width: 85px
- .q720
- font-family: $MainFontBold
- font-smoothing: antialiased
- font-size: 11px
- color: #fff
- font-weight: normal
- position: absolute
-
- .q1080
- font-family: $MainFontBold
- font-smoothing: antialiased
- font-size: 11px
- color: #fff
- font-weight: normal
- left: 58px
- position: absolute
-
- .q2160
- font-family: $MainFontBold
- font-smoothing: antialiased
- font-size: 11px
- color: #fff
- font-weight: normal
- left: 116px
- position: absolute
-
.disabled
opacity: .3
cursor: default
diff --git a/src/app/styl/views/player.styl b/src/app/styl/views/player.styl
index effb1b0bd3..4356f57323 100644
--- a/src/app/styl/views/player.styl
+++ b/src/app/styl/views/player.styl
@@ -25,6 +25,53 @@
top: 20px
z-index: 20
}
+ .minimize-icon {
+ top: -3px
+ right: 55px
+ position: absolute
+ color: #fff
+ font-size: 2.5em
+ font-smoothing: antialiased
+ transition: all 0.5s
+ &:hover {
+ cursor: pointer
+ color: #a3a5a7
+ }
+ }
+ .maximize-icon {
+ display: none
+ bottom: 20px
+ right: 20px
+ position: fixed
+ color: $CloseButton
+ font-size: 25px
+ cursor: pointer
+ z-index: 100
+ background: $BgColor2
+ padding: 10px 20px
+ opacity: 0.9
+ border-radius: 3px
+ box-shadow: 0 2px 6px 0 rgba(0,0,0,0.15), 0 4px 14px 0 rgba(0,0,0,0.25)
+ font-smoothing: antialiased
+ transition: background 0.2s, color 0.2s, opacity 0.2s
+ &:hover {
+ color: $ButtonText
+ background: $ButtonBgHover
+ opacity: 1
+ }
+ &.done:hover {
+ background: #27ae60
+ }
+ & > * {
+ font-size: 12px
+ vertical-align: top
+ line-height: 25px
+ }
+ #maxic {
+ font-size: 25px
+ margin-left: 10px
+ }
+ }
.close-info-player {
top: -1px
right: 18px
@@ -40,7 +87,7 @@
}
.quality-info-player {
position: absolute
- right: 95px
+ right: 132px
top: 3px
font-size: 1.3em
color: #fff
@@ -61,7 +108,7 @@
font-size: 2em
color: #fff
top: -1px
- right: 55px
+ right: 92px
z-index: 4
transition: all 0.5s
&:hover {
diff --git a/src/app/styl/views/seedbox.styl b/src/app/styl/views/seedbox.styl
index 7d8182da7a..0d5a05ade2 100644
--- a/src/app/styl/views/seedbox.styl
+++ b/src/app/styl/views/seedbox.styl
@@ -241,6 +241,7 @@
color: $ShowText2
.seedbox-infos-date
+ margin-left: 5px
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
@@ -251,7 +252,7 @@
line-height: 18px
text-align: justify
margin-top: 10px
- padding: 2px 15px 5px 0
+ padding: 4px 15px 5px 0
height: calc(100vh - 334px)
scrollable()
diff --git a/src/app/styl/views/settings_container.styl b/src/app/styl/views/settings_container.styl
index d5dbf61fa6..89f246f450 100644
--- a/src/app/styl/views/settings_container.styl
+++ b/src/app/styl/views/settings_container.styl
@@ -160,6 +160,11 @@
.tv_detail_jump_to > select
width 158px
+ #localisation
+ .subtitles-language > select,
+ .translateTitle > select
+ width 158px
+
#subtitles
.subtitles-language-default > select,
.subtitles-font > select,
@@ -290,6 +295,34 @@
padding-left: 4px
padding-right: 8px
+ select
+ padding-left 10px
+ border 0 !important
+ -webkit-appearance none
+ height 30px
+ line-height 24px
+ margin-top -9px
+ background-color $InputBoxBg
+ cursor pointer
+ color $InputBoxText
+ font-family $Font, $AlternateFont
+ font-size 13px
+ outline 0
+ padding-right 10px
+ width: 62px
+
+ .dropdown-arrow
+ width 0
+ height 0
+ border-left 4px solid transparent
+ border-right 4px solid transparent
+ border-top 5px solid $Text2
+ top 12px
+ margin-left -13px
+ position relative
+ float none
+ cursor pointer
+
.reset-tvAPI
font-size: 13px
color: $SettingsText2
@@ -510,6 +543,13 @@
&:after
opacity: 1
+ input[type="text"]
+ downloadLimit
+ width: 78px
+ uploadLimit
+ width: 78px
+ margin-left: 7px
+
.settings-label
&:before
content: '\f0c8'
diff --git a/src/app/styl/views/show_detail.styl b/src/app/styl/views/show_detail.styl
index 55fd9b8238..721ef54dc9 100644
--- a/src/app/styl/views/show_detail.styl
+++ b/src/app/styl/views/show_detail.styl
@@ -84,6 +84,9 @@
color: #fff
-webkit-font-smoothing: antialiased
+ div.shmi-year
+ cursor: pointer
+
div.shmi-imdb
cursor: pointer
background: url(../images/icons/imdb.png) no-repeat
@@ -172,8 +175,20 @@
float: left
position: relative
width: calc(100% - 198px)
- height: 20px
- margin-top: -5px
+ height: 35px
+ margin-top: -15px
+ padding-right 45px
+
+ display: flex
+ flex-direction: row
+ justify-content: space-between
+ white-space: nowrap
+
+ .dropdowns-container
+ margin: -10px -30px 0 0
+
+ #audio-dropdown .lang-dropdown
+ background: none
.sha-bookmark
float: left
@@ -184,7 +199,7 @@
font-family: $MainFont
font-smoothing: antialiased
font-size: 12px
- line-height: 17px
+ line-height: 37px
&:before
content: "\f004"
font-family: "Font Awesome 5 Free"
@@ -259,16 +274,16 @@
opacity: 1
.sha-watched
- float: right
+ float: left
cursor: pointer
padding-left: 24px
- right: 45px
+ margin-left: 20px
color: #FFF
- position: absolute
+ position: relative
font-family: $MainFont
font-smoothing: antialiased
font-size: 12px
- line-height: 17px
+ line-height: 37px
display: none
&:before
@@ -437,7 +452,7 @@
margin-top: -6px
&>div
- padding: 5px 2px
+ padding: 5px 3px
cursor: pointer
position: relative
@@ -463,6 +478,15 @@
color: #FFF
transition: all .5s
+ .source-icon
+ font-size: 13px
+ color: #DDD
+ margin-right: 2px
+
+ &:hover
+ color: #FFF
+ transition: all .5s
+
.sdoi-aired
font-size: 12px
line-height: 15px
@@ -539,6 +563,18 @@
.sd-seasons[style*="display: none"] + .sd-episodes ul a div
max-width: calc(60vw - 100px)
+ > .spinner
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5) center center no-repeat;
+ pointer-events: all;
+ z-index: 1;
+
+ .loading-container
+ margin: 180px auto 0px
+ opacity: .8;
+
.show-details.active
filter: brightness(50%) blur(4px)
pointer-events: none
diff --git a/src/app/templates/about.tpl b/src/app/templates/about.tpl
index ec2178bb01..a2d7b6b617 100644
--- a/src/app/templates/about.tpl
+++ b/src/app/templates/about.tpl
@@ -8,7 +8,7 @@
diff --git a/src/app/templates/browser/filter-bar.tpl b/src/app/templates/browser/filter-bar.tpl
index 9ac52ef09d..3b908eef8f 100644
--- a/src/app/templates/browser/filter-bar.tpl
+++ b/src/app/templates/browser/filter-bar.tpl
@@ -5,60 +5,26 @@
<%= i18n.__("Favorites") %>
- <% if(typeof type !== 'undefined' && types.length !== 0){ %>
- -
+ <% filters = [
+ {class: 'types', title: "Type", current: type, list: types},
+ {class: 'ratings', title: "Rating", current: rating, list: ratings},
+ {class: 'genres', title: "Genre", current: genre, list: genres},
+ {class: 'sorters', title: "Sort by", current: sorter, list: sorters},
+ ] %>
+ <% _.each (filters, function (filter) { if(typeof filter.current !== 'undefined' && filter.list.length !== 0){ %>
+
-
- <%= i18n.__("Type") %>
- <%= i18n.__(type) %>
-
+ <%= i18n.__(filter.title) %>
+
+
-
- <% }if(typeof rating !== 'undefined' && ratings.length !== 0){ %>
- -
-
- <%= i18n.__("Rating") %>
- <%= i18n.__(rating.capitalizeEach()) %><% if (rating !== 'All') { %>+<% } %>
-
-
-
-
- <% }if(typeof genre !== 'undefined' && genres.length !== 0){ %>
- -
-
- <%= i18n.__("Genre") %>
- <%= i18n.__(genre.capitalizeEach()) %>
-
-
-
-
- <%} if(typeof sorter !== 'undefined' && sorters.length !== 0){ %>
- -
-
- <%= i18n.__("Sort by") %>
- <%= i18n.__(sorter.capitalizeEach()) %>
-
-
-
-
- <%}%>
+ <% }}); %>
<% if (Settings.vpnEnabled) { %>
diff --git a/src/app/templates/browser/item.tpl b/src/app/templates/browser/item.tpl
index 662ec6a99c..e787eda0f3 100644
--- a/src/app/templates/browser/item.tpl
+++ b/src/app/templates/browser/item.tpl
@@ -30,40 +30,26 @@
- 20){ %> title="<%= title %>" data-toggle="tooltip" data-placement="auto bottom" <% } %> ><%= title %>
+ 20){ %> title="<%= title1 %>" data-toggle="tooltip" data-placement="auto bottom" <% } %> ><%= title1 %>
+<% if (typeof title2 !== 'undefined' && title2 !== '') { %>
+ 20){ %> title="<%= title2 %>" data-toggle="tooltip" data-placement="auto bottom" <% } %> ><%= title2 %>
+<%} %>
- <% if (typeof year !== 'undefined') {%>
+ <% if (typeof year !== 'undefined') { %>
<%= year %>
- <%} %>
+ <% } %>
-<% if (typeof item_data !== 'undefined') {%>
-
- <%= i18n.__(item_data) %>
-
-<% } else if(typeof num_seasons !== 'undefined'){%>
+<% if (typeof item_data !== 'undefined') { %>
+
+ <%= i18n.__(item_data) %>
+
+<% } else if (typeof num_seasons !== 'undefined') { %>
<%= num_seasons %> <%= num_seasons == 1 ? i18n.__("Season") : i18n.__("Seasons") %>
-<%}else if (typeof torrents !== 'undefined') { %>
- style="display: block;" <% } %> >
- <% q720 = torrents["720p"] !== undefined; q1080 = torrents["1080p"] !== undefined; q2160 = torrents["2160p"] !== undefined;
- if (q720 && q1080 && q2160) { %>
- 720p/1080p/2160p
- <% } else if (q720 && q1080) { %>
- 720p/1080p
- <% } else if (q720 && q2160) { %>
- 720p/2160p
- <% } else if (q1080 && q2160) { %>
- 1080p/2160p
- <% } else if (q2160) { %>
- 2160p
- <% } else if (q1080) { %>
- 1080p
- <% } else if (q720) { %>
- 720p
- <% } else { %>
- HDRip
- <% } %>
+<% } else if (typeof qualityList !== 'undefined') { %>
+
+ <%= qualityList %>
-<%} %>
+<% } %>
diff --git a/src/app/templates/loading.tpl b/src/app/templates/loading.tpl
index 8e0a6f2f87..ae64765ba6 100644
--- a/src/app/templates/loading.tpl
+++ b/src/app/templates/loading.tpl
@@ -4,7 +4,7 @@
">
-
+
@
@@ -68,7 +68,8 @@
-
()
+
()
+
">
<%= i18n.__("Download") %>:
<%= Common.fileSize(0) %>/s
diff --git a/src/app/templates/movie-detail.tpl b/src/app/templates/movie-detail.tpl
index 0ffc4f1ef0..bf435c78ae 100644
--- a/src/app/templates/movie-detail.tpl
+++ b/src/app/templates/movie-detail.tpl
@@ -1,10 +1,10 @@
<%
if(typeof health === "undefined"){ health = false; };
-if(typeof synopsis === "undefined"){ synopsis = "Synopsis not available."; };
+if(typeof synopsis === "undefined"){ synopsis = "Synopsis not available."; };
if(typeof runtime === "undefined"){ runtime = "N/A"; };
if (genre) {
for(var i = 0; i < genre.length; i++) {
- genre[i] = i18n.__(genre[i].capitalizeEach()).toLowerCase();
+ genre[i] = i18n.__(genre[i].capitalizeEach()).toLowerCase();
}
} else {
var genre = [undefined];
@@ -23,16 +23,16 @@ if (genre) {