├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── addon.xml ├── changelog.txt └── resources ├── __init__.py ├── fanart.jpg ├── icon.png ├── language ├── resource.language.de_de │ └── strings.po ├── resource.language.en_gb │ └── strings.po ├── resource.language.fr_fr │ └── strings.po └── resource.language.it_it │ └── strings.po ├── lib ├── __init__.py ├── addon.py ├── api.py ├── hof.py ├── mapper.py ├── settings.py ├── utils.py └── view.py └── settings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # ignore files 6 | *.ignore 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | # lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # PyCharm 63 | .idea/ 64 | 65 | # Visual Studio Code 66 | .vscode/ 67 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 4 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 1.1.2 2 | 3 | - better date / locale handling & prevent crash when http error 4 | 5 | # Version 1.1.1 6 | 7 | - minor python 3 fixes & code improvements (from Kodi Travis CI) 8 | 9 | # Version 1.1.0 10 | 11 | - API fixes 12 | - Added add-on option to select video stream (language, subtitles...) 13 | 14 | # Version 1.0.2 15 | 16 | - weekly browse 17 | - bugfix (settings parsing #54) 18 | 19 | # Version 1.0.1 20 | 21 | Major encoding fix & html entities decode 22 | 23 | # Version 1.0.0 24 | 25 | Major rewrite. 26 | 27 | A lot of new features, feedback welcome. 28 | 29 | # Version 0.5.8 30 | 31 | thanks to https://github.com/cifera 32 | 33 | - Can now use new api see issue #35, 34 | - Now gets all videos for last 7 days (can take a while) 35 | - Most other functions removed 36 | 37 | # Version 0.5.4 38 | 39 | - updated dependency version 40 | - changed live stream to m3u8 to get around RTMP scaling issue 41 | - playing an item should no longer change the listitem label 42 | 43 | # Version 0.5.3 44 | 45 | - cosmetic improvements (inspired by http://www.kodinerds.net/index.php/Thread/48300-Release-Arte-tv-v3/) 46 | 47 | # Version 0.5.2 48 | 49 | - encoding fix (download notification) 50 | 51 | # Version 0.5.1 52 | 53 | - fixed download when RTMP playback is selected 54 | - fixed german translation (thanks to https://github.com/hstraub) 55 | - Added ability to browse by date 56 | - Added queuing system for videos via context menu 57 | 58 | # Version 0.5.0 59 | 60 | - Switched to a new API 61 | - Strings updated to match the new ARTE categories 62 | 63 | # Version 0.4.0 64 | 65 | - Fixed download procedure (got rid of simpledownloader for a more shitty solution) 66 | - Added download quality setting 67 | - Added very low quality setting (may not be available for every video \* fallbacks on highest) 68 | - You can choose to always play videos in subtitled original version rather than dubbed if available 69 | 70 | # Version 0.3.0 71 | 72 | - Added ability to browse by theme (thanks to https://github.com/zerty) 73 | 74 | # Version 0.2.0 75 | 76 | - Fixed quality setting 77 | - Added ability to download videos 78 | 79 | # Version 0.1.0 80 | 81 | Initial version 82 | 83 | - Choose between french and german language for videos 84 | - Choose preferred video quality (High, Medium, Low) 85 | - Choose between HTTP and RTMP protocol 86 | - Stream from New, Selection, Most Viewed and Last Chance categories 87 | - Stream live TV 88 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plugin.video.arteplussept 2 | 3 | Plugin Kodi (ex XBMC) permettant de voir les vidéos disponibles sur Arte +7 4 | 5 | For feature requests or reporting issues go [here](https://github.com/known-as-bmf/plugin.video.arteplussept/issues). 6 | 7 | Contributions are welcome ! 8 | 9 | # Installation 10 | 11 | You can either : 12 | 13 | 1. Install a stable version directly from the official Kodi repositories (gotham or helix) 14 | 2. Install the latest dev version by reading the "Manual installation" chapter 15 | 16 | 17 | # Manual installation 18 | 19 | Download the plugin [here](https://github.com/known-as-bmf/plugin.video.arteplussept/archive/master.zip) 20 | 21 | Then follow the steps bellow depending on your system and software version 22 | 23 | ##1. Open the addons folder 24 | 25 | ### Windows 26 | 27 | * For Kodi : Press `Windows + R` and type in `%APPDATA%\kodi\addons` 28 | * For XBMC : Press `Windows + R` and type in `%APPDATA%\XBMC\addons` 29 | 30 | ### Linux 31 | 32 | * For Kodi : Open the `~/.kodi/addons` folder 33 | * For XBMC : Open the `~/.xbmc/addons` folder 34 | 35 | ### OSX 36 | 37 | * For Kodi : Open the `/Users//Library/Application Support/Kodi/addons` folder 38 | * For XBMC : Open the `/Users//Library/Application Support/XBMC/addons` folder 39 | 40 | ##2. Install the add-on 41 | 42 | * Extract the content of the zip in the `addons` folder 43 | * Rename the extracted directory from `plugin.video.arteplussept-master` to `plugin.video.arteplussept` 44 | * Done ! The plugin should show up in your video add-ons section. 45 | 46 | # Troubleshooting 47 | 48 | If you are having issues with the add-on, you can open a issue ticket and join your log file. The log file will contain your system user name and sometimes passwords of services you use in the software, so you may want to sanitize it beforehand. Detailed procedure [here](http://kodi.wiki/view/Log_file/Easy). 49 | 50 | You should also try installing the dependancies manually via Kodi / XBMC. The dependancies are : 51 | 52 | * xbmcswift2 (script.module.xbmcswift2) 53 | * requests (script.module.requests) 54 | 55 | They should be in the "addon libraries" section of the official repository. 56 | -------------------------------------------------------------------------------- /addon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | video 11 | 12 | 13 | all 14 | Arte +7 15 | fr de en it 16 | 17 | Regardez les vidéos de Arte +7 18 | Pour toute demande ou reporter un problème: 19 | https://github.com/known-as-bmf/plugin.video.arteplussept/issues 20 | Les contributions sont les bienvenues: 21 | https://github.com/known-as-bmf/plugin.video.arteplussept 22 | 23 | 24 | Sehen Sie Videos von Arte +7 25 | Für Fehlerberichte und Verbesserungsvorschläge: 26 | https://github.com/known-as-bmf/plugin.video.arteplussept/issues 27 | Mithilfe ist willkommen: 28 | https://github.com/known-as-bmf/plugin.video.arteplussept 29 | 30 | 31 | Watch videos from Arte +7 32 | For feature requests / issues: 33 | https://github.com/known-as-bmf/plugin.video.arteplussept/issues 34 | Contributions are welcome: 35 | https://github.com/known-as-bmf/plugin.video.arteplussept 36 | 37 | 38 | Guarda video da Arte +7 39 | Per richiedere nuove funzionalità o segnalare problemi: 40 | https://github.com/known-as-bmf/plugin.video.arteplussept/issues 41 | Nuovi contributi sono ben accetti: 42 | https://github.com/known-as-bmf/plugin.video.arteplussept 43 | 44 | MIT 45 | https://github.com/known-as-bmf/plugin.video.arteplussept 46 | 48 | https://www.arte.tv/fr/ 49 | 50 | resources/icon.png 51 | resources/fanart.jpg 52 | 53 | 54 | v1.1.2 55 | - better date / locale handling & prevent crash when http error 56 | v1.1.1 57 | - minor python 3 fixes & code improvements (from Kodi Travis CI) 58 | v1.1.0 59 | - API fixes 60 | - Added add-on option to select video stream (language, subtitles...) 61 | v1.0.2 62 | - weekly browse 63 | - bugfix (settings parsing #54) 64 | v1.0.1 65 | - major bug hotfix 66 | v1.0.0 67 | - brand new version 68 | - support for new arte api 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | v1.1.2 2 | - better date / locale handling & prevent crash when http error 3 | 4 | v1.1.1 5 | - minor python 3 fixes & code improvements (from Kodi Travis CI) 6 | 7 | v1.1.0 8 | - API fixes 9 | - Added add-on option to select video stream (language, subtitles...) 10 | 11 | v1.0.2 12 | - weekly browse 13 | - bugfix (settings parsing #54) 14 | 15 | v1.0.1 16 | - Major encoding fix & html entities decode 17 | 18 | v1.0.0 19 | - Major rewrite. 20 | - A lot of new features, feedback welcome. 21 | 22 | v0.5.8 23 | - Can now use new api see issue #35, 24 | - Now gets all videos for last 7 days (can take a while) 25 | - Most other functions removed 26 | 27 | v0.5.4 28 | - updated dependency version 29 | - changed live stream to m3u8 to get around RTMP scaling issue 30 | - playing an item should no longer change the listitem label 31 | 32 | v0.5.3 33 | - cosmetic improvements (inspired by http://www.kodinerds.net/index.php/Thread/48300-Release-Arte-tv-v3/) 34 | 35 | v0.5.2 36 | - encoding fix (download notification) 37 | 38 | v0.5.1 39 | - fixed download when RTMP playback is selected 40 | - fixed german translation (thanks to https://github.com/hstraub) 41 | - Added ability to browse by date 42 | - Added queuing system for videos via context menu 43 | 44 | v0.5.0 45 | - Switched to a new API 46 | - Strings updated to match the new ARTE categories 47 | 48 | v0.4.0 49 | - Fixed download procedure (got rid of simpledownloader for a more shitty solution) 50 | - Added download quality setting 51 | - Added very low quality setting (may not be available for every video - fallbacks on highest) 52 | - You can choose to always play videos in subtitled original version rather than dubbed if available 53 | 54 | v0.3.0 55 | - Added ability to browse by theme (thanks to https://github.com/zerty) 56 | 57 | v0.2.0 58 | - Fixed quality setting 59 | - Added ability to download videos 60 | 61 | v0.1.0 62 | - Initial version 63 | - Choose between french and german language for videos 64 | - Choose preferred video quality (High, Medium, Low) 65 | - Choose between HTTP and RTMP protocol 66 | - Stream from New, Selection, Most Viewed and Last Chance categories 67 | - Stream live TV 68 | -------------------------------------------------------------------------------- /resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/known-as-bmf/plugin.video.arteplussept/bb3e82a7b66b4a1494e5c138a0297289d9cb1c00/resources/__init__.py -------------------------------------------------------------------------------- /resources/fanart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/known-as-bmf/plugin.video.arteplussept/bb3e82a7b66b4a1494e5c138a0297289d9cb1c00/resources/fanart.jpg -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/known-as-bmf/plugin.video.arteplussept/bb3e82a7b66b4a1494e5c138a0297289d9cb1c00/resources/icon.png -------------------------------------------------------------------------------- /resources/language/resource.language.de_de/strings.po: -------------------------------------------------------------------------------- 1 | # Kodi Media Center language file 2 | # Addon Name: Arte +7 3 | # Addon id: plugin.video.arteplussept 4 | # Addon Provider: bmf 5 | msgid "" 6 | msgstr "" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "Language: de\n" 11 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 12 | 13 | msgctxt "#30050" 14 | msgid "Language (Videos)" 15 | msgstr "Sprache (Videos)" 16 | 17 | msgctxt "#30052" 18 | msgid "Stream video quality" 19 | msgstr "Qualität des Videostreams" 20 | 21 | msgctxt "#30053" 22 | msgid "Show video stream options" 23 | msgstr "Show video stream options" 24 | 25 | msgctxt "#30005" 26 | msgid "Most recent" 27 | msgstr "Neuste" 28 | 29 | msgctxt "#30006" 30 | msgid "Most viewed" 31 | msgstr "Am meisten gesehen" 32 | 33 | msgctxt "#30007" 34 | msgid "Last chance" 35 | msgstr "Letzte chance" 36 | 37 | msgctxt "#30008" 38 | msgid "Magazines A-Z" 39 | msgstr "Sendungen A-Z" 40 | 41 | msgctxt "#30009" 42 | msgid "This week" 43 | msgstr "Diese Woche" 44 | -------------------------------------------------------------------------------- /resources/language/resource.language.en_gb/strings.po: -------------------------------------------------------------------------------- 1 | # Kodi Media Center language file 2 | # Addon Name: Arte +7 3 | # Addon id: plugin.video.arteplussept 4 | # Addon Provider: bmf 5 | msgid "" 6 | msgstr "" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "Language: en\n" 11 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 12 | 13 | msgctxt "#30050" 14 | msgid "Language (Videos)" 15 | msgstr "" 16 | 17 | msgctxt "#30052" 18 | msgid "Stream video quality" 19 | msgstr "" 20 | 21 | msgctxt "#30053" 22 | msgid "Show video stream options" 23 | msgstr "" 24 | 25 | msgctxt "#30005" 26 | msgid "Most recent" 27 | msgstr "" 28 | 29 | msgctxt "#30006" 30 | msgid "Most viewed" 31 | msgstr "" 32 | 33 | msgctxt "#30007" 34 | msgid "Last chance" 35 | msgstr "" 36 | 37 | msgctxt "#30008" 38 | msgid "Magazines A-Z" 39 | msgstr "" 40 | 41 | msgctxt "#30009" 42 | msgid "This week" 43 | msgstr "" 44 | -------------------------------------------------------------------------------- /resources/language/resource.language.fr_fr/strings.po: -------------------------------------------------------------------------------- 1 | # Kodi Media Center language file 2 | # Addon Name: Arte +7 3 | # Addon id: plugin.video.arteplussept 4 | # Addon Provider: bmf 5 | msgid "" 6 | msgstr "" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "Language: fr\n" 11 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 12 | 13 | msgctxt "#30050" 14 | msgid "Language (Videos)" 15 | msgstr "Langage (Vidéos)" 16 | 17 | msgctxt "#30052" 18 | msgid "Stream video quality" 19 | msgstr "Qualité vidéo préferée" 20 | 21 | msgctxt "#30053" 22 | msgid "Show video stream options" 23 | msgstr "Afficher les options de flux vidéo" 24 | 25 | msgctxt "#30005" 26 | msgid "Most recent" 27 | msgstr "Les plus récentes" 28 | 29 | msgctxt "#30006" 30 | msgid "Most viewed" 31 | msgstr "Les plus vues" 32 | 33 | msgctxt "#30007" 34 | msgid "Last chance" 35 | msgstr "Dernière chance" 36 | 37 | msgctxt "#30008" 38 | msgid "Magazines A-Z" 39 | msgstr "Émissions A-Z" 40 | 41 | msgctxt "#30009" 42 | msgid "This week" 43 | msgstr "Cette semaine" 44 | -------------------------------------------------------------------------------- /resources/language/resource.language.it_it/strings.po: -------------------------------------------------------------------------------- 1 | # Kodi Media Center language file 2 | # Addon Name: Arte +7 3 | # Addon id: plugin.video.arteplussept 4 | # Addon Provider: bmf 5 | msgid "" 6 | msgstr "" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "Language: it\n" 11 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 12 | 13 | msgctxt "#30050" 14 | msgid "Language (Videos)" 15 | msgstr "Lingua (Video)" 16 | 17 | msgctxt "#30052" 18 | msgid "Stream video quality" 19 | msgstr "Qualità video" 20 | 21 | msgctxt "#30053" 22 | msgid "Show video stream options" 23 | msgstr "Show video stream options" 24 | 25 | msgctxt "#30005" 26 | msgid "Most recent" 27 | msgstr "I più recenti" 28 | 29 | msgctxt "#30006" 30 | msgid "Most viewed" 31 | msgstr "I più visti" 32 | 33 | msgctxt "#30007" 34 | msgid "Last chance" 35 | msgstr "Ultima opportunità!" 36 | 37 | msgctxt "#30008" 38 | msgid "Magazines A-Z" 39 | msgstr "Spettacoli A-Z" 40 | 41 | msgctxt "#30009" 42 | msgid "This week" 43 | msgstr "Questa settimana" 44 | -------------------------------------------------------------------------------- /resources/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/known-as-bmf/plugin.video.arteplussept/bb3e82a7b66b4a1494e5c138a0297289d9cb1c00/resources/lib/__init__.py -------------------------------------------------------------------------------- /resources/lib/addon.py: -------------------------------------------------------------------------------- 1 | 2 | # coding=utf-8 3 | # -*- coding: utf-8 -*- 4 | # 5 | # plugin.video.arteplussept, Kodi add-on to watch videos from http://www.arte.tv/guide/fr/plus7/ 6 | # Copyright (C) 2015 known-as-bmf 7 | # 8 | # This program is free software; you can redistribute it and/or 9 | # modify it under the terms of the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 11 | # of the License, or (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | # 22 | from xbmcswift2 import Plugin 23 | 24 | 25 | # global declarations 26 | # plugin stuff 27 | plugin = Plugin() 28 | 29 | 30 | class PluginInformation: 31 | name = plugin.name 32 | version = plugin.addon.getAddonInfo('version') 33 | 34 | 35 | # my imports 36 | import view 37 | from settings import Settings 38 | 39 | settings = Settings(plugin) 40 | 41 | 42 | @plugin.route('/', name='index') 43 | def index(): 44 | return view.build_categories(settings) 45 | 46 | 47 | @plugin.route('/category/', name='category') 48 | def category(category_code): 49 | return view.build_category(category_code, settings) 50 | 51 | 52 | # @plugin.route('/creative', name='creative') 53 | # def creative(): 54 | # return [] 55 | 56 | 57 | @plugin.route('/magazines', name='magazines') 58 | def magazines(): 59 | plugin.set_content('tvshows') 60 | return plugin.finish(view.build_magazines(settings)) 61 | 62 | 63 | @plugin.route('/newest', name='newest') 64 | def newest(): 65 | plugin.set_content('tvshows') 66 | return plugin.finish(view.build_newest(settings)) 67 | 68 | 69 | @plugin.route('/most_viewed', name='most_viewed') 70 | def most_viewed(): 71 | plugin.set_content('tvshows') 72 | return plugin.finish(view.build_most_viewed(settings)) 73 | 74 | 75 | @plugin.route('/last_chance', name='last_chance') 76 | def last_chance(): 77 | plugin.set_content('tvshows') 78 | return plugin.finish(view.build_last_chance(settings)) 79 | 80 | 81 | @plugin.route('/sub_category/', name='sub_category_by_code') 82 | def sub_category_by_code(sub_category_code): 83 | plugin.set_content('tvshows') 84 | return plugin.finish(view.build_sub_category_by_code(sub_category_code, settings)) 85 | 86 | 87 | @plugin.route('/sub_category//', name='sub_category_by_title') 88 | def sub_category_by_title(category_code, sub_category_title): 89 | plugin.set_content('tvshows') 90 | return plugin.finish(view.build_sub_category_by_title(category_code, sub_category_title, settings)) 91 | 92 | 93 | @plugin.route('/collection//', name='collection') 94 | def collection(kind, collection_id): 95 | plugin.set_content('tvshows') 96 | return plugin.finish(view.build_mixed_collection(kind, collection_id, settings)) 97 | 98 | 99 | @plugin.route('/streams/', name='streams') 100 | def streams(program_id): 101 | plugin.set_content('tvshows') 102 | return plugin.finish(view.build_video_streams(program_id, settings)) 103 | 104 | 105 | @plugin.route('/play//', name='play') 106 | @plugin.route('/play///', name='play_specific') 107 | def play(kind, program_id, audio_slot='1'): 108 | return plugin.set_resolved_url(view.build_stream_url(kind, program_id, int(audio_slot), settings)) 109 | 110 | 111 | @plugin.route('/weekly', name='weekly') 112 | def weekly(): 113 | plugin.set_content('tvshows') 114 | return plugin.finish(view.build_weekly(settings)) 115 | 116 | 117 | # @plugin.route('/broadcast', name='broadcast') 118 | # def broadcast(): 119 | # plugin.set_content('tvshows') 120 | # items = custom.map_broadcast_item( 121 | # custom.past_week_programs(language.get('short', 'fr'))) 122 | # return plugin.finish(items) 123 | 124 | 125 | # plugin bootstrap 126 | if __name__ == '__main__': 127 | plugin.run() 128 | -------------------------------------------------------------------------------- /resources/lib/api.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | import requests 3 | 4 | import hof 5 | 6 | from addon import PluginInformation 7 | 8 | _base_api_url = 'http://www.arte.tv/hbbtvv2/services/web/index.php' 9 | _base_headers = { 10 | 'user-agent': PluginInformation.name + '/' + PluginInformation.version 11 | } 12 | _endpoints = { 13 | 'categories': '/EMAC/teasers/{type}/v2/{lang}', 14 | 'category': '/EMAC/teasers/category/v2/{category_code}/{lang}', 15 | 'subcategory': '/OPA/v3/videos/subcategory/{sub_category_code}/page/1/limit/100/{lang}', 16 | 'magazines': '/OPA/v3/magazines/{lang}', 17 | 'collection': '/EMAC/teasers/collection/v2/{collection_id}/{lang}', 18 | # program details 19 | 'video': '/OPA/v3/videos/{program_id}/{lang}', 20 | # program streams 21 | 'streams': '/OPA/v3/streams/{program_id}/{kind}/{lang}', 22 | 'daily': '/OPA/v3/programs/{date}/{lang}' 23 | } 24 | 25 | 26 | def categories(lang): 27 | url = _endpoints['categories'].format(type='categories', lang=lang) 28 | return _load_json(url).get('categories', {}) 29 | 30 | 31 | def home_category(name, lang): 32 | url = _endpoints['categories'].format(type='home', lang=lang) 33 | return _load_json(url).get('teasers', {}).get(name, []) 34 | 35 | 36 | def magazines(lang): 37 | url = _endpoints['magazines'].format(lang=lang) 38 | return _load_json(url).get('magazines', {}) 39 | 40 | 41 | def category(category_code, lang): 42 | url = _endpoints['category'].format(category_code=category_code, lang=lang) 43 | return _load_json(url).get('category', {}) 44 | 45 | 46 | def subcategory(sub_category_code, lang): 47 | url = _endpoints['subcategory'].format( 48 | sub_category_code=sub_category_code, lang=lang) 49 | return _load_json(url).get('videos', {}) 50 | 51 | 52 | def collection(kind, collection_id, lang): 53 | url = _endpoints['collection'].format(kind=kind, 54 | collection_id=collection_id, lang=lang) 55 | subCollections = _load_json(url).get('subCollections', []) 56 | return hof.flat_map(lambda subCollections: subCollections.get('videos', []), subCollections) 57 | 58 | 59 | def video(program_id, lang): 60 | url = _endpoints['video'] .format( 61 | program_id=program_id, lang=lang) 62 | return _load_json(url).get('videos', [])[0] 63 | 64 | 65 | def streams(kind, program_id, lang): 66 | url = _endpoints['streams'] .format( 67 | kind=kind, program_id=program_id, lang=lang) 68 | return _load_json(url).get('videoStreams', []) 69 | 70 | 71 | def daily(date, lang): 72 | url = _endpoints['daily'].format(date=date, lang=lang) 73 | return _load_json(url).get('programs', []) 74 | 75 | 76 | def _load_json(url, params=None, headers=_base_headers): 77 | try: 78 | r = requests.get(_base_api_url + url, params=params, headers=headers) 79 | return r.json(object_pairs_hook=OrderedDict) 80 | except ValueError: # JSON decode failure (empty response ?) 81 | return {} 82 | -------------------------------------------------------------------------------- /resources/lib/hof.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | def find(findFn, l): 4 | """ 5 | Will return the first item matching the findFn 6 | findFn: A function taking one param: value. MUST return a boolean 7 | l: The list to search 8 | """ 9 | for item in l: 10 | if findFn(item): 11 | return item 12 | return None 13 | 14 | 15 | def find_dict(findFn, d): 16 | """ 17 | Will return the first value matching the findFn 18 | findFn: A function taking two params: value, key. MUST return a boolean 19 | d: The dict to search 20 | """ 21 | for k, v in d.items(): 22 | if findFn(v, k): 23 | return v 24 | return None 25 | 26 | 27 | def map_dict(mapFn, d): 28 | """ 29 | mapFn: A function taking two params: value, key 30 | d: The dict to map 31 | """ 32 | return {k: mapFn(v, k) for k, v in d.items()} 33 | 34 | 35 | def filter_dict(filterFn, d): 36 | """ 37 | filterFn: A function taking two params: value, key. MUST return a boolean 38 | d: The dict to filter 39 | """ 40 | return {k: v for k, v in d.items() if filterFn(v, k)} 41 | 42 | 43 | def reject_dict(filterFn, d): 44 | """ 45 | filterFn: A function taking two params: value, key. MUST return a boolean 46 | d: The dict to filter 47 | """ 48 | def invert(*args, **kwargs): 49 | return not filterFn(*args, **kwargs) 50 | return filter_dict(invert, d) 51 | 52 | 53 | def get_property(d, path, default=None): 54 | def walk(sub_d, segment): 55 | if sub_d is None: 56 | return None 57 | return sub_d.get(segment) 58 | segments = path.split('.') 59 | return functools.reduce(walk, segments, d) or default 60 | 61 | 62 | def merge_dicts(*args): 63 | result = {} 64 | for d in args: 65 | result.update(d) 66 | return result 67 | 68 | 69 | def flatten(l): 70 | return [item for sublist in l for item in sublist] 71 | 72 | 73 | def flat_map(f, lst): 74 | return [item for list_item in lst for item in f(list_item)] 75 | -------------------------------------------------------------------------------- /resources/lib/mapper.py: -------------------------------------------------------------------------------- 1 | from addon import plugin 2 | 3 | import hof 4 | import utils 5 | 6 | 7 | def map_categories_item(item): 8 | return { 9 | 'label': utils.colorize(item.get('title'), item.get('color')), 10 | 'path': plugin.url_for('category', category_code=item.get('code')) 11 | } 12 | 13 | 14 | # def create_creative_item(): 15 | # return { 16 | # 'label': 'Creative I18N', 17 | # 'path': plugin.url_for('creative') 18 | # } 19 | 20 | 21 | def create_magazines_item(): 22 | return { 23 | 'label': plugin.addon.getLocalizedString(30008), 24 | 'path': plugin.url_for('magazines') 25 | } 26 | 27 | 28 | def create_week_item(): 29 | return { 30 | 'label': plugin.addon.getLocalizedString(30009), 31 | 'path': plugin.url_for('weekly') 32 | } 33 | 34 | 35 | def create_newest_item(): 36 | return { 37 | 'label': plugin.addon.getLocalizedString(30005), 38 | 'path': plugin.url_for('newest') 39 | } 40 | 41 | 42 | def create_most_viewed_item(): 43 | return { 44 | 'label': plugin.addon.getLocalizedString(30006), 45 | 'path': plugin.url_for('most_viewed') 46 | } 47 | 48 | 49 | def create_last_chance_item(): 50 | return { 51 | 'label': plugin.addon.getLocalizedString(30007), 52 | 'path': plugin.url_for('last_chance') 53 | } 54 | 55 | 56 | def map_category_item(item, category_code): 57 | # code = item.get('code') 58 | title = item.get('title') 59 | 60 | # if code: 61 | # path = plugin.url_for('sub_category_by_code', 62 | # category_code=category_code, sub_category_code=code) 63 | # else: 64 | path = plugin.url_for('sub_category_by_title', 65 | category_code=category_code, sub_category_title=utils.encode_string(title)) 66 | 67 | return { 68 | 'label': title, 69 | 'path': path 70 | } 71 | 72 | 73 | def map_generic_item(item, show_video_streams): 74 | programId = item.get('programId') 75 | 76 | is_playlist = programId.startswith('RC-') or programId.startswith('PL-') 77 | if not is_playlist: 78 | return map_video(item, show_video_streams) 79 | else: 80 | return map_playlist(item) 81 | 82 | 83 | def map_video(item, show_video_streams): 84 | programId = item.get('programId') 85 | kind = item.get('kind') 86 | duration = int(item.get('duration') or 0) * \ 87 | 60 or item.get('durationSeconds') 88 | airdate = item.get('broadcastBegin') 89 | if airdate is not None: 90 | airdate = str(utils.parse_date(airdate)) 91 | 92 | return { 93 | 'label': utils.format_title_and_subtitle(item.get('title'), item.get('subtitle')), 94 | 'path': plugin.url_for('streams', program_id=programId) if show_video_streams else plugin.url_for('play', kind=kind, program_id=programId), 95 | 'thumbnail': item.get('imageUrl'), 96 | 'is_playable': not show_video_streams, 97 | 'info_type': 'video', 98 | 'info': { 99 | 'title': item.get('title'), 100 | 'duration': duration, 101 | 'genre': item.get('genrePresse'), 102 | 'plot': item.get('shortDescription') or item.get('fullDescription'), 103 | 'plotoutline': item.get('teaserText'), 104 | # year is not correctly used by kodi :( 105 | # the aired year will be used by kodi for production year :( 106 | # 'year': int(config.get('productionYear')), 107 | 'country': [country.get('label') for country in item.get('productionCountries', [])], 108 | 'director': item.get('director'), 109 | 'aired': airdate 110 | }, 111 | 'properties': { 112 | 'fanart_image': item.get('imageUrl'), 113 | } 114 | } 115 | 116 | 117 | def map_playlist(item): 118 | programId = item.get('programId') 119 | kind = item.get('kind') 120 | 121 | return { 122 | 'label': utils.format_title_and_subtitle(item.get('title'), item.get('subtitle')), 123 | 'path': plugin.url_for('collection', kind=kind, collection_id=programId), 124 | 'thumbnail': item.get('imageUrl'), 125 | 'info': { 126 | 'title': item.get('title'), 127 | 'plotoutline': item.get('teaserText') 128 | } 129 | } 130 | 131 | 132 | def map_streams(item, streams, quality): 133 | programId = item.get('programId') 134 | kind = item.get('kind') 135 | 136 | video_item = map_video(item, False) 137 | 138 | # TODO: filter streams by quality 139 | filtered_streams = None 140 | for q in [quality] + [i for i in ['SQ', 'EQ', 'HQ', 'MQ'] if i is not quality]: 141 | filtered_streams = [s for s in streams if s.get('quality') == q] 142 | if len(filtered_streams) > 0: 143 | break 144 | 145 | if filtered_streams is None or len(filtered_streams) == 0: 146 | raise RuntimeError('Could not resolve stream...') 147 | 148 | sorted_filtered_streams = sorted( 149 | filtered_streams, key=lambda s: s.get('audioSlot')) 150 | 151 | def map_stream(video_item, stream): 152 | audio_slot = stream.get('audioSlot') 153 | audio_label = stream.get('audioLabel') 154 | 155 | video_item['label'] = audio_label 156 | video_item['is_playable'] = True 157 | video_item['path'] = plugin.url_for( 158 | 'play_specific', kind=kind, program_id=programId, audio_slot=str(audio_slot)) 159 | 160 | return video_item 161 | 162 | return [map_stream(dict(video_item), stream) for stream in sorted_filtered_streams] 163 | 164 | 165 | def map_playable(streams, quality, audio_slot): 166 | stream = None 167 | for q in [quality] + [i for i in ['SQ', 'EQ', 'HQ', 'MQ'] if i is not quality]: 168 | stream = hof.find(lambda s: match(s, q, audio_slot), streams) 169 | if stream: 170 | break 171 | 172 | if stream is None: 173 | raise RuntimeError('Could not resolve stream...') 174 | 175 | return { 176 | 'path': stream.get('url') 177 | } 178 | 179 | 180 | def match(item, quality, audio_slot): 181 | return item.get('quality') == quality and item.get('audioSlot') == audio_slot 182 | -------------------------------------------------------------------------------- /resources/lib/settings.py: -------------------------------------------------------------------------------- 1 | 2 | languages = ['fr', 'de', 'en', 'es', 'pl', 'it'] 3 | qualities = ['SQ', 'EQ', 'HQ'] 4 | 5 | 6 | class Settings: 7 | def __init__(self, plugin): 8 | # Language used to query arte api 9 | # defaults to fr 10 | self.language = plugin.get_setting( 11 | 'lang', choices=languages) or languages[0] 12 | # Quality of the videos 13 | # defaults to SQ 14 | self.quality = plugin.get_setting( 15 | 'quality', choices=qualities) or qualities[0] 16 | # Should the plugin display all available streams for videos? 17 | # defaults to False 18 | self.show_video_streams = plugin.get_setting( 19 | 'show_video_streams', bool) or False 20 | -------------------------------------------------------------------------------- /resources/lib/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import dateutil.parser 3 | import xbmc 4 | import html 5 | import urllib.parse 6 | 7 | 8 | def colorize(text, color): 9 | """ 10 | color: a hex color string (RRGGBB or #RRGGBB) or None 11 | """ 12 | if not color: 13 | return text 14 | if color.startswith('#'): 15 | color = color[1:] 16 | return '[COLOR ff' + color + ']' + text + '[/COLOR]' 17 | 18 | 19 | def format_title_and_subtitle(title, subtitle=None): 20 | label = u'[B]{title}[/B]'.format(title=html.unescape(title)) 21 | # suffixes 22 | if subtitle: 23 | label += u' - {subtitle}'.format(subtitle=html.unescape(subtitle)) 24 | return label 25 | 26 | 27 | def encode_string(str): 28 | return urllib.parse.quote_plus(str, encoding='utf-8', errors='replace') 29 | 30 | 31 | def decode_string(str): 32 | return urllib.parse.unquote_plus(str, encoding='utf-8', errors='replace') 33 | 34 | 35 | def parse_date(datestr): 36 | date = None 37 | try: 38 | date = dateutil.parser.parse(datestr) 39 | except dateutil.parser.ParserError as e: 40 | logmsg = "[{addon_id}] Problem with parsing date: {error}".format(addon_id="plugin.video.arteplussept", error=e) 41 | xbmc.log(msg=logmsg, level=xbmc.LOGWARNING) 42 | return date 43 | 44 | 45 | def past_week(): 46 | today = datetime.date.today() 47 | one_day = datetime.timedelta(days=1) 48 | 49 | for i in range(0, 8): # TODO: find better interval 50 | yield today - (one_day * i) 51 | -------------------------------------------------------------------------------- /resources/lib/view.py: -------------------------------------------------------------------------------- 1 | 2 | import api 3 | import mapper 4 | import hof 5 | import utils 6 | 7 | 8 | def build_categories(settings): 9 | categories = [ 10 | mapper.create_newest_item(), 11 | mapper.create_most_viewed_item(), 12 | mapper.create_last_chance_item(), 13 | ] 14 | categories.extend([mapper.map_categories_item( 15 | item) for item in api.categories(settings.language)]) 16 | # categories.append(mapper.create_creative_item()) 17 | categories.append(mapper.create_magazines_item()) 18 | categories.append(mapper.create_week_item()) 19 | 20 | return categories 21 | 22 | 23 | def build_category(category_code, settings): 24 | category = [mapper.map_category_item( 25 | item, category_code) for item in api.category(category_code, settings.language)] 26 | 27 | return category 28 | 29 | 30 | def build_magazines(settings): 31 | return [mapper.map_generic_item(item, settings.show_video_streams) for item in api.magazines(settings.language)] 32 | 33 | 34 | def build_newest(settings): 35 | return [mapper.map_generic_item(item, settings.show_video_streams) for 36 | item in api.home_category('mostRecent', settings.language)] 37 | 38 | 39 | def build_most_viewed(settings): 40 | return [mapper.map_generic_item(item, settings.show_video_streams) for 41 | item in api.home_category('mostViewed', settings.language)] 42 | 43 | 44 | def build_last_chance(settings): 45 | return [mapper.map_generic_item(item, settings.show_video_streams) for 46 | item in api.home_category('lastChance', settings.language)] 47 | 48 | 49 | def build_sub_category_by_code(sub_category_code, settings): 50 | return [mapper.map_generic_item(item, settings.show_video_streams) for item in api.subcategory(sub_category_code, settings.language)] 51 | 52 | 53 | def build_sub_category_by_title(category_code, sub_category_title, settings): 54 | category = api.category(category_code, settings.language) 55 | unquoted_title = utils.decode_string(sub_category_title) 56 | 57 | sub_category = hof.find(lambda i: i.get('title') == unquoted_title, category) 58 | 59 | return [mapper.map_generic_item(item, settings.show_video_streams) for item in sub_category.get('teasers')] 60 | 61 | 62 | def build_mixed_collection(kind, collection_id, settings): 63 | return [mapper.map_generic_item(item, settings.show_video_streams) for item in api.collection(kind, collection_id, settings.language)] 64 | 65 | 66 | def build_video_streams(program_id, settings): 67 | item = api.video(program_id, settings.language) 68 | 69 | if item is None: 70 | raise RuntimeError('Video not found...') 71 | 72 | programId = item.get('programId') 73 | kind = item.get('kind') 74 | 75 | return mapper.map_streams(item, api.streams(kind, programId, settings.language), settings.quality) 76 | 77 | 78 | def build_stream_url(kind, program_id, audio_slot, settings): 79 | return mapper.map_playable(api.streams(kind, program_id, settings.language), settings.quality, audio_slot) 80 | 81 | 82 | _useless_kinds = ['CLIP', 'MANUAL_CLIP', 'TRAILER'] 83 | 84 | 85 | def build_weekly(settings): 86 | programs = hof.flatten([api.daily(date, settings.language) 87 | for date in utils.past_week()]) 88 | 89 | def keep_video_item(item): 90 | video = hof.get_property(item, 'video') 91 | 92 | if video is None: 93 | return False 94 | return hof.get_property(item, 'kind') not in _useless_kinds 95 | 96 | videos_filtered = [hof.get_property(item, 'video') 97 | for item in programs if keep_video_item(item)] 98 | 99 | videos_mapped = [mapper.map_generic_item( 100 | item, settings.show_video_streams) for item in videos_filtered] 101 | videos_mapped.sort(key=lambda item: hof.get_property( 102 | item, 'info.aired'), reverse=True) 103 | 104 | return videos_mapped 105 | -------------------------------------------------------------------------------- /resources/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | 21 | 22 | --------------------------------------------------------------------------------