├── .editorconfig
├── .flake8
├── .gitignore
├── .travis.yml
├── .vscode
└── settings.json
├── 3rdparty
└── JsonPath
│ ├── JsonPath.php
│ └── JsonStore.php
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── core
├── class
│ ├── googlecast.class.php
│ └── googlecast_utils.inc.php
├── i18n
│ ├── en_US.json
│ ├── es_ES.json
│ └── fr_FR.json
├── php
│ ├── googlecast.ajax.php
│ ├── googlecast.api.php
│ └── googlecast.ifttt.php
├── template
│ ├── dashboard
│ │ ├── cmd.action.message.googlecast_speak.html
│ │ ├── cmd.action.other.googlecast_reboot.html
│ │ ├── cmd.info.binary.googlecast_busy.html
│ │ ├── cmd.info.binary.googlecast_status.html
│ │ └── cmd.info.string.googlecast_playing.html
│ └── mobile
│ │ ├── cmd.action.message.googlecast_speak.html
│ │ └── cmd.info.string.googlecast_playing.html
└── webradios
│ └── radiolist.json
├── data
├── README.md
└── media
│ ├── README.md
│ ├── bigben1.mp3
│ ├── bigben2.mp3
│ ├── house_firealarm.mp3
│ ├── railroad_crossing_bell.mp3
│ ├── submarine_diving.mp3
│ └── tornado_siren.mp3
├── desktop
├── images
│ ├── chromecast1.png
│ ├── chromecast2.png
│ ├── notif.png
│ ├── radio_nologo.png
│ └── tts.png
├── js
│ └── googlecast.js
├── modal
│ └── googlecast.health.php
├── models
│ ├── model_androidtv.png
│ ├── model_castgroup.png
│ ├── model_chromecast_audio.png
│ ├── model_chromecast_video.png
│ ├── model_chromecast_video_ultra.png
│ ├── model_default.png
│ ├── model_googlehome.png
│ ├── model_googlehome_hub.png
│ ├── model_googlehome_mini.png
│ └── model_tv.png
└── php
│ └── googlecast.php
├── docs
├── 404.html
├── _config.yml
├── _layouts
│ └── default.html
├── assets
│ ├── css
│ │ ├── components
│ │ │ ├── _buttons.scss
│ │ │ ├── _cards.scss
│ │ │ ├── _carousel.scss
│ │ │ ├── _chips.scss
│ │ │ ├── _collapsible.scss
│ │ │ ├── _color.scss
│ │ │ ├── _dropdown.scss
│ │ │ ├── _global.scss
│ │ │ ├── _grid.scss
│ │ │ ├── _icons-material-design.scss
│ │ │ ├── _materialbox.scss
│ │ │ ├── _me.scss
│ │ │ ├── _mixins.scss
│ │ │ ├── _modal.scss
│ │ │ ├── _navbar.scss
│ │ │ ├── _normalize.scss
│ │ │ ├── _prefixer.scss
│ │ │ ├── _preloader.scss
│ │ │ ├── _roboto.scss
│ │ │ ├── _sideNav.scss
│ │ │ ├── _slider.scss
│ │ │ ├── _table_of_contents.scss
│ │ │ ├── _tabs.scss
│ │ │ ├── _toast.scss
│ │ │ ├── _tooltip.scss
│ │ │ ├── _typography.scss
│ │ │ ├── _variables.scss
│ │ │ ├── _waves.scss
│ │ │ ├── date_picker
│ │ │ │ ├── _default.date.scss
│ │ │ │ ├── _default.scss
│ │ │ │ └── _default.time.scss
│ │ │ └── forms
│ │ │ │ ├── _checkboxes.scss
│ │ │ │ ├── _file-input.scss
│ │ │ │ ├── _forms.scss
│ │ │ │ ├── _input-fields.scss
│ │ │ │ ├── _radio-buttons.scss
│ │ │ │ ├── _range.scss
│ │ │ │ ├── _select.scss
│ │ │ │ └── _switches.scss
│ │ ├── materialize.css
│ │ ├── materialize.scss
│ │ └── styles.css
│ ├── font
│ │ ├── material-design-icons
│ │ │ ├── LICENSE.txt
│ │ │ ├── Material-Design-Icons.eot
│ │ │ ├── Material-Design-Icons.svg
│ │ │ ├── Material-Design-Icons.ttf
│ │ │ ├── Material-Design-Icons.woff
│ │ │ └── Material-Design-Icons.woff2
│ │ └── roboto
│ │ │ ├── Roboto-Bold.eot
│ │ │ ├── Roboto-Bold.ttf
│ │ │ ├── Roboto-Bold.woff
│ │ │ ├── Roboto-Bold.woff2
│ │ │ ├── Roboto-Light.eot
│ │ │ ├── Roboto-Light.ttf
│ │ │ ├── Roboto-Light.woff
│ │ │ ├── Roboto-Light.woff2
│ │ │ ├── Roboto-Medium.eot
│ │ │ ├── Roboto-Medium.ttf
│ │ │ ├── Roboto-Medium.woff
│ │ │ ├── Roboto-Medium.woff2
│ │ │ ├── Roboto-Regular.eot
│ │ │ ├── Roboto-Regular.ttf
│ │ │ ├── Roboto-Regular.woff
│ │ │ ├── Roboto-Regular.woff2
│ │ │ ├── Roboto-Thin.eot
│ │ │ ├── Roboto-Thin.ttf
│ │ │ ├── Roboto-Thin.woff
│ │ │ └── Roboto-Thin.woff2
│ ├── fonts
│ │ └── roboto
│ │ │ ├── Roboto-Bold.eot
│ │ │ ├── Roboto-Bold.ttf
│ │ │ ├── Roboto-Bold.woff
│ │ │ ├── Roboto-Bold.woff2
│ │ │ ├── Roboto-Light.eot
│ │ │ ├── Roboto-Light.ttf
│ │ │ ├── Roboto-Light.woff
│ │ │ ├── Roboto-Light.woff2
│ │ │ ├── Roboto-Medium.eot
│ │ │ ├── Roboto-Medium.ttf
│ │ │ ├── Roboto-Medium.woff
│ │ │ ├── Roboto-Medium.woff2
│ │ │ ├── Roboto-Regular.eot
│ │ │ ├── Roboto-Regular.ttf
│ │ │ ├── Roboto-Regular.woff
│ │ │ ├── Roboto-Regular.woff2
│ │ │ ├── Roboto-Thin.eot
│ │ │ ├── Roboto-Thin.ttf
│ │ │ ├── Roboto-Thin.woff
│ │ │ └── Roboto-Thin.woff2
│ ├── images
│ │ └── logo.png
│ └── js
│ │ ├── jquery-2.1.1.min.js
│ │ ├── jquery.inview.min.js
│ │ ├── jquery.toc.js
│ │ └── materialize.min.js
├── en_US
│ ├── changelog.md
│ └── index.md
├── fr_FR
│ ├── changelog.md
│ ├── gcloudttskey.md
│ └── index.md
├── images
│ ├── chromecast.png
│ ├── commands.png
│ ├── commands_list.png
│ ├── configuration.png
│ ├── configuration_css.png
│ ├── configuration_plugin.png
│ ├── dashboard.png
│ ├── dashboard2.png
│ ├── display1.png
│ ├── gcloudtts
│ │ ├── gctts_api1.png
│ │ ├── gctts_gcp1.png
│ │ ├── gctts_key1.png
│ │ ├── gctts_key2.png
│ │ └── gctts_key3.png
│ ├── googlecast_logo.png
│ ├── logoplugin.png
│ ├── scenario.png
│ ├── summary.png
│ ├── tv.png
│ └── widget_speak.png
└── index.html
├── plugin_info
├── .htaccess
├── configuration.php
├── googlecast_icon.png
├── info.json
└── install.php
├── resources
├── .htaccess
├── gcloudtts
│ ├── __init__.py
│ └── gcloudtts.py
├── globals.py
├── googlecast.py
├── gtts
│ ├── LICENSE
│ ├── __init__.py
│ ├── cli.py
│ ├── lang.py
│ ├── langs.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── input_files
│ │ │ ├── test_cli_test_ascii.txt
│ │ │ └── test_cli_test_utf8.txt
│ │ ├── test_cli.py
│ │ ├── test_lang.py
│ │ ├── test_tts.py
│ │ └── test_utils.py
│ ├── tokenizer
│ │ ├── __init__.py
│ │ ├── core.py
│ │ ├── pre_processors.py
│ │ ├── symbols.py
│ │ ├── tests
│ │ │ ├── test_core.py
│ │ │ ├── test_pre_processors.py
│ │ │ └── test_tokenizer_cases.py
│ │ └── tokenizer_cases.py
│ ├── tts.py
│ ├── utils.py
│ └── version.py
├── install.sh
├── install_check.sh
├── jeedom
│ ├── __init__.py
│ └── jeedom.py
├── plexapi
│ ├── __init__.py
│ ├── alert.py
│ ├── audio.py
│ ├── base.py
│ ├── client.py
│ ├── compat.py
│ ├── config.py
│ ├── exceptions.py
│ ├── library.py
│ ├── media.py
│ ├── myplex.py
│ ├── photo.py
│ ├── playlist.py
│ ├── playqueue.py
│ ├── server.py
│ ├── settings.py
│ ├── sync.py
│ ├── utils.py
│ └── video.py
├── pychromecast
│ ├── .gitignore
│ ├── LICENSE
│ ├── MANIFEST.in
│ ├── chromecast_protobuf
│ │ ├── README.md
│ │ ├── authority_keys.proto
│ │ ├── cast_channel.proto
│ │ └── logging.proto
│ ├── examples
│ │ ├── bbciplayer_example.py
│ │ ├── bbcsounds_example.py
│ │ ├── bubbleupnp_example.py
│ │ ├── custom_loop.py
│ │ ├── dashcast_example.py
│ │ ├── discovery_example.py
│ │ ├── discovery_example2.py
│ │ ├── discovery_example3.py
│ │ ├── get_chromecasts.py
│ │ ├── homeassistant_media_example.py
│ │ ├── list_chromecasts.py
│ │ ├── media_enqueue.py
│ │ ├── media_example.py
│ │ ├── media_example2.py
│ │ ├── multizone_example.py
│ │ ├── plex_multi_example.py
│ │ ├── simple_listener_example.py
│ │ ├── spotify_example.py
│ │ ├── supla_example.py
│ │ ├── yleareena_example.py
│ │ └── youtube_example.py
│ ├── fabfile.py
│ ├── pychromecast
│ │ ├── __init__.py
│ │ ├── authority_keys_pb2.py
│ │ ├── cast_channel_pb2.py
│ │ ├── config.py
│ │ ├── const.py
│ │ ├── controllers
│ │ │ ├── __init__.py
│ │ │ ├── bbciplayer.py
│ │ │ ├── bbcsounds.py
│ │ │ ├── bubbleupnp.py
│ │ │ ├── dashcast.py
│ │ │ ├── homeassistant.py
│ │ │ ├── homeassistant_media.py
│ │ │ ├── media.py
│ │ │ ├── multizone.py
│ │ │ ├── plex.py
│ │ │ ├── receiver.py
│ │ │ ├── spotify.py
│ │ │ ├── supla.py
│ │ │ ├── yleareena.py
│ │ │ └── youtube.py
│ │ ├── customcontrollers
│ │ │ ├── netflix.py
│ │ │ ├── plex.py
│ │ │ ├── plex2.py
│ │ │ └── plex3.py
│ │ ├── dial.py
│ │ ├── discovery.py
│ │ ├── error.py
│ │ ├── logging_pb2.py
│ │ ├── models.py
│ │ ├── quick_play.py
│ │ └── socket_client.py
│ ├── pylintrc
│ ├── pyproject.toml
│ ├── requirements-test.txt
│ ├── requirements.txt
│ ├── setup.cfg
│ └── setup.py
├── pydub
│ ├── AUTHORS
│ ├── LICENSE
│ ├── __init__.py
│ ├── audio_segment.py
│ ├── effects.py
│ ├── exceptions.py
│ ├── generators.py
│ ├── logging_utils.py
│ ├── playback.py
│ ├── pyaudioop.py
│ ├── scipy_effects.py
│ ├── silence.py
│ └── utils.py
├── requirements-nodep.txt
├── requirements.txt
├── spotipy
│ ├── __init__.py
│ ├── cache_handler.py
│ ├── client.py
│ ├── exceptions.py
│ ├── oauth2.py
│ ├── spotify_token.py
│ └── util.py
└── update.sh
└── tests
└── tools
├── .aspell.fr.pws
├── lintAllPythonFiles.sh
└── spellCheckMD.sh
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | # PHP PSR-2 Coding Standards
5 | # http://www.php-fig.org/psr/psr-2/
6 |
7 | root = true
8 |
9 | [*.php]
10 | charset = utf-8
11 | end_of_line = lf
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 | indent_style = space
15 | indent_size = 4
16 |
17 | [*.js]
18 | charset = utf-8
19 | trim_trailing_whitespace = true
20 | indent_style = space
21 | indent_size = 4
22 |
23 | [*.css]
24 | charset = utf-8
25 | trim_trailing_whitespace = true
26 | indent_style = space
27 | indent_size = 4
28 |
29 | [*.py]
30 | indent_style = space
31 | indent_size = 4
32 | end_of_line = lf
33 | charset = utf-8
34 | trim_trailing_whitespace = true
35 | insert_final_newline = true
36 |
37 | [*.sh]
38 | indent_style = space
39 | indent_size = 4
40 | end_of_line = lf
41 | charset = utf-8
42 | trim_trailing_whitespace = true
43 | insert_final_newline = true
44 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | per-file-ignores =
3 | resources/googlecast.py: E501
4 | resources/jeedom/jeedom.py: E501
5 | exclude = resources/pychromecast/*
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | .DS_Store
3 | Thumbs.db
4 | *.php~
5 | __pycache__
6 | *.bak
7 | tmp/
8 |
9 | .vscode/
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | include:
3 | - language: php
4 | php: 7.2
5 | before_script:
6 | - find . -type f -name *.php | xargs -n1 php -l
7 | script:
8 | - cd ${TRAVIS_BUILD_DIR}
9 | - pwd
10 | after_success:
11 | - cd ${TRAVIS_BUILD_DIR}
12 | - ls -latr
13 | after_failure:
14 | - cd ${TRAVIS_BUILD_DIR}
15 | - ls -latr
16 |
17 | - language: python
18 | python: 2.7
19 | install:
20 | - pip install pylint
21 | script:
22 | - cd ${TRAVIS_BUILD_DIR}
23 | - ./tests/tools/lintAllPythonFiles.sh
24 | - language: markdown
25 | addons:
26 | apt:
27 | packages:
28 | - aspell
29 | - aspell-fr
30 | script:
31 | - gem install mdl
32 | - cd ${TRAVIS_BUILD_DIR}
33 | - mdl -r $MDLWAR *.md docs/fr_FR/*.md
34 | - ./tests/tools/spellCheckMD.sh
35 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.linting.pylintEnabled": false,
3 | "python.linting.flake8Enabled": true,
4 | "python.linting.enabled": true,
5 |
6 | "phpcs.standard": null
7 | }
8 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | ## Etapes à reproduire (pour les bugs)
4 |
5 | 1.
6 | 2.
7 | 3.
8 | 4.
9 |
10 | ## Contexte:
11 |
12 | ## Proposition de solution (optionnel):
13 |
14 | ## Environnement:
15 |
16 | * **Version Jeedom**:
17 | * **Plateforme**:
18 | * **Version du Plugin**:
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## plugin-googlecast
2 |
3 | Plugin for Jeedom Open Source domotic solution.
4 |
5 | Remotely control Google Cast devices directly from Jeedom.
6 |
7 | 
8 | 
9 |
10 |
11 | - Plugin [user documentation - french](docs/fr_FR/index.md)
12 | - Plugin [user documentation - english](docs/en_US/index.md)
13 |
14 |
15 | ##### Base projects
16 |
17 | - pychromecast (https://github.com/balloob/pychromecast)
18 | - pydub (https://github.com/jiaaro/pydub)
19 | - plexapi (https://github.com/pkkid/python-plexapi)
20 | - Jeedom core (jeedom/core)
21 | - Jeedom plugins (gcast, blea...)
22 | - python gtts
23 |
--------------------------------------------------------------------------------
/core/php/googlecast.ajax.php:
--------------------------------------------------------------------------------
1 | .
16 | */
17 | try {
18 | require_once dirname(__FILE__) . '/../../../../core/php/core.inc.php';
19 |
20 | include_file('core', 'authentification', 'php');
21 |
22 | ajax::init();
23 |
24 | if (init('action') == 'changeIncludeState') {
25 | googlecast::changeIncludeState(init('state'), init('mode'));
26 | ajax::success();
27 | }
28 |
29 | if (init('action') == 'cleanTTScache') {
30 | ajax::success(googlecast::cleanTTScache());
31 | }
32 |
33 | if (init('action') == 'nowplaying') {
34 | ajax::success(googlecast::registerNowPlayging(init('uuid')));
35 | }
36 |
37 | if (init('action') == 'refreshall') {
38 | ajax::success(googlecast::refreshStatusAll());
39 | }
40 |
41 | if (init('action') == 'testAddress') {
42 | ajax::success( googlecast::testAddress( init('value') ) );
43 | }
44 |
45 |
46 | if (init('action') == 'sendcmd') {
47 | $ret = googlecast::sendDisplayAction(init('uuid'),init('cmd'), init('options'));
48 | if ($ret) {
49 | ajax::success();
50 | }
51 | else {
52 | ajax::error();
53 | }
54 | }
55 |
56 | throw new Exception(__('Aucune methode correspondante à : ', __FILE__) . init('action'));
57 | /* * *********Catch exeption*************** */
58 | } catch (Exception $e) {
59 | ajax::error(displayException($e), $e->getCode());
60 | }
61 |
--------------------------------------------------------------------------------
/core/template/dashboard/cmd.action.message.googlecast_speak.html:
--------------------------------------------------------------------------------
1 |
54 |
81 |
--------------------------------------------------------------------------------
/core/template/dashboard/cmd.action.other.googlecast_reboot.html:
--------------------------------------------------------------------------------
1 |
2 | #name_display#
3 |
4 |
11 |
--------------------------------------------------------------------------------
/core/template/dashboard/cmd.info.binary.googlecast_busy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
--------------------------------------------------------------------------------
/core/template/dashboard/cmd.info.binary.googlecast_status.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/core/template/mobile/cmd.action.message.googlecast_speak.html:
--------------------------------------------------------------------------------
1 |
54 |
81 |
--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 | plugin data dedicated folder
2 |
--------------------------------------------------------------------------------
/data/media/README.md:
--------------------------------------------------------------------------------
1 | Local Media files goes here !
2 | Don't forget to make this folder writable if you need to add new assets (using chmod)
3 |
--------------------------------------------------------------------------------
/data/media/bigben1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/data/media/bigben1.mp3
--------------------------------------------------------------------------------
/data/media/bigben2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/data/media/bigben2.mp3
--------------------------------------------------------------------------------
/data/media/house_firealarm.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/data/media/house_firealarm.mp3
--------------------------------------------------------------------------------
/data/media/railroad_crossing_bell.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/data/media/railroad_crossing_bell.mp3
--------------------------------------------------------------------------------
/data/media/submarine_diving.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/data/media/submarine_diving.mp3
--------------------------------------------------------------------------------
/data/media/tornado_siren.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/data/media/tornado_siren.mp3
--------------------------------------------------------------------------------
/desktop/images/chromecast1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/images/chromecast1.png
--------------------------------------------------------------------------------
/desktop/images/chromecast2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/images/chromecast2.png
--------------------------------------------------------------------------------
/desktop/images/notif.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/images/notif.png
--------------------------------------------------------------------------------
/desktop/images/radio_nologo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/images/radio_nologo.png
--------------------------------------------------------------------------------
/desktop/images/tts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/images/tts.png
--------------------------------------------------------------------------------
/desktop/modal/googlecast.health.php:
--------------------------------------------------------------------------------
1 | .
16 | */
17 |
18 | if (!isConnect('admin')) {
19 | throw new Exception('401 Unauthorized');
20 | }
21 | $eqLogics = googlecast::byType('googlecast');
22 | ?>
23 |
28 |
29 |
30 |
31 | |
32 | {{Nom}} |
33 | {{Nom diffusé}} |
34 | {{UUID}} |
35 | {{Modèle}} |
36 | {{Type}} |
37 | {{Online}} |
38 | {{Occupé}} |
39 | {{Dernière com}} |
40 | {{Date création}} |
41 |
42 |
43 |
44 | getIsEnable()) ? '' : jeedom::getConfiguration('eqLogic:style:noactive');
47 | $img = '
';
48 | echo '';
49 | echo '' . $img . ' | ';
50 | echo '' . $eqLogic->getHumanName(true) . ' | ';
51 | echo '' . $eqLogic->getConfiguration('friendly_name') . ' | ';
52 | echo '' . $eqLogic->getLogicalId() . ' | ';
53 | echo '' . $eqLogic->getConfiguration('model_name') . ' | ';
54 | echo '' . $eqLogic->getConfiguration('cast_type') . ' | ';
55 | $onlinecmd = $eqLogic->getCmd('info', 'online');
56 | $online = '{{Non}}';
57 | if ($onlinecmd->execCmd() == 1) {
58 | $online = '{{Oui}}';
59 | }
60 | echo '' . $online . ' | ';
61 | $busycmd = $eqLogic->getCmd('info', 'is_busy');
62 | $busy = '{{Non}}';
63 | if ($busycmd->execCmd() == 1) {
64 | $busy = '{{Oui}}';
65 | }
66 | echo '' . $busy . ' | ';
67 | echo '' . $eqLogic->getStatus('lastCommunication') . ' | ';
68 | echo '' . $eqLogic->getConfiguration('createtime') . ' | ';
69 | echo '
';
70 | }
71 | ?>
72 |
73 |
74 |
--------------------------------------------------------------------------------
/desktop/models/model_androidtv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_androidtv.png
--------------------------------------------------------------------------------
/desktop/models/model_castgroup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_castgroup.png
--------------------------------------------------------------------------------
/desktop/models/model_chromecast_audio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_chromecast_audio.png
--------------------------------------------------------------------------------
/desktop/models/model_chromecast_video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_chromecast_video.png
--------------------------------------------------------------------------------
/desktop/models/model_chromecast_video_ultra.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_chromecast_video_ultra.png
--------------------------------------------------------------------------------
/desktop/models/model_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_default.png
--------------------------------------------------------------------------------
/desktop/models/model_googlehome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_googlehome.png
--------------------------------------------------------------------------------
/desktop/models/model_googlehome_hub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_googlehome_hub.png
--------------------------------------------------------------------------------
/desktop/models/model_googlehome_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_googlehome_mini.png
--------------------------------------------------------------------------------
/desktop/models/model_tv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/desktop/models/model_tv.png
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Page 404 - Jeedom
7 |
35 |
36 |
37 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | langs: [fr_FR,en_US,es_ES]
3 | baseurl: "/"
4 | sass:
5 | sass_dir: assets/css
6 | style: compressed
7 | markdown: kramdown
8 | permalink: /404.html
9 | kramdown:
10 | input: GFM
11 | hard_wrap: false
12 | highlighter: rouge
13 | gems:
14 | - jekyll-paginate
15 | - jekyll-seo-tag
16 | exclude:
17 | - vendor
18 | plugins:
19 | - jekyll-seo-tag
20 | title: "Documentation Jeedom"
21 | description: "Ceci est la documentation, lier à la solution domotique Jeedom."
22 | author: "Jeedom"
23 | image: https://www.jeedom.com/site/logo.png
24 | twitter:
25 | username: Jeedom_domotic
26 | social:
27 | name: Jeedom
28 | links:
29 | - https://twitter.com/Jeedom_domotic
30 | - https://www.facebook.com/Jeedom-249936178537210/
31 | - https://github.com/jeedom
--------------------------------------------------------------------------------
/docs/assets/css/components/_cards.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .card-panel {
4 | transition: box-shadow .25s;
5 | padding: $card-padding;
6 | margin: $element-top-margin 0 $element-bottom-margin 0;
7 | border-radius: 2px;
8 | @extend .z-depth-1;
9 | background-color: $card-bg-color;
10 | }
11 |
12 | .card {
13 | position: relative;
14 | margin: $element-top-margin 0 $element-bottom-margin 0;
15 | background-color: $card-bg-color;
16 | transition: box-shadow .25s;
17 | border-radius: 2px;
18 | @extend .z-depth-1;
19 |
20 |
21 | .card-title {
22 | font-size: 24px;
23 | font-weight: 300;
24 | &.activator {
25 | cursor: pointer;
26 | }
27 | }
28 |
29 | // Card Sizes
30 | &.small, &.medium, &.large {
31 | position: relative;
32 |
33 | .card-image {
34 | max-height: 60%;
35 | overflow: hidden;
36 | }
37 | .card-content {
38 | max-height: 40%;
39 | overflow: hidden;
40 | }
41 | .card-action {
42 | position: absolute;
43 | bottom: 0;
44 | left: 0;
45 | right: 0;
46 | }
47 | }
48 |
49 | &.small {
50 | height: 300px;
51 | }
52 |
53 | &.medium {
54 | height: 400px;
55 | }
56 |
57 | &.large {
58 | height: 500px;
59 | }
60 |
61 |
62 | .card-image {
63 | position: relative;
64 |
65 | // Image background for content
66 | img {
67 | display: block;
68 | border-radius: 2px 2px 0 0;
69 | position: relative;
70 | left: 0;
71 | right: 0;
72 | top: 0;
73 | bottom: 0;
74 | width: 100%;
75 | }
76 |
77 | .card-title {
78 | color: $card-bg-color;
79 | position: absolute;
80 | bottom: 0;
81 | left: 0;
82 | padding: $card-padding;
83 | }
84 |
85 | }
86 |
87 | .card-content {
88 | padding: $card-padding;
89 | border-radius: 0 0 2px 2px;
90 |
91 | p {
92 | margin: 0;
93 | color: inherit;
94 | }
95 | .card-title {
96 | line-height: 48px;
97 | }
98 | }
99 |
100 | .card-action {
101 | position: relative;
102 | background-color: inherit;
103 | border-top: 1px solid rgba(160,160,160,.2);
104 | padding: $card-padding;
105 | z-index: 2;
106 |
107 | a:not(.btn):not(.btn-large):not(.btn-floating) {
108 | color: $card-link-color;
109 | margin-right: $card-padding;
110 | transition: color .3s ease;
111 | text-transform: uppercase;
112 |
113 | &:hover { color: $card-link-color-light; }
114 | }
115 |
116 | & + .card-reveal {
117 | z-index: 1;
118 | padding-bottom: 64px;
119 | }
120 | }
121 |
122 | .card-reveal {
123 | padding: $card-padding;
124 | position: absolute;
125 | background-color: $card-bg-color;
126 | width: 100%;
127 | overflow-y: auto;
128 | top: 100%;
129 | height: 100%;
130 | z-index: 3;
131 | display: none;
132 |
133 | .card-title {
134 | cursor: pointer;
135 | display: block;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_carousel.scss:
--------------------------------------------------------------------------------
1 | .carousel {
2 | overflow: hidden;
3 | position: relative;
4 | width: 100%;
5 | height: 400px;
6 | perspective: 500px;
7 | transform-style: preserve-3d;
8 | transform-origin: 0% 50%;
9 |
10 | .carousel-item {
11 | width: 200px;
12 | position: absolute;
13 | top: 0;
14 | left: 0;
15 |
16 | img {
17 | width: 100%;
18 | }
19 | }
20 |
21 | &.carousel-slider {
22 | top: 0;
23 | left: 0;
24 | height: 0;
25 |
26 | .carousel-item {
27 | width: 100%;
28 | height: 100%;
29 | position: absolute;
30 | top: 0;
31 | left: 0;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_chips.scss:
--------------------------------------------------------------------------------
1 | .chip {
2 | display: inline-block;
3 | height: 32px;
4 | font-size: 13px;
5 | font-weight: 500;
6 | color: rgba(0,0,0,.6);
7 | line-height: 32px;
8 | padding: 0 12px;
9 | border-radius: 16px;
10 | background-color: $chip-bg-color;
11 |
12 | img {
13 | float: left;
14 | margin: 0 8px 0 -12px;
15 | height: 32px;
16 | width: 32px;
17 | border-radius: 50%;
18 | }
19 |
20 | i.material-icons {
21 | cursor: pointer;
22 | float: right;
23 | font-size: 16px;
24 | line-height: 32px;
25 | padding-left: 8px;
26 | }
27 | }
--------------------------------------------------------------------------------
/docs/assets/css/components/_collapsible.scss:
--------------------------------------------------------------------------------
1 | .collapsible {
2 | border-top: 1px solid $collapsible-border-color;
3 | border-right: 1px solid $collapsible-border-color;
4 | border-left: 1px solid $collapsible-border-color;
5 | margin: $element-top-margin 0 $element-bottom-margin 0;
6 | @extend .z-depth-1;
7 | }
8 |
9 | .collapsible-header {
10 | display: block;
11 | cursor: pointer;
12 | min-height: $collapsible-height;
13 | line-height: $collapsible-height;
14 | padding: 0 1rem;
15 | background-color: $collapsible-header-color;
16 | border-bottom: 1px solid $collapsible-border-color;
17 |
18 | i {
19 | width: 2rem;
20 | font-size: 1.6rem;
21 | line-height: $collapsible-height;
22 | display: block;
23 | float: left;
24 | text-align: center;
25 | margin-right: 1rem;
26 | }
27 | }
28 |
29 | .collapsible-body {
30 | display: none;
31 | border-bottom: 1px solid $collapsible-border-color;
32 | box-sizing: border-box;
33 |
34 | p {
35 | margin: 0;
36 | padding: 2rem;
37 | }
38 | }
39 |
40 | // sideNav collapsible styling
41 | .side-nav,
42 | .side-nav.fixed {
43 |
44 | .collapsible {
45 | border: none;
46 | box-shadow: none;
47 |
48 | li { padding: 0; }
49 | }
50 |
51 | .collapsible-header {
52 | background-color: transparent;
53 | border: none;
54 | line-height: inherit;
55 | height: inherit;
56 | padding: 0 $sidenav-padding-right;
57 |
58 | &:hover { background-color: rgba(0,0,0,.05); }
59 | i { line-height: inherit; }
60 | }
61 |
62 | .collapsible-body {
63 | border: 0;
64 | background-color: $collapsible-header-color;
65 |
66 | li a {
67 | padding: 0 (7.5px + $sidenav-padding-right)
68 | 0 (15px + $sidenav-padding-right);
69 | }
70 | }
71 |
72 | }
73 |
74 | // Popout Collapsible
75 |
76 | .collapsible.popout {
77 | border: none;
78 | box-shadow: none;
79 | > li {
80 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
81 | // transform: scaleX(.92);
82 | margin: 0 24px;
83 | transition: margin .35s cubic-bezier(0.250, 0.460, 0.450, 0.940);
84 | }
85 | > li.active {
86 | box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15);
87 | margin: 16px 0;
88 | // transform: scaleX(1);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_dropdown.scss:
--------------------------------------------------------------------------------
1 | .dropdown-content {
2 | @extend .z-depth-1;
3 | background-color: $dropdown-bg-color;
4 | margin: 0;
5 | display: none;
6 | min-width: 100px;
7 | max-height: 650px;
8 | overflow-y: auto;
9 | opacity: 0;
10 | position: absolute;
11 | z-index: 999;
12 | will-change: width, height;
13 |
14 | li {
15 | clear: both;
16 | color: $off-black;
17 | cursor: pointer;
18 | min-height: $dropdown-item-height;
19 | line-height: 1.5rem;
20 | width: 100%;
21 | text-align: left;
22 | text-transform: none;
23 |
24 | &:hover, &.active, &.selected {
25 | background-color: $dropdown-hover-bg-color;
26 | }
27 |
28 | &.active.selected {
29 | background-color: darken($dropdown-hover-bg-color, 5%);
30 | }
31 |
32 | &.divider {
33 | min-height: 0;
34 | height: 1px;
35 | }
36 |
37 | & > a, & > span {
38 | font-size: 16px;
39 | color: $dropdown-color;
40 | display: block;
41 | line-height: 22px;
42 | padding: (($dropdown-item-height - 22) / 2) 16px;
43 | }
44 |
45 | & > span > label {
46 | top: 1px;
47 | left: 3px;
48 | height: 18px;
49 | }
50 |
51 | // Icon alignment override
52 | & > a > i {
53 | height: inherit;
54 | line-height: inherit;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_grid.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | margin: 0 auto;
3 | max-width: 1280px;
4 | width: 90%;
5 | }
6 | @media #{$medium-and-up} {
7 | .container {
8 | width: 85%;
9 | }
10 | }
11 | @media #{$large-and-up} {
12 | .container {
13 | width: 70%;
14 | }
15 | }
16 | .container .row {
17 | margin-left: (-1 * $gutter-width / 2);
18 | margin-right: (-1 * $gutter-width / 2);
19 | }
20 |
21 | .section {
22 | padding-top: 1rem;
23 | padding-bottom: 1rem;
24 |
25 | &.no-pad {
26 | padding: 0;
27 | }
28 | &.no-pad-bot {
29 | padding-bottom: 0;
30 | }
31 | &.no-pad-top {
32 | padding-top: 0;
33 | }
34 | }
35 |
36 |
37 | .row {
38 | margin-left: auto;
39 | margin-right: auto;
40 | margin-bottom: 20px;
41 |
42 | // Clear floating children
43 | &:after {
44 | content: "";
45 | display: table;
46 | clear: both;
47 | }
48 |
49 | .col {
50 | float: left;
51 | box-sizing: border-box;
52 | padding: 0 $gutter-width / 2;
53 |
54 | &[class*="push-"],
55 | &[class*="pull-"] {
56 | position: relative;
57 | }
58 |
59 | $i: 1;
60 | @while $i <= $num-cols {
61 | $perc: unquote((100 / ($num-cols / $i)) + "%");
62 | &.s#{$i} {
63 | width: $perc;
64 | margin-left: auto;
65 | left: auto;
66 | right: auto;
67 | }
68 | $i: $i + 1;
69 | }
70 |
71 | $i: 1;
72 | @while $i <= $num-cols {
73 | $perc: unquote((100 / ($num-cols / $i)) + "%");
74 | &.offset-s#{$i} {
75 | margin-left: $perc;
76 | }
77 | &.pull-s#{$i} {
78 | right: $perc;
79 | }
80 | &.push-s#{$i} {
81 | left: $perc;
82 | }
83 | $i: $i + 1;
84 | }
85 |
86 | @media #{$medium-and-up} {
87 |
88 | $i: 1;
89 | @while $i <= $num-cols {
90 | $perc: unquote((100 / ($num-cols / $i)) + "%");
91 | &.m#{$i} {
92 | width: $perc;
93 | margin-left: auto;
94 | left: auto;
95 | right: auto;
96 | }
97 | $i: $i + 1
98 | }
99 |
100 | $i: 1;
101 | @while $i <= $num-cols {
102 | $perc: unquote((100 / ($num-cols / $i)) + "%");
103 | &.offset-m#{$i} {
104 | margin-left: $perc;
105 | }
106 | &.pull-m#{$i} {
107 | right: $perc;
108 | }
109 | &.push-m#{$i} {
110 | left: $perc;
111 | }
112 | $i: $i + 1;
113 | }
114 | }
115 |
116 | @media #{$large-and-up} {
117 |
118 | $i: 1;
119 | @while $i <= $num-cols {
120 | $perc: unquote((100 / ($num-cols / $i)) + "%");
121 | &.l#{$i} {
122 | width: $perc;
123 | margin-left: auto;
124 | left: auto;
125 | right: auto;
126 | }
127 | $i: $i + 1;
128 | }
129 |
130 | $i: 1;
131 | @while $i <= $num-cols {
132 | $perc: unquote((100 / ($num-cols / $i)) + "%");
133 | &.offset-l#{$i} {
134 | margin-left: $perc;
135 | }
136 | &.pull-l#{$i} {
137 | right: $perc;
138 | }
139 | &.push-l#{$i} {
140 | left: $perc;
141 | }
142 | $i: $i + 1;
143 | }
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_icons-material-design.scss:
--------------------------------------------------------------------------------
1 | /* This is needed for some mobile phones to display the Google Icon font properly */
2 | .material-icons {
3 | text-rendering: optimizeLegibility;
4 | font-feature-settings: 'liga';
5 | }
6 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_materialbox.scss:
--------------------------------------------------------------------------------
1 | .materialboxed {
2 | display: block;
3 | cursor: zoom-in;
4 | position: relative;
5 | transition: opacity .4s;
6 |
7 | &:hover {
8 | &:not(.active) {
9 | opacity: .8;
10 | }
11 | will-change: left, top, width, height;
12 | }
13 | }
14 |
15 | .materialboxed.active {
16 | cursor: zoom-out;
17 | }
18 |
19 | #materialbox-overlay {
20 | position:fixed;
21 | top:0;
22 | left:0;
23 | right: 0;
24 | bottom: 0;
25 | background-color: #292929;
26 | z-index: 1000;
27 |
28 | will-change: opacity;
29 | }
30 | .materialbox-caption {
31 | position: fixed;
32 | display: none;
33 | color: #fff;
34 | line-height: 50px;
35 | bottom: 0;
36 | width: 100%;
37 | text-align: center;
38 | padding: 0% 15%;
39 | height: 50px;
40 | z-index: 1000;
41 | -webkit-font-smoothing: antialiased;
42 | }
--------------------------------------------------------------------------------
/docs/assets/css/components/_me.scss:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | nav {
7 | -webkit-box-shadow: none;
8 | box-shadow: none;
9 | background: none;
10 | position: absolute;
11 | top: 0;
12 | width: 100%;
13 | height: 55px;
14 | }
15 |
16 | section {
17 | display: table;
18 | width: 100%;
19 | }
20 |
21 | .fixed {
22 | background-attachment: fixed;
23 | background-size: cover;
24 | background-repeat: no-repeat;
25 | }
26 | .v100 {
27 | height: 100vh;
28 | }
29 | .v80 {
30 | height: 80vh;
31 | }
32 |
33 | .v70 {
34 | height: 70vh;
35 | }
36 |
37 | .v50 {
38 | height: 50vh;
39 | }
40 |
41 | .radius-border{
42 | -webkit-border-radius: 3px;
43 | -moz-border-radius: 3px;
44 | border-radius: 3px;
45 | }
46 | .opacity-background{
47 | background: rgba(0,0,0,.5);
48 | }
49 |
50 | .love-footer {
51 | color:red;
52 | }
53 |
54 | .container-wide {
55 | padding: 5em 2em;
56 | }
57 |
58 | .post-title {
59 | font-size: 2em;
60 | }
61 |
62 | .post-date {
63 | font-size: 0.8em;
64 | }
--------------------------------------------------------------------------------
/docs/assets/css/components/_mixins.scss:
--------------------------------------------------------------------------------
1 | // @mixin box-shadow-2($args1, $args2) {
2 | // -webkit-box-shadow: $args1, $args2;
3 | // -moz-box-shadow: $args1, $args2;
4 | // box-shadow: $args1, $args2;
5 | // }
--------------------------------------------------------------------------------
/docs/assets/css/components/_modal.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | @extend .z-depth-4;
3 |
4 | display: none;
5 | position: fixed;
6 | left: 0;
7 | right: 0;
8 | background-color: #fafafa;
9 | padding: 0;
10 | max-height: 70%;
11 | width: 55%;
12 | margin: auto;
13 | overflow-y: auto;
14 |
15 | border-radius: 2px;
16 | will-change: top, opacity;
17 |
18 | @media #{$medium-and-down} {
19 | width: 80%;
20 | }
21 |
22 | h1,h2,h3,h4 {
23 | margin-top: 0;
24 | }
25 |
26 | .modal-content {
27 | padding: 24px;
28 | }
29 | .modal-close {
30 | cursor: pointer;
31 | }
32 |
33 | .modal-footer {
34 | border-radius: 0 0 2px 2px;
35 | background-color: #fafafa;
36 | padding: 4px 6px;
37 | height: 56px;
38 | width: 100%;
39 |
40 | .btn, .btn-flat {
41 | float: right;
42 | margin: 6px 0;
43 | }
44 | }
45 | }
46 | .lean-overlay {
47 | position: fixed;
48 | z-index:999;
49 | top: -100px;
50 | left: 0;
51 | bottom: 0;
52 | right: 0;
53 | height: 125%;
54 | width: 100%;
55 | background: #000;
56 | display: none;
57 |
58 | will-change: opacity;
59 | }
60 |
61 | // Modal with fixed action footer
62 | .modal.modal-fixed-footer {
63 | padding: 0;
64 | height: 70%;
65 |
66 | .modal-content {
67 | position: absolute;
68 | height: calc(100% - 56px);
69 | max-height: 100%;
70 | width: 100%;
71 | overflow-y: auto;
72 | }
73 |
74 | .modal-footer {
75 | border-top: 1px solid rgba(0,0,0,.1);
76 | position: absolute;
77 | bottom: 0;
78 | }
79 | }
80 |
81 | // Modal Bottom Sheet Style
82 | .modal.bottom-sheet {
83 | top: auto;
84 | bottom: -100%;
85 | margin: 0;
86 | width: 100%;
87 | max-height: 45%;
88 | border-radius: 0;
89 | will-change: bottom, opacity;
90 | }
91 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_roboto.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "Roboto";
3 | src: local(Roboto Thin), url('#{$roboto-font-path}Roboto-Thin.eot');
4 | src: url("#{$roboto-font-path}Roboto-Thin.eot?#iefix") format('embedded-opentype'),
5 | url("#{$roboto-font-path}Roboto-Thin.woff2") format("woff2"),
6 | url("#{$roboto-font-path}Roboto-Thin.woff") format("woff"),
7 | url("#{$roboto-font-path}Roboto-Thin.ttf") format("truetype");
8 |
9 | font-weight: 200;
10 | }
11 | @font-face {
12 | font-family: "Roboto";
13 | src: local(Roboto Light), url('#{$roboto-font-path}Roboto-Light.eot');
14 | src: url("#{$roboto-font-path}Roboto-Light.eot?#iefix") format('embedded-opentype'),
15 | url("#{$roboto-font-path}Roboto-Light.woff2") format("woff2"),
16 | url("#{$roboto-font-path}Roboto-Light.woff") format("woff"),
17 | url("#{$roboto-font-path}Roboto-Light.ttf") format("truetype");
18 | font-weight: 300;
19 | }
20 |
21 | @font-face {
22 | font-family: "Roboto";
23 | src: local(Roboto Regular), url('#{$roboto-font-path}Roboto-Regular.eot');
24 | src: url("#{$roboto-font-path}Roboto-Regular.eot?#iefix") format('embedded-opentype'),
25 | url("#{$roboto-font-path}Roboto-Regular.woff2") format("woff2"),
26 | url("#{$roboto-font-path}Roboto-Regular.woff") format("woff"),
27 | url("#{$roboto-font-path}Roboto-Regular.ttf") format("truetype");
28 | font-weight: 400;
29 | }
30 |
31 | @font-face {
32 | font-family: "Roboto";
33 | src: url('#{$roboto-font-path}Roboto-Medium.eot');
34 | src: url("#{$roboto-font-path}Roboto-Medium.eot?#iefix") format('embedded-opentype'),
35 | url("#{$roboto-font-path}Roboto-Medium.woff2") format("woff2"),
36 | url("#{$roboto-font-path}Roboto-Medium.woff") format("woff"),
37 | url("#{$roboto-font-path}Roboto-Medium.ttf") format("truetype");
38 | font-weight: 500;
39 | }
40 |
41 | @font-face {
42 | font-family: "Roboto";
43 | src: url('#{$roboto-font-path}Roboto-Bold.eot');
44 | src: url("#{$roboto-font-path}Roboto-Bold.eot?#iefix") format('embedded-opentype'),
45 | url("#{$roboto-font-path}Roboto-Bold.woff2") format("woff2"),
46 | url("#{$roboto-font-path}Roboto-Bold.woff") format("woff"),
47 | url("#{$roboto-font-path}Roboto-Bold.ttf") format("truetype");
48 | font-weight: 700;
49 | }
50 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_sideNav.scss:
--------------------------------------------------------------------------------
1 | .side-nav {
2 | position: fixed;
3 | width: 240px;
4 | left: 0;
5 | top: 0;
6 | margin: 0;
7 | transform: translateX(-100%);
8 | height: 100%;
9 | height: calc(100% + 60px);
10 | height: -moz-calc(100%); //Temporary Firefox Fix
11 | padding-bottom: 60px;
12 | background-color: $sidenav-bg-color;
13 | z-index: 999;
14 | backface-visibility: hidden;
15 | overflow-y: auto;
16 | will-change: transform;
17 | backface-visibility: hidden;
18 | transform: translateX(-105%);
19 |
20 | @extend .z-depth-1;
21 |
22 | // Right Align
23 | &.right-aligned {
24 | right: 0;
25 | transform: translateX(105%);
26 | left: auto;
27 | transform: translateX(100%);
28 | }
29 |
30 | .collapsible {
31 | margin: 0;
32 | }
33 |
34 |
35 | li {
36 | float: none;
37 | line-height: $sidenav-item-height;
38 |
39 | &.active { background-color: rgba(0,0,0,.05); }
40 | }
41 |
42 | a {
43 | color: $sidenav-font-color;
44 | display: block;
45 | font-size: 1rem;
46 | height: $sidenav-item-height;
47 | line-height: $sidenav-item-height;
48 | padding: 0 $sidenav-padding-right;
49 |
50 | &:hover { background-color: rgba(0,0,0,.05);}
51 |
52 | &.btn, &.btn-large, &.btn-flat, &.btn-floating {
53 | margin: 10px 15px;
54 | }
55 |
56 | &.btn,
57 | &.btn-large,
58 | &.btn-floating { color: $button-raised-color; }
59 | &.btn-flat { color: $button-flat-color; }
60 |
61 | &.btn:hover,
62 | &.btn-large:hover { background-color: lighten($button-raised-background, 5%); }
63 | &.btn-floating:hover { background-color: $button-raised-background; }
64 | }
65 | }
66 |
67 |
68 | // Touch interaction
69 | .drag-target {
70 | height: 100%;
71 | width: 10px;
72 | position: fixed;
73 | top: 0;
74 | z-index: 998;
75 | }
76 |
77 |
78 | // Hidden side-nav for all sizes
79 | .side-nav.fixed {
80 | a {
81 | display: block;
82 | padding: 0 $sidenav-padding-right;
83 | color: $sidenav-font-color;
84 | }
85 | }
86 |
87 |
88 | // Fixed side-nav shown
89 | .side-nav.fixed {
90 | left: 0;
91 | transform: translateX(0);
92 | position: fixed;
93 |
94 | // Right Align
95 | &.right-aligned {
96 | right: 0;
97 | left: auto;
98 | }
99 | }
100 |
101 | // Fixed sideNav hide on smaller
102 | @media #{$medium-and-down} {
103 | .side-nav.fixed {
104 | transform: translateX(-105%);
105 |
106 | &.right-aligned {
107 | transform: translateX(105%);
108 | }
109 | }
110 | }
111 |
112 |
113 | .side-nav .collapsible-body li.active,
114 | .side-nav.fixed .collapsible-body li.active {
115 | background-color: $primary-color;
116 | a {
117 | color: $sidenav-bg-color;
118 | }
119 | }
120 |
121 |
122 | #sidenav-overlay {
123 | position: fixed;
124 | top: 0;
125 | left: 0;
126 | right: 0;
127 |
128 | height: 120vh;
129 | background-color: rgba(0,0,0,.5);
130 | z-index: 997;
131 |
132 | will-change: opacity;
133 | }
134 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_slider.scss:
--------------------------------------------------------------------------------
1 | .slider {
2 | position: relative;
3 | height: 400px;
4 | width: 100%;
5 |
6 | // Fullscreen slider
7 | &.fullscreen {
8 | height: 100%;
9 | width: 100%;
10 | position: absolute;
11 | top: 0;
12 | left: 0;
13 | right: 0;
14 | bottom: 0;
15 |
16 | ul.slides {
17 | height: 100%;
18 | }
19 |
20 | ul.indicators {
21 | z-index: 2;
22 | bottom: 30px;
23 | }
24 | }
25 |
26 | .slides {
27 | background-color: $slider-bg-color;
28 | margin: 0;
29 | height: 400px;
30 |
31 | li {
32 | opacity: 0;
33 | position: absolute;
34 | top: 0;
35 | left: 0;
36 | z-index: 1;
37 | width: 100%;
38 | height: inherit;
39 | overflow: hidden;
40 |
41 | img {
42 | height: 100%;
43 | width: 100%;
44 | background-size: cover;
45 | background-position: center;
46 | }
47 |
48 | .caption {
49 | color: #fff;
50 | position: absolute;
51 | top: 15%;
52 | left: 15%;
53 | width: 70%;
54 | opacity: 0;
55 |
56 | p { color: $slider-bg-color-light; }
57 | }
58 |
59 | &.active {
60 | z-index: 2;
61 | }
62 | }
63 | }
64 |
65 |
66 | .indicators {
67 | position: absolute;
68 | text-align: center;
69 | left: 0;
70 | right: 0;
71 | bottom: 0;
72 | margin: 0;
73 |
74 | .indicator-item {
75 | display: inline-block;
76 | position: relative;
77 | cursor: pointer;
78 | height: 16px;
79 | width: 16px;
80 | margin: 0 12px;
81 | background-color: $slider-bg-color-light;
82 |
83 | transition: background-color .3s;
84 | border-radius: 50%;
85 |
86 | &.active {
87 | background-color: $slider-indicator-color;
88 | }
89 | }
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/docs/assets/css/components/_table_of_contents.scss:
--------------------------------------------------------------------------------
1 | /***************
2 | Nav List
3 | ***************/
4 | .table-of-contents {
5 | &.fixed {
6 | position: fixed;
7 | }
8 |
9 | li {
10 | padding: 2px 0;
11 | }
12 | a {
13 | display: inline-block;
14 | font-weight: 300;
15 | color: #757575;
16 | padding-left: 20px;
17 | height: 1.5rem;
18 | line-height: 1.5rem;
19 | letter-spacing: .4;
20 | display: inline-block;
21 |
22 | &:hover {
23 | color: lighten(#757575, 20%);
24 | padding-left: 19px;
25 | border-left: 1px solid lighten(color("materialize-red", "base"),10%);
26 | }
27 | &.active {
28 | font-weight: 500;
29 | padding-left: 18px;
30 | border-left: 2px solid lighten(color("materialize-red", "base"),10%);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_tabs.scss:
--------------------------------------------------------------------------------
1 | .tabs {
2 | display: flex;
3 | position: relative;
4 | overflow-x: auto;
5 | overflow-y: hidden;
6 | height: 48px;
7 | background-color: $tabs-bg-color;
8 | margin: 0 auto;
9 | width: 100%;
10 | white-space: nowrap;
11 |
12 | .tab {
13 | -webkit-box-flex: 1;
14 | -webkit-flex-grow: 1;
15 | -ms-flex-positive: 1;
16 | flex-grow: 1;
17 | display: block;
18 | float: left;
19 | text-align: center;
20 | line-height: 48px;
21 | height: 48px;
22 | padding: 0;
23 | margin: 0;
24 | text-transform: uppercase;
25 | text-overflow: ellipsis;
26 | overflow: hidden;
27 | letter-spacing: .8px;
28 | width: 15%;
29 | min-width: 80px;
30 |
31 | a {
32 | color: $tabs-text-color;
33 | display: block;
34 | width: 100%;
35 | height: 100%;
36 | text-overflow: ellipsis;
37 | overflow: hidden;
38 | transition: color .28s ease;
39 | &:hover {
40 | color: lighten($tabs-text-color, 20%);
41 | }
42 | }
43 |
44 | &.disabled a {
45 | color: lighten($tabs-text-color, 20%);
46 | cursor: default;
47 | }
48 | }
49 | .indicator {
50 | position: absolute;
51 | bottom: 0;
52 | height: 2px;
53 | background-color: $tabs-underline-color;
54 | will-change: left, right;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_toast.scss:
--------------------------------------------------------------------------------
1 | #toast-container {
2 | display:block;
3 | position: fixed;
4 | z-index: 10000;
5 |
6 | @media #{$small-and-down} {
7 | min-width: 100%;
8 | bottom: 0%;
9 | }
10 | @media #{$medium-only} {
11 | left: 5%;
12 | bottom: 7%;
13 | max-width: 90%;
14 | }
15 | @media #{$large-and-up} {
16 | top: 10%;
17 | right: 7%;
18 | max-width: 86%;
19 | }
20 | }
21 |
22 | .toast {
23 | @extend .z-depth-1;
24 | border-radius: 2px;
25 | top: 0;
26 | width: auto;
27 | clear: both;
28 | margin-top: 10px;
29 | position: relative;
30 | max-width:100%;
31 | height: auto;
32 | min-height: $toast-height;
33 | line-height: 1.5em;
34 | word-break: break-all;
35 | background-color: $toast-color;
36 | padding: 10px 25px;
37 | font-size: 1.1rem;
38 | font-weight: 300;
39 | color: $toast-text-color;
40 |
41 | display: flex;
42 | align-items: center;
43 | justify-content: space-between;
44 |
45 | .btn, .btn-flat {
46 | margin: 0;
47 | margin-left: 3rem;
48 | }
49 |
50 | &.rounded{
51 | border-radius: 24px;
52 | }
53 |
54 | @media #{$small-and-down} {
55 | width:100%;
56 | border-radius: 0;
57 | }
58 | @media #{$medium-only} {
59 | float: left;
60 | }
61 | @media #{$large-and-up} {
62 | float: right;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_tooltip.scss:
--------------------------------------------------------------------------------
1 | .material-tooltip {
2 | padding: 10px 8px;
3 | font-size: 1rem;
4 | z-index: 2000;
5 | background-color: transparent;
6 | border-radius: 2px;
7 | color: #fff;
8 | min-height: 36px;
9 | line-height: 120%;
10 | opacity: 0;
11 | display: none;
12 | position: absolute;
13 | text-align: center;
14 | max-width: calc(100% - 4px);
15 | overflow: hidden;
16 | left:0;
17 | top:0;
18 | pointer-events: none;
19 | will-change: top, left;
20 | }
21 |
22 | .backdrop {
23 | position: absolute;
24 | opacity: 0;
25 | display: none;
26 | height: 7px;
27 | width: 14px;
28 | border-radius: 0 0 14px 14px;
29 | background-color: #323232;
30 | z-index: -1;
31 | transform-origin: 50% 10%;
32 |
33 | will-change: transform, opacity;
34 | }
35 |
--------------------------------------------------------------------------------
/docs/assets/css/components/_typography.scss:
--------------------------------------------------------------------------------
1 |
2 | a {
3 | text-decoration: none;
4 | }
5 |
6 | html{
7 | line-height: 1.5;
8 |
9 | @media only screen and (min-width: 0) {
10 | font-size: 14px;
11 | }
12 |
13 | @media only screen and (min-width: $medium-screen) {
14 | font-size: 14.5px;
15 | }
16 |
17 | @media only screen and (min-width: $large-screen) {
18 | font-size: 15px;
19 | }
20 |
21 | font-family: "Roboto", sans-serif;
22 | font-weight: normal;
23 | color: $off-black;
24 | }
25 | h1, h2, h3, h4, h5, h6 {
26 | font-weight: 400;
27 | line-height: 1.1;
28 | }
29 |
30 | // Header Styles
31 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; }
32 | h1 { font-size: $h1-fontsize; line-height: 110%; margin: ($h1-fontsize / 2) 0 ($h1-fontsize / 2.5) 0;}
33 | h2 { font-size: $h2-fontsize; line-height: 110%; margin: ($h2-fontsize / 2) 0 ($h2-fontsize / 2.5) 0;}
34 | h3 { font-size: $h3-fontsize; line-height: 110%; margin: ($h3-fontsize / 2) 0 ($h3-fontsize / 2.5) 0;}
35 | h4 { font-size: $h4-fontsize; line-height: 110%; margin: ($h4-fontsize / 2) 0 ($h4-fontsize / 2.5) 0;}
36 | h5 { font-size: $h5-fontsize; line-height: 110%; margin: ($h5-fontsize / 2) 0 ($h5-fontsize / 2.5) 0;}
37 | h6 { font-size: $h6-fontsize; line-height: 110%; margin: ($h6-fontsize / 2) 0 ($h6-fontsize / 2.5) 0;}
38 |
39 | // Text Styles
40 | em { font-style: italic; }
41 | strong { font-weight: 500; }
42 | small { font-size: 75%; }
43 | .light { font-weight: 300; }
44 | .thin { font-weight: 200; }
45 |
46 |
47 | .flow-text{
48 | font-weight: 300;
49 | $i: 0;
50 | @while $i <= $intervals {
51 | @media only screen and (min-width : 360 + ($i * $interval-size)) {
52 | font-size: 1.2rem * (1 + (.02 * $i));
53 | }
54 | $i: $i + 1;
55 | }
56 |
57 | // Handle below 360px screen
58 | @media only screen and (max-width: 360px) {
59 | font-size: 1.2rem;
60 | }
61 | }
--------------------------------------------------------------------------------
/docs/assets/css/components/date_picker/_default.time.scss:
--------------------------------------------------------------------------------
1 | /* ==========================================================================
2 | $BASE-TIME-PICKER
3 | ========================================================================== */
4 | /**
5 | * The list of times.
6 | */
7 | .picker__list {
8 | list-style: none;
9 | padding: 0.75em 0 4.2em;
10 | margin: 0;
11 | }
12 | /**
13 | * The times on the clock.
14 | */
15 | .picker__list-item {
16 | border-bottom: 1px solid #dddddd;
17 | border-top: 1px solid #dddddd;
18 | margin-bottom: -1px;
19 | position: relative;
20 | background: #ffffff;
21 | padding: .75em 1.25em;
22 | }
23 | @media (min-height: 46.75em) {
24 | .picker__list-item {
25 | padding: .5em 1em;
26 | }
27 | }
28 | /* Hovered time */
29 | .picker__list-item:hover {
30 | cursor: pointer;
31 | color: #000000;
32 | background: #b1dcfb;
33 | border-color: #0089ec;
34 | z-index: 10;
35 | }
36 | /* Highlighted and hovered/focused time */
37 | .picker__list-item--highlighted {
38 | border-color: #0089ec;
39 | z-index: 10;
40 | }
41 | .picker__list-item--highlighted:hover,
42 | .picker--focused .picker__list-item--highlighted {
43 | cursor: pointer;
44 | color: #000000;
45 | background: #b1dcfb;
46 | }
47 | /* Selected and hovered/focused time */
48 | .picker__list-item--selected,
49 | .picker__list-item--selected:hover,
50 | .picker--focused .picker__list-item--selected {
51 | background: #0089ec;
52 | color: #ffffff;
53 | z-index: 10;
54 | }
55 | /* Disabled time */
56 | .picker__list-item--disabled,
57 | .picker__list-item--disabled:hover,
58 | .picker--focused .picker__list-item--disabled {
59 | background: #f5f5f5;
60 | border-color: #f5f5f5;
61 | color: #dddddd;
62 | cursor: default;
63 | border-color: #dddddd;
64 | z-index: auto;
65 | }
66 | /**
67 | * The clear button
68 | */
69 | .picker--time .picker__button--clear {
70 | display: block;
71 | width: 80%;
72 | margin: 1em auto 0;
73 | padding: 1em 1.25em;
74 | background: none;
75 | border: 0;
76 | font-weight: 500;
77 | font-size: .67em;
78 | text-align: center;
79 | text-transform: uppercase;
80 | color: #666;
81 | }
82 | .picker--time .picker__button--clear:hover,
83 | .picker--time .picker__button--clear:focus {
84 | color: #000000;
85 | background: #b1dcfb;
86 | background: #ee2200;
87 | border-color: #ee2200;
88 | cursor: pointer;
89 | color: #ffffff;
90 | outline: none;
91 | }
92 | .picker--time .picker__button--clear:before {
93 | top: -0.25em;
94 | color: #666;
95 | font-size: 1.25em;
96 | font-weight: bold;
97 | }
98 | .picker--time .picker__button--clear:hover:before,
99 | .picker--time .picker__button--clear:focus:before {
100 | color: #ffffff;
101 | }
102 |
103 | /* ==========================================================================
104 | $DEFAULT-TIME-PICKER
105 | ========================================================================== */
106 | /**
107 | * The frame the bounds the time picker.
108 | */
109 | .picker--time .picker__frame {
110 | min-width: 256px;
111 | max-width: 320px;
112 | }
113 | /**
114 | * The picker box.
115 | */
116 | .picker--time .picker__box {
117 | font-size: 1em;
118 | background: #f2f2f2;
119 | padding: 0;
120 | }
121 | @media (min-height: 40.125em) {
122 | .picker--time .picker__box {
123 | margin-bottom: 5em;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/docs/assets/css/components/forms/_file-input.scss:
--------------------------------------------------------------------------------
1 | /* File Input
2 | ========================================================================== */
3 |
4 | .file-field {
5 | position: relative;
6 |
7 | .file-path-wrapper {
8 | overflow: hidden;
9 | padding-left: 10px;
10 | }
11 |
12 | input.file-path { width: 100%; }
13 |
14 | .btn {
15 | float: left;
16 | height: $input-height;
17 | line-height: $input-height;
18 | }
19 |
20 | span {
21 | cursor: pointer;
22 | }
23 |
24 | input[type=file] {
25 | position: absolute;
26 | top: 0;
27 | right: 0;
28 | left: 0;
29 | bottom: 0;
30 | width: 100%;
31 | margin: 0;
32 | padding: 0;
33 | font-size: 20px;
34 | cursor: pointer;
35 | opacity: 0;
36 | filter: alpha(opacity=0);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docs/assets/css/components/forms/_forms.scss:
--------------------------------------------------------------------------------
1 | // Remove Focus Boxes
2 | select:focus {
3 | outline: $select-focus;
4 | }
5 |
6 | button:focus {
7 | outline: none;
8 | background-color: $button-background-focus;
9 | }
10 |
11 | label {
12 | font-size: $label-font-size;
13 | color: $input-border-color;
14 | }
15 |
16 | @import 'input-fields';
17 | @import 'radio-buttons';
18 | @import 'checkboxes';
19 | @import 'switches';
20 | @import 'select';
21 | @import 'file-input';
22 | @import 'range';
23 |
--------------------------------------------------------------------------------
/docs/assets/css/components/forms/_radio-buttons.scss:
--------------------------------------------------------------------------------
1 | /* Radio Buttons
2 | ========================================================================== */
3 |
4 | // Remove default Radio Buttons
5 | [type="radio"]:not(:checked),
6 | [type="radio"]:checked {
7 | position: absolute;
8 | left: -9999px;
9 | opacity: 0;
10 | }
11 |
12 | [type="radio"]:not(:checked) + label,
13 | [type="radio"]:checked + label {
14 | position: relative;
15 | padding-left: 35px;
16 | cursor: pointer;
17 | display: inline-block;
18 | height: 25px;
19 | line-height: 25px;
20 | font-size: 1rem;
21 | transition: .28s ease;
22 |
23 | -khtml-user-select: none; /* webkit (konqueror) browsers */
24 | user-select: none;
25 | }
26 |
27 | [type="radio"] + label:before,
28 | [type="radio"] + label:after {
29 | content: '';
30 | position: absolute;
31 | left: 0;
32 | top: 0;
33 | margin: 4px;
34 | width: 16px;
35 | height: 16px;
36 | z-index: 0;
37 | transition: .28s ease;
38 | }
39 |
40 | /* Unchecked styles */
41 | [type="radio"]:not(:checked) + label:before,
42 | [type="radio"]:not(:checked) + label:after,
43 | [type="radio"]:checked + label:before,
44 | [type="radio"]:checked + label:after,
45 | [type="radio"].with-gap:checked + label:before,
46 | [type="radio"].with-gap:checked + label:after {
47 | border-radius: 50%;
48 | }
49 |
50 | [type="radio"]:not(:checked) + label:before,
51 | [type="radio"]:not(:checked) + label:after {
52 | border: 2px solid $radio-empty-color;
53 | }
54 |
55 | [type="radio"]:not(:checked) + label:after {
56 | z-index: -1;
57 | transform: scale(0);
58 | }
59 |
60 | /* Checked styles */
61 | [type="radio"]:checked + label:before {
62 | border: 2px solid transparent;
63 | }
64 |
65 | [type="radio"]:checked + label:after,
66 | [type="radio"].with-gap:checked + label:before,
67 | [type="radio"].with-gap:checked + label:after {
68 | border: $radio-border;
69 | }
70 |
71 | [type="radio"]:checked + label:after,
72 | [type="radio"].with-gap:checked + label:after {
73 | background-color: $radio-fill-color;
74 | z-index: 0;
75 | }
76 |
77 | [type="radio"]:checked + label:after {
78 | transform: scale(1.02);
79 | }
80 |
81 | /* Radio With gap */
82 | [type="radio"].with-gap:checked + label:after {
83 | transform: scale(.5);
84 | }
85 |
86 | /* Focused styles */
87 | [type="radio"].tabbed:focus + label:before {
88 | box-shadow: 0 0 0 10px rgba(0,0,0,.1);
89 | }
90 |
91 | /* Disabled Radio With gap */
92 | [type="radio"].with-gap:disabled:checked + label:before {
93 | border: 2px solid $input-disabled-color;
94 | }
95 |
96 | [type="radio"].with-gap:disabled:checked + label:after {
97 | border: none;
98 | background-color: $input-disabled-color;
99 | }
100 |
101 | /* Disabled style */
102 | [type="radio"]:disabled:not(:checked) + label:before,
103 | [type="radio"]:disabled:checked + label:before {
104 | background-color: transparent;
105 | border-color: $input-disabled-color;
106 | }
107 |
108 | [type="radio"]:disabled + label {
109 | color: $input-disabled-color;
110 | }
111 |
112 | [type="radio"]:disabled:not(:checked) + label:before {
113 | border-color: $input-disabled-color;
114 | }
115 |
116 | [type="radio"]:disabled:checked + label:after {
117 | background-color: $input-disabled-color;
118 | border-color: $input-disabled-solid-color;
119 | }
120 |
--------------------------------------------------------------------------------
/docs/assets/css/components/forms/_select.scss:
--------------------------------------------------------------------------------
1 | /* Select Field
2 | ========================================================================== */
3 |
4 | select { display: none; }
5 | select.browser-default { display: block; }
6 |
7 | select {
8 | background-color: $select-background;
9 | width: 100%;
10 | padding: $select-padding;
11 | border: $select-border;
12 | border-radius: $select-radius;
13 | height: $input-height;
14 | }
15 |
16 | .select-label {
17 | position: absolute;
18 | }
19 |
20 | .select-wrapper {
21 | position: relative;
22 |
23 | input.select-dropdown {
24 | position: relative;
25 | cursor: pointer;
26 | background-color: transparent;
27 | border: none;
28 | border-bottom: $input-border;
29 | outline: none;
30 | height: $input-height;
31 | line-height: $input-height;
32 | width: 100%;
33 | font-size: $input-font-size;
34 | margin: $input-margin;
35 | padding: 0;
36 | display: block;
37 | }
38 |
39 | span.caret {
40 | color: initial;
41 | position: absolute;
42 | right: 0;
43 | top: 16px;
44 | font-size: 10px;
45 | &.disabled {
46 | color: $input-disabled-color;
47 | }
48 | }
49 |
50 | & + label {
51 | position: absolute;
52 | top: -14px;
53 | font-size: $label-font-size;
54 | }
55 | }
56 |
57 | // Disabled styles
58 | select:disabled {
59 | color: rgba(0,0,0,.3);
60 | }
61 |
62 | .select-wrapper input.select-dropdown:disabled {
63 | color: rgba(0,0,0,.3);
64 | cursor: default;
65 | -webkit-user-select: none; /* webkit (safari, chrome) browsers */
66 | -moz-user-select: none; /* mozilla browsers */
67 | -ms-user-select: none; /* IE10+ */
68 | border-bottom: 1px solid rgba(0,0,0,.3);
69 | }
70 |
71 | .select-wrapper i {
72 | color: $select-disabled-color;
73 | }
74 |
75 | .select-dropdown li.disabled,
76 | .select-dropdown li.disabled > span,
77 | .select-dropdown li.optgroup {
78 | color: $select-disabled-color;
79 | background-color: transparent;
80 | }
81 |
82 | // Prefix Icons
83 | .prefix ~ .select-wrapper {
84 | margin-left: 3rem;
85 | width: 92%;
86 | width: calc(100% - 3rem);
87 | }
88 |
89 | .prefix ~ label { margin-left: 3rem; }
90 |
91 | // Icons
92 | .select-dropdown li {
93 | img {
94 | height: $dropdown-item-height - 10;
95 | width: $dropdown-item-height - 10;
96 | margin: 5px 15px;
97 | float: right;
98 | }
99 | }
100 |
101 | // Optgroup styles
102 | .select-dropdown li.optgroup {
103 | border-top: 1px solid $dropdown-hover-bg-color;
104 |
105 | &.selected > span {
106 | color: rgba(0, 0, 0, .7);
107 | }
108 |
109 | & > span {
110 | color: rgba(0, 0, 0, .4);
111 | }
112 |
113 | & ~ li.optgroup-option {
114 | padding-left: 1rem;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/docs/assets/css/components/forms/_switches.scss:
--------------------------------------------------------------------------------
1 | /* Switch
2 | ========================================================================== */
3 |
4 | .switch,
5 | .switch * {
6 | -webkit-user-select: none;
7 | -moz-user-select: none;
8 | -khtml-user-select: none;
9 | -ms-user-select: none;
10 | }
11 |
12 | .switch label {
13 | cursor: pointer;
14 | }
15 |
16 | .switch label input[type=checkbox] {
17 | opacity: 0;
18 | width: 0;
19 | height: 0;
20 |
21 | &:checked + .lever {
22 | background-color: $switch-checked-lever-bg;
23 |
24 | &:after {
25 | background-color: $switch-bg-color;
26 | left: 24px;
27 | }
28 | }
29 | }
30 |
31 | .switch label .lever {
32 | content: "";
33 | display: inline-block;
34 | position: relative;
35 | width: 40px;
36 | height: 15px;
37 | background-color: $switch-unchecked-lever-bg;
38 | border-radius: $switch-radius;
39 | margin-right: 10px;
40 | transition: background 0.3s ease;
41 | vertical-align: middle;
42 | margin: 0 16px;
43 |
44 | &:after {
45 | content: "";
46 | position: absolute;
47 | display: inline-block;
48 | width: 21px;
49 | height: 21px;
50 | background-color: $switch-unchecked-bg;
51 | border-radius: 21px;
52 | box-shadow: 0 1px 3px 1px rgba(0,0,0,.4);
53 | left: -5px;
54 | top: -3px;
55 | transition: left 0.3s ease, background .3s ease, box-shadow 0.1s ease;
56 | }
57 | }
58 |
59 | // Switch active style
60 | input[type=checkbox]:checked:not(:disabled) ~ .lever:active::after,
61 | input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::after {
62 | box-shadow: 0 1px 3px 1px rgba(0,0,0,.4), 0 0 0 15px transparentize($switch-bg-color, .9);
63 | }
64 |
65 | input[type=checkbox]:not(:disabled) ~ .lever:active:after,
66 | input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::after {
67 | box-shadow: 0 1px 3px 1px rgba(0,0,0,.4), 0 0 0 15px rgba(0, 0, 0, .08);
68 | }
69 |
70 | // Disabled Styles
71 | .switch input[type=checkbox][disabled] + .lever {
72 | cursor: default;
73 | }
74 |
75 | .switch label input[type=checkbox][disabled] + .lever:after,
76 | .switch label input[type=checkbox][disabled]:checked + .lever:after {
77 | background-color: $input-disabled-solid-color;
78 | }
79 |
--------------------------------------------------------------------------------
/docs/assets/css/materialize.scss:
--------------------------------------------------------------------------------
1 | ---
2 | # This is the Front Matter block
3 | ---
4 |
5 | // Mixins
6 | // @import "components/prefixer";
7 | @import "components/mixins";
8 | @import "components/color";
9 |
10 | // Variables;
11 | @import "components/variables";
12 |
13 | // Reset
14 | @import "components/normalize";
15 |
16 | // components
17 | @import "components/global";
18 | @import "components/icons-material-design";
19 | @import "components/grid";
20 | @import "components/navbar";
21 | @import "components/roboto";
22 | @import "components/typography";
23 | @import "components/cards";
24 | @import "components/toast";
25 | @import "components/tabs";
26 | @import "components/tooltip";
27 | @import "components/buttons";
28 | @import "components/dropdown";
29 | @import "components/waves";
30 | @import "components/modal";
31 | @import "components/collapsible";
32 | @import "components/chips";
33 | @import "components/materialbox";
34 | @import "components/forms/forms";
35 | @import "components/table_of_contents";
36 | @import "components/sideNav";
37 | @import "components/preloader";
38 | @import "components/slider";
39 | @import "components/carousel";
40 | @import "components/date_picker/default";
41 | @import "components/date_picker/default.date";
42 | @import "components/date_picker/default.time";
43 |
--------------------------------------------------------------------------------
/docs/assets/css/styles.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | font-size : 2.5rem !important;
3 | }
4 | h2 {
5 | font-size : 2rem !important;
6 | }
7 | h3 {
8 | font-size : 1.5rem !important;
9 | }
10 | h1,h2,h3,h4,h5 {
11 | color : #95C12B !important;
12 | }
13 | .z-depth-1, nav, .card-panel, .card, .toast, .btn, .btn-large, .btn-floating, .dropdown-content, .collapsible, .side-nav {
14 | box-shadow: 0 2px 5px 0 rgba(149,193,43,0.16), 0 2px 10px 0 rgba(0,0,0,0.12) !important;
15 | }
16 | .toctext{
17 | color : #000000 !important;
18 | }
19 | #div_content ul {
20 | list-style-type: initial !important;
21 | }
22 | #div_content ul li {
23 | list-style-type: initial !important;
24 | margin-left : 20px !important;
25 | }
26 | #div_content {
27 | padding-left : 400px !important;
28 | }
29 | #side-nav {
30 | background-color:#F3F3F3 !important;
31 | z-index:2 !important;
32 | top:60px !important;
33 | width : 350px !important;
34 | }
35 | #div_menuMobile {
36 | background-color:#F3F3F3 !important;
37 | }
38 | @media only screen and (max-width : 992px) {
39 | #div_content {
40 | padding-left : 0 !important;
41 | }
42 | }
43 | .toclevel-2{
44 | margin-left : 10px !important;
45 | }
46 | nav ul a,nav {
47 | color: #444 !important;
48 | background-color: #fff !important;
49 | }
50 | .jeedomcolorbg {
51 | background-color: #95C12B !important;
52 | }
53 | .jeedomcolor {
54 | color: #95C12B !important;
55 | }
56 | .collection a.collection-item {
57 | color: #85a835 !important;
58 | }
59 | .container {
60 | @media only screen and (min-width: 993px){
61 | width: 85% !important;
62 | }
63 | }
64 | .waves-effect.waves-jeedom .waves-ripple {
65 | background-color: rgba(149, 193, 43, 0.65);
66 | }
67 | .toclevel-1 a{
68 | padding-left: 10px !important;
69 | height: 100% !important;
70 | vertical-align: middle !important;
71 | }
72 | .toclevel-1{
73 | vertical-align: middle !important;
74 | }
75 | .toctext{
76 | font-size : 0.85rem !important;
77 | }
78 | .tocnumber{
79 | color : #95C12B !important;
80 | }
81 | #side-nav a{
82 | line-height: 24px !important;
83 | }
84 | .toc a.active{
85 | font-weight: bold;
86 | }
87 | .dropdown-content li>a, .dropdown-content li>span{
88 | color : #95C12B !important;
89 | }
90 | @-moz-document url-prefix() {
91 | .side-nav {
92 | height: 100% !important;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/docs/assets/font/material-design-icons/Material-Design-Icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/material-design-icons/Material-Design-Icons.eot
--------------------------------------------------------------------------------
/docs/assets/font/material-design-icons/Material-Design-Icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/material-design-icons/Material-Design-Icons.ttf
--------------------------------------------------------------------------------
/docs/assets/font/material-design-icons/Material-Design-Icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/material-design-icons/Material-Design-Icons.woff
--------------------------------------------------------------------------------
/docs/assets/font/material-design-icons/Material-Design-Icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/material-design-icons/Material-Design-Icons.woff2
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Bold.eot
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Bold.woff
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Bold.woff2
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Light.eot
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Light.ttf
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Light.woff
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Light.woff2
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Medium.eot
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Medium.ttf
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Medium.woff
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Medium.woff2
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Regular.eot
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Regular.woff
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Regular.woff2
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Thin.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Thin.eot
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Thin.ttf
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Thin.woff
--------------------------------------------------------------------------------
/docs/assets/font/roboto/Roboto-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/font/roboto/Roboto-Thin.woff2
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Bold.eot
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Bold.woff
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Bold.woff2
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Light.eot
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Light.ttf
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Light.woff
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Light.woff2
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Medium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Medium.eot
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Medium.ttf
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Medium.woff
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Medium.woff2
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Regular.eot
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Regular.woff
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Regular.woff2
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Thin.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Thin.eot
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Thin.ttf
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Thin.woff
--------------------------------------------------------------------------------
/docs/assets/fonts/roboto/Roboto-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/fonts/roboto/Roboto-Thin.woff2
--------------------------------------------------------------------------------
/docs/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/assets/images/logo.png
--------------------------------------------------------------------------------
/docs/assets/js/jquery.inview.min.js:
--------------------------------------------------------------------------------
1 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){function i(){var b,c,d={height:f.innerHeight,width:f.innerWidth};return d.height||(b=e.compatMode,(b||!a.support.boxModel)&&(c="CSS1Compat"===b?g:e.body,d={height:c.clientHeight,width:c.clientWidth})),d}function j(){return{top:f.pageYOffset||g.scrollTop||e.body.scrollTop,left:f.pageXOffset||g.scrollLeft||e.body.scrollLeft}}function k(){if(b.length){var e=0,f=a.map(b,function(a){var b=a.data.selector,c=a.$element;return b?c.find(b):c});for(c=c||i(),d=d||j();ed.top&&l.topd.left&&l.left **Note sur la tarification de l'API 'Google Cloud Text-to-Speech'**
5 | > L'utilisation est gratuite jusqu'à un certain quota d'utilisation qui est largement suffisant pour une utilisation domotique d'un particulier.
6 | > - Voix standards (hors WaveNet): Gratuit de 0 à 4 millions de caractères par mois (puis 4 USD/1 million de caractères supplémentaires)
7 | > - Voix WaveNet: gratuit de 0 à 1 million de caractères par mois (puis 16 USD/1 million de caractères supplémentaires)
8 |
9 | #### Créez un projet sur la console Google
10 |
11 | – Rendez vous à cette adresse https://cloud.google.com/console
12 | – Cliquez sur *Nouveau Projet* pour créer un nouveau projet.
13 |
14 | Donner un nom au projet puis cliquer 'Créer'
15 | 
16 |
17 | #### Activation de "Cloud Text-to-Speech API"
18 |
19 | – Rendez vous dans onglet > APIs et services > Bibliothèque
20 | – Dans Browse API taper "Speech" et identifier puis selectionner le service appelé *Cloud Text-to-Speech API*
21 | 
22 | – Une fois sur la page de l'API, cliquer sur 'Activer'
23 |
24 |
25 | #### Création d'une clef "Cloud Text-to-Speech API"
26 |
27 | - Aller dans le menu *'API et services' > Identifiants* du projet nouvellement crée
28 | 
29 |
30 | - Selectionner le Clé API dans le type d'identifiant à créer
31 | 
32 |
33 | - Récupérer la clé API via copier/coller
34 | 
35 |
--------------------------------------------------------------------------------
/docs/images/chromecast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/chromecast.png
--------------------------------------------------------------------------------
/docs/images/commands.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/commands.png
--------------------------------------------------------------------------------
/docs/images/commands_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/commands_list.png
--------------------------------------------------------------------------------
/docs/images/configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/configuration.png
--------------------------------------------------------------------------------
/docs/images/configuration_css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/configuration_css.png
--------------------------------------------------------------------------------
/docs/images/configuration_plugin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/configuration_plugin.png
--------------------------------------------------------------------------------
/docs/images/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/dashboard.png
--------------------------------------------------------------------------------
/docs/images/dashboard2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/dashboard2.png
--------------------------------------------------------------------------------
/docs/images/display1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/display1.png
--------------------------------------------------------------------------------
/docs/images/gcloudtts/gctts_api1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/gcloudtts/gctts_api1.png
--------------------------------------------------------------------------------
/docs/images/gcloudtts/gctts_gcp1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/gcloudtts/gctts_gcp1.png
--------------------------------------------------------------------------------
/docs/images/gcloudtts/gctts_key1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/gcloudtts/gctts_key1.png
--------------------------------------------------------------------------------
/docs/images/gcloudtts/gctts_key2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/gcloudtts/gctts_key2.png
--------------------------------------------------------------------------------
/docs/images/gcloudtts/gctts_key3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/gcloudtts/gctts_key3.png
--------------------------------------------------------------------------------
/docs/images/googlecast_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/googlecast_logo.png
--------------------------------------------------------------------------------
/docs/images/logoplugin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/logoplugin.png
--------------------------------------------------------------------------------
/docs/images/scenario.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/scenario.png
--------------------------------------------------------------------------------
/docs/images/summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/summary.png
--------------------------------------------------------------------------------
/docs/images/tv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/tv.png
--------------------------------------------------------------------------------
/docs/images/widget_speak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/docs/images/widget_speak.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/plugin_info/.htaccess:
--------------------------------------------------------------------------------
1 | Order allow,deny
2 |
3 | allow from all
4 |
5 | Deny from all
6 |
--------------------------------------------------------------------------------
/plugin_info/googlecast_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/plugin_info/googlecast_icon.png
--------------------------------------------------------------------------------
/plugin_info/info.json:
--------------------------------------------------------------------------------
1 | {
2 | "id" : "googlecast",
3 | "name" : "Google Cast",
4 | "description" : "Plugin de gestion des équipements compatibles Google Cast",
5 | "licence" : "AGPL",
6 | "author" : "guirem",
7 | "hasOwnDeamon" : true,
8 | "hasDependency" : true,
9 | "maxDependancyInstallTime" : 20,
10 | "version" : "2.20",
11 | "require" : "3.0",
12 | "category" : "multimedia",
13 | "changelog" : "https://github.com/guirem/plugin-googlecast/blob/develop/docs/fr_FR/changelog.md",
14 | "documentation" : "https://github.com/guirem/plugin-googlecast/blob/develop/docs/fr_FR/index.md",
15 | "forum_link" : "https://community.jeedom.com/t/plugin-tiers-sujet-principal-google-cast/12632",
16 | "languages" : "french, english, spanish",
17 | "compatibility" : [
18 | "miniplus",
19 | "smart",
20 | "rpi",
21 | "docker",
22 | "diy",
23 | "mobileapp",
24 | "v4"
25 | ],
26 | "whiteListFolders": [
27 | "/data/media",
28 | "/data/cache"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/resources/.htaccess:
--------------------------------------------------------------------------------
1 | Order allow,deny
2 | Deny from all
3 |
--------------------------------------------------------------------------------
/resources/gcloudtts/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from .gcloudtts import gcloudTTS, gcloudTTSError
3 |
4 | __all__ = ['gcloudTTS', 'gcloudTTSError']
5 |
--------------------------------------------------------------------------------
/resources/globals.py:
--------------------------------------------------------------------------------
1 | # This file is part of Jeedom.
2 | #
3 | # Jeedom is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # Jeedom is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with Jeedom. If not, see .
15 | #
16 |
17 | import time
18 | import os
19 | import os.path
20 |
21 | JEEDOM_COM = ''
22 | JEEDOM_WEB = ''
23 |
24 | IS_SHUTTINGDOWN = False
25 |
26 | KNOWN_DEVICES = {}
27 | NOWPLAYING_DEVICES = {}
28 | GCAST_DEVICES = {}
29 |
30 | NOWPLAYING_TIMEOUT = 60*6 # 6 minutes
31 | NOWPLAYING_FREQUENCY = 15 # 15 seconds
32 | NOWPLAYING_FREQUENCY_MAX = 120 # 120 seconds
33 | NOWPLAYING_LAST = 0
34 |
35 | LEARN_BEGIN = int(time.time())
36 | LEARN_MODE = False # is learn mode ?
37 | LEARN_TIMEOUT = 90
38 |
39 | ZEROCONF_RESTART = False
40 |
41 | HEARTBEAT_FREQUENCY = 900 # 15 minutes
42 | LAST_BEAT = int(time.time())
43 |
44 | SCAN_FREQUENCY = 60 # in seconds
45 | SCAN_PENDING = False # is scanner running?
46 | SCAN_LAST = 0 # when last started
47 | SCAN_TIMEOUT = 8 # timout of gcast scan
48 |
49 | NETDISCOVERY_CHROMECASTMANAGER = None
50 | NETDISCOVERY_DEVICES = {}
51 |
52 | DISCOVERY_FREQUENCY = 7200 # every 2 hours
53 | DISCOVERY_LAST = int(time.time()) # when last started
54 |
55 | # Resent offline msg after 15 minutes
56 | LOSTDEVICE_RESENDNOTIFDELAY = 60*15
57 |
58 | DEFAULT_NOSTATUS = ""
59 | DEFAULT_NODISPLAY = ""
60 |
61 | cycle_factor = 2
62 | cycle_event = 0.5
63 | cycle_main = 2
64 |
65 | disable_mediastatus = False
66 |
67 | tts_language = 'fr-FR'
68 | tts_engine = 'picotts'
69 | tts_cacheenabled = True
70 | tts_speed = 1.2
71 | tts_cachefolderweb = os.path.abspath(os.path.join(
72 | os.path.dirname(os.path.dirname(__file__)), 'data/cache'))
73 | tts_cachefoldertmp = os.path.join('/tmp/jeedom/', 'googlecast_tts')
74 | tts_gapi_url = 'https://www.google.com/speech-api/'
75 | tts_gapi_key = 'none'
76 | tts_gapi_voice = 'fr-FR-Standard-A'
77 | tts_gapi_haskey = False
78 |
79 | tts_default_restoredelay = 1300 # additionnal time in ms to add after tts (before vol up command)
80 | tts_default_silenceduration = 300 # default silence duration added at tts start
81 |
82 | localmedia_folder = 'data/media'
83 | localmedia_fullpath = os.path.abspath(os.path.join(
84 | os.path.dirname(os.path.dirname(__file__)), localmedia_folder))
85 |
86 | log_level = "info"
87 | pidfile = '/tmp/googlecast.pid'
88 | apikey = ''
89 | callback = ''
90 | daemonname = ''
91 | socketport = 55012
92 | sockethost = '127.0.0.1'
93 |
--------------------------------------------------------------------------------
/resources/gtts/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2014-2021 Pierre Nicolas Durette
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/resources/gtts/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from .version import __version__ # noqa: F401
3 | from .tts import gTTS, gTTSError
4 |
5 | __all__ = ['gTTS', 'gTTSError']
6 |
--------------------------------------------------------------------------------
/resources/gtts/lang.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from gtts.langs import _main_langs
3 | from warnings import warn
4 | import logging
5 |
6 | __all__ = ['tts_langs']
7 |
8 | # Logger
9 | log = logging.getLogger(__name__)
10 | log.addHandler(logging.NullHandler())
11 |
12 |
13 | def tts_langs():
14 | """Languages Google Text-to-Speech supports.
15 |
16 | Returns:
17 | dict: A dictionary of the type `{ '': ''}`
18 |
19 | Where `` is an IETF language tag such as `en` or `zh-TW`,
20 | and `` is the full English name of the language, such as
21 | `English` or `Chinese (Mandarin/Taiwan)`.
22 |
23 | The dictionary returned combines languages from two origins:
24 |
25 | - Languages fetched from Google Translate (pre-generated in :mod:`gtts.langs`)
26 | - Languages that are undocumented variations that were observed to work and
27 | present different dialects or accents.
28 |
29 | """
30 | langs = dict()
31 | langs.update(_main_langs())
32 | langs.update(_extra_langs())
33 | log.debug("langs: {}".format(langs))
34 | return langs
35 |
36 |
37 | def _extra_langs():
38 | """Define extra languages.
39 |
40 | Returns:
41 | dict: A dictionnary of extra languages manually defined.
42 |
43 | Variations of the ones generated in `_main_langs`,
44 | observed to provide different dialects or accents or
45 | just simply accepted by the Google Translate Text-to-Speech API.
46 |
47 | """
48 | return {
49 | # Chinese
50 | 'zh-TW': 'Chinese (Mandarin/Taiwan)',
51 | 'zh': 'Chinese (Mandarin)'
52 | }
53 |
54 |
55 | def _fallback_deprecated_lang(lang):
56 | """Languages Google Text-to-Speech used to support.
57 |
58 | Language tags that don't work anymore, but that can
59 | fallback to a more general language code to maintain
60 | compatibility.
61 |
62 | Args:
63 | lang (string): The language tag.
64 |
65 | Returns:
66 | string: The language tag, as-is if not deprecated,
67 | or a fallack if it exits.
68 |
69 | Example:
70 | ``en-GB`` returns ``en``.
71 | ``en-gb`` returns ``en``.
72 |
73 | """
74 |
75 | deprecated = {
76 | # '': []
77 | 'en': ['en-us', 'en-ca', 'en-uk', 'en-gb', 'en-au', 'en-gh', 'en-in',
78 | 'en-ie', 'en-nz', 'en-ng', 'en-ph', 'en-za', 'en-tz'],
79 | 'fr': ['fr-ca', 'fr-fr'],
80 | 'pt': ['pt-br', 'pt-pt'],
81 | 'es': ['es-es', 'es-us'],
82 | 'zh-CN': ['zh-cn'],
83 | 'zh-TW': ['zh-tw'],
84 | }
85 |
86 | for fallback_lang, deprecated_langs in deprecated.items():
87 | if lang.lower() in deprecated_langs:
88 | msg = (
89 | "'{}' has been deprecated, falling back to '{}'. "
90 | "This fallback will be removed in a future version."
91 | ).format(lang, fallback_lang)
92 |
93 | warn(msg, DeprecationWarning)
94 | log.warning(msg)
95 |
96 | return fallback_lang
97 |
98 | return lang
--------------------------------------------------------------------------------
/resources/gtts/langs.py:
--------------------------------------------------------------------------------
1 | # Note: this file is generated
2 | _langs = {
3 | "af": "Afrikaans",
4 | "ar": "Arabic",
5 | "bg": "Bulgarian",
6 | "bn": "Bengali",
7 | "bs": "Bosnian",
8 | "ca": "Catalan",
9 | "cs": "Czech",
10 | "cy": "Welsh",
11 | "da": "Danish",
12 | "de": "German",
13 | "el": "Greek",
14 | "en": "English",
15 | "eo": "Esperanto",
16 | "es": "Spanish",
17 | "et": "Estonian",
18 | "fi": "Finnish",
19 | "fr": "French",
20 | "gu": "Gujarati",
21 | "hi": "Hindi",
22 | "hr": "Croatian",
23 | "hu": "Hungarian",
24 | "hy": "Armenian",
25 | "id": "Indonesian",
26 | "is": "Icelandic",
27 | "it": "Italian",
28 | "ja": "Japanese",
29 | "jw": "Javanese",
30 | "km": "Khmer",
31 | "kn": "Kannada",
32 | "ko": "Korean",
33 | "la": "Latin",
34 | "lv": "Latvian",
35 | "mk": "Macedonian",
36 | "ml": "Malayalam",
37 | "mr": "Marathi",
38 | "my": "Myanmar (Burmese)",
39 | "ne": "Nepali",
40 | "nl": "Dutch",
41 | "no": "Norwegian",
42 | "pl": "Polish",
43 | "pt": "Portuguese",
44 | "ro": "Romanian",
45 | "ru": "Russian",
46 | "si": "Sinhala",
47 | "sk": "Slovak",
48 | "sq": "Albanian",
49 | "sr": "Serbian",
50 | "su": "Sundanese",
51 | "sv": "Swedish",
52 | "sw": "Swahili",
53 | "ta": "Tamil",
54 | "te": "Telugu",
55 | "th": "Thai",
56 | "tl": "Filipino",
57 | "tr": "Turkish",
58 | "uk": "Ukrainian",
59 | "ur": "Urdu",
60 | "vi": "Vietnamese",
61 | "zh-CN": "Chinese"
62 | }
63 |
64 | def _main_langs():
65 | return _langs
66 |
--------------------------------------------------------------------------------
/resources/gtts/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/resources/gtts/tests/__init__.py
--------------------------------------------------------------------------------
/resources/gtts/tests/input_files/test_cli_test_ascii.txt:
--------------------------------------------------------------------------------
1 | Can you make pink a little more pinkish can you make pink a little more pinkish, nor can you make the font bigger?
2 | How much will it cost the website doesn't have the theme i was going for.
--------------------------------------------------------------------------------
/resources/gtts/tests/input_files/test_cli_test_utf8.txt:
--------------------------------------------------------------------------------
1 | 这是一个三岁的小孩
2 | 在讲述她从一系列照片里看到的东西。
3 | 对这个世界, 她也许还有很多要学的东西,
4 | 但在一个重要的任务上, 她已经是专家了:
5 | 去理解她所看到的东西。
6 |
--------------------------------------------------------------------------------
/resources/gtts/tests/test_lang.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import pytest
3 | from gtts.lang import tts_langs, _extra_langs, _fallback_deprecated_lang
4 | from gtts.langs import _main_langs
5 |
6 | """Test language list"""
7 |
8 |
9 | def test_main_langs():
10 | """Fetch languages successfully"""
11 | # Safe to assume 'en' (English) will always be there
12 | scraped_langs = _main_langs()
13 | assert 'en' in scraped_langs
14 |
15 |
16 | def test_deprecated_lang():
17 | """Test language deprecation fallback"""
18 | with pytest.deprecated_call():
19 | assert _fallback_deprecated_lang('en-gb') == 'en'
20 |
21 |
22 | if __name__ == '__main__':
23 | pytest.main(['-x', __file__])
24 |
--------------------------------------------------------------------------------
/resources/gtts/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import pytest
3 | from gtts.utils import _minimize, _len, _clean_tokens, _translate_url
4 |
5 | delim = ' '
6 | Lmax = 10
7 |
8 |
9 | def test_ascii():
10 | _in = "Bacon ipsum dolor sit amet"
11 | _out = ["Bacon", "ipsum", "dolor sit", "amet"]
12 | assert _minimize(_in, delim, Lmax) == _out
13 |
14 |
15 | def test_ascii_no_delim():
16 | _in = "Baconipsumdolorsitametflankcornedbee"
17 | _out = ["Baconipsum", "dolorsitam", "etflankcor", "nedbee"]
18 | assert _minimize(_in, delim, Lmax) == _out
19 |
20 |
21 | def test_unicode():
22 | _in = u"这是一个三岁的小孩在讲述他从一系列照片里看到的东西。"
23 | _out = [u"这是一个三岁的小孩在", u"讲述他从一系列照片里", u"看到的东西。"]
24 | assert _minimize(_in, delim, Lmax) == _out
25 |
26 |
27 | def test_startwith_delim():
28 | _in = delim + "test"
29 | _out = ["test"]
30 | assert _minimize(_in, delim, Lmax) == _out
31 |
32 |
33 | def test_len_ascii():
34 | text = "Bacon ipsum dolor sit amet flank corned beef."
35 | assert _len(text) == 45
36 |
37 |
38 | def test_len_unicode():
39 | text = u"但在一个重要的任务上"
40 | assert _len(text) == 10
41 |
42 |
43 | def test_only_space_and_punc():
44 | _in = [",(:)?", "\t ", "\n"]
45 | _out = []
46 | assert _clean_tokens(_in) == _out
47 |
48 |
49 | def test_strip():
50 | _in = [" Bacon ", "& ", "ipsum\r", "."]
51 | _out = ["Bacon", "&", "ipsum"]
52 | assert _clean_tokens(_in) == _out
53 |
54 |
55 | def test_translate_url():
56 | _in = {"tld": "qwerty", "path": "asdf"}
57 | _out = "https://translate.google.qwerty/asdf"
58 | assert _translate_url(**_in) == _out
59 |
60 |
61 | if __name__ == '__main__':
62 | pytest.main(['-x', __file__])
63 |
--------------------------------------------------------------------------------
/resources/gtts/tokenizer/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*
2 | from .core import RegexBuilder, PreProcessorRegex, PreProcessorSub, Tokenizer # noqa: F401
3 |
--------------------------------------------------------------------------------
/resources/gtts/tokenizer/pre_processors.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from gtts.tokenizer import PreProcessorRegex, PreProcessorSub, symbols
3 | import re
4 |
5 |
6 | def tone_marks(text):
7 | """Add a space after tone-modifying punctuation.
8 |
9 | Because the `tone_marks` tokenizer case will split after a tone-modidfying
10 | punctuation mark, make sure there's whitespace after.
11 |
12 | """
13 | return PreProcessorRegex(
14 | search_args=symbols.TONE_MARKS,
15 | search_func=lambda x: u"(?<={})".format(x),
16 | repl=' ').run(text)
17 |
18 |
19 | def end_of_line(text):
20 | """Re-form words cut by end-of-line hyphens.
21 |
22 | Remove "".
23 |
24 | """
25 | return PreProcessorRegex(
26 | search_args=u'-',
27 | search_func=lambda x: u"{}\n".format(x),
28 | repl='').run(text)
29 |
30 |
31 | def abbreviations(text):
32 | """Remove periods after an abbreviation from a list of known
33 | abbrevations that can be spoken the same without that period. This
34 | prevents having to handle tokenization of that period.
35 |
36 | Note:
37 | Could potentially remove the ending period of a sentence.
38 |
39 | Note:
40 | Abbreviations that Google Translate can't pronounce without
41 | (or even with) a period should be added as a word substitution with a
42 | :class:`PreProcessorSub` pre-processor. Ex.: 'Esq.', 'Esquire'.
43 |
44 | """
45 | return PreProcessorRegex(
46 | search_args=symbols.ABBREVIATIONS,
47 | search_func=lambda x: r"(?<={})(?=\.).".format(x),
48 | repl='', flags=re.IGNORECASE).run(text)
49 |
50 |
51 | def word_sub(text):
52 | """Word-for-word substitutions."""
53 | return PreProcessorSub(
54 | sub_pairs=symbols.SUB_PAIRS).run(text)
55 |
--------------------------------------------------------------------------------
/resources/gtts/tokenizer/symbols.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ABBREVIATIONS = [
4 | 'dr', 'jr', 'mr',
5 | 'mrs', 'ms', 'msgr',
6 | 'prof', 'sr', 'st']
7 |
8 | SUB_PAIRS = [
9 | ('Esq.', 'Esquire')
10 | ]
11 |
12 | ALL_PUNC = u"?!?!.,¡()[]¿…‥،;:—。,、:\n"
13 |
14 | TONE_MARKS = u"?!?!"
15 |
16 | PERIOD_COMMA = u".,"
17 |
18 | COLON = u":"
19 |
--------------------------------------------------------------------------------
/resources/gtts/tokenizer/tests/test_core.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import unittest
3 | import re
4 | from gtts.tokenizer.core import RegexBuilder, PreProcessorRegex, PreProcessorSub, Tokenizer
5 |
6 | # Tests based on classes usage examples
7 | # See class documentation for details
8 |
9 |
10 | class TestRegexBuilder(unittest.TestCase):
11 | def test_regexbuilder(self):
12 | rb = RegexBuilder('abc', lambda x: "{}".format(x))
13 | self.assertEqual(rb.regex, re.compile('a|b|c'))
14 |
15 |
16 | class TestPreProcessorRegex(unittest.TestCase):
17 | def test_preprocessorregex(self):
18 | pp = PreProcessorRegex('ab', lambda x: "{}".format(x), 'c')
19 | self.assertEqual(len(pp.regexes), 2)
20 | self.assertEqual(pp.regexes[0].pattern, 'a')
21 | self.assertEqual(pp.regexes[1].pattern, 'b')
22 |
23 |
24 | class TestPreProcessorSub(unittest.TestCase):
25 | def test_proprocessorsub(self):
26 | sub_pairs = [('Mac', 'PC'), ('Firefox', 'Chrome')]
27 | pp = PreProcessorSub(sub_pairs)
28 | _in = "I use firefox on my mac"
29 | _out = "I use Chrome on my PC"
30 | self.assertEqual(pp.run(_in), _out)
31 |
32 |
33 | class TestTokenizer(unittest.TestCase):
34 | # tokenizer case 1
35 | def case1(self):
36 | return re.compile(r"\,")
37 |
38 | # tokenizer case 2
39 | def case2(self):
40 | return RegexBuilder('abc', lambda x: r"{}\.".format(x)).regex
41 |
42 | def test_tokenizer(self):
43 | t = Tokenizer([self.case1, self.case2])
44 | _in = "Hello, my name is Linda a. Call me Lin, b. I'm your friend"
45 | _out = [
46 | 'Hello',
47 | ' my name is Linda ',
48 | ' Call me Lin',
49 | ' ',
50 | " I'm your friend"]
51 | self.assertEqual(t.run(_in), _out)
52 |
53 | def test_bad_params_not_list(self):
54 | # original exception: TypeError
55 | with self.assertRaises(TypeError):
56 | Tokenizer(self.case1)
57 |
58 | def test_bad_params_not_callable(self):
59 | # original exception: TypeError
60 | with self.assertRaises(TypeError):
61 | Tokenizer([100])
62 |
63 | def test_bad_params_not_callable_returning_regex(self):
64 | # original exception: AttributeError
65 | def not_regex():
66 | return 1
67 |
68 | with self.assertRaises(TypeError):
69 | Tokenizer([not_regex])
70 |
71 |
72 | if __name__ == '__main__':
73 | unittest.main()
74 |
--------------------------------------------------------------------------------
/resources/gtts/tokenizer/tests/test_pre_processors.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import unittest
3 | from gtts.tokenizer.pre_processors import tone_marks, end_of_line, abbreviations, word_sub
4 |
5 |
6 | class TestPreProcessors(unittest.TestCase):
7 | def test_tone_marks(self):
8 | _in = "lorem!ipsum?"
9 | _out = "lorem! ipsum? "
10 | self.assertEqual(tone_marks(_in), _out)
11 |
12 | def test_end_of_line(self):
13 | _in = """test-
14 | ing"""
15 | _out = "testing"
16 | self.assertEqual(end_of_line(_in), _out)
17 |
18 | def test_abbreviations(self):
19 | _in = "jr. sr. dr."
20 | _out = "jr sr dr"
21 | self.assertEqual(abbreviations(_in), _out)
22 |
23 | def test_word_sub(self):
24 | _in = "Esq. Bacon"
25 | _out = "Esquire Bacon"
26 | self.assertEqual(word_sub(_in), _out)
27 |
28 |
29 | if __name__ == '__main__':
30 | unittest.main()
31 |
--------------------------------------------------------------------------------
/resources/gtts/tokenizer/tests/test_tokenizer_cases.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import unittest
3 | from gtts.tokenizer.tokenizer_cases import tone_marks, period_comma, colon, other_punctuation, legacy_all_punctuation
4 | from gtts.tokenizer import Tokenizer, symbols
5 |
6 |
7 | class TestPreTokenizerCases(unittest.TestCase):
8 | def test_tone_marks(self):
9 | t = Tokenizer([tone_marks])
10 | _in = "Lorem? Ipsum!"
11 | _out = ['Lorem?', 'Ipsum!']
12 | self.assertEqual(t.run(_in), _out)
13 |
14 | def test_period_comma(self):
15 | t = Tokenizer([period_comma])
16 | _in = "Hello, it's 24.5 degrees in the U.K. today. $20,000,000."
17 | _out = ['Hello', "it's 24.5 degrees in the U.K. today", '$20,000,000.']
18 | self.assertEqual(t.run(_in), _out)
19 |
20 | def test_colon(self):
21 | t = Tokenizer([colon])
22 | _in = "It's now 6:30 which means: morning missing:space"
23 | _out = ["It's now 6:30 which means", ' morning missing', 'space']
24 | self.assertEqual(t.run(_in), _out)
25 |
26 | def test_other_punctuation(self):
27 | # String of the unique 'other punctuations'
28 | other_punc_str = ''.join(
29 | set(symbols.ALL_PUNC) -
30 | set(symbols.TONE_MARKS) -
31 | set(symbols.PERIOD_COMMA) -
32 | set(symbols.COLON))
33 |
34 | t = Tokenizer([other_punctuation])
35 | self.assertEqual(len(t.run(other_punc_str)) - 1, len(other_punc_str))
36 |
37 | def test_legacy_all_punctuation(self):
38 | t = Tokenizer([legacy_all_punctuation])
39 | self.assertEqual(len(t.run(symbols.ALL_PUNC)) -
40 | 1, len(symbols.ALL_PUNC))
41 |
42 |
43 | if __name__ == '__main__':
44 | unittest.main()
45 |
--------------------------------------------------------------------------------
/resources/gtts/tokenizer/tokenizer_cases.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from gtts.tokenizer import RegexBuilder, symbols
3 |
4 |
5 | def tone_marks():
6 | """Keep tone-modifying punctuation by matching following character.
7 |
8 | Assumes the `tone_marks` pre-processor was run for cases where there might
9 | not be any space after a tone-modifying punctuation mark.
10 | """
11 | return RegexBuilder(
12 | pattern_args=symbols.TONE_MARKS,
13 | pattern_func=lambda x: u"(?<={}).".format(x)).regex
14 |
15 |
16 | def period_comma():
17 | """Period and comma case.
18 |
19 | Match if not preceded by "." and only if followed by space.
20 | Won't cut in the middle/after dotted abbreviations; won't cut numbers.
21 |
22 | Note:
23 | Won't match if a dotted abbreviation ends a sentence.
24 |
25 | Note:
26 | Won't match the end of a sentence if not followed by a space.
27 |
28 | """
29 | return RegexBuilder(
30 | pattern_args=symbols.PERIOD_COMMA,
31 | pattern_func=lambda x: r"(?&1 | grep -q "Python 3"; then
3 | echo "nok"
4 | exit 0
5 | fi
6 |
7 | pip3cmd=$(compgen -ac | grep -E '^pip-?3' | sort -r | head -1)
8 | if [[ -z $pip3cmd ]]; then # pip3 not found
9 | if python3 -m pip -V 2>&1 | grep -q -i "^pip " ; then # but try other way
10 | pip3cmd="python3 -m pip"
11 | else
12 | echo "nok"
13 | exit 0
14 | fi
15 | fi
16 | if [[ ! -z $pip3cmd ]]; then # pip3 found
17 | $(sudo $pip3cmd list 2>/dev/null | grep -E "zeroconf|requests|protobuf|bs4|websocket-client|tqdm" | wc -l > /tmp/dependancycheck_googlecast)
18 | content=$(cat /tmp/dependancycheck_googlecast 2>/dev/null)
19 | rm -f /tmp/dependancycheck_googlecast
20 | if [[ -z $content ]]; then
21 | content=0
22 | fi
23 | if [ "$content" -lt 6 ];then
24 | echo "nok"
25 | exit 0
26 | fi
27 | else
28 | echo "nok"
29 | exit 0
30 | fi
31 | echo "ok"
32 | exit 0
33 |
--------------------------------------------------------------------------------
/resources/jeedom/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guirem/plugin-googlecast/6621bff247470e959250a0a21c17f07bdd0d6f4c/resources/jeedom/__init__.py
--------------------------------------------------------------------------------
/resources/plexapi/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import logging
3 | import os
4 | from logging.handlers import RotatingFileHandler
5 | from platform import uname
6 | from plexapi.config import PlexConfig, reset_base_headers
7 | from plexapi.utils import SecretsFilter
8 | from uuid import getnode
9 |
10 | # Load User Defined Config
11 | DEFAULT_CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini')
12 | CONFIG_PATH = os.environ.get('PLEXAPI_CONFIG_PATH', DEFAULT_CONFIG_PATH)
13 | CONFIG = PlexConfig(CONFIG_PATH)
14 |
15 | # PlexAPI Settings
16 | PROJECT = 'PlexAPI'
17 | VERSION = '3.1.0'
18 | TIMEOUT = CONFIG.get('plexapi.timeout', 30, int)
19 | X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int)
20 | X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool)
21 |
22 | # Plex Header Configuation
23 | X_PLEX_PROVIDES = CONFIG.get('header.provides', 'controller')
24 | X_PLEX_PLATFORM = CONFIG.get('header.platform', CONFIG.get('header.platorm', uname()[0]))
25 | X_PLEX_PLATFORM_VERSION = CONFIG.get('header.platform_version', uname()[2])
26 | X_PLEX_PRODUCT = CONFIG.get('header.product', PROJECT)
27 | X_PLEX_VERSION = CONFIG.get('header.version', VERSION)
28 | X_PLEX_DEVICE = CONFIG.get('header.device', X_PLEX_PLATFORM)
29 | X_PLEX_DEVICE_NAME = CONFIG.get('header.device_name', uname()[1])
30 | X_PLEX_IDENTIFIER = CONFIG.get('header.identifier', str(hex(getnode())))
31 | BASE_HEADERS = reset_base_headers()
32 |
33 | # Logging Configuration
34 | log = logging.getLogger('plexapi')
35 | logfile = CONFIG.get('log.path')
36 | logformat = CONFIG.get('log.format', '%(asctime)s %(module)12s:%(lineno)-4s %(levelname)-9s %(message)s')
37 | loglevel = CONFIG.get('log.level', 'INFO').upper()
38 | loghandler = logging.NullHandler()
39 |
40 | if logfile: # pragma: no cover
41 | logbackups = CONFIG.get('log.backup_count', 3, int)
42 | logbytes = CONFIG.get('log.rotate_bytes', 512000, int)
43 | loghandler = RotatingFileHandler(os.path.expanduser(logfile), 'a', logbytes, logbackups)
44 |
45 | loghandler.setFormatter(logging.Formatter(logformat))
46 | log.addHandler(loghandler)
47 | log.setLevel(loglevel)
48 | logfilter = SecretsFilter()
49 | if CONFIG.get('log.show_secrets', '').lower() != 'true':
50 | log.addFilter(logfilter)
51 |
--------------------------------------------------------------------------------
/resources/plexapi/alert.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import json
3 | import threading
4 | import websocket
5 | from plexapi import log
6 |
7 |
8 | class AlertListener(threading.Thread):
9 | """ Creates a websocket connection to the PlexServer to optionally recieve alert notifications.
10 | These often include messages from Plex about media scans as well as updates to currently running
11 | Transcode Sessions. This class implements threading.Thread, therfore to start monitoring
12 | alerts you must call .start() on the object once it's created. When calling
13 | `PlexServer.startAlertListener()`, the thread will be started for you.
14 |
15 | Known `state`-values for timeline entries, with identifier=`com.plexapp.plugins.library`:
16 |
17 | :0: The item was created
18 | :1: Reporting progress on item processing
19 | :2: Matching the item
20 | :3: Downloading the metadata
21 | :4: Processing downloaded metadata
22 | :5: The item processed
23 | :9: The item deleted
24 |
25 | When metadata agent is not set for the library processing ends with state=1.
26 |
27 | Parameters:
28 | server (:class:`~plexapi.server.PlexServer`): PlexServer this listener is connected to.
29 | callback (func): Callback function to call on recieved messages. The callback function
30 | will be sent a single argument 'data' which will contain a dictionary of data
31 | recieved from the server. :samp:`def my_callback(data): ...`
32 | """
33 | key = '/:/websockets/notifications'
34 |
35 | def __init__(self, server, callback=None):
36 | super(AlertListener, self).__init__()
37 | self.daemon = True
38 | self._server = server
39 | self._callback = callback
40 | self._ws = None
41 |
42 | def run(self):
43 | # create the websocket connection
44 | url = self._server.url(self.key, includeToken=True).replace('http', 'ws')
45 | log.info('Starting AlertListener: %s', url)
46 | self._ws = websocket.WebSocketApp(url, on_message=self._onMessage,
47 | on_error=self._onError)
48 | self._ws.run_forever()
49 |
50 | def stop(self):
51 | """ Stop the AlertListener thread. Once the notifier is stopped, it cannot be diractly
52 | started again. You must call :func:`plexapi.server.PlexServer.startAlertListener()`
53 | from a PlexServer instance.
54 | """
55 | log.info('Stopping AlertListener.')
56 | self._ws.close()
57 |
58 | def _onMessage(self, ws, message):
59 | """ Called when websocket message is recieved. """
60 | try:
61 | data = json.loads(message)['NotificationContainer']
62 | log.debug('Alert: %s %s %s', *data)
63 | if self._callback:
64 | self._callback(data)
65 | except Exception as err: # pragma: no cover
66 | log.error('AlertListener Msg Error: %s', err)
67 |
68 | def _onError(self, ws, err): # pragma: no cover
69 | """ Called when websocket error is recieved. """
70 | log.error('AlertListener Error: %s' % err)
71 |
--------------------------------------------------------------------------------
/resources/plexapi/config.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | from collections import defaultdict
4 | from plexapi.compat import ConfigParser
5 |
6 |
7 | class PlexConfig(ConfigParser):
8 | """ PlexAPI configuration object. Settings are stored in an INI file within the
9 | user's home directory and can be overridden after importing plexapi by simply
10 | setting the value. See the documentation section 'Configuration' for more
11 | details on available options.
12 |
13 | Parameters:
14 | path (str): Path of the configuration file to load.
15 | """
16 | def __init__(self, path):
17 | ConfigParser.__init__(self)
18 | self.read(path)
19 | self.data = self._asDict()
20 |
21 | def get(self, key, default=None, cast=None):
22 | """ Returns the specified configuration value or if not found.
23 |
24 | Parameters:
25 | key (str): Configuration variable to load in the format '.'.
26 | default: Default value to use if key not found.
27 | cast (func): Cast the value to the specified type before returning.
28 | """
29 | try:
30 | # First: check environment variable is set
31 | envkey = 'PLEXAPI_%s' % key.upper().replace('.', '_')
32 | value = os.environ.get(envkey)
33 | if value is None:
34 | # Second: check the config file has attr
35 | section, name = key.lower().split('.')
36 | value = self.data.get(section, {}).get(name, default)
37 | return cast(value) if cast else value
38 | except: # noqa: E722
39 | return default
40 |
41 | def _asDict(self):
42 | """ Returns all configuration values as a dictionary. """
43 | config = defaultdict(dict)
44 | for section in self._sections:
45 | for name, value in self._sections[section].items():
46 | if name != '__name__':
47 | config[section.lower()][name.lower()] = value
48 | return dict(config)
49 |
50 |
51 | def reset_base_headers():
52 | """ Convenience function returns a dict of all base X-Plex-* headers for session requests. """
53 | import plexapi
54 | return {
55 | 'X-Plex-Platform': plexapi.X_PLEX_PLATFORM,
56 | 'X-Plex-Platform-Version': plexapi.X_PLEX_PLATFORM_VERSION,
57 | 'X-Plex-Provides': plexapi.X_PLEX_PROVIDES,
58 | 'X-Plex-Product': plexapi.X_PLEX_PRODUCT,
59 | 'X-Plex-Version': plexapi.X_PLEX_VERSION,
60 | 'X-Plex-Device': plexapi.X_PLEX_DEVICE,
61 | 'X-Plex-Device-Name': plexapi.X_PLEX_DEVICE_NAME,
62 | 'X-Plex-Client-Identifier': plexapi.X_PLEX_IDENTIFIER,
63 | 'X-Plex-Sync-Version': '2',
64 | }
65 |
--------------------------------------------------------------------------------
/resources/plexapi/exceptions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | class PlexApiException(Exception):
5 | """ Base class for all PlexAPI exceptions. """
6 | pass
7 |
8 |
9 | class BadRequest(PlexApiException):
10 | """ An invalid request, generally a user error. """
11 | pass
12 |
13 |
14 | class NotFound(PlexApiException):
15 | """ Request media item or device is not found. """
16 | pass
17 |
18 |
19 | class UnknownType(PlexApiException):
20 | """ Unknown library type. """
21 | pass
22 |
23 |
24 | class Unsupported(PlexApiException):
25 | """ Unsupported client request. """
26 | pass
27 |
28 |
29 | class Unauthorized(PlexApiException):
30 | """ Invalid username or password. """
31 | pass
32 |
--------------------------------------------------------------------------------
/resources/pychromecast/.gitignore:
--------------------------------------------------------------------------------
1 | # Hide sublime text stuff
2 | *.sublime-project
3 | *.sublime-workspace
4 |
5 | # Hide some OS X stuff
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 | Icon
10 |
11 | # Thumbnails
12 | ._*
13 |
14 |
15 | # GITHUB Proposed Python stuff:
16 | *.py[cod]
17 |
18 | # C extensions
19 | *.so
20 |
21 | # Packages
22 | *.egg
23 | *.egg-info
24 | dist
25 | build
26 | eggs
27 | parts
28 | bin
29 | var
30 | sdist
31 | develop-eggs
32 | .installed.cfg
33 | lib
34 | lib64
35 |
36 | # Build files
37 | README.rst
38 |
39 | # Installer logs
40 | pip-log.txt
41 |
42 | # Unit test / coverage reports
43 | .coverage
44 | .tox
45 | nosetests.xml
46 |
47 | # Translations
48 | *.mo
49 |
50 | # Mr Developer
51 | .mr.developer.cfg
52 | .project
53 | .pydevproject
--------------------------------------------------------------------------------
/resources/pychromecast/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Paulus Schoutsen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/resources/pychromecast/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include LICENSE
3 | include requirements.txt
4 | graft pychromecast
5 | recursive-exclude * *.py[co]
6 |
--------------------------------------------------------------------------------
/resources/pychromecast/chromecast_protobuf/README.md:
--------------------------------------------------------------------------------
1 | These files were imported from https://chromium.googlesource.com/chromium/src.git/+/master/extensions/common/api/cast_channel to generate the \_pb2.py-files.
--------------------------------------------------------------------------------
/resources/pychromecast/chromecast_protobuf/authority_keys.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | syntax = "proto2";
6 |
7 | option optimize_for = LITE_RUNTIME;
8 |
9 | package extensions.api.cast_channel.proto;
10 |
11 | message AuthorityKeys {
12 | message Key {
13 | required bytes fingerprint = 1;
14 | required bytes public_key = 2;
15 | }
16 | repeated Key keys = 1;
17 | }
18 |
--------------------------------------------------------------------------------
/resources/pychromecast/chromecast_protobuf/cast_channel.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | syntax = "proto2";
6 |
7 | option optimize_for = LITE_RUNTIME;
8 |
9 | package extensions.api.cast_channel;
10 |
11 | message CastMessage {
12 | // Always pass a version of the protocol for future compatibility
13 | // requirements.
14 | enum ProtocolVersion {
15 | CASTV2_1_0 = 0;
16 | }
17 | required ProtocolVersion protocol_version = 1;
18 |
19 | // source and destination ids identify the origin and destination of the
20 | // message. They are used to route messages between endpoints that share a
21 | // device-to-device channel.
22 | //
23 | // For messages between applications:
24 | // - The sender application id is a unique identifier generated on behalf of
25 | // the sender application.
26 | // - The receiver id is always the the session id for the application.
27 | //
28 | // For messages to or from the sender or receiver platform, the special ids
29 | // 'sender-0' and 'receiver-0' can be used.
30 | //
31 | // For messages intended for all endpoints using a given channel, the
32 | // wildcard destination_id '*' can be used.
33 | required string source_id = 2;
34 | required string destination_id = 3;
35 |
36 | // This is the core multiplexing key. All messages are sent on a namespace
37 | // and endpoints sharing a channel listen on one or more namespaces. The
38 | // namespace defines the protocol and semantics of the message.
39 | required string namespace = 4;
40 |
41 | // Encoding and payload info follows.
42 |
43 | // What type of data do we have in this message.
44 | enum PayloadType {
45 | STRING = 0;
46 | BINARY = 1;
47 | }
48 | required PayloadType payload_type = 5;
49 |
50 | // Depending on payload_type, exactly one of the following optional fields
51 | // will always be set.
52 | optional string payload_utf8 = 6;
53 | optional bytes payload_binary = 7;
54 | }
55 |
56 | enum SignatureAlgorithm {
57 | UNSPECIFIED = 0;
58 | RSASSA_PKCS1v15 = 1;
59 | RSASSA_PSS = 2;
60 | }
61 |
62 | enum HashAlgorithm {
63 | SHA1 = 0;
64 | SHA256 = 1;
65 | }
66 |
67 | // Messages for authentication protocol between a sender and a receiver.
68 | message AuthChallenge {
69 | optional SignatureAlgorithm signature_algorithm = 1
70 | [default = RSASSA_PKCS1v15];
71 | optional bytes sender_nonce = 2;
72 | optional HashAlgorithm hash_algorithm = 3 [default = SHA1];
73 | }
74 |
75 | message AuthResponse {
76 | required bytes signature = 1;
77 | required bytes client_auth_certificate = 2;
78 | repeated bytes intermediate_certificate = 3;
79 | optional SignatureAlgorithm signature_algorithm = 4
80 | [default = RSASSA_PKCS1v15];
81 | optional bytes sender_nonce = 5;
82 | optional HashAlgorithm hash_algorithm = 6 [default = SHA1];
83 | optional bytes crl = 7;
84 | }
85 |
86 | message AuthError {
87 | enum ErrorType {
88 | INTERNAL_ERROR = 0;
89 | NO_TLS = 1; // The underlying connection is not TLS
90 | SIGNATURE_ALGORITHM_UNAVAILABLE = 2;
91 | }
92 | required ErrorType error_type = 1;
93 | }
94 |
95 | message DeviceAuthMessage {
96 | // Request fields
97 | optional AuthChallenge challenge = 1;
98 | // Response fields
99 | optional AuthResponse response = 2;
100 | optional AuthError error = 3;
101 | }
102 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/bbciplayer_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the BBC iPlayer Controller
3 | """
4 | # pylint: disable=invalid-name
5 |
6 | import argparse
7 | import logging
8 | import sys
9 | from time import sleep
10 | import json
11 |
12 | import zeroconf
13 | import pychromecast
14 | from pychromecast import quick_play
15 |
16 | # Change to the name of your Chromecast
17 | CAST_NAME = "Lounge Video"
18 |
19 | # Note: Media ID is NOT the 8 digit alpha-numeric in the URL
20 | # it can be found by right clicking the playing video on the web interface
21 | # e.g. https://www.bbc.co.uk/iplayer/episode/b09w7fd9/bitz-bob-series-1-1-castle-makeover shows:
22 | # "2908kbps | dash (mf_cloudfront_dash_https)
23 | # b09w70r2 | 960x540"
24 | MEDIA_ID = "b09w70r2"
25 | IS_LIVE = False
26 | METADATA = {
27 | "metadatatype": 0,
28 | "title": "Bitz & Bob",
29 | "subtitle": "Castle Makeover",
30 | "images": [{"url": "https://ichef.bbci.co.uk/images/ic/1280x720/p07j4m3r.jpg"}],
31 | }
32 |
33 | parser = argparse.ArgumentParser(
34 | description="Example on how to use the BBC iPlayer Controller to play an media stream."
35 | )
36 | parser.add_argument(
37 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
38 | )
39 | parser.add_argument(
40 | "--known-host",
41 | help="Add known host (IP), can be used multiple times",
42 | action="append",
43 | )
44 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
45 | parser.add_argument(
46 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
47 | )
48 | parser.add_argument(
49 | "--media_id", help='MediaID (default: "%(default)s")', default=MEDIA_ID
50 | )
51 | parser.add_argument(
52 | "--metadata", help='Metadata (default: "%(default)s")', default=json.dumps(METADATA)
53 | )
54 | parser.add_argument(
55 | "--is_live",
56 | help="Show 'live' and no current/end timestamps on UI",
57 | action="store_true",
58 | default=IS_LIVE,
59 | )
60 | args = parser.parse_args()
61 |
62 | app_name = "bbciplayer"
63 | app_data = {
64 | "media_id": args.media_id,
65 | "is_live": args.is_live,
66 | "metadata": json.loads(args.metadata),
67 | }
68 |
69 | if args.show_debug:
70 | logging.basicConfig(level=logging.DEBUG)
71 | if args.show_zeroconf_debug:
72 | print("Zeroconf version: " + zeroconf.__version__)
73 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
74 |
75 | chromecasts, browser = pychromecast.get_listed_chromecasts(
76 | friendly_names=[args.cast], known_hosts=args.known_host
77 | )
78 | if not chromecasts:
79 | print(f'No chromecast with name "{args.cast}" discovered')
80 | sys.exit(1)
81 |
82 | cast = chromecasts[0]
83 | # Start socket client's worker thread and wait for initial status update
84 | cast.wait()
85 | print(f'Found chromecast with name "{args.cast}", attempting to play "{args.media_id}"')
86 |
87 | quick_play.quick_play(cast, app_name, app_data)
88 |
89 | sleep(10)
90 |
91 | browser.stop_discovery()
92 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/bbcsounds_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the BBC iPlayer Controller
3 | """
4 | # pylint: disable=invalid-name
5 |
6 | import argparse
7 | import logging
8 | import sys
9 | from time import sleep
10 | import json
11 |
12 | import zeroconf
13 | import pychromecast
14 | from pychromecast import quick_play
15 |
16 | # Change to the name of your Chromecast
17 | CAST_NAME = "Lounge Video"
18 |
19 | # Media ID can be found in the URL
20 | # e.g. https://www.bbc.co.uk/sounds/live:bbc_radio_one
21 | MEDIA_ID = "bbc_radio_one"
22 | IS_LIVE = True
23 | METADATA = {
24 | "metadatatype": 0,
25 | "title": "Radio 1",
26 | "images": [
27 | {
28 | "url": "https://sounds.files.bbci.co.uk/2.3.0/networks/bbc_radio_one/background_1280x720.png"
29 | }
30 | ],
31 | }
32 |
33 | parser = argparse.ArgumentParser(
34 | description="Example on how to use the BBC Sounds Controller to play an media stream."
35 | )
36 | parser.add_argument(
37 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
38 | )
39 | parser.add_argument(
40 | "--known-host",
41 | help="Add known host (IP), can be used multiple times",
42 | action="append",
43 | )
44 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
45 | parser.add_argument(
46 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
47 | )
48 | parser.add_argument(
49 | "--media_id", help='MediaID (default: "%(default)s")', default=MEDIA_ID
50 | )
51 | parser.add_argument(
52 | "--metadata", help='Metadata (default: "%(default)s")', default=json.dumps(METADATA)
53 | )
54 | parser.add_argument(
55 | "--is_live",
56 | help="Show 'live' and no current/end timestamps on UI",
57 | action="store_true",
58 | default=IS_LIVE,
59 | )
60 | args = parser.parse_args()
61 |
62 | app_name = "bbcsounds"
63 | app_data = {
64 | "media_id": args.media_id,
65 | "is_live": args.is_live,
66 | "metadata": json.loads(args.metadata),
67 | }
68 |
69 | if args.show_debug:
70 | logging.basicConfig(level=logging.DEBUG)
71 | if args.show_zeroconf_debug:
72 | print("Zeroconf version: " + zeroconf.__version__)
73 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
74 |
75 | chromecasts, browser = pychromecast.get_listed_chromecasts(
76 | friendly_names=[args.cast], known_hosts=args.known_host
77 | )
78 | if not chromecasts:
79 | print(f'No chromecast with name "{args.cast}" discovered')
80 | sys.exit(1)
81 |
82 | cast = chromecasts[0]
83 | # Start socket client's worker thread and wait for initial status update
84 | cast.wait()
85 | print(f'Found chromecast with name "{args.cast}", attempting to play "{args.media_id}"')
86 |
87 | quick_play.quick_play(cast, app_name, app_data)
88 |
89 | sleep(10)
90 |
91 | browser.stop_discovery()
92 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/bubbleupnp_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the BubbleUPNP Controller to play an URL.
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | from time import sleep
11 |
12 | import zeroconf
13 |
14 | import pychromecast
15 | from pychromecast.controllers.bubbleupnp import BubbleUPNPController
16 |
17 |
18 | # Change to the friendly name of your Chromecast
19 | CAST_NAME = "Kitchen speaker"
20 |
21 | # Change to an audio or video url
22 | MEDIA_URL = "https://c3.toivon.net/toivon/toivon_3?mp=/stream"
23 |
24 | parser = argparse.ArgumentParser(
25 | description="Example on how to use the BubbleUPNP Controller to play an URL."
26 | )
27 | parser.add_argument(
28 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
29 | )
30 | parser.add_argument(
31 | "--known-host",
32 | help="Add known host (IP), can be used multiple times",
33 | action="append",
34 | )
35 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
36 | parser.add_argument(
37 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
38 | )
39 | parser.add_argument(
40 | "--url", help='Media url (default: "%(default)s")', default=MEDIA_URL
41 | )
42 | args = parser.parse_args()
43 |
44 | if args.show_debug:
45 | logging.basicConfig(level=logging.DEBUG)
46 | if args.show_zeroconf_debug:
47 | print("Zeroconf version: " + zeroconf.__version__)
48 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
49 |
50 | # pylint: disable=unbalanced-tuple-unpacking
51 | chromecasts, browser = pychromecast.get_listed_chromecasts(
52 | friendly_names=[args.cast], known_hosts=args.known_host
53 | )
54 | if not chromecasts:
55 | print(f'No chromecast with name "{args.cast}" discovered')
56 | sys.exit(1)
57 |
58 | cast = list(chromecasts)[0]
59 | # Start socket client's worker thread and wait for initial status update
60 | cast.wait()
61 | print(f'Found chromecast with name "{args.cast}", attempting to play "{args.url}"')
62 | bubbleupnp = BubbleUPNPController()
63 | cast.register_handler(bubbleupnp)
64 | bubbleupnp.launch()
65 | bubbleupnp.play_media(args.url, "audio/mp3", stream_type="LIVE")
66 |
67 | sleep(10)
68 |
69 | browser.stop_discovery()
70 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/dashcast_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example that shows how the DashCast controller can be used.
3 | """
4 | # pylint: disable=invalid-name
5 |
6 | import argparse
7 | import logging
8 | import sys
9 | import time
10 |
11 | import zeroconf
12 |
13 | import pychromecast
14 | from pychromecast.controllers import dashcast
15 |
16 | # Change to the friendly name of your Chromecast
17 | CAST_NAME = "Living Room"
18 |
19 | parser = argparse.ArgumentParser(
20 | description="Example that shows how the DashCast controller can be used."
21 | )
22 | parser.add_argument(
23 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
24 | )
25 | parser.add_argument(
26 | "--known-host",
27 | help="Add known host (IP), can be used multiple times",
28 | action="append",
29 | )
30 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
31 | parser.add_argument(
32 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
33 | )
34 | args = parser.parse_args()
35 |
36 | if args.show_debug:
37 | logging.basicConfig(level=logging.DEBUG)
38 | if args.show_zeroconf_debug:
39 | print("Zeroconf version: " + zeroconf.__version__)
40 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
41 |
42 | chromecasts, browser = pychromecast.get_listed_chromecasts(
43 | friendly_names=[args.cast], known_hosts=args.known_host
44 | )
45 | if not chromecasts:
46 | print(f'No chromecast with name "{args.cast}" discovered')
47 | sys.exit(1)
48 |
49 | cast = chromecasts[0]
50 | # Start socket client's worker thread and wait for initial status update
51 | cast.wait()
52 |
53 | d = dashcast.DashCastController()
54 | cast.register_handler(d)
55 |
56 | print()
57 | print(cast.cast_info)
58 | time.sleep(1)
59 | print()
60 | print(cast.status)
61 | print()
62 | print(cast.media_controller.status)
63 | print()
64 |
65 | if not cast.is_idle:
66 | print("Killing current running app")
67 | cast.quit_app()
68 | t = 5
69 | while cast.status.app_id is not None and t > 0:
70 | time.sleep(0.1)
71 | t = t - 0.1
72 |
73 | time.sleep(1)
74 |
75 | # Test that the callback chain works. This should send a message to
76 | # load the first url, but immediately after send a message load the
77 | # second url.
78 | warning_message = "If you see this on your TV then something is broken"
79 | d.load_url(
80 | "https://home-assistant.io/? " + warning_message,
81 | callback_function=lambda result: d.load_url("https://home-assistant.io/"),
82 | )
83 |
84 | # If debugging, sleep after running so we can see any error messages.
85 | if args.show_debug:
86 | time.sleep(10)
87 |
88 | # Shut down discovery
89 | browser.stop_discovery()
90 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/discovery_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example that shows how to receive updates on discovered chromecasts.
3 | """
4 | # pylint: disable=invalid-name
5 |
6 | import argparse
7 | import logging
8 | import time
9 |
10 | import zeroconf
11 |
12 | import pychromecast
13 |
14 | parser = argparse.ArgumentParser(
15 | description="Example on how to receive updates on discovered chromecasts."
16 | )
17 | parser.add_argument(
18 | "--known-host",
19 | help="Add known host (IP), can be used multiple times",
20 | action="append",
21 | )
22 | parser.add_argument(
23 | "--force-zeroconf",
24 | help="Zeroconf will be used even if --known-host is present",
25 | action="store_true",
26 | )
27 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
28 | parser.add_argument(
29 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
30 | )
31 | args = parser.parse_args()
32 |
33 | if args.show_debug:
34 | logging.basicConfig(level=logging.DEBUG)
35 | if args.show_zeroconf_debug:
36 | print("Zeroconf version: " + zeroconf.__version__)
37 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
38 |
39 |
40 | def list_devices():
41 | """Print a list of known devices."""
42 | print("Currently known cast devices:")
43 | for uuid, service in browser.services.items():
44 | print(f" {uuid} {service}")
45 |
46 |
47 | class MyCastListener(pychromecast.discovery.AbstractCastListener):
48 | """Listener for discovering chromecasts."""
49 |
50 | def add_cast(self, uuid, _service):
51 | """Called when a new cast has beeen discovered."""
52 | print(f"Found cast device with UUID {uuid}")
53 | list_devices()
54 |
55 | def remove_cast(self, uuid, _service, cast_info):
56 | """Called when a cast has beeen lost (MDNS info expired or host down)."""
57 | print(f"Lost cast device with UUID {uuid} {cast_info}")
58 | list_devices()
59 |
60 | def update_cast(self, uuid, _service):
61 | """Called when a cast has beeen updated (MDNS info renewed or changed)."""
62 | print(f"Updated cast device with UUID {uuid}")
63 | list_devices()
64 |
65 |
66 | if args.known_host and not args.force_zeroconf:
67 | zconf = None
68 | else:
69 | zconf = zeroconf.Zeroconf()
70 | browser = pychromecast.discovery.CastBrowser(MyCastListener(), zconf, args.known_host)
71 | browser.start_discovery()
72 |
73 | try:
74 | while True:
75 | time.sleep(1)
76 | except KeyboardInterrupt:
77 | pass
78 |
79 | # Shut down discovery
80 | browser.stop_discovery()
81 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/discovery_example2.py:
--------------------------------------------------------------------------------
1 | """
2 | Example that shows how to list all available chromecasts.
3 | """
4 | # pylint: disable=invalid-name
5 |
6 | import argparse
7 | import logging
8 |
9 | import zeroconf
10 |
11 | import pychromecast
12 |
13 | parser = argparse.ArgumentParser(
14 | description="Example that shows how to list all available chromecasts."
15 | )
16 | parser.add_argument(
17 | "--known-host",
18 | help="Add known host (IP), can be used multiple times",
19 | action="append",
20 | )
21 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
22 | parser.add_argument(
23 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
24 | )
25 | args = parser.parse_args()
26 |
27 | if args.show_debug:
28 | logging.basicConfig(level=logging.DEBUG)
29 | if args.show_zeroconf_debug:
30 | print("Zeroconf version: " + zeroconf.__version__)
31 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
32 |
33 | devices, browser = pychromecast.discovery.discover_chromecasts(
34 | known_hosts=args.known_host
35 | )
36 | # Shut down discovery
37 | browser.stop_discovery()
38 |
39 | print(f"Discovered {len(devices)} device(s):")
40 | for device in devices:
41 | print(f" {device}")
42 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/discovery_example3.py:
--------------------------------------------------------------------------------
1 | """
2 | Example that shows how to list chromecasts matching on name or uuid.
3 | """
4 | # pylint: disable=invalid-name
5 |
6 | import argparse
7 | import logging
8 | import sys
9 | from uuid import UUID
10 |
11 | import zeroconf
12 |
13 | import pychromecast
14 |
15 | parser = argparse.ArgumentParser(
16 | description="Example that shows how to list chromecasts matching on name or uuid."
17 | )
18 | parser.add_argument("--cast", help='Name of wanted cast device")', default=None)
19 | parser.add_argument("--uuid", help="UUID of wanted cast device", default=None)
20 | parser.add_argument(
21 | "--known-host",
22 | help="Add known host (IP), can be used multiple times",
23 | action="append",
24 | )
25 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
26 | parser.add_argument(
27 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
28 | )
29 | args = parser.parse_args()
30 |
31 | if args.show_debug:
32 | logging.basicConfig(level=logging.DEBUG)
33 | if args.show_zeroconf_debug:
34 | print("Zeroconf version: " + zeroconf.__version__)
35 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
36 |
37 | if args.cast is None and args.uuid is None:
38 | print("Need to supply `cast` or `uuid`")
39 | sys.exit(1)
40 |
41 | friendly_names = []
42 | if args.cast:
43 | friendly_names.append(args.cast)
44 |
45 | uuids = []
46 | if args.uuid:
47 | uuids.append(UUID(args.uuid))
48 |
49 | devices, browser = pychromecast.discovery.discover_listed_chromecasts(
50 | friendly_names=friendly_names, uuids=uuids, known_hosts=args.known_host
51 | )
52 | # Shut down discovery
53 | browser.stop_discovery()
54 |
55 | print(f"Discovered {len(devices)} device(s):")
56 | for device in devices:
57 | print(f" {device}")
58 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/get_chromecasts.py:
--------------------------------------------------------------------------------
1 | """
2 | Example that shows how to connect to all chromecasts.
3 | """
4 | # pylint: disable=invalid-name
5 |
6 | import argparse
7 | import logging
8 | import sys
9 |
10 | import zeroconf
11 |
12 | import pychromecast
13 |
14 | parser = argparse.ArgumentParser(
15 | description="Example on how to connect to all chromecasts."
16 | )
17 | parser.add_argument(
18 | "--known-host",
19 | help="Add known host (IP), can be used multiple times",
20 | action="append",
21 | )
22 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
23 | parser.add_argument(
24 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
25 | )
26 | args = parser.parse_args()
27 |
28 | if args.show_debug:
29 | logging.basicConfig(level=logging.DEBUG)
30 | if args.show_zeroconf_debug:
31 | print("Zeroconf version: " + zeroconf.__version__)
32 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
33 |
34 | casts, browser = pychromecast.get_chromecasts(known_hosts=args.known_host)
35 | # Shut down discovery as we don't care about updates
36 | browser.stop_discovery()
37 | if len(casts) == 0:
38 | print("No Devices Found")
39 | sys.exit(1)
40 |
41 | print("Found cast devices:")
42 | for cast in casts:
43 | print(
44 | f' "{cast.name}" on mDNS/host service {cast.cast_info.services} with UUID:{cast.uuid}' # pylint: disable=protected-access
45 | )
46 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/homeassistant_media_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the Home Assistant Media app to play an URL.
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | from time import sleep
11 |
12 | import zeroconf
13 |
14 | import pychromecast
15 | from pychromecast import quick_play
16 |
17 |
18 | # Change to the friendly name of your Chromecast
19 | CAST_NAME = "Kitchen speaker"
20 |
21 | # Change to an audio or video url
22 | MEDIA_URL = (
23 | "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
24 | )
25 |
26 | parser = argparse.ArgumentParser(
27 | description="Example on how to use the Home Asssitant Media Controller to play an URL."
28 | )
29 | parser.add_argument(
30 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
31 | )
32 | parser.add_argument(
33 | "--known-host",
34 | help="Add known host (IP), can be used multiple times",
35 | action="append",
36 | )
37 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
38 | parser.add_argument(
39 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
40 | )
41 | parser.add_argument(
42 | "--url", help='Media url (default: "%(default)s")', default=MEDIA_URL
43 | )
44 | args = parser.parse_args()
45 |
46 | app_name = "homeassistant_media"
47 | app_data = {
48 | "media_id": args.url,
49 | }
50 |
51 | if args.show_debug:
52 | logging.basicConfig(level=logging.DEBUG)
53 | if args.show_zeroconf_debug:
54 | print("Zeroconf version: " + zeroconf.__version__)
55 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
56 |
57 | # pylint: disable=unbalanced-tuple-unpacking
58 | chromecasts, browser = pychromecast.get_listed_chromecasts(
59 | friendly_names=[args.cast], known_hosts=args.known_host
60 | )
61 | if not chromecasts:
62 | print(f'No chromecast with name "{args.cast}" discovered')
63 | sys.exit(1)
64 |
65 | cast = list(chromecasts)[0]
66 | # Start socket client's worker thread and wait for initial status update
67 | cast.wait()
68 | print(f'Found chromecast with name "{args.cast}", attempting to play "{args.url}"')
69 |
70 | quick_play.quick_play(cast, app_name, app_data)
71 |
72 | sleep(10)
73 |
74 | browser.stop_discovery()
75 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/list_chromecasts.py:
--------------------------------------------------------------------------------
1 | """
2 | Example that shows how to list chromecasts.
3 | """
4 | import argparse
5 | import logging
6 |
7 | import pychromecast
8 |
9 | parser = argparse.ArgumentParser(description="Example on how to list chromecasts.")
10 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
11 | args = parser.parse_args()
12 |
13 | if args.show_debug:
14 | logging.basicConfig(level=logging.DEBUG)
15 |
16 | casts = pychromecast.get_chromecasts()
17 | if len(casts) == 0:
18 | print("No Devices Found")
19 | exit()
20 |
21 | print("Found cast devices:")
22 | for cast in casts:
23 | print(
24 | ' "{}" on {}:{} with UUID:{}'.format(
25 | cast.name, cast.host, cast.port, cast.uuid
26 | )
27 | )
28 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/media_enqueue.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use queuing with Media Controller
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | import time
11 |
12 | import zeroconf
13 |
14 | import pychromecast
15 |
16 | # Change to the friendly name of your Chromecast
17 | CAST_NAME = "Living Room"
18 |
19 | # Change to an audio or video url
20 | MEDIA_URLS = [
21 | "https://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/dash/nonuk/dash_low/llnws/bbc_radio_fourfm.mpd",
22 | "https://www.bensound.com/bensound-music/bensound-jazzyfrenchy.mp3",
23 | "https://audio.guim.co.uk/2020/08/14-65292-200817TIFXR.mp3",
24 | ]
25 |
26 |
27 | parser = argparse.ArgumentParser(
28 | description="Example on how to use the Media Controller with a queue."
29 | )
30 | parser.add_argument(
31 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
32 | )
33 | parser.add_argument(
34 | "--known-host",
35 | help="Add known host (IP), can be used multiple times",
36 | action="append",
37 | )
38 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
39 | parser.add_argument(
40 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
41 | )
42 | args = parser.parse_args()
43 |
44 | if args.show_debug:
45 | logging.basicConfig(level=logging.DEBUG)
46 | if args.show_zeroconf_debug:
47 | print("Zeroconf version: " + zeroconf.__version__)
48 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
49 |
50 | chromecasts, browser = pychromecast.get_listed_chromecasts(
51 | friendly_names=[args.cast], known_hosts=args.known_host
52 | )
53 | if not chromecasts:
54 | print(f'No chromecast with name "{args.cast}" discovered')
55 | sys.exit(1)
56 |
57 | cast = chromecasts[0]
58 |
59 | # Start socket client's worker thread and wait for initial status update
60 | cast.wait()
61 | print(f'Found chromecast with name "{args.cast}"')
62 |
63 | cast.media_controller.play_media(MEDIA_URLS[0], "audio/mp3")
64 |
65 | # Wait for Chromecast to start playing
66 | while cast.media_controller.status.player_state != "PLAYING":
67 | time.sleep(0.1)
68 |
69 | # Queue next items
70 | for URL in MEDIA_URLS[1:]:
71 | print("Enqueuing...")
72 | cast.media_controller.play_media(URL, "audio/mp3", enqueue=True)
73 |
74 |
75 | for URL in MEDIA_URLS[1:]:
76 | time.sleep(5)
77 | print("Skipping...")
78 | cast.media_controller.queue_next()
79 |
80 | # Shut down discovery
81 | browser.stop_discovery()
82 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/media_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the Media Controller to play an URL.
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | import time
11 |
12 | import zeroconf
13 |
14 | import pychromecast
15 |
16 | # Change to the friendly name of your Chromecast
17 | CAST_NAME = "Living Room"
18 |
19 | # Change to an audio or video url
20 | MEDIA_URL = "https://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/dash/nonuk/dash_low/llnws/bbc_radio_fourfm.mpd"
21 |
22 | parser = argparse.ArgumentParser(
23 | description="Example on how to use the Media Controller to play an URL."
24 | )
25 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
26 | parser.add_argument(
27 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
28 | )
29 | parser.add_argument(
30 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
31 | )
32 | parser.add_argument(
33 | "--known-host",
34 | help="Add known host (IP), can be used multiple times",
35 | action="append",
36 | )
37 | parser.add_argument(
38 | "--url", help='Media url (default: "%(default)s")', default=MEDIA_URL
39 | )
40 | args = parser.parse_args()
41 |
42 | if args.show_debug:
43 | logging.basicConfig(level=logging.DEBUG)
44 | if args.show_zeroconf_debug:
45 | print("Zeroconf version: " + zeroconf.__version__)
46 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
47 |
48 | chromecasts, browser = pychromecast.get_listed_chromecasts(
49 | friendly_names=[args.cast], known_hosts=args.known_host
50 | )
51 | if not chromecasts:
52 | print(f'No chromecast with name "{args.cast}" discovered')
53 | sys.exit(1)
54 |
55 | cast = chromecasts[0]
56 | # Start socket client's worker thread and wait for initial status update
57 | cast.wait()
58 | print(f'Found chromecast with name "{args.cast}", attempting to play "{args.url}"')
59 | cast.media_controller.play_media(args.url, "audio/mp3")
60 |
61 | # Wait for player_state PLAYING
62 | player_state = None
63 | t = 30
64 | has_played = False
65 | while True:
66 | try:
67 | if player_state != cast.media_controller.status.player_state:
68 | player_state = cast.media_controller.status.player_state
69 | print("Player state:", player_state)
70 | if player_state == "PLAYING":
71 | has_played = True
72 | if cast.socket_client.is_connected and has_played and player_state != "PLAYING":
73 | has_played = False
74 | cast.media_controller.play_media(args.url, "audio/mp3")
75 |
76 | time.sleep(0.1)
77 | t = t - 0.1
78 | except KeyboardInterrupt:
79 | break
80 |
81 | # Shut down discovery
82 | browser.stop_discovery()
83 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/media_example2.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the Media Controller.
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | import time
11 |
12 | import zeroconf
13 |
14 | import pychromecast
15 |
16 | # Change to the friendly name of your Chromecast
17 | CAST_NAME = "Living Room"
18 |
19 | # Change to an audio or video url
20 | MEDIA_URL = (
21 | "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
22 | )
23 |
24 | parser = argparse.ArgumentParser(
25 | description="Example on how to use the Media Controller."
26 | )
27 | parser.add_argument(
28 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
29 | )
30 | parser.add_argument(
31 | "--known-host",
32 | help="Add known host (IP), can be used multiple times",
33 | action="append",
34 | )
35 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
36 | parser.add_argument(
37 | "--show-status-only", help="Show status, then exit", action="store_true"
38 | )
39 | parser.add_argument(
40 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
41 | )
42 | parser.add_argument(
43 | "--url", help='Media url (default: "%(default)s")', default=MEDIA_URL
44 | )
45 | args = parser.parse_args()
46 |
47 | if args.show_debug:
48 | fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
49 | datefmt = "%Y-%m-%d %H:%M:%S"
50 | logging.basicConfig(format=fmt, datefmt=datefmt, level=logging.DEBUG)
51 | if args.show_zeroconf_debug:
52 | print("Zeroconf version: " + zeroconf.__version__)
53 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
54 |
55 | chromecasts, browser = pychromecast.get_listed_chromecasts(
56 | friendly_names=[args.cast], known_hosts=args.known_host
57 | )
58 | if not chromecasts:
59 | print(f'No chromecast with name "{args.cast}" discovered')
60 | sys.exit(1)
61 |
62 | cast = chromecasts[0]
63 |
64 | # Start socket client's worker thread and wait for initial status update
65 | cast.wait()
66 |
67 | print()
68 | print(cast.cast_info)
69 | time.sleep(1)
70 | print()
71 | print(cast.status)
72 | print()
73 | print(cast.media_controller.status)
74 | print()
75 |
76 | if args.show_status_only:
77 | sys.exit()
78 |
79 | if not cast.is_idle:
80 | print("Killing current running app")
81 | cast.quit_app()
82 | t = 5
83 | while cast.status.app_id is not None and t > 0:
84 | time.sleep(0.1)
85 | t = t - 0.1
86 |
87 | print(f'Playing media "{args.url}"')
88 | cast.play_media(args.url, "video/mp4")
89 |
90 | t = 0
91 |
92 | while True:
93 | try:
94 | t += 1
95 |
96 | if t > 10 and t % 3 == 0:
97 | print("Media status", cast.media_controller.status)
98 |
99 | if t == 15:
100 | print("Sending pause command")
101 | cast.media_controller.pause()
102 | elif t == 20:
103 | print("Sending play command")
104 | cast.media_controller.play()
105 | elif t == 25:
106 | print("Sending stop command")
107 | cast.media_controller.stop()
108 | elif t == 32:
109 | cast.quit_app()
110 | break
111 |
112 | time.sleep(1)
113 | except KeyboardInterrupt:
114 | break
115 |
116 | # Shut down discovery
117 | browser.stop_discovery()
118 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/multizone_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the Multizone (Audio Group) Controller
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | import time
11 |
12 | import zeroconf
13 |
14 | import pychromecast
15 | from pychromecast.controllers.multizone import (
16 | MultizoneController,
17 | MultiZoneControllerListener,
18 | )
19 | from pychromecast.socket_client import ConnectionStatusListener
20 |
21 | # Change to the name of your Chromecast
22 | CAST_NAME = "Whole house"
23 |
24 | parser = argparse.ArgumentParser(
25 | description="Example on how to use the Multizone Controller to track groupp members."
26 | )
27 | parser.add_argument(
28 | "--cast", help='Name of speaker group (default: "%(default)s")', default=CAST_NAME
29 | )
30 | parser.add_argument(
31 | "--known-host",
32 | help="Add known host (IP), can be used multiple times",
33 | action="append",
34 | )
35 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
36 | parser.add_argument(
37 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
38 | )
39 | args = parser.parse_args()
40 |
41 | if args.show_debug:
42 | logging.basicConfig(level=logging.DEBUG)
43 | if args.show_zeroconf_debug:
44 | print("Zeroconf version: " + zeroconf.__version__)
45 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
46 |
47 |
48 | class MyConnectionStatusListener(ConnectionStatusListener):
49 | """ConnectionStatusListener"""
50 |
51 | def __init__(self, _mz):
52 | self._mz = _mz
53 |
54 | def new_connection_status(self, status):
55 | if status.status == "CONNECTED":
56 | self._mz.update_members()
57 |
58 |
59 | class MyMultiZoneControllerListener(MultiZoneControllerListener):
60 | """MultiZoneControllerListener"""
61 |
62 | def multizone_member_added(self, group_uuid):
63 | print(f"New member: {group_uuid}")
64 |
65 | def multizone_member_removed(self, group_uuid):
66 | print(f"Removed member: {group_uuid}")
67 |
68 | def multizone_status_received(self):
69 | print(f"Members: {mz.members}")
70 |
71 |
72 | chromecasts, browser = pychromecast.get_listed_chromecasts(
73 | friendly_names=[args.cast], known_hosts=args.known_host
74 | )
75 | if not chromecasts:
76 | print(f'No chromecast with name "{args.cast}" discovered')
77 | sys.exit(1)
78 |
79 | cast = chromecasts[0]
80 | # Add listeners
81 | mz = MultizoneController(cast.uuid)
82 | mz.register_listener(MyMultiZoneControllerListener())
83 | cast.register_handler(mz)
84 | cast.register_connection_listener(MyConnectionStatusListener(mz))
85 |
86 | # Start socket client's worker thread and wait for initial status update
87 | cast.wait()
88 |
89 | while True:
90 | try:
91 | time.sleep(1)
92 | except KeyboardInterrupt:
93 | break
94 |
95 | # Shut down discovery
96 | browser.stop_discovery()
97 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/simple_listener_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example showing how to create a simple Chromecast event listener for
3 | device and media status events
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | import time
11 | import zeroconf
12 |
13 | import pychromecast
14 | from pychromecast.controllers.media import MediaStatusListener
15 | from pychromecast.controllers.receiver import CastStatusListener
16 |
17 | # Change to the friendly name of your Chromecast
18 | CAST_NAME = "Living Room Speaker"
19 |
20 |
21 | class MyCastStatusListener(CastStatusListener):
22 | """Cast status listener"""
23 |
24 | def __init__(self, name, cast):
25 | self.name = name
26 | self.cast = cast
27 |
28 | def new_cast_status(self, status):
29 | print("[", time.ctime(), " - ", self.name, "] status chromecast change:")
30 | print(status)
31 |
32 |
33 | class MyMediaStatusListener(MediaStatusListener):
34 | """Status media listener"""
35 |
36 | def __init__(self, name, cast):
37 | self.name = name
38 | self.cast = cast
39 |
40 | def new_media_status(self, status):
41 | print("[", time.ctime(), " - ", self.name, "] status media change:")
42 | print(status)
43 |
44 |
45 | parser = argparse.ArgumentParser(
46 | description="Example on how to create a simple Chromecast event listener."
47 | )
48 | parser.add_argument(
49 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
50 | )
51 | parser.add_argument(
52 | "--known-host",
53 | help="Add known host (IP), can be used multiple times",
54 | action="append",
55 | )
56 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
57 | parser.add_argument(
58 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
59 | )
60 | args = parser.parse_args()
61 |
62 | if args.show_debug:
63 | logging.basicConfig(level=logging.DEBUG)
64 | if args.show_zeroconf_debug:
65 | print("Zeroconf version: " + zeroconf.__version__)
66 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
67 |
68 | chromecasts, browser = pychromecast.get_listed_chromecasts(
69 | friendly_names=[args.cast], known_hosts=args.known_host
70 | )
71 | if not chromecasts:
72 | print(f'No chromecast with name "{args.cast}" discovered')
73 | sys.exit(1)
74 |
75 | chromecast = chromecasts[0]
76 | # Start socket client's worker thread and wait for initial status update
77 | chromecast.wait()
78 |
79 | listenerCast = MyCastStatusListener(chromecast.name, chromecast)
80 | chromecast.register_status_listener(listenerCast)
81 |
82 | listenerMedia = MyMediaStatusListener(chromecast.name, chromecast)
83 | chromecast.media_controller.register_status_listener(listenerMedia)
84 |
85 | input("Listening for Chromecast events...\n\n")
86 |
87 | # Shut down discovery
88 | browser.stop_discovery()
89 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/supla_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the Supla Controller
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import logging
8 | from time import sleep
9 | import sys
10 |
11 | import requests
12 | from bs4 import BeautifulSoup # pylint: disable=import-error
13 |
14 | import pychromecast
15 | from pychromecast.controllers.supla import SuplaController
16 |
17 |
18 | # Change to the name of your Chromecast
19 | CAST_NAME = "Kitchen Speaker"
20 |
21 | # Change to the video id of the YouTube video
22 | # video id is the last part of the url http://youtube.com/watch?v=video_id
23 | PROGRAM = "aamulypsy"
24 |
25 |
26 | result = requests.get(f"https://www.supla.fi/ohjelmat/{PROGRAM}")
27 | soup = BeautifulSoup(result.content)
28 | MEDIA_ID = soup.select('a[title*="Koko Shitti"]')[0]["href"].split("/")[-1]
29 | print(MEDIA_ID)
30 |
31 |
32 | logging.basicConfig(level=logging.DEBUG)
33 |
34 | chromecasts, browser = pychromecast.get_listed_chromecasts(friendly_names=[CAST_NAME])
35 | if not chromecasts:
36 | print(f'No chromecast with name "{CAST_NAME}" discovered')
37 | sys.exit(1)
38 |
39 | cast = chromecasts[0]
40 | # Start socket client's worker thread and wait for initial status update
41 | cast.wait()
42 |
43 | supla = SuplaController()
44 | cast.register_handler(supla)
45 | supla.launch()
46 | supla.play_media(MEDIA_ID)
47 | cast.wait()
48 |
49 | sleep(10)
50 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/yleareena_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the Yle Areena Controller
3 |
4 | """
5 | # pylint: disable=invalid-name, import-outside-toplevel
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | from time import sleep
11 | import zeroconf
12 |
13 | import pychromecast
14 | from pychromecast.controllers.yleareena import YleAreenaController
15 |
16 | logger = logging.getLogger(__name__)
17 |
18 |
19 | # Change to the name of your Chromecast
20 | CAST_NAME = "My Chromecast"
21 |
22 | parser = argparse.ArgumentParser(
23 | description="Example on how to use the Yle Areena Controller."
24 | )
25 | parser.add_argument(
26 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
27 | )
28 | parser.add_argument(
29 | "--known-host",
30 | help="Add known host (IP), can be used multiple times",
31 | action="append",
32 | )
33 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
34 | parser.add_argument(
35 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
36 | )
37 | parser.add_argument("--program", help="Areena Program ID", default="1-50097921")
38 | parser.add_argument("--audio_language", help="audio_language", default="")
39 | parser.add_argument("--text_language", help="text_language", default="off")
40 | args = parser.parse_args()
41 |
42 | if args.show_debug:
43 | logging.basicConfig(level=logging.DEBUG)
44 | if args.show_zeroconf_debug:
45 | print("Zeroconf version: " + zeroconf.__version__)
46 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
47 |
48 |
49 | def get_kaltura_id(program_id):
50 | """
51 | Dive into the yledl internals and fetch the kaltura player id.
52 | This can be used with Chromecast
53 | """
54 | # yledl is not available in CI, silence import warnings
55 | from yledl.streamfilters import StreamFilters # pylint: disable=import-error
56 | from yledl.http import HttpClient # pylint: disable=import-error
57 | from yledl.localization import TranslationChooser # pylint: disable=import-error
58 | from yledl.extractors import extractor_factory # pylint: disable=import-error
59 | from yledl.titleformatter import TitleFormatter # pylint: disable=import-error
60 |
61 | title_formatter = TitleFormatter()
62 | language_chooser = TranslationChooser("fin")
63 | httpclient = HttpClient(None)
64 | stream_filters = StreamFilters()
65 |
66 | url = f"https://areena.yle.fi/{program_id}"
67 |
68 | extractor = extractor_factory(url, stream_filters, language_chooser, httpclient)
69 | pid = extractor.program_id_from_url(url)
70 |
71 | info = extractor.program_info_for_pid(pid, url, title_formatter, None)
72 |
73 | return info.media_id.split("-")[-1]
74 |
75 |
76 | chromecasts, browser = pychromecast.get_listed_chromecasts(
77 | friendly_names=[args.cast], known_hosts=args.known_host
78 | )
79 | if not chromecasts:
80 | print(f'No chromecast with name "{args.cast}" discovered')
81 | sys.exit(1)
82 |
83 | cast = chromecasts[0]
84 | # Start socket client's worker thread and wait for initial status update
85 | cast.wait()
86 |
87 | yt = YleAreenaController()
88 | cast.register_handler(yt)
89 | yt.play_areena_media(
90 | get_kaltura_id(args.program),
91 | audio_language=args.audio_language,
92 | text_language=args.text_language,
93 | )
94 | sleep(10)
95 |
96 | # Shut down discovery
97 | browser.stop_discovery()
98 |
--------------------------------------------------------------------------------
/resources/pychromecast/examples/youtube_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example on how to use the YouTube Controller
3 |
4 | """
5 | # pylint: disable=invalid-name
6 |
7 | import argparse
8 | import logging
9 | import sys
10 | import zeroconf
11 |
12 | import pychromecast
13 | from pychromecast.controllers.youtube import YouTubeController
14 |
15 |
16 | # Change to the name of your Chromecast
17 | CAST_NAME = "Living Room TV"
18 |
19 | # Change to the video id of the YouTube video
20 | # video id is the last part of the url http://youtube.com/watch?v=video_id
21 | VIDEO_ID = "dQw4w9WgXcQ"
22 |
23 |
24 | parser = argparse.ArgumentParser(
25 | description="Example on how to use the Youtube Controller."
26 | )
27 | parser.add_argument(
28 | "--cast", help='Name of cast device (default: "%(default)s")', default=CAST_NAME
29 | )
30 | parser.add_argument(
31 | "--known-host",
32 | help="Add known host (IP), can be used multiple times",
33 | action="append",
34 | )
35 | parser.add_argument("--show-debug", help="Enable debug log", action="store_true")
36 | parser.add_argument(
37 | "--show-zeroconf-debug", help="Enable zeroconf debug log", action="store_true"
38 | )
39 | parser.add_argument(
40 | "--videoid", help='Youtube video ID (default: "%(default)s")', default=VIDEO_ID
41 | )
42 | args = parser.parse_args()
43 |
44 | if args.show_debug:
45 | logging.basicConfig(level=logging.DEBUG)
46 | if args.show_zeroconf_debug:
47 | print("Zeroconf version: " + zeroconf.__version__)
48 | logging.getLogger("zeroconf").setLevel(logging.DEBUG)
49 |
50 | chromecasts, browser = pychromecast.get_listed_chromecasts(
51 | friendly_names=[args.cast], known_hosts=args.known_host
52 | )
53 | if not chromecasts:
54 | print(f'No chromecast with name "{args.cast}" discovered')
55 | sys.exit(1)
56 |
57 | cast = chromecasts[0]
58 | # Start socket client's worker thread and wait for initial status update
59 | cast.wait()
60 |
61 | yt = YouTubeController()
62 | cast.register_handler(yt)
63 | yt.play_video(VIDEO_ID)
64 |
65 | # Shut down discovery
66 | browser.stop_discovery()
67 |
--------------------------------------------------------------------------------
/resources/pychromecast/fabfile.py:
--------------------------------------------------------------------------------
1 | import os
2 | from fabric.decorators import task
3 | from fabric.operations import local
4 |
5 |
6 | @task
7 | def build():
8 | """
9 | Builds the distribution files
10 | """
11 | if not os.path.exists("build"):
12 | os.mkdir("build")
13 | local("date >> build/log")
14 | local("python setup.py sdist >> build/log")
15 | local("python setup.py bdist_wheel >> build/log")
16 |
17 |
18 | @task
19 | def release():
20 | """
21 | Uploads files to PyPi to create a new release.
22 |
23 | Note: Requires that files have been built first
24 | """
25 | local("twine upload dist/*")
26 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/config.py:
--------------------------------------------------------------------------------
1 | """
2 | Data and methods to retrieve app specific configuration
3 | """
4 | import json
5 |
6 | import requests
7 |
8 | APP_BACKDROP = "E8C28D3C"
9 | APP_YOUTUBE = "233637DE"
10 | APP_MEDIA_RECEIVER = "CC1AD845"
11 | APP_PLEX = "06ee44ee-e7e3-4249-83b6-f5d0b6f07f34_1"
12 | APP_DASHCAST = "84912283"
13 | APP_SPOTIFY = "CC32E753"
14 | APP_HOMEASSISTANT_LOVELACE = "A078F6B0"
15 | APP_HOMEASSISTANT_MEDIA = "B45F4572"
16 | APP_SUPLA = "A41B766D"
17 | APP_YLEAREENA = "A9BCCB7C"
18 | APP_BUBBLEUPNP = "3927FA74"
19 | APP_BBCSOUNDS = "03977A48"
20 | APP_BBCIPLAYER = "5E81F6DB"
21 |
22 |
23 | def get_possible_app_ids():
24 | """Returns all possible app ids."""
25 |
26 | try:
27 | req = requests.get(
28 | "https://clients3.google.com/cast/chromecast/device/baseconfig"
29 | )
30 | data = json.loads(req.text[4:])
31 |
32 | return [app["app_id"] for app in data["applications"]] + data["enabled_app_ids"]
33 |
34 | except ValueError:
35 | # If json fails to parse
36 | return []
37 |
38 |
39 | def get_app_config(app_id):
40 | """Get specific configuration for 'app_id'."""
41 | try:
42 | req = requests.get(
43 | f"https://clients3.google.com/cast/chromecast/device/app?a={app_id}"
44 | )
45 |
46 | return json.loads(req.text[4:]) if req.status_code == 200 else {}
47 |
48 | except ValueError:
49 | # If json fails to parse
50 | return {}
51 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/const.py:
--------------------------------------------------------------------------------
1 | """
2 | Chromecast constants
3 | """
4 | # Regular chromecast, supports video/audio
5 | CAST_TYPE_CHROMECAST = "cast"
6 | # Cast Audio device, supports only audio
7 | CAST_TYPE_AUDIO = "audio"
8 | # Cast Audio group device, supports only audio
9 | CAST_TYPE_GROUP = "group"
10 |
11 | SERVICE_TYPE_HOST = "host"
12 | SERVICE_TYPE_MDNS = "mdns"
13 |
14 | MESSAGE_TYPE = "type"
15 | REQUEST_ID = "requestId"
16 | SESSION_ID = "sessionId"
17 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/controllers/bbciplayer.py:
--------------------------------------------------------------------------------
1 | """
2 | Controller to interface with BBC iPlayer.
3 | """
4 | # Note: Media ID is NOT the 8 digit alpha-numeric in the URL
5 | # it can be found by right clicking the playing video on the web interface
6 | # e.g. https://www.bbc.co.uk/iplayer/episode/b09w7fd9/bitz-bob-series-1-1-castle-makeover shows:
7 | # "2908kbps | dash (mf_cloudfront_dash_https)
8 | # b09w70r2 | 960x540"
9 |
10 | import logging
11 |
12 | from . import BaseController
13 | from ..config import APP_BBCIPLAYER
14 | from .media import STREAM_TYPE_BUFFERED, STREAM_TYPE_LIVE
15 |
16 | APP_NAMESPACE = "urn:x-cast:com.google.cast.media"
17 |
18 |
19 | class BbcIplayerController(BaseController):
20 | """Controller to interact with BBC iPlayer namespace."""
21 |
22 | def __init__(self):
23 | super().__init__(APP_NAMESPACE, APP_BBCIPLAYER)
24 |
25 | self.logger = logging.getLogger(__name__)
26 |
27 | def play_media(self, media_id, is_live=False, **kwargs):
28 | """Play BBC iPlayer media"""
29 | stream_type = STREAM_TYPE_LIVE if is_live else STREAM_TYPE_BUFFERED
30 | metadata = kwargs.get("metadata", {"metadataType": 0, "title": ""})
31 | subtitle = metadata.pop("subtitle", "")
32 |
33 | msg = {
34 | "media": {
35 | "contentId": media_id,
36 | "customData": {"secondary_title": subtitle},
37 | "metadata": metadata,
38 | "streamType": stream_type,
39 | },
40 | "type": "LOAD",
41 | }
42 | self.send_message(msg, inc_session_id=False)
43 |
44 | def quick_play(self, media_id=None, is_live=False, **kwargs):
45 | """Quick Play"""
46 | self.play_media(media_id, is_live=is_live, **kwargs)
47 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/controllers/bbcsounds.py:
--------------------------------------------------------------------------------
1 | """
2 | Controller to interface with BBC Sounds.
3 | """
4 | # Media ID can be found in the URL
5 | # e.g. https://www.bbc.co.uk/sounds/live:bbc_radio_one
6 |
7 | import logging
8 |
9 | from . import BaseController
10 | from ..config import APP_BBCSOUNDS
11 | from .media import STREAM_TYPE_BUFFERED, STREAM_TYPE_LIVE
12 |
13 | APP_NAMESPACE = "urn:x-cast:com.google.cast.media"
14 |
15 |
16 | class BbcSoundsController(BaseController):
17 | """Controller to interact with BBC Sounds namespace."""
18 |
19 | def __init__(self):
20 | super().__init__(APP_NAMESPACE, APP_BBCSOUNDS)
21 |
22 | self.logger = logging.getLogger(__name__)
23 |
24 | def play_media(self, media_id, is_live=False, **kwargs):
25 | """Play BBC Sounds media"""
26 | stream_type = STREAM_TYPE_LIVE if is_live else STREAM_TYPE_BUFFERED
27 | metadata_default = {"metadataType": 0, "title": ""}
28 |
29 | msg = {
30 | "media": {
31 | "contentId": media_id,
32 | "metadata": kwargs.get("metadata", metadata_default),
33 | "streamType": stream_type,
34 | },
35 | "type": "LOAD",
36 | }
37 |
38 | self.send_message(msg, inc_session_id=False)
39 |
40 | def quick_play(self, media_id=None, is_live=False, **kwargs):
41 | """Quick Play"""
42 | self.play_media(media_id, is_live=is_live, **kwargs)
43 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/controllers/bubbleupnp.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple Controller to use BubbleUPNP as a media controller.
3 | """
4 |
5 | from ..config import APP_BUBBLEUPNP
6 | from .media import MediaController
7 |
8 |
9 | class BubbleUPNPController(MediaController):
10 | """Controller to interact with BubbleUPNP app namespace."""
11 |
12 | def __init__(self):
13 | super().__init__()
14 | self.app_id = APP_BUBBLEUPNP
15 | self.supporting_app_id = APP_BUBBLEUPNP
16 |
17 | def quick_play(self, media_id=None, media_type="video/mp4", **kwargs):
18 | """Quick Play"""
19 | self.play_media(media_id, media_type, **kwargs)
20 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/controllers/dashcast.py:
--------------------------------------------------------------------------------
1 | """
2 | Controller to interface with the DashCast app namespace.
3 | """
4 | from ..config import APP_DASHCAST
5 | from . import BaseController
6 |
7 |
8 | APP_NAMESPACE = "urn:x-cast:com.madmod.dashcast"
9 |
10 |
11 | class DashCastController(BaseController):
12 | """Controller to interact with DashCast app namespace."""
13 |
14 | def __init__(self, appNamespace=APP_NAMESPACE, appId=APP_DASHCAST):
15 | super().__init__(appNamespace, appId)
16 |
17 | def receive_message(self, _message, _data: dict):
18 | """
19 | Called when a load complete message is received.
20 |
21 | This is currently un-used by this controller. It is implemented
22 | so that we don't get "Message unhandled" warnings. In the future
23 | it might be used to update a public status object like the media
24 | controller does.
25 | """
26 | # Indicate that the message was successfully handled.
27 | return True
28 |
29 | def load_url(self, url, force=False, reload_seconds=0, callback_function=None):
30 | """
31 | Starts loading a URL with an optional reload time
32 | in seconds.
33 |
34 | Setting force to True may load pages which block
35 | iframe embedding, but will prevent reload from
36 | working and will cause calls to load_url()
37 | to reload the app.
38 | """
39 |
40 | def launch_callback():
41 | """Loads requested URL after app launched."""
42 | should_reload = not force and reload_seconds not in (0, None)
43 | reload_milliseconds = 0 if not should_reload else reload_seconds * 1000
44 | msg = {
45 | "url": url,
46 | "force": force,
47 | "reload": should_reload,
48 | "reload_time": reload_milliseconds,
49 | }
50 |
51 | self.send_message(
52 | msg, inc_session_id=True, callback_function=callback_function
53 | )
54 |
55 | self.launch(callback_function=launch_callback)
56 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/controllers/homeassistant_media.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple Controller to use the Home Assistant Media Player Cast App as a media controller.
3 | """
4 |
5 | from ..config import APP_HOMEASSISTANT_MEDIA
6 | from .media import MediaController
7 |
8 |
9 | class HomeAssistantMediaController(MediaController):
10 | """Controller to interact with HomeAssistantMedia app namespace."""
11 |
12 | def __init__(self):
13 | super().__init__()
14 | self.app_id = APP_HOMEASSISTANT_MEDIA
15 | self.supporting_app_id = APP_HOMEASSISTANT_MEDIA
16 |
17 | def quick_play(self, media_id=None, media_type="video/mp4", **kwargs):
18 | """Quick Play"""
19 | self.play_media(media_id, media_type, **kwargs)
20 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/controllers/supla.py:
--------------------------------------------------------------------------------
1 | """
2 | Controller to interface with Supla.
3 | """
4 | import logging
5 |
6 | from . import BaseController
7 | from ..config import APP_SUPLA
8 |
9 | APP_NAMESPACE = "urn:x-cast:fi.ruutu.chromecast"
10 |
11 |
12 | # pylint: disable=too-many-instance-attributes
13 | class SuplaController(BaseController):
14 | """Controller to interact with Supla namespace."""
15 |
16 | def __init__(self):
17 | super().__init__(APP_NAMESPACE, APP_SUPLA)
18 |
19 | self.logger = logging.getLogger(__name__)
20 |
21 | def play_media(self, media_id, is_live=False):
22 | """
23 | Play Supla media
24 | """
25 | msg = {
26 | "type": "load",
27 | "mediaId": media_id,
28 | "currentTime": 0,
29 | "isLive": is_live,
30 | "isAtLiveMoment": False,
31 | "bookToken": "",
32 | "sample": True,
33 | "fw_site": "Supla",
34 | "Sanoma_adkv": "",
35 | "prerollAdsPlayed": True,
36 | "supla": True,
37 | "nextInSequenceList": 0,
38 | "playbackRate": 1,
39 | "env": "prod",
40 | }
41 | self.send_message(msg, inc_session_id=True)
42 |
43 | def quick_play(self, media_id=None, is_live=False, **kwargs):
44 | """Quick Play"""
45 | self.play_media(media_id, is_live=is_live, **kwargs)
46 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/controllers/yleareena.py:
--------------------------------------------------------------------------------
1 | """
2 | Controller to interface with the Yle Areena app namespace.
3 | """
4 |
5 | from ..config import APP_YLEAREENA
6 | from .media import MediaController, STREAM_TYPE_BUFFERED, TYPE_LOAD, MESSAGE_TYPE
7 |
8 |
9 | class YleAreenaController(MediaController):
10 | """Controller to interact with Yle Areena app namespace."""
11 |
12 | def __init__(self):
13 | super().__init__()
14 | self.app_id = APP_YLEAREENA
15 | self.supporting_app_id = APP_YLEAREENA
16 |
17 | def play_areena_media( # pylint: disable=too-many-locals
18 | self,
19 | kaltura_id,
20 | audio_language="",
21 | text_language="off",
22 | current_time=0,
23 | autoplay=True,
24 | stream_type=STREAM_TYPE_BUFFERED,
25 | ):
26 | """
27 | Play media with the entry id "kaltura_id".
28 | This value can be found by loading a page on Areena, e.g. https://areena.yle.fi/1-50097921
29 | And finding the kaltura player which has an id of yle-kaltura-player3430579305188-29-0_whwjqpry
30 | In this case the kaltura id is 0_whwjqpry
31 | """
32 | msg = {
33 | "media": {
34 | "streamType": stream_type,
35 | "customData": {
36 | "mediaInfo": {"entryId": kaltura_id},
37 | "audioLanguage": audio_language,
38 | "textLanguage": text_language,
39 | },
40 | },
41 | MESSAGE_TYPE: TYPE_LOAD,
42 | "currentTime": current_time,
43 | "autoplay": autoplay,
44 | "customData": {},
45 | "textTrackStyle": {
46 | "foregroundColor": "#FFFFFFFF",
47 | "backgroundColor": "#000000FF",
48 | "fontScale": 1,
49 | "fontFamily": "sans-serif",
50 | },
51 | }
52 |
53 | self.send_message(msg, inc_session_id=True)
54 |
55 | def quick_play(self, media_id=None, audio_lang="", text_lang="off", **kwargs):
56 | """Quick Play"""
57 | self.play_areena_media(
58 | media_id, audio_language=audio_lang, text_language=text_lang, **kwargs
59 | )
60 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/customcontrollers/netflix.py:
--------------------------------------------------------------------------------
1 | """
2 | Controller to interface with the Netflix namespace.
3 | """
4 | import logging
5 | import time
6 |
7 | from ..controllers BaseController
8 |
9 | APP_NAMESPACE = "urn:x-cast:mdx-netflix-com:service:target:2"
10 | APP_NETFLIX = "CA5E8412"
11 |
12 | # NOTE : not used, no public API provided by Netflix so no purpose to have this...
13 |
14 | # pylint: disable=too-many-instance-attributes
15 | class NetflixController(BaseController):
16 |
17 | # pylint: disable=useless-super-delegation
18 | # The pylint rule useless-super-delegation doesn't realize
19 | # we are setting default values here.
20 | def __init__(self):
21 | super(NetflixController, self).__init__(APP_NAMESPACE, APP_NETFLIX)
22 | self.logger = logging.getLogger(__name__)
23 | # pylint: enable=useless-super-delegation
24 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/error.py:
--------------------------------------------------------------------------------
1 | """
2 | Errors to be used by PyChromecast.
3 | """
4 |
5 |
6 | class PyChromecastError(Exception):
7 | """Base error for PyChromecast."""
8 |
9 |
10 | class NoChromecastFoundError(PyChromecastError):
11 | """
12 | When a command has to auto-discover a Chromecast and cannot find one.
13 | """
14 |
15 |
16 | class MultipleChromecastsFoundError(PyChromecastError):
17 | """
18 | When getting a singular chromecast results in getting multiple chromecasts.
19 | """
20 |
21 |
22 | class ChromecastConnectionError(PyChromecastError):
23 | """When a connection error occurs within PyChromecast."""
24 |
25 |
26 | class LaunchError(PyChromecastError):
27 | """When an app fails to launch."""
28 |
29 |
30 | class PyChromecastStopped(PyChromecastError):
31 | """Raised when a command is invoked while the Chromecast's socket_client
32 | is stopped.
33 |
34 | """
35 |
36 |
37 | class NotConnected(PyChromecastError):
38 | """
39 | Raised when a command is invoked while not connected to a Chromecast.
40 | """
41 |
42 |
43 | class UnsupportedNamespace(PyChromecastError):
44 | """
45 | Raised when trying to send a message with a namespace that is not
46 | supported by the current running app.
47 | """
48 |
49 |
50 | class ControllerNotRegistered(PyChromecastError):
51 | """
52 | Raised when trying to interact with a controller while it is
53 | not registered with a ChromeCast object.
54 | """
55 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/models.py:
--------------------------------------------------------------------------------
1 | """
2 | Chromecast types
3 | """
4 | import asyncio
5 | from collections import namedtuple
6 |
7 | import zeroconf
8 |
9 | ZEROCONF_ERRORS = (IOError, asyncio.TimeoutError)
10 | if hasattr(zeroconf, "EventLoopBlocked"):
11 | # Added in zeroconf 0.37.0
12 | ZEROCONF_ERRORS = (*ZEROCONF_ERRORS, zeroconf.EventLoopBlocked)
13 |
14 | CastInfo = namedtuple(
15 | "CastInfo",
16 | [
17 | "services",
18 | "uuid",
19 | "model_name",
20 | "friendly_name",
21 | "host",
22 | "port",
23 | "cast_type",
24 | "manufacturer",
25 | ],
26 | )
27 | ServiceInfo = namedtuple("ServiceInfo", ["type", "data"])
28 |
--------------------------------------------------------------------------------
/resources/pychromecast/pychromecast/quick_play.py:
--------------------------------------------------------------------------------
1 | """ Choose a controller and quick play """
2 |
3 | from .controllers.youtube import YouTubeController
4 | from .controllers.supla import SuplaController
5 | from .controllers.yleareena import YleAreenaController
6 | from .controllers.bubbleupnp import BubbleUPNPController
7 | from .controllers.bbciplayer import BbcIplayerController
8 | from .controllers.bbcsounds import BbcSoundsController
9 | from .controllers.homeassistant_media import HomeAssistantMediaController
10 |
11 |
12 | def quick_play(cast, app_name, data):
13 | """
14 | Given a Chromecast connection, launch the app `app_name` and start playing media
15 | based on parameters defined in `data`.
16 |
17 | :param cast: Chromecast connection to cast to
18 | :param app_name: App name "slug" to cast
19 | :param data: Data to send to the app controller. Must contain "media_id", and other
20 | values can be passed depending on the controller.
21 | :type cast: Chromecast
22 | :type app_name: string
23 | :type data: dict
24 |
25 | `data` can contain the following keys:
26 | media_id: string (Required)
27 | Primary identifier of the media
28 | media_type: string
29 | Type of the media identified by `media_id`. e.g. "program" if the media is a
30 | program name instead of a direct item id.
31 | When using a regular media controller (e.g. BubbleUPNP) this should be the
32 | content_type ('audio/mp3')
33 | enqueue: boolean
34 | Enqueue the media to the current playlist, if possible.
35 | index: string
36 | Play index x of matching media. "random" should also be allowed.
37 | audio_lang: string
38 | Audio language (3 characters for YleAreena)
39 | text_lang: string
40 | Subtitle language (3 characters for YleAreena)
41 |
42 | Youtube-specific:
43 | playlist_id: string
44 | Youtube playlist id
45 |
46 | Supla-specific:
47 | is_live: boolean
48 | Whether the media is a livestream
49 |
50 | Media controller (BubbleUPNP)-specific:
51 | stream_type: string
52 | "BUFFERED" or "LIVE"
53 | """
54 |
55 | if app_name == "youtube":
56 | controller = YouTubeController()
57 | elif app_name == "supla":
58 | controller = SuplaController()
59 | elif app_name == "yleareena":
60 | controller = YleAreenaController()
61 | elif app_name == "bubbleupnp":
62 | controller = BubbleUPNPController()
63 | elif app_name == "bbciplayer":
64 | controller = BbcIplayerController()
65 | elif app_name == "bbcsounds":
66 | controller = BbcSoundsController()
67 | elif app_name == "homeassistant_media":
68 | controller = HomeAssistantMediaController()
69 | else:
70 | raise NotImplementedError()
71 |
72 | cast.register_handler(controller)
73 |
74 | controller.quick_play(**data)
75 |
--------------------------------------------------------------------------------
/resources/pychromecast/pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 | ignore=cast_channel_pb2.py,authority_keys_pb2.py,logging_pb2.py
3 | reports=no
4 |
5 | disable=
6 | format,
7 | locally-disabled,
8 | too-few-public-methods,
9 | too-many-arguments,
10 | too-many-instance-attributes,
11 | too-many-public-methods,
12 | duplicate-code,
13 | too-many-nested-blocks,
14 |
15 | [EXCEPTIONS]
16 | overgeneral-exceptions=Exception,PyChromecastError
17 |
--------------------------------------------------------------------------------
/resources/pychromecast/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | exclude = 'pb2'
3 |
--------------------------------------------------------------------------------
/resources/pychromecast/requirements-test.txt:
--------------------------------------------------------------------------------
1 | flake8==4.0.1
2 | pylint==2.12.1
3 | black==21.11b1
4 |
--------------------------------------------------------------------------------
/resources/pychromecast/requirements.txt:
--------------------------------------------------------------------------------
1 | protobuf>=3.0.0
2 | zeroconf>=0.25.1
3 | casttube>=0.2.0
4 |
--------------------------------------------------------------------------------
/resources/pychromecast/setup.cfg:
--------------------------------------------------------------------------------
1 | [wheel]
2 | universal = 1
3 |
4 | [flake8]
5 | # To work with Black
6 | max-line-length = 88
7 | # E501: line too long
8 | # W503: Line break occurred before a binary operator
9 | # E203: Whitespace before ':'
10 | # D202 No blank lines allowed after function docstring
11 | ignore =
12 | E501,
13 | W503,
14 | E203,
15 | D202
16 |
--------------------------------------------------------------------------------
/resources/pychromecast/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 |
4 | long_description = open("README.rst").read()
5 |
6 | setup(
7 | name="PyChromecast",
8 | version="10.1.1",
9 | license="MIT",
10 | url="https://github.com/balloob/pychromecast",
11 | author="Paulus Schoutsen",
12 | author_email="paulus@paulusschoutsen.nl",
13 | description="Python module to talk to Google Chromecast.",
14 | long_description=long_description,
15 | packages=find_packages(),
16 | zip_safe=False,
17 | include_package_data=True,
18 | platforms="any",
19 | install_requires=list(val.strip() for val in open("requirements.txt")),
20 | classifiers=[
21 | "Intended Audience :: Developers",
22 | "License :: OSI Approved :: MIT License",
23 | "Operating System :: OS Independent",
24 | "Programming Language :: Python",
25 | "Programming Language :: Python :: 3",
26 | "Topic :: Software Development :: Libraries :: Python Modules",
27 | ],
28 | )
29 |
--------------------------------------------------------------------------------
/resources/pydub/AUTHORS:
--------------------------------------------------------------------------------
1 | James Robert
2 | github: jiaaro
3 | twitter: @jiaaro
4 | web: jiaaro.com
5 | email: pydub@jiaaro.com
6 |
7 | Marc Webbie
8 | github: marcwebbie
9 |
10 | Jean-philippe Serafin
11 | github: jeanphix
12 |
13 | Anurag Ramdasan
14 | github: AnuragRamdasan
15 |
16 | Choongmin Lee
17 | github: clee704
18 |
19 | Patrick Pittman
20 | github: ptpittman
21 |
22 | Hunter Lang
23 | github: hunterlang
24 |
25 | Alexey
26 | github: nihisil
27 |
28 | Jaymz Campbell
29 | github: jaymzcd
30 |
31 | Ross McFarland
32 | github: ross
33 |
34 | John McMellen
35 | github: jmcmellen
36 |
37 | Johan Lövgren
38 | github: dashj
39 |
40 | Joachim Krüger
41 | github: jkrgr
42 |
43 | Shichao An
44 | github: shichao-an
45 |
46 | Michael Bortnyck
47 | github: mbortnyck
48 |
49 | André Cloete
50 | github: aj-cloete
51 |
52 | David Acacio
53 | github: dacacioa
54 |
55 | Thiago Abdnur
56 | github: bolaum
57 |
58 | Aurélien Ooms
59 | github: aureooms
60 |
61 | Mike Mattozzi
62 | github: mmattozzi
63 |
64 | Marcio Mazza
65 | github: marciomazza
66 |
67 | Sungsu Lim
68 | github: proflim
69 |
70 | Evandro Myller
71 | github: emyller
72 |
73 | Sérgio Agostinho
74 | github: SergioRAgostinho
75 |
76 | Antonio Larrosa
77 | github: antlarr
78 |
79 | Aaron Craig
80 | github: craigthelinguist
81 |
82 | Carlos del Castillo
83 | github: greyalien502
84 |
85 | Yudong Sun
86 | github: sunjerry019
87 |
88 | Jorge Perianez
89 | github: JPery
90 |
91 | Chendi Luo
92 | github: Creonalia
93 |
94 | Daniel Lefevre
95 | gitHub: dplefevre
96 |
97 | Grzegorz Kotfis
98 | github: gkotfis
--------------------------------------------------------------------------------
/resources/pydub/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 James Robert, http://jiaaro.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/resources/pydub/__init__.py:
--------------------------------------------------------------------------------
1 | from .audio_segment import AudioSegment
--------------------------------------------------------------------------------
/resources/pydub/exceptions.py:
--------------------------------------------------------------------------------
1 | class PydubException(Exception):
2 | """
3 | Base class for any Pydub exception
4 | """
5 |
6 |
7 | class TooManyMissingFrames(PydubException):
8 | pass
9 |
10 |
11 | class InvalidDuration(PydubException):
12 | pass
13 |
14 |
15 | class InvalidTag(PydubException):
16 | pass
17 |
18 |
19 | class InvalidID3TagVersion(PydubException):
20 | pass
21 |
22 |
23 | class CouldntDecodeError(PydubException):
24 | pass
25 |
26 |
27 | class CouldntEncodeError(PydubException):
28 | pass
29 |
30 |
31 | class MissingAudioParameter(PydubException):
32 | pass
33 |
--------------------------------------------------------------------------------
/resources/pydub/logging_utils.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | """
4 | import logging
5 |
6 | converter_logger = logging.getLogger("pydub.converter")
7 |
8 | def log_conversion(conversion_command):
9 | converter_logger.debug("subprocess.call(%s)", repr(conversion_command))
10 |
11 | def log_subprocess_output(output):
12 | if output:
13 | for line in output.rstrip().splitlines():
14 | converter_logger.debug('subprocess output: %s', line.rstrip())
15 |
--------------------------------------------------------------------------------
/resources/pydub/playback.py:
--------------------------------------------------------------------------------
1 | """
2 | Support for playing AudioSegments. Pyaudio will be used if it's installed,
3 | otherwise will fallback to ffplay. Pyaudio is a *much* nicer solution, but
4 | is tricky to install. See my notes on installing pyaudio in a virtualenv (on
5 | OSX 10.10): https://gist.github.com/jiaaro/9767512210a1d80a8a0d
6 | """
7 |
8 | import subprocess
9 | from tempfile import NamedTemporaryFile
10 | from .utils import get_player_name, make_chunks
11 |
12 | def _play_with_ffplay(seg):
13 | PLAYER = get_player_name()
14 | with NamedTemporaryFile("w+b", suffix=".wav") as f:
15 | seg.export(f.name, "wav")
16 | subprocess.call([PLAYER, "-nodisp", "-autoexit", "-hide_banner", f.name])
17 |
18 |
19 | def _play_with_pyaudio(seg):
20 | import pyaudio
21 |
22 | p = pyaudio.PyAudio()
23 | stream = p.open(format=p.get_format_from_width(seg.sample_width),
24 | channels=seg.channels,
25 | rate=seg.frame_rate,
26 | output=True)
27 |
28 | # Just in case there were any exceptions/interrupts, we release the resource
29 | # So as not to raise OSError: Device Unavailable should play() be used again
30 | try:
31 | # break audio into half-second chunks (to allows keyboard interrupts)
32 | for chunk in make_chunks(seg, 500):
33 | stream.write(chunk._data)
34 | finally:
35 | stream.stop_stream()
36 | stream.close()
37 |
38 | p.terminate()
39 |
40 |
41 | def _play_with_simpleaudio(seg):
42 | import simpleaudio
43 | return simpleaudio.play_buffer(
44 | seg.raw_data,
45 | num_channels=seg.channels,
46 | bytes_per_sample=seg.sample_width,
47 | sample_rate=seg.frame_rate
48 | )
49 |
50 |
51 | def play(audio_segment):
52 | try:
53 | playback = _play_with_simpleaudio(audio_segment)
54 | try:
55 | playback.wait_done()
56 | except KeyboardInterrupt:
57 | playback.stop()
58 | except ImportError:
59 | pass
60 | else:
61 | return
62 |
63 | try:
64 | _play_with_pyaudio(audio_segment)
65 | return
66 | except ImportError:
67 | pass
68 | else:
69 | return
70 |
71 | _play_with_ffplay(audio_segment)
72 |
--------------------------------------------------------------------------------
/resources/requirements-nodep.txt:
--------------------------------------------------------------------------------
1 | androidviewclient==20.0.0b4
2 |
--------------------------------------------------------------------------------
/resources/requirements.txt:
--------------------------------------------------------------------------------
1 | setuptools
2 | requests>=2.21.0
3 | click
4 | bs4>=4.8.1
5 | six
6 | tqdm
7 | websocket-client
8 | protobuf>=3.11.0
9 | zeroconf==0.31.0
10 | casttube>=0.2.1
--------------------------------------------------------------------------------
/resources/spotipy/__init__.py:
--------------------------------------------------------------------------------
1 | from .cache_handler import * # noqa
2 | from .client import * # noqa
3 | from .exceptions import * # noqa
4 | from .oauth2 import * # noqa
5 | from .util import * # noqa
6 |
--------------------------------------------------------------------------------
/resources/spotipy/exceptions.py:
--------------------------------------------------------------------------------
1 | class SpotifyException(Exception):
2 |
3 | def __init__(self, http_status, code, msg, reason=None, headers=None):
4 | self.http_status = http_status
5 | self.code = code
6 | self.msg = msg
7 | self.reason = reason
8 | # `headers` is used to support `Retry-After` in the event of a
9 | # 429 status code.
10 | if headers is None:
11 | headers = {}
12 | self.headers = headers
13 |
14 | def __str__(self):
15 | return 'http status: {0}, code:{1} - {2}, reason: {3}'.format(
16 | self.http_status, self.code, self.msg, self.reason)
17 |
--------------------------------------------------------------------------------
/resources/spotipy/spotify_token.py:
--------------------------------------------------------------------------------
1 | """Utility module that helps get a webplayer access token"""
2 | import os
3 | import requests
4 | from bs4 import BeautifulSoup
5 | import json
6 |
7 | USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) \
8 | AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
9 |
10 |
11 | def start_session(dc=None, key=None):
12 | """ Starts session to get access token. """
13 |
14 | session = requests.Session()
15 |
16 | cookies = {'sp_dc': dc, 'sp_key': key}
17 | headers = {'user-agent': USER_AGENT}
18 |
19 | response = session.get("https://open.spotify.com/get_access_token?reason=transport&productType=web_player",
20 | headers=headers, cookies=cookies)
21 | response.raise_for_status()
22 | data = response.content.decode("utf-8")
23 | config = json.loads(data)
24 |
25 | access_token = config['accessToken']
26 | expires_timestamp = config['accessTokenExpirationTimestampMs']
27 | expiration_date = int(expires_timestamp) // 1000
28 | return access_token, expiration_date
29 |
--------------------------------------------------------------------------------
/resources/update.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pip3cmd=$(compgen -ac | grep -E '^pip-?3' | sort -r | head -1)
4 | if [[ -z $pip3cmd ]]; then # pip3 not found
5 | if python3 -m pip -V 2>&1 | grep -q -i "^pip " ; then # but try other way
6 | pip3cmd="python3 -m pip"
7 | fi
8 | fi
9 |
10 | if [[ ! -z $pip3cmd ]]; then # pip3 found
11 | echo "-- Updating requirements :"
12 | echo $(sudo $pip3cmd install -r $1/requirements.txt)
13 | # echo $(sudo $pip3cmd install -r $1/requirements-nodep.txt --no-deps)
14 | else
15 | echo "Error: Cound not found pip3 program to update python dependencies !"
16 | fi
17 |
18 | BASEDIR="$(dirname "$(dirname "$(readlink -fm "$0")")")"
19 |
20 | # make sure htaccess is created
21 | HTACCESS="$BASEDIR/.htaccess"
22 | if [[ ! -f "$HTACCESS" ]]; then # htaccess created
23 | echo "Options +FollowSymLinks\n" >> $HTACCESS
24 | chown www-data:www-data $HTACCESS
25 | chmod 644 $HTACCESS
26 | fi
27 |
28 | #### JEEDOM 4.2 MIGRATION
29 | # migrate media files from jeedom version prior to 4.2
30 | if [[ ! -z $BASEDIR ]]; then # basedir is not empty
31 |
32 | MIGRATION_SRC=$BASEDIR/localmedia
33 | MIGRATION_DEST=$BASEDIR/data/media
34 | if [[ -d "$MIGRATION_SRC" ]]; then
35 | cp -n $MIGRATION_SRC/* $MIGRATION_DEST
36 | rm -Rf $MIGRATION_SRC
37 | fi
38 | # clean old temp folder symlinkg for jeedom version prior to 4.2
39 | OLDTMPDIR=$BASEDIR/tmp
40 | if [[ -d "$OLDTMPDIR" ]]; then
41 | rm -f $OLDTMPDIR
42 | fi
43 |
44 | fi
45 |
46 |
--------------------------------------------------------------------------------
/tests/tools/.aspell.fr.pws:
--------------------------------------------------------------------------------
1 | personal_ws-1.1 fr 0 utf-8
2 | Etapes
3 | plateforme
4 | docs
5 | fr
6 | FR
7 | presentation.md
8 | https
9 | téléchargement
10 | prévisualisation
11 | xxxx
12 | FAQ
13 | releases
14 | release
15 | bold
16 | market
17 | desktop
18 | bug
19 | bugtracker
20 | bugs
21 | changelog
22 | config
23 | jeedom
24 | plugin
25 | screenshot
--------------------------------------------------------------------------------
/tests/tools/lintAllPythonFiles.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | for file in `find ./resources -name "*.py" ! -name "__init__.py"`;
3 | do
4 | echo "Check $file with pylint"
5 | python3 -m flake8 $file
6 | done
7 |
--------------------------------------------------------------------------------
/tests/tools/spellCheckMD.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | for file in *.md docs/fr_FR/*.md;
3 | do
4 | echo $files
5 | if [ $file = "docs/fr_FR/index-template.md" ] || [ $file = "docs/fr_FR/index.md" ]
6 | then
7 | echo "skip "$file
8 | else
9 | echo "process "$file
10 | cat $file | aspell --personal=./tests/tools/.aspell.fr.pws --lang=fr --encoding=utf-8 list;
11 | fi
12 | done
13 |
--------------------------------------------------------------------------------