├── .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 | ![Logo Jeedom](docs/assets/images/logo.png "Logo Jeedom") 8 | ![Logo plugin](docs/images/logoplugin.png "Logo plugin") 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 |
55 | 56 |
57 |
#name_display#
58 | 80 |
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 |
55 | 56 |
57 |
#name_display#
58 | 80 |
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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | getIsEnable()) ? '' : jeedom::getConfiguration('eqLogic:style:noactive'); 47 | $img = ''; 48 | echo ''; 49 | echo ''; 50 | echo ''; 51 | echo ''; 52 | echo ''; 53 | echo ''; 54 | echo ''; 55 | $onlinecmd = $eqLogic->getCmd('info', 'online'); 56 | $online = '{{Non}}'; 57 | if ($onlinecmd->execCmd() == 1) { 58 | $online = '{{Oui}}'; 59 | } 60 | echo ''; 61 | $busycmd = $eqLogic->getCmd('info', 'is_busy'); 62 | $busy = '{{Non}}'; 63 | if ($busycmd->execCmd() == 1) { 64 | $busy = '{{Oui}}'; 65 | } 66 | echo ''; 67 | echo ''; 68 | echo ''; 69 | echo ''; 70 | } 71 | ?> 72 | 73 |
{{Nom}}{{Nom diffusé}}{{UUID}}{{Modèle}}{{Type}}{{Online}}{{Occupé}}{{Dernière com}}{{Date création}}
' . $img . '' . $eqLogic->getHumanName(true) . '' . $eqLogic->getConfiguration('friendly_name') . '' . $eqLogic->getLogicalId() . '' . $eqLogic->getConfiguration('model_name') . '' . $eqLogic->getConfiguration('cast_type') . '' . $online . '' . $busy . '' . $eqLogic->getStatus('lastCommunication') . '' . $eqLogic->getConfiguration('createtime') . '
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 |
38 | 39 |

Oups ! Pas le bon lien.Erreur 404

40 |

Vous allez être redirigé automatiquement dans quelques instants

41 |
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 | ![gctts_gcp1](../images/gcloudtts/gctts_gcp1.png "gctts_gcp1") 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 | ![gctts_api1](../images/gcloudtts/gctts_api1.png "gctts_api1") 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 | ![gctts_key1](../images/gcloudtts/gctts_key1.png "gctts_key1") 29 | 30 | - Selectionner le Clé API dans le type d'identifiant à créer 31 | ![gctts_key2](../images/gcloudtts/gctts_key2.png "gctts_key2") 32 | 33 | - Récupérer la clé API via copier/coller 34 | ![gctts_key2](../images/gcloudtts/gctts_key3.png "gctts_key2") 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 | --------------------------------------------------------------------------------