├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── not-casting-working-after-fresh-install-update.md
│ ├── particular-video-file-not-working.md
│ ├── problems-with-video-transcoding.md
│ └── translation-request.md
├── .gitignore
├── COPYING
├── Makefile
├── README.md
├── _config.yml
├── addons.js
├── appIcon
├── cast-to-tv.svg
└── prefs.png
├── compat.js
├── crowdin.yml
├── extension.js
├── file-chooser.js
├── helper.js
├── metadata.json
├── nautilus
└── nautilus-cast-to-tv.py
├── node_scripts
├── addons-importer.js
├── bridge.js
├── chromecast.js
├── encode.js
├── events.js
├── gettext.js
├── gnome.js
├── messages.js
├── notify.js
├── playercast.js
├── remote-controller.js
├── remove.js
├── sender.js
├── server-socket.js
├── server.js
├── utils
│ ├── install.js
│ ├── local-ip.js
│ ├── scanner.js
│ └── vttextract.js
└── web-creator.js
├── package-lock.json
├── package.json
├── playlist.js
├── po
├── cast-to-tv-links-addon
│ ├── cast-to-tv-links-addon.pot
│ ├── de.po
│ ├── en_GB.po
│ ├── es.po
│ ├── fo.po
│ ├── fr.po
│ ├── it.po
│ ├── nl.po
│ ├── pl.po
│ ├── ro.po
│ └── tr.po
└── cast-to-tv
│ ├── cast-to-tv.pot
│ ├── de.po
│ ├── en_GB.po
│ ├── es.po
│ ├── fo.po
│ ├── fr.po
│ ├── it.po
│ ├── nl.po
│ ├── pl.po
│ ├── ro.po
│ └── tr.po
├── prefs.js
├── prefs_shared.js
├── schemas
├── gschemas.compiled
└── org.gnome.shell.extensions.cast-to-tv.gschema.xml
├── server-monitor.js
├── shared.js
├── soup.js
├── temp.js
├── webplayer
├── images
│ ├── cover.png
│ ├── icon.png
│ └── play.png
├── loading.html
├── loading.js
├── message.html
├── message.js
├── picture.html
├── preload.js
├── webplayer-defaults.js
├── webplayer-encode.js
├── webplayer-init.js
├── webplayer.css
├── webplayer_direct.html
├── webplayer_encode.html
└── websocket.js
└── widget.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: https://www.paypal.me/Rafostar
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/not-casting-working-after-fresh-install-update.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Not casting/working after fresh install/update
3 | about: If you just installed or updated this extension follow these steps.
4 | title: Casting not working
5 | labels: ''
6 | assignees: Rafostar
7 |
8 | ---
9 |
10 | 1. After a fresh install of this extension and it's node modules from extension prefs, usually a reboot fixes this issue (and by reboot I mean full system reboot - not just restarting gnome-shell).
11 |
12 | 2. Make sure you have receiver type in extension prefs set correctly to desired device type (e.g. "Chromecast"). If it wasn't try casting again.
13 |
14 | 3. If extension seems to be working (you have a top panel drop down menu), but still experiencing problems with casting, lets take a look at what happens during file cast. Turn off this extension node service without disabling whole extension (from gnome top bar menu). TV indicator icon should disappear from top bar. Then do the following in terminal:
15 |
16 | ```
17 | cd ~/.local/share/gnome-shell/extensions/cast-to-tv@rafostar.github.com/node_scripts
18 | DEBUG=bridge,chromecast* node server
19 | ```
20 |
21 | After running, extension menu and indicator should reappear.
22 | Try reproducing your problem and send me output. Stop the terminal process with Ctrl+c buttons.
23 |
24 | Output:
25 | ```
26 | COPY_HERE
27 | ```
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/particular-video-file-not-working.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Particular video file not working
3 | about: Problems with a certain video file, while others work fine.
4 | title: Video file not working
5 | labels: ''
6 | assignees: Rafostar
7 |
8 | ---
9 |
10 | Before submitting this issue, please try casting with transcoding audio, video and video+audio (you can find selection box on bottom-left of select video window or in Nautilus right click menu), if neither of them solves your problem fill and submit this new issue.
11 |
12 | Please send me the output of (replace `VIDEO.mkv` with your actual file name):
13 | ```
14 | ffprobe -show_streams -show_format -print_format json "VIDEO.mkv"
15 | ```
16 |
17 | Output:
18 | ```
19 | COPY_HERE
20 | ```
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/problems-with-video-transcoding.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Problems with video transcoding
3 | about: Video transcoding (either software or hardware accelerated) not working.
4 | title: Transcoding not working
5 | labels: ''
6 | assignees: Rafostar
7 |
8 | ---
9 |
10 | CPU: e.g. i9-9900k
11 | GPU: e.g. Intel UHD Graphics 630
12 |
13 | Turn off this extension node service without disabling whole extension (from gnome top bar menu). TV indicator icon should disappear from top bar. Then do the following in terminal:
14 | ```
15 | cd ~/.local/share/gnome-shell/extensions/cast-to-tv@rafostar.github.com/node_scripts
16 | DEBUG=bridge,ffmpeg node server
17 | ```
18 | After running, extension menu and indicator should reappear.
19 | Try reproducing your problem and send me output. Stop the terminal process with Ctrl+c buttons.
20 |
21 | Output:
22 | ```
23 | COPY_HERE
24 | ```
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/translation-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Translation request
3 | about: Request adding new language
4 | title: Translation request
5 | labels: translation
6 | assignees: Rafostar
7 |
8 | ---
9 |
10 |
11 |
12 | Cast to TV - Crowdin
13 |
14 |
15 |
16 | If you want to become a translator for missing language, fill the below template (I must add new language to Crowdin first).
17 |
18 | Using Crowdin for updating translations is preferred, otherwise please use an editing app like `poedit` for generating files and not do it in plain text editor.
19 |
20 | ---
21 | I am willing to translate the extension into language: _____
22 |
23 | Select method:
24 | - [ ] I will use Crowdin (preferred method). Please add my language to the project.
25 | - [ ] I will translate on my own using translation software (e.g. `poedit`).
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | locale/
3 | locale_addons/
4 | po/cast-to-tv*/*.po~
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Basic Makefile
2 |
3 | EXTNAME = gnome-shell-extension-cast-to-tv
4 | UUID = cast-to-tv@rafostar.github.com
5 | GETTEXT = cast-to-tv
6 | PACKAGE = "Cast to TV"
7 | TOLOCALIZE = extension.js widget.js playlist.js file-chooser.js prefs.js node_scripts/messages.js
8 | MSGSRC = $(wildcard ./po/cast-to-tv/*.po)
9 | POTFILE = ./po/cast-to-tv/cast-to-tv.pot
10 | ZIPFILES = *.js *.json node_scripts webplayer schemas locale appIcon nautilus COPYING README.md
11 | INSTALLPATH = ~/.local/share/gnome-shell/extensions
12 |
13 | # Add-ons translations #
14 | POFOLDERS = $(wildcard ./po/cast-to-tv-*-addon)
15 |
16 | # Compile schemas #
17 | glib-schemas:
18 | glib-compile-schemas ./schemas/
19 |
20 | # Create/update potfile #
21 | potfile:
22 | mkdir -p ./po
23 | xgettext -o $(POTFILE) --language=JavaScript --add-comments=TRANSLATORS: --package-name $(PACKAGE) $(TOLOCALIZE)
24 |
25 | # Update '.po' from 'potfile' #
26 | mergepo:
27 | for i in $(MSGSRC); do \
28 | msgmerge -U $$i $(POTFILE); \
29 | done;
30 |
31 | # Compile all .mo files #
32 | compilemo: compilemo-base compilemo-addons
33 |
34 | # Compile extension .mo files #
35 | compilemo-base:
36 | for i in $(MSGSRC); do \
37 | msggrep $$i -T -e "" | grep -q "" \
38 | && mkdir -p ./locale/`basename $$i .po`/LC_MESSAGES \
39 | && msgfmt -o ./locale/`basename $$i .po`/LC_MESSAGES/$(GETTEXT).mo $$i; \
40 | done;
41 |
42 | # Compile addons .mo files #
43 | compilemo-addons:
44 | for i in $(POFOLDERS); do \
45 | for j in $$i/*.po; do \
46 | msggrep $$j -T -e "" | grep -q "" \
47 | && mkdir -p ./locale_addons/`basename $$i`/`basename $$j .po`/LC_MESSAGES \
48 | && msgfmt -o ./locale_addons/`basename $$i`/`basename $$j .po`/LC_MESSAGES/`basename $$i`.mo $$j; \
49 | done; \
50 | done;
51 |
52 | # Create release zip #
53 | zip-file: _build
54 | zip -qr $(UUID).zip $(ZIPFILES)
55 |
56 | # Update metadata #
57 | metadata:
58 | ifeq ($(CUSTOMPATH),)
59 | ifeq ($(PKGDIR),)
60 | LASTCOMMIT=$(shell git rev-parse --short HEAD); \
61 | grep -q '"git":' metadata.json \
62 | && sed -i "/\"git\":/c \ \ \"git\": \"$$LASTCOMMIT\"," metadata.json \
63 | || sed -i "/uuid/a \ \ \"git\": \"$$LASTCOMMIT\"," metadata.json
64 | else
65 | grep -q '"custom-install":' metadata.json \
66 | || sed -i "/uuid/a \ \ \"custom-install\": true," metadata.json
67 | endif
68 | endif
69 |
70 | # Build and install #
71 | install: compilemo-base metadata
72 | ifeq ($(CUSTOMPATH),)
73 | glib-compile-schemas ./schemas/
74 | mkdir -p $(INSTALLPATH)/$(UUID)
75 | cp -r $(ZIPFILES) $(INSTALLPATH)/$(UUID)
76 | else
77 | mkdir -p $(CUSTOMPATH)/$(UUID)
78 | cp -r $(filter-out schemas locale README.md COPYING, $(ZIPFILES)) $(CUSTOMPATH)/$(UUID)
79 | mkdir -p $(PKGDIR)/usr/share/glib-2.0/schemas
80 | cp -r ./schemas/*.gschema.* $(PKGDIR)/usr/share/glib-2.0/schemas/
81 | glib-compile-schemas $(PKGDIR)/usr/share/glib-2.0/schemas 2>/dev/null
82 | mkdir -p $(PKGDIR)/usr/share/locale
83 | cp -r ./locale/* $(PKGDIR)/usr/share/locale/
84 | mkdir -p $(PKGDIR)/usr/share/doc/$(EXTNAME)
85 | cp ./README.md $(PKGDIR)/usr/share/doc/$(EXTNAME)/
86 | mkdir -p $(PKGDIR)/usr/share/licenses/$(EXTNAME)
87 | cp ./COPYING $(PKGDIR)/usr/share/licenses/$(EXTNAME)/
88 | mkdir -p $(CUSTOMPATH)/$(UUID)/node_modules
89 | chmod 777 $(CUSTOMPATH)/$(UUID)/node_modules
90 | endif
91 |
92 | _build: glib-schemas compilemo-base
93 |
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gnome Shell Extension Cast to TV
2 | [](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv/blob/master/COPYING)
3 | [](https://crowdin.com/project/cast-to-tv)
4 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TFVDFD88KQ322)
5 | [](https://www.paypal.me/Rafostar)
6 | [](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2FRafostar%2Fgnome-shell-extension-cast-to-tv)
7 |
8 |
9 |
10 |
11 |
12 | ## Features
13 | * Cast videos, music and pictures to:
14 | * Chromecast devices
15 | * Any device with web browser (other PC or smartphone)
16 | * Media player app (eg. MPV, VLC)
17 | * Supports external and built-in subtitles (along with custom fansubs)
18 | * Chromecast remote controller (control playback from gnome top bar)
19 | * Play on other device using integrated web player and change content without refreshing web page
20 | * Transcode videos to supported format on the fly
21 | * Optional VAAPI/NVENC video encoding for low cpu usage
22 | * Stream music with visualizations (requires fast cpu)
23 | * Nautilus right click menu integration
24 | * Media playlist with "Drag and Drop" support
25 |
26 | [Playercast](https://rafostar.github.io/playercast) app turns your media player on any other Linux device (e.g. HTPC, Raspberry Pi) into a media receiver that works similarly to Chromecast.
27 |
28 | Expand extension functionality through Add-ons:
29 | * [Links Add-on](https://github.com/Rafostar/cast-to-tv-links-addon) - cast media from web pages
30 | * [Desktop Add-on](https://github.com/Rafostar/cast-to-tv-desktop-addon) - desktop streaming
31 |
32 | ## Download
33 | ### For latest release and changelog check out [releases page](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv/releases).
34 |
35 | [
](https://extensions.gnome.org/extension/1544/cast-to-tv)
36 |
37 | Installation from source code is described in the [wiki](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv/wiki).
38 |
39 | After enabling the extension, remember to install all [requirements](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv#requirements) and [npm dependencies](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv#install-npm-dependencies).
40 |
41 | ## Requirements
42 | Here is a list of required programs that Cast to TV depends on:
43 | * [npm](https://www.npmjs.com/get-npm) (for dependencies installation)
44 | * [nodejs](https://nodejs.org) (v8.6 or newer)
45 | * [ffmpeg](https://ffmpeg.org) (with ffprobe)
46 |
47 | Please make sure you have all of the above installed.
48 |
49 | ### Optional:
50 | * [nautilus-python](https://github.com/GNOME/nautilus-python) (for nautilus integration)
51 |
52 | Nautilus extension is included in Cast to TV (since version 9).
53 |
54 | You can optionally use hardware VAAPI or NVENC encoding. This of course requires working drivers. More info and how to install hardware acceleration [here](https://wiki.archlinux.org/index.php/Hardware_video_acceleration).
55 |
56 | ## Installation
57 |
58 | ### Ubuntu
59 | Having enabled universe repo run:
60 | ```
61 | sudo apt install npm nodejs ffmpeg
62 | ```
63 | Ubuntu is shipping wrong npm version for some reason.
64 | Update it and clear bash cache:
65 | ```
66 | sudo npm install -g npm
67 | hash -r
68 | ```
69 |
70 | ### Fedora
71 | Having enabled rpm fusion repos run:
72 | ```
73 | sudo dnf install npm nodejs ffmpeg
74 | ```
75 |
76 | ### Arch
77 | ```
78 | sudo pacman -S npm nodejs ffmpeg
79 | ```
80 |
81 | ### Nautilus integration (optional)
82 | * Ubuntu: `sudo apt install python3-nautilus python3-gi`
83 | * Fedora: `sudo dnf install nautilus-python python3-gobject`
84 | * Arch: `sudo pacman -S python-nautilus python-gobject`
85 |
86 | Ubuntu releases older than 20.04 require `python-nautilus` instead of ` python3-nautilus`.
87 | Older Fedora releases additionally require `pygobject3`.
88 |
89 | Nautilus integration is disabled by default. Remember to turn it on in this extension settings.
90 |
91 | ## Install npm dependencies
92 | **Before using extension** you also **must** install some additional npm packages.
93 |
94 | You should also repeat this step when updating the extension to the new version, otherwise you may not have newly added or updated dependencies.
95 |
96 | ### New method
97 | In version 9 and later this can be done from extension preferences.
98 | Go to `Cast Settings -> Modules` and click `Install npm modules` button.
99 |
100 | You must have `npm` and `nodejs` installed prior to this step.
101 |
102 | ### Old method
103 | Run below code in terminal:
104 | ```
105 | cd ~/.local/share/gnome-shell/extensions/cast-to-tv@rafostar.github.com
106 | npm install
107 | ```
108 |
109 | ## How to use
110 | Detailed instructions related to configuration and using the extension are in the [wiki](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv/wiki).
111 | You can also find some usage examples and firewall config there.
112 |
113 | Check out [FAQ](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv/wiki/FAQ), before asking questions.
114 |
115 | ## Info for translators
116 | Preferred translation method is to use [Cast to TV Crowdin](https://crowdin.com/project/cast-to-tv) web page.
117 |
118 | Crowdin does not require any additional tools and translating can be done through web browser. You can login using GitHub account or create a new one. Only I can add new languages to this project, so if your language is not available, please contact me first (you can leave comment [here](https://github.com/Rafostar/gnome-shell-extension-cast-to-tv/issues/29)).
119 |
120 | Alternatively you can still use Makefile and tools like Poedit to generate translations:
121 |
122 | `make potfile` - generates updated POT file.
123 | `make mergepo` - merges changes from POT file into all PO files.
124 | `make compilemo` - compiles translation files.
125 |
126 | After compiling restart gnome-shell for changes to be applied.
127 |
128 | ## Special Thanks
129 | Special thanks go to [Simon Kusterer (xat)](https://github.com/xat) for developing [chromecast-player](https://github.com/xat/chromecast-player) and [Sam Potts](https://github.com/sampotts) for making [Plyr](https://github.com/sampotts/plyr), an awesome HTML5 video player.
130 |
131 | ### Nautilus Extension
132 | Many thanks to [Rendy Anthony](https://github.com/rendyanthony) for helping me make Nautilus integration based on his [nautilus-cast](https://github.com/rendyanthony/nautilus-cast) extension.
133 |
134 | ### Translations
135 | Many thanks to everyone involved in translating this extension either through GitHub or Crowdin.
136 |
137 | ## Donation
138 | If you like my work please support it by buying me a cup of coffee :-)
139 |
140 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TFVDFD88KQ322)
141 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-architect
--------------------------------------------------------------------------------
/addons.js:
--------------------------------------------------------------------------------
1 | const { GLib } = imports.gi;
2 | const Main = imports.ui.main;
3 | const AggregateMenu = Main.panel.statusArea.aggregateMenu;
4 |
5 | let castMenu;
6 | let addonMenuItems = {};
7 | let addonMenuSignals = {};
8 | let timeouts = {};
9 |
10 | function findCastToTv()
11 | {
12 | let menuItems = AggregateMenu.menu._getMenuItems();
13 | let index = 0;
14 |
15 | while(index < menuItems.length)
16 | {
17 | if(
18 | menuItems[index].hasOwnProperty('extensionId')
19 | && menuItems[index].extensionId === 'cast-to-tv'
20 | ) {
21 | return menuItems[index];
22 | }
23 |
24 | index++;
25 | }
26 |
27 | return null;
28 | }
29 |
30 | function setLastMenuItem(extMenu, item, endOffset)
31 | {
32 | if(!endOffset) endOffset = 0;
33 |
34 | let subMenuItems = extMenu.castSubMenu.menu._getMenuItems();
35 | let lastItemIndex = subMenuItems.length - 1;
36 | extMenu.castSubMenu.menu.moveMenuItem(item, lastItemIndex - endOffset);
37 | }
38 |
39 | function enableAddon(uuid)
40 | {
41 | let addonName = uuid.split('@')[0];
42 |
43 | if(timeouts[addonName])
44 | return;
45 |
46 | /* Give main extension time to finish startup */
47 | timeouts[addonName] = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 2500, () =>
48 | {
49 | timeouts[addonName] = null;
50 |
51 | castMenu = findCastToTv();
52 | let mainExtension = Main.extensionManager.lookup('cast-to-tv@rafostar.github.com');
53 | let addonExtension = Main.extensionManager.lookup(uuid);
54 |
55 | if(!castMenu || !mainExtension || !addonExtension)
56 | return GLib.SOURCE_REMOVE;
57 |
58 | let { helper, soup, shared } = mainExtension.imports;
59 | let { AddonMenuItem } = addonExtension.imports.widget;
60 |
61 | addonMenuItems[addonName] = new AddonMenuItem({
62 | helper: helper,
63 | soupClient: soup.client,
64 | shared: shared.module.exports,
65 | path: mainExtension.path
66 | });
67 |
68 | addonMenuSignals[addonName] = [
69 | addonMenuItems[addonName].connect('destroy', () =>
70 | {
71 | addonMenuSignals[addonName].forEach(signal =>
72 | addonMenuItems[addonName].disconnect(signal))
73 |
74 | addonMenuSignals[addonName] = null;
75 | addonMenuItems[addonName].destroyed = true;
76 | })
77 | ];
78 |
79 | if(
80 | addonMenuItems[addonName].hasOwnProperty('hasExtApp')
81 | && addonMenuItems[addonName].hasExtApp
82 | ) {
83 | addonMenuSignals[addonName].push(
84 | addonMenuItems[addonName].connect('activate', () =>
85 | helper.startApp(addonExtension.path)
86 | )
87 | )
88 | }
89 | else
90 | {
91 | addonMenuSignals[addonName].push(
92 | addonMenuItems[addonName].connect('activate', () =>
93 | helper.closeOtherApps()
94 | )
95 | )
96 | }
97 |
98 | let castMenuItems = castMenu.castSubMenu.menu._getMenuItems();
99 | let insertIndex = castMenuItems.length - 1;
100 |
101 | let prevMenuItem = castMenuItems.find(item =>
102 | {
103 | if(
104 | item.hasOwnProperty('isDesktopStream')
105 | || (castMenuItems.indexOf(item) > 2
106 | && addonMenuItems[addonName].label.text < item.label.text)
107 | ) {
108 | return true;
109 | }
110 |
111 | return false;
112 | });
113 |
114 | /* Desktop streaming should be last on the list (experimental feature) */
115 | if(prevMenuItem && !addonMenuItems[addonName].hasOwnProperty('isDesktopStream'))
116 | insertIndex = castMenuItems.indexOf(prevMenuItem);
117 |
118 | castMenu.castSubMenu.menu.addMenuItem(addonMenuItems[addonName], insertIndex);
119 |
120 | if(
121 | castMenu.hasOwnProperty('isServiceEnabled')
122 | && castMenu.isServiceEnabled === false
123 | ) {
124 | if(addonMenuItems[addonName].hasOwnProperty('actor'))
125 | addonMenuItems[addonName].actor.hide();
126 | else
127 | addonMenuItems[addonName].hide();
128 | }
129 |
130 | if(castMenu.hasOwnProperty('serviceMenuItem'))
131 | setLastMenuItem(castMenu, castMenu.serviceMenuItem);
132 |
133 | if(castMenu.hasOwnProperty('settingsMenuItem'))
134 | setLastMenuItem(castMenu, castMenu.settingsMenuItem);
135 |
136 | return GLib.SOURCE_REMOVE;
137 | });
138 | }
139 |
140 | function disableAddon(uuid)
141 | {
142 | let addonName = uuid.split('@')[0];
143 |
144 | if(timeouts[addonName])
145 | {
146 | GLib.source_remove(timeouts[addonName]);
147 | timeouts[addonName] = null;
148 | }
149 |
150 | if(!addonMenuItems[addonName] || addonMenuItems[addonName].destroyed)
151 | return;
152 |
153 | /* No need to reorder menu items when locking screen,
154 | as whole cast menu will be destroyed then anyway */
155 | let lockingScreen = (
156 | Main.sessionMode.currentMode == 'unlock-dialog'
157 | || Main.sessionMode.currentMode == 'lock-screen'
158 | );
159 |
160 | if(!lockingScreen && castMenu)
161 | setLastMenuItem(castMenu, addonMenuItems[addonName]);
162 |
163 | /* Force GUI refresh by hiding item before removal */
164 | if(addonMenuItems[addonName].hasOwnProperty('actor'))
165 | addonMenuItems[addonName].actor.hide();
166 | else
167 | addonMenuItems[addonName].hide();
168 |
169 | addonMenuItems[addonName].destroy();
170 | addonMenuItems[addonName] = null;
171 | }
172 |
--------------------------------------------------------------------------------
/appIcon/cast-to-tv.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
28 |
--------------------------------------------------------------------------------
/appIcon/prefs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rafostar/gnome-shell-extension-cast-to-tv/10502fc3cd247f776895d418585243477de23e24/appIcon/prefs.png
--------------------------------------------------------------------------------
/compat.js:
--------------------------------------------------------------------------------
1 | const { Clutter, GObject } = imports.gi;
2 | const PopupMenu = imports.ui.popupMenu;
3 | const Config = imports.misc.config;
4 |
5 | var IS_OLD_SHELL = (Config.PACKAGE_VERSION.split('.')[1] < 35);
6 |
7 | var AltPopupBase = GObject.registerClass(
8 | class AltPopupBase extends PopupMenu.PopupBaseMenuItem
9 | {
10 | _init()
11 | {
12 | super._init({ hover: false });
13 | }
14 |
15 | add_style_pseudo_class()
16 | {
17 | return null;
18 | }
19 |
20 | _onButtonReleaseEvent(actor, event)
21 | {
22 | return this.vfunc_button_release_event();
23 | }
24 |
25 | vfunc_button_release_event()
26 | {
27 | return Clutter.EVENT_STOP;
28 | }
29 | });
30 |
31 | var AltPopupImage = GObject.registerClass(
32 | class AltPopupImage extends PopupMenu.PopupImageMenuItem
33 | {
34 | _init(text, icon)
35 | {
36 | super._init(text, icon);
37 | }
38 |
39 | /* Default temporary action for override */
40 | _onItemClicked()
41 | {
42 | return null;
43 | }
44 |
45 | _onButtonReleaseEvent(actor, event)
46 | {
47 | return this.vfunc_button_release_event();
48 | }
49 |
50 | vfunc_button_release_event()
51 | {
52 | this.remove_style_pseudo_class('active');
53 | this._onItemClicked();
54 | return Clutter.EVENT_STOP;
55 | }
56 | });
57 |
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | files:
2 | - source: /po/cast-to-tv/cast-to-tv.pot
3 | translation: /po/cast-to-tv/%two_letters_code%.po
4 | - source: /po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot
5 | translation: /po/cast-to-tv-links-addon/%two_letters_code%.po
6 |
--------------------------------------------------------------------------------
/helper.js:
--------------------------------------------------------------------------------
1 | /*
2 | Convenience replacement
3 | Original convenience.js does not work correctly with this extension use-cases
4 | */
5 |
6 | const { Gio, GLib } = imports.gi;
7 | const ByteArray = imports.byteArray;
8 | const Gettext = imports.gettext;
9 |
10 | const NOTIFY_PATH = GLib.find_program_in_path('notify-send');
11 | const GJS_PATH = GLib.find_program_in_path('gjs');
12 |
13 | let launcher;
14 | let runApp;
15 |
16 | function getSettings(localPath, schemaName)
17 | {
18 | if(!localPath) return null;
19 |
20 | schemaName = schemaName || 'org.gnome.shell.extensions.cast-to-tv';
21 |
22 | const GioSSS = Gio.SettingsSchemaSource;
23 | let schemaDir = Gio.File.new_for_path(localPath).get_child('schemas');
24 | let schemaSource = null;
25 |
26 | if(schemaDir.query_exists(null))
27 | schemaSource = GioSSS.new_from_directory(
28 | localPath + '/schemas', GioSSS.get_default(), false
29 | );
30 | else
31 | schemaSource = GioSSS.get_default();
32 |
33 | let schemaObj = schemaSource.lookup(schemaName, true);
34 | if(!schemaObj)
35 | throw new Error('Cast to TV: extension schemas could not be found!');
36 |
37 | return new Gio.Settings({ settings_schema: schemaObj });
38 | }
39 |
40 | function initTranslations(localPath, gettextDomain)
41 | {
42 | gettextDomain = gettextDomain || 'cast-to-tv';
43 |
44 | if(localPath)
45 | {
46 | let localeDir = Gio.File.new_for_path(localPath).get_child('locale');
47 |
48 | if(localeDir.query_exists(null))
49 | Gettext.bindtextdomain(gettextDomain, localPath + '/locale');
50 | else
51 | Gettext.bindtextdomain(gettextDomain, '/usr/share/locale');
52 | }
53 | }
54 |
55 | function closeOtherApps(mainPath, totalKill)
56 | {
57 | if(runApp)
58 | {
59 | if(!runApp.get_identifier())
60 | runApp = null;
61 | else
62 | {
63 | runApp.wait_async(null, () => runApp = null);
64 | runApp.force_exit();
65 | }
66 | }
67 |
68 | /* Close other possible opened extension windows */
69 | if(mainPath && totalKill)
70 | GLib.spawn_command_line_async('pkill -SIGINT -f ' + mainPath);
71 | }
72 |
73 | function startApp(appPath, appName, args, noClose)
74 | {
75 | appName = appName || 'app';
76 | let spawnArgs = [GJS_PATH, appPath + '/' + appName + '.js'];
77 |
78 | if(args && Array.isArray(args))
79 | args.forEach(arg => spawnArgs.push(arg));
80 |
81 | /* To not freeze gnome shell app needs to be run as separate process */
82 | if(noClose) return GLib.spawn_async(appPath, spawnArgs, null, 0, null);
83 |
84 | if(!launcher)
85 | launcher = new Gio.SubprocessLauncher();
86 |
87 | launcher.set_cwd(appPath);
88 |
89 | if(!runApp || !runApp.get_identifier())
90 | return runApp = launcher.spawnv(spawnArgs);
91 |
92 | runApp.wait_async(null, () => runApp = launcher.spawnv(spawnArgs));
93 | runApp.force_exit();
94 | }
95 |
96 | function setDevicesWidget(widget, devices, activeText)
97 | {
98 | if(Array.isArray(devices) && devices.length)
99 | {
100 | let foundActive = false;
101 | let appendIndex = 0;
102 | let appendArray = [];
103 |
104 | devices.forEach(device =>
105 | {
106 | if(
107 | (!device.name
108 | || !device.friendlyName
109 | || appendArray.includes(device.friendlyName))
110 | || (!device.name.endsWith('.local')
111 | && !device.ip)
112 | ) {
113 | return;
114 | }
115 |
116 | widget.append(device.friendlyName, device.friendlyName);
117 | appendArray.push(device.friendlyName);
118 | appendIndex++;
119 |
120 | if(!foundActive && activeText && activeText === device.friendlyName)
121 | {
122 | widget.set_active(appendIndex);
123 | foundActive = true;
124 | }
125 | });
126 |
127 | if(activeText && !foundActive)
128 | widget.set_active(0);
129 |
130 | return;
131 | }
132 |
133 | widget.set_active(0);
134 | }
135 |
136 | function parsePlayercastDevices(localData, webData)
137 | {
138 | if(webData)
139 | {
140 | webData.forEach(fn =>
141 | {
142 | if(localData.some(dev => dev.friendlyName === fn))
143 | return;
144 |
145 | localData.unshift({
146 | name: (fn.split(' ').join('')).toLowerCase() + '.local',
147 | friendlyName: fn,
148 | ip: ''
149 | });
150 | });
151 | }
152 |
153 | return localData;
154 | }
155 |
156 | function readFromFile(path)
157 | {
158 | let fileExists = GLib.file_test(path, GLib.FileTest.EXISTS);
159 |
160 | if(fileExists)
161 | {
162 | let [success, contents] = GLib.file_get_contents(path);
163 |
164 | if(success)
165 | {
166 | if(contents instanceof Uint8Array)
167 | {
168 | try { contents = JSON.parse(ByteArray.toString(contents)); }
169 | catch(err) { contents = null; }
170 | }
171 | else
172 | {
173 | try { contents = JSON.parse(contents); }
174 | catch(err) { contents = null; }
175 | }
176 |
177 | return contents;
178 | }
179 | }
180 |
181 | return null;
182 | }
183 |
184 | function readFromFileAsync(file, callback)
185 | {
186 | /* Either filepath or Gio.File can be used */
187 | if(file && typeof file === 'string')
188 | file = Gio.file_new_for_path(file);
189 |
190 | file.load_contents_async(null, (file, res) =>
191 | {
192 | let success, contents;
193 |
194 | try {
195 | [success, contents] = file.load_contents_finish(res);
196 |
197 | if(success)
198 | {
199 | if(contents instanceof Uint8Array)
200 | contents = JSON.parse(ByteArray.toString(contents));
201 | else
202 | contents = JSON.parse(contents);
203 | }
204 | else
205 | contents = null;
206 | }
207 | catch(err) {
208 | contents = null;
209 | }
210 |
211 | callback(contents);
212 | });
213 | }
214 |
215 | function writeToFile(path, contents)
216 | {
217 | GLib.file_set_contents(path, JSON.stringify(contents, null, 1));
218 | }
219 |
220 | function readOutputAsync(stream, callback)
221 | {
222 | stream.read_line_async(GLib.PRIORITY_LOW, null, (source, res) =>
223 | {
224 | let out_fd, length, outStr;
225 |
226 | [out_fd, length] = source.read_line_finish(res);
227 |
228 | if(out_fd !== null)
229 | {
230 | if(out_fd instanceof Uint8Array)
231 | outStr = ByteArray.toString(out_fd);
232 | else
233 | outStr = out_fd.toString();
234 |
235 | callback(outStr, false);
236 | return readOutputAsync(source, callback);
237 | }
238 |
239 | callback('', true);
240 | });
241 | }
242 |
243 | function createDir(dirPath, permissions)
244 | {
245 | permissions = permissions || 493 // 755 in octal
246 |
247 | let dirExists = GLib.file_test(dirPath, GLib.FileTest.EXISTS);
248 |
249 | if(!dirExists)
250 | GLib.mkdir_with_parents(dirPath, permissions);
251 | }
252 |
253 | function notify(summary, mainBody)
254 | {
255 | if(NOTIFY_PATH)
256 | {
257 | GLib.spawn_async(null, [
258 | NOTIFY_PATH,
259 | '-i', 'tv-symbolic',
260 | '-u', 'normal',
261 | summary, mainBody
262 | ], null, 0, null);
263 | }
264 |
265 | log(summary + ': ' + mainBody);
266 | }
267 |
--------------------------------------------------------------------------------
/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Cast files to Chromecast, web browser or media player app over local network.\n\nRequires: npm nodejs ffmpeg\n\nAfter enabling go to Cast Settings \u2192 Modules and click \"Install npm modules\" button.\n\nVisit this extension homepage for more info on how to install and use.\nPlease use GitHub to report issues.",
3 | "extension-id": "cast-to-tv",
4 | "gettext-domain": "cast-to-tv",
5 | "name": "Cast to TV",
6 | "settings-schema": "org.gnome.shell.extensions.cast-to-tv",
7 | "shell-version": [
8 | "3.34",
9 | "3.36"
10 | ],
11 | "url": "https://rafostar.github.io/gnome-shell-extension-cast-to-tv",
12 | "uuid": "cast-to-tv@rafostar.github.com",
13 | "version": 15
14 | }
15 |
--------------------------------------------------------------------------------
/node_scripts/addons-importer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const debug = require('debug')('addons-importer');
4 | const extensionsPath = path.join(__dirname + '/../..');
5 |
6 | var addons = [];
7 |
8 | module.exports = function(name)
9 | {
10 | return addons[name];
11 | }
12 |
13 | fs.readdir(extensionsPath, (err, extensions) =>
14 | {
15 | extensions.forEach(folder =>
16 | {
17 | if(!folder.startsWith('cast-to-tv') || !folder.includes('addon@'))
18 | return;
19 |
20 | debug(`Addon folder: ${folder}`);
21 |
22 | var addonPath = path.join(extensionsPath, folder, 'node_scripts/addon');
23 | debug(`Addon path: ${addonPath}`);
24 |
25 | var addonName = folder.substring(11, folder.lastIndexOf('-'));
26 | debug(`Addon name: ${addonName}`);
27 |
28 | fs.access(addonPath + '.js', fs.constants.F_OK, (err) =>
29 | {
30 | if(err) return debug(err);
31 |
32 | addons[addonName] = require(addonPath);
33 | debug(`Imported: ${addonName}`);
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/node_scripts/encode.js:
--------------------------------------------------------------------------------
1 | const { spawn } = require('child_process');
2 | const debug = require('debug')('ffmpeg');
3 | const bridge = require('./bridge');
4 | const notify = require('./notify');
5 | const messages = require('./messages.js');
6 | const shared = require('../shared');
7 | const stdioConf = (debug.enabled) ? 'inherit' : 'ignore';
8 |
9 | exports.streamProcess = null;
10 | exports.enabled = true;
11 | var notifyError = false;
12 |
13 | String.prototype.replaceAt = function(index, replacement)
14 | {
15 | return this.substr(0, index) + replacement + this.substr(index + 1);
16 | }
17 |
18 | function getSubsOptions()
19 | {
20 | var subsPathEscaped = (bridge.selection.subsPath) ? bridge.selection.subsPath : bridge.selection.filePath;
21 | var index = subsPathEscaped.length;
22 |
23 | debug('Parsing subtitles path...');
24 |
25 | while(index--)
26 | {
27 | if(shared.escapeChars.includes(subsPathEscaped.charAt(index)))
28 | subsPathEscaped = subsPathEscaped.replaceAt(index, '\\' + subsPathEscaped.charAt(index));
29 | }
30 |
31 | debug(`Parsed subtitles path: ${subsPathEscaped}`);
32 |
33 | if(bridge.mediaData.charEnc)
34 | {
35 | subsPathEscaped += ':charenc=' + bridge.mediaData.charEnc;
36 | debug(`Added ${bridge.mediaData.charEnc} char encoding to subtitles options`);
37 | }
38 |
39 | return subsPathEscaped;
40 | }
41 |
42 | function getAudioOptsArray(isEncodeAudio)
43 | {
44 | return (isEncodeAudio && isEncodeAudio === true) ? ['flac', '-ac', '2'] : ['copy'];
45 | }
46 |
47 | function getPlayerOptsArray()
48 | {
49 | if(bridge.config.receiverType === 'playercast')
50 | return ['-f', 'matroska'];
51 |
52 | return [
53 | '-frag_duration', '1000000',
54 | '-movflags', '+empty_moov',
55 | '-strict', '-2',
56 | '-f', 'mp4'
57 | ];
58 | }
59 |
60 | function getIsBurnSubs()
61 | {
62 | return (
63 | bridge.config.burnSubtitles
64 | && (bridge.mediaData.isSubsMerged || bridge.selection.subsPath)
65 | ) ? true : false;
66 | }
67 |
68 | function insertSubsAfter(encodeOpts, arrEl)
69 | {
70 | if(!getIsBurnSubs())
71 | return;
72 |
73 | encodeOpts.splice(
74 | encodeOpts.indexOf(arrEl) + 1, 0,
75 | '-vf', 'subtitles=' + getSubsOptions(), '-sn'
76 | );
77 | }
78 |
79 | function createEncodeProcess(encodeOpts)
80 | {
81 | debug(`Starting FFmpeg with opts: ${JSON.stringify(encodeOpts)}`);
82 |
83 | exports.streamProcess = spawn(bridge.config.ffmpegPath, encodeOpts,
84 | { stdio: ['ignore', 'pipe', stdioConf] });
85 |
86 | notifyError = false;
87 | exports.streamProcess.once('exit', onAutoExit);
88 | exports.streamProcess.once('error', onEncodeError);
89 |
90 | return exports.streamProcess.stdout;
91 | }
92 |
93 | function onAutoExit(code)
94 | {
95 | if(!notifyError)
96 | {
97 | exports.streamProcess.removeListener('error', onEncodeError);
98 |
99 | if(code) notify('Cast to TV', messages.ffmpegError, bridge.selection.filePath);
100 | }
101 |
102 | exports.streamProcess = null;
103 |
104 | if(code !== null)
105 | debug(`FFmpeg exited with code: ${code}`);
106 |
107 | debug('FFmpeg auto exit');
108 | }
109 |
110 | function onManualExit(code)
111 | {
112 | if(!notifyError)
113 | exports.streamProcess.removeListener('error', onEncodeError);
114 |
115 | exports.streamProcess = null;
116 | debug('FFmpeg manual exit');
117 | }
118 |
119 | function onEncodeError(error)
120 | {
121 | if(error.message == 'spawn ' + bridge.config.ffmpegPath + ' ENOENT')
122 | {
123 | notify('Cast to TV', messages.ffmpegPath);
124 | notifyError = true;
125 | }
126 |
127 | exports.streamProcess = null;
128 | debug('FFmpeg had error!');
129 | debug(error);
130 | }
131 |
132 | exports.video = function(isEncodeAudio)
133 | {
134 | var encodeOpts = [
135 | '-i', bridge.selection.filePath,
136 | '-c:v', 'libx264',
137 | '-pix_fmt', 'yuv420p',
138 | '-preset', 'superfast',
139 | '-level:v', '4.1',
140 | '-b:v', bridge.config.videoBitrate + 'M',
141 | '-c:a', ...getAudioOptsArray(isEncodeAudio),
142 | '-metadata', 'title=Cast to TV - Software Encoded Stream',
143 | ...getPlayerOptsArray(),
144 | 'pipe:1'
145 | ];
146 |
147 | insertSubsAfter(encodeOpts, 'libx264');
148 |
149 | return createEncodeProcess(encodeOpts);
150 | }
151 |
152 | exports.videoVaapi = function(isEncodeAudio)
153 | {
154 | var encodeOpts = [
155 | '-i', bridge.selection.filePath,
156 | '-c:v', 'h264_vaapi',
157 | '-level:v', '4.1',
158 | '-b:v', bridge.config.videoBitrate + 'M',
159 | '-c:a', ...getAudioOptsArray(isEncodeAudio),
160 | '-metadata', 'title=Cast to TV - VAAPI Encoded Stream',
161 | ...getPlayerOptsArray(),
162 | 'pipe:1'
163 | ];
164 |
165 | if(getIsBurnSubs())
166 | {
167 | encodeOpts.unshift(
168 | '-hwaccel', 'vaapi',
169 | '-hwaccel_device', '/dev/dri/renderD128',
170 | '-hwaccel_output_format', 'vaapi'
171 | );
172 | encodeOpts.splice(
173 | encodeOpts.indexOf('h264_vaapi') + 1, 0,
174 | '-vf', 'scale_vaapi,hwmap=mode=read+write,format=nv12,subtitles=' +
175 | getSubsOptions() + ',hwmap', '-sn'
176 | );
177 | }
178 | else
179 | {
180 | encodeOpts.unshift('-vaapi_device', '/dev/dri/renderD128');
181 | encodeOpts.splice(encodeOpts.indexOf('h264_vaapi') + 1, 0, '-vf', 'format=nv12,hwmap');
182 | }
183 |
184 | return createEncodeProcess(encodeOpts);
185 | }
186 |
187 | exports.videoNvenc = function(isEncodeAudio)
188 | {
189 | var encodeOpts = [
190 | '-i', bridge.selection.filePath,
191 | '-c:v', 'h264_nvenc',
192 | '-level:v', '4.1',
193 | '-b:v', bridge.config.videoBitrate + 'M',
194 | '-c:a', ...getAudioOptsArray(isEncodeAudio),
195 | '-metadata', 'title=Cast to TV - NVENC Encoded Stream',
196 | ...getPlayerOptsArray(),
197 | 'pipe:1'
198 | ];
199 |
200 | insertSubsAfter(encodeOpts, 'h264_nvenc');
201 |
202 | return createEncodeProcess(encodeOpts);
203 | }
204 |
205 | exports.videoAmf = function(isEncodeAudio)
206 | {
207 | var encodeOpts = [
208 | '-i', bridge.selection.filePath,
209 | '-c:v', 'h264_amf',
210 | '-level:v', '4.1',
211 | '-b:v', bridge.config.videoBitrate + 'M',
212 | '-c:a', ...getAudioOptsArray(isEncodeAudio),
213 | '-metadata', 'title=Cast to TV - AMF Encoded Stream',
214 | ...getPlayerOptsArray(),
215 | 'pipe:1'
216 | ];
217 |
218 | insertSubsAfter(encodeOpts, 'h264_amf');
219 |
220 | return createEncodeProcess(encodeOpts);
221 | }
222 |
223 | exports.audio = function()
224 | {
225 | var encodeOpts = [
226 | '-i', bridge.selection.filePath,
227 | '-c:v', 'copy',
228 | '-c:a', ...getAudioOptsArray(true),
229 | '-metadata', 'title=Cast to TV - Audio Encoded Stream',
230 | ...getPlayerOptsArray(),
231 | 'pipe:1'
232 | ];
233 |
234 | return createEncodeProcess(encodeOpts);
235 | }
236 |
237 | exports.musicVisualizer = function()
238 | {
239 | var encodeOpts = [
240 | '-i', bridge.selection.filePath,
241 | '-filter_complex',
242 | `firequalizer=gain='(1.4884e8 * f*f*f / (f*f + 424.36) / (f*f + 1.4884e8) / sqrt(f*f + 25122.25)) / sqrt(2)':
243 | scale=linlin:
244 | wfunc=tukey:
245 | zero_phase=on:
246 | fft2=on,
247 | showcqt=fps=60:
248 | size=1280x360:
249 | count=1:
250 | csp=bt470bg:
251 | cscheme=1|0|0.5|0|1|0.5:
252 | bar_g=2:
253 | sono_g=4:
254 | bar_v=9:
255 | sono_v=17:
256 | sono_h=0:
257 | bar_t=0.5:
258 | axis_h=0:
259 | tc=0.33:
260 | tlength='st(0,0.17); 384*tc / (384 / ld(0) + tc*f /(1-ld(0))) + 384*tc / (tc*f / ld(0) + 384 /(1-ld(0)))',
261 | format=yuv420p,split [v0],vflip [v1]; [v0][v1] vstack [vis]`,
262 | '-map', '[vis]',
263 | '-map', '0:a',
264 | '-c:v', 'libx264',
265 | '-pix_fmt', 'yuv420p',
266 | '-preset', 'superfast',
267 | '-level:v', '4.1',
268 | '-b:v', bridge.config.videoBitrate + 'M',
269 | '-c:a', 'copy',
270 | '-metadata', 'title=Cast to TV - Music Visualizer',
271 | ...getPlayerOptsArray(),
272 | 'pipe:1'
273 | ];
274 |
275 | return createEncodeProcess(encodeOpts);
276 | }
277 |
278 | exports.closeStreamProcess = function()
279 | {
280 | if(!exports.streamProcess) return;
281 |
282 | if(exports.streamProcess.stdout)
283 | {
284 | exports.streamProcess.removeListener('exit', onAutoExit);
285 | exports.streamProcess.once('exit', onManualExit);
286 |
287 | if(!exports.streamProcess.stdout.destroyed)
288 | {
289 | exports.streamProcess.stdout.destroy();
290 | debug('Destroyed stream process remaining stdout data');
291 | }
292 | else
293 | debug('Stream process stdout was destroyed earlier');
294 |
295 | exports.streamProcess.stdout = null;
296 | }
297 | else
298 | {
299 | debug('Force killing stream process...');
300 |
301 | try {
302 | process.kill(exports.streamProcess.pid, 'SIGHUP');
303 | debug('Force killed stream process');
304 | }
305 | catch(err) {
306 | debug('Could not kill stream process!');
307 | debug(err);
308 | }
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/node_scripts/events.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require('events');
2 | var ee = new EventEmitter();
3 |
4 | module.exports = ee;
5 |
--------------------------------------------------------------------------------
/node_scripts/gettext.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const Gettext = require('node-gettext');
4 | const moParser = require('gettext-parser').mo;
5 |
6 | const extLocaleDir = path.join(__dirname + '/../locale');
7 | const translationsDir = (fs.existsSync(extLocaleDir)) ? extLocaleDir : '/usr/share/locale';
8 | const domain = 'cast-to-tv';
9 |
10 | var gt = new Gettext();
11 | exports.locales = [];
12 |
13 | exports.initTranslations = function()
14 | {
15 | gt.setTextDomain(domain);
16 |
17 | exports.locales = fs.readdirSync(translationsDir);
18 |
19 | exports.locales.forEach(locale =>
20 | {
21 | var translationsFilePath = path.join(translationsDir, locale, 'LC_MESSAGES', domain + '.mo');
22 | fs.access(translationsFilePath, fs.constants.F_OK, (err) =>
23 | {
24 | if(err) return;
25 |
26 | fs.readFile(translationsFilePath, (err, data) =>
27 | {
28 | if(err) return console.log(`Cast to TV: error reading node ${locale} translation`);
29 |
30 | var parsedTranslations = moParser.parse(data);
31 | gt.addTranslations(locale, domain, parsedTranslations);
32 | });
33 | });
34 | });
35 |
36 | exports.locales.unshift('en');
37 | }
38 |
39 | exports.setLocale = function(locale)
40 | {
41 | gt.setLocale(locale);
42 | }
43 |
44 | exports.translate = function(text)
45 | {
46 | return gt.gettext(text);
47 | }
48 |
--------------------------------------------------------------------------------
/node_scripts/gnome.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const { spawn, spawnSync } = require('child_process');
4 | const debug = require('debug')('gnome');
5 | const sender = require('./sender');
6 | const noop = () => {};
7 |
8 | var schemaName = 'org.gnome.shell.extensions.cast-to-tv';
9 | var schemaDir = path.join(__dirname + '/../schemas');
10 | var isSchema = false;
11 |
12 | module.exports =
13 | {
14 | isRemote: false,
15 | isLockScreen: false,
16 |
17 | loadSchema: function(customName, customPath)
18 | {
19 | schemaName = customName || schemaName;
20 | schemaDir = customPath || schemaDir;
21 |
22 | isSchema = fs.existsSync(`${schemaDir}/gschemas.compiled`);
23 | debug(`Settings schema available: ${isSchema}`);
24 | },
25 |
26 | setSetting: function(setting, value, cb)
27 | {
28 | cb = cb || noop;
29 |
30 | var args = ['set', schemaName, setting, value];
31 | if(isSchema) args.unshift('--schemadir', schemaDir);
32 |
33 | debug(`Set ${setting}: ${value}`);
34 | var gProcess = spawn('gsettings', args);
35 | gProcess.once('exit', cb);
36 | },
37 |
38 | getSetting: function(setting)
39 | {
40 | var args = ['get', schemaName, setting];
41 | if(isSchema) args.unshift('--schemadir', schemaDir);
42 |
43 | var gsettings = spawnSync('gsettings', args);
44 | var value = String(gsettings.stdout).replace(/\n/, '').replace(/\'/g, '');
45 | debug(`Get ${setting}: ${value}`);
46 |
47 | return value;
48 | },
49 |
50 | getBoolean: function(setting)
51 | {
52 | var value = this.getSetting(setting);
53 | return (value === 'true' || value === true);
54 | },
55 |
56 | getJSON: function(setting)
57 | {
58 | var value = this.getSetting(setting);
59 | return JSON.parse(value);
60 | },
61 |
62 | showRemote: function(enable, playbackData, cb)
63 | {
64 | cb = cb || noop;
65 | this.isRemote = enable;
66 |
67 | if(this.isLockScreen)
68 | return cb(null);
69 |
70 | debug(`Show remote widget: ${enable}`);
71 |
72 | var data = { isPlaying: enable };
73 |
74 | if(playbackData)
75 | data = { ...playbackData, ...data };
76 |
77 | sender.sendPlaybackData(data, cb);
78 | },
79 |
80 | getTempConfig: function()
81 | {
82 | var config = {
83 | ffmpegPath: this.getSetting('ffmpeg-path'),
84 | ffprobePath: this.getSetting('ffprobe-path'),
85 | receiverType: this.getSetting('receiver-type'),
86 | listeningPort: this.getSetting('listening-port'),
87 | internalPort: this.getSetting('internal-port'),
88 | webplayerSubs: parseFloat(this.getSetting('webplayer-subs')).toFixed(1),
89 | videoBitrate: parseFloat(this.getSetting('video-bitrate')).toFixed(1),
90 | videoAcceleration: this.getSetting('video-acceleration'),
91 | burnSubtitles: this.getBoolean('burn-subtitles'),
92 | musicVisualizer: this.getBoolean('music-visualizer'),
93 | slideshowTime: this.getSetting('slideshow-time'),
94 | extractorReuse: this.getBoolean('extractor-reuse'),
95 | subsPreferred: this.getSetting('subs-preferred'),
96 | subsFallback: this.getSetting('subs-fallback'),
97 | extractorDir: this.getSetting('extractor-dir'),
98 | chromecastName: this.getSetting('chromecast-name'),
99 | playercastName: this.getSetting('playercast-name'),
100 | chromecastDevices: this.getJSON('chromecast-devices'),
101 | chromecastSubtitles: this.getJSON('chromecast-subtitles')
102 | };
103 |
104 | /* Use default paths if custom paths are not defined */
105 | if(!config.ffmpegPath) config.ffmpegPath = '/usr/bin/ffmpeg';
106 | if(!config.ffprobePath) config.ffprobePath = '/usr/bin/ffprobe';
107 |
108 | if(!config.subsPreferred)
109 | config.subsPreferred = 'eng/English';
110 |
111 | return config;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/node_scripts/messages.js:
--------------------------------------------------------------------------------
1 | function _(text) { return text; }
2 |
3 | module.exports = {
4 | loading: _("LOADING"),
5 | noMedia: _("No file selected"),
6 | receiverChromecast: _("Receiver type is set to Chromecast"),
7 | receiverPlayercast: _("Receiver type is set to Playercast app"),
8 | streamActive: _("Streaming process is still active"),
9 | /* TRANSLATORS: Do not remove HTML tags */
10 | connectLimit: _("Too many connections!Close all other tabs that are accessing this page in all browsers
"),
11 | /* TRANSLATORS: This sentence will contain file path after end */
12 | ffmpegError: _("FFmpeg could not transcode file:"),
13 | ffmpegPath: _("FFmpeg path is incorrect"),
14 | /* TRANSLATORS: This sentence will contain file path after end */
15 | ffprobeError: _("FFprobe could not process file:"),
16 | ffprobePath: _("FFprobe path is incorrect"),
17 | /* TRANSLATORS: This sentence will contain file path after end */
18 | extractError: _("Could not analyze selected file:"),
19 | plyr: {
20 | speed: _("Speed"),
21 | /* TRANSLATORS: One of "Speed" setting value */
22 | normal: _("Normal")
23 | },
24 | chromecast: {
25 | notFound: _("Device not found"),
26 | loadFailed: _("Failed to load media"),
27 | connectFailed: _("Could not connect to device"),
28 | verifyIp: _("Verify device IP in extension preferences"),
29 | /* TRANSLATORS: This sentence will contain file path after end */
30 | playError: _("Could not play file:"),
31 | tryAgain: _("Try again with transcoding enabled")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/node_scripts/notify.js:
--------------------------------------------------------------------------------
1 | const gettext = require('./gettext');
2 | const { spawn } = require('child_process');
3 |
4 | const sysLang = process.env.LANG.substring(0, 2);
5 |
6 | module.exports = function(summary, mainBody, data, infoBody)
7 | {
8 | if(!summary || !mainBody) return;
9 |
10 | gettext.setLocale(sysLang);
11 | mainBody = gettext.translate(mainBody);
12 |
13 | if(data && typeof data === 'string')
14 | mainBody += ` ${data}`;
15 |
16 | if(infoBody && typeof infoBody === 'string')
17 | mainBody += '.\n' + gettext.translate(infoBody) + '.';
18 |
19 | spawn('notify-send', [
20 | '-i', 'tv-symbolic',
21 | '-u', 'normal',
22 | summary, mainBody
23 | ]);
24 |
25 | console.log(summary + ': ' + mainBody);
26 | }
27 |
--------------------------------------------------------------------------------
/node_scripts/playercast.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('playercast');
2 | const scanner = require('multicast-scanner');
3 | const internalIp = require('internal-ip').v4;
4 | const bridge = require('./bridge');
5 | const socket = require('./server-socket');
6 | const sender = require('./sender');
7 | const events = require('./events');
8 |
9 | var connectTimeout;
10 |
11 | exports.cast = function()
12 | {
13 | var recName = null;
14 | var foundDev = null;
15 |
16 | if(bridge.config.playercastName)
17 | {
18 | var isConnected = socket.playercasts.some(dev => dev === bridge.config.playercastName);
19 |
20 | if(isConnected)
21 | {
22 | recName = bridge.config.playercastName;
23 | debug(`Cast to already connected playercast: ${recName}`);
24 | }
25 | }
26 |
27 | if(!bridge.config.playercastName && socket.playercasts.length)
28 | {
29 | recName = socket.playercasts[0];
30 | debug(`Cast to first playercast: ${recName}`);
31 | }
32 |
33 | if(recName)
34 | return emitCast(recName);
35 |
36 | recName = bridge.config.playercastName || null;
37 | debug(`Searching for ${recName || 'any Playercast'}...`);
38 |
39 | findReceiver(recName, (err, device) =>
40 | {
41 | if(err) return debug(err);
42 |
43 | debug('Playercast found');
44 | connectClient(device);
45 | });
46 | }
47 |
48 | function emitCast(receiverName)
49 | {
50 | socket.emit('playercast', {
51 | name: receiverName,
52 | mediaData: bridge.mediaData,
53 | ...bridge.selection
54 | });
55 | }
56 |
57 | function findReceiver(receiverName, cb)
58 | {
59 | const opts = {
60 | friendly_name: receiverName,
61 | service_name: '_playercast._tcp.local'
62 | };
63 |
64 | scanner(opts, cb);
65 | }
66 |
67 | function connectClient(device)
68 | {
69 | const reqOpts = {
70 | hostname: device.ip,
71 | port: device.port || 9881
72 | };
73 |
74 | debug('Checking sender IP...');
75 |
76 | internalIp().then(address =>
77 | {
78 | if(!address)
79 | return debug(new Error('Local IP undetected'));
80 |
81 | debug(`Sender IP: ${address}`);
82 |
83 | const reqData = {
84 | hostname: address,
85 | port: bridge.config.listeningPort
86 | };
87 |
88 | debug('Sending connection request...');
89 | sender.sendPlayercastRequest(reqOpts, reqData, (err) =>
90 | {
91 | if(err) return debug(err);
92 |
93 | debug('Send connection request');
94 |
95 | events.on('playercast-added', onPlayercastAdded);
96 | setConnectTimeout();
97 | });
98 | });
99 | }
100 |
101 | function setConnectTimeout()
102 | {
103 | if(connectTimeout) return;
104 |
105 | connectTimeout = setTimeout(() =>
106 | {
107 | connectTimeout = null;
108 |
109 | debug('Playercast connection timeout');
110 | events.removeListener('playercast-added', onPlayercastAdded);
111 | }, 7000);
112 |
113 | debug('Started connection timeout');
114 | }
115 |
116 | function clearConnectTimeout()
117 | {
118 | if(!connectTimeout) return;
119 |
120 | clearTimeout(connectTimeout);
121 | connectTimeout = null;
122 |
123 | debug('Removed connection timeout');
124 | }
125 |
126 | function onPlayercastAdded(addedName)
127 | {
128 | debug('New Playercast added');
129 |
130 | if(bridge.config.playercastName)
131 | {
132 | if(addedName !== bridge.config.playercastName)
133 | return debug('Playercast name mismatch');
134 | }
135 |
136 | clearConnectTimeout();
137 | events.removeListener('playercast-added', onPlayercastAdded);
138 |
139 | emitCast(addedName);
140 | }
141 |
--------------------------------------------------------------------------------
/node_scripts/remote-controller.js:
--------------------------------------------------------------------------------
1 | const bridge = require('./bridge');
2 | const socket = require('./server-socket');
3 | const gnome = require('./gnome');
4 |
5 | exports.repeat = false;
6 | exports.slideshow = false;
7 |
8 | var slideshowTimeout;
9 |
10 | exports.webControl = function(action, value)
11 | {
12 | var currentTrackID;
13 | var listLastID;
14 |
15 | switch(action)
16 | {
17 | case 'SKIP+':
18 | currentTrackID = bridge.playlist.indexOf(bridge.selection.filePath) + 1;
19 | listLastID = bridge.playlist.length;
20 | if(currentTrackID < listLastID) exports.changeTrack(currentTrackID + 1);
21 | break;
22 | case 'SKIP-':
23 | currentTrackID = bridge.playlist.indexOf(bridge.selection.filePath) + 1;
24 | if(currentTrackID > 1) exports.changeTrack(currentTrackID - 1);
25 | break;
26 | case 'REPEAT':
27 | exports.repeat = value;
28 | break;
29 | case 'SLIDESHOW':
30 | exports.slideshow = value;
31 | if(exports.slideshow)
32 | exports.setSlideshow();
33 | else
34 | exports.clearSlideshow();
35 | break;
36 | default:
37 | if(typeof value !== 'undefined')
38 | socket.emit('remote-signal', { action, value });
39 | else
40 | socket.emit('remote-signal', { action });
41 | break;
42 | }
43 | }
44 |
45 | exports.changeTrack = function(id)
46 | {
47 | /* Tracks are counted from 1, list indexes from 0 */
48 | bridge.selection.filePath = bridge.playlist[id - 1];
49 | bridge.selection.subsPath = "";
50 |
51 | bridge.updateSelection(bridge.selection);
52 | }
53 |
54 | exports.checkNextTrack = function()
55 | {
56 | var currentTrackID = bridge.playlist.indexOf(bridge.selection.filePath) + 1;
57 | var listLastID = bridge.playlist.length;
58 |
59 | if(exports.repeat && currentTrackID === listLastID)
60 | {
61 | exports.changeTrack(1);
62 | return true;
63 | }
64 | else if(currentTrackID < listLastID)
65 | {
66 | exports.changeTrack(currentTrackID + 1);
67 | return true;
68 | }
69 |
70 | return false;
71 | }
72 |
73 | exports.clearSlideshow = function()
74 | {
75 | if(!slideshowTimeout)
76 | return false;
77 |
78 | clearTimeout(slideshowTimeout);
79 | slideshowTimeout = null;
80 |
81 | return true;
82 | }
83 |
84 | exports.setSlideshow = function()
85 | {
86 | exports.clearSlideshow();
87 |
88 | if(exports.slideshow && bridge.selection.streamType === 'PICTURE')
89 | {
90 | var time = bridge.config.slideshowTime * 1000;
91 |
92 | slideshowTimeout = setTimeout(() =>
93 | {
94 | slideshowTimeout = null;
95 | var trackChanged = exports.checkNextTrack();
96 |
97 | if(!trackChanged)
98 | bridge.handleRemoteSignal('STOP');
99 | }, time);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/node_scripts/remove.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const debug = require('debug')('remove');
3 | const shared = require('../shared');
4 | const noop = () => {};
5 |
6 | exports.file = function(filePath, cb)
7 | {
8 | cb = cb || noop;
9 |
10 | debug(`Removing ${filePath}...`);
11 | fs.access(filePath, fs.constants.F_OK, (err) =>
12 | {
13 | if(err)
14 | {
15 | var noFileMsg = `File ${filePath} does not exist`;
16 | debug(noFileMsg);
17 |
18 | return cb(new Error(noFileMsg));
19 | }
20 |
21 | fs.unlink(filePath, (err) =>
22 | {
23 | if(!err)
24 | {
25 | debug(`Removed ${filePath}`);
26 | return cb(null);
27 | }
28 |
29 | var noUnlinkMsg = `Could not remove file: ${filePath}`;
30 | debug(noUnlinkMsg);
31 |
32 | return cb(new Error(noUnlinkMsg));
33 | });
34 | });
35 | }
36 |
37 | exports.tempCover = function(cb)
38 | {
39 | cb = cb || noop;
40 |
41 | exports.file(shared.coverDefault + '.jpg', cb);
42 | }
43 |
44 | exports.tempSubs = function(cb)
45 | {
46 | cb = cb || noop;
47 |
48 | exports.file(shared.vttSubsPath, cb);
49 | }
50 |
--------------------------------------------------------------------------------
/node_scripts/sender.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const debug = require('debug')('sender');
3 | const noop = () => {};
4 |
5 | var lastData;
6 |
7 | module.exports =
8 | {
9 | enabled: false,
10 |
11 | configure: function(port)
12 | {
13 | this.opts = {
14 | host: '127.0.0.1',
15 | port: port,
16 | path: '/',
17 | method: 'POST',
18 | headers: {
19 | 'Content-Type': 'application/json'
20 | },
21 | timeout: 3000
22 | };
23 |
24 | debug(`Sender configured to port: ${this.opts.port}`);
25 | },
26 |
27 | _httpRequest: function(opts, data, cb)
28 | {
29 | var req = http.request(opts, () =>
30 | {
31 | debug('Received response');
32 | req.removeListener('error', debug);
33 | });
34 | req.on('error', debug);
35 | req.end(data, cb);
36 | },
37 |
38 | send: function(type, data, cb)
39 | {
40 | cb = cb || noop;
41 |
42 | if(!this.enabled)
43 | return cb(null);
44 |
45 | if(!this.opts)
46 | return cb(new Error('Sender not configured'));
47 |
48 | var dataString = JSON.stringify(data);
49 |
50 | /* Do not send same data */
51 | if(dataString === lastData)
52 | return cb(null);
53 |
54 | lastData = dataString;
55 | this.opts.path = '/api/' + type;
56 |
57 | this._httpRequest(this.opts, dataString, cb);
58 | debug(`Send data: ${dataString}`);
59 | },
60 |
61 | sendPlaybackStatus: function(status, cb)
62 | {
63 | this.send('status', status, cb);
64 | },
65 |
66 | sendPlaybackData: function(data, cb)
67 | {
68 | this.send('data', data, cb);
69 | },
70 |
71 | sendBrowserName: function(name, cb)
72 | {
73 | this.send('browser', { name: name }, cb);
74 | },
75 |
76 | sendPlayercastRequest: function(opts, data, cb)
77 | {
78 | const reqOpts = {
79 | host: opts.hostname || '127.0.0.1',
80 | port: opts.port || 9881,
81 | path: opts.path || '/api/connect',
82 | method: opts.method || 'POST',
83 | timeout: 3000,
84 | headers: {
85 | 'Content-Type': 'application/json'
86 | }
87 | };
88 |
89 | var dataString = JSON.stringify(data);
90 | this._httpRequest(reqOpts, dataString, cb);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/node_scripts/server-socket.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const io = require('socket.io');
3 | const WebSocket = require('ws');
4 | const extract = require('ffmpeg-extract');
5 | const debug = require('debug')('socket');
6 | const bridge = require('./bridge');
7 | const encode = require('./encode');
8 | const gettext = require('./gettext');
9 | const messages = require('./messages');
10 | const gnome = require('./gnome');
11 | const controller = require('./remote-controller');
12 | const sender = require('./sender');
13 | const events = require('./events');
14 | const shared = require('../shared');
15 |
16 | var clientTimeout;
17 | var reconnectTimeout;
18 | var websocket;
19 | var wsConnected;
20 |
21 | exports.activeConnections = 0;
22 | exports.playercasts = [];
23 |
24 | exports.listen = function(server)
25 | {
26 | websocket = io.listen(server);
27 | websocket.on('connection', handleMessages);
28 | }
29 |
30 | exports.emit = function(message, opts)
31 | {
32 | websocket.emit(message, opts);
33 | }
34 |
35 | exports.connectWs = function(port)
36 | {
37 | if(gnome.isLockScreen || wsConnected)
38 | return;
39 |
40 | if(reconnectTimeout)
41 | {
42 | clearTimeout(reconnectTimeout);
43 | reconnectTimeout = null;
44 | }
45 |
46 | port = port || bridge.config.internalPort;
47 |
48 | debug(`Connecting to GNOME websocket on port: ${port}`);
49 | var ws = new WebSocket(`ws://127.0.0.1:${port}/websocket/node`);
50 |
51 | const onConnOpen = function()
52 | {
53 | debug('GNOME websocket connected');
54 | ws.send('connected');
55 | wsConnected = true;
56 | sender.enabled = true;
57 | }
58 |
59 | const onConnClose = function()
60 | {
61 | debug('GNOME websocket disconnected');
62 | ws.removeAllListeners();
63 | wsConnected = false;
64 | sender.enabled = false;
65 |
66 | if(!gnome.isLockScreen)
67 | {
68 | if(reconnectTimeout)
69 | clearTimeout(reconnectTimeout);
70 |
71 | reconnectTimeout = setTimeout(() =>
72 | {
73 | reconnectTimeout = null;
74 | exports.connectWs(bridge.config.internalPort);
75 | }, 4250);
76 | }
77 | }
78 |
79 | ws.once('open', onConnOpen);
80 | ws.once('close', onConnClose);
81 | ws.once('error', onConnClose);
82 | }
83 |
84 | function handleMessages(socket)
85 | {
86 | exports.activeConnections++;
87 |
88 | if(clientTimeout)
89 | {
90 | clearTimeout(clientTimeout);
91 | clientTimeout = null;
92 | }
93 |
94 | socket.on('webplayer', msg =>
95 | {
96 | switch(msg)
97 | {
98 | case 'webplayer-ask':
99 | initWebPlayer();
100 | controller.setSlideshow();
101 | break;
102 | case 'track-ended':
103 | controller.checkNextTrack();
104 | break;
105 | case 'processes-ask':
106 | if(!extract.video.subsProcess && !extract.music.coverProcess)
107 | websocket.emit('processes-done', true);
108 | else
109 | websocket.emit('processes-done', false);
110 | break;
111 | case 'loading-ask':
112 | websocket.emit('loading-text', gettext.translate(messages.loading));
113 | break;
114 | case 'message-ask':
115 | sendMessage();
116 | break;
117 | default:
118 | break;
119 | }
120 | });
121 |
122 | socket.on('playercast-connect', msg =>
123 | {
124 | socket.playercastName = msg;
125 |
126 | if(exports.activeConnections > 0)
127 | exports.activeConnections--;
128 |
129 | if(!exports.playercasts.includes(socket.playercastName))
130 | {
131 | exports.playercasts.push(socket.playercastName);
132 | socket.emit('invalid', false);
133 | events.emit('playercast-added', socket.playercastName);
134 | }
135 | else
136 | {
137 | socket.playercastInvalid = true;
138 | socket.emit('invalid', 'name');
139 | }
140 | });
141 |
142 | socket.on('playercast-ctl', msg =>
143 | {
144 | switch(msg)
145 | {
146 | case 'track-ended':
147 | if(!controller.checkNextTrack())
148 | controller.webControl('STOP');
149 | break;
150 | case 'previous-track':
151 | controller.webControl('SKIP-');
152 | break;
153 | case 'next-track':
154 | controller.webControl('SKIP+');
155 | break;
156 | default:
157 | break;
158 | }
159 | });
160 |
161 | socket.on('status-update', bridge.setGnomeStatus);
162 | socket.on('show-remote', msg =>
163 | {
164 | if(msg)
165 | controller.setSlideshow();
166 | else
167 | {
168 | controller.clearSlideshow();
169 | controller.slideshow = false;
170 | }
171 |
172 | bridge.setGnomeRemote(msg)
173 | });
174 |
175 | socket.on('disconnect', () =>
176 | {
177 | if(!socket.playercastName)
178 | return checkClients();
179 |
180 | if(
181 | socket.playercastInvalid
182 | || !exports.playercasts.includes(socket.playercastName)
183 | )
184 | return;
185 |
186 | var index = exports.playercasts.indexOf(socket.playercastName);
187 | exports.playercasts.splice(index, 1);
188 | });
189 | }
190 |
191 | function initWebPlayer()
192 | {
193 | var initType = (
194 | bridge.selection.streamType === 'MUSIC'
195 | && !bridge.config.musicVisualizer
196 | ) ? 'MUSIC' : 'VIDEO';
197 |
198 | var isSub = (
199 | bridge.selection.streamType !== 'MUSIC'
200 | && bridge.selection.streamType !== 'PICTURE'
201 | && (bridge.selection.subsPath || bridge.selection.subsSrc)
202 | && (bridge.selection.streamType === 'VIDEO'
203 | || bridge.selection.streamType === 'VIDEO_AENC'
204 | || !bridge.config.burnSubtitles)
205 | );
206 |
207 | var webData = {
208 | type: initType,
209 | subs: isSub,
210 | i18n: {
211 | speed: gettext.translate(messages.plyr.speed),
212 | normal: gettext.translate(messages.plyr.normal)
213 | }
214 | }
215 |
216 | websocket.emit('webplayer-init', webData);
217 |
218 | if(!gnome.isRemote)
219 | bridge.setGnomeRemote(true);
220 | }
221 |
222 | function checkClients()
223 | {
224 | if(exports.activeConnections > 0)
225 | exports.activeConnections--;
226 |
227 | clientTimeout = setTimeout(() =>
228 | {
229 | clientTimeout = null;
230 |
231 | if(exports.activeConnections == 0)
232 | {
233 | controller.clearSlideshow();
234 | controller.slideshow = false;
235 | bridge.setGnomeRemote(false);
236 | }
237 | }, 2500);
238 | }
239 |
240 | function sendMessage()
241 | {
242 | if(bridge.config.receiverType == 'chromecast')
243 | websocket.emit('message-refresh', gettext.translate(messages.receiverChromecast));
244 | else if(bridge.config.receiverType == 'playercast')
245 | websocket.emit('message-refresh', gettext.translate(messages.receiverPlayercast));
246 | else if(!bridge.selection.filePath)
247 | websocket.emit('message-refresh', gettext.translate(messages.noMedia));
248 | else if(encode.streamProcess)
249 | websocket.emit('message-refresh', gettext.translate(messages.streamActive));
250 | else if(exports.activeConnections > 1)
251 | websocket.emit('message-refresh', gettext.translate(messages.connectLimit));
252 | else if(exports.activeConnections == 1)
253 | exports.activeConnections--;
254 | else
255 | websocket.emit('message-clear');
256 | }
257 |
--------------------------------------------------------------------------------
/node_scripts/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const bowser = require('bowser');
4 | const path = require('path');
5 | const extract = require('ffmpeg-extract');
6 | const bridge = require('./bridge');
7 | const webcreator = require('./web-creator');
8 | const sender = require('./sender');
9 | const socket = require('./server-socket');
10 | const encode = require('./encode');
11 | const gettext = require('./gettext');
12 |
13 | const app = express();
14 |
15 | var userAgent = null;
16 | var server = app.listen(bridge.config.listeningPort, () =>
17 | {
18 | gettext.initTranslations();
19 | sender.configure(bridge.config.internalPort);
20 | socket.listen(server);
21 |
22 | bridge.createTempDir(() => socket.connectWs());
23 | });
24 |
25 | app.use(bodyParser.json());
26 |
27 | exports.changePort = function(port)
28 | {
29 | server.close();
30 |
31 | server = app.listen(port);
32 | socket.listen(server);
33 | }
34 |
35 | function checkMessagePage(req, res)
36 | {
37 | if(
38 | bridge.config.receiverType != 'other'
39 | || !bridge.selection.filePath
40 | || encode.streamProcess
41 | || socket.activeConnections > 0
42 | ) {
43 | res.sendFile(path.join(__dirname + '/../webplayer/message.html'));
44 | return true;
45 | }
46 |
47 | if(extract.video.subsProcess || extract.music.coverProcess)
48 | {
49 | res.sendFile(path.join(__dirname + '/../webplayer/loading.html'));
50 | return true;
51 | }
52 |
53 | return false;
54 | }
55 |
56 | function getBrowserName()
57 | {
58 | if(!userAgent)
59 | return null;
60 |
61 | var parsedAgent = bowser.parse(userAgent);
62 |
63 | if(parsedAgent && parsedAgent.browser && parsedAgent.browser.name)
64 | return parsedAgent.browser.name;
65 |
66 | return null;
67 | }
68 |
69 | app.get('/', function(req, res)
70 | {
71 | var lang = req.acceptsLanguages.apply(req, gettext.locales);
72 |
73 | if(lang) gettext.setLocale(lang);
74 | else gettext.setLocale('en');
75 |
76 | var isMessage = checkMessagePage(req, res);
77 | if(isMessage) return;
78 |
79 | if(
80 | bridge.config.receiverType === 'other'
81 | && userAgent !== req.headers['user-agent']
82 | ) {
83 | userAgent = req.headers['user-agent'];
84 | sender.sendBrowserName(getBrowserName());
85 | }
86 |
87 | switch(bridge.selection.streamType)
88 | {
89 | case 'VIDEO':
90 | res.sendFile(path.join(__dirname + '/../webplayer/webplayer_direct.html'));
91 | break;
92 | case 'MUSIC':
93 | if(bridge.config.musicVisualizer)
94 | res.sendFile(path.join(__dirname + '/../webplayer/webplayer_encode.html'));
95 | else
96 | res.sendFile(path.join(__dirname + '/../webplayer/webplayer_direct.html'));
97 | break;
98 | case 'PICTURE':
99 | res.sendFile(path.join(__dirname + '/../webplayer/picture.html'));
100 | break;
101 | default:
102 | res.sendFile(path.join(__dirname + '/../webplayer/webplayer_encode.html'));
103 | }
104 | });
105 |
106 | app.get('/cast', function(req, res)
107 | {
108 | if(bridge.selection.addon)
109 | {
110 | /* Send to add-on if available, otherwise ignore request */
111 | if(bridge.addon)
112 | bridge.addon.fileStream(req, res, bridge.selection, bridge.config);
113 |
114 | return;
115 | }
116 |
117 | switch(bridge.selection.streamType)
118 | {
119 | case 'MUSIC':
120 | if(bridge.config.musicVisualizer)
121 | webcreator.encodedStream(req, res);
122 | else
123 | webcreator.fileStream(req, res);
124 | break;
125 | case 'VIDEO':
126 | case 'PICTURE':
127 | webcreator.fileStream(req, res);
128 | break;
129 | default:
130 | webcreator.encodedStream(req, res);
131 | break;
132 | }
133 | });
134 |
135 | app.get('/subs(webplayer)?', function(req, res)
136 | {
137 | if(bridge.selection.addon && bridge.selection.subsSrc)
138 | bridge.addon.subsStream(req, res, bridge.selection, bridge.config);
139 | else
140 | webcreator.subsStream(req, res);
141 | });
142 |
143 | app.get('/cover', function(req, res)
144 | {
145 | if(bridge.selection.addon && bridge.selection.coverSrc)
146 | bridge.addon.coverStream(req, res, bridge.selection, bridge.config);
147 | else
148 | webcreator.coverStream(req, res);
149 | });
150 |
151 | app.get('/webplayer/webconfig.css', function(req, res)
152 | {
153 | webcreator.webConfig(req, res);
154 | });
155 |
156 | app.get('/api/*', function(req, res)
157 | {
158 | if(req.params[0] === 'browser')
159 | res.send({ name: getBrowserName() });
160 | else
161 | webcreator.apiGet(req.params[0], req, res);
162 | });
163 |
164 | app.post('/api/*', function(req, res)
165 | {
166 | webcreator.apiPost(req.params[0], req, res);
167 | });
168 |
169 | app.get('/segment*', function(req, res)
170 | {
171 | webcreator.hlsStream(req, res);
172 | });
173 |
174 | app.use('/webplayer', express.static(__dirname + '/../webplayer'));
175 | app.use('/plyr', express.static(__dirname + '/../node_modules/plyr/dist'));
176 |
177 | app.get('/*', function(req, res)
178 | {
179 | res.redirect('/');
180 | });
181 |
--------------------------------------------------------------------------------
/node_scripts/utils/install.js:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | //bin/false || exec "$(command -v nodejs || command -v node)" "$0"
3 |
4 | const fs = require('fs');
5 | const path = require('path');
6 | const { execSync } = require('child_process');
7 |
8 | const extensionsPath = path.join(__dirname + '/../../..');
9 |
10 | /* Add-ons should be installed synchronously */
11 | var extensions = fs.readdirSync(extensionsPath);
12 |
13 | extensions.forEach(folder =>
14 | {
15 | if(folder.startsWith('cast-to-tv') && folder.includes('addon@'))
16 | {
17 | var addonFolder = path.join(extensionsPath, folder);
18 | var addonName = folder.substring(11, folder.lastIndexOf('-'));
19 | var isPackage = fs.existsSync(addonFolder + '/package.json');
20 |
21 | if(isPackage)
22 | {
23 | var addonText = addonName[0].toUpperCase() + addonName.slice(1);
24 | var installText = `Installing: Cast to TV - ${addonText} Add-on`;
25 | var textLength = installText.length;
26 |
27 | while(textLength)
28 | {
29 | process.stdout.write('-');
30 | textLength--;
31 | }
32 |
33 | process.stdout.write('\n');
34 | console.log(installText);
35 | execSync('npm install', { cwd: addonFolder, stdio: 'inherit' });
36 | process.stdout.write('\n');
37 | }
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/node_scripts/utils/local-ip.js:
--------------------------------------------------------------------------------
1 | const internalIp = require('internal-ip').v4;
2 | const ip = internalIp.sync();
3 | if(ip) console.log(ip);
4 |
--------------------------------------------------------------------------------
/node_scripts/utils/scanner.js:
--------------------------------------------------------------------------------
1 | const scanner = require('multicast-scanner');
2 | const gnome = require('../gnome');
3 | const SERVICE = (process.argv[2]) ? process.argv[2] : 'googlecast';
4 |
5 | var opts = {
6 | service_name: `_${SERVICE}._tcp.local`,
7 | full_scan: true
8 | };
9 |
10 | scanner(opts, (err, devices) =>
11 | {
12 | var results = [];
13 |
14 | if(!err)
15 | {
16 | devices.forEach(device =>
17 | {
18 | results.push({
19 | name: device.name,
20 | friendlyName: device.friendlyName,
21 | ip: ''
22 | });
23 | });
24 | }
25 |
26 | const devName = (SERVICE !== 'googlecast') ? SERVICE : 'chromecast';
27 | gnome.setSetting(`${devName}-devices`, JSON.stringify(results));
28 | });
29 |
30 | gnome.loadSchema();
31 |
--------------------------------------------------------------------------------
/node_scripts/utils/vttextract.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const extract = require('ffmpeg-extract');
6 | const remove = require('../remove');
7 | const gnome = require('../gnome');
8 | const metadata = require('../../metadata');
9 |
10 | var opts = {
11 | ffprobePath: null,
12 | inPath: null,
13 | outDir: null,
14 | quiet: false,
15 | recursive: false
16 | };
17 |
18 | var config = {};
19 | var currOutPath = null;
20 | var filesCount = 0;
21 |
22 | function showHelp()
23 | {
24 | const version = (metadata.git) ? `git-${metadata.git}` : `v${metadata.version}`;
25 |
26 | console.log([
27 | ``,
28 | `vttextract - Cast to TV ${version} subtitles extractor`,
29 | ``,
30 | `Usage: vttextract [OPTIONS]`,
31 | ``,
32 | ` source - can be path to single video file or dir with videos`,
33 | ``,
34 | `OPTIONS:`,
35 | ` -q, --quiet Do not print extraction info except errors`,
36 | ` -r, --recursive Extract from files in subdirectories`,
37 | ``
38 | ].join('\n'));
39 | }
40 |
41 | function logInfo(text)
42 | {
43 | if(!opts.quiet)
44 | console.log(text);
45 | }
46 |
47 | function writeProgress(fileName, mark, isEndLine)
48 | {
49 | mark = mark || '\u2B58';
50 |
51 | process.stdout.cursorTo(0);
52 | process.stdout.clearLine(0);
53 | process.stdout.write(`${mark} ${fileName}`);
54 |
55 | if(isEndLine)
56 | process.stdout.write('\n');
57 | }
58 |
59 | function parseArgs()
60 | {
61 | for(var i = 2; i < process.argv.length; i++)
62 | {
63 | switch(process.argv[i])
64 | {
65 | case '-q':
66 | case '--quiet':
67 | opts.quiet = true;
68 | break;
69 | case '-r':
70 | case '--recursive':
71 | opts.recursive = true;
72 | break;
73 | case '-h':
74 | case '--help':
75 | return false;
76 | default:
77 | if(!opts.inPath)
78 | opts.inPath = process.argv[i];
79 | else
80 | return false;
81 | break;
82 | }
83 | }
84 |
85 | return (opts.inPath !== null);
86 | }
87 |
88 | function extractFromDir(dirPath)
89 | {
90 | logInfo(`Browsing dir: "${dirPath}"`);
91 |
92 | return new Promise((resolve, reject) =>
93 | {
94 | fs.readdir(dirPath, async (err, files) =>
95 | {
96 | if(err) return reject(err);
97 |
98 | for(var fileName of files)
99 | {
100 | var runPath = path.join(dirPath, fileName);
101 | await runInPath(runPath, opts.recursive).catch(onReject);
102 | }
103 |
104 | resolve();
105 | });
106 | });
107 | }
108 |
109 | function extractFromFile(filePath)
110 | {
111 | return new Promise((resolve, reject) =>
112 | {
113 | var ffprobeOpts = {
114 | ffprobePath : opts.ffprobePath,
115 | filePath: filePath
116 | };
117 |
118 | extract.analyzeFile(ffprobeOpts, (err, data) =>
119 | {
120 | if(err)
121 | {
122 | /* Returns process error when run on non-video file */
123 | if(err.message.includes('FFprobe process error'))
124 | return resolve();
125 | else
126 | return reject(err);
127 | }
128 |
129 | if(!extract.video.getIsSubsMerged(data))
130 | return resolve();
131 |
132 | var parsed = path.parse(filePath);
133 |
134 | /*
135 | Access is done here, so we disable second
136 | access test with overwrite set to true
137 | */
138 | var extOpts = {
139 | file: filePath,
140 | outPath: path.join(opts.outDir, parsed.name + '.vtt'),
141 | overwrite: true,
142 | vttparser: true
143 | };
144 |
145 | if(data.streams.length > 3)
146 | {
147 | extOpts.streamIndex = extract.video.getSubsTrackIndex(
148 | data, config.subsPreferred
149 | );
150 |
151 | if(!extOpts.streamIndex && config.subsFallback)
152 | {
153 | extOpts.streamIndex = extract.video.getSubsTrackIndex(
154 | data, config.subsFallback
155 | );
156 | }
157 | }
158 |
159 | fs.access(extOpts.outPath, fs.constants.F_OK, (err) =>
160 | {
161 | if(!err) return resolve();
162 |
163 | var fileName = parsed.name + parsed.ext;
164 |
165 | if(!opts.quiet)
166 | writeProgress(fileName);
167 |
168 | currOutPath = extOpts.outPath;
169 |
170 | extract.video.videoToVtt(extOpts, (err) =>
171 | {
172 | currOutPath = null;
173 |
174 | if(err)
175 | {
176 | writeProgress(fileName, '\u2716', true);
177 | return remove.file(extOpts.outPath, () => reject(err));
178 | }
179 |
180 | if(!opts.quiet)
181 | writeProgress(fileName, '\u2714', true);
182 |
183 | filesCount++;
184 | resolve();
185 | });
186 | });
187 | });
188 | });
189 | }
190 |
191 | function runInPath(runPath, isRecursive)
192 | {
193 | return new Promise((resolve, reject) =>
194 | {
195 | fs.access(runPath, fs.constants.F_OK, (err) =>
196 | {
197 | if(err) return reject(err);
198 |
199 | fs.stat(runPath, async (err, stat) =>
200 | {
201 | if(err) return reject(err);
202 |
203 | if(stat.isDirectory())
204 | {
205 | if(isRecursive)
206 | await extractFromDir(runPath).catch(onReject);
207 | }
208 | else if(stat.isFile())
209 | await extractFromFile(runPath).catch(onReject);
210 | else
211 | return reject(new Error(`Not a file or dir: ${runPath}`));
212 |
213 | resolve();
214 | });
215 | });
216 | });
217 | }
218 |
219 | function onReject(err)
220 | {
221 | console.error(err.message);
222 | }
223 |
224 | function onFinish(hideMsg)
225 | {
226 | if(hideMsg)
227 | console.log();
228 | else if(filesCount > 0)
229 | {
230 | var text = `Extracted subtitles from ${filesCount} file`;
231 |
232 | if(filesCount > 1)
233 | text += 's';
234 |
235 | logInfo(text);
236 | }
237 | else
238 | logInfo('No subtitles extracted');
239 |
240 | exitWithCode(0);
241 | }
242 |
243 | function onUncaughtException(err)
244 | {
245 | if(err)
246 | console.error(err.message);
247 |
248 | exitWithCode(1);
249 | }
250 |
251 | function exitWithCode(code)
252 | {
253 | if(currOutPath)
254 | remove.file(currOutPath, () => process.exit(code));
255 | else
256 | process.exit(code);
257 | }
258 |
259 | function startExtract()
260 | {
261 | const argsOk = parseArgs();
262 |
263 | if(!argsOk)
264 | return showHelp();
265 |
266 | gnome.loadSchema();
267 |
268 | if(!gnome.getBoolean('extractor-reuse'))
269 | return console.error('Reusing subtitles is disabled');
270 |
271 | opts.outDir = gnome.getSetting('extractor-dir');
272 |
273 | if(!opts.outDir)
274 | return console.error('Could not obtain save dir setting');
275 |
276 | opts.ffprobePath = gnome.getSetting('ffprobe-path') || '/usr/bin/ffprobe';
277 | config.subsPreferred = gnome.getSetting('subs-preferred') || 'eng/English';
278 | config.subsFallback = gnome.getSetting('subs-fallback');
279 |
280 | process.on('SIGINT', () => onFinish(true));
281 | process.on('SIGTERM', () => onFinish(true));
282 | process.on('uncaughtException', onUncaughtException);
283 |
284 | /* First run must be recursive to support starting in dir */
285 | runInPath(opts.inPath, true)
286 | .then(onFinish)
287 | .catch(onReject)
288 | }
289 |
290 | startExtract();
291 |
--------------------------------------------------------------------------------
/node_scripts/web-creator.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const debug = require('debug')('web-creator');
4 | const bridge = require('./bridge');
5 | const socket = require('./server-socket');
6 | const encode = require('./encode');
7 | const shared = require('../shared');
8 |
9 | exports.fileStream = function(req, res)
10 | {
11 | res.setHeader('Access-Control-Allow-Origin', '*');
12 |
13 | var streamType = bridge.selection.streamType;
14 | var filePath = bridge.selection.filePath;
15 |
16 | if(!filePath)
17 | {
18 | debug('No file path');
19 | return res.sendStatus(404);
20 | }
21 |
22 | /* Check if file exists */
23 | fs.access(filePath, fs.constants.F_OK, (err) =>
24 | {
25 | if(err)
26 | {
27 | debug(err);
28 | return res.sendStatus(404);
29 | }
30 |
31 | return res.sendFile(filePath);
32 | });
33 | }
34 |
35 | exports.encodedStream = function(req, res)
36 | {
37 | if(!encode.enabled)
38 | return res.sendStatus(204);
39 |
40 | var filePath = bridge.selection.filePath;
41 |
42 | if(!filePath)
43 | return res.sendStatus(404);
44 |
45 | /* Prevent spawning more then one ffmpeg encode process */
46 | if(encode.streamProcess)
47 | return res.sendStatus(429);
48 |
49 | var streamType = bridge.selection.streamType;
50 |
51 | /* Check if file exists */
52 | fs.access(filePath, fs.constants.F_OK, (err) =>
53 | {
54 | if(err) return res.sendStatus(404);
55 |
56 | res.writeHead(200, {
57 | 'Access-Control-Allow-Origin': '*',
58 | 'Connection': 'close',
59 | 'Content-Type': 'video/mp4'
60 | });
61 |
62 | switch(streamType)
63 | {
64 | case 'MUSIC':
65 | encode.musicVisualizer().pipe(res);
66 | break;
67 | case 'VIDEO_VENC':
68 | case 'VIDEO_VENC_AENC':
69 | case 'VIDEO_AENC_VENC':
70 | var audioEnc = streamType.includes('_AENC');
71 | switch(bridge.config.videoAcceleration)
72 | {
73 | case 'vaapi':
74 | encode.videoVaapi(audioEnc).pipe(res);
75 | break;
76 | case 'nvenc':
77 | encode.videoNvenc(audioEnc).pipe(res);
78 | break;
79 | case 'amf':
80 | encode.videoAmf(audioEnc).pipe(res);
81 | break;
82 | default:
83 | encode.video(audioEnc).pipe(res);
84 | break;
85 | }
86 | break;
87 | case 'VIDEO_AENC':
88 | encode.audio().pipe(res);
89 | break;
90 | default:
91 | encode.video().pipe(res);
92 | break;
93 | }
94 |
95 | res.once('close', encode.closeStreamProcess);
96 | });
97 | }
98 |
99 | exports.subsStream = function(req, res)
100 | {
101 | res.setHeader('Access-Control-Allow-Origin', '*');
102 |
103 | if(!bridge.selection.streamType.startsWith('VIDEO'))
104 | return res.sendStatus(204);
105 |
106 | var subsPath = bridge.selection.subsPath;
107 |
108 | /* Check if file is specified and exists */
109 | if(!subsPath)
110 | return res.sendStatus(204);
111 |
112 | if(
113 | req._parsedUrl.pathname === '/subswebplayer'
114 | && !subsPath.endsWith('.vtt')
115 | ) {
116 | return res.sendStatus(204);
117 | }
118 |
119 | fs.access(subsPath, fs.constants.F_OK, (err) =>
120 | {
121 | if(err) return res.sendStatus(404);
122 |
123 | return res.sendFile(subsPath);
124 | });
125 | }
126 |
127 | exports.coverStream = function(req, res)
128 | {
129 | res.setHeader('Access-Control-Allow-Origin', '*');
130 |
131 | if(bridge.selection.streamType !== 'MUSIC')
132 | return res.sendStatus(204);
133 |
134 | var coverPath = bridge.mediaData.coverPath;
135 |
136 | if(!coverPath)
137 | return res.sendStatus(204);
138 |
139 | fs.access(coverPath, fs.constants.F_OK, (err) =>
140 | {
141 | if(err) return res.sendStatus(404);
142 |
143 | return res.sendFile(coverPath);
144 | });
145 | }
146 |
147 | exports.hlsStream = function(req, res)
148 | {
149 | res.setHeader('Access-Control-Allow-Origin', '*');
150 |
151 | var filePath = shared.hlsDir + req.url;
152 |
153 | /* Check if stream segment exists */
154 | fs.access(filePath, fs.constants.F_OK, (err) =>
155 | {
156 | if(err)
157 | {
158 | debug(err);
159 | return res.sendStatus(404);
160 | }
161 |
162 | debug(`Send HLS segment: ${req.url}`);
163 | return res.sendFile(filePath);
164 | });
165 | }
166 |
167 | exports.webConfig = function(req, res)
168 | {
169 | res.setHeader('Access-Control-Allow-Origin', '*');
170 | res.setHeader('Content-Type', 'text/css');
171 |
172 | var size = bridge.config.webplayerSubs;
173 |
174 | var webConfig = `@media {
175 | .plyr__captions{font-size:${5*size}vmin}
176 | .plyr:-webkit-full-screen .plyr__captions{font-size:${5*size}vmin}
177 | .plyr:-moz-full-screen .plyr__captions{font-size:${5*size}vmin}
178 | .plyr:-ms-fullscreen .plyr__captions{font-size:${5*size}vmin}
179 | .plyr:fullscreen .plyr__captions{font-size:${5*size}vmin}\n}`
180 |
181 | res.send(webConfig);
182 | }
183 |
184 | exports.apiGet = function(type, req, res)
185 | {
186 | switch(type)
187 | {
188 | case 'config':
189 | case 'selection':
190 | case 'playlist':
191 | case 'status':
192 | res.send(bridge[type]);
193 | break;
194 | case 'playercasts':
195 | res.send(socket.playercasts);
196 | break;
197 | case 'playback-data':
198 | res.send(bridge.getPlaybackData());
199 | break;
200 | case 'remote-buttons':
201 | res.send(bridge.getRemoteButtons());
202 | break;
203 | case 'is-enabled':
204 | res.send({ isEnabled: true });
205 | break;
206 | case 'media-data':
207 | res.send(bridge.mediaData);
208 | break;
209 | default:
210 | res.sendStatus(404);
211 | break;
212 | }
213 | }
214 |
215 | exports.apiPost = function(type, req, res)
216 | {
217 | switch(type)
218 | {
219 | case 'config':
220 | bridge.updateConfig(req.body);
221 | res.sendStatus(200);
222 | break;
223 | case 'selection':
224 | bridge.updateSelection(req.body);
225 | res.sendStatus(200);
226 | break;
227 | case 'playlist':
228 | const append = (req.query && req.query.append === 'true');
229 | bridge.updatePlaylist(req.body, append);
230 | res.sendStatus(200);
231 | break;
232 | case 'remote':
233 | bridge.updateRemote(req.body);
234 | res.sendStatus(200);
235 | break;
236 | case 'playback-data':
237 | bridge.updatePlaylist(req.body.playlist, false);
238 | bridge.updateSelection(req.body.selection);
239 | res.sendStatus(200);
240 | break;
241 | case 'lock-screen':
242 | bridge.updateLockScreen(req.body);
243 | res.sendStatus(200);
244 | break;
245 | default:
246 | res.sendStatus(404);
247 | break;
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cast-to-tv",
3 | "description": "Cast files to Chromecast, web browser or media player app over local network.",
4 | "main": "node_scripts/server.js",
5 | "author": "Rafostar",
6 | "license": "GPL-2.0",
7 | "dependencies": {
8 | "body-parser": "^1.19.0",
9 | "bowser": "^2.9.0",
10 | "chromecast-controller": "^0.3.1",
11 | "debug": "^4.1.1",
12 | "express": "^4.17.1",
13 | "ffmpeg-extract": "^1.1.2",
14 | "gettext-parser": "^3.1.0",
15 | "internal-ip": "^4.3.0",
16 | "multicast-scanner": "^2.3.0",
17 | "node-gettext": "^2.0.0",
18 | "plyr": "3.5.3",
19 | "socket.io": "^2.3.0",
20 | "ws": "^7.2.1"
21 | },
22 | "scripts": {
23 | "install": "sh node_scripts/utils/install.js"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/Rafostar/gnome-shell-extension-cast-to-tv.git"
28 | },
29 | "keywords": [
30 | "gnome-shell-extension",
31 | "chromecast",
32 | "media",
33 | "video",
34 | "stream",
35 | "cast"
36 | ],
37 | "bugs": {
38 | "url": "https://github.com/Rafostar/gnome-shell-extension-cast-to-tv/issues"
39 | },
40 | "homepage": "https://github.com/Rafostar/gnome-shell-extension-cast-to-tv",
41 | "devDependencies": {
42 | "bufferutil": "^4.0.1",
43 | "utf-8-validate": "^5.0.2"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the Cast to TV - Links Addon package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: Cast to TV - Links Addon\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=CHARSET\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
21 | #: app.js:140
22 | msgid "No Image"
23 | msgstr ""
24 |
25 | #: app.js:152
26 | msgid "Title:"
27 | msgstr ""
28 |
29 | #: app.js:153
30 | msgid "Format:"
31 | msgstr ""
32 |
33 | #: app.js:154
34 | msgid "Resolution:"
35 | msgstr ""
36 |
37 | #: app.js:155
38 | msgid "Video codec:"
39 | msgstr ""
40 |
41 | #: app.js:156
42 | msgid "Audio codec:"
43 | msgstr ""
44 |
45 | #: app.js:157
46 | msgid "Subtitles:"
47 | msgstr ""
48 |
49 | #: app.js:173
50 | msgid "Enter or drop link here"
51 | msgstr ""
52 |
53 | #: app.js:182
54 | msgid "Cast link"
55 | msgstr ""
56 |
57 | #: widget.js:18
58 | msgid "Link"
59 | msgstr ""
60 |
61 | #: links_prefs.js:31
62 | msgid "Links"
63 | msgstr ""
64 |
65 | #: links_prefs.js:36
66 | msgid "Links Options"
67 | msgstr ""
68 |
69 | #: links_prefs.js:40
70 | msgid "Preferred format"
71 | msgstr ""
72 |
73 | #: links_prefs.js:42
74 | msgid "Best seekable"
75 | msgstr ""
76 |
77 | #: links_prefs.js:43
78 | msgid "Best quality"
79 | msgstr ""
80 |
81 | #: links_prefs.js:43
82 | msgid "(experimental)"
83 | msgstr ""
84 |
85 | #: links_prefs.js:49
86 | msgid "Path to youtube-dl"
87 | msgstr ""
88 |
89 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
90 | #: links_prefs.js:58
91 | msgid "Max video quality"
92 | msgstr ""
93 |
94 | #: links_prefs.js:76
95 | msgid "Allow VP9 codec"
96 | msgstr ""
97 |
98 | #: links_prefs.js:84
99 | msgid "Subtitles"
100 | msgstr ""
101 |
102 | #: links_prefs.js:88
103 | msgid "Preferred language"
104 | msgstr ""
105 |
106 | #: links_prefs.js:96
107 | msgid "Fallback language"
108 | msgstr ""
109 |
110 | #: links_prefs.js:98
111 | msgid "none"
112 | msgstr ""
113 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/de.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: German\n"
9 | "Language: de_DE\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: de\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr "Kein Bild"
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr "Titel:"
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr "Format:"
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr "Auflösung:"
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr "Videocodec:"
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr "Audio Codec:"
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr "Untertitel:"
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr "Link hier eingeben oder ablegen"
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr "Castlink"
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr "Link"
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr "Links"
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr "Linkoptionen"
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr "Bevorzugtes Format"
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr "Am besten spulbar"
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr "Beste Qualität"
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr "(experimentell)"
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr "Pfad zu youtube-dl"
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr "Max. Videoqualität"
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr "VP9 Codec erlauben"
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr "Untertitel"
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr "Bevorzugte Sprache"
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr "Fallback-Sprache"
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr "keine"
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/en_GB.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: English, United Kingdom\n"
9 | "Language: en_GB\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: en-GB\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr ""
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr ""
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr ""
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr ""
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr ""
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr ""
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr ""
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr ""
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr ""
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr ""
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr ""
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr ""
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr ""
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr ""
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr ""
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr ""
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr ""
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr ""
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr ""
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr ""
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr ""
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr ""
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr ""
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/es.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Spanish\n"
9 | "Language: es_ES\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: es-ES\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr "Sin Imagen"
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr "Título:"
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr "Formato:"
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr "Resolución:"
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr "Códec de video:"
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr "Códec de audio:"
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr "Subtitulos:"
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr "Ingrese o arrastre la dirección acá"
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr "Emitir enlace"
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr "Enlace"
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr "Enlaces"
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr "Opciones del enlace"
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr "Formato preferido"
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr "Mejor busqueda"
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr "Mejor calidad"
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr "(experimental)"
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr "Ruta a youtube-dl"
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr "Max calidad de video"
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr "Permitir el códec VP9"
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr "Subtitulos"
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr "Lenguage preferido"
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr "Lenguaje de soporte"
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr "ninguno"
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/fo.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Faroese\n"
9 | "Language: fo_FO\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: fo\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr ""
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr ""
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr ""
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr ""
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr ""
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr ""
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr ""
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr ""
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr ""
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr ""
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr ""
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr ""
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr ""
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr ""
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr ""
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr ""
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr ""
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr ""
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr ""
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr ""
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr ""
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr ""
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr ""
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/fr.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-06-25 14:05\n"
7 | "Last-Translator: \n"
8 | "Language-Team: French\n"
9 | "Language: fr_FR\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Project-ID: 356831\n"
16 | "X-Crowdin-Language: fr\n"
17 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
18 | "X-Crowdin-File-ID: 26\n"
19 |
20 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
21 | #: app.js:140
22 | msgid "No Image"
23 | msgstr ""
24 |
25 | #: app.js:152
26 | msgid "Title:"
27 | msgstr ""
28 |
29 | #: app.js:153
30 | msgid "Format:"
31 | msgstr ""
32 |
33 | #: app.js:154
34 | msgid "Resolution:"
35 | msgstr ""
36 |
37 | #: app.js:155
38 | msgid "Video codec:"
39 | msgstr ""
40 |
41 | #: app.js:156
42 | msgid "Audio codec:"
43 | msgstr ""
44 |
45 | #: app.js:157
46 | msgid "Subtitles:"
47 | msgstr ""
48 |
49 | #: app.js:173
50 | msgid "Enter or drop link here"
51 | msgstr ""
52 |
53 | #: app.js:182
54 | msgid "Cast link"
55 | msgstr ""
56 |
57 | #: widget.js:18
58 | msgid "Link"
59 | msgstr ""
60 |
61 | #: links_prefs.js:31
62 | msgid "Links"
63 | msgstr ""
64 |
65 | #: links_prefs.js:36
66 | msgid "Links Options"
67 | msgstr ""
68 |
69 | #: links_prefs.js:40
70 | msgid "Preferred format"
71 | msgstr ""
72 |
73 | #: links_prefs.js:42
74 | msgid "Best seekable"
75 | msgstr ""
76 |
77 | #: links_prefs.js:43
78 | msgid "Best quality"
79 | msgstr ""
80 |
81 | #: links_prefs.js:43
82 | msgid "(experimental)"
83 | msgstr ""
84 |
85 | #: links_prefs.js:49
86 | msgid "Path to youtube-dl"
87 | msgstr ""
88 |
89 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
90 | #: links_prefs.js:58
91 | msgid "Max video quality"
92 | msgstr ""
93 |
94 | #: links_prefs.js:76
95 | msgid "Allow VP9 codec"
96 | msgstr ""
97 |
98 | #: links_prefs.js:84
99 | msgid "Subtitles"
100 | msgstr ""
101 |
102 | #: links_prefs.js:88
103 | msgid "Preferred language"
104 | msgstr ""
105 |
106 | #: links_prefs.js:96
107 | msgid "Fallback language"
108 | msgstr ""
109 |
110 | #: links_prefs.js:98
111 | msgid "none"
112 | msgstr ""
113 |
114 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/it.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Italian\n"
9 | "Language: it_IT\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: it\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr "Nessuna\n"
22 | "immagine"
23 |
24 | #: app.js:152
25 | msgid "Title:"
26 | msgstr "Titolo:"
27 |
28 | #: app.js:153
29 | msgid "Format:"
30 | msgstr "Formato:"
31 |
32 | #: app.js:154
33 | msgid "Resolution:"
34 | msgstr "Risoluzione:"
35 |
36 | #: app.js:155
37 | msgid "Video codec:"
38 | msgstr "Codec video:"
39 |
40 | #: app.js:156
41 | msgid "Audio codec:"
42 | msgstr "Codec audio:"
43 |
44 | #: app.js:157
45 | msgid "Subtitles:"
46 | msgstr "Sottotitoli:"
47 |
48 | #: app.js:173
49 | msgid "Enter or drop link here"
50 | msgstr "Inserisci o rilascia link qui"
51 |
52 | #: app.js:182
53 | msgid "Cast link"
54 | msgstr "Link cast"
55 |
56 | #: widget.js:18
57 | msgid "Link"
58 | msgstr "Link"
59 |
60 | #: links_prefs.js:31
61 | msgid "Links"
62 | msgstr "Links"
63 |
64 | #: links_prefs.js:36
65 | msgid "Links Options"
66 | msgstr "Opzioni link"
67 |
68 | #: links_prefs.js:40
69 | msgid "Preferred format"
70 | msgstr "Formato preferito"
71 |
72 | #: links_prefs.js:42
73 | msgid "Best seekable"
74 | msgstr "Migliore ricercabile"
75 |
76 | #: links_prefs.js:43
77 | msgid "Best quality"
78 | msgstr "Migliore qualità"
79 |
80 | #: links_prefs.js:43
81 | msgid "(experimental)"
82 | msgstr "(sperimentale)"
83 |
84 | #: links_prefs.js:49
85 | msgid "Path to youtube-dl"
86 | msgstr "Percorso a youtube-dl"
87 |
88 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
89 | #: links_prefs.js:58
90 | msgid "Max video quality"
91 | msgstr "Massima qualità video"
92 |
93 | #: links_prefs.js:76
94 | msgid "Allow VP9 codec"
95 | msgstr "Consenti codec VP9"
96 |
97 | #: links_prefs.js:84
98 | msgid "Subtitles"
99 | msgstr "Sottotitoli"
100 |
101 | #: links_prefs.js:88
102 | msgid "Preferred language"
103 | msgstr "Lingua preferita"
104 |
105 | #: links_prefs.js:96
106 | msgid "Fallback language"
107 | msgstr "Lingua di Fallback"
108 |
109 | #: links_prefs.js:98
110 | msgid "none"
111 | msgstr "nessuna"
112 |
113 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/nl.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Dutch\n"
9 | "Language: nl_NL\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: nl\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr "Geen afbeelding"
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr "Titel:"
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr "Formaat:"
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr "Resolutie:"
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr "Videocodec:"
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr "Audiocodec:"
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr "Ondertiteling:"
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr "Voer een link in of versleep een link hierheen"
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr "Link casten"
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr "Link"
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr "Links"
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr "Link-instellingen"
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr "Voorkeursformaat"
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr "Best doorspoelbaar"
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr "Beste kwaliteit"
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr "(experimenteel)"
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr "Pad naar youtube-dl"
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr "Max. videokwaliteit"
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr "VP9-codec toestaan"
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr "Ondertiteling"
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr "Voorkeurstaal"
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr "Terugvallen op taal"
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr "geen"
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/pl.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Polish\n"
9 | "Language: pl_PL\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: pl\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr "Brak obrazka"
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr "Tytuł:"
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr "Format:"
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr "Rozdzielczość:"
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr "Kodek wideo:"
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr "Kodek audio:"
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr "Napisy:"
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr "Wprowadź lub upuść link tutaj"
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr "Prześlij link"
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr "Link"
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr "Linki"
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr "Opcje linków"
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr "Preferowany format"
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr "Najlepszy z przewijaniem"
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr "Najlepsza jakość"
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr "(eksperymentalna)"
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr "Ścieżka do youtube-dl"
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr "Maksymalna jakość wideo"
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr "Dopuść kodek VP9"
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr "Napisy"
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr "Preferowany język"
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr "Drugorzędny język"
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr "brak"
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/ro.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-21 10:05\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Romanian\n"
9 | "Language: ro_RO\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: ro\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr ""
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr ""
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr ""
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr ""
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr ""
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr ""
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr ""
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr ""
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr ""
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr ""
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr ""
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr ""
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr ""
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr ""
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr ""
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr ""
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr ""
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr ""
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr ""
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr ""
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr ""
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr ""
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr ""
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv-links-addon/tr.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2019-10-15 20:55+0200\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Turkish\n"
9 | "Language: tr_TR\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: tr\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv-links-addon/cast-to-tv-links-addon.pot\n"
17 |
18 | #. TRANSLATORS: Make sure this text fits the black background. Otherwise the frame will get stretched!!!
19 | #: app.js:140
20 | msgid "No Image"
21 | msgstr "Görsel Yok"
22 |
23 | #: app.js:152
24 | msgid "Title:"
25 | msgstr "Başlık:"
26 |
27 | #: app.js:153
28 | msgid "Format:"
29 | msgstr "Biçim:"
30 |
31 | #: app.js:154
32 | msgid "Resolution:"
33 | msgstr "Çözünürlük:"
34 |
35 | #: app.js:155
36 | msgid "Video codec:"
37 | msgstr "Video çözücü:"
38 |
39 | #: app.js:156
40 | msgid "Audio codec:"
41 | msgstr "Ses çözücü:"
42 |
43 | #: app.js:157
44 | msgid "Subtitles:"
45 | msgstr "Altyazılar:"
46 |
47 | #: app.js:173
48 | msgid "Enter or drop link here"
49 | msgstr "Bağlantıyı buraya gir veya sürükle"
50 |
51 | #: app.js:182
52 | msgid "Cast link"
53 | msgstr "Akış bağlantısı"
54 |
55 | #: widget.js:18
56 | msgid "Link"
57 | msgstr "Bağlantı"
58 |
59 | #: links_prefs.js:31
60 | msgid "Links"
61 | msgstr "Bağlantılar"
62 |
63 | #: links_prefs.js:36
64 | msgid "Links Options"
65 | msgstr "Bağlantı Seçenekleri"
66 |
67 | #: links_prefs.js:40
68 | msgid "Preferred format"
69 | msgstr "Tercih edilen biçim"
70 |
71 | #: links_prefs.js:42
72 | msgid "Best seekable"
73 | msgstr "En iyi aranabilir"
74 |
75 | #: links_prefs.js:43
76 | msgid "Best quality"
77 | msgstr "En iyi kalite"
78 |
79 | #: links_prefs.js:43
80 | msgid "(experimental)"
81 | msgstr "(deneysel)"
82 |
83 | #: links_prefs.js:49
84 | msgid "Path to youtube-dl"
85 | msgstr "Eklenti yolu youtube-dl"
86 |
87 | #. TRANSLATORS: Can be translated simply as "Max quality" to make it shorter
88 | #: links_prefs.js:58
89 | msgid "Max video quality"
90 | msgstr "En iyi video kalitesi"
91 |
92 | #: links_prefs.js:76
93 | msgid "Allow VP9 codec"
94 | msgstr "VP9 çözücüye izin ver"
95 |
96 | #: links_prefs.js:84
97 | msgid "Subtitles"
98 | msgstr "Altyazılar"
99 |
100 | #: links_prefs.js:88
101 | msgid "Preferred language"
102 | msgstr "Tercih edilen dil"
103 |
104 | #: links_prefs.js:96
105 | msgid "Fallback language"
106 | msgstr "İkincil dil"
107 |
108 | #: links_prefs.js:98
109 | msgid "none"
110 | msgstr "hiçbiri"
111 |
112 |
--------------------------------------------------------------------------------
/po/cast-to-tv/cast-to-tv.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the Cast to TV package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: Cast to TV\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2020-02-04 16:20+0100\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=CHARSET\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #. TRANSLATORS: When "Cast Media" service is turned off
21 | #: widget.js:34 widget.js:111
22 | msgid "Cast Off"
23 | msgstr ""
24 |
25 | #: widget.js:39 file-chooser.js:80
26 | msgid "Video"
27 | msgstr ""
28 |
29 | #: widget.js:40
30 | msgid "Music"
31 | msgstr ""
32 |
33 | #: widget.js:41
34 | msgid "Picture"
35 | msgstr ""
36 |
37 | #: widget.js:42 widget.js:109
38 | msgid "Turn On"
39 | msgstr ""
40 |
41 | #: widget.js:43
42 | msgid "Cast Settings"
43 | msgstr ""
44 |
45 | #: widget.js:92
46 | msgid "Turn Off"
47 | msgstr ""
48 |
49 | #: widget.js:93
50 | msgid "Cast Media"
51 | msgstr ""
52 |
53 | #: widget.js:341
54 | msgid "Browser"
55 | msgstr ""
56 |
57 | #: playlist.js:344
58 | msgid "Playlist"
59 | msgstr ""
60 |
61 | #. TRANSLATORS: Button text when selected SINGLE file
62 | #: file-chooser.js:18
63 | msgid "Cast Selected File"
64 | msgstr ""
65 |
66 | #. TRANSLATORS: Button text when selected MULTIPLE files
67 | #: file-chooser.js:20
68 | msgid "Cast Selected Files"
69 | msgstr ""
70 |
71 | #: file-chooser.js:21
72 | msgid "Add to Playlist"
73 | msgstr ""
74 |
75 | #: file-chooser.js:77
76 | msgid "Transcode"
77 | msgstr ""
78 |
79 | #: file-chooser.js:81
80 | msgid "Audio"
81 | msgstr ""
82 |
83 | #: file-chooser.js:82
84 | msgid "Video + Audio"
85 | msgstr ""
86 |
87 | #: file-chooser.js:142 file-chooser.js:175 prefs.js:1283
88 | msgid "Automatic"
89 | msgstr ""
90 |
91 | #: file-chooser.js:270
92 | msgid "Cancel"
93 | msgstr ""
94 |
95 | #: file-chooser.js:279
96 | msgid "Add Subtitles"
97 | msgstr ""
98 |
99 | #: file-chooser.js:281
100 | msgid "Select Video"
101 | msgstr ""
102 |
103 | #: file-chooser.js:285
104 | msgid "Video Files"
105 | msgstr ""
106 |
107 | #: file-chooser.js:293
108 | msgid "Select Music"
109 | msgstr ""
110 |
111 | #: file-chooser.js:297
112 | msgid "Audio Files"
113 | msgstr ""
114 |
115 | #: file-chooser.js:305
116 | msgid "Select Picture"
117 | msgstr ""
118 |
119 | #: file-chooser.js:309
120 | msgid "Pictures"
121 | msgstr ""
122 |
123 | #: file-chooser.js:370
124 | msgid "Select Subtitles"
125 | msgstr ""
126 |
127 | #: file-chooser.js:374
128 | msgid "Subtitle Files"
129 | msgstr ""
130 |
131 | #. TRANSLATORS: Will contain dependency name at the beginning (e.g. Node.js is not installed)
132 | #: prefs.js:36
133 | msgid "is not installed"
134 | msgstr ""
135 |
136 | #: prefs.js:54
137 | msgid "Streaming in progress"
138 | msgstr ""
139 |
140 | #. TRANSLATORS: Keep line this short (otherwise extension prefs will strech)
141 | #: prefs.js:64
142 | msgid "Stop media transfer before accessing extension settings"
143 | msgstr ""
144 |
145 | #: prefs.js:94
146 | msgid "Main Options"
147 | msgstr ""
148 |
149 | #: prefs.js:98
150 | msgid "Receiver type"
151 | msgstr ""
152 |
153 | #. TRANSLATORS: "Playercast" is a name of an app, so do not change it
154 | #: prefs.js:102 prefs.js:659
155 | msgid "Playercast app"
156 | msgstr ""
157 |
158 | #. TRANSLATORS: Web browser or Media player app selection.
159 | #. This should be as short as possible e.g. "Browser | Player".
160 | #: prefs.js:105
161 | msgid "Web browser | Media player"
162 | msgstr ""
163 |
164 | #: prefs.js:110
165 | msgid "FFmpeg path"
166 | msgstr ""
167 |
168 | #: prefs.js:117
169 | msgid "FFprobe path"
170 | msgstr ""
171 |
172 | #: prefs.js:124
173 | msgid "Listening port"
174 | msgstr ""
175 |
176 | #: prefs.js:134
177 | msgid "Internal communication port"
178 | msgstr ""
179 |
180 | #: prefs.js:203
181 | msgid "Access web player from devices on local network"
182 | msgstr ""
183 |
184 | #: prefs.js:227
185 | msgid "Remote Controller"
186 | msgstr ""
187 |
188 | #: prefs.js:231
189 | msgid "Remote position"
190 | msgstr ""
191 |
192 | #: prefs.js:233
193 | msgid "Left"
194 | msgstr ""
195 |
196 | #: prefs.js:234
197 | msgid "Center (left side)"
198 | msgstr ""
199 |
200 | #: prefs.js:235
201 | msgid "Center (right side)"
202 | msgstr ""
203 |
204 | #: prefs.js:236
205 | msgid "Right"
206 | msgstr ""
207 |
208 | #: prefs.js:241
209 | msgid "Seek backward/forward (seconds)"
210 | msgstr ""
211 |
212 | #: prefs.js:251
213 | msgid "Slideshow time per picture (seconds)"
214 | msgstr ""
215 |
216 | #: prefs.js:261
217 | msgid "Media control buttons size"
218 | msgstr ""
219 |
220 | #: prefs.js:271
221 | msgid "Slider icon size"
222 | msgstr ""
223 |
224 | #: prefs.js:281
225 | msgid "Unify sliders"
226 | msgstr ""
227 |
228 | #: prefs.js:289
229 | msgid "Show remote label"
230 | msgstr ""
231 |
232 | #: prefs.js:297
233 | msgid "Receiver name as label"
234 | msgstr ""
235 |
236 | #: prefs.js:347
237 | msgid "Chromecast Options"
238 | msgstr ""
239 |
240 | #: prefs.js:351 prefs.js:663
241 | msgid "Device selection"
242 | msgstr ""
243 |
244 | #: prefs.js:378
245 | msgid "Subtitles"
246 | msgstr ""
247 |
248 | #: prefs.js:382
249 | msgid "Font family"
250 | msgstr ""
251 |
252 | #: prefs.js:401
253 | msgid "Font style"
254 | msgstr ""
255 |
256 | #. TRANSLATORS: One of "Speed" setting value
257 | #: prefs.js:403 node_scripts/messages.js:22
258 | msgid "Normal"
259 | msgstr ""
260 |
261 | #: prefs.js:404
262 | msgid "Bold"
263 | msgstr ""
264 |
265 | #: prefs.js:405
266 | msgid "Italic"
267 | msgstr ""
268 |
269 | #: prefs.js:406
270 | msgid "Bold italic"
271 | msgstr ""
272 |
273 | #: prefs.js:416
274 | msgid "Scale factor"
275 | msgstr ""
276 |
277 | #: prefs.js:430
278 | msgid "Font color"
279 | msgstr ""
280 |
281 | #: prefs.js:442
282 | msgid "Font outline"
283 | msgstr ""
284 |
285 | #: prefs.js:472
286 | msgid "Background color"
287 | msgstr ""
288 |
289 | #: prefs.js:540
290 | msgid "Encoder"
291 | msgstr ""
292 |
293 | #: prefs.js:545
294 | msgid "Media Encoding"
295 | msgstr ""
296 |
297 | #: prefs.js:549
298 | msgid "Hardware acceleration"
299 | msgstr ""
300 |
301 | #: prefs.js:551 prefs.js:1141
302 | msgid "None"
303 | msgstr ""
304 |
305 | #: prefs.js:558
306 | msgid "Bitrate (Mbps)"
307 | msgstr ""
308 |
309 | #: prefs.js:568
310 | msgid "Burn subtitles when transcoding video"
311 | msgstr ""
312 |
313 | #. TRANSLATORS: "Players" as video players
314 | #: prefs.js:583
315 | msgid "Extractor"
316 | msgstr ""
317 |
318 | #: prefs.js:588
319 | msgid "Subtitles Extraction"
320 | msgstr ""
321 |
322 | #. TRANSLATORS: "vttextract" is the name of executable, do not change
323 | #: prefs.js:593
324 | msgid "Add vttextract executable"
325 | msgstr ""
326 |
327 | #: prefs.js:602
328 | msgid "Reuse extracted subtitles"
329 | msgstr ""
330 |
331 | #. TRANSLATORS: Destination folder to save subtitles
332 | #: prefs.js:611
333 | msgid "Save folder"
334 | msgstr ""
335 |
336 | #: prefs.js:613
337 | msgid "Select folder"
338 | msgstr ""
339 |
340 | #: prefs.js:654
341 | msgid "Misc"
342 | msgstr ""
343 |
344 | #: prefs.js:676
345 | msgid "Web Player"
346 | msgstr ""
347 |
348 | #: prefs.js:680
349 | msgid "Subtitles scale factor"
350 | msgstr ""
351 |
352 | #. TRANSLATORS: The rest of extension settings
353 | #: prefs.js:691
354 | msgid "Miscellaneous"
355 | msgstr ""
356 |
357 | #: prefs.js:695
358 | msgid "Music visualizer"
359 | msgstr ""
360 |
361 | #: prefs.js:703
362 | msgid "Nautilus/Nemo integration"
363 | msgstr ""
364 |
365 | #: prefs.js:809
366 | msgid "Install npm modules"
367 | msgstr ""
368 |
369 | #: prefs.js:833
370 | msgid "Installing..."
371 | msgstr ""
372 |
373 | #: prefs.js:887
374 | msgid "version:"
375 | msgstr ""
376 |
377 | #: prefs.js:894
378 | msgid "Developed by"
379 | msgstr ""
380 |
381 | #: prefs.js:903
382 | msgid "Extension Homepage"
383 | msgstr ""
384 |
385 | #: prefs.js:910
386 | msgid "Playercast Homepage"
387 | msgstr ""
388 |
389 | #: prefs.js:917
390 | msgid "Donate"
391 | msgstr ""
392 |
393 | #: prefs.js:931
394 | msgid "Main"
395 | msgstr ""
396 |
397 | #: prefs.js:935
398 | msgid "Remote"
399 | msgstr ""
400 |
401 | #. TRANSLATORS: Other extension settings
402 | #: prefs.js:944
403 | msgid "Other"
404 | msgstr ""
405 |
406 | #: prefs.js:957
407 | msgid "Add-ons"
408 | msgstr ""
409 |
410 | #: prefs.js:962
411 | msgid "Modules"
412 | msgstr ""
413 |
414 | #: prefs.js:966
415 | msgid "About"
416 | msgstr ""
417 |
418 | #: prefs.js:1074
419 | msgid "Manual IP Config"
420 | msgstr ""
421 |
422 | #: prefs.js:1131
423 | msgid "Auto"
424 | msgstr ""
425 |
426 | #: prefs.js:1132
427 | msgid "Name"
428 | msgstr ""
429 |
430 | #. TRANSLATORS: Text field temporary text
431 | #: prefs.js:1148
432 | msgid "Insert name"
433 | msgstr ""
434 |
435 | #. TRANSLATORS: Shown when scan for Chromecast devices is running
436 | #: prefs.js:1263
437 | msgid "Scanning..."
438 | msgstr ""
439 |
440 | #: node_scripts/messages.js:4
441 | msgid "LOADING"
442 | msgstr ""
443 |
444 | #: node_scripts/messages.js:5
445 | msgid "No file selected"
446 | msgstr ""
447 |
448 | #: node_scripts/messages.js:6
449 | msgid "Receiver type is set to Chromecast"
450 | msgstr ""
451 |
452 | #: node_scripts/messages.js:7
453 | msgid "Receiver type is set to Playercast app"
454 | msgstr ""
455 |
456 | #: node_scripts/messages.js:8
457 | msgid "Streaming process is still active"
458 | msgstr ""
459 |
460 | #. TRANSLATORS: Do not remove HTML tags
461 | #: node_scripts/messages.js:10
462 | msgid ""
463 | "Too many connections!Close all other tabs that are accessing this page in "
464 | "all browsers
"
465 | msgstr ""
466 |
467 | #. TRANSLATORS: This sentence will contain file path after end
468 | #: node_scripts/messages.js:12
469 | msgid "FFmpeg could not transcode file:"
470 | msgstr ""
471 |
472 | #: node_scripts/messages.js:13
473 | msgid "FFmpeg path is incorrect"
474 | msgstr ""
475 |
476 | #. TRANSLATORS: This sentence will contain file path after end
477 | #: node_scripts/messages.js:15
478 | msgid "FFprobe could not process file:"
479 | msgstr ""
480 |
481 | #: node_scripts/messages.js:16
482 | msgid "FFprobe path is incorrect"
483 | msgstr ""
484 |
485 | #. TRANSLATORS: This sentence will contain file path after end
486 | #: node_scripts/messages.js:18
487 | msgid "Could not analyze selected file:"
488 | msgstr ""
489 |
490 | #: node_scripts/messages.js:20
491 | msgid "Speed"
492 | msgstr ""
493 |
494 | #: node_scripts/messages.js:25
495 | msgid "Device not found"
496 | msgstr ""
497 |
498 | #: node_scripts/messages.js:26
499 | msgid "Failed to load media"
500 | msgstr ""
501 |
502 | #: node_scripts/messages.js:27
503 | msgid "Could not connect to device"
504 | msgstr ""
505 |
506 | #: node_scripts/messages.js:28
507 | msgid "Verify device IP in extension preferences"
508 | msgstr ""
509 |
510 | #. TRANSLATORS: This sentence will contain file path after end
511 | #: node_scripts/messages.js:30
512 | msgid "Could not play file:"
513 | msgstr ""
514 |
515 | #: node_scripts/messages.js:31
516 | msgid "Try again with transcoding enabled"
517 | msgstr ""
518 |
--------------------------------------------------------------------------------
/po/cast-to-tv/en_GB.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2020-02-04 16:20+0100\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: English, United Kingdom\n"
9 | "Language: en_GB\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: en-GB\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv/cast-to-tv.pot\n"
17 |
18 | #. TRANSLATORS: When "Cast Media" service is turned off
19 | #: widget.js:34 widget.js:111
20 | msgid "Cast Off"
21 | msgstr ""
22 |
23 | #: widget.js:39 file-chooser.js:80
24 | msgid "Video"
25 | msgstr ""
26 |
27 | #: widget.js:40
28 | msgid "Music"
29 | msgstr ""
30 |
31 | #: widget.js:41
32 | msgid "Picture"
33 | msgstr ""
34 |
35 | #: widget.js:42 widget.js:109
36 | msgid "Turn On"
37 | msgstr ""
38 |
39 | #: widget.js:43
40 | msgid "Cast Settings"
41 | msgstr ""
42 |
43 | #: widget.js:92
44 | msgid "Turn Off"
45 | msgstr ""
46 |
47 | #: widget.js:93
48 | msgid "Cast Media"
49 | msgstr ""
50 |
51 | #: widget.js:341
52 | msgid "Browser"
53 | msgstr ""
54 |
55 | #: playlist.js:344
56 | msgid "Playlist"
57 | msgstr ""
58 |
59 | #. TRANSLATORS: Button text when selected SINGLE file
60 | #: file-chooser.js:18
61 | msgid "Cast Selected File"
62 | msgstr ""
63 |
64 | #. TRANSLATORS: Button text when selected MULTIPLE files
65 | #: file-chooser.js:20
66 | msgid "Cast Selected Files"
67 | msgstr ""
68 |
69 | #: file-chooser.js:21
70 | msgid "Add to Playlist"
71 | msgstr ""
72 |
73 | #: file-chooser.js:77
74 | msgid "Transcode"
75 | msgstr ""
76 |
77 | #: file-chooser.js:81
78 | msgid "Audio"
79 | msgstr ""
80 |
81 | #: file-chooser.js:82
82 | msgid "Video + Audio"
83 | msgstr ""
84 |
85 | #: file-chooser.js:142 file-chooser.js:175 prefs.js:1283
86 | msgid "Automatic"
87 | msgstr ""
88 |
89 | #: file-chooser.js:270
90 | msgid "Cancel"
91 | msgstr ""
92 |
93 | #: file-chooser.js:279
94 | msgid "Add Subtitles"
95 | msgstr ""
96 |
97 | #: file-chooser.js:281
98 | msgid "Select Video"
99 | msgstr ""
100 |
101 | #: file-chooser.js:285
102 | msgid "Video Files"
103 | msgstr ""
104 |
105 | #: file-chooser.js:293
106 | msgid "Select Music"
107 | msgstr ""
108 |
109 | #: file-chooser.js:297
110 | msgid "Audio Files"
111 | msgstr ""
112 |
113 | #: file-chooser.js:305
114 | msgid "Select Picture"
115 | msgstr ""
116 |
117 | #: file-chooser.js:309
118 | msgid "Pictures"
119 | msgstr ""
120 |
121 | #: file-chooser.js:370
122 | msgid "Select Subtitles"
123 | msgstr ""
124 |
125 | #: file-chooser.js:374
126 | msgid "Subtitle Files"
127 | msgstr ""
128 |
129 | #. TRANSLATORS: Will contain dependency name at the beginning (e.g. Node.js is not installed)
130 | #: prefs.js:36
131 | msgid "is not installed"
132 | msgstr ""
133 |
134 | #: prefs.js:54
135 | msgid "Streaming in progress"
136 | msgstr ""
137 |
138 | #. TRANSLATORS: Keep line this short (otherwise extension prefs will strech)
139 | #: prefs.js:64
140 | msgid "Stop media transfer before accessing extension settings"
141 | msgstr ""
142 |
143 | #: prefs.js:94
144 | msgid "Main Options"
145 | msgstr ""
146 |
147 | #: prefs.js:98
148 | msgid "Receiver type"
149 | msgstr ""
150 |
151 | #. TRANSLATORS: "Playercast" is a name of an app, so do not change it
152 | #: prefs.js:102 prefs.js:659
153 | msgid "Playercast app"
154 | msgstr ""
155 |
156 | #. TRANSLATORS: Web browser or Media player app selection.
157 | #. This should be as short as possible e.g. "Browser | Player".
158 | #: prefs.js:105
159 | msgid "Web browser | Media player"
160 | msgstr ""
161 |
162 | #: prefs.js:110
163 | msgid "FFmpeg path"
164 | msgstr ""
165 |
166 | #: prefs.js:117
167 | msgid "FFprobe path"
168 | msgstr ""
169 |
170 | #: prefs.js:124
171 | msgid "Listening port"
172 | msgstr ""
173 |
174 | #: prefs.js:134
175 | msgid "Internal communication port"
176 | msgstr ""
177 |
178 | #: prefs.js:203
179 | msgid "Access web player from devices on local network"
180 | msgstr ""
181 |
182 | #: prefs.js:227
183 | msgid "Remote Controller"
184 | msgstr ""
185 |
186 | #: prefs.js:231
187 | msgid "Remote position"
188 | msgstr ""
189 |
190 | #: prefs.js:233
191 | msgid "Left"
192 | msgstr ""
193 |
194 | #: prefs.js:234
195 | msgid "Center (left side)"
196 | msgstr ""
197 |
198 | #: prefs.js:235
199 | msgid "Center (right side)"
200 | msgstr ""
201 |
202 | #: prefs.js:236
203 | msgid "Right"
204 | msgstr ""
205 |
206 | #: prefs.js:241
207 | msgid "Seek backward/forward (seconds)"
208 | msgstr ""
209 |
210 | #: prefs.js:251
211 | msgid "Slideshow time per picture (seconds)"
212 | msgstr ""
213 |
214 | #: prefs.js:261
215 | msgid "Media control buttons size"
216 | msgstr ""
217 |
218 | #: prefs.js:271
219 | msgid "Slider icon size"
220 | msgstr ""
221 |
222 | #: prefs.js:281
223 | msgid "Unify sliders"
224 | msgstr ""
225 |
226 | #: prefs.js:289
227 | msgid "Show remote label"
228 | msgstr ""
229 |
230 | #: prefs.js:297
231 | msgid "Receiver name as label"
232 | msgstr ""
233 |
234 | #: prefs.js:347
235 | msgid "Chromecast Options"
236 | msgstr ""
237 |
238 | #: prefs.js:351 prefs.js:663
239 | msgid "Device selection"
240 | msgstr ""
241 |
242 | #: prefs.js:378
243 | msgid "Subtitles"
244 | msgstr ""
245 |
246 | #: prefs.js:382
247 | msgid "Font family"
248 | msgstr ""
249 |
250 | #: prefs.js:401
251 | msgid "Font style"
252 | msgstr ""
253 |
254 | #. TRANSLATORS: One of "Speed" setting value
255 | #: prefs.js:403 node_scripts/messages.js:22
256 | msgid "Normal"
257 | msgstr ""
258 |
259 | #: prefs.js:404
260 | msgid "Bold"
261 | msgstr ""
262 |
263 | #: prefs.js:405
264 | msgid "Italic"
265 | msgstr ""
266 |
267 | #: prefs.js:406
268 | msgid "Bold italic"
269 | msgstr ""
270 |
271 | #: prefs.js:416
272 | msgid "Scale factor"
273 | msgstr ""
274 |
275 | #: prefs.js:430
276 | msgid "Font color"
277 | msgstr "Font colour"
278 |
279 | #: prefs.js:442
280 | msgid "Font outline"
281 | msgstr ""
282 |
283 | #: prefs.js:472
284 | msgid "Background color"
285 | msgstr "Background colour"
286 |
287 | #: prefs.js:540
288 | msgid "Encoder"
289 | msgstr ""
290 |
291 | #: prefs.js:545
292 | msgid "Media Encoding"
293 | msgstr ""
294 |
295 | #: prefs.js:549
296 | msgid "Hardware acceleration"
297 | msgstr ""
298 |
299 | #: prefs.js:551 prefs.js:1141
300 | msgid "None"
301 | msgstr ""
302 |
303 | #: prefs.js:558
304 | msgid "Bitrate (Mbps)"
305 | msgstr ""
306 |
307 | #: prefs.js:568
308 | msgid "Burn subtitles when transcoding video"
309 | msgstr ""
310 |
311 | #. TRANSLATORS: "Players" as video players
312 | #: prefs.js:583
313 | msgid "Extractor"
314 | msgstr ""
315 |
316 | #: prefs.js:588
317 | msgid "Subtitles Extraction"
318 | msgstr ""
319 |
320 | #. TRANSLATORS: "vttextract" is the name of executable, do not change
321 | #: prefs.js:593
322 | msgid "Add vttextract executable"
323 | msgstr ""
324 |
325 | #: prefs.js:602
326 | msgid "Reuse extracted subtitles"
327 | msgstr ""
328 |
329 | #. TRANSLATORS: Destination folder to save subtitles
330 | #: prefs.js:611
331 | msgid "Save folder"
332 | msgstr ""
333 |
334 | #: prefs.js:613
335 | msgid "Select folder"
336 | msgstr ""
337 |
338 | #: prefs.js:654
339 | msgid "Misc"
340 | msgstr ""
341 |
342 | #: prefs.js:676
343 | msgid "Web Player"
344 | msgstr ""
345 |
346 | #: prefs.js:680
347 | msgid "Subtitles scale factor"
348 | msgstr ""
349 |
350 | #. TRANSLATORS: The rest of extension settings
351 | #: prefs.js:691
352 | msgid "Miscellaneous"
353 | msgstr ""
354 |
355 | #: prefs.js:695
356 | msgid "Music visualizer"
357 | msgstr ""
358 |
359 | #: prefs.js:703
360 | msgid "Nautilus/Nemo integration"
361 | msgstr ""
362 |
363 | #: prefs.js:809
364 | msgid "Install npm modules"
365 | msgstr ""
366 |
367 | #: prefs.js:833
368 | msgid "Installing..."
369 | msgstr ""
370 |
371 | #: prefs.js:887
372 | msgid "version:"
373 | msgstr ""
374 |
375 | #: prefs.js:894
376 | msgid "Developed by"
377 | msgstr ""
378 |
379 | #: prefs.js:903
380 | msgid "Extension Homepage"
381 | msgstr ""
382 |
383 | #: prefs.js:910
384 | msgid "Playercast Homepage"
385 | msgstr ""
386 |
387 | #: prefs.js:917
388 | msgid "Donate"
389 | msgstr ""
390 |
391 | #: prefs.js:931
392 | msgid "Main"
393 | msgstr ""
394 |
395 | #: prefs.js:935
396 | msgid "Remote"
397 | msgstr ""
398 |
399 | #. TRANSLATORS: Other extension settings
400 | #: prefs.js:944
401 | msgid "Other"
402 | msgstr ""
403 |
404 | #: prefs.js:957
405 | msgid "Add-ons"
406 | msgstr ""
407 |
408 | #: prefs.js:962
409 | msgid "Modules"
410 | msgstr ""
411 |
412 | #: prefs.js:966
413 | msgid "About"
414 | msgstr ""
415 |
416 | #: prefs.js:1074
417 | msgid "Manual IP Config"
418 | msgstr ""
419 |
420 | #: prefs.js:1131
421 | msgid "Auto"
422 | msgstr ""
423 |
424 | #: prefs.js:1132
425 | msgid "Name"
426 | msgstr ""
427 |
428 | #. TRANSLATORS: Text field temporary text
429 | #: prefs.js:1148
430 | msgid "Insert name"
431 | msgstr ""
432 |
433 | #. TRANSLATORS: Shown when scan for Chromecast devices is running
434 | #: prefs.js:1263
435 | msgid "Scanning..."
436 | msgstr ""
437 |
438 | #: node_scripts/messages.js:4
439 | msgid "LOADING"
440 | msgstr ""
441 |
442 | #: node_scripts/messages.js:5
443 | msgid "No file selected"
444 | msgstr ""
445 |
446 | #: node_scripts/messages.js:6
447 | msgid "Receiver type is set to Chromecast"
448 | msgstr ""
449 |
450 | #: node_scripts/messages.js:7
451 | msgid "Receiver type is set to Playercast app"
452 | msgstr ""
453 |
454 | #: node_scripts/messages.js:8
455 | msgid "Streaming process is still active"
456 | msgstr ""
457 |
458 | #. TRANSLATORS: Do not remove HTML tags
459 | #: node_scripts/messages.js:10
460 | msgid "Too many connections!Close all other tabs that are accessing this page in all browsers
"
461 | msgstr ""
462 |
463 | #. TRANSLATORS: This sentence will contain file path after end
464 | #: node_scripts/messages.js:12
465 | msgid "FFmpeg could not transcode file:"
466 | msgstr ""
467 |
468 | #: node_scripts/messages.js:13
469 | msgid "FFmpeg path is incorrect"
470 | msgstr ""
471 |
472 | #. TRANSLATORS: This sentence will contain file path after end
473 | #: node_scripts/messages.js:15
474 | msgid "FFprobe could not process file:"
475 | msgstr ""
476 |
477 | #: node_scripts/messages.js:16
478 | msgid "FFprobe path is incorrect"
479 | msgstr ""
480 |
481 | #. TRANSLATORS: This sentence will contain file path after end
482 | #: node_scripts/messages.js:18
483 | msgid "Could not analyze selected file:"
484 | msgstr "Could not analyse selected file:"
485 |
486 | #: node_scripts/messages.js:20
487 | msgid "Speed"
488 | msgstr ""
489 |
490 | #: node_scripts/messages.js:25
491 | msgid "Device not found"
492 | msgstr ""
493 |
494 | #: node_scripts/messages.js:26
495 | msgid "Failed to load media"
496 | msgstr ""
497 |
498 | #: node_scripts/messages.js:27
499 | msgid "Could not connect to device"
500 | msgstr ""
501 |
502 | #: node_scripts/messages.js:28
503 | msgid "Verify device IP in extension preferences"
504 | msgstr ""
505 |
506 | #. TRANSLATORS: This sentence will contain file path after end
507 | #: node_scripts/messages.js:30
508 | msgid "Could not play file:"
509 | msgstr ""
510 |
511 | #: node_scripts/messages.js:31
512 | msgid "Try again with transcoding enabled"
513 | msgstr ""
514 |
515 |
--------------------------------------------------------------------------------
/po/cast-to-tv/fo.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2020-02-04 16:20+0100\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Faroese\n"
9 | "Language: fo_FO\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: fo\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv/cast-to-tv.pot\n"
17 |
18 | #. TRANSLATORS: When "Cast Media" service is turned off
19 | #: widget.js:34 widget.js:111
20 | msgid "Cast Off"
21 | msgstr ""
22 |
23 | #: widget.js:39 file-chooser.js:80
24 | msgid "Video"
25 | msgstr "Sjónband"
26 |
27 | #: widget.js:40
28 | msgid "Music"
29 | msgstr "Tónleik"
30 |
31 | #: widget.js:41
32 | msgid "Picture"
33 | msgstr "Mynd"
34 |
35 | #: widget.js:42 widget.js:109
36 | msgid "Turn On"
37 | msgstr ""
38 |
39 | #: widget.js:43
40 | msgid "Cast Settings"
41 | msgstr "Varpar Setingar"
42 |
43 | #: widget.js:92
44 | msgid "Turn Off"
45 | msgstr ""
46 |
47 | #: widget.js:93
48 | msgid "Cast Media"
49 | msgstr "Varpa Media"
50 |
51 | #: widget.js:341
52 | msgid "Browser"
53 | msgstr ""
54 |
55 | #: playlist.js:344
56 | msgid "Playlist"
57 | msgstr ""
58 |
59 | #. TRANSLATORS: Button text when selected SINGLE file
60 | #: file-chooser.js:18
61 | msgid "Cast Selected File"
62 | msgstr "Varpa valdi fíluna"
63 |
64 | #. TRANSLATORS: Button text when selected MULTIPLE files
65 | #: file-chooser.js:20
66 | msgid "Cast Selected Files"
67 | msgstr "Varpa valdu fílurnar"
68 |
69 | #: file-chooser.js:21
70 | msgid "Add to Playlist"
71 | msgstr ""
72 |
73 | #: file-chooser.js:77
74 | msgid "Transcode"
75 | msgstr ""
76 |
77 | #: file-chooser.js:81
78 | msgid "Audio"
79 | msgstr ""
80 |
81 | #: file-chooser.js:82
82 | msgid "Video + Audio"
83 | msgstr ""
84 |
85 | #: file-chooser.js:142 file-chooser.js:175 prefs.js:1283
86 | msgid "Automatic"
87 | msgstr "Sjálvvirkið"
88 |
89 | #: file-chooser.js:270
90 | msgid "Cancel"
91 | msgstr "Avlýs"
92 |
93 | #: file-chooser.js:279
94 | msgid "Add Subtitles"
95 | msgstr "Legg undirtekstir aftrat"
96 |
97 | #: file-chooser.js:281
98 | msgid "Select Video"
99 | msgstr "Vel Sjónband"
100 |
101 | #: file-chooser.js:285
102 | msgid "Video Files"
103 | msgstr "Sjónbond"
104 |
105 | #: file-chooser.js:293
106 | msgid "Select Music"
107 | msgstr "Vel Tónleik"
108 |
109 | #: file-chooser.js:297
110 | msgid "Audio Files"
111 | msgstr "Tónleikar Fílur"
112 |
113 | #: file-chooser.js:305
114 | msgid "Select Picture"
115 | msgstr "Vel Mynd"
116 |
117 | #: file-chooser.js:309
118 | msgid "Pictures"
119 | msgstr "Myndir"
120 |
121 | #: file-chooser.js:370
122 | msgid "Select Subtitles"
123 | msgstr "Vel Undirtekstir"
124 |
125 | #: file-chooser.js:374
126 | msgid "Subtitle Files"
127 | msgstr "Undirteksta fílur"
128 |
129 | #. TRANSLATORS: Will contain dependency name at the beginning (e.g. Node.js is not installed)
130 | #: prefs.js:36
131 | msgid "is not installed"
132 | msgstr ""
133 |
134 | #: prefs.js:54
135 | msgid "Streaming in progress"
136 | msgstr ""
137 |
138 | #. TRANSLATORS: Keep line this short (otherwise extension prefs will strech)
139 | #: prefs.js:64
140 | msgid "Stop media transfer before accessing extension settings"
141 | msgstr ""
142 |
143 | #: prefs.js:94
144 | msgid "Main Options"
145 | msgstr ""
146 |
147 | #: prefs.js:98
148 | msgid "Receiver type"
149 | msgstr "Slag av móttakara"
150 |
151 | #. TRANSLATORS: "Playercast" is a name of an app, so do not change it
152 | #: prefs.js:102 prefs.js:659
153 | msgid "Playercast app"
154 | msgstr ""
155 |
156 | #. TRANSLATORS: Web browser or Media player app selection.
157 | #. This should be as short as possible e.g. "Browser | Player".
158 | #: prefs.js:105
159 | msgid "Web browser | Media player"
160 | msgstr ""
161 |
162 | #: prefs.js:110
163 | msgid "FFmpeg path"
164 | msgstr "FFmpeg slóð"
165 |
166 | #: prefs.js:117
167 | msgid "FFprobe path"
168 | msgstr "FFprobe slóð"
169 |
170 | #: prefs.js:124
171 | msgid "Listening port"
172 | msgstr "Portur at lurta at"
173 |
174 | #: prefs.js:134
175 | msgid "Internal communication port"
176 | msgstr ""
177 |
178 | #: prefs.js:203
179 | msgid "Access web player from devices on local network"
180 | msgstr ""
181 |
182 | #: prefs.js:227
183 | msgid "Remote Controller"
184 | msgstr ""
185 |
186 | #: prefs.js:231
187 | msgid "Remote position"
188 | msgstr "Remote position"
189 |
190 | #: prefs.js:233
191 | msgid "Left"
192 | msgstr "Vinstra"
193 |
194 | #: prefs.js:234
195 | msgid "Center (left side)"
196 | msgstr "Mitt fyri (vinstru síði)"
197 |
198 | #: prefs.js:235
199 | msgid "Center (right side)"
200 | msgstr "Mitt fyri (høgru síði)"
201 |
202 | #: prefs.js:236
203 | msgid "Right"
204 | msgstr "Høgra"
205 |
206 | #: prefs.js:241
207 | msgid "Seek backward/forward (seconds)"
208 | msgstr "Spola aftur/fram (sekund)"
209 |
210 | #: prefs.js:251
211 | msgid "Slideshow time per picture (seconds)"
212 | msgstr ""
213 |
214 | #: prefs.js:261
215 | msgid "Media control buttons size"
216 | msgstr ""
217 |
218 | #: prefs.js:271
219 | msgid "Slider icon size"
220 | msgstr ""
221 |
222 | #: prefs.js:281
223 | msgid "Unify sliders"
224 | msgstr ""
225 |
226 | #: prefs.js:289
227 | msgid "Show remote label"
228 | msgstr ""
229 |
230 | #: prefs.js:297
231 | msgid "Receiver name as label"
232 | msgstr ""
233 |
234 | #: prefs.js:347
235 | msgid "Chromecast Options"
236 | msgstr ""
237 |
238 | #: prefs.js:351 prefs.js:663
239 | msgid "Device selection"
240 | msgstr ""
241 |
242 | #: prefs.js:378
243 | msgid "Subtitles"
244 | msgstr ""
245 |
246 | #: prefs.js:382
247 | msgid "Font family"
248 | msgstr ""
249 |
250 | #: prefs.js:401
251 | msgid "Font style"
252 | msgstr ""
253 |
254 | #. TRANSLATORS: One of "Speed" setting value
255 | #: prefs.js:403 node_scripts/messages.js:22
256 | msgid "Normal"
257 | msgstr "Vanlig"
258 |
259 | #: prefs.js:404
260 | msgid "Bold"
261 | msgstr ""
262 |
263 | #: prefs.js:405
264 | msgid "Italic"
265 | msgstr ""
266 |
267 | #: prefs.js:406
268 | msgid "Bold italic"
269 | msgstr ""
270 |
271 | #: prefs.js:416
272 | msgid "Scale factor"
273 | msgstr ""
274 |
275 | #: prefs.js:430
276 | msgid "Font color"
277 | msgstr ""
278 |
279 | #: prefs.js:442
280 | msgid "Font outline"
281 | msgstr ""
282 |
283 | #: prefs.js:472
284 | msgid "Background color"
285 | msgstr ""
286 |
287 | #: prefs.js:540
288 | msgid "Encoder"
289 | msgstr ""
290 |
291 | #: prefs.js:545
292 | msgid "Media Encoding"
293 | msgstr "Media umkoting"
294 |
295 | #: prefs.js:549
296 | msgid "Hardware acceleration"
297 | msgstr "Hardware acceleration"
298 |
299 | #: prefs.js:551 prefs.js:1141
300 | msgid "None"
301 | msgstr "Ongin"
302 |
303 | #: prefs.js:558
304 | msgid "Bitrate (Mbps)"
305 | msgstr "Bitferð (Mdps)"
306 |
307 | #: prefs.js:568
308 | msgid "Burn subtitles when transcoding video"
309 | msgstr ""
310 |
311 | #. TRANSLATORS: "Players" as video players
312 | #: prefs.js:583
313 | msgid "Extractor"
314 | msgstr ""
315 |
316 | #: prefs.js:588
317 | msgid "Subtitles Extraction"
318 | msgstr ""
319 |
320 | #. TRANSLATORS: "vttextract" is the name of executable, do not change
321 | #: prefs.js:593
322 | msgid "Add vttextract executable"
323 | msgstr ""
324 |
325 | #: prefs.js:602
326 | msgid "Reuse extracted subtitles"
327 | msgstr ""
328 |
329 | #. TRANSLATORS: Destination folder to save subtitles
330 | #: prefs.js:611
331 | msgid "Save folder"
332 | msgstr ""
333 |
334 | #: prefs.js:613
335 | msgid "Select folder"
336 | msgstr ""
337 |
338 | #: prefs.js:654
339 | msgid "Misc"
340 | msgstr ""
341 |
342 | #: prefs.js:676
343 | msgid "Web Player"
344 | msgstr ""
345 |
346 | #: prefs.js:680
347 | msgid "Subtitles scale factor"
348 | msgstr ""
349 |
350 | #. TRANSLATORS: The rest of extension settings
351 | #: prefs.js:691
352 | msgid "Miscellaneous"
353 | msgstr "Annað"
354 |
355 | #: prefs.js:695
356 | msgid "Music visualizer"
357 | msgstr "Vís endurgeving av tónleiki visuelt"
358 |
359 | #: prefs.js:703
360 | msgid "Nautilus/Nemo integration"
361 | msgstr ""
362 |
363 | #: prefs.js:809
364 | msgid "Install npm modules"
365 | msgstr ""
366 |
367 | #: prefs.js:833
368 | msgid "Installing..."
369 | msgstr ""
370 |
371 | #: prefs.js:887
372 | msgid "version:"
373 | msgstr ""
374 |
375 | #: prefs.js:894
376 | msgid "Developed by"
377 | msgstr ""
378 |
379 | #: prefs.js:903
380 | msgid "Extension Homepage"
381 | msgstr ""
382 |
383 | #: prefs.js:910
384 | msgid "Playercast Homepage"
385 | msgstr ""
386 |
387 | #: prefs.js:917
388 | msgid "Donate"
389 | msgstr ""
390 |
391 | #: prefs.js:931
392 | msgid "Main"
393 | msgstr ""
394 |
395 | #: prefs.js:935
396 | msgid "Remote"
397 | msgstr ""
398 |
399 | #. TRANSLATORS: Other extension settings
400 | #: prefs.js:944
401 | msgid "Other"
402 | msgstr ""
403 |
404 | #: prefs.js:957
405 | msgid "Add-ons"
406 | msgstr ""
407 |
408 | #: prefs.js:962
409 | msgid "Modules"
410 | msgstr ""
411 |
412 | #: prefs.js:966
413 | msgid "About"
414 | msgstr ""
415 |
416 | #: prefs.js:1074
417 | msgid "Manual IP Config"
418 | msgstr ""
419 |
420 | #: prefs.js:1131
421 | msgid "Auto"
422 | msgstr ""
423 |
424 | #: prefs.js:1132
425 | msgid "Name"
426 | msgstr ""
427 |
428 | #. TRANSLATORS: Text field temporary text
429 | #: prefs.js:1148
430 | msgid "Insert name"
431 | msgstr ""
432 |
433 | #. TRANSLATORS: Shown when scan for Chromecast devices is running
434 | #: prefs.js:1263
435 | msgid "Scanning..."
436 | msgstr "Leitar..."
437 |
438 | #: node_scripts/messages.js:4
439 | msgid "LOADING"
440 | msgstr "ARBEIÐUR"
441 |
442 | #: node_scripts/messages.js:5
443 | msgid "No file selected"
444 | msgstr "Eingin fíla vald"
445 |
446 | #: node_scripts/messages.js:6
447 | msgid "Receiver type is set to Chromecast"
448 | msgstr "Móttakara slag er stilla til Chromecast"
449 |
450 | #: node_scripts/messages.js:7
451 | msgid "Receiver type is set to Playercast app"
452 | msgstr ""
453 |
454 | #: node_scripts/messages.js:8
455 | msgid "Streaming process is still active"
456 | msgstr "Varping er enn í gongd"
457 |
458 | #. TRANSLATORS: Do not remove HTML tags
459 | #: node_scripts/messages.js:10
460 | msgid "Too many connections!Close all other tabs that are accessing this page in all browsers
"
461 | msgstr ""
462 |
463 | #. TRANSLATORS: This sentence will contain file path after end
464 | #: node_scripts/messages.js:12
465 | msgid "FFmpeg could not transcode file:"
466 | msgstr "FFMpeg kláraði ikki at umkota hesa fíluna:"
467 |
468 | #: node_scripts/messages.js:13
469 | msgid "FFmpeg path is incorrect"
470 | msgstr "FFmpeg slógin er ikki røtt"
471 |
472 | #. TRANSLATORS: This sentence will contain file path after end
473 | #: node_scripts/messages.js:15
474 | msgid "FFprobe could not process file:"
475 | msgstr "FFprobe fekk ikki viðgjørt hesa fíluna:"
476 |
477 | #: node_scripts/messages.js:16
478 | msgid "FFprobe path is incorrect"
479 | msgstr "FFprobe slógin er ikki røtt"
480 |
481 | #. TRANSLATORS: This sentence will contain file path after end
482 | #: node_scripts/messages.js:18
483 | msgid "Could not analyze selected file:"
484 | msgstr ""
485 |
486 | #: node_scripts/messages.js:20
487 | msgid "Speed"
488 | msgstr "Ferð"
489 |
490 | #: node_scripts/messages.js:25
491 | msgid "Device not found"
492 | msgstr "Tól ikki funnið"
493 |
494 | #: node_scripts/messages.js:26
495 | msgid "Failed to load media"
496 | msgstr "Fekk ikki lisið media"
497 |
498 | #: node_scripts/messages.js:27
499 | msgid "Could not connect to device"
500 | msgstr ""
501 |
502 | #: node_scripts/messages.js:28
503 | msgid "Verify device IP in extension preferences"
504 | msgstr ""
505 |
506 | #. TRANSLATORS: This sentence will contain file path after end
507 | #: node_scripts/messages.js:30
508 | msgid "Could not play file:"
509 | msgstr "Fekk ikki spælt fíluna:"
510 |
511 | #: node_scripts/messages.js:31
512 | msgid "Try again with transcoding enabled"
513 | msgstr "Royn aftur ímeðan umkoting er koblað til"
514 |
515 |
--------------------------------------------------------------------------------
/po/cast-to-tv/fr.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2020-02-04 16:20+0100\n"
6 | "PO-Revision-Date: 2020-06-25 14:05\n"
7 | "Last-Translator: \n"
8 | "Language-Team: French\n"
9 | "Language: fr_FR\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Project-ID: 356831\n"
16 | "X-Crowdin-Language: fr\n"
17 | "X-Crowdin-File: /master/po/cast-to-tv/cast-to-tv.pot\n"
18 | "X-Crowdin-File-ID: 24\n"
19 |
20 | #. TRANSLATORS: When "Cast Media" service is turned off
21 | #: widget.js:34 widget.js:111
22 | msgid "Cast Off"
23 | msgstr ""
24 |
25 | #: widget.js:39 file-chooser.js:80
26 | msgid "Video"
27 | msgstr ""
28 |
29 | #: widget.js:40
30 | msgid "Music"
31 | msgstr ""
32 |
33 | #: widget.js:41
34 | msgid "Picture"
35 | msgstr ""
36 |
37 | #: widget.js:42 widget.js:109
38 | msgid "Turn On"
39 | msgstr ""
40 |
41 | #: widget.js:43
42 | msgid "Cast Settings"
43 | msgstr ""
44 |
45 | #: widget.js:92
46 | msgid "Turn Off"
47 | msgstr ""
48 |
49 | #: widget.js:93
50 | msgid "Cast Media"
51 | msgstr ""
52 |
53 | #: widget.js:341
54 | msgid "Browser"
55 | msgstr ""
56 |
57 | #: playlist.js:344
58 | msgid "Playlist"
59 | msgstr ""
60 |
61 | #. TRANSLATORS: Button text when selected SINGLE file
62 | #: file-chooser.js:18
63 | msgid "Cast Selected File"
64 | msgstr ""
65 |
66 | #. TRANSLATORS: Button text when selected MULTIPLE files
67 | #: file-chooser.js:20
68 | msgid "Cast Selected Files"
69 | msgstr ""
70 |
71 | #: file-chooser.js:21
72 | msgid "Add to Playlist"
73 | msgstr ""
74 |
75 | #: file-chooser.js:77
76 | msgid "Transcode"
77 | msgstr ""
78 |
79 | #: file-chooser.js:81
80 | msgid "Audio"
81 | msgstr ""
82 |
83 | #: file-chooser.js:82
84 | msgid "Video + Audio"
85 | msgstr ""
86 |
87 | #: file-chooser.js:142 file-chooser.js:175 prefs.js:1283
88 | msgid "Automatic"
89 | msgstr ""
90 |
91 | #: file-chooser.js:270
92 | msgid "Cancel"
93 | msgstr ""
94 |
95 | #: file-chooser.js:279
96 | msgid "Add Subtitles"
97 | msgstr ""
98 |
99 | #: file-chooser.js:281
100 | msgid "Select Video"
101 | msgstr ""
102 |
103 | #: file-chooser.js:285
104 | msgid "Video Files"
105 | msgstr ""
106 |
107 | #: file-chooser.js:293
108 | msgid "Select Music"
109 | msgstr ""
110 |
111 | #: file-chooser.js:297
112 | msgid "Audio Files"
113 | msgstr ""
114 |
115 | #: file-chooser.js:305
116 | msgid "Select Picture"
117 | msgstr ""
118 |
119 | #: file-chooser.js:309
120 | msgid "Pictures"
121 | msgstr ""
122 |
123 | #: file-chooser.js:370
124 | msgid "Select Subtitles"
125 | msgstr ""
126 |
127 | #: file-chooser.js:374
128 | msgid "Subtitle Files"
129 | msgstr ""
130 |
131 | #. TRANSLATORS: Will contain dependency name at the beginning (e.g. Node.js is not installed)
132 | #: prefs.js:36
133 | msgid "is not installed"
134 | msgstr ""
135 |
136 | #: prefs.js:54
137 | msgid "Streaming in progress"
138 | msgstr ""
139 |
140 | #. TRANSLATORS: Keep line this short (otherwise extension prefs will strech)
141 | #: prefs.js:64
142 | msgid "Stop media transfer before accessing extension settings"
143 | msgstr ""
144 |
145 | #: prefs.js:94
146 | msgid "Main Options"
147 | msgstr ""
148 |
149 | #: prefs.js:98
150 | msgid "Receiver type"
151 | msgstr ""
152 |
153 | #. TRANSLATORS: "Playercast" is a name of an app, so do not change it
154 | #: prefs.js:102 prefs.js:659
155 | msgid "Playercast app"
156 | msgstr ""
157 |
158 | #. TRANSLATORS: Web browser or Media player app selection.
159 | #. This should be as short as possible e.g. "Browser | Player".
160 | #: prefs.js:105
161 | msgid "Web browser | Media player"
162 | msgstr ""
163 |
164 | #: prefs.js:110
165 | msgid "FFmpeg path"
166 | msgstr ""
167 |
168 | #: prefs.js:117
169 | msgid "FFprobe path"
170 | msgstr ""
171 |
172 | #: prefs.js:124
173 | msgid "Listening port"
174 | msgstr ""
175 |
176 | #: prefs.js:134
177 | msgid "Internal communication port"
178 | msgstr ""
179 |
180 | #: prefs.js:203
181 | msgid "Access web player from devices on local network"
182 | msgstr ""
183 |
184 | #: prefs.js:227
185 | msgid "Remote Controller"
186 | msgstr ""
187 |
188 | #: prefs.js:231
189 | msgid "Remote position"
190 | msgstr ""
191 |
192 | #: prefs.js:233
193 | msgid "Left"
194 | msgstr ""
195 |
196 | #: prefs.js:234
197 | msgid "Center (left side)"
198 | msgstr ""
199 |
200 | #: prefs.js:235
201 | msgid "Center (right side)"
202 | msgstr ""
203 |
204 | #: prefs.js:236
205 | msgid "Right"
206 | msgstr ""
207 |
208 | #: prefs.js:241
209 | msgid "Seek backward/forward (seconds)"
210 | msgstr ""
211 |
212 | #: prefs.js:251
213 | msgid "Slideshow time per picture (seconds)"
214 | msgstr ""
215 |
216 | #: prefs.js:261
217 | msgid "Media control buttons size"
218 | msgstr ""
219 |
220 | #: prefs.js:271
221 | msgid "Slider icon size"
222 | msgstr ""
223 |
224 | #: prefs.js:281
225 | msgid "Unify sliders"
226 | msgstr ""
227 |
228 | #: prefs.js:289
229 | msgid "Show remote label"
230 | msgstr ""
231 |
232 | #: prefs.js:297
233 | msgid "Receiver name as label"
234 | msgstr ""
235 |
236 | #: prefs.js:347
237 | msgid "Chromecast Options"
238 | msgstr ""
239 |
240 | #: prefs.js:351 prefs.js:663
241 | msgid "Device selection"
242 | msgstr ""
243 |
244 | #: prefs.js:378
245 | msgid "Subtitles"
246 | msgstr ""
247 |
248 | #: prefs.js:382
249 | msgid "Font family"
250 | msgstr ""
251 |
252 | #: prefs.js:401
253 | msgid "Font style"
254 | msgstr ""
255 |
256 | #. TRANSLATORS: One of "Speed" setting value
257 | #: prefs.js:403 node_scripts/messages.js:22
258 | msgid "Normal"
259 | msgstr ""
260 |
261 | #: prefs.js:404
262 | msgid "Bold"
263 | msgstr ""
264 |
265 | #: prefs.js:405
266 | msgid "Italic"
267 | msgstr ""
268 |
269 | #: prefs.js:406
270 | msgid "Bold italic"
271 | msgstr ""
272 |
273 | #: prefs.js:416
274 | msgid "Scale factor"
275 | msgstr ""
276 |
277 | #: prefs.js:430
278 | msgid "Font color"
279 | msgstr ""
280 |
281 | #: prefs.js:442
282 | msgid "Font outline"
283 | msgstr ""
284 |
285 | #: prefs.js:472
286 | msgid "Background color"
287 | msgstr ""
288 |
289 | #: prefs.js:540
290 | msgid "Encoder"
291 | msgstr ""
292 |
293 | #: prefs.js:545
294 | msgid "Media Encoding"
295 | msgstr ""
296 |
297 | #: prefs.js:549
298 | msgid "Hardware acceleration"
299 | msgstr ""
300 |
301 | #: prefs.js:551 prefs.js:1141
302 | msgid "None"
303 | msgstr ""
304 |
305 | #: prefs.js:558
306 | msgid "Bitrate (Mbps)"
307 | msgstr ""
308 |
309 | #: prefs.js:568
310 | msgid "Burn subtitles when transcoding video"
311 | msgstr ""
312 |
313 | #. TRANSLATORS: "Players" as video players
314 | #: prefs.js:583
315 | msgid "Extractor"
316 | msgstr ""
317 |
318 | #: prefs.js:588
319 | msgid "Subtitles Extraction"
320 | msgstr ""
321 |
322 | #. TRANSLATORS: "vttextract" is the name of executable, do not change
323 | #: prefs.js:593
324 | msgid "Add vttextract executable"
325 | msgstr ""
326 |
327 | #: prefs.js:602
328 | msgid "Reuse extracted subtitles"
329 | msgstr ""
330 |
331 | #. TRANSLATORS: Destination folder to save subtitles
332 | #: prefs.js:611
333 | msgid "Save folder"
334 | msgstr ""
335 |
336 | #: prefs.js:613
337 | msgid "Select folder"
338 | msgstr ""
339 |
340 | #: prefs.js:654
341 | msgid "Misc"
342 | msgstr ""
343 |
344 | #: prefs.js:676
345 | msgid "Web Player"
346 | msgstr ""
347 |
348 | #: prefs.js:680
349 | msgid "Subtitles scale factor"
350 | msgstr ""
351 |
352 | #. TRANSLATORS: The rest of extension settings
353 | #: prefs.js:691
354 | msgid "Miscellaneous"
355 | msgstr ""
356 |
357 | #: prefs.js:695
358 | msgid "Music visualizer"
359 | msgstr ""
360 |
361 | #: prefs.js:703
362 | msgid "Nautilus/Nemo integration"
363 | msgstr ""
364 |
365 | #: prefs.js:809
366 | msgid "Install npm modules"
367 | msgstr ""
368 |
369 | #: prefs.js:833
370 | msgid "Installing..."
371 | msgstr ""
372 |
373 | #: prefs.js:887
374 | msgid "version:"
375 | msgstr ""
376 |
377 | #: prefs.js:894
378 | msgid "Developed by"
379 | msgstr ""
380 |
381 | #: prefs.js:903
382 | msgid "Extension Homepage"
383 | msgstr ""
384 |
385 | #: prefs.js:910
386 | msgid "Playercast Homepage"
387 | msgstr ""
388 |
389 | #: prefs.js:917
390 | msgid "Donate"
391 | msgstr ""
392 |
393 | #: prefs.js:931
394 | msgid "Main"
395 | msgstr ""
396 |
397 | #: prefs.js:935
398 | msgid "Remote"
399 | msgstr ""
400 |
401 | #. TRANSLATORS: Other extension settings
402 | #: prefs.js:944
403 | msgid "Other"
404 | msgstr ""
405 |
406 | #: prefs.js:957
407 | msgid "Add-ons"
408 | msgstr ""
409 |
410 | #: prefs.js:962
411 | msgid "Modules"
412 | msgstr ""
413 |
414 | #: prefs.js:966
415 | msgid "About"
416 | msgstr ""
417 |
418 | #: prefs.js:1074
419 | msgid "Manual IP Config"
420 | msgstr ""
421 |
422 | #: prefs.js:1131
423 | msgid "Auto"
424 | msgstr ""
425 |
426 | #: prefs.js:1132
427 | msgid "Name"
428 | msgstr ""
429 |
430 | #. TRANSLATORS: Text field temporary text
431 | #: prefs.js:1148
432 | msgid "Insert name"
433 | msgstr ""
434 |
435 | #. TRANSLATORS: Shown when scan for Chromecast devices is running
436 | #: prefs.js:1263
437 | msgid "Scanning..."
438 | msgstr ""
439 |
440 | #: node_scripts/messages.js:4
441 | msgid "LOADING"
442 | msgstr ""
443 |
444 | #: node_scripts/messages.js:5
445 | msgid "No file selected"
446 | msgstr ""
447 |
448 | #: node_scripts/messages.js:6
449 | msgid "Receiver type is set to Chromecast"
450 | msgstr ""
451 |
452 | #: node_scripts/messages.js:7
453 | msgid "Receiver type is set to Playercast app"
454 | msgstr ""
455 |
456 | #: node_scripts/messages.js:8
457 | msgid "Streaming process is still active"
458 | msgstr ""
459 |
460 | #. TRANSLATORS: Do not remove HTML tags
461 | #: node_scripts/messages.js:10
462 | msgid "Too many connections!Close all other tabs that are accessing this page in all browsers
"
463 | msgstr ""
464 |
465 | #. TRANSLATORS: This sentence will contain file path after end
466 | #: node_scripts/messages.js:12
467 | msgid "FFmpeg could not transcode file:"
468 | msgstr ""
469 |
470 | #: node_scripts/messages.js:13
471 | msgid "FFmpeg path is incorrect"
472 | msgstr ""
473 |
474 | #. TRANSLATORS: This sentence will contain file path after end
475 | #: node_scripts/messages.js:15
476 | msgid "FFprobe could not process file:"
477 | msgstr ""
478 |
479 | #: node_scripts/messages.js:16
480 | msgid "FFprobe path is incorrect"
481 | msgstr ""
482 |
483 | #. TRANSLATORS: This sentence will contain file path after end
484 | #: node_scripts/messages.js:18
485 | msgid "Could not analyze selected file:"
486 | msgstr ""
487 |
488 | #: node_scripts/messages.js:20
489 | msgid "Speed"
490 | msgstr ""
491 |
492 | #: node_scripts/messages.js:25
493 | msgid "Device not found"
494 | msgstr ""
495 |
496 | #: node_scripts/messages.js:26
497 | msgid "Failed to load media"
498 | msgstr ""
499 |
500 | #: node_scripts/messages.js:27
501 | msgid "Could not connect to device"
502 | msgstr ""
503 |
504 | #: node_scripts/messages.js:28
505 | msgid "Verify device IP in extension preferences"
506 | msgstr ""
507 |
508 | #. TRANSLATORS: This sentence will contain file path after end
509 | #: node_scripts/messages.js:30
510 | msgid "Could not play file:"
511 | msgstr ""
512 |
513 | #: node_scripts/messages.js:31
514 | msgid "Try again with transcoding enabled"
515 | msgstr ""
516 |
517 |
--------------------------------------------------------------------------------
/po/cast-to-tv/nl.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2020-02-04 16:20+0100\n"
6 | "PO-Revision-Date: 2020-02-04 15:48\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Dutch\n"
9 | "Language: nl_NL\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: nl\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv/cast-to-tv.pot\n"
17 |
18 | #. TRANSLATORS: When "Cast Media" service is turned off
19 | #: widget.js:34 widget.js:111
20 | msgid "Cast Off"
21 | msgstr "Casten is uitgeschakeld"
22 |
23 | #: widget.js:39 file-chooser.js:80
24 | msgid "Video"
25 | msgstr "Video"
26 |
27 | #: widget.js:40
28 | msgid "Music"
29 | msgstr "Muziek"
30 |
31 | #: widget.js:41
32 | msgid "Picture"
33 | msgstr "Afbeelding"
34 |
35 | #: widget.js:42 widget.js:109
36 | msgid "Turn On"
37 | msgstr "Inschakelen"
38 |
39 | #: widget.js:43
40 | msgid "Cast Settings"
41 | msgstr "Cast-instellingen"
42 |
43 | #: widget.js:92
44 | msgid "Turn Off"
45 | msgstr "Uitschakelen"
46 |
47 | #: widget.js:93
48 | msgid "Cast Media"
49 | msgstr "Media casten"
50 |
51 | #: widget.js:341
52 | msgid "Browser"
53 | msgstr ""
54 |
55 | #: playlist.js:344
56 | msgid "Playlist"
57 | msgstr ""
58 |
59 | #. TRANSLATORS: Button text when selected SINGLE file
60 | #: file-chooser.js:18
61 | msgid "Cast Selected File"
62 | msgstr "Bestand casten"
63 |
64 | #. TRANSLATORS: Button text when selected MULTIPLE files
65 | #: file-chooser.js:20
66 | msgid "Cast Selected Files"
67 | msgstr "Bestanden casten"
68 |
69 | #: file-chooser.js:21
70 | msgid "Add to Playlist"
71 | msgstr ""
72 |
73 | #: file-chooser.js:77
74 | msgid "Transcode"
75 | msgstr ""
76 |
77 | #: file-chooser.js:81
78 | msgid "Audio"
79 | msgstr ""
80 |
81 | #: file-chooser.js:82
82 | msgid "Video + Audio"
83 | msgstr ""
84 |
85 | #: file-chooser.js:142 file-chooser.js:175 prefs.js:1283
86 | msgid "Automatic"
87 | msgstr "Automatisch"
88 |
89 | #: file-chooser.js:270
90 | msgid "Cancel"
91 | msgstr "Annuleren"
92 |
93 | #: file-chooser.js:279
94 | msgid "Add Subtitles"
95 | msgstr "Ondertiteling toevoegen"
96 |
97 | #: file-chooser.js:281
98 | msgid "Select Video"
99 | msgstr "Video selecteren"
100 |
101 | #: file-chooser.js:285
102 | msgid "Video Files"
103 | msgstr "Videobestanden"
104 |
105 | #: file-chooser.js:293
106 | msgid "Select Music"
107 | msgstr "Muziek selecteren"
108 |
109 | #: file-chooser.js:297
110 | msgid "Audio Files"
111 | msgstr "Audiobestanden"
112 |
113 | #: file-chooser.js:305
114 | msgid "Select Picture"
115 | msgstr "Afbeelding selecteren"
116 |
117 | #: file-chooser.js:309
118 | msgid "Pictures"
119 | msgstr "Afbeeldingen"
120 |
121 | #: file-chooser.js:370
122 | msgid "Select Subtitles"
123 | msgstr "Ondertiteling selecteren"
124 |
125 | #: file-chooser.js:374
126 | msgid "Subtitle Files"
127 | msgstr "Ondertitelingsbestanden"
128 |
129 | #. TRANSLATORS: Will contain dependency name at the beginning (e.g. Node.js is not installed)
130 | #: prefs.js:36
131 | msgid "is not installed"
132 | msgstr "is niet geïnstalleerd"
133 |
134 | #: prefs.js:54
135 | msgid "Streaming in progress"
136 | msgstr "Bezig met streamen"
137 |
138 | #. TRANSLATORS: Keep line this short (otherwise extension prefs will strech)
139 | #: prefs.js:64
140 | msgid "Stop media transfer before accessing extension settings"
141 | msgstr "Stop de mediaoverdracht voordat je de uitbreidingsinstellingen opent"
142 |
143 | #: prefs.js:94
144 | msgid "Main Options"
145 | msgstr "Algemene instellingen"
146 |
147 | #: prefs.js:98
148 | msgid "Receiver type"
149 | msgstr "Soort ontvanger"
150 |
151 | #. TRANSLATORS: "Playercast" is a name of an app, so do not change it
152 | #: prefs.js:102 prefs.js:659
153 | msgid "Playercast app"
154 | msgstr "Playercast-app"
155 |
156 | #. TRANSLATORS: Web browser or Media player app selection.
157 | #. This should be as short as possible e.g. "Browser | Player".
158 | #: prefs.js:105
159 | msgid "Web browser | Media player"
160 | msgstr "Webbrowser | Mediaspeler"
161 |
162 | #: prefs.js:110
163 | msgid "FFmpeg path"
164 | msgstr "FFmpeg-pad"
165 |
166 | #: prefs.js:117
167 | msgid "FFprobe path"
168 | msgstr "FFprobe-pad"
169 |
170 | #: prefs.js:124
171 | msgid "Listening port"
172 | msgstr "Luisteren op poort"
173 |
174 | #: prefs.js:134
175 | msgid "Internal communication port"
176 | msgstr ""
177 |
178 | #: prefs.js:203
179 | msgid "Access web player from devices on local network"
180 | msgstr "Toegang tot de webspeler vanaf apparaten op het lokale netwerk"
181 |
182 | #: prefs.js:227
183 | msgid "Remote Controller"
184 | msgstr "Afstandsbediening"
185 |
186 | #: prefs.js:231
187 | msgid "Remote position"
188 | msgstr "Positie"
189 |
190 | #: prefs.js:233
191 | msgid "Left"
192 | msgstr "Links"
193 |
194 | #: prefs.js:234
195 | msgid "Center (left side)"
196 | msgstr "Gecentreerd (linkerkant)"
197 |
198 | #: prefs.js:235
199 | msgid "Center (right side)"
200 | msgstr "Gecentreerd (rechterkant)"
201 |
202 | #: prefs.js:236
203 | msgid "Right"
204 | msgstr "Rechts"
205 |
206 | #: prefs.js:241
207 | msgid "Seek backward/forward (seconds)"
208 | msgstr "Terug-/Vooruitspoelen (in seconden)"
209 |
210 | #: prefs.js:251
211 | msgid "Slideshow time per picture (seconds)"
212 | msgstr ""
213 |
214 | #: prefs.js:261
215 | msgid "Media control buttons size"
216 | msgstr ""
217 |
218 | #: prefs.js:271
219 | msgid "Slider icon size"
220 | msgstr ""
221 |
222 | #: prefs.js:281
223 | msgid "Unify sliders"
224 | msgstr "Schuifregelaars gelijktrekken"
225 |
226 | #: prefs.js:289
227 | msgid "Show remote label"
228 | msgstr "Labels tonen"
229 |
230 | #: prefs.js:297
231 | msgid "Receiver name as label"
232 | msgstr ""
233 |
234 | #: prefs.js:347
235 | msgid "Chromecast Options"
236 | msgstr "Chromecast-instellingen"
237 |
238 | #: prefs.js:351 prefs.js:663
239 | msgid "Device selection"
240 | msgstr "Apparaatkeuze"
241 |
242 | #: prefs.js:378
243 | msgid "Subtitles"
244 | msgstr "Ondertiteling"
245 |
246 | #: prefs.js:382
247 | msgid "Font family"
248 | msgstr "Lettertype"
249 |
250 | #: prefs.js:401
251 | msgid "Font style"
252 | msgstr "Lettertypestijl"
253 |
254 | #. TRANSLATORS: One of "Speed" setting value
255 | #: prefs.js:403 node_scripts/messages.js:22
256 | msgid "Normal"
257 | msgstr "Normaal"
258 |
259 | #: prefs.js:404
260 | msgid "Bold"
261 | msgstr "Vetgedrukt"
262 |
263 | #: prefs.js:405
264 | msgid "Italic"
265 | msgstr "Cursief"
266 |
267 | #: prefs.js:406
268 | msgid "Bold italic"
269 | msgstr "Vetgedrukt cursief"
270 |
271 | #: prefs.js:416
272 | msgid "Scale factor"
273 | msgstr "Schaalfactor"
274 |
275 | #: prefs.js:430
276 | msgid "Font color"
277 | msgstr "Lettertypekleur"
278 |
279 | #: prefs.js:442
280 | msgid "Font outline"
281 | msgstr "Lettertype-omlijning"
282 |
283 | #: prefs.js:472
284 | msgid "Background color"
285 | msgstr "Achtergrondkleur"
286 |
287 | #: prefs.js:540
288 | msgid "Encoder"
289 | msgstr ""
290 |
291 | #: prefs.js:545
292 | msgid "Media Encoding"
293 | msgstr "Mediacodering"
294 |
295 | #: prefs.js:549
296 | msgid "Hardware acceleration"
297 | msgstr "Hardwareversnelling"
298 |
299 | #: prefs.js:551 prefs.js:1141
300 | msgid "None"
301 | msgstr "Geen"
302 |
303 | #: prefs.js:558
304 | msgid "Bitrate (Mbps)"
305 | msgstr "Bitsnelheid (Mbps)"
306 |
307 | #: prefs.js:568
308 | msgid "Burn subtitles when transcoding video"
309 | msgstr ""
310 |
311 | #. TRANSLATORS: "Players" as video players
312 | #: prefs.js:583
313 | msgid "Extractor"
314 | msgstr ""
315 |
316 | #: prefs.js:588
317 | msgid "Subtitles Extraction"
318 | msgstr ""
319 |
320 | #. TRANSLATORS: "vttextract" is the name of executable, do not change
321 | #: prefs.js:593
322 | msgid "Add vttextract executable"
323 | msgstr ""
324 |
325 | #: prefs.js:602
326 | msgid "Reuse extracted subtitles"
327 | msgstr ""
328 |
329 | #. TRANSLATORS: Destination folder to save subtitles
330 | #: prefs.js:611
331 | msgid "Save folder"
332 | msgstr ""
333 |
334 | #: prefs.js:613
335 | msgid "Select folder"
336 | msgstr ""
337 |
338 | #: prefs.js:654
339 | msgid "Misc"
340 | msgstr ""
341 |
342 | #: prefs.js:676
343 | msgid "Web Player"
344 | msgstr "Webspeler"
345 |
346 | #: prefs.js:680
347 | msgid "Subtitles scale factor"
348 | msgstr "Schaal van ondertiteling"
349 |
350 | #. TRANSLATORS: The rest of extension settings
351 | #: prefs.js:691
352 | msgid "Miscellaneous"
353 | msgstr "Overig"
354 |
355 | #: prefs.js:695
356 | msgid "Music visualizer"
357 | msgstr "Muziekvisualisatie"
358 |
359 | #: prefs.js:703
360 | msgid "Nautilus/Nemo integration"
361 | msgstr ""
362 |
363 | #: prefs.js:809
364 | msgid "Install npm modules"
365 | msgstr "npm-modules installeren"
366 |
367 | #: prefs.js:833
368 | msgid "Installing..."
369 | msgstr "Bezig met installeren..."
370 |
371 | #: prefs.js:887
372 | msgid "version:"
373 | msgstr "versie:"
374 |
375 | #: prefs.js:894
376 | msgid "Developed by"
377 | msgstr "Ontwikkeld door"
378 |
379 | #: prefs.js:903
380 | msgid "Extension Homepage"
381 | msgstr "Website"
382 |
383 | #: prefs.js:910
384 | msgid "Playercast Homepage"
385 | msgstr "Playercast-website"
386 |
387 | #: prefs.js:917
388 | msgid "Donate"
389 | msgstr "Doneren"
390 |
391 | #: prefs.js:931
392 | msgid "Main"
393 | msgstr "Algemeen"
394 |
395 | #: prefs.js:935
396 | msgid "Remote"
397 | msgstr "Op afstand"
398 |
399 | #. TRANSLATORS: Other extension settings
400 | #: prefs.js:944
401 | msgid "Other"
402 | msgstr "Anders"
403 |
404 | #: prefs.js:957
405 | msgid "Add-ons"
406 | msgstr "Uitbreidingen"
407 |
408 | #: prefs.js:962
409 | msgid "Modules"
410 | msgstr "Modules"
411 |
412 | #: prefs.js:966
413 | msgid "About"
414 | msgstr "Over"
415 |
416 | #: prefs.js:1074
417 | msgid "Manual IP Config"
418 | msgstr ""
419 |
420 | #: prefs.js:1131
421 | msgid "Auto"
422 | msgstr ""
423 |
424 | #: prefs.js:1132
425 | msgid "Name"
426 | msgstr ""
427 |
428 | #. TRANSLATORS: Text field temporary text
429 | #: prefs.js:1148
430 | msgid "Insert name"
431 | msgstr ""
432 |
433 | #. TRANSLATORS: Shown when scan for Chromecast devices is running
434 | #: prefs.js:1263
435 | msgid "Scanning..."
436 | msgstr "Bezig met zoeken..."
437 |
438 | #: node_scripts/messages.js:4
439 | msgid "LOADING"
440 | msgstr "BEZIG MET LADEN"
441 |
442 | #: node_scripts/messages.js:5
443 | msgid "No file selected"
444 | msgstr "Geen bestand gekozen"
445 |
446 | #: node_scripts/messages.js:6
447 | msgid "Receiver type is set to Chromecast"
448 | msgstr "Het soort ontvanger is ingesteld op Chromecast"
449 |
450 | #: node_scripts/messages.js:7
451 | msgid "Receiver type is set to Playercast app"
452 | msgstr "Het soort ontvanger is ingesteld op Playercast"
453 |
454 | #: node_scripts/messages.js:8
455 | msgid "Streaming process is still active"
456 | msgstr "Het streamproces loopt nog"
457 |
458 | #. TRANSLATORS: Do not remove HTML tags
459 | #: node_scripts/messages.js:10
460 | msgid "Too many connections!Close all other tabs that are accessing this page in all browsers
"
461 | msgstr "Te veel verbindingen!Sluit alle andere tabbladen die toegang hebben tot deze pagina in alle browsers
"
462 |
463 | #. TRANSLATORS: This sentence will contain file path after end
464 | #: node_scripts/messages.js:12
465 | msgid "FFmpeg could not transcode file:"
466 | msgstr "FFmpeg kan het bestand niet converteren:"
467 |
468 | #: node_scripts/messages.js:13
469 | msgid "FFmpeg path is incorrect"
470 | msgstr "Onjuist FFmpeg-pad"
471 |
472 | #. TRANSLATORS: This sentence will contain file path after end
473 | #: node_scripts/messages.js:15
474 | msgid "FFprobe could not process file:"
475 | msgstr "FFprobe kan het bestand niet verwerken:"
476 |
477 | #: node_scripts/messages.js:16
478 | msgid "FFprobe path is incorrect"
479 | msgstr "Onjuist FFprobe-pad"
480 |
481 | #. TRANSLATORS: This sentence will contain file path after end
482 | #: node_scripts/messages.js:18
483 | msgid "Could not analyze selected file:"
484 | msgstr ""
485 |
486 | #: node_scripts/messages.js:20
487 | msgid "Speed"
488 | msgstr "Snelheid"
489 |
490 | #: node_scripts/messages.js:25
491 | msgid "Device not found"
492 | msgstr "Apparaat niet gevonden"
493 |
494 | #: node_scripts/messages.js:26
495 | msgid "Failed to load media"
496 | msgstr "Kan media niet laden"
497 |
498 | #: node_scripts/messages.js:27
499 | msgid "Could not connect to device"
500 | msgstr ""
501 |
502 | #: node_scripts/messages.js:28
503 | msgid "Verify device IP in extension preferences"
504 | msgstr ""
505 |
506 | #. TRANSLATORS: This sentence will contain file path after end
507 | #: node_scripts/messages.js:30
508 | msgid "Could not play file:"
509 | msgstr "Kan bestand niet afspelen:"
510 |
511 | #: node_scripts/messages.js:31
512 | msgid "Try again with transcoding enabled"
513 | msgstr "Probeer het opnieuw met converteren ingeschakeld"
514 |
515 |
--------------------------------------------------------------------------------
/po/cast-to-tv/ro.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: cast-to-tv\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 2020-02-04 16:20+0100\n"
6 | "PO-Revision-Date: 2020-02-21 10:05\n"
7 | "Last-Translator: FULL NAME \n"
8 | "Language-Team: Romanian\n"
9 | "Language: ro_RO\n"
10 | "MIME-Version: 1.0\n"
11 | "Content-Type: text/plain; charset=UTF-8\n"
12 | "Content-Transfer-Encoding: 8bit\n"
13 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
14 | "X-Crowdin-Project: cast-to-tv\n"
15 | "X-Crowdin-Language: ro\n"
16 | "X-Crowdin-File: /master/po/cast-to-tv/cast-to-tv.pot\n"
17 |
18 | #. TRANSLATORS: When "Cast Media" service is turned off
19 | #: widget.js:34 widget.js:111
20 | msgid "Cast Off"
21 | msgstr ""
22 |
23 | #: widget.js:39 file-chooser.js:80
24 | msgid "Video"
25 | msgstr ""
26 |
27 | #: widget.js:40
28 | msgid "Music"
29 | msgstr ""
30 |
31 | #: widget.js:41
32 | msgid "Picture"
33 | msgstr ""
34 |
35 | #: widget.js:42 widget.js:109
36 | msgid "Turn On"
37 | msgstr ""
38 |
39 | #: widget.js:43
40 | msgid "Cast Settings"
41 | msgstr ""
42 |
43 | #: widget.js:92
44 | msgid "Turn Off"
45 | msgstr ""
46 |
47 | #: widget.js:93
48 | msgid "Cast Media"
49 | msgstr ""
50 |
51 | #: widget.js:341
52 | msgid "Browser"
53 | msgstr ""
54 |
55 | #: playlist.js:344
56 | msgid "Playlist"
57 | msgstr ""
58 |
59 | #. TRANSLATORS: Button text when selected SINGLE file
60 | #: file-chooser.js:18
61 | msgid "Cast Selected File"
62 | msgstr ""
63 |
64 | #. TRANSLATORS: Button text when selected MULTIPLE files
65 | #: file-chooser.js:20
66 | msgid "Cast Selected Files"
67 | msgstr ""
68 |
69 | #: file-chooser.js:21
70 | msgid "Add to Playlist"
71 | msgstr ""
72 |
73 | #: file-chooser.js:77
74 | msgid "Transcode"
75 | msgstr ""
76 |
77 | #: file-chooser.js:81
78 | msgid "Audio"
79 | msgstr ""
80 |
81 | #: file-chooser.js:82
82 | msgid "Video + Audio"
83 | msgstr ""
84 |
85 | #: file-chooser.js:142 file-chooser.js:175 prefs.js:1283
86 | msgid "Automatic"
87 | msgstr ""
88 |
89 | #: file-chooser.js:270
90 | msgid "Cancel"
91 | msgstr ""
92 |
93 | #: file-chooser.js:279
94 | msgid "Add Subtitles"
95 | msgstr ""
96 |
97 | #: file-chooser.js:281
98 | msgid "Select Video"
99 | msgstr ""
100 |
101 | #: file-chooser.js:285
102 | msgid "Video Files"
103 | msgstr ""
104 |
105 | #: file-chooser.js:293
106 | msgid "Select Music"
107 | msgstr ""
108 |
109 | #: file-chooser.js:297
110 | msgid "Audio Files"
111 | msgstr ""
112 |
113 | #: file-chooser.js:305
114 | msgid "Select Picture"
115 | msgstr ""
116 |
117 | #: file-chooser.js:309
118 | msgid "Pictures"
119 | msgstr ""
120 |
121 | #: file-chooser.js:370
122 | msgid "Select Subtitles"
123 | msgstr ""
124 |
125 | #: file-chooser.js:374
126 | msgid "Subtitle Files"
127 | msgstr ""
128 |
129 | #. TRANSLATORS: Will contain dependency name at the beginning (e.g. Node.js is not installed)
130 | #: prefs.js:36
131 | msgid "is not installed"
132 | msgstr ""
133 |
134 | #: prefs.js:54
135 | msgid "Streaming in progress"
136 | msgstr ""
137 |
138 | #. TRANSLATORS: Keep line this short (otherwise extension prefs will strech)
139 | #: prefs.js:64
140 | msgid "Stop media transfer before accessing extension settings"
141 | msgstr ""
142 |
143 | #: prefs.js:94
144 | msgid "Main Options"
145 | msgstr ""
146 |
147 | #: prefs.js:98
148 | msgid "Receiver type"
149 | msgstr ""
150 |
151 | #. TRANSLATORS: "Playercast" is a name of an app, so do not change it
152 | #: prefs.js:102 prefs.js:659
153 | msgid "Playercast app"
154 | msgstr ""
155 |
156 | #. TRANSLATORS: Web browser or Media player app selection.
157 | #. This should be as short as possible e.g. "Browser | Player".
158 | #: prefs.js:105
159 | msgid "Web browser | Media player"
160 | msgstr ""
161 |
162 | #: prefs.js:110
163 | msgid "FFmpeg path"
164 | msgstr ""
165 |
166 | #: prefs.js:117
167 | msgid "FFprobe path"
168 | msgstr ""
169 |
170 | #: prefs.js:124
171 | msgid "Listening port"
172 | msgstr ""
173 |
174 | #: prefs.js:134
175 | msgid "Internal communication port"
176 | msgstr ""
177 |
178 | #: prefs.js:203
179 | msgid "Access web player from devices on local network"
180 | msgstr ""
181 |
182 | #: prefs.js:227
183 | msgid "Remote Controller"
184 | msgstr ""
185 |
186 | #: prefs.js:231
187 | msgid "Remote position"
188 | msgstr ""
189 |
190 | #: prefs.js:233
191 | msgid "Left"
192 | msgstr ""
193 |
194 | #: prefs.js:234
195 | msgid "Center (left side)"
196 | msgstr ""
197 |
198 | #: prefs.js:235
199 | msgid "Center (right side)"
200 | msgstr ""
201 |
202 | #: prefs.js:236
203 | msgid "Right"
204 | msgstr ""
205 |
206 | #: prefs.js:241
207 | msgid "Seek backward/forward (seconds)"
208 | msgstr ""
209 |
210 | #: prefs.js:251
211 | msgid "Slideshow time per picture (seconds)"
212 | msgstr ""
213 |
214 | #: prefs.js:261
215 | msgid "Media control buttons size"
216 | msgstr ""
217 |
218 | #: prefs.js:271
219 | msgid "Slider icon size"
220 | msgstr ""
221 |
222 | #: prefs.js:281
223 | msgid "Unify sliders"
224 | msgstr ""
225 |
226 | #: prefs.js:289
227 | msgid "Show remote label"
228 | msgstr ""
229 |
230 | #: prefs.js:297
231 | msgid "Receiver name as label"
232 | msgstr ""
233 |
234 | #: prefs.js:347
235 | msgid "Chromecast Options"
236 | msgstr ""
237 |
238 | #: prefs.js:351 prefs.js:663
239 | msgid "Device selection"
240 | msgstr ""
241 |
242 | #: prefs.js:378
243 | msgid "Subtitles"
244 | msgstr ""
245 |
246 | #: prefs.js:382
247 | msgid "Font family"
248 | msgstr ""
249 |
250 | #: prefs.js:401
251 | msgid "Font style"
252 | msgstr ""
253 |
254 | #. TRANSLATORS: One of "Speed" setting value
255 | #: prefs.js:403 node_scripts/messages.js:22
256 | msgid "Normal"
257 | msgstr ""
258 |
259 | #: prefs.js:404
260 | msgid "Bold"
261 | msgstr ""
262 |
263 | #: prefs.js:405
264 | msgid "Italic"
265 | msgstr ""
266 |
267 | #: prefs.js:406
268 | msgid "Bold italic"
269 | msgstr ""
270 |
271 | #: prefs.js:416
272 | msgid "Scale factor"
273 | msgstr ""
274 |
275 | #: prefs.js:430
276 | msgid "Font color"
277 | msgstr ""
278 |
279 | #: prefs.js:442
280 | msgid "Font outline"
281 | msgstr ""
282 |
283 | #: prefs.js:472
284 | msgid "Background color"
285 | msgstr ""
286 |
287 | #: prefs.js:540
288 | msgid "Encoder"
289 | msgstr ""
290 |
291 | #: prefs.js:545
292 | msgid "Media Encoding"
293 | msgstr ""
294 |
295 | #: prefs.js:549
296 | msgid "Hardware acceleration"
297 | msgstr ""
298 |
299 | #: prefs.js:551 prefs.js:1141
300 | msgid "None"
301 | msgstr ""
302 |
303 | #: prefs.js:558
304 | msgid "Bitrate (Mbps)"
305 | msgstr ""
306 |
307 | #: prefs.js:568
308 | msgid "Burn subtitles when transcoding video"
309 | msgstr ""
310 |
311 | #. TRANSLATORS: "Players" as video players
312 | #: prefs.js:583
313 | msgid "Extractor"
314 | msgstr ""
315 |
316 | #: prefs.js:588
317 | msgid "Subtitles Extraction"
318 | msgstr ""
319 |
320 | #. TRANSLATORS: "vttextract" is the name of executable, do not change
321 | #: prefs.js:593
322 | msgid "Add vttextract executable"
323 | msgstr ""
324 |
325 | #: prefs.js:602
326 | msgid "Reuse extracted subtitles"
327 | msgstr ""
328 |
329 | #. TRANSLATORS: Destination folder to save subtitles
330 | #: prefs.js:611
331 | msgid "Save folder"
332 | msgstr ""
333 |
334 | #: prefs.js:613
335 | msgid "Select folder"
336 | msgstr ""
337 |
338 | #: prefs.js:654
339 | msgid "Misc"
340 | msgstr ""
341 |
342 | #: prefs.js:676
343 | msgid "Web Player"
344 | msgstr ""
345 |
346 | #: prefs.js:680
347 | msgid "Subtitles scale factor"
348 | msgstr ""
349 |
350 | #. TRANSLATORS: The rest of extension settings
351 | #: prefs.js:691
352 | msgid "Miscellaneous"
353 | msgstr ""
354 |
355 | #: prefs.js:695
356 | msgid "Music visualizer"
357 | msgstr ""
358 |
359 | #: prefs.js:703
360 | msgid "Nautilus/Nemo integration"
361 | msgstr ""
362 |
363 | #: prefs.js:809
364 | msgid "Install npm modules"
365 | msgstr ""
366 |
367 | #: prefs.js:833
368 | msgid "Installing..."
369 | msgstr ""
370 |
371 | #: prefs.js:887
372 | msgid "version:"
373 | msgstr ""
374 |
375 | #: prefs.js:894
376 | msgid "Developed by"
377 | msgstr ""
378 |
379 | #: prefs.js:903
380 | msgid "Extension Homepage"
381 | msgstr ""
382 |
383 | #: prefs.js:910
384 | msgid "Playercast Homepage"
385 | msgstr ""
386 |
387 | #: prefs.js:917
388 | msgid "Donate"
389 | msgstr ""
390 |
391 | #: prefs.js:931
392 | msgid "Main"
393 | msgstr ""
394 |
395 | #: prefs.js:935
396 | msgid "Remote"
397 | msgstr ""
398 |
399 | #. TRANSLATORS: Other extension settings
400 | #: prefs.js:944
401 | msgid "Other"
402 | msgstr ""
403 |
404 | #: prefs.js:957
405 | msgid "Add-ons"
406 | msgstr ""
407 |
408 | #: prefs.js:962
409 | msgid "Modules"
410 | msgstr ""
411 |
412 | #: prefs.js:966
413 | msgid "About"
414 | msgstr ""
415 |
416 | #: prefs.js:1074
417 | msgid "Manual IP Config"
418 | msgstr ""
419 |
420 | #: prefs.js:1131
421 | msgid "Auto"
422 | msgstr ""
423 |
424 | #: prefs.js:1132
425 | msgid "Name"
426 | msgstr ""
427 |
428 | #. TRANSLATORS: Text field temporary text
429 | #: prefs.js:1148
430 | msgid "Insert name"
431 | msgstr ""
432 |
433 | #. TRANSLATORS: Shown when scan for Chromecast devices is running
434 | #: prefs.js:1263
435 | msgid "Scanning..."
436 | msgstr ""
437 |
438 | #: node_scripts/messages.js:4
439 | msgid "LOADING"
440 | msgstr ""
441 |
442 | #: node_scripts/messages.js:5
443 | msgid "No file selected"
444 | msgstr ""
445 |
446 | #: node_scripts/messages.js:6
447 | msgid "Receiver type is set to Chromecast"
448 | msgstr ""
449 |
450 | #: node_scripts/messages.js:7
451 | msgid "Receiver type is set to Playercast app"
452 | msgstr ""
453 |
454 | #: node_scripts/messages.js:8
455 | msgid "Streaming process is still active"
456 | msgstr ""
457 |
458 | #. TRANSLATORS: Do not remove HTML tags
459 | #: node_scripts/messages.js:10
460 | msgid "Too many connections!Close all other tabs that are accessing this page in all browsers
"
461 | msgstr ""
462 |
463 | #. TRANSLATORS: This sentence will contain file path after end
464 | #: node_scripts/messages.js:12
465 | msgid "FFmpeg could not transcode file:"
466 | msgstr ""
467 |
468 | #: node_scripts/messages.js:13
469 | msgid "FFmpeg path is incorrect"
470 | msgstr ""
471 |
472 | #. TRANSLATORS: This sentence will contain file path after end
473 | #: node_scripts/messages.js:15
474 | msgid "FFprobe could not process file:"
475 | msgstr ""
476 |
477 | #: node_scripts/messages.js:16
478 | msgid "FFprobe path is incorrect"
479 | msgstr ""
480 |
481 | #. TRANSLATORS: This sentence will contain file path after end
482 | #: node_scripts/messages.js:18
483 | msgid "Could not analyze selected file:"
484 | msgstr ""
485 |
486 | #: node_scripts/messages.js:20
487 | msgid "Speed"
488 | msgstr ""
489 |
490 | #: node_scripts/messages.js:25
491 | msgid "Device not found"
492 | msgstr ""
493 |
494 | #: node_scripts/messages.js:26
495 | msgid "Failed to load media"
496 | msgstr ""
497 |
498 | #: node_scripts/messages.js:27
499 | msgid "Could not connect to device"
500 | msgstr ""
501 |
502 | #: node_scripts/messages.js:28
503 | msgid "Verify device IP in extension preferences"
504 | msgstr ""
505 |
506 | #. TRANSLATORS: This sentence will contain file path after end
507 | #: node_scripts/messages.js:30
508 | msgid "Could not play file:"
509 | msgstr ""
510 |
511 | #: node_scripts/messages.js:31
512 | msgid "Try again with transcoding enabled"
513 | msgstr ""
514 |
515 |
--------------------------------------------------------------------------------
/prefs_shared.js:
--------------------------------------------------------------------------------
1 | imports.gi.versions.Gtk = '3.0';
2 |
3 | const { Gtk } = imports.gi;
4 | let gridIndex = 0;
5 |
6 | var SettingLabel = class SharedSettingLabel
7 | {
8 | constructor(text, isTitle, isTopMargin)
9 | {
10 | let label = null;
11 | let marginLR = 0;
12 | let marginTop = 0;
13 |
14 | if(isTitle) label = '' + text + '';
15 | else
16 | {
17 | label = text;
18 | marginLR = 12;
19 | }
20 |
21 | if(isTopMargin) marginTop = 20;
22 |
23 | return new Gtk.Label({
24 | label: label,
25 | use_markup: true,
26 | hexpand: true,
27 | halign: Gtk.Align.START,
28 | margin_top: marginTop,
29 | margin_left: marginLR,
30 | margin_right: marginLR
31 | });
32 | }
33 | }
34 |
35 | function addToGrid(grid, leftWidget, rightWidget, resetIndex)
36 | {
37 | if(resetIndex) gridIndex = 0;
38 | if(leftWidget) grid.attach(leftWidget, 0, gridIndex, 1, 1);
39 | if(rightWidget) grid.attach(rightWidget, 1, gridIndex, 1, 1);
40 |
41 | gridIndex++;
42 | }
43 |
--------------------------------------------------------------------------------
/schemas/gschemas.compiled:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rafostar/gnome-shell-extension-cast-to-tv/10502fc3cd247f776895d418585243477de23e24/schemas/gschemas.compiled
--------------------------------------------------------------------------------
/schemas/org.gnome.shell.extensions.cast-to-tv.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | "chromecast"
6 | Device receiving media
7 |
8 |
9 | "/usr/bin/ffmpeg"
10 | Custom path to FFmpeg
11 |
12 |
13 | "/usr/bin/ffprobe"
14 | Custom path to FFprobe
15 |
16 |
17 | 4000
18 | Port used for incoming connections
19 |
20 |
21 | 9200
22 | Port used for internal communication
23 |
24 |
25 |
26 | "right"
27 | Position of chromecast remote menu button
28 |
29 |
30 | 10
31 | Time amount to skip when seeking
32 |
33 |
34 | 30
35 | Time amount after which slideshow image changes
36 |
37 |
38 | true
39 | Unify volume and progress sliders
40 |
41 |
42 | true
43 | Show label next to remote controller
44 |
45 |
46 | false
47 | Show receiver friendly name as remote controller label
48 |
49 |
50 | 20
51 | Icon size for widget media control buttons
52 |
53 |
54 | 16
55 | Icon size for widget slider
56 |
57 |
58 |
59 | ""
60 | Selected Chromecast device name
61 |
62 |
63 | "{}"
64 | Custom Chromecast subtitles config
65 |
66 |
67 | "[]"
68 | Scanned Chromecast devices list
69 |
70 |
71 |
72 | ""
73 | Playercast device name
74 |
75 |
76 | "[]"
77 | Scanned Playercast devices list
78 |
79 |
80 |
81 | "none"
82 | Video streaming hardware acceleration
83 |
84 |
85 | 5.0
86 | Media encoding video bitrate
87 |
88 |
89 | false
90 | Burn subtitles into video during transcode
91 |
92 |
93 |
94 | true
95 | Enable reusing subtitles from extractor dir
96 |
97 |
98 | "/tmp"
99 | Extractor save folder
100 |
101 |
102 | "eng/English"
103 | Preferred subtitles language
104 |
105 |
106 | ""
107 | Fallback subtitles language
108 |
109 |
110 |
111 | false
112 | FFmpeg music visualizer
113 |
114 |
115 | 1.0
116 | Web player subtitles size
117 |
118 |
119 |
120 | false
121 | Remember last service state
122 |
123 |
124 |
125 | 0
126 | Restore previous transcode option in file chooser
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/server-monitor.js:
--------------------------------------------------------------------------------
1 | const { Gio, GLib } = imports.gi;
2 | const Settings = new Gio.Settings({ schema: 'org.gnome.shell' });
3 |
4 | const EXTENSION_NAME = 'cast-to-tv@rafostar.github.com';
5 | const LOCAL_PATH = GLib.get_current_dir();
6 | const NODE_PATH = (GLib.find_program_in_path('nodejs') || GLib.find_program_in_path('node'));
7 |
8 | imports.searchPath.unshift(LOCAL_PATH);
9 | const Helper = imports.helper;
10 | const Soup = imports.soup;
11 | imports.searchPath.shift();
12 |
13 | const CastSettings = Helper.getSettings(LOCAL_PATH);
14 |
15 | let statusTimer;
16 | let restartCount = 0;
17 | let persistent = true;
18 | let loop = GLib.MainLoop.new(null, false);
19 |
20 | class ServerMonitor
21 | {
22 | constructor()
23 | {
24 | let canStart = (this._getIsExtensionEnabled() && !this._getIsServerRunning());
25 | if(!canStart) return;
26 |
27 | if(!NODE_PATH)
28 | Helper.notify('Cast to TV', 'nodejs' + ' ' + "is not installed!");
29 |
30 | if(
31 | !NODE_PATH
32 | || !this._checkModules()
33 | || !this._checkAddons()
34 | ) {
35 | /* If there was an error do not try to start on each login */
36 | if(CastSettings.get_boolean('service-wanted'))
37 | CastSettings.set_boolean('service-wanted', false);
38 |
39 | return;
40 | }
41 |
42 | Settings.connect('changed::disable-user-extensions', () => this._onSettingsChanged());
43 | Settings.connect('changed::enabled-extensions', () => this._onSettingsChanged());
44 |
45 | this.startServer();
46 | loop.run();
47 | }
48 |
49 | startServer()
50 | {
51 | let proc = Gio.Subprocess.new(
52 | [NODE_PATH, `${LOCAL_PATH}/node_scripts/server`],
53 | Gio.SubprocessFlags.NONE
54 | );
55 | log('Cast to TV: service started');
56 |
57 | proc.wait_async(null, () =>
58 | {
59 | loop.quit();
60 |
61 | if(persistent)
62 | {
63 | log('Cast to TV: restarting server');
64 |
65 | let modulesInstalled = this._checkModules();
66 | let addonsInstalled = this._checkAddons();
67 |
68 | if(modulesInstalled && addonsInstalled)
69 | {
70 | this.startServer();
71 |
72 | restartCount++;
73 | if(restartCount >= 3)
74 | {
75 | log('Cast to TV: server crashed too many times!');
76 | return this.stopServer();
77 | }
78 |
79 | if(!statusTimer)
80 | this._startTimer();
81 |
82 | return loop.run();
83 | }
84 | }
85 |
86 | log('Cast to TV: service stopped');
87 | });
88 | }
89 |
90 | stopServer()
91 | {
92 | persistent = false;
93 | GLib.spawn_command_line_sync(`pkill -SIGINT -f ${LOCAL_PATH}/node_scripts/server`);
94 | }
95 |
96 | _getIsExtensionEnabled()
97 | {
98 | let allDisabled = Settings.get_boolean('disable-user-extensions');
99 | if(!allDisabled)
100 | {
101 | let enabledExtensions = Settings.get_strv('enabled-extensions');
102 |
103 | if(enabledExtensions.includes(EXTENSION_NAME))
104 | return true;
105 | }
106 |
107 | log('Cast to TV: extension is disabled');
108 |
109 | return false;
110 | }
111 |
112 | _getIsServerRunning()
113 | {
114 | if(!Soup.client)
115 | Soup.createClient(CastSettings.get_int('listening-port'));
116 |
117 | let data = Soup.client.getIsServiceEnabledSync();
118 |
119 | return (data && data.isEnabled) ? true : false;
120 | }
121 |
122 | _checkModules(sourceDir)
123 | {
124 | sourceDir = sourceDir || LOCAL_PATH;
125 |
126 | let modulesPath = `${sourceDir}/node_modules`;
127 |
128 | /* Read cast-to-tv package.json */
129 | let pkgInfo = this._getPkgInfo(sourceDir);
130 |
131 | if(!pkgInfo || !pkgInfo.dependencies)
132 | {
133 | Helper.notify('Cast to TV', `Unreadable package.json in "${sourceDir}"`);
134 | return false;
135 | }
136 |
137 | /* Modules check should be after package.json read */
138 | let folderExists = GLib.file_test(modulesPath, GLib.FileTest.EXISTS);
139 | if(!folderExists)
140 | {
141 | log(`Cast to TV: missing folder "${modulesPath}"`);
142 | Helper.notify('Cast to TV', 'Required npm modules are not installed!');
143 | return false;
144 | }
145 |
146 | let dependencies = pkgInfo.dependencies;
147 |
148 | for(let module in dependencies)
149 | {
150 | let modulePath = `${modulesPath}/${module}`;
151 | let moduleExists = GLib.file_test(modulePath, GLib.FileTest.EXISTS);
152 |
153 | if(!moduleExists)
154 | {
155 | Helper.notify('Cast to TV', `Missing npm module: ${module}`);
156 | return false;
157 | }
158 |
159 | let isRequiredVer = this._checkPkgVersion(modulePath, dependencies[module]);
160 |
161 | if(!isRequiredVer)
162 | {
163 | Helper.notify('Cast to TV', 'Installed npm modules are outdated!');
164 | return false;
165 | }
166 | }
167 |
168 | return true;
169 | }
170 |
171 | _checkAddons()
172 | {
173 | let extPath = LOCAL_PATH.substring(0, LOCAL_PATH.lastIndexOf('/'));
174 | let extDir = Gio.File.new_for_path(extPath);
175 | let dirEnum = extDir.enumerate_children('standard::name,standard::type', 0, null);
176 | let addons = [];
177 |
178 | let info;
179 | while((info = dirEnum.next_file(null)))
180 | {
181 | let dirName = info.get_name();
182 |
183 | if(dirName.includes('cast-to-tv') && dirName.includes('addon'))
184 | addons.push(`${extPath}/${dirName}`);
185 | }
186 |
187 | for(let addonDir of addons)
188 | {
189 | let addonModulesInstalled = this._checkModules(addonDir);
190 |
191 | if(!addonModulesInstalled)
192 | return false;
193 | }
194 |
195 | return true;
196 | }
197 |
198 | _checkPkgVersion(modulePath, version)
199 | {
200 | let isExactVer = false;
201 | let pkgInfo = this._getPkgInfo(modulePath);
202 |
203 | if(!pkgInfo || !pkgInfo.version)
204 | return false;
205 |
206 | if(isNaN(version.charAt(0)))
207 | version = version.substring(1);
208 | else
209 | isExactVer = true;
210 |
211 | return (isExactVer)
212 | ? pkgInfo.version == version
213 | : pkgInfo.version >= version
214 | }
215 |
216 | _getPkgInfo(modulePath)
217 | {
218 | let data = Helper.readFromFile(`${modulePath}/package.json`);
219 | return (data) ? data : null;
220 | }
221 |
222 | _onSettingsChanged()
223 | {
224 | let enabled = this._getIsExtensionEnabled();
225 |
226 | if(!enabled)
227 | this.stopServer();
228 | }
229 |
230 | _startTimer()
231 | {
232 | statusTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 30, () =>
233 | {
234 | statusTimer = null;
235 | restartCount = 0;
236 |
237 | return GLib.SOURCE_REMOVE;
238 | });
239 | }
240 | }
241 |
242 | let monitor = new ServerMonitor();
243 |
--------------------------------------------------------------------------------
/shared.js:
--------------------------------------------------------------------------------
1 | if(typeof process === 'undefined')
2 | {
3 | /* When importing to GJS */
4 | var exports = {};
5 | var module = {exports};
6 | }
7 |
8 | var tempDir = '/tmp/.cast-to-tv';
9 |
10 | module.exports = {
11 | tempDir: tempDir,
12 | hlsDir: tempDir + '/stream',
13 | vttSubsPath: tempDir + '/webplayer_subs.vtt',
14 | coverDefault: tempDir + '/cover',
15 | escapeChars: [' ', '[', ']', '"', "'"],
16 | coverNames: ['cover', 'cover_01', 'cover 01', 'cover1'],
17 | coverExtensions: ['.jpg', '.png'],
18 | subsFormats: ['srt', 'ass', 'vtt'],
19 | chromecast: {
20 | videoBuffer: 2500,
21 | visualizerBuffer: 6500,
22 | subsStyle: {
23 | backgroundColor: '#00000000',
24 | foregroundColor: '#FFFFFFFF',
25 | edgeType: 'OUTLINE',
26 | edgeColor: '#000000FF',
27 | fontScale: '1.0',
28 | fontStyle: 'NORMAL',
29 | fontFamily: 'Droid Sans',
30 | fontGenericFamily: 'SANS_SERIF',
31 | windowType: 'NONE'
32 | },
33 | tracks: [{
34 | trackId: 1,
35 | type: 'TEXT',
36 | trackContentType: 'text/vtt',
37 | name: 'Subtitles',
38 | subtype: 'SUBTITLES'
39 | }]
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/temp.js:
--------------------------------------------------------------------------------
1 | const { GLib } = imports.gi;
2 | const Local = imports.misc.extensionUtils.getCurrentExtension();
3 | const Settings = Local.imports.helper.getSettings(Local.path);
4 |
5 | function getConfig()
6 | {
7 | /* Get only settings required by extension */
8 | let config = {
9 | listeningPort: Settings.get_int('listening-port'),
10 | internalPort: Settings.get_int('internal-port'),
11 | musicVisualizer: Settings.get_boolean('music-visualizer')
12 | };
13 |
14 | return config;
15 | }
16 |
17 | function getRemoteOpts()
18 | {
19 | let opts = {
20 | mode: 'DIRECT',
21 | seekTime: Settings.get_int('seek-time'),
22 | isUnifiedSlider: Settings.get_boolean('unified-slider'),
23 | isLabel: Settings.get_boolean('remote-label'),
24 | receiverType: Settings.get_string('receiver-type'),
25 | useFriendlyName: Settings.get_boolean('remote-label-fn'),
26 | sliderIconSize: Settings.get_int('slider-icon-size'),
27 | mediaButtonsSize: Settings.get_int('media-buttons-size')
28 | };
29 |
30 | return opts;
31 | }
32 |
--------------------------------------------------------------------------------
/webplayer/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rafostar/gnome-shell-extension-cast-to-tv/10502fc3cd247f776895d418585243477de23e24/webplayer/images/cover.png
--------------------------------------------------------------------------------
/webplayer/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rafostar/gnome-shell-extension-cast-to-tv/10502fc3cd247f776895d418585243477de23e24/webplayer/images/icon.png
--------------------------------------------------------------------------------
/webplayer/images/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rafostar/gnome-shell-extension-cast-to-tv/10502fc3cd247f776895d418585243477de23e24/webplayer/images/play.png
--------------------------------------------------------------------------------
/webplayer/loading.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cast to TV
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/webplayer/loading.js:
--------------------------------------------------------------------------------
1 | var websocket = io();
2 | websocket.once('connect', onWebsocketConnect);
3 | websocket.on('loading-text', onLoadingText);
4 | websocket.on('processes-done', onProcessesDone);
5 |
6 | function onWebsocketConnect()
7 | {
8 | websocket.emit('webplayer', 'loading-ask');
9 | }
10 |
11 | function onLoadingText(text)
12 | {
13 | document.getElementById('msg').innerHTML = text;
14 | websocket.emit('webplayer', 'processes-ask');
15 | }
16 |
17 | function onProcessesDone(isDone)
18 | {
19 | if(!isDone) return;
20 |
21 | websocket.disconnect();
22 | setTimeout(() => location.reload(true), 250);
23 | }
24 |
--------------------------------------------------------------------------------
/webplayer/message.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cast to TV
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/webplayer/message.js:
--------------------------------------------------------------------------------
1 | var websocket = io();
2 | websocket.once('connect', onWebsocketConnect);
3 | websocket.on('message-refresh', refreshMessage);
4 | websocket.on('message-clear', changePage);
5 | var msgCheckInterval = null;
6 |
7 | function onWebsocketConnect()
8 | {
9 | websocket.emit('webplayer', 'message-ask');
10 | msgCheckInterval = setInterval(() => { websocket.emit('webplayer', 'message-ask'); }, 1000);
11 | }
12 |
13 | function refreshMessage(msg)
14 | {
15 | if(document.getElementById("msg").innerHTML != msg)
16 | document.getElementById("msg").innerHTML = msg;
17 | }
18 |
19 | function changePage()
20 | {
21 | if(msgCheckInterval)
22 | clearInterval(msgCheckInterval);
23 |
24 | websocket.disconnect();
25 | setTimeout(() => location.reload(true), 250);
26 | }
27 |
--------------------------------------------------------------------------------
/webplayer/picture.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cast to TV
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/webplayer/preload.js:
--------------------------------------------------------------------------------
1 | function makeID()
2 | {
3 | var text = "";
4 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
5 |
6 | for(var i = 0; i < 10; i++)
7 | {
8 | text += possible.charAt(Math.floor(Math.random() * possible.length));
9 | }
10 |
11 | return text;
12 | }
13 |
--------------------------------------------------------------------------------
/webplayer/webplayer-defaults.js:
--------------------------------------------------------------------------------
1 | var playerOptions = {
2 | controls: ['play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'fullscreen'],
3 | settings: ['speed'],
4 | clickToPlay: false,
5 | invertTime: false,
6 | toggleInvert: true,
7 | keyboard: { focused: true, global: true },
8 | captions: { active: true, language: 'en', update: false },
9 | iconUrl: '/plyr/plyr.svg',
10 | blankVideo: '/plyr/blank.mp4',
11 | storage: { enabled: true, key: 'cast-to-tv' }
12 | };
13 |
--------------------------------------------------------------------------------
/webplayer/webplayer-encode.js:
--------------------------------------------------------------------------------
1 | playerOptions.controls = ['play', 'current-time', 'mute', 'volume', 'captions', 'pip', 'fullscreen'];
2 | playerOptions.toggleInvert = false;
3 |
--------------------------------------------------------------------------------
/webplayer/webplayer-init.js:
--------------------------------------------------------------------------------
1 | const isMobile = (/Android|iPhone|iPad|iPod|BlackBerry|webOS|IEMobile|Windows Phone/i.test(navigator.userAgent)) ? true : false;
2 | const player = new Plyr('#player', playerOptions);
3 |
4 | function preparePlayer(msg)
5 | {
6 | var sessionID = makeID();
7 | var posterPath = '/webplayer/images/play.png';
8 | var subsKind = 'none';
9 | var subsSrc = null;
10 |
11 | player.config.i18n = msg.i18n;
12 |
13 | /* Show album cover when playing without visualizations */
14 | if(msg.type == 'MUSIC')
15 | {
16 | posterPath = '/cover?session=' + sessionID;
17 | }
18 | else if(msg.subs)
19 | {
20 | /* Enable subtitles */
21 | subsKind = 'captions';
22 | subsSrc = '/subswebplayer?session=' + sessionID;
23 | }
24 |
25 | player.source = {
26 | type: 'video',
27 | title: 'Cast to TV',
28 | sources: [{
29 | src: '/cast?session=' + sessionID,
30 | type: 'video/mp4'
31 | }],
32 | poster: posterPath,
33 | tracks: [{
34 | kind: subsKind,
35 | label: 'Subtitles',
36 | srclang: 'en',
37 | src: subsSrc,
38 | default: true
39 | }]
40 | };
41 | }
42 |
43 | function addClickListeners()
44 | {
45 | /* Toggle play on click event listener */
46 | var div = document.getElementsByClassName('plyr__video-wrapper')[0];
47 | div.addEventListener('click', startPlayer);
48 | }
49 |
50 | function startPlayer()
51 | {
52 | /* When on mobile */
53 | if(isMobile)
54 | {
55 | if(!player.fullscreen.active)
56 | {
57 | /* Enter fullscreen after touch (when paused) */
58 | player.fullscreen.enter();
59 |
60 | if(!player.playing) player.togglePlay();
61 | return;
62 | }
63 | }
64 |
65 | /* Play and pause on click/touch */
66 | player.togglePlay();
67 | }
68 |
--------------------------------------------------------------------------------
/webplayer/webplayer.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #222222;
3 | margin: 0;
4 | }
5 |
6 | .plyr__captions {
7 | bottom: 2vh;
8 | }
9 |
10 | .plyr__captions .plyr__caption {
11 | background: rgba(0,0,0,0);
12 | text-shadow: -1px -1px 1px #000000, -1px 1px 1px #000000, 1px -1px 1px #000000, 1px 1px 1px #000000;
13 | font-family: sans-serif;
14 | font-weight: bold;
15 | line-height: 1.0;
16 |
17 | -webkit-touch-callout: none;
18 | -webkit-user-select: none;
19 | -khtml-user-select: none;
20 | -moz-user-select: none;
21 | -ms-user-select: none;
22 | user-select: none;
23 | }
24 |
25 | .plyr__time {
26 | cursor: pointer;
27 |
28 | -webkit-touch-callout: none;
29 | -webkit-user-select: none;
30 | -khtml-user-select: none;
31 | -moz-user-select: none;
32 | -ms-user-select: none;
33 | user-select: none;
34 | }
35 |
36 | .plyr video {
37 | height: 100vh;
38 | }
39 |
40 | .cast-picture {
41 | display: block;
42 | height: 100vh;
43 | }
44 |
45 | .screen-center {
46 | margin: 0;
47 | position: absolute;
48 | top: 50%;
49 | left: 50%;
50 |
51 | -webkit-transform: translateX(-50%) translateY(-50%);
52 | -khtml-transform: translateX(-50%) translateY(-50%);
53 | -moz--transform: translateX(-50%) translateY(-50%);
54 | -ms-transform: translateX(-50%) translateY(-50%);
55 | transform: translateX(-50%) translateY(-50%);
56 | }
57 |
58 | #msg {
59 | color: white;
60 |
61 | -webkit-touch-callout: none;
62 | -webkit-user-select: none;
63 | -khtml-user-select: none;
64 | -moz-user-select: none;
65 | -ms-user-select: none;
66 | user-select: none;
67 | }
68 |
69 | .load-text {
70 | margin-top: 10px;
71 | margin-left: 2px;
72 | position: relative;
73 | font-family: 'Sniglet', cursive;
74 | font-weight: bold;
75 | font-size: 52px;
76 | }
77 |
78 | .msg-text {
79 | margin-top: 10px;
80 | margin-left: 2px;
81 | position: relative;
82 | font-family: 'Sniglet', cursive;
83 | font-weight: bold;
84 | font-size: 36px;
85 | }
86 |
87 | .loader {
88 | border: 20px solid white;
89 | border-radius: 50%;
90 | border-top: 20px solid #3498db;
91 | width: 80px;
92 | height: 80px;
93 |
94 | -webkit-animation: spin 0.8s linear infinite;
95 | -khtml-animation: spin 0.8s linear infinite;
96 | -moz-animation: spin 0.8s linear infinite;
97 | -ms-animation: spin 0.8s linear infinite;
98 | animation: spin 0.8s linear infinite;
99 | }
100 |
101 | @-webkit-keyframes spin {
102 | 0% { -webkit-transform: rotate(0deg); }
103 | 100% { -webkit-transform: rotate(360deg); }
104 | }
105 |
106 | @-khtml-keyframes spin {
107 | 0% { -webkit-transform: rotate(0deg); }
108 | 100% { -webkit-transform: rotate(360deg); }
109 | }
110 |
111 | @-moz-keyframes spin {
112 | 0% { -webkit-transform: rotate(0deg); }
113 | 100% { -webkit-transform: rotate(360deg); }
114 | }
115 |
116 | @-ms-keyframes spin {
117 | 0% { -webkit-transform: rotate(0deg); }
118 | 100% { -webkit-transform: rotate(360deg); }
119 | }
120 |
121 | @keyframes spin {
122 | 0% { transform: rotate(0deg); }
123 | 100% { transform: rotate(360deg); }
124 | }
125 |
--------------------------------------------------------------------------------
/webplayer/webplayer_direct.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cast to TV
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/webplayer/webplayer_encode.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cast to TV
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/webplayer/websocket.js:
--------------------------------------------------------------------------------
1 | var websocket = io();
2 | websocket.once('connect', onWebsocketConnect);
3 |
4 | var statusContents = {
5 | playerState: 'PAUSED',
6 | currentTime: 0,
7 | media: { duration: 0 },
8 | volume: getVolume()
9 | };
10 |
11 | function getVolume()
12 | {
13 | if(typeof player === 'undefined' || player.muted)
14 | return 0;
15 | else
16 | return player.volume;
17 | }
18 |
19 | function onWebsocketConnect()
20 | {
21 | /* Web player related websocket functions */
22 | if(typeof player !== 'undefined')
23 | {
24 | var progress = 0;
25 | var playbackStarted = false;
26 |
27 | websocket.emit('webplayer', 'webplayer-ask');
28 |
29 | websocket.on('webplayer-init', msg =>
30 | {
31 | preparePlayer(msg);
32 | addClickListeners();
33 | });
34 | player.on('ended', () => websocket.emit('webplayer', 'track-ended'));
35 |
36 | player.on('loadeddata', () =>
37 | {
38 | /* Workaround Plyr volume bug */
39 | player.currentTime = 0;
40 |
41 | statusContents.media.duration = player.duration;
42 | });
43 |
44 | player.on('canplay', () =>
45 | {
46 | if(!playbackStarted) player.play();
47 | });
48 |
49 | player.on('playing', () =>
50 | {
51 | playbackStarted = true;
52 | statusContents.playerState = 'PLAYING';
53 | websocket.emit('status-update', statusContents);
54 | });
55 |
56 | player.on('pause', () =>
57 | {
58 | statusContents.playerState = 'PAUSED';
59 | websocket.emit('status-update', statusContents);
60 | });
61 |
62 | player.on('seeked', () =>
63 | {
64 | progress = 0;
65 | statusContents.currentTime = player.currentTime;
66 | websocket.emit('status-update', statusContents);
67 | });
68 |
69 | player.on('volumechange', () =>
70 | {
71 | statusContents.volume = getVolume();
72 | websocket.emit('status-update', statusContents);
73 | });
74 |
75 | setInterval(() =>
76 | {
77 | statusContents.currentTime = player.currentTime;
78 | websocket.emit('status-update', statusContents);
79 | }, 1000);
80 |
81 | websocket.on('remote-signal', msg =>
82 | {
83 | switch(msg.action)
84 | {
85 | case 'PLAY':
86 | player.play();
87 | break;
88 | case 'PAUSE':
89 | player.pause();
90 | break;
91 | case 'SEEK':
92 | player.currentTime = statusContents.media.duration * msg.value;
93 | break;
94 | case 'SEEK+':
95 | player.forward(msg.value);
96 | break;
97 | case 'SEEK-':
98 | player.rewind(msg.value);
99 | break;
100 | case 'VOLUME':
101 | player.volume = msg.value;
102 | break;
103 | case 'STOP':
104 | player.stop();
105 | break;
106 | default:
107 | break;
108 | }
109 | });
110 | }
111 | else
112 | {
113 | websocket.emit('show-remote', true);
114 | websocket.emit('status-update', statusContents);
115 | }
116 | }
117 |
118 | websocket.on('reload', () =>
119 | {
120 | websocket.disconnect();
121 |
122 | /* Temporary solution for changing web page before
123 | nodejs reduces active connections number */
124 | setTimeout(() => location.reload(true), 250);
125 | });
126 |
--------------------------------------------------------------------------------