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 @@
id="changelog"><%= App.settings.version %> "<%= App.settings.releaseName %>" Beta <% if(App.git) { %> - - <%= App.git.branch %> (<%= App.git.commit.slice(0,8) %>) + - ( class="links" href="<%= Settings.commitUrl %>/<%= App.git.commit %>"><%= App.git.commit %>) <% } %>     <%= i18n.__("Report an issue") %>
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") %>