├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── gulpfile.js ├── package.json └── src └── app ├── License.txt ├── app.js ├── bootstrap.js ├── common.js ├── css ├── animation.css └── font-awesome.min.css ├── database.js ├── fonts ├── OpenSans-Bold.svg ├── OpenSans-Bold.woff ├── OpenSans-Regular.svg ├── OpenSans-Regular.woff ├── OpenSans-Semibold.svg ├── OpenSans-Semibold.woff ├── vjs.eot ├── vjs.svg ├── vjs.ttf └── vjs.woff ├── httpapi.js ├── images ├── bg-header.jpg ├── events │ ├── aprilsfool.png │ ├── halloween.png │ ├── newyear.png │ ├── pt_anniv.png │ ├── stpatrick.png │ ├── stvalentine.png │ └── xmas.png ├── flags │ ├── arabic.png │ ├── armenian.png │ ├── basque.png │ ├── bengali.png │ ├── bosnian.png │ ├── brazilian.png │ ├── bulgarian.png │ ├── chinese.png │ ├── croatian.png │ ├── czech.png │ ├── danish.png │ ├── dutch.png │ ├── english.png │ ├── estonian.png │ ├── farsi.png │ ├── finnish.png │ ├── french.png │ ├── german.png │ ├── greek.png │ ├── hebrew.png │ ├── hungarian.png │ ├── indonesian.png │ ├── italian.png │ ├── latvian.png │ ├── lithuanian.png │ ├── maltese.png │ ├── none.png │ ├── norwegian.png │ ├── polish.png │ ├── portuguese.png │ ├── romanian.png │ ├── russian.png │ ├── serbian.png │ ├── slovak.png │ ├── slovenian.png │ ├── spanish.png │ ├── swedish.png │ ├── thai.png │ ├── turkish.png │ ├── ukrainian.png │ └── vietnamese.png ├── icon.png ├── icons │ ├── Player │ │ ├── Sound0.png │ │ ├── Sound1.png │ │ ├── Sound2.png │ │ ├── Sound3.png │ │ └── Subtitles.png │ ├── airplay-icon.png │ ├── airplay-xbmc-icon.png │ ├── chromecast-icon.png │ ├── dlna-icon.png │ ├── external-bomi-icon.png │ ├── external-fleex-player-icon.png │ ├── external-icon.png │ ├── external-mpc-hc-icon.png │ ├── external-mplayer-icon.png │ ├── external-mpv-icon.png │ ├── external-smplayer-icon.png │ ├── external-vlc-icon.png │ ├── getstrike.png │ ├── googlecloud-favicon.png │ ├── googlecloud-icon.png │ ├── html5-icon.png │ ├── icon-blog.png │ ├── icon-discourse.png │ ├── icon-facebook.png │ ├── icon-github.png │ ├── icon-gitlab.png │ ├── icon-google.png │ ├── icon-popcorn.png │ ├── icon-stash.png │ ├── icon-twitter.png │ ├── imdb.png │ ├── kickasstorrents.png │ ├── local-icon.png │ ├── rarbg.png │ ├── topbar_sprite.png │ └── topbar_sprite_win.png ├── popcorn-time-logo.svg ├── popcorntime.icns ├── popcorntime.ico ├── popcorntime_uninstall.ico └── posterholder.png ├── index.html ├── language.js ├── language ├── .tx │ └── config ├── ar.json ├── bg.json ├── bn.json ├── ca.json ├── cs.json ├── da.json ├── de.json ├── el.json ├── en.json ├── es-mx.json ├── es.json ├── et.json ├── eu.json ├── fa.json ├── fi.json ├── fr.json ├── gl.json ├── he.json ├── hr.json ├── hu.json ├── id.json ├── is.json ├── it.json ├── ka.json ├── ko.json ├── lt.json ├── mk.json ├── ms.json ├── nb.json ├── nl.json ├── nn.json ├── pl.json ├── pt-br.json ├── pt.json ├── ro.json ├── ru.json ├── sk.json ├── sl.json ├── sr.json ├── sv.json ├── tr.json ├── uk.json ├── zh-cn.json └── zh-tw.json ├── lib ├── cache.js ├── cachev2.js ├── config.js ├── device │ ├── airplay.js │ ├── chromecast.js │ ├── dlna.js │ ├── ext-player.js │ ├── generic.js │ ├── googlecloud.js │ ├── nacholink.js │ └── xbmc.js ├── jquery.plugins.js ├── models │ ├── anime_collection.js │ ├── favorite_collection.js │ ├── filter.js │ ├── generic_collection.js │ ├── movie.js │ ├── movie_collection.js │ ├── notification.js │ ├── show.js │ ├── show_collection.js │ ├── stream_info.js │ └── watchlist_collection.js ├── providers │ ├── cache_provider.js │ ├── cache_providerv2.js │ ├── favorites.js │ ├── generic.js │ ├── haruhichan.js │ ├── opensubtitles.js │ ├── torrent_cache.js │ ├── trakttv.js │ ├── tvapi.js │ ├── tvshowtime.js │ ├── watchlist.js │ ├── ysubs.js │ ├── yts.js │ └── yts.js-BACKUP ├── streamer.js ├── subtitle │ ├── generic.js │ └── server.js └── views │ ├── about.js │ ├── browser │ ├── anime_browser.js │ ├── favorite_browser.js │ ├── filter_bar.js │ ├── generic_browser.js │ ├── item.js │ ├── list.js │ ├── movie_browser.js │ ├── show_browser.js │ └── watchlist_browser.js │ ├── disclaimer.js │ ├── file_selector.js │ ├── help.js │ ├── init_modal.js │ ├── issue.js │ ├── keyboard.js │ ├── main_window.js │ ├── movie_detail.js │ ├── notification.js │ ├── player │ ├── loading.js │ └── player.js │ ├── plugins.js │ ├── register.js │ ├── settings_container.js │ ├── show_detail.js │ ├── title_bar.js │ ├── torrent_collection.js │ └── vpn_connect.js ├── settings.js ├── templates ├── about.tpl ├── browser │ ├── browser.tpl │ ├── filter-bar.tpl │ ├── item.tpl │ └── list.tpl ├── changelog.tpl ├── disclaimer.tpl ├── file-selector.tpl ├── header.tpl ├── help.tpl ├── initializing.tpl ├── issue.tpl ├── keyboard.tpl ├── loading.tpl ├── main-window.tpl ├── movie-detail.tpl ├── movie-error.tpl ├── notification.tpl ├── player-chooser.tpl ├── player.tpl ├── plugins.tpl ├── register.tpl ├── settings-container.tpl ├── show-detail.tpl └── torrent_collection.tpl ├── themes ├── Official_-_Black_&_Yellow_theme.css ├── Official_-_Dark_theme.css ├── Official_-_FlaX_theme.css ├── Official_-_Flat_UI_theme.css ├── Official_-_Light_theme.css └── third_party │ ├── Ghostbears_-_Bubblegum_theme.css │ ├── Ghostbears_-_Chocolate_theme.css │ ├── Ghostbears_-_Colors_theme.css │ ├── Ghostbears_-_Green_theme.css │ ├── Ghostbears_-_Jiav_theme.css │ ├── Ghostbears_-_Ocean_theme.css │ ├── Ghostbears_-_Purple_theme.css │ ├── Ghostbears_-_Red_theme.css │ ├── Ghostbears_-_Scandinavian_theme.css │ ├── Ghostbears_-_White_theme.css │ ├── Ghostbears_-_Zayt_theme.css │ ├── Sebastiaans_-_Black_&_Red_theme.css │ ├── Sentience_theme.css │ └── Windows_10_theme.css ├── updater.js ├── vendor ├── backbone.babysitter │ └── lib │ │ ├── backbone.babysitter.js │ │ ├── backbone.babysitter.map │ │ └── backbone.babysitter.min.js ├── backbone.wreqr │ └── lib │ │ ├── backbone.wreqr.js │ │ ├── backbone.wreqr.map │ │ └── backbone.wreqr.min.js ├── backbone │ └── backbone.js ├── bootstrap │ └── dist │ │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ └── bootstrap.min.css │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js ├── font-awesome │ ├── bower.json │ ├── css │ │ ├── font-awesome.css │ │ ├── font-awesome.css.map │ │ └── font-awesome.min.css │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── jquery │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map ├── marionette │ └── lib │ │ ├── backbone.marionette.js │ │ ├── backbone.marionette.map │ │ ├── backbone.marionette.min.js │ │ └── core │ │ ├── backbone.marionette.js │ │ ├── backbone.marionette.map │ │ └── backbone.marionette.min.js ├── mousetrap │ ├── Gruntfile.js │ ├── README.md │ ├── mousetrap.js │ ├── mousetrap.min.js │ ├── package.json │ └── plugins │ │ ├── README.md │ │ ├── bind-dictionary │ │ ├── README.md │ │ ├── mousetrap-bind-dictionary.js │ │ └── mousetrap-bind-dictionary.min.js │ │ ├── global-bind │ │ ├── README.md │ │ ├── mousetrap-global-bind.js │ │ └── mousetrap-global-bind.min.js │ │ ├── pause │ │ ├── README.md │ │ ├── mousetrap-pause.js │ │ └── mousetrap-pause.min.js │ │ └── record │ │ ├── README.md │ │ ├── mousetrap-record.js │ │ └── mousetrap-record.min.js ├── underscore │ └── underscore.js ├── video.js │ └── dist │ │ └── video-js │ │ ├── demo.captions.vtt │ │ ├── demo.html │ │ ├── font │ │ ├── vjs.eot │ │ ├── vjs.svg │ │ ├── vjs.ttf │ │ └── vjs.woff │ │ ├── lang │ │ ├── ar.js │ │ ├── ca.js │ │ ├── cs.js │ │ ├── de.js │ │ ├── es.js │ │ ├── fr.js │ │ ├── hu.js │ │ ├── it.js │ │ ├── ja.js │ │ ├── ko.js │ │ ├── nl.js │ │ ├── pt-BR.js │ │ ├── ru.js │ │ ├── uk.js │ │ ├── vi.js │ │ ├── zh-TW.js │ │ └── zh.js │ │ ├── video-js.css │ │ ├── video-js.less │ │ ├── video-js.min.css │ │ ├── video-js.swf │ │ ├── video.dev.js │ │ └── video.js ├── videojs-youtube │ └── src │ │ └── youtube.js ├── videojshooks.js └── videojsplugins.js └── vpn.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | node_modules/ 50 | build/ 51 | cache/ 52 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var NwBuilder = require('nw-builder'); 3 | var os = require('os'); 4 | var argv = require('yargs') 5 | .alias('p', 'platforms') 6 | .argv; 7 | var del = require('del'); 8 | var detectCurrentPlatform = require('nw-builder/lib/detectCurrentPlatform.js'); 9 | var nw = new NwBuilder({ 10 | files: ['./src/**', './node_modules/**', './package.json'], 11 | version: '0.18.0', 12 | platforms: argv.p ? argv.p.split(',') : [detectCurrentPlatform()] 13 | }).on('log', console.log); 14 | 15 | gulp.task('run', function() { 16 | nw.options.files = './**'; 17 | return nw.run().catch(function(error) { 18 | console.error(error); 19 | }); 20 | }); 21 | 22 | gulp.task('build', ['clean'], function() { 23 | return nw.build().catch(function(error) { 24 | console.error(error); 25 | }); 26 | }); 27 | 28 | gulp.task('clean', function() { 29 | return del('build/'); 30 | }); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Popcorn-Time-Lightweight", 3 | "homepage": "https://popcorntime.is", 4 | "bugs": "https://github.com/PopcornTimeCommunity/desktop/issues", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/PopcornTimeCommunity/desktop.git" 8 | }, 9 | "license": "GPL-3.0", 10 | "main": "src/app/index.html", 11 | "version": "0.4.1-5", 12 | "node-remote": "*://*", 13 | "releaseName": "Popcorn Time Lightweight", 14 | "scripts": { 15 | "start": "gulp run" 16 | }, 17 | "single-instance": false, 18 | "window": { 19 | "title": "Popcorn Time Lightweight", 20 | "icon": "src/app/images/icon.png", 21 | "toolbar": false, 22 | "frame": true, 23 | "min_width": 960, 24 | "min_height": 520, 25 | "resizable": true, 26 | "show": false, 27 | "position": "center" 28 | }, 29 | "dependencies": { 30 | "urijs": "^1.17.0", 31 | "adm-zip": "0.4.7", 32 | "airplay-js": "^0.2.16", 33 | "async": "0.9.0", 34 | "chromecast-js": "git+https://github.com/captainyarr/chromecast-js.git", 35 | "gitlab": "1.3.0", 36 | "i18n": "0.5.0", 37 | "iconv-lite": "^0.4.7", 38 | "jschardet": "1.1.1", 39 | "json-rpc2": "^0.8.1", 40 | "kat-api-ce": "^0.0.4", 41 | "rarbg-api": "git+https://github.com/talas9/rarbg-api", 42 | "markdown": "~0.5.0", 43 | "memoizee": "^0.3.8", 44 | "mkdirp": "*", 45 | "moment": "^2.10.2", 46 | "mv": "^2.0.3", 47 | "nedb": "1.1.2", 48 | "node-captions": "0.3.8", 49 | "node-tvdb": "1.3.1", 50 | "node-webkit-fdialogs": "latest", 51 | "nodecast-js": "^0.1.2", 52 | "opensubtitles-ce": "^1.0.0", 53 | "os-name": "^1.0.3", 54 | "peerflix": "^0.32.2", 55 | "q": "2.0.3", 56 | "read-torrent": "1.3.0", 57 | "readdirp": "*", 58 | "request": "^2.55.0", 59 | "rimraf": "2.2.8", 60 | "sanitizer": "^0.1.2", 61 | "semver": "^4.3.3", 62 | "send": "0.12.2", 63 | "strike-api": "0.2.0", 64 | "tar": "^1.0.3", 65 | "temp": "^0.8.1", 66 | "torrent-tracker-health": "git+https://github.com/PTCE-Public/torrent-tracker-health.git", 67 | "underscore": "^1.8.3", 68 | "upnp-mediarenderer-client": "^1.2.1", 69 | "xmlbuilder": "^2.6.2" 70 | }, 71 | "devDependencies": { 72 | "del": "^2.2.0", 73 | "gulp": "^3.9.0", 74 | "nw-builder": "^2.0.2", 75 | "nw-gyp": "^0.12.4", 76 | "yargs": "^3.31.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/app/bootstrap.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | App.start(); 4 | })(window.App); 5 | -------------------------------------------------------------------------------- /src/app/css/animation.css: -------------------------------------------------------------------------------- 1 | #init-content { 2 | width: 100%; 3 | /* Full Width */ 4 | height: 5px; 5 | background: #000; 6 | margin-top: 14%; 7 | } 8 | 9 | .init-expand { 10 | width: 100%; 11 | height: 1px; 12 | margin: 2px 0; 13 | background: #2187e7; 14 | position: absolute; 15 | box-shadow: 0px 0px 10px 1px rgba(0,198,255,0.7); 16 | -moz-animation: fullexpand 10s ease-out; 17 | -webkit-animation: fullexpand 10s ease-out; 18 | } 19 | 20 | /* Full Width Animation Bar */ 21 | 22 | @-webkit-keyframes fullexpand { 23 | 0% { 24 | width: 0px; 25 | } 26 | 27 | 100% { 28 | width: 100%; 29 | }; 30 | } 31 | 32 | @-webkit-keyframes spin { 33 | 0% { -webkit-transform:rotate(0deg); } 34 | 100% { -webkit-transform:rotate(360deg); } 35 | } 36 | 37 | @-webkit-keyframes spinoff { 38 | 0% { -webkit-transform:rotate(0deg); } 39 | 100% { -webkit-transform:rotate(-360deg); } 40 | } 41 | 42 | 43 | @keyframes spin { 44 | 45 | 0% { 46 | 47 | transform: rotate(0deg); 48 | 49 | } 50 | 51 | 100% { 52 | 53 | transform: rotate(359deg); 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/fonts/OpenSans-Bold.woff -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/fonts/OpenSans-Regular.woff -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/fonts/OpenSans-Semibold.woff -------------------------------------------------------------------------------- /src/app/fonts/vjs.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/fonts/vjs.eot -------------------------------------------------------------------------------- /src/app/fonts/vjs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/fonts/vjs.ttf -------------------------------------------------------------------------------- /src/app/fonts/vjs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/fonts/vjs.woff -------------------------------------------------------------------------------- /src/app/images/bg-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/bg-header.jpg -------------------------------------------------------------------------------- /src/app/images/events/aprilsfool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/events/aprilsfool.png -------------------------------------------------------------------------------- /src/app/images/events/halloween.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/events/halloween.png -------------------------------------------------------------------------------- /src/app/images/events/newyear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/events/newyear.png -------------------------------------------------------------------------------- /src/app/images/events/pt_anniv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/events/pt_anniv.png -------------------------------------------------------------------------------- /src/app/images/events/stpatrick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/events/stpatrick.png -------------------------------------------------------------------------------- /src/app/images/events/stvalentine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/events/stvalentine.png -------------------------------------------------------------------------------- /src/app/images/events/xmas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/events/xmas.png -------------------------------------------------------------------------------- /src/app/images/flags/arabic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/arabic.png -------------------------------------------------------------------------------- /src/app/images/flags/armenian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/armenian.png -------------------------------------------------------------------------------- /src/app/images/flags/basque.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/basque.png -------------------------------------------------------------------------------- /src/app/images/flags/bengali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/bengali.png -------------------------------------------------------------------------------- /src/app/images/flags/bosnian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/bosnian.png -------------------------------------------------------------------------------- /src/app/images/flags/brazilian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/brazilian.png -------------------------------------------------------------------------------- /src/app/images/flags/bulgarian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/bulgarian.png -------------------------------------------------------------------------------- /src/app/images/flags/chinese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/chinese.png -------------------------------------------------------------------------------- /src/app/images/flags/croatian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/croatian.png -------------------------------------------------------------------------------- /src/app/images/flags/czech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/czech.png -------------------------------------------------------------------------------- /src/app/images/flags/danish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/danish.png -------------------------------------------------------------------------------- /src/app/images/flags/dutch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/dutch.png -------------------------------------------------------------------------------- /src/app/images/flags/english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/english.png -------------------------------------------------------------------------------- /src/app/images/flags/estonian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/estonian.png -------------------------------------------------------------------------------- /src/app/images/flags/farsi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/farsi.png -------------------------------------------------------------------------------- /src/app/images/flags/finnish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/finnish.png -------------------------------------------------------------------------------- /src/app/images/flags/french.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/french.png -------------------------------------------------------------------------------- /src/app/images/flags/german.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/german.png -------------------------------------------------------------------------------- /src/app/images/flags/greek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/greek.png -------------------------------------------------------------------------------- /src/app/images/flags/hebrew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/hebrew.png -------------------------------------------------------------------------------- /src/app/images/flags/hungarian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/hungarian.png -------------------------------------------------------------------------------- /src/app/images/flags/indonesian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/indonesian.png -------------------------------------------------------------------------------- /src/app/images/flags/italian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/italian.png -------------------------------------------------------------------------------- /src/app/images/flags/latvian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/latvian.png -------------------------------------------------------------------------------- /src/app/images/flags/lithuanian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/lithuanian.png -------------------------------------------------------------------------------- /src/app/images/flags/maltese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/maltese.png -------------------------------------------------------------------------------- /src/app/images/flags/none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/none.png -------------------------------------------------------------------------------- /src/app/images/flags/norwegian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/norwegian.png -------------------------------------------------------------------------------- /src/app/images/flags/polish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/polish.png -------------------------------------------------------------------------------- /src/app/images/flags/portuguese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/portuguese.png -------------------------------------------------------------------------------- /src/app/images/flags/romanian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/romanian.png -------------------------------------------------------------------------------- /src/app/images/flags/russian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/russian.png -------------------------------------------------------------------------------- /src/app/images/flags/serbian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/serbian.png -------------------------------------------------------------------------------- /src/app/images/flags/slovak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/slovak.png -------------------------------------------------------------------------------- /src/app/images/flags/slovenian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/slovenian.png -------------------------------------------------------------------------------- /src/app/images/flags/spanish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/spanish.png -------------------------------------------------------------------------------- /src/app/images/flags/swedish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/swedish.png -------------------------------------------------------------------------------- /src/app/images/flags/thai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/thai.png -------------------------------------------------------------------------------- /src/app/images/flags/turkish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/turkish.png -------------------------------------------------------------------------------- /src/app/images/flags/ukrainian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/ukrainian.png -------------------------------------------------------------------------------- /src/app/images/flags/vietnamese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/flags/vietnamese.png -------------------------------------------------------------------------------- /src/app/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icon.png -------------------------------------------------------------------------------- /src/app/images/icons/Player/Sound0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/Player/Sound0.png -------------------------------------------------------------------------------- /src/app/images/icons/Player/Sound1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/Player/Sound1.png -------------------------------------------------------------------------------- /src/app/images/icons/Player/Sound2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/Player/Sound2.png -------------------------------------------------------------------------------- /src/app/images/icons/Player/Sound3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/Player/Sound3.png -------------------------------------------------------------------------------- /src/app/images/icons/Player/Subtitles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/Player/Subtitles.png -------------------------------------------------------------------------------- /src/app/images/icons/airplay-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/airplay-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/airplay-xbmc-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/airplay-xbmc-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/chromecast-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/chromecast-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/dlna-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/dlna-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-bomi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-bomi-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-fleex-player-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-fleex-player-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mpc-hc-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-mpc-hc-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mplayer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-mplayer-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mpv-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-mpv-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-smplayer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-smplayer-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-vlc-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/external-vlc-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/getstrike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/getstrike.png -------------------------------------------------------------------------------- /src/app/images/icons/googlecloud-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/googlecloud-favicon.png -------------------------------------------------------------------------------- /src/app/images/icons/googlecloud-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/googlecloud-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/html5-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/html5-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-blog.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-discourse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-discourse.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-facebook.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-github.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-gitlab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-gitlab.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-google.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-popcorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-popcorn.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-stash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-stash.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/icon-twitter.png -------------------------------------------------------------------------------- /src/app/images/icons/imdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/imdb.png -------------------------------------------------------------------------------- /src/app/images/icons/kickasstorrents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/kickasstorrents.png -------------------------------------------------------------------------------- /src/app/images/icons/local-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/local-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/rarbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/rarbg.png -------------------------------------------------------------------------------- /src/app/images/icons/topbar_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/topbar_sprite.png -------------------------------------------------------------------------------- /src/app/images/icons/topbar_sprite_win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/icons/topbar_sprite_win.png -------------------------------------------------------------------------------- /src/app/images/popcorntime.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/popcorntime.icns -------------------------------------------------------------------------------- /src/app/images/popcorntime.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/popcorntime.ico -------------------------------------------------------------------------------- /src/app/images/popcorntime_uninstall.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/popcorntime_uninstall.ico -------------------------------------------------------------------------------- /src/app/images/posterholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopcornTimeCommunity/desktop/18cdbcfa2cadbba3c282277ca9eddeab5ab54055/src/app/images/posterholder.png -------------------------------------------------------------------------------- /src/app/language/.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | lang_map = af_ZA: af-ZA, am_ET: am-ET, ar_AE: ar-AE, ar_BH: ar-BH, ar_DZ: ar-DZ, ar_EG: ar-EG, ar_IQ: ar-IQ, ar_JO: ar-JO, ar_KW: ar-KW, ar_LB: ar-LB, ar_LY: ar-LY, ar_MA: ar-MA, ar_OM: ar-OM, ar_QA: ar-QA, ar_SA: ar-SA, ar_SY: ar-SY, ar_TN: ar-TN, ar_YE: ar-YE, arn_CL: arn-CL, as_IN: as-IN, az_AZ: az-AZ, ba_RU: ba-RU, be_BY: be-BY, bg_BG: bg-BG, bn_BD: bn-BD, bn_IN: bn-IN, bo_CN: bo-CN, br_FR: br-FR, bs_BA: bs-BA, ca_ES: ca-ES, co_FR: co-FR, cs_CZ: cs-CZ, cy_GB: cy-GB, da_DK: da-DK, de_AT: de-AT, de_CH: de-CH, de_DE: de-DE, de_LI: de-LI, de_LU: de-LU, dsb_DE: dsb-DE, dv_MV: dv-MV, el_GR: el-GR, en_AU: en-AU, en_BZ: en-BZ, en_CA: en-CA, en_GB: en-gb, en_IE: en-IE, en_IN: en-IN, en_JM: en-JM, en_MY: en-MY, en_NZ: en-NZ, en_PH: en-PH, en_SG: en-SG, en_TT: en-TT, en_US: en-US, en_ZA: en-ZA, en_ZW: en-ZW, es_AR: es-ar, es_BO: es-BO, es_CL: es-CL, es_CO: es-CO, es_CR: es-CR, es_DO: es-DO, es_EC: es-EC, es_ES: es-ES, es_GT: es-GT, es_HN: es-HN, es_MX: es-mx, es_NI: es-NI, es_PA: es-PA, es_PE: es-PE, es_PR: es-PR, es_PY: es-PY, es_SV: es-SV, es_US: es-US, es_UY: es-UY, es_VE: es-VE, et_EE: et-EE, eu_ES: eu-ES, fa_IR: fa-IR, fi_FI: fi-FI, fil_PH: fil-PH, fo_FO: fo-FO, fr_BE: fr-be, fr_CA: fr-CA, fr_CH: fr-CH, fr_FR: fr-FR, fr_LU: fr-LU, fr_MC: fr-MC, fy_NL: fy-NL, ga_IE: ga-IE, gd_GB: gd-GB, gl_ES: gl-ES, gsw_FR: gsw-FR, gu_IN: gu-IN, ha_NG: ha-NG, he_IL: he-IL, hi_IN: hi-IN, hr_BA: hr-BA, hr_HR: hr-HR, hsb_DE: hsb-DE, hu_HU: hu-HU, hy_AM: hy-AM, id_ID: id-ID, ig_NG: ig-NG, ii_CN: ii-CN, is_IS: is-IS, it_CH: it-CH, it_IT: it-IT, iu_CA: iu-CA, ja_JP: ja-JP, ka_GE: ka-GE, kk_KZ: kk-KZ, kl_GL: kl-GL, km_KH: km-KH, kn_IN: kn-IN, ko_KR: ko-KR, kok_IN: kok-IN, ku_IQ: ku-iq, ky_KG: ky-KG, lb_LU: lb-LU, lo_LA: lo-LA, lt_LT: lt-LT, lv_LV: lv-LV, mi_NZ: mi-NZ, mk_MK: mk-MK, ml_IN: ml-IN, mn_CN: mn-CN, mn_MN: mn-MN, moh_CA: moh-CA, mr_IN: mr-IN, ms_BN: ms-BN, ms_MY: ms-MY, mt_MT: mt-MT, nb_NO: nb-NO, ne_NP: ne-NP, nl_BE: nl-BE, nl_NL: nl-NL, nn_NO: nn-NO, nso_ZA: nso-ZA, oc_FR: oc-FR, or_IN: or-IN, pa_IN: pa-IN, pl_PL: pl-PL, prs_AF: prs-AF, ps_AF: ps-AF, pt_BR: pt-br, pt_PT: pt-PT, qut_GT: qut-GT, quz_BO: quz-BO, quz_EC: quz-EC, quz_PE: quz-PE, rm_CH: rm-CH, ro_RO: ro-RO, ru_RU: ru-RU, rw_RW: rw-RW, sa_IN: sa-IN, sah_RU: sah-RU, se_FI: se-FI, se_NO: se-NO, se_SE: se-SE, si_LK: si-LK, sk_SK: sk-SK, sl_SI: sl-SI, sma_NO: sma-NO, sma_SE: sma-SE, smj_NO: smj-NO, smj_SE: smj-SE, smn_FI: smn-FI, sms_FI: sms-FI, sq_AL: sq-AL, sr_BA: sr-BA, sr_CS: sr-CS, sr_ME: sr-ME, sr_RS: sr-RS, sv_FI: sv-FI, sv_SE: sv-SE, sw_KE: sw-KE, syr_SY: syr-SY, ta_IN: ta-IN, te_IN: te-IN, tg_TJ: tg-TJ, th_TH: th-TH, tk_TM: tk-TM, tn_ZA: tn-ZA, tr_TR: tr-TR, tt_RU: tt-RU, tzm_DZ: tzm-DZ, ug_CN: ug-CN, uk_UA: uk-UA, ur_PK: ur-PK, uz_UZ: uz-UZ, vi_VN: vi-VN, wo_SN: wo-SN, xh_ZA: xh-ZA, yo_NG: yo-NG, zh_CN: zh-cn, zh_HK: zh-HK, zh_MO: zh-MO, zh_SG: zh-SG, zh_TW: zh-tw, zu_ZA: zu-ZA 4 | 5 | [popcorn-time-app.desktop] 6 | file_filter = .json 7 | source_lang = en 8 | source_file = en.json 9 | type = KEYVALUEJSON 10 | minimum_perc = 80 11 | -------------------------------------------------------------------------------- /src/app/lib/cache.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | var cache = App.Config.cache; 7 | var db = openDatabase(cache.name, '', cache.desc, cache.size); 8 | 9 | var tableStruct = 'id TEXT, data TEXT, ttl INTEGER, date_saved INTEGER'; 10 | 11 | // Update db 12 | if (db.version !== cache.version) { 13 | db.changeVersion(db.version, cache.version, function (tx) { 14 | win.debug('New database version'); 15 | 16 | _.each(cache.tables, function (table) { 17 | tx.executeSql('DROP TABLE IF EXISTS ' + table, [], function () { 18 | win.debug('Create table ' + table); 19 | tx.executeSql('CREATE TABLE ' + table + ' (' + tableStruct + ')'); 20 | }, function (tx, err) { 21 | win.error('Ceating db table', err); 22 | }); 23 | }); 24 | }); 25 | } 26 | 27 | var buildWhereIn = function (ids) { 28 | return 'id IN (' + _.map(ids, function () { 29 | return '?'; 30 | }).join(',') + ')'; 31 | }; 32 | 33 | var Cache = function (table) { 34 | this.table = table; 35 | this.db = db; 36 | }; 37 | 38 | _.extend(Cache.prototype, { 39 | getItems: function (ids) { 40 | var self = this; 41 | 42 | // getItems can be use with scalar id 43 | ids = $.makeArray(ids); 44 | 45 | var deferred = Q.defer(); 46 | db.transaction(function (tx) { 47 | // Select item in db 48 | var query = 'SELECT * FROM ' + self.table + ' WHERE ' + buildWhereIn(ids); 49 | tx.executeSql(query, ids, function (tx, results) { 50 | var cachedData = {}; 51 | var expiredData = []; 52 | var now = +new Date(); 53 | 54 | // Filter expired item 55 | for (var i = 0; i < results.rows.length; i++) { 56 | var row = results.rows.item(i); 57 | var data = JSON.parse(row.data); 58 | 59 | if (row.ttl !== 0 && row.ttl < now - row.date_saved) { 60 | expiredData.push(row.id); 61 | } else { 62 | cachedData[row.id] = data; 63 | } 64 | } 65 | 66 | // Delete expired data 67 | if (!_.isEmpty(expiredData)) { 68 | self.deleteItems(expiredData); 69 | } 70 | 71 | deferred.resolve(cachedData); 72 | }, function (tx, err) { 73 | win.error('Expired data cleaning', err); 74 | deferred.resolve([]); 75 | }); 76 | }); 77 | return deferred.promise; 78 | }, 79 | 80 | setItem: function (id, item, ttl) { 81 | var items = {}; 82 | items[id] = item; 83 | this.setItems(items, ttl); 84 | }, 85 | 86 | setItems: function (items, ttl) { 87 | var self = this; 88 | ttl = +ttl || 0; 89 | var now = +new Date(); 90 | 91 | self.getItems(_.keys(items)) 92 | .then(function (cachedData) { 93 | db.transaction(function (tx) { 94 | _.each(cachedData, function (item, id) { 95 | var data = JSON.stringify(item); 96 | tx.executeSql('UPDATE ' + self.table + ' SET data = ?, ttl = ?, date_saved = ? WHERE id = ?', [data, ttl, now, id]); 97 | }); 98 | 99 | var missedData = _.difference(_.keys(items), _.keys(cachedData)); 100 | _.each(missedData, function (id) { 101 | var data = JSON.stringify(items[id]); 102 | var query = 'INSERT INTO ' + self.table + ' VALUES (?, ?, ?, ?)'; 103 | tx.executeSql(query, [id, data, ttl, now]); 104 | }); 105 | }, function (err) { 106 | win.error('db.transaction()', err); 107 | }); 108 | }); 109 | }, 110 | 111 | deleteItems: function (ids) { 112 | var self = this; 113 | db.transaction(function (tx) { 114 | var query = 'DELETE FROM ' + self.table + ' WHERE ' + buildWhereIn(ids); 115 | tx.executeSql(query, ids, function () {}); 116 | }); 117 | }, 118 | 119 | flushTable: function () { 120 | var self = this; 121 | 122 | return Q.Promise(function (resolve, reject) { 123 | db.transaction(function (tx) { 124 | var query = 'DELETE FROM ' + self.table; 125 | tx.executeSql(query, function () {}); 126 | resolve(); 127 | }); 128 | }); 129 | } 130 | }); 131 | 132 | App.Cache = Cache; 133 | 134 | })(window.App); 135 | -------------------------------------------------------------------------------- /src/app/lib/cachev2.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | // Wraps a IndexedDB Request in a promise 7 | function WrapRequest(deferred, request) { 8 | if (request === undefined) { 9 | request = deferred; 10 | deferred = Q.defer(); 11 | } 12 | 13 | var existingSuccess = request.onsuccess; 14 | request.onsuccess = function () { 15 | deferred.resolve(this.result); 16 | if (existingSuccess !== null) { 17 | existingSuccess.call(request); 18 | } 19 | }; 20 | 21 | var existingError = request.onerror; 22 | request.onerror = function () { 23 | deferred.reject(this.error); 24 | if (existingError !== null) { 25 | existingError.call(request); 26 | } 27 | }; 28 | 29 | return deferred; 30 | } 31 | 32 | function Cache(table) { 33 | var self = this; 34 | 35 | this.table = table; 36 | 37 | var db = indexedDB.open(App.Config.cachev2.name, App.Config.cachev2.version); 38 | db.onsuccess = function () { 39 | self.db = this.result; 40 | }; 41 | db.onupgradeneeded = function () { 42 | var self = this; 43 | App.Config.cachev2.tables.forEach(function (tableName) { 44 | if (_.contains(self.result.objectStoreNames, tableName)) { 45 | return; 46 | } 47 | self.result.createObjectStore(tableName, { 48 | keyPath: '_id' 49 | }); 50 | }); 51 | }; 52 | this._openPromise = WrapRequest(db).promise; 53 | } 54 | 55 | Cache.prototype.set = function (key, value, options) { 56 | options = options || {}; 57 | var data = _.extend(value, { 58 | _id: key, 59 | _ttl: options.ttl || 14400000, 60 | _lastModified: +new Date() 61 | }); 62 | return WrapRequest(this.db.transaction(this.table, 'readwrite').objectStore(this.table).put(data)).promise; 63 | }; 64 | 65 | Cache.prototype.get = function (key) { 66 | var deferred = Q.defer(); 67 | var request = this.db.transaction(this.table, 'readonly').objectStore(this.table).get(key); 68 | request.onsuccess = function () { 69 | delete this.result._id; 70 | delete this.result._ttl; 71 | delete this.result._lastModified; 72 | deferred.resolve(this.result); 73 | }; 74 | request.onerror = function () { 75 | deferred.reject(this.error); 76 | }; 77 | return deferred.promise; 78 | }; 79 | 80 | Cache.prototype.getMultiple = function (keys) { 81 | var deferred = Q.defer(); 82 | var request = this.db.transaction(this.table, 'readonly').objectStore(this.table).openCursor(); 83 | var results = []; 84 | request.onsuccess = function () { 85 | var cursor = this.result; 86 | if (cursor) { 87 | if (_.contains(keys, cursor.key)) { 88 | results.push(cursor.value); 89 | } 90 | cursor.continue(); 91 | } else { 92 | deferred.resolve(results); 93 | } 94 | }; 95 | request.onerror = function () { 96 | deferred.reject(this.error); 97 | }; 98 | return deferred.promise; 99 | }; 100 | 101 | Cache.prototype.update = function (key, value, options) { 102 | var self = this; 103 | options = options || {}; 104 | var deferred = Q.defer(); 105 | var request = this.db.transaction(this.table, 'readonly').objectStore(this.table).get(key); 106 | request.onsuccess = function () { 107 | var data = _.extend(this.result, value); 108 | data._id = key; 109 | data._ttl = options.ttl || 14400000; 110 | data._lastModified = +new Date(); 111 | WrapRequest(deferred, self.db.transaction(this.table, 'readwrite').objectStore(this.table).put(data)); 112 | }; 113 | request.onerror = function () { 114 | deferred.reject(this.error); 115 | }; 116 | return deferred.promise; 117 | }; 118 | 119 | Cache.prototype.gc = function () { 120 | var request = this.db.transaction(this.table, 'readwrite').objectStore(this.table).openCursor(); 121 | request.onsuccess = function () { 122 | var cursor = this.result; 123 | if (cursor) { 124 | var ttl = cursor.value._ttl; 125 | var lastModified = cursor.value._lastModified; 126 | if (new Date() - lastModified > ttl) { 127 | cursor.delete(); 128 | } 129 | cursor.continue(); 130 | } 131 | }; 132 | }; 133 | 134 | App.CacheV2 = Cache; 135 | 136 | })(window.App); 137 | -------------------------------------------------------------------------------- /src/app/lib/config.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Config = { 5 | title: 'Popcorn Time CE', 6 | platform: process.platform, 7 | genres: [ 8 | 'All', 9 | 'Action', 10 | 'Adventure', 11 | 'Animation', 12 | 'Biography', 13 | 'Comedy', 14 | 'Crime', 15 | 'Documentary', 16 | 'Drama', 17 | 'Family', 18 | 'Fantasy', 19 | 'Film-Noir', 20 | 'History', 21 | 'Horror', 22 | 'Music', 23 | 'Musical', 24 | 'Mystery', 25 | 'Romance', 26 | 'Sci-Fi', 27 | 'Short', 28 | 'Sport', 29 | 'Thriller', 30 | 'War', 31 | 'Western' 32 | ], 33 | 34 | sorters: [ 35 | 'trending', 36 | 'popularity', 37 | 'last added', 38 | 'year', 39 | 'rating', 40 | 'downloads', 41 | 'likes' 42 | ], 43 | 44 | sorters_tv: [ 45 | 'popularity', 46 | 'trending', 47 | 'updated', 48 | 'year', 49 | 'name', 50 | 'rating' 51 | ], 52 | 53 | types_anime: [ 54 | 'All', 55 | 'Movies', 56 | 'TV', 57 | 'OVA', 58 | 'ONA' 59 | ], 60 | 61 | genres_anime: [ 62 | 'All', 63 | 'Action', 64 | 'Adventure', 65 | 'Cars', 66 | 'Comedy', 67 | 'Dementia', 68 | 'Demons', 69 | 'Drama', 70 | 'Ecchi', 71 | 'Fantasy', 72 | 'Game', 73 | 'Harem', 74 | 'Historical', 75 | 'Horror', 76 | 'Josei', 77 | 'Kids', 78 | 'Magic', 79 | 'Martial Arts', 80 | 'Mecha', 81 | 'Military', 82 | 'Music', 83 | 'Mystery', 84 | 'Parody', 85 | 'Police', 86 | 'Psychological', 87 | 'Romance', 88 | 'Samurai', 89 | 'School', 90 | 'Sci-Fi', 91 | 'Seinen', 92 | 'Shoujo', 93 | 'Shoujo Ai', 94 | 'Shounen', 95 | 'Shounen Ai', 96 | 'Slice of Life', 97 | 'Space', 98 | 'Sports', 99 | 'Super Power', 100 | 'Supernatural', 101 | 'Thriller', 102 | 'Vampire' 103 | ], 104 | 105 | genres_tv: [ 106 | 'All', 107 | 'Action', 108 | 'Adventure', 109 | 'Animation', 110 | 'Children', 111 | 'Comedy', 112 | 'Crime', 113 | 'Documentary', 114 | 'Drama', 115 | 'Family', 116 | 'Fantasy', 117 | 'Game Show', 118 | 'Home and Garden', 119 | 'Horror', 120 | 'Mini Series', 121 | 'Mystery', 122 | 'News', 123 | 'Reality', 124 | 'Romance', 125 | 'Science Fiction', 126 | 'Soap', 127 | 'Special Interest', 128 | 'Sport', 129 | 'Suspense', 130 | 'Talk Show', 131 | 'Thriller', 132 | 'Western' 133 | ], 134 | 135 | cache: { 136 | name: 'cachedb', 137 | version: '1.7', 138 | tables: ['subtitle'], 139 | desc: 'Cache database', 140 | size: 10 * 1024 * 1024 141 | }, 142 | 143 | cachev2: { 144 | name: 'cache', 145 | version: 5, 146 | tables: ['metadata'] 147 | }, 148 | 149 | providers: { 150 | movie: ['Yts'], 151 | tvshow: ['TVApi'], 152 | anime: ['Haruhichan'], 153 | subtitle: 'YSubs', 154 | metadata: 'Trakttv', 155 | tvst: 'TVShowTime', 156 | 157 | tvshowsubtitle: 'OpenSubtitles', 158 | torrentCache: 'TorrentCache' 159 | }, 160 | 161 | getProvider: function (type) { 162 | var provider = App.Config.providers[type]; 163 | if (provider instanceof Array) { 164 | return _.map(provider, function (t) { 165 | return App.Providers.get(t); 166 | }); 167 | } 168 | return App.Providers.get(provider); 169 | } 170 | }; 171 | 172 | App.Config = Config; 173 | })(window.App); 174 | -------------------------------------------------------------------------------- /src/app/lib/device/airplay.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var browser = require('airplay-js').createBrowser(), 5 | collection = App.Device.Collection; 6 | 7 | var makeID = function (baseID) { 8 | return 'airplay-' + baseID.replace(':', ''); 9 | }; 10 | 11 | var Airplay = App.Device.Generic.extend({ 12 | defaults: { 13 | type: 'airplay', 14 | typeFamily: 'external' 15 | }, 16 | makeID: makeID, 17 | initialize: function (attrs) { 18 | this.device = attrs.device; 19 | this.attributes.id = this.makeID(this.device.serverInfo.macAddress || this.device.serverInfo.deviceId || '' + this.device.id); 20 | this.attributes.name = this.device.name || this.device.serverInfo.model; 21 | this.attributes.address = this.device.info[0]; 22 | }, 23 | play: function (streamModel) { 24 | var url = streamModel.attributes.src; 25 | this.device.play(url); 26 | }, 27 | stop: function () { 28 | this.device.stop(function () {}); 29 | } 30 | }); 31 | 32 | 33 | browser.on('deviceOn', function (device) { 34 | 35 | collection.add(new Airplay({ 36 | device: device 37 | })); 38 | }); 39 | 40 | browser.on('deviceOff', function (device) { 41 | 42 | var model = collection.get({ 43 | id: makeID(device.id) 44 | }); 45 | if (model) { 46 | model.destroy(); 47 | } 48 | }); 49 | win.info('Scanning: Local Network for Airplay devices'); 50 | browser.start(); 51 | App.Device.Airplay = Airplay; 52 | 53 | })(window.App); 54 | -------------------------------------------------------------------------------- /src/app/lib/device/googlecloud.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 3 | 'use strict'; 4 | var collection = App.Device.Collection; 5 | 6 | var Airplay = App.Device.Generic.extend(); 7 | 8 | //if (this.model.get('google_video')) { 9 | collection.add(new Airplay({ 10 | id: 'googlecloud', 11 | type: 'googlecloud', //icon 12 | typeFamily: 'internal', 13 | name: 'Google Cloud' 14 | })); 15 | //} 16 | 17 | 18 | App.Device.Airplay = Airplay; 19 | 20 | })(window.App); 21 | -------------------------------------------------------------------------------- /src/app/lib/device/nacholink.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 3 | 'use strict'; 4 | var collection = App.Device.Collection; 5 | 6 | var Airplay = App.Device.Generic.extend({ 7 | stop: function () {}, 8 | }); 9 | 10 | //if (this.model.get('google_video')) { 11 | collection.add(new Airplay({ 12 | id: 'html5', 13 | type: 'html5', //icon 14 | typeFamily: 'internal', 15 | name: 'HTML5 Video' 16 | })); 17 | //} 18 | 19 | 20 | App.Device.Airplay = Airplay; 21 | 22 | })(window.App); 23 | -------------------------------------------------------------------------------- /src/app/lib/device/xbmc.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var browser = require('airplay-xbmc').createBrowser(); 5 | 6 | var collection = App.Device.Collection; 7 | 8 | var makeID = function (baseID) { 9 | return 'airplay-xbmc' + baseID.replace(':', ''); 10 | }; 11 | 12 | var AirplayXBMC = App.Device.Airplay.extend({ 13 | defaults: { 14 | type: 'airplay-xbmc', 15 | typeFamily: 'external' 16 | }, 17 | makeID: makeID, 18 | initialize: function (attrs) { 19 | this.device = attrs.device; 20 | this.attributes.address = this.device.info[0]; 21 | } 22 | }); 23 | 24 | browser.on('deviceOn', function (device) { 25 | collection.add(new AirplayXBMC({ 26 | device: device 27 | })); 28 | }); 29 | 30 | browser.on('deviceOff', function (device) { 31 | var model = collection.get({ 32 | id: makeID(device.id) 33 | }); 34 | if (model) { 35 | model.destroy(); 36 | } 37 | }); 38 | 39 | browser.start(); 40 | 41 | App.Device.AirplayXBMC = AirplayXBMC; 42 | })(window.App); 43 | -------------------------------------------------------------------------------- /src/app/lib/models/anime_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var AnimeCollection = App.Model.Collection.extend({ 5 | model: App.Model.Movie, 6 | popid: 'mal_id', 7 | type: 'animes', 8 | getProviders: function () { 9 | return { 10 | torrents: App.Config.getProvider('anime') 11 | }; 12 | }, 13 | }); 14 | 15 | App.Model.AnimeCollection = AnimeCollection; 16 | })(window.App); 17 | -------------------------------------------------------------------------------- /src/app/lib/models/favorite_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | var FavoriteCollection = Backbone.Collection.extend({ 7 | model: App.Model.Movie, 8 | 9 | initialize: function (models, options) { 10 | this.providers = { 11 | torrent: App.Providers.get('Favorites') 12 | }; 13 | 14 | options = options || {}; 15 | options.filter = options.filter || new App.Model.Filter(); 16 | 17 | this.filter = _.defaults(_.clone(options.filter.attributes), { 18 | page: 1 19 | }); 20 | this.hasMore = true; 21 | 22 | Backbone.Collection.prototype.initialize.apply(this, arguments); 23 | }, 24 | 25 | fetch: function () { 26 | var self = this; 27 | 28 | if (this.state === 'loading' && !this.hasMore) { 29 | return; 30 | } 31 | 32 | this.state = 'loading'; 33 | self.trigger('loading', self); 34 | 35 | var torrent = this.providers.torrent; 36 | var torrentPromise = torrent.fetch(this.filter); 37 | 38 | var idsPromise = torrentPromise.then(_.bind(torrent.extractIds, torrent)); 39 | 40 | return Q.all([torrentPromise]) 41 | .spread(function (movies) { 42 | 43 | // If a new request was started... 44 | _.each(movies, function (movie) { 45 | var id = movie.imdb_id; 46 | }); 47 | 48 | if (_.isEmpty(movies)) { 49 | self.hasMore = false; 50 | } 51 | 52 | self.add(movies); 53 | self.trigger('sync', self); 54 | self.state = 'loaded'; 55 | self.trigger('loaded', self, self.state); 56 | }) 57 | .catch(function (err) { 58 | self.state = 'error'; 59 | self.trigger('loaded', self, self.state); 60 | win.error('FavoriteCollection.fetch()', err); 61 | }); 62 | }, 63 | 64 | fetchMore: function () { 65 | this.filter.page += 1; 66 | this.fetch(); 67 | } 68 | 69 | }); 70 | 71 | App.Model.FavoriteCollection = FavoriteCollection; 72 | })(window.App); 73 | -------------------------------------------------------------------------------- /src/app/lib/models/filter.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Filter = Backbone.Model.extend({ 5 | defaults: { 6 | genres: [], 7 | sorters: [], 8 | types: [], 9 | order: -1 10 | }, 11 | 12 | initialize: function () { 13 | this.set('sorter', this.get('sorter') || this.get('sorters')[0]); 14 | this.set('genre', this.get('genre') || this.get('genres')[0]); 15 | this.set('type', this.get('type') || this.get('types')[0]); 16 | this.set('order', this.get('order') || -1); 17 | } 18 | }); 19 | 20 | App.Model.Filter = Filter; 21 | })(window.App); 22 | -------------------------------------------------------------------------------- /src/app/lib/models/generic_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | var PopCollection = Backbone.Collection.extend({ 7 | popid: 'imdb_id', 8 | initialize: function (models, options) { 9 | this.providers = this.getProviders(); 10 | 11 | options = options || {}; 12 | options.filter = options.filter || new App.Model.Filter(); 13 | 14 | this.filter = _.defaults(_.clone(options.filter.attributes), { 15 | page: 1 16 | }); 17 | this.hasMore = true; 18 | 19 | Backbone.Collection.prototype.initialize.apply(this, arguments); 20 | }, 21 | 22 | fetch: function () { 23 | var self = this; 24 | 25 | if (this.state === 'loading' && !this.hasMore) { 26 | return; 27 | } 28 | 29 | this.state = 'loading'; 30 | self.trigger('loading', self); 31 | 32 | var subtitle = this.providers.subtitle; 33 | var torrents = this.providers.torrents; 34 | 35 | /* XXX(xaiki): provider hack 36 | * 37 | * we actually do this to 'save' the provider number, 38 | * this is shit, as we can't dynamically switch 39 | * providers, the 'right' way to do it is to have every 40 | * provider declare a unique id, and then lookthem up in 41 | * a hash. 42 | */ 43 | var torrentPromises = _.map(torrents, function (torrentProvider, pid) { //XXX(xaiki): provider hack 44 | var deferred = Q.defer(); 45 | 46 | var promises = [torrentProvider.fetch(self.filter)]; 47 | 48 | var idsPromise = promises[0].then(_.bind(torrentProvider.extractIds, torrentProvider)); 49 | 50 | if (subtitle) { 51 | promises.push(idsPromise.then(_.bind(subtitle.fetch, subtitle))); 52 | } 53 | 54 | Q.all(promises) 55 | .spread(function (torrents, subtitles) { 56 | // If a new request was started... 57 | _.each(torrents.results, function (movie) { 58 | var id = movie[self.popid]; 59 | /* XXX(xaiki): check if we already have this 60 | * torrent if we do merge our torrents with the 61 | * ones we already have and update. 62 | */ 63 | var model = self.get(id); 64 | if (model) { 65 | var ts = model.get('torrents'); 66 | _.extend(ts, movie.torrents); 67 | model.set('torrents', ts); 68 | 69 | return; 70 | } 71 | movie.provider = torrentProvider.name; 72 | 73 | if (subtitles) { 74 | movie.subtitle = subtitles[id]; 75 | } 76 | }); 77 | 78 | return deferred.resolve(torrents); 79 | }) 80 | .catch(function (err) { 81 | self.state = 'error'; 82 | self.trigger('loaded', self, self.state); 83 | win.error('PopCollection.fetch() : torrentPromises mapping', err); 84 | }); 85 | 86 | return deferred.promise; 87 | }); 88 | 89 | Q.all(torrentPromises).done(function (torrents) { 90 | _.forEach(torrents, function (t) { 91 | self.add(t.results); 92 | }); 93 | self.hasMore = _.pluck(torrents, 'hasMore')[0]; 94 | self.trigger('sync', self); 95 | self.state = 'loaded'; 96 | self.trigger('loaded', self, self.state); 97 | }); 98 | }, 99 | 100 | fetchMore: function () { 101 | this.filter.page += 1; 102 | this.fetch(); 103 | } 104 | }); 105 | 106 | App.Model.Collection = PopCollection; 107 | })(window.App); 108 | -------------------------------------------------------------------------------- /src/app/lib/models/movie.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Movie = Backbone.Model.extend({ 5 | events: { 6 | 'change:torrents': 'updateHealth', 7 | }, 8 | 9 | idAttribute: 'imdb_id', 10 | 11 | initialize: function () { 12 | this.updateHealth(); 13 | }, 14 | 15 | updateHealth: function () { 16 | var torrents = this.get('torrents'); 17 | 18 | _.each(torrents, function (torrent) { 19 | torrent.health = Common.healthMap[Common.calcHealth(torrent)]; 20 | }); 21 | 22 | this.set('torrents', torrents, { 23 | silent: true 24 | }); 25 | } 26 | }); 27 | 28 | App.Model.Movie = Movie; 29 | })(window.App); 30 | -------------------------------------------------------------------------------- /src/app/lib/models/movie_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | var MovieCollection = App.Model.Collection.extend({ 7 | model: App.Model.Movie, 8 | popid: 'imdb_id', 9 | type: 'movies', 10 | getProviders: function () { 11 | return { 12 | torrents: App.Config.getProvider('movie'), 13 | subtitle: App.Config.getProvider('subtitle') 14 | }; 15 | } 16 | }); 17 | 18 | App.Model.MovieCollection = MovieCollection; 19 | })(window.App); 20 | -------------------------------------------------------------------------------- /src/app/lib/models/notification.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Notification = Backbone.Model.extend({ 5 | defaults: { 6 | body: 'Notification Body', 7 | buttons: [], 8 | showClose: true, 9 | showRestart: false, 10 | title: 'Notification Title', 11 | type: 'info' 12 | }, 13 | 14 | // Added 'showRestart' option here since we'll use it more than once. 15 | initialize: function () { 16 | this.toggleShowRestart(); 17 | // If this property is changed after init, add/remove the button. 18 | this.on('change:showRestart', this.toggleShowRestart.bind(this)); 19 | }, 20 | 21 | addShowRestart: function () { 22 | this.set('buttons', this.get('buttons').concat([{ 23 | title: i18n.__('Restart'), 24 | action: this.restartPopcornTime 25 | }])); 26 | }, 27 | 28 | removeShowRestart: function () { 29 | this.set('buttons', this.get('buttons').filter(function (b) { 30 | return b.title !== i18n.__('Restart'); 31 | })); 32 | }, 33 | 34 | toggleShowRestart: function () { 35 | this[(this.get('showRestart') ? 'add' : 'remove') + 'ShowRestart'](); 36 | }, 37 | 38 | restartPopcornTime: function () { 39 | App.vent.trigger('restartPopcornTime'); 40 | } 41 | }); 42 | 43 | App.Model.Notification = Notification; 44 | })(window.App); 45 | -------------------------------------------------------------------------------- /src/app/lib/models/show.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Show = App.Model.Movie.extend({ 5 | idAttribute: 'tvdb_id', 6 | updateHealth: function () { 7 | var torrents = this.get('torrents'); 8 | 9 | _.each(torrents, function (torrent) { 10 | _.each(torrent, function (episode, key) { 11 | torrent[key].health = Common.healthMap[Common.calcHealth(episode)]; 12 | }); 13 | }); 14 | 15 | this.set('torrents', torrents, { 16 | silent: true 17 | }); 18 | } 19 | }); 20 | 21 | App.Model.Show = Show; 22 | })(window.App); 23 | -------------------------------------------------------------------------------- /src/app/lib/models/show_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var ShowCollection = App.Model.Collection.extend({ 5 | model: App.Model.Movie, 6 | popid: 'imdb_id', 7 | type: 'shows', 8 | getProviders: function () { 9 | return { 10 | torrents: App.Config.getProvider('tvshow') 11 | }; 12 | }, 13 | }); 14 | 15 | App.Model.ShowCollection = ShowCollection; 16 | })(window.App); 17 | -------------------------------------------------------------------------------- /src/app/lib/models/stream_info.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var StreamInfo = Backbone.Model.extend({ 5 | initialize: function () { 6 | var engine = this.get('engine'); 7 | 8 | var self = this; 9 | 10 | engine.once('ready', function () { 11 | var size = 0; 12 | 13 | if (self.get('file_index')) { 14 | size = engine.files[self.get('file_index')].length; // torrent with multi-files 15 | } else { 16 | engine.files.forEach(function (file) { // pct torrent 17 | size += file.length || 0; 18 | }); 19 | } 20 | 21 | self.set('size', size); 22 | }); 23 | 24 | this.on('change:size', function () { 25 | self.set('sizeFormatted', Common.fileSize(self.get('size'))); 26 | }); 27 | 28 | this.set('size', 0); 29 | }, 30 | 31 | updateStats: function () { 32 | var active = function (wire) { 33 | return !wire.peerChoking; 34 | }; 35 | var engine = this.get('engine'); 36 | var swarm = engine.swarm; 37 | var BUFFERING_SIZE = 10 * 1024 * 1024; 38 | var converted_speed = 0; 39 | var converted_downloaded = 0; 40 | var buffer_percent = 0; 41 | 42 | var upload_speed = swarm.uploadSpeed(); // upload speed 43 | var final_upload_speed = Common.fileSize(0) + '/s'; 44 | if (!isNaN(upload_speed) && upload_speed !== 0) { 45 | final_upload_speed = Common.fileSize(upload_speed) + '/s'; 46 | } 47 | 48 | var download_speed = swarm.downloadSpeed(); // download speed 49 | var final_download_speed = Common.fileSize(0) + '/s'; 50 | if (!isNaN(download_speed) && download_speed !== 0) { 51 | final_download_speed = Common.fileSize(download_speed) + '/s'; 52 | } 53 | 54 | var downloaded = swarm.downloaded || 0; // downloaded 55 | if (swarm.cachedDownload) { 56 | downloaded += swarm.cachedDownload; 57 | } 58 | var final_downloaded = Common.fileSize(0); 59 | var final_downloaded_percent = 0; 60 | if (downloaded !== 0) { 61 | final_downloaded = Common.fileSize(downloaded); 62 | final_downloaded_percent = 100 / this.get('size') * downloaded; 63 | } 64 | 65 | if (final_downloaded_percent >= 100) { 66 | final_downloaded_percent = 100; 67 | } 68 | 69 | var downloadTimeLeft = Math.round((this.get('size') - downloaded) / swarm.downloadSpeed()); // time to wait before download complete 70 | if (isNaN(downloadTimeLeft) || downloadTimeLeft < 0) { 71 | downloadTimeLeft = 0; 72 | } else if (!isFinite(downloadTimeLeft)) { // infinite 73 | downloadTimeLeft = undefined; 74 | } 75 | 76 | this.set('pieces', swarm.piecesGot); 77 | this.set('downloaded', downloaded); 78 | this.set('active_peers', swarm.wires.filter(active).length); 79 | this.set('total_peers', swarm.wires.length); 80 | 81 | this.set('uploadSpeed', final_upload_speed); // variable for Upload Speed 82 | this.set('downloadSpeed', final_download_speed); // variable for Download Speed 83 | this.set('downloadedFormatted', final_downloaded); // variable for Downloaded 84 | this.set('downloadedPercent', final_downloaded_percent); // variable for Downloaded percentage 85 | this.set('time_left', downloadTimeLeft); // variable for time left before 100% downloaded 86 | 87 | buffer_percent = downloaded / (BUFFERING_SIZE / 100); 88 | if (buffer_percent >= 100) { 89 | buffer_percent = 99; // wait for subtitles 90 | } 91 | this.set('buffer_percent', buffer_percent); 92 | } 93 | }); 94 | 95 | App.Model.StreamInfo = StreamInfo; 96 | })(window.App); 97 | -------------------------------------------------------------------------------- /src/app/lib/models/watchlist_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | var WatchlistCollection = App.Model.Collection.extend({ 7 | model: App.Model.Movie, 8 | hasMore: false, 9 | 10 | getProviders: function () { 11 | return { 12 | torrents: [App.Providers.get('Watchlist')] 13 | }; 14 | } 15 | 16 | }); 17 | 18 | App.Model.WatchlistCollection = WatchlistCollection; 19 | })(window.App); 20 | -------------------------------------------------------------------------------- /src/app/lib/providers/cache_provider.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | var CacheProvider = function (table, ttl) { 7 | this.table = table; 8 | this.cache = new App.Cache(table); 9 | this.ttl = ttl; 10 | }; 11 | 12 | // TODO: Duplicate cache entry 13 | CacheProvider.prototype.fetch = function (ids) { 14 | var self = this; 15 | ids = _.map(ids, function (id) { 16 | return id.toString(); 17 | }); 18 | var cachePromise = this.cache.getItems(ids); 19 | var queryPromise = cachePromise.then(function (items) { 20 | // Filter out cached subtitles 21 | var cachedIds = _.keys(items); 22 | win.debug(cachedIds.length + ' cached ' + self.table); 23 | var filteredId = _.difference(ids, cachedIds); 24 | return filteredId; 25 | }) 26 | .then(this.query); 27 | 28 | // Cache ysubs subtitles 29 | queryPromise.then(function (items) { 30 | win.debug('Cache ' + _.keys(items).length + ' ' + self.table); 31 | self.cache.setItems(items, self.ttl); 32 | }); 33 | 34 | // Wait for all query promise to finish 35 | return Q.allSettled([cachePromise, queryPromise]) 36 | .then(function (results) { 37 | // Merge all promise result 38 | var items = {}; 39 | _.each(results, function (result) { 40 | if (result.state === 'fulfilled') { 41 | _.extend(items, result.value); 42 | } 43 | }); 44 | 45 | return items; 46 | }); 47 | }; 48 | 49 | App.Providers.CacheProvider = CacheProvider; 50 | })(window.App); 51 | -------------------------------------------------------------------------------- /src/app/lib/providers/cache_providerv2.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Q = require('q'); 5 | 6 | var CacheProvider = function (table, ttl) { 7 | this._table = table; 8 | this._cache = new App.CacheV2(table); 9 | this.ttl = ttl; 10 | }; 11 | 12 | CacheProvider.prototype.fetch = function (ids) { 13 | var self = this; 14 | if (this._cache._openPromise.isFulfilled()) { 15 | return this._cache.getMultiple(ids).then(function (items) { 16 | win.debug('Loaded ' + items.length + ' items from the ' + self._table + ' cache!'); 17 | return items; 18 | }); 19 | } else { 20 | return this._cache._openPromise.then(function () { 21 | return self._cache.getMultiple(ids).then(function (items) { 22 | win.debug('Loaded ' + items.length + ' items from the ' + self._table + ' cache!'); 23 | return items; 24 | }); 25 | }); 26 | } 27 | }; 28 | 29 | CacheProvider.prototype.store = function (key, items) { 30 | var promises = []; 31 | var self = this; 32 | win.debug('Stored ' + items.length + ' items in the ' + this._table + ' cache!'); 33 | _.each(items, function (item) { 34 | if (!item[key]) { 35 | return; 36 | } 37 | promises.push(self._cache.set(item[key], item, { 38 | ttl: self.ttl 39 | })); 40 | }); 41 | return Q(items); 42 | }; 43 | 44 | App.Providers.CacheProviderV2 = CacheProvider; 45 | })(window.App); 46 | -------------------------------------------------------------------------------- /src/app/lib/providers/favorites.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var Q = require('q'); 4 | 5 | var Favorites = function () {}; 6 | Favorites.prototype.constructor = Favorites; 7 | 8 | var queryTorrents = function (filters) { 9 | return App.db.getBookmarks(filters) 10 | .then(function (data) { 11 | return data; 12 | }, 13 | function (error) { 14 | return []; 15 | }); 16 | }; 17 | 18 | var formatForPopcorn = function (items) { 19 | var movieList = []; 20 | 21 | items.forEach(function (movie) { 22 | 23 | var deferred = Q.defer(); 24 | // we check if its a movie 25 | // or tv show then we extract right data 26 | if (movie.type === 'movie') { 27 | // its a movie 28 | Database.getMovie(movie.imdb_id) 29 | .then(function (data) { 30 | data.type = 'bookmarkedmovie'; 31 | if (/slurm.trakt.us/.test(data.image)) { 32 | data.image = data.image.replace(/slurm.trakt.us/, 'walter.trakt.us'); 33 | } 34 | deferred.resolve(data); 35 | }, 36 | function (err) { 37 | deferred.reject(err); 38 | }); 39 | } else { 40 | // its a tv show 41 | var _data = null; 42 | Database.getTVShowByImdb(movie.imdb_id) 43 | .then(function (data) { 44 | data.type = 'bookmarkedshow'; 45 | data.imdb = data.imdb_id; 46 | // Fallback for old bookmarks without provider in database or marked as Eztv 47 | if (typeof (data.provider) === 'undefined' || data.provider === 'Eztv') { 48 | data.provider = 'TVApi'; 49 | } 50 | // This is an old boxart, fetch the latest boxart 51 | if (/slurm.trakt.us/.test(data.images.poster)) { 52 | // Keep reference to old data in case of error 53 | _data = data; 54 | var provider = App.Providers.get(data.provider); 55 | return provider.detail(data.imdb_id, data); 56 | } else { 57 | data.image = data.images.poster; 58 | deferred.resolve(data); 59 | return null; 60 | } 61 | }, function (err) { 62 | deferred.reject(err); 63 | }).then(function (data) { 64 | if (data) { 65 | // Cache new show and return 66 | Database.updateTVShow(data); 67 | data.type = 'bookmarkedshow'; 68 | data.imdb = data.imdb_id; 69 | data.image = data.images.poster; 70 | deferred.resolve(data); 71 | } 72 | }, function (err) { 73 | // Show no longer exists on provider 74 | // Scrub bookmark and TV show 75 | // But return previous data one last time 76 | // So error to erase database doesn't show 77 | Database.deleteBookmark(_data.imdb_id); 78 | Database.deleteTVShow(_data.imdb_id); 79 | deferred.resolve(_data); 80 | }); 81 | } 82 | 83 | movieList.push(deferred.promise); 84 | }); 85 | 86 | return Q.all(movieList); 87 | }; 88 | 89 | Favorites.prototype.extractIds = function (items) { 90 | return _.pluck(items, 'imdb_id'); 91 | }; 92 | 93 | Favorites.prototype.fetch = function (filters) { 94 | return queryTorrents(filters) 95 | .then(formatForPopcorn); 96 | }; 97 | 98 | App.Providers.Favorites = Favorites; 99 | 100 | })(window.App); 101 | -------------------------------------------------------------------------------- /src/app/lib/providers/generic.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var memoize = require('memoizee'); 4 | var cache = {}; 5 | 6 | var Provider = function () { 7 | var memopts = { 8 | maxAge: 10 * 60 * 1000, 9 | /* 10 minutes */ 10 | preFetch: 0.5, 11 | /* recache every 5 minutes */ 12 | primitive: true 13 | }; 14 | 15 | this.memfetch = memoize(this.fetch.bind(this), memopts); 16 | this.fetch = this._fetch.bind(this); 17 | 18 | this.detail = memoize(this.detail.bind(this), _.extend(memopts, { 19 | async: true 20 | })); 21 | }; 22 | 23 | Provider.prototype._fetch = function (filters) { 24 | filters.toString = this.toString; 25 | var promise = this.memfetch(filters), 26 | _this = this; 27 | promise.catch(function (error) { 28 | // Delete the cached result if we get an error so retry will work 29 | _this.memfetch.delete(filters); 30 | }); 31 | return promise; 32 | }; 33 | 34 | Provider.prototype.toString = function (arg) { 35 | return JSON.stringify(this); 36 | }; 37 | 38 | function getProvider(name) { 39 | 40 | if (!name) { 41 | /* XXX(xaiki): this is for debug purposes, will it bite us later ? */ 42 | win.error('dumping provider cache'); 43 | return cache; 44 | } 45 | 46 | if (cache[name]) { 47 | win.info('Returning cached provider', name); 48 | return cache[name]; 49 | } 50 | 51 | var provider = App.Providers[name]; 52 | if (!provider) { 53 | win.error('couldn\'t find provider', name); 54 | return null; 55 | } 56 | 57 | win.info('Spawning new provider', name); 58 | cache[name] = new provider(); 59 | //HACK(xaiki): set the provider name in the returned object. 60 | cache[name].name = name; 61 | return cache[name]; 62 | } 63 | 64 | function delProvider(name) { 65 | if (cache[name]) { 66 | win.info('Delete provider cache', name); 67 | return delete cache[name]; 68 | } 69 | } 70 | 71 | App.Providers.get = getProvider; 72 | App.Providers.delete = delProvider; 73 | App.Providers.Generic = Provider; 74 | 75 | })(window.App); 76 | -------------------------------------------------------------------------------- /src/app/lib/providers/opensubtitles.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var openSRT = require('opensubtitles-ce'); 4 | var Q = require('q'); 5 | var userAgent = 'Popcorn Time v1'; 6 | 7 | var OpenSubtitles = function () {}; 8 | OpenSubtitles.prototype.constructor = OpenSubtitles; 9 | 10 | var normalizeLangCodes = function (data) { 11 | if ('pb' in data) { 12 | data['pt-br'] = data['pb']; 13 | delete data['pb']; 14 | } 15 | return data; 16 | }; 17 | 18 | var formatForPopcorn = function (data) { 19 | data = normalizeLangCodes(data); 20 | for (var lang in data) { 21 | data[lang] = data[lang].url; 22 | } 23 | return Common.sanitize(data); 24 | }; 25 | 26 | OpenSubtitles.prototype.fetch = function (queryParams) { 27 | return openSRT.searchEpisode(queryParams, userAgent) 28 | .then(function (data) { 29 | if (typeof data === 'object') { 30 | return formatForPopcorn(data); 31 | } else { 32 | return null; 33 | } 34 | }); 35 | }; 36 | 37 | 38 | App.Providers.OpenSubtitles = OpenSubtitles; 39 | 40 | })(window.App); 41 | -------------------------------------------------------------------------------- /src/app/lib/providers/ysubs.js: -------------------------------------------------------------------------------- 1 | (function (context) { 2 | 'use strict'; 3 | 4 | var _ = require('underscore'); 5 | var request = require('request'); 6 | var Q = require('q'); 7 | 8 | var baseUrl = 'http://api.yifysubtitles.com/subs/'; 9 | var mirrorUrl = 'http://api.ysubs.com/subs/'; 10 | var prefix = 'http://www.yifysubtitles.com'; 11 | 12 | var TTL = 1000 * 60 * 60 * 4; // 4 hours 13 | 14 | var YSubs = function () { 15 | App.Providers.CacheProvider.call(this, 'subtitle', TTL); 16 | }; 17 | 18 | YSubs.prototype = Object.create(App.Providers.CacheProvider.prototype); 19 | YSubs.prototype.constructor = YSubs; 20 | 21 | var querySubtitles = function (imdbIds) { 22 | if (_.isEmpty(imdbIds)) { 23 | return {}; 24 | } 25 | 26 | var url = baseUrl + _.map(imdbIds.sort(), function (id) { 27 | return id; 28 | }).join('-'); 29 | var mirrorurl = mirrorUrl + _.map(imdbIds.sort(), function (id) { 30 | return id; 31 | }).join('-'); 32 | 33 | var deferred = Q.defer(); 34 | 35 | request({ 36 | url: url, 37 | json: true 38 | }, function (error, response, data) { 39 | if (error || response.statusCode >= 400 || !data || !data.success) { 40 | request({ 41 | url: mirrorurl, 42 | json: true 43 | }, function (error, response, data) { 44 | if (error || response.statusCode >= 400 || !data || !data.success) { 45 | deferred.reject(error); 46 | } else { 47 | deferred.resolve(data); 48 | } 49 | }); 50 | } else { 51 | deferred.resolve(data); 52 | } 53 | }); 54 | 55 | return deferred.promise; 56 | }; 57 | 58 | var formatForPopcorn = function (data) { 59 | var allSubs = {}; 60 | // Iterate each movie 61 | _.each(data.subs, function (langs, imdbId) { 62 | var movieSubs = {}; 63 | // Iterate each language 64 | _.each(langs, function (subs, lang) { 65 | // Pick highest rated 66 | var langCode = languageMapping[lang]; 67 | movieSubs[langCode] = prefix + _.max(subs, function (s) { 68 | return s.rating; 69 | }).url; 70 | }); 71 | 72 | // Remove unsupported subtitles 73 | var filteredSubtitle = App.Localization.filterSubtitle(movieSubs); 74 | 75 | allSubs[imdbId] = filteredSubtitle; 76 | }); 77 | 78 | return Common.sanitize(allSubs); 79 | }; 80 | 81 | YSubs.prototype.query = function (ids) { 82 | return Q.when(querySubtitles(ids)) 83 | .then(formatForPopcorn); 84 | }; 85 | 86 | // Language mapping to match PT langcodes 87 | var languageMapping = { 88 | 'albanian': 'sq', 89 | 'arabic': 'ar', 90 | 'bengali': 'bn', 91 | 'brazilian-portuguese': 'pt-br', 92 | 'bulgarian': 'bg', 93 | 'bosnian': 'bs', 94 | 'chinese': 'zh', 95 | 'croatian': 'hr', 96 | 'czech': 'cs', 97 | 'danish': 'da', 98 | 'dutch': 'nl', 99 | 'english': 'en', 100 | 'estonian': 'et', 101 | 'farsi-persian': 'fa', 102 | 'finnish': 'fi', 103 | 'french': 'fr', 104 | 'german': 'de', 105 | 'greek': 'el', 106 | 'hebrew': 'he', 107 | 'hungarian': 'hu', 108 | 'indonesian': 'id', 109 | 'italian': 'it', 110 | 'japanese': 'ja', 111 | 'korean': 'ko', 112 | 'lithuanian': 'lt', 113 | 'macedonian': 'mk', 114 | 'malay': 'ms', 115 | 'norwegian': 'no', 116 | 'polish': 'pl', 117 | 'portuguese': 'pt', 118 | 'romanian': 'ro', 119 | 'russian': 'ru', 120 | 'serbian': 'sr', 121 | 'slovenian': 'sl', 122 | 'spanish': 'es', 123 | 'swedish': 'sv', 124 | 'thai': 'th', 125 | 'turkish': 'tr', 126 | 'urdu': 'ur', 127 | 'ukrainian': 'uk', 128 | 'vietnamese': 'vi' 129 | }; 130 | 131 | context.App.Providers.YSubs = YSubs; 132 | 133 | })(window); 134 | -------------------------------------------------------------------------------- /src/app/lib/subtitle/server.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | var server; 3 | var httpServer; 4 | var PORT = 9999; 5 | var subtitlePath = {}; 6 | var encoding = 'utf8'; 7 | var http = require('http'); 8 | var path = require('path'); 9 | var url = require('url'); 10 | var iconv = require('iconv-lite'); 11 | var send = require('send'); 12 | 13 | server = http.createServer(function (req, res) { 14 | var uri = url.parse(req.url); 15 | var ext = path.extname(uri.pathname).substr(1); 16 | var sub_path = subtitlePath[ext]; 17 | var sub_dir = path.dirname(sub_path); 18 | var sub_uri = '/' + path.basename(sub_path); 19 | 20 | var headers = function (res, path, stat) { 21 | if (req.headers.origin) { 22 | res.setHeader('Access-Control-Allow-Origin', req.headers.origin); 23 | } 24 | res.setHeader('Content-Type', 'text/' + ext + ';charset=' + encoding); 25 | win.debug('SubtitlesServer: served vtt/srt with encoding: ' + encoding); 26 | }; 27 | 28 | if (ext in subtitlePath) { 29 | send(req, sub_uri, { 30 | root: sub_dir 31 | }) 32 | .on('headers', headers) 33 | .pipe(res); 34 | } else { 35 | res.writeHead(404); 36 | res.end(); 37 | win.error('SubtitlesServer: No subtitle with format %s available.', ext); 38 | } 39 | }); 40 | 41 | function startListening(cb) { 42 | httpServer = server.listen(PORT); 43 | } 44 | 45 | function stopServer(cb) { 46 | httpServer.close(function () { 47 | httpServer = null; 48 | if (cb) { 49 | cb(); 50 | } 51 | }); 52 | } 53 | 54 | var SubtitlesServer = { 55 | start: function (data, cb) { 56 | iconv.extendNodeEncodings(); 57 | 58 | encoding = data.encoding || 'utf8'; 59 | win.debug('SubtitleServer: loading', data.srt || data.vtt); 60 | if (data.vtt) { 61 | fs.readFile(data.vtt, function (err, data) { 62 | if (err) { 63 | win.error('SubtitlesServer: Unable to load VTT file'); 64 | return; 65 | } 66 | }); 67 | subtitlePath['vtt'] = data.vtt; 68 | } 69 | 70 | if (data.srt) { 71 | fs.readFile(data.srt, function (err, data) { 72 | if (err) { 73 | win.error('SubtitlesServer: Unable to load SRT file'); 74 | return; 75 | } 76 | }); 77 | subtitlePath['srt'] = data.srt; 78 | } 79 | 80 | if (!httpServer) { 81 | startListening(cb); 82 | } 83 | }, 84 | 85 | stop: function () { 86 | if (httpServer) { 87 | stopServer(); 88 | } 89 | } 90 | }; 91 | App.Subtitles.Server = SubtitlesServer; 92 | })(window.App); 93 | -------------------------------------------------------------------------------- /src/app/lib/views/about.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var About = Backbone.Marionette.ItemView.extend({ 5 | template: '#about-tpl', 6 | className: 'about', 7 | 8 | ui: { 9 | success_alert: '.success_alert' 10 | }, 11 | 12 | events: { 13 | 'click .close-icon': 'closeAbout', 14 | 'click #changelog': 'showChangelog', 15 | 'click .title-issue': 'reportIssue' 16 | }, 17 | 18 | onShow: function () { 19 | $('.filter-bar').hide(); 20 | $('#header').addClass('header-shadow'); 21 | 22 | Mousetrap.bind(['esc', 'backspace'], function (e) { 23 | App.vent.trigger('about:close'); 24 | }); 25 | $('.links,#changelog').tooltip(); 26 | win.info('Show about'); 27 | $('#movie-detail').hide(); 28 | }, 29 | 30 | onDestroy: function () { 31 | Mousetrap.unbind(['esc', 'backspace']); 32 | $('.filter-bar').show(); 33 | $('#header').removeClass('header-shadow'); 34 | $('#movie-detail').show(); 35 | }, 36 | 37 | closeAbout: function () { 38 | if ($('.changelog-overlay').css('display') === 'block') { 39 | this.closeChangelog(); 40 | } else { 41 | App.vent.trigger('about:close'); 42 | } 43 | }, 44 | 45 | showChangelog: function () { 46 | fs.readFile('./CHANGELOG.md', 'utf-8', function (err, contents) { 47 | if (!err) { 48 | $('.changelog-text').html(contents.replace(/\n/g, '
')); 49 | $('.changelog-overlay').show(); 50 | } else { 51 | gui.Shell.openExternal('https://git.popcorntime.ag/popcorntime-ce/desktop/blob/master/CHANGELOG.md'); 52 | } 53 | }); 54 | }, 55 | 56 | reportIssue: function () { 57 | App.vent.trigger('issue:new'); 58 | }, 59 | 60 | closeChangelog: function () { 61 | $('.changelog-overlay').hide(); 62 | } 63 | 64 | }); 65 | 66 | App.View.About = About; 67 | })(window.App); 68 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/anime_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var AnimeBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.AnimeCollection, 6 | filters: { 7 | genres: App.Config.genres_anime, 8 | sorters: App.Config.sorters_tv, 9 | types: App.Config.types_anime 10 | } 11 | }); 12 | 13 | App.View.AnimeBrowser = AnimeBrowser; 14 | })(window.App); 15 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/favorite_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var FavoriteBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.FavoriteCollection 6 | }); 7 | 8 | App.View.FavoriteBrowser = FavoriteBrowser; 9 | })(window.App); 10 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/generic_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | /** 5 | * Manage browsing: 6 | * * Create filter views 7 | * * Create movie list 8 | * * Fetch new movie collection and pass them to the movie list view 9 | * * Show movie detail 10 | * * Start playing a movie 11 | */ 12 | var PCTBrowser = Backbone.Marionette.LayoutView.extend({ 13 | template: '#browser-tpl', 14 | className: 'main-browser', 15 | regions: { 16 | FilterBar: '.filter-bar-region', 17 | ItemList: '.list-region' 18 | }, 19 | events: { 20 | 'click .retry-button': 'onFilterChange', 21 | 'click .online-search': 'onlineSearch' 22 | }, 23 | 24 | initialize: function () { 25 | this.filter = new App.Model.Filter(this.filters); 26 | 27 | this.collection = new this.collectionModel([], { 28 | filter: this.filter 29 | }); 30 | 31 | this.collection.fetch(); 32 | 33 | this.listenTo(this.filter, 'change', this.onFilterChange); 34 | 35 | }, 36 | 37 | onShow: function () { 38 | this.bar = new App.View.FilterBar({ 39 | model: this.filter 40 | }); 41 | 42 | this.FilterBar.show(this.bar); 43 | 44 | this.ItemList.show(new App.View.List({ 45 | collection: this.collection 46 | })); 47 | 48 | if (!isNaN(startupTime)) { 49 | win.debug('Popcorn Time CE %s startup time: %sms', Settings.version, (window.performance.now() - startupTime).toFixed(3)); // started in database.js; 50 | startupTime = 'none'; 51 | if (Settings.bigPicture) { 52 | var zoom = ScreenResolution.HD ? 2 : 3; 53 | win.zoomLevel = zoom; 54 | } 55 | App.vent.trigger('app:started'); 56 | } 57 | }, 58 | onFilterChange: function () { 59 | 60 | this.collection = new this.collectionModel([], { 61 | filter: this.filter 62 | }); 63 | App.vent.trigger('show:closeDetail'); 64 | this.collection.fetch(); 65 | 66 | this.ItemList.show(new App.View.List({ 67 | collection: this.collection 68 | })); 69 | }, 70 | onlineSearch: function () { 71 | switch (App.currentview) { 72 | case 'movies': 73 | Settings.OnlineSearchCategory = 'Movies'; 74 | break; 75 | case 'shows': 76 | Settings.OnlineSearchCategory = 'TV Series'; 77 | break; 78 | case 'anime': 79 | Settings.OnlineSearchCategory = 'Anime'; 80 | break; 81 | default: 82 | } 83 | 84 | if (!Settings.activateTorrentCollection) { 85 | AdvSettings.set('activateTorrentCollection', true); 86 | $('#torrent_col').css('display', 'block'); 87 | } 88 | 89 | $('#filterbar-torrent-collection').click(); 90 | }, 91 | 92 | focusSearch: function (e) { 93 | this.bar.focusSearch(); 94 | } 95 | }); 96 | 97 | App.View.PCTBrowser = PCTBrowser; 98 | })(window.App); 99 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/movie_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var MovieBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.MovieCollection, 6 | filters: { 7 | genres: App.Config.genres, 8 | sorters: App.Config.sorters 9 | } 10 | }); 11 | 12 | App.View.MovieBrowser = MovieBrowser; 13 | })(window.App); 14 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/show_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var ShowBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.ShowCollection, 6 | filters: { 7 | genres: App.Config.genres_tv, 8 | sorters: App.Config.sorters_tv 9 | } 10 | }); 11 | 12 | App.View.ShowBrowser = ShowBrowser; 13 | })(window.App); 14 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/watchlist_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var WatchlistBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.WatchlistCollection 6 | }); 7 | 8 | App.View.WatchlistBrowser = WatchlistBrowser; 9 | })(window.App); 10 | -------------------------------------------------------------------------------- /src/app/lib/views/disclaimer.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var DisclaimerModal = Backbone.Marionette.ItemView.extend({ 5 | template: '#disclaimer-tpl', 6 | className: 'disclaimer', 7 | 8 | events: { 9 | 'click .btn-accept': 'acceptDisclaimer', 10 | 'click .btn-close': 'closeApp', 11 | }, 12 | 13 | initialize: function () { 14 | Mousetrap.pause(); 15 | win.warn('Show Disclaimer'); 16 | }, 17 | 18 | acceptDisclaimer: function (e) { 19 | e.preventDefault(); 20 | Mousetrap.unpause(); 21 | AdvSettings.set('disclaimerAccepted', 1); 22 | App.vent.trigger('disclaimer:close'); 23 | }, 24 | 25 | closeApp: function (e) { 26 | e.preventDefault(); 27 | gui.App.quit(); 28 | } 29 | 30 | }); 31 | 32 | App.View.DisclaimerModal = DisclaimerModal; 33 | })(window.App); 34 | -------------------------------------------------------------------------------- /src/app/lib/views/help.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var dyks; 4 | 5 | var Help = Backbone.Marionette.ItemView.extend({ 6 | template: '#help-tpl', 7 | className: 'help', 8 | 9 | events: { 10 | 'click .close-icon': 'closeHelp', 11 | 'click #in-app-reporter': 'reportIssue' 12 | }, 13 | 14 | initialize: function () { 15 | dyks = [ 16 | i18n.__('You can paste magnet links anywhere in Popcorn Time with CTRL+V.'), 17 | i18n.__('You can drag & drop a .torrent file into Popcorn Time.'), 18 | i18n.__('The Popcorn Time project started in February 2014 and has already had 150 people that contributed more than 3000 times to it\'s development in August 2014.'), 19 | i18n.__('If a subtitle for a TV series is missing, you can add it on %s. And the same way for a movie, but on %s.', 'opensubtitles.org', 'yifysubtitles.com'), 20 | i18n.__('If you\'re experiencing connection drop issues, try to reduce the DHT Limit in settings.'), 21 | i18n.__('If you search \"1998\", you can see all the movies or TV series that came out that year.'), 22 | i18n.__('You can login to Trakt.tv to save all your watched items, and synchronize them across multiple devices.'), 23 | i18n.__('Clicking on the rating stars will display a number instead.'), 24 | i18n.__('This application is entirely written in HTML5, CSS3 and Javascript.'), 25 | i18n.__('You can find out more about a movie or a TV series? Just click the IMDb icon.'), 26 | i18n.__('Clicking twice on a \"Sort By\" filter reverses the order of the list.') 27 | ]; 28 | }, 29 | 30 | onShow: function () { 31 | $('.search input').blur(); 32 | Mousetrap.bind('esc', function (e) { 33 | App.vent.trigger('help:close'); 34 | }); 35 | var dyk = dyks[_.random(dyks.length - 1)]; 36 | $('.randomized-dyk').html(dyk); 37 | }, 38 | 39 | reportIssue: function () { 40 | App.vent.trigger('issue:new'); 41 | }, 42 | 43 | onDestroy: function () {}, 44 | 45 | closeHelp: function () { 46 | App.vent.trigger('help:close'); 47 | } 48 | 49 | }); 50 | 51 | App.View.Help = Help; 52 | })(window.App); 53 | -------------------------------------------------------------------------------- /src/app/lib/views/init_modal.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var fixer; 4 | var InitModal = Backbone.Marionette.ItemView.extend({ 5 | template: '#initializing-tpl', 6 | className: 'init-container', 7 | 8 | ui: { 9 | initstatus: '.init-status', 10 | initbar: '#initbar-contents', 11 | waitingblock: '#waiting-block' 12 | }, 13 | 14 | events: { 15 | 'click .fixApp': 'fixApp', 16 | }, 17 | 18 | initialize: function () { 19 | win.info('Loading DB'); 20 | }, 21 | 22 | onShow: function () { 23 | var self = this; 24 | 25 | this.ui.initbar.animate({ 26 | width: '25%' 27 | }, 1000, 'swing'); 28 | this.ui.initstatus.text(i18n.__('Status: Checking Database...')); 29 | 30 | fixer = setTimeout(function () { 31 | self.ui.waitingblock.show(); 32 | }, 10000); 33 | }, 34 | 35 | onDestroy: function () { 36 | clearTimeout(fixer); 37 | }, 38 | 39 | fixApp: function (e) { 40 | 41 | e.preventDefault(); 42 | 43 | var cache = new App.Cache('subtitle'); 44 | cache.flushTable() 45 | .then(Database.deleteDatabases) 46 | .then(function () { 47 | App.vent.trigger('restartPopcornTime'); 48 | }); 49 | 50 | }, 51 | 52 | }); 53 | 54 | App.View.InitModal = InitModal; 55 | })(window.App); 56 | -------------------------------------------------------------------------------- /src/app/lib/views/keyboard.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Keyboard = Backbone.Marionette.ItemView.extend({ 5 | template: '#keyboard-tpl', 6 | className: 'keyboard', 7 | 8 | events: { 9 | 'click .close-icon': 'closeKeyboard', 10 | }, 11 | 12 | 13 | onShow: function () { 14 | $('.search input').blur(); 15 | Mousetrap.bind('esc', function (e) { 16 | App.vent.trigger('keyboard:close'); 17 | }); 18 | }, 19 | 20 | onDestroy: function () {}, 21 | 22 | closeKeyboard: function () { 23 | App.vent.trigger('keyboard:close'); 24 | }, 25 | 26 | }); 27 | 28 | App.View.Keyboard = Keyboard; 29 | })(window.App); 30 | -------------------------------------------------------------------------------- /src/app/lib/views/notification.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | App.View.Notification = Backbone.Marionette.ItemView.extend({ 5 | template: '#notification-tpl', 6 | className: 'notificationWrapper', 7 | 8 | ui: { 9 | notification: '.notification' 10 | }, 11 | 12 | events: { 13 | 'click .close': 'closeNotification' 14 | }, 15 | 16 | initialize: function () { 17 | if (!(this.model instanceof App.Model.Notification)) { 18 | throw new Error('When instantiating a Notification, you must pass an App.Model.Notification.'); 19 | } 20 | }, 21 | 22 | onShow: function () { 23 | var _this = this; 24 | // Uniquely identify each button and wire up the callback. 25 | this.model.get('buttons').forEach(function (button, index) { 26 | _this.$('.action-button-' + index).on('click', button.action); 27 | }); 28 | }, 29 | 30 | closeNotification: function () { 31 | App.vent.trigger('notification:close'); 32 | } 33 | 34 | }); 35 | })(window.App); 36 | -------------------------------------------------------------------------------- /src/app/lib/views/register.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var RegisterModal = Backbone.Marionette.ItemView.extend({ 5 | template: '#register-tpl', 6 | className: 'disclaimer', 7 | 8 | events: { 9 | 'click .btn-accept': 'setRegister', 10 | 'click .btn-close': 'closeRegister', 11 | 'change select,input': 'saveSetting', 12 | }, 13 | 14 | initialize: function () { 15 | Mousetrap.pause(); 16 | win.warn('Set Register'); 17 | }, 18 | 19 | setRegister: function (e) { 20 | e.preventDefault(); 21 | Mousetrap.unpause(); 22 | App.vent.trigger('register:close'); 23 | //AdvSettings.set('chosenPlayer', 'html5'); 24 | this.regTorrent(); 25 | }, 26 | 27 | closeRegister: function (e) { 28 | e.preventDefault(); 29 | App.vent.trigger('register:close'); 30 | //AdvSettings.set('chosenPlayer', 'html5'); 31 | }, 32 | 33 | saveSetting: function (e) { 34 | var value = false, 35 | field = $(e.currentTarget); 36 | 37 | switch (field.attr('name')) { 38 | case 'rememberRegister': 39 | //case 'chosenPlayer': 40 | //AdvSettings.set('chosenPlayer', 'html5'); 41 | default: 42 | win.warn('Setting not defined: ' + field.attr('name')); 43 | } 44 | win.info('Setting changed: ' + field.attr('name') + ' - ' + value); 45 | 46 | // update active session 47 | App.settings[field.attr('name')] = value; 48 | 49 | // save to db 50 | App.db.writeSetting({ 51 | key: field.attr('name'), 52 | value: value 53 | }); 54 | 55 | that.syncSetting(field.attr('name'), value); 56 | }, 57 | 58 | syncSetting: function (setting, value) { 59 | switch (setting) { 60 | case 'rememberRegister': 61 | break; 62 | default: 63 | } 64 | }, 65 | 66 | writeDesktopFile: function (cb) { 67 | var pctPath = process.execPath.substr(0,process.execPath.lastIndexOf("/")+1); 68 | var Exec = pctPath+'Popcorn-Time'; //process.execPath 69 | fs.writeFile(gui.App.dataPath+'/popcorntime.desktop', '[Desktop Entry]\nVersion=2.0\nName=Popcorn Time\nComment=Popcorn Time downloads and streams torrents instantly, directly to your browser! Just click on the torrent or magnet link and start downloading and playing it easily and in no time.\nExec='+Exec+' %U\nPath='+pctPath+'\nIcon='+pctPath+'popcorntime.png\nTerminal=false\nType=Application\nMimeType=application/x-bittorrent;x-scheme-handler/magnet;video/avi;video/msvideo;video/x-msvideo;video/mp4;video/x-matroska;video/mpeg;\n', cb); 70 | }, 71 | 72 | regTorrent: function () { 73 | if (process.platform == 'linux') { 74 | this.writeDesktopFile(function(err) { 75 | if (err) throw err; 76 | var desktopFile = gui.App.dataPath+'/popcorntime.desktop'; 77 | var tempMime = 'application/x-bittorrent'; 78 | require('child_process').exec('gnome-terminal -x bash -c "echo \'Setting Popcorn Time as default player requires Admin Rights\'; echo; sudo echo; sudo echo \'Authentication Successful\'; sudo echo; sudo mv -f '+desktopFile+' /usr/share/applications; sudo xdg-mime default popcorntime.desktop '+tempMime+'; sudo gvfs-mime --set '+tempMime+' popcorntime.desktop; echo; echo \'Success! Press any key to close ...\'; read" & disown'); 79 | AdvSettings.set('registerTorrents', true); 80 | }); 81 | } else if (process.platform == 'darwin') { 82 | var pctPath = process.execPath.substr(0,process.execPath.lastIndexOf("/")+1)+"../../../../Resources/app.nw/"; 83 | require('child_process').exec('"'+pctPath+'src/duti/duti" -s media.popcorntime-ce.player .torrent viewer'); 84 | alert("Success!"); 85 | AdvSettings.set('registerTorrents', true); 86 | } else { 87 | fs.writeFile(gui.App.dataPath+'\\register-torrent.reg', 'REGEDIT4\r\n[HKEY_CURRENT_USER\\Software\\Classes\\popcorntime.player\\DefaultIcon]\r\n@="'+process.execPath.split("\\").join("\\\\")+'"\r\n[HKEY_CURRENT_USER\\Software\\Classes\\popcorntime.player\\shell\\open\\command]\r\n@="\\"'+process.execPath.split("\\").join("\\\\")+'\\" \\"%1\\""\r\n[HKEY_CURRENT_USER\\Software\\Classes\\.torrent]\r\n@="popcorntime.player"\r\n"Content Type"="application/x-bittorrent"', function (err) { 88 | if (err) throw err; 89 | gui.Shell.openExternal(gui.App.dataPath+'\\register-torrent.reg'); 90 | AdvSettings.set('registerTorrents', true); 91 | }); 92 | } 93 | } 94 | 95 | }); 96 | 97 | App.View.RegisterModal = RegisterModal; 98 | })(window.App); 99 | -------------------------------------------------------------------------------- /src/app/lib/views/title_bar.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | 3 | (function (App) { 4 | 'use strict'; 5 | 6 | // use of darwin string instead of mac (mac os x returns darwin as platform) 7 | var ButtonOrder = { 8 | 'win32': ['min', 'max', 'close'], 9 | 'darwin': ['close', 'min', 'max'], 10 | 'linux': ['min', 'max', 'close'] 11 | }; 12 | 13 | var TitleBar = Backbone.Marionette.ItemView.extend({ 14 | template: '#header-tpl', 15 | 16 | events: { 17 | 'click .btn-os.os-max': 'maximize', 18 | 'click .btn-os.os-min': 'minimize', 19 | 'click .btn-os.os-close': 'closeWindow', 20 | 'click .btn-os.fullscreen': 'toggleFullscreen' 21 | }, 22 | 23 | initialize: function () { 24 | this.nativeWindow = require('nw.gui').Window.get(); 25 | }, 26 | 27 | templateHelpers: { 28 | getButtons: function () { 29 | return ButtonOrder[App.Config.platform]; 30 | }, 31 | 32 | fsTooltipPos: function () { 33 | return App.Config.platform === 'darwin' ? 'left' : 'right'; 34 | }, 35 | 36 | events: function () { 37 | var date = new Date(); 38 | var today = ('0' + (date.getMonth() +  1)).slice(-2) + ('0' + (date.getDate())).slice(-2); 39 | if (today === '1231' || today === '0101') { 40 | return 'newyear'; 41 | } else if (today >= '1218' || today <= '0103') { 42 | return 'xmas'; 43 | } else if (today >= '1027' && today <= '1103') { 44 | return 'halloween'; 45 | } else if (today === '0220') { 46 | return 'pt_anniv'; 47 | } else if (today === '0214') { 48 | return 'stvalentine'; 49 | } else if (today === '0317') { 50 | return 'stpatrick'; 51 | } else if (today === '0401') { 52 | return 'aprilsfool'; 53 | } 54 | } 55 | }, 56 | 57 | maximize: function () { 58 | if (this.nativeWindow.isFullscreen) { 59 | this.nativeWindow.toggleFullscreen(); 60 | } else { 61 | if (window.screen.availHeight <= this.nativeWindow.height) { 62 | this.nativeWindow.unmaximize(); 63 | if (process.platform === 'win32') { 64 | $('.os-max').removeClass('os-is-max'); 65 | } 66 | } else { 67 | this.nativeWindow.maximize(); 68 | if (process.platform === 'win32') { 69 | $('.os-max').addClass('os-is-max'); 70 | } 71 | } 72 | } 73 | }, 74 | 75 | minimize: function () { 76 | var that = this.nativeWindow; 77 | if (AdvSettings.get('minimizeToTray')) { 78 | minimizeToTray(); 79 | } else { 80 | that.minimize(); 81 | } 82 | }, 83 | 84 | closeWindow: function () { 85 | this.nativeWindow.close(); 86 | }, 87 | 88 | toggleFullscreen: function () { 89 | win.toggleFullscreen(); 90 | if (this.nativeWindow.isFullscreen) { 91 | $('.os-min, .os-max').css('display', 'none'); 92 | } else { 93 | $('.os-min, .os-max').css('display', 'block'); 94 | } 95 | this.$el.find('.btn-os.fullscreen').toggleClass('active'); 96 | }, 97 | 98 | onShow: function () { 99 | $('.tooltipped').tooltip({ 100 | delay: { 101 | 'show': 800, 102 | 'hide': 100 103 | } 104 | 105 | }); 106 | 107 | } 108 | 109 | }); 110 | 111 | App.View.TitleBar = TitleBar; 112 | })(window.App); 113 | -------------------------------------------------------------------------------- /src/app/lib/views/vpn_connect.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var timerEvent; 4 | var currentIp; 5 | var VpnConnect = Backbone.Marionette.ItemView.extend({ 6 | template: '#initializing-tpl', 7 | className: 'init-container', 8 | 9 | ui: { 10 | initstatus: '.init-status', 11 | inittext: '.init-text', 12 | initbar: '#initbar-contents', 13 | cancelblock: '#cancel-block' 14 | }, 15 | 16 | events: { 17 | 'click .cancel': 'cancelConnexion', 18 | }, 19 | 20 | initialize: function () { 21 | win.info('Connecting VPN'); 22 | }, 23 | 24 | onDestroy: function () { 25 | clearInterval(timerEvent); 26 | }, 27 | 28 | onShow: function () { 29 | var self = this; 30 | 31 | this.ui.initbar.animate({ 32 | width: '25%' 33 | }, 1000, 'swing'); 34 | 35 | this.ui.inittext.text(i18n.__('Please, allow ~ 1 minute')); 36 | this.ui.initstatus.text(i18n.__('Status: Connecting to VPN...')); 37 | this.ui.cancelblock.show(); 38 | 39 | App.VPN.getIp() 40 | .then(function (currentIp) { 41 | // we trigger our connexion 42 | App.VpnConnexion = App.VPN.connect().then(function () { 43 | 44 | // we'll monitor our ip change every 15 sec 45 | timerEvent = setInterval(function () { 46 | self.monitorIp(currentIp); 47 | }, 5000); 48 | }); 49 | }); 50 | 51 | }, 52 | 53 | monitorIp: function (currentIp) { 54 | this.ui.initstatus.text(i18n.__('Status: Monitoring connection') + ' - ' + currentIp); 55 | var self = this; 56 | App.VPN.getIp() 57 | .then(function (newIp) { 58 | 59 | win.info('VPN monitoring - IP:', currentIp); 60 | win.info('VPN monitoring - IP:', newIp); 61 | 62 | self.ui.initstatus.text(i18n.__('Status: Monitoring connection') + ' - ' + newIp); 63 | 64 | // we have a new ip... 65 | if (newIp !== currentIp) { 66 | 67 | self.ui.initstatus.text(i18n.__('Status: Connected')); 68 | setTimeout(function () { 69 | self.destroy(); 70 | App.vent.trigger('movies:list'); 71 | }, 3000); 72 | 73 | } 74 | 75 | }); 76 | 77 | }, 78 | 79 | cancelConnexion: function (e) { 80 | e.preventDefault(); 81 | var self = this; 82 | // we kill all process 83 | App.VPN.disconnect() 84 | .then(function () { 85 | App.vent.trigger('movies:list'); 86 | self.destroy(); 87 | }) 88 | .catch(function () { 89 | App.vent.trigger('movies:list'); 90 | self.destroy(); 91 | }); 92 | } 93 | 94 | 95 | }); 96 | 97 | App.View.VpnConnect = VpnConnect; 98 | })(window.App); 99 | -------------------------------------------------------------------------------- /src/app/templates/about.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | id='changelog'><%= App.settings.version %> "<%= App.settings.releaseName %>" Beta 10 | <% if(App.git) { %> 11 | - <%= App.git.branch %> (<%= App.git.commit.slice(0,8) %>) 12 | <% } %> 13 |
14 | 15 | 18 | 19 |
20 |
21 | <%= i18n.__("Popcorn Time! is the result of many developers and designers putting a bunch of APIs together to make the experience of watching torrent movies as simple as possible.") %>
22 | <%= i18n.__("We are an open source project. We are from all over the world. We love our movies. And boy, do we love popcorn.") %> 23 |
24 |
25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | <%= i18n.__("Made with") %> <%= i18n.__("by a bunch of geeks from All Around The World") %> 40 |
41 | 42 |
43 |
44 |
<%=i18n.__("Changelog")%>
45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /src/app/templates/browser/browser.tpl: -------------------------------------------------------------------------------- 1 |
2 |
-------------------------------------------------------------------------------- /src/app/templates/browser/item.tpl: -------------------------------------------------------------------------------- 1 | <% 2 | if (typeof image === 'undefined') { var image = images.poster; } 3 | if (typeof rating === 'object') { var rating = rating.percentage /10; } 4 | %> 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 | <% if(typeof rating !== 'undefined'){ 13 | var p_rating = Math.round(rating) / 2; %> 14 |
style="display: block;"<% } %> > 15 |
16 | <% for (var i = 1; i <= Math.floor(p_rating); i++) { %> 17 | 18 | <% }; %> 19 | <% if (p_rating % 1 > 0) { %> 20 | 21 | 22 | 23 | 24 | <% }; %> 25 | <% for (var i = Math.ceil(p_rating); i < 5; i++) { %> 26 | 27 | <% }; %> 28 |
29 |
<%= rating %>/10
30 |
31 | <%} %> 32 |
33 |
34 | 35 |

<%= title %>

36 |

37 | 38 | <% if (typeof item_data !== 'undefined' || typeof num_seasons !== 'undefined') {%> 39 | <%}else if (typeof torrents !== 'undefined') { %> 40 | <% if (google_video !== false) {%> 41 | Google Cloud 42 | <%} %> 43 | <%} %> 44 | 45 | <% if (typeof year !== 'undefined') {%> 46 | <%= year %> 47 | <%} %> 48 |

49 | 50 | 51 | <% if (typeof item_data !== 'undefined') {%> 52 |

53 | <%= i18n.__(item_data) %> 54 |

55 | <% } else if(typeof num_seasons !== 'undefined'){%> 56 |

57 | <%= num_seasons %> <%= num_seasons == 1 ? i18n.__("Season") : i18n.__("Seasons") %> 58 |

59 | <%}else if (typeof torrents !== 'undefined') { %> 60 |

style="display: block;" <% } %> > 61 | <% q720 = torrents["720p"] !== undefined; q1080 = torrents["1080p"] !== undefined; 62 | if (q720 && q1080) { %> 63 | 720p/1080p 64 | <% } else if (q1080) { %> 65 | 1080p 66 | <% } else if (q720) { %> 67 | 720p 68 | <% } else { %> 69 | HDRip 70 | <% } %> 71 |

72 | <%} %> 73 | -------------------------------------------------------------------------------- /src/app/templates/browser/list.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
-------------------------------------------------------------------------------- /src/app/templates/changelog.tpl: -------------------------------------------------------------------------------- 1 |
2 |

<%= title %>

3 |

<%= description %>

4 | -------------------------------------------------------------------------------- /src/app/templates/disclaimer.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |

<%= i18n.__("Terms of Service") %>

7 |
8 |

Your Acceptance

9 | 10 |

By using the 'POPCORN TIME' app you signify your agreement to (1) these terms and conditions (the 'Terms of Service').

11 | 12 |

Privacy Policy.

13 | 14 |

You understand that by using 'POPCORN TIME' you may encounter material that you may deem to be offensive, indecent, or objectionable, and that such content may or may not be identified as having explicit material. 'POPCORN TIME' will have no liability to you for such material – you agree that your use of 'POPCORN TIME' is at your sole risk.

15 | 16 |

DISCLAIMERS

17 | 18 |

YOU EXPRESSLY AGREE THAT YOUR USE OF 'POPCORN TIME' IS AT YOUR SOLE RISK. 'POPCORN TIME' AND ALL PRODUCTS ARE PROVIDED TO YOU “AS IS” WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. 'POPCORN TIME' MAKES ABSOLUTELY NO WARRANTIES WHATSOEVER, EXPRESS OR IMPLIED. TO THE FULLEST EXTENT POSSIBLE UNDER APPLICABLE LAWS, YIFY DISCLAIMS ALL WARRANTIES, EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR OTHER VIOLATIONS OF RIGHTS.

19 | 20 |

LIMITATION OF LIABILITY

21 | 22 |

'POPCORN TIME' IS NOT RESPONSIBLE FOR ANY PROBLEMS OR TECHNICAL MALFUNCTION OF ANY WEBSITE, NETWORK, COMPUTER SYSTEMS, SERVERS, PROVIDERS, COMPUTER EQUIPMENT, OR SOFTWARE, OR FOR ANY FAILURE DUE TO TECHNICAL PROBLEMS OR TRAFFIC CONGESTION ON THE INTERNET OR 'POPCORN TIME' OR COMBINATION THEREOF, INCLUDING ANY INJURY OR DAMAGE TO USERS OR TO ANY COMPUTER OR OTHER DEVICE ON OR THROUGH WHICH 'POPCORN TIME' IS PROVIDED. UNDER NO CIRCUMSTANCES WILL 'POPCORN TIME' BE LIABLE FOR ANY LOSS OR DAMAGE, INCLUDING PERSONAL INJURY OR DEATH, RESULTING FROM YOUR USE OF 'POPCORN TIME'.

23 | 24 |

SOURCE MATERIAL

25 | 26 |

ALL MOVIES ARE NOT HOSTED ON ANY SERVER AND ARE STREAMED USING THE P2P BIT TORRENT PROTOCOL. ALL MOVIES ARE PULLED IN FROM OPEN PUBLIC APIS. BY WATCHING A MOVIE WITH THIS APPLICATION YOU MIGHT BE COMMITTING COPYRIGHT VIOLATIONS DEPENDING ON YOUR COUNTRY´S LAWS. WE DO NOT TAKE ANY RESPONSIBILITIES.

27 | 28 |

Ability to Accept Terms of Service

29 | 30 |

By using 'POPCORN TIME' or accessing this site you affirm that you are either more than 18 years of age, or an emancipated minor, or possess legal parental or guardian consent, and are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations, and warranties set forth in these Terms of Service, and to abide by and comply with these Terms of Service. In any case, you affirm that you are over the age of 13, as the Service is not intended for children under 13. If you are under 13 years of age, then please do not use the Service. There are lots of other great web sites for you. Talk to your parents about what sites are appropriate for you.

31 |
32 | <%= i18n.__("I Accept") %> <%= i18n.__("Leave") %> 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/app/templates/file-selector.tpl: -------------------------------------------------------------------------------- 1 | <% function formatBytes(bytes,decimals) { 2 | if(bytes == 0) return '0 Byte'; 3 | var k = 1000; 4 | var dm = decimals + 1 || 3; 5 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 6 | var i = Math.floor(Math.log(bytes) / Math.log(k)); 7 | return (bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i]; 8 | } %> 9 | 10 |
11 |
12 | 13 |
<%=i18n.__('Please select a file to play')%>
14 |
15 | 23 |
24 | 25 |
26 | 27 | <% if (Settings.activateTorrentCollection) { %> 28 |
29 | <% } %> 30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /src/app/templates/header.tpl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 |

11 | Popcorn Time Community 12 |
13 |

14 | -------------------------------------------------------------------------------- /src/app/templates/initializing.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | <%= i18n.__("Made with") %> <%= i18n.__("by a bunch of geeks from All Around The World") %> 5 |
6 |
7 |
<%= i18n.__("Initializing Popcorn Time CE. Please Wait...") %>
8 |
9 |
10 |
11 |
12 | 13 | 16 | 17 |
18 | 19 |

20 | <%= i18n.__("Loading stuck ? Click here !") %> 21 |

22 | -------------------------------------------------------------------------------- /src/app/templates/issue.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

<%= i18n.__("Report an issue") %>

6 |
7 | 8 |
9 | 10 |
11 |
12 | <%= i18n.__("Log in into your GitLab account") %> 13 | <%= i18n.__("Create Account") %> 14 |
15 | 16 |
17 | 18 |
19 | 20 | 21 | 26 |
27 | 28 | 41 | 42 |
43 |
44 |

<%= i18n.__("Step 2: Report a new issue") %>

45 | <%= i18n.__("Note: please don't use this form to contact us. It is limited to bug reports only.") %>
46 | <%= i18n.__("Warning: Always use English when contacting us, or we might not understand you.") %> 47 |
48 | 49 |

<%= i18n.__("Title") %>

50 | 51 | 52 |

<%= i18n.__("Description") %>

53 |

<%= i18n.__("200 characters minimum") %> (0/200)

54 | 55 | 56 |
<%= i18n.__("Submit") %>
57 |
58 | 59 |
60 |
61 | <%= i18n.__("Step 3: Thank you !") %> 62 |
63 | 64 |

<%= i18n.__("Success") %>

65 |

<%= i18n.__("Your issue has been reported.") %>

66 |

67 |
68 |
69 |
70 |
71 | -------------------------------------------------------------------------------- /src/app/templates/loading.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 | 9 |
<%= i18n.__(state) %>
10 | 11 | 29 | 35 | 38 |
39 |
<%= i18n.__("Cancel") %>
40 |
41 |
42 |
43 | <%= i18n.__('Your disk is almost full.') %>
44 | <%= i18n.__('You need to make more space available on your disk by deleting files.') %> 45 |
46 |
47 | -------------------------------------------------------------------------------- /src/app/templates/main-window.tpl: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
-------------------------------------------------------------------------------- /src/app/templates/movie-error.tpl: -------------------------------------------------------------------------------- 1 |
2 |

<%= error %>

3 | 6 | 9 |
-------------------------------------------------------------------------------- /src/app/templates/notification.tpl: -------------------------------------------------------------------------------- 1 |