From aefa7d3d72e3a461ec6db79fe305023395118cf1 Mon Sep 17 00:00:00 2001 From: Sylvain CECCHETTO Date: Wed, 22 Mar 2017 03:24:43 +0100 Subject: [plugin.video.catchuptvandmore] 0.1.0 (#989) --- .../resources/__init__.py | 0 .../language/resource.language.en_gb/strings.po | 137 +++ .../language/resource.language.fr_fr/strings.po | 140 ++++ .../resources/lib/__init__.py | 0 .../resources/lib/channels/__init__.py | 0 .../resources/lib/channels/fr/6play.py | 381 +++++++++ .../resources/lib/channels/fr/__init__.py | 0 .../resources/lib/channels/fr/arte.py | 260 ++++++ .../resources/lib/channels/fr/bfmtv.py | 224 +++++ .../resources/lib/channels/fr/c.py | 210 +++++ .../resources/lib/channels/fr/canalplus.py | 318 +++++++ .../resources/lib/channels/fr/gulli.py | 203 +++++ .../resources/lib/channels/fr/itele.py | 192 +++++ .../resources/lib/channels/fr/pluzz.py | 282 +++++++ .../resources/lib/channels/fr/tf1.py | 238 ++++++ .../resources/lib/common.py | 30 + .../resources/lib/simpleplugin.py | 923 +++++++++++++++++++++ .../resources/lib/skeleton.py | 54 ++ .../resources/lib/utils.py | 172 ++++ .../resources/media/channels/fr/arte.png | Bin 0 -> 23700 bytes .../resources/media/channels/fr/bfmtv.png | Bin 0 -> 35775 bytes .../resources/media/channels/fr/bfmtv_fanart.png | Bin 0 -> 483976 bytes .../resources/media/channels/fr/c8.png | Bin 0 -> 85413 bytes .../resources/media/channels/fr/c8_fanart.png | Bin 0 -> 201320 bytes .../resources/media/channels/fr/canalplus.png | Bin 0 -> 23038 bytes .../media/channels/fr/canalplus_fanart.png | Bin 0 -> 104838 bytes .../resources/media/channels/fr/cstar.png | Bin 0 -> 36243 bytes .../resources/media/channels/fr/cstar_fanart.png | Bin 0 -> 128048 bytes .../resources/media/channels/fr/france2.png | Bin 0 -> 53728 bytes .../resources/media/channels/fr/france2_fanart.png | Bin 0 -> 333122 bytes .../resources/media/channels/fr/france3.png | Bin 0 -> 31569 bytes .../resources/media/channels/fr/france3_fanart.png | Bin 0 -> 176208 bytes .../resources/media/channels/fr/france4.png | Bin 0 -> 78093 bytes .../resources/media/channels/fr/france5.png | Bin 0 -> 51222 bytes .../resources/media/channels/fr/france5_fanart.png | Bin 0 -> 157085 bytes .../resources/media/channels/fr/gulli.png | Bin 0 -> 162929 bytes .../resources/media/channels/fr/gulli_fanart.png | Bin 0 -> 318305 bytes .../resources/media/channels/fr/itele.png | Bin 0 -> 26801 bytes .../resources/media/channels/fr/itele_fanart.png | Bin 0 -> 85354 bytes .../resources/media/channels/fr/m6.png | Bin 0 -> 125343 bytes .../resources/media/channels/fr/m6_fanart.png | Bin 0 -> 569115 bytes .../resources/media/channels/fr/nrj12.png | Bin 0 -> 73550 bytes .../resources/media/channels/fr/nrj12_fanart.png | Bin 0 -> 340086 bytes .../resources/media/channels/fr/nt1.png | Bin 0 -> 178003 bytes .../resources/media/channels/fr/nt1_fanart.png | Bin 0 -> 339900 bytes .../resources/media/channels/fr/tf1.png | Bin 0 -> 38203 bytes .../resources/media/channels/fr/tf1_fanart.png | Bin 0 -> 694530 bytes .../resources/media/channels/fr/tmc.png | Bin 0 -> 25166 bytes .../resources/media/channels/fr/tmc_fanart.png | Bin 0 -> 345299 bytes .../resources/media/channels/fr/w9.png | Bin 0 -> 115591 bytes .../resources/screenshots/screenshot-01.jpg | Bin 0 -> 534015 bytes .../resources/screenshots/screenshot-02.jpg | Bin 0 -> 543386 bytes .../resources/screenshots/screenshot-03.jpg | Bin 0 -> 532567 bytes .../resources/screenshots/screenshot-04.jpg | Bin 0 -> 809400 bytes .../resources/screenshots/screenshot-05.jpg | Bin 0 -> 849740 bytes .../resources/settings.xml | 122 +++ 56 files changed, 3886 insertions(+) create mode 100755 plugin.video.catchuptvandmore/resources/__init__.py create mode 100755 plugin.video.catchuptvandmore/resources/language/resource.language.en_gb/strings.po create mode 100755 plugin.video.catchuptvandmore/resources/language/resource.language.fr_fr/strings.po create mode 100755 plugin.video.catchuptvandmore/resources/lib/__init__.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/__init__.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/6play.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/__init__.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/arte.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/bfmtv.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/c.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/canalplus.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/gulli.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/itele.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/pluzz.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/channels/fr/tf1.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/common.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/simpleplugin.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/skeleton.py create mode 100755 plugin.video.catchuptvandmore/resources/lib/utils.py create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/arte.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/c8.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/c8_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/cstar.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/cstar_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/france2.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/france2_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/france3.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/france3_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/france4.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/france5.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/france5_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/gulli.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/gulli_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/itele.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/itele_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/m6.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/m6_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/nt1.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/nt1_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/tf1.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/tf1_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/tmc.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/tmc_fanart.png create mode 100755 plugin.video.catchuptvandmore/resources/media/channels/fr/w9.png create mode 100755 plugin.video.catchuptvandmore/resources/screenshots/screenshot-01.jpg create mode 100755 plugin.video.catchuptvandmore/resources/screenshots/screenshot-02.jpg create mode 100755 plugin.video.catchuptvandmore/resources/screenshots/screenshot-03.jpg create mode 100755 plugin.video.catchuptvandmore/resources/screenshots/screenshot-04.jpg create mode 100755 plugin.video.catchuptvandmore/resources/screenshots/screenshot-05.jpg create mode 100755 plugin.video.catchuptvandmore/resources/settings.xml (limited to 'plugin.video.catchuptvandmore/resources') diff --git a/plugin.video.catchuptvandmore/resources/__init__.py b/plugin.video.catchuptvandmore/resources/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/plugin.video.catchuptvandmore/resources/language/resource.language.en_gb/strings.po b/plugin.video.catchuptvandmore/resources/language/resource.language.en_gb/strings.po new file mode 100755 index 0000000..abd4300 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/language/resource.language.en_gb/strings.po @@ -0,0 +1,137 @@ +# Kodi Media Center language file +# Addon Name: Catch-up TV & More +# Addon id: plugin.video.catchuptvandmore +# Addon Provider: SylvainCecchetto +msgid "" +msgstr "" +"Project-Id-Version: Kodi Addons\n" +"Report-Msgid-Bugs-To: alanwww1@kodi.org\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: SylvainCecchetto\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +# Settings categories (from 30000 to 30009) +msgctxt "#30000" +msgid "Main menu" +msgstr "Menu principal" + +msgctxt "#30001" +msgid "Channels" +msgstr "" + +msgctxt "#30002" +msgid "Quality and content" +msgstr "" + +# Settings line separators (from 30010 to 30019) +msgctxt "#30010" +msgid "Hide main menu categories" +msgstr "" + +msgctxt "#30011" +msgid "Hide channels from categories" +msgstr "" + + +# Main categories (from 30020 to 30039) +msgctxt "#30020" +msgid "French channels" +msgstr "" + +msgctxt "#30021" +msgid "Belgian channels" +msgstr "" + + +# Context menu (from 30040 to 30049) +msgctxt "#30040" +msgid "Move down" +msgstr "" + +msgctxt "#30041" +msgid "Move up" +msgstr "" + +msgctxt "#30042" +msgid "Hide" +msgstr "" + + +# Dialog boxes (from 30050 to 30069) +msgctxt "#30050" +msgid "Information" +msgstr "" + +msgctxt "#30051" +msgid "To re-enable hidden items go to the plugin settings" +msgstr "" + + +# Settings quality and content (from 30070 to 30099) +msgctxt "#30070" +msgid "TF1: Show bonus videos" +msgstr "" + +msgctxt "#30071" +msgid "France 2: Video quality" +msgstr "" + +msgctxt "#30072" +msgid "France 3: Video quality" +msgstr "" + +msgctxt "#30073" +msgid "France 4: Video quality" +msgstr "" + +msgctxt "#30074" +msgid "France 5: Video quality" +msgstr "" + +msgctxt "#30075" +msgid "France Ô: Video quality" +msgstr "" + +msgctxt "#30076" +msgid "M6: Video quality" +msgstr "" + +msgctxt "#30077" +msgid "W9: Video quality" +msgstr "" + +msgctxt "#30078" +msgid "6ter: Video quality" +msgstr "" + +msgctxt "#30079" +msgid "Arte: Video quality" +msgstr "" + +msgctxt "#30080" +msgid "Arte: Language" +msgstr "" + +msgctxt "#30081" +msgid "BFM TV: Video quality" +msgstr "" + + +# Others (from 30100 to 30140) +msgctxt "#30100" +msgid "More videos..." +msgstr "" + +msgctxt "#30101" +msgid "All videos" +msgstr "" + +msgctxt "#30102" +msgid "DRM protected video" +msgstr "Vidéo protégée par DRM" \ No newline at end of file diff --git a/plugin.video.catchuptvandmore/resources/language/resource.language.fr_fr/strings.po b/plugin.video.catchuptvandmore/resources/language/resource.language.fr_fr/strings.po new file mode 100755 index 0000000..00433d9 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/language/resource.language.fr_fr/strings.po @@ -0,0 +1,140 @@ +# Kodi Media Center language file +# Addon Name: Catch-up TV & More +# Addon id: plugin.video.catchuptvandmore +# Addon Provider: SylvainCecchetto +msgid "" +msgstr "" +"Project-Id-Version: Kodi Addons\n" +"Report-Msgid-Bugs-To: alanwww1@kodi.org\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: SylvainCecchetto\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +# Settings categories (from 30000 to 30009) +msgctxt "#30000" +msgid "Main menu" +msgstr "Menu principal" + +msgctxt "#30001" +msgid "Channels" +msgstr "Chaînes" + +msgctxt "#30002" +msgid "Quality and content" +msgstr "Qualité et contenu" + + +# Settings line separators (from 30010 to 30019) +msgctxt "#30010" +msgid "Hide main menu categories" +msgstr "Masquer des catégories du menu principal" + +msgctxt "#30011" +msgid "Hide channels from categories" +msgstr "Masquer des chaînes des catégories" + + +# Main categories (from 30020 to 30039) +msgctxt "#30020" +msgid "French channels" +msgstr "Chaînes françaises" + +msgctxt "#30021" +msgid "Belgian channels" +msgstr "Chaînes belges" + + +# Context menu (from 30040 to 30049) +msgctxt "#30040" +msgid "Move down" +msgstr "Descendre" + +msgctxt "#30041" +msgid "Move up" +msgstr "Monter" + +msgctxt "#30042" +msgid "Hide" +msgstr "Masquer" + + +# Dialog boxes (from 30050 to 30069) +msgctxt "#30050" +msgid "Information" +msgstr "Information" + +msgctxt "#30051" +msgid "To re-enable hidden items go to the plugin settings" +msgstr "Pour réactiver les éléments masqués rendez-vous dans les réglages du plugin" + + +# Settings quality and content (from 30070 to 30099) +msgctxt "#30070" +msgid "TF1: Show bonus videos" +msgstr "TF1 : Afficher les vidéos bonus" + +msgctxt "#30071" +msgid "France 2: Video quality" +msgstr "France 2 : Qualité vidéo" + +msgctxt "#30072" +msgid "France 3: Video quality" +msgstr "France 3 : Qualité vidéo" + +msgctxt "#30073" +msgid "France 4: Video quality" +msgstr "France 4 : Qualité vidéo" + +msgctxt "#30074" +msgid "France 5: Video quality" +msgstr "France 5 : Qualité vidéo" + +msgctxt "#30075" +msgid "France Ô: Video quality" +msgstr "France Ô : Qualité vidéo" + +msgctxt "#30076" +msgid "M6: Video quality" +msgstr "M6 : Qualité vidéo" + +msgctxt "#30077" +msgid "W9: Video quality" +msgstr "W9 : Qualité vidéo" + +msgctxt "#30078" +msgid "6ter: Video quality" +msgstr "6ter : Qualité vidéo" + +msgctxt "#30079" +msgid "Arte: Video quality" +msgstr "Arte : Qualité vidéo" + +msgctxt "#30080" +msgid "Arte: Language" +msgstr "Arte : Langue" + +msgctxt "#30081" +msgid "BFM TV: Video quality" +msgstr "BFM TV : Qualité vidéo" + + +# Others (from 30100 to 30140) +msgctxt "#30100" +msgid "More videos..." +msgstr "Plus de vidéos..." + +msgctxt "#30101" +msgid "All videos" +msgstr "Toutes les vidéos" + +msgctxt "#30102" +msgid "DRM protected video" +msgstr "Vidéo protégée par DRM" + + diff --git a/plugin.video.catchuptvandmore/resources/lib/__init__.py b/plugin.video.catchuptvandmore/resources/lib/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/__init__.py b/plugin.video.catchuptvandmore/resources/lib/channels/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/6play.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/6play.py new file mode 100755 index 0000000..d60ebd0 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/6play.py @@ -0,0 +1,381 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Original work (C) JUL1EN094, SPM, SylvainCecchetto + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + + +import json +from resources.lib import utils +from resources.lib import common + + +# Url to get channel's categories +# e.g. Info, Divertissement, Séries, ... +# We get an id by category +url_root = 'http://pc.middleware.6play.fr/6play/v2/platforms/' \ + 'm6group_web/services/%sreplay/folders?limit=999&offset=0' + +# Url to get catgory's programs +# e.g. Le meilleur patissier, La france à un incroyable talent, ... +# We get an id by program +url_category = 'http://pc.middleware.6play.fr/6play/v2/platforms/' \ + 'm6group_web/services/6play/folders/%s/programs' \ + '?limit=999&offset=0&csa=9&with=parentcontext' + +# Url to get program's subfolders +# e.g. Saison 5, Les meilleurs moments, les recettes pas à pas, ... +# We get an id by subfolder +url_subcategory = 'http://pc.middleware.6play.fr/6play/v2/platforms/' \ + 'm6group_web/services/6play/programs/%s' \ + '?with=links,subcats,rights' + + +# Url to get shows list +# e.g. Episode 1, Episode 2, ... +url_videos = 'http://pc.middleware.6play.fr/6play/v2/platforms/' \ + 'm6group_web/services/6play/programs/%s/videos?' \ + 'csa=6&with=clips,freemiumpacks&type=vi,vc,playlist&limit=999'\ + '&offset=0&subcat=%s&sort=subcat' + +url_videos2 = 'https://pc.middleware.6play.fr/6play/v2/platforms/' \ + 'm6group_web/services/6play/programs/%s/videos?' \ + 'csa=6&with=clips,freemiumpacks&type=vi&limit=999&offset=0' + + +url_json_video = 'https://pc.middleware.6play.fr/6play/v2/platforms/' \ + 'm6group_web/services/6play/videos/%s'\ + '?csa=9&with=clips,freemiumpacks' + + +url_img = 'https://images.6play.fr/v1/images/%s/raw' + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + shows = [] + + if params.next == 'list_shows_1': + file_path = utils.download_catalog( + url_root % (params.channel_name), + '%s.json' % (params.channel_name), + random_ua=True) + file_prgm = open(file_path).read() + json_parser = json.loads(file_prgm) + + # do not cache failed catalog fetch + # the error format is: + # {"error":{"code":403,"message":"Forbidden"}} + if isinstance(json_parser, dict) and \ + 'error' in json_parser.keys(): + utils.os.remove(file_path) + raise Exception('Failed to fetch the 6play catalog') + + for array in json_parser: + category_id = str(array['id']) + category_name = array['name'].encode('utf-8') + shows.append({ + 'label': category_name, + 'url': common.plugin.get_url( + action='channel_entry', + category_id=category_id, + next='list_shows_2', + title=category_name + ) + }) + + shows = common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + elif params.next == 'list_shows_2': + file_prgm = utils.get_webcontent( + url_category % (params.category_id), + random_ua=True) + json_parser = json.loads(file_prgm) + + for array in json_parser: + program_title = array['title'].encode('utf-8') + program_id = str(array['id']) + program_desc = array['description'].encode('utf-8') + program_imgs = array['images'] + program_img = '' + for img in program_imgs: + if img['role'].encode('utf-8') == 'vignette': + external_key = img['external_key'].encode('utf-8') + program_img = url_img % (external_key) + elif img['role'].encode('utf-8') == 'carousel': + external_key = img['external_key'].encode('utf-8') + program_fanart = url_img % (external_key) + + info = { + 'video': { + 'title': program_title, + 'plot': program_desc + } + } + shows.append({ + 'label': program_title, + 'thumb': program_img, + 'fanart': program_fanart, + 'url': common.plugin.get_url( + action='channel_entry', + next='list_shows_3', + program_id=program_id, + program_img=program_img, + program_fanart=program_fanart, + program_desc=program_desc, + title=program_title + ), + 'info': info + }) + + shows = common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + elif params.next == 'list_shows_3': + program_json = utils.get_webcontent( + url_subcategory % (params.program_id), + random_ua=True) + + json_parser = json.loads(program_json) + for sub_category in json_parser['program_subcats']: + sub_category_id = str(sub_category['id']) + sub_category_title = sub_category['title'].encode('utf-8') + + info = { + 'video': { + 'title': sub_category_title, + 'plot': params.program_desc + } + } + + shows.append({ + 'label': sub_category_title, + 'thumb': params.program_img, + 'fanart': params.program_fanart, + 'url': common.plugin.get_url( + action='channel_entry', + next='list_videos', + program_id=params.program_id, + sub_category_id=sub_category_id + ), + 'info': info + }) + + info = { + 'video': { + 'title': common.addon.get_localized_string(30101), + 'plot': params.program_desc + } + } + shows.append({ + 'label': common.addon.get_localized_string(30101), + 'thumb': params.program_img, + 'fanart': params.program_fanart, + 'url': common.plugin.get_url( + action='channel_entry', + next='list_videos', + program_id=params.program_id, + sub_category_id='null' + + ), + 'info': info + }) + + shows = common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + return shows + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + + if params.sub_category_id == 'null': + url = url_videos2 % params.program_id + else: + url = url_videos % (params.program_id, params.sub_category_id) + program_json = utils.get_webcontent( + url, + random_ua=True) + json_parser = json.loads(program_json) + + for video in json_parser: + video_id = str(video['id']) + + title = video['title'].encode('utf-8') + duration = video['clips'][0]['duration'] + description = video['description'].encode('utf-8') + try: + aired = video['clips'][0]['product']['last_diffusion'] + aired = aired.encode('utf-8') + aired = aired[:10] + year = aired[:4] + # date : string (%d.%m.%Y / 01.01.2009) + # aired : string (2008-12-07) + day = aired.split('-')[2] + mounth = aired.split('-')[1] + year = aired.split('-')[0] + date = '.'.join((day, mounth, year)) + + except: + aired = '' + year = '' + date = '' + img = '' + + program_imgs = video['clips'][0]['images'] + program_img = '' + for img in program_imgs: + if img['role'].encode('utf-8') == 'vignette': + external_key = img['external_key'].encode('utf-8') + program_img = url_img % (external_key) + + info = { + 'video': { + 'title': title, + 'plot': description, + 'aired': aired, + 'date': date, + 'duration': duration, + 'year': year, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': program_img, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + video_id=video_id, + ), + 'is_playable': True, + 'info': info + }) + + return common.plugin.create_listing( + videos, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_DATE, + common.sp.xbmcplugin.SORT_METHOD_DURATION, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, + common.sp.xbmcplugin.SORT_METHOD_UNSORTED + ), + content='tvshows') + + +@common.plugin.cached(common.cache_time) +def get_video_URL(params): + video_json = utils.get_webcontent( + url_json_video % (params.video_id), + random_ua=True) + json_parser = json.loads(video_json) + + video_assets = json_parser['clips'][0]['assets'] + url = '' + url2 = '' + url3 = '' + for asset in video_assets: + if 'ism' in asset['video_container'].encode('utf-8'): + url = asset['full_physical_path'].encode('utf-8') + if 'mp4' in asset['video_container'].encode('utf-8'): + if 'hd' in asset['video_quality'].encode('utf-8'): + url2 = asset['full_physical_path'].encode('utf-8') + else: + url3 = asset['full_physical_path'].encode('utf-8') + manifest_url = '' + if url: + manifest_url = url + elif url2: + manifest_url = url2 + else: + manifest_url = url3 + + manifest = utils.get_webcontent( + manifest_url, + random_ua=True) + if 'drm' in manifest: + utils.send_notification(common.addon.get_localized_string(30102)) + return '' + + desired_quality = common.plugin.get_setting( + params.channel_id + '.quality') + + if desired_quality == 'Auto': + return manifest_url + + root = common.os.path.dirname(manifest_url) + + url_sd = '' + url_hd = '' + url_ultra_sd = '' + url_ultra_hd = '' + + lines = manifest.splitlines() + for k in range(0, len(lines) - 1): + if 'RESOLUTION=400' in lines[k]: + url_ultra_sd = root + '/' + lines[k + 1] + elif 'RESOLUTION=640' in lines[k]: + url_sd = root + '/' + lines[k + 1] + elif 'RESOLUTION=720' in lines[k]: + url_hd = root + '/' + lines[k + 1] + elif 'RESOLUTION=1080' in lines[k]: + url_ultra_hd = root + '/' + lines[k + 1] + + if desired_quality == 'Force HD': + if url_ultra_hd: + return url_ultra_hd + elif url_hd: + return url_hd + return manifest_url + + elif desired_quality == 'Force SD': + if url_ultra_sd: + return url_ultra_sd + elif url_sd: + return url_sd + return manifest_url diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/__init__.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/arte.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/arte.py new file mode 100755 index 0000000..f2b7a2b --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/arte.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import json +from resources.lib import utils +from resources.lib import common + +authorization_key = 'Bearer OTE3NjJhOTYwNzQzNWY0MGE0OGI5MGQ0YmVm' \ + 'MWY2Y2JiYzc5NDQzY2IxMmYxYjQ0NDVlYmEyOTBmYjVkMDg3OQ' + +headers = {'Authorization': authorization_key} + +url_categories = 'https://api-cdn.arte.tv/api/opa/v2/categories?' \ + 'language=%s&limit=100&sort=order' +# Valid languages list : fr|de|en|es|pl + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + shows = [] + + disered_language = common.plugin.get_setting( + params.channel_id + '.language') + + if disered_language == 'Auto': + disered_language = params.channel_country + + file_path = utils.download_catalog( + url_categories % disered_language, + '%s.json' % params.channel_name, + specific_headers=headers) + file_categories = open(file_path).read() + json_parser = json.loads(file_categories) + + for category in json_parser['categories']: + label = category['label'].encode('utf-8') + desc = category['description'].encode('utf-8') + href = category['links']['videos']['href'].encode('utf-8') + code = category['code'].encode('utf-8') + + info = { + 'video': { + 'title': label, + 'plot': desc + } + } + + shows.append({ + 'label': label, + 'url': common.plugin.get_url( + action='channel_entry', + code=code, + href=href, + next='list_videos', + title=label + ), + 'info': info + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + + params_url = { + 'geoblockingZone': 'EUR_DE_FR,ALL,SAT,DE_FR', + 'imageSize': '1920x1080,625x224,940x530,720x406,400x225', + 'kind': 'SHOW', + 'limit': '100', + 'platform': 'ARTEPLUS7', + 'sort': '-broadcastBegin', + 'videoLibrary': 'true' + } + file_path = utils.download_catalog( + params.href, + '%s.json' % (params.channel_name + params.code), + specific_headers=headers, + params=params_url) + file_shows = open(file_path).read() + json_parser = json.loads(file_shows) + + for video in json_parser['videos']: + title = video['title'].encode('utf-8') + subtitle = '' + if video['subtitle'] is not None: + subtitle = video['subtitle'].encode('utf-8') + + original_title = video['originalTitle'].encode('utf-8') + plotoutline = '' + if video['shortDescription'] is not None: + plotoutline = video['shortDescription'].encode('utf-8') + plot = '' + if video['fullDescription'] is not None: + plot = video['fullDescription'].encode('utf-8') + duration = video['durationSeconds'] + year_prod = video['productionYear'] + genre = video['genrePresse'].encode('utf-8') + season = video['season'] + episode = video['episode'] + total_episodes = video['totalEpisodes'] + href = video['links']['videoStreams']['href'].encode('utf-8') + views = video['views'] + director = video['director'] + aired = video['arteSchedulingDay'] # year-mounth-day + day = aired.split('-')[2] + mounth = aired.split('-')[1] + year = aired.split('-')[0] + date = '.'.join((day, mounth, year)) + fanart = video['mainImage']['url'].encode('utf-8') + thumb = video['mainImage']['alternateResolutions'][1]['url'].encode('utf-8') + + if subtitle: + title = title + ' - [I]' + subtitle + '[/I]' + + info = { + 'video': { + 'title': title, + 'originaltitle': original_title, + 'plot': plot, + 'plotoutline': plotoutline, + 'aired': aired, + 'date': date, + 'duration': duration, + 'year': year_prod, + 'genre': genre, + 'season': season, + 'episode': episode, + 'playcount': views, + 'director': director, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'fanart': fanart, + 'thumb': thumb, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + href=href, + ), + 'is_playable': True, + 'info': info + }) + + return common.plugin.create_listing( + videos, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_DATE, + common.sp.xbmcplugin.SORT_METHOD_DURATION, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, + common.sp.xbmcplugin.SORT_METHOD_GENRE, + common.sp.xbmcplugin.SORT_METHOD_PLAYCOUNT, + common.sp.xbmcplugin.SORT_METHOD_UNSORTED + ), + content='tvshows') + + +@common.plugin.cached(common.cache_time) +def get_video_URL(params): + file_medias = utils.get_webcontent( + params.href, + specific_headers=headers) + json_parser = json.loads(file_medias) + + url_auto = '' + url_hd_plus = '' + url_hd = '' + url_sd = '' + url_sd_minus = '' + for video_stream in json_parser['videoStreams']: + if video_stream['audioSlot'] == 1: + if video_stream['quality'] == 'AQ' or \ + video_stream['quality'] == 'XQ': + url_auto = video_stream['url'].encode('utf-8') + + elif video_stream['quality'] == 'SQ' and \ + video_stream['mediaType'] == 'mp4' and \ + video_stream['protocol'] == 'HTTP': + url_hd_plus = video_stream['url'].encode('utf-8') + + elif video_stream['quality'] == 'EQ' and \ + video_stream['mediaType'] == 'mp4' and \ + video_stream['protocol'] == 'HTTP': + url_hd = video_stream['url'].encode('utf-8') + + elif video_stream['quality'] == 'HQ' and \ + video_stream['mediaType'] == 'mp4' and \ + video_stream['protocol'] == 'HTTP': + url_sd = video_stream['url'].encode('utf-8') + + elif video_stream['quality'] == 'MQ' and \ + video_stream['mediaType'] == 'mp4' and \ + video_stream['protocol'] == 'HTTP': + url_sd_minus = video_stream['url'].encode('utf-8') + + desired_quality = common.plugin.get_setting( + params.channel_id + '.quality') + + if desired_quality == 'Auto' and url_auto: + return url_auto + + if desired_quality == 'HD+' and url_hd_plus: + return url_hd_plus + elif url_hd: + return url_hd + + if desired_quality == 'HD' and url_hd: + return url_hd + elif url_hd_plus: + return url_hd_plus + + if desired_quality == 'SD' and url_sd: + return url_sd + elif url_sd_minus: + return url_sd_minus + + if desired_quality == 'SD-' and url_sd_minus: + return url_sd_minus + elif url_sd: + return url_sd + + return url_auto diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/bfmtv.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/bfmtv.py new file mode 100755 index 0000000..f88615b --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/bfmtv.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +''' + Catch-up TV & More + Copyright (C) 2017 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +''' + +import json +from resources.lib import utils +from resources.lib import common + +url_token = 'http://api.nextradiotv.com/bfmtv-applications/' + +url_menu = 'http://www.bfmtv.com/static/static-mobile/bfmtv/' \ + 'ios-smartphone/v0/configuration.json' + +url_replay = 'http://api.nextradiotv.com/bfmtv-applications/%s/' \ + 'getPage?pagename=replay' +# token + +url_show = 'http://api.nextradiotv.com/bfmtv-applications/%s/' \ + 'getVideosList?category=%s&count=100&page=%s' +# token, category, page_number + +url_video = 'http://api.nextradiotv.com/bfmtv-applications/%s/' \ + 'getVideo?idVideo=%s' +# token, video_id + + +@common.plugin.cached(common.cache_time) +def get_token(): + file_token = utils.get_webcontent(url_token) + token_json = json.loads(file_token) + return token_json['session']['token'].encode('utf-8') + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + # Create categories list + shows = [] + + if params.next == 'list_shows_1': + file_path = utils.download_catalog( + url_replay % get_token(), + '%s.json' % (params.channel_name)) + file_categories = open(file_path).read() + json_categories = json.loads(file_categories) + json_categories = json_categories['page']['contents'][0] + json_categories = json_categories['elements'][0]['items'] + + for categories in json_categories: + title = categories['title'].encode('utf-8') + image_url = categories['image_url'].encode('utf-8') + category = categories['categories'].encode('utf-8') + + shows.append({ + 'label': title, + 'thumb': image_url, + 'url': common.plugin.get_url( + action='channel_entry', + category=category, + next='list_videos_1', + title=title, + page='1' + ) + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + if params.next == 'list_videos_1': + file_path = utils.download_catalog( + url_show % ( + get_token(), + params.category, + params.page), + '%s_%s_%s.json' % ( + params.channel_name, + params.category, + params.page)) + file_show = open(file_path).read() + json_show = json.loads(file_show) + + for video in json_show['videos']: + video_id = video['video'].encode('utf-8') + video_id_ext = video['id_ext'].encode('utf-8') + category = video['category'].encode('utf-8') + title = video['title'].encode('utf-8') + description = video['description'].encode('utf-8') + begin_date = video['begin_date'] # 1486725600, + image = video['image'].encode('utf-8') + duration = video['video_duration_ms'] / 1000 + + info = { + 'video': { + 'title': title, + 'plot': description, + #'aired': aired, + #'date': date, + 'duration': duration, + #'year': year, + 'genre': category, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': image, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + video_id=video_id, + video_id_ext=video_id_ext + ), + 'is_playable': True, + 'info': info + }) + + # More videos... + videos.append({ + 'label': common.addon.get_localized_string(30100), + 'url': common.plugin.get_url( + action='channel_entry', + category=params.category, + next='list_videos_1', + title=title, + page=str(int(params.page) + 1) + ) + + }) + + return common.plugin.create_listing( + videos, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_DURATION, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, + common.sp.xbmcplugin.SORT_METHOD_GENRE, + common.sp.xbmcplugin.SORT_METHOD_UNSORTED + ), + content='tvshows') + + +@common.plugin.cached(common.cache_time) +def get_video_URL(params): + file_medias = utils.get_webcontent( + url_video % (get_token(), params.video_id)) + json_parser = json.loads(file_medias) + + url_hd_plus = '' + url_hd = '' + url_sd = '' + url_sd_minus = '' + url_default = '' + + for media in json_parser['video']['medias']: + if media['frame_height'] == 270: + url_sd_minus = media['video_url'].encode('utf-8') + elif media['frame_height'] == 360: + url_sd = media['video_url'].encode('utf-8') + elif media['frame_height'] == 720: + url_hd = media['video_url'].encode('utf-8') + elif media['frame_height'] == 1080: + url_hd_plus = media['video_url'].encode('utf-8') + url_default = media['video_url'].encode('utf-8') + + desired_quality = common.plugin.get_setting( + params.channel_id + '.quality') + + if desired_quality == 'HD+' and url_hd_plus: + return url_hd_plus + elif url_hd: + return url_hd + + if desired_quality == 'HD' and url_hd: + return url_hd + elif url_hd_plus: + return url_hd_plus + + if desired_quality == 'SD' and url_sd: + return url_sd + elif url_sd_minus: + return url_sd_minus + + if desired_quality == 'SD-' and url_sd_minus: + return url_sd_minus + elif url_sd: + return url_sd + + return url_default diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/c.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/c.py new file mode 100755 index 0000000..84d3b2d --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/c.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Copyright (C) 2017 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import json +from resources.lib import utils +from resources.lib import common + + +url_root = 'http://lab.canal-plus.pro/web/app_prod.php/api/replay/%s' +# Channel id : +# c8 : 1 +# cstar : 2 + +url_shows = 'http://lab.canal-plus.pro/web/app_prod.php/api/pfv/list/%s/%s' +# channel_id/show_id + +url_video = 'http://lab.canal-plus.pro/web/app_prod.php/api/pfv/video/%s/%s' +# channel_id/video_id + + +def get_channel_id(params): + if params.channel_name == 'c8': + return '1' + elif params.channel_name == 'cstar': + return '2' + else: + return '1' + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + # Create categories list + shows = [] + + if params.next == 'list_shows_1': + file_path = utils.download_catalog( + url_root % get_channel_id(params), + '%s.json' % (params.channel_name)) + file_categories = open(file_path).read() + json_categories = json.loads(file_categories) + + for categories in json_categories: + title = categories['title'].encode('utf-8') + slug = categories['slug'].encode('utf-8') + + shows.append({ + 'label': title, + 'url': common.plugin.get_url( + action='channel_entry', + slug=slug, + next='list_shows_2', + title=title + ) + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + elif params.next == 'list_shows_2': + # Create category's programs list + file_path = utils.download_catalog( + url_root % get_channel_id(params), + '%s_%s.json' % (params.channel_name, params.slug)) + file_categories = open(file_path).read() + json_categories = json.loads(file_categories) + + for categories in json_categories: + if categories['slug'].encode('utf-8') == params.slug: + for programs in categories['programs']: + id = str(programs['id']) + title = programs['title'].encode('utf-8') + slug = programs['slug'].encode('utf-8') + videos_recent = str(programs['videos_recent']) + + shows.append({ + 'label': title, + 'url': common.plugin.get_url( + action='channel_entry', + next='list_videos', + id=id, + videos_recent=videos_recent, + slug=slug, + title=title + ) + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + file_path = utils.download_catalog( + url_shows % (get_channel_id(params), params.videos_recent), + '%s_%s.json' % (params.channel_name, params.videos_recent)) + file_videos = open(file_path).read() + videos_json = json.loads(file_videos) + + for video in videos_json: + id = video['ID'].encode('utf-8') + try: + duration = int(video['DURATION'].encode('utf-8')) + except: + duration = 0 + description = video['INFOS']['DESCRIPTION'].encode('utf-8') + views = int(video['INFOS']['NB_VUES'].encode('utf-8')) + try: + date_video = video['INFOS']['DIFFUSION']['DATE'].encode('utf-8') # 31/12/2017 + except: + date_video = "00/00/0000" + day = date_video.split('/')[0] + mounth = date_video.split('/')[1] + year = date_video.split('/')[2] + aired = '-'.join((day, mounth, year)) + date = date_video.replace('/', '.') + title = video['INFOS']['TITRAGE']['TITRE'].encode('utf-8') + subtitle = video['INFOS']['TITRAGE']['SOUS_TITRE'].encode('utf-8') + thumb = video['MEDIA']['IMAGES']['GRAND'].encode('utf-8') + category = video['RUBRIQUAGE']['CATEGORIE'].encode('utf-8') + + if subtitle: + title = title + ' - [I]' + subtitle + '[/I]' + + info = { + 'video': { + 'title': title, + 'plot': description, + 'aired': aired, + 'date': date, + 'duration': duration, + 'year': year, + 'genre': category, + 'playcount': views, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': thumb, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + id=id, + ), + 'is_playable': True, + 'info': info + }) + + return common.plugin.create_listing( + videos, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_DATE, + common.sp.xbmcplugin.SORT_METHOD_DURATION, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, + common.sp.xbmcplugin.SORT_METHOD_GENRE, + common.sp.xbmcplugin.SORT_METHOD_PLAYCOUNT, + common.sp.xbmcplugin.SORT_METHOD_UNSORTED + ), + content='tvshows') + + + +@common.plugin.cached(common.cache_time) +def get_video_URL(params): + file_video = utils.get_webcontent( + url_video % (get_channel_id(params), params.id) + ) + video_json = json.loads(file_video) + return video_json['main']['MEDIA']['VIDEOS']['HLS'].encode('utf-8') + diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/canalplus.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/canalplus.py new file mode 100755 index 0000000..12240ef --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/canalplus.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Original work (C) JUL1EN094, SPM, SylvainCecchetto + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import json +from resources.lib import utils +from resources.lib import common + + +url_auth = 'http://service.mycanal.fr/authenticate.json/iphone/' \ + '1.6?highResolution=1&isActivated=0&isAuthenticated=0&paired=0' + +url_categories = 'http://service.mycanal.fr/page/%s/4578.json?' \ + 'cache=60000&nbContent=96' + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +def get_token(): + token_json = utils.get_webcontent(url_auth) + token_json = json.loads(token_json) + token = token_json['token'] + return token + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + # Create categories list + shows = [] + + if params.next == 'list_shows_1': + file_path = utils.download_catalog( + url_categories % get_token(), + '%s.json' % (params.channel_name)) + file_categories = open(file_path).read() + json_categories = json.loads(file_categories) + + for strate in json_categories['strates']: + if strate['type'] == 'textList': + for content in strate['contents']: + title = content['title'].encode('utf-8') + url_page = content['onClick']['URLPage'].encode('utf-8') + + shows.append({ + 'label': title, + 'url': common.plugin.get_url( + action='channel_entry', + url_page=url_page, + next='list_shows_2', + title=title + ) + }) + + shows = common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + elif params.next == 'list_shows_2': + # Create category's programs list + file_path = utils.download_catalog( + params.url_page, + '%s.json' % (params.title)) + file_shows = open(file_path).read() + shows_json = json.loads(file_shows) + + for strate in shows_json['strates']: + if strate['type'] == 'contentGrid': + for content in strate['contents']: + title = content['title'].encode('utf-8') + try: + subtitle = content['subtitle'].encode('utf-8') + except: + subtitle = '' + img = content['URLImage'].encode('utf-8') + url_page = content['onClick']['URLPage'].encode('utf-8') + + info = { + 'video': { + 'title': title, + 'plot': subtitle, + } + } + shows.append({ + 'label': title, + 'thumb': img, + 'url': common.plugin.get_url( + action='channel_entry', + next='list_shows_3', + url_page=url_page, + title=title + ), + 'info': info + }) + + shows = common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + elif params.next == 'list_shows_3': + # Check if there is any folder for this program + file_path = utils.download_catalog( + params.url_page, + '%s.json' % (params.title)) + file_shows = open(file_path).read() + shows_json = json.loads(file_shows) + + if 'strates' not in shows_json or len(shows_json['strates']) <= 2: + params.next = 'list_videos' + params.title = 'none' + params.index_page = 1 + return list_videos(params) + else: + fanart = '' + for strate in shows_json['strates']: + if strate['type'] == 'carrousel': + for content in strate['contents']: + fanart = content['URLImage'].encode('utf-8') + elif strate['type'] == 'contentRow': + title = strate['title'].encode('utf-8') + + info = { + 'video': { + 'title': title, + } + } + + shows.append({ + 'label': title, + 'fanart': fanart, + 'url': common.plugin.get_url( + action='channel_entry', + next='list_videos', + url_page=params.url_page, + title=title, + index_page=1 + ), + 'info': info + }) + + shows = common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + return shows + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + file_path = utils.download_catalog( + params.url_page, + '%s.json' % (params.url_page)) + file_videos = open(file_path).read() + videos_json = json.loads(file_videos) + + more_videos = False + no_strates = False + fanart = '' + + if 'strates' not in videos_json: + no_strates = True + else: + for strate in videos_json['strates']: + if strate['type'] == 'carrousel': + for content in strate['contents']: + fanart = content['URLImage'].encode('utf-8') + if strate['type'] == 'contentRow' or strate['type'] == 'contentGrid': + if strate['title'].encode('utf-8') == params.title or params.title == 'none': + if 'URLPage' in strate['paging']: + url = strate['paging']['URLPage'].encode('utf-8') + url = url + '&indexPage=' + params.index_page + params.index_page = int(params.index_page) + 1 + more_videos = True + file_videos = utils.get_webcontent(url) + videos_json = json.loads(file_videos) + + if more_videos or no_strates: + if len(videos_json['contents']) == 0: + more_videos = False + for content in videos_json['contents']: + title = content['title'].encode('utf-8') + try: + subtitle = content['subtitle'].encode('utf-8') + except: + subtitle = '' + img = content['URLImage'].encode('utf-8') + url_media = content['onClick']['URLPage'].encode('utf-8') + + info = { + 'video': { + 'title': title, + 'plot': subtitle, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': img, + 'fanart': fanart, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + url_media=url_media, + url_page=params.url_page, + title=title, + index_page=params.index_page + ), + 'info': info, + 'is_playable': True + }) + else: + for strate in videos_json['strates']: + if strate['type'] == 'contentRow' or strate['type'] == 'contentGrid': + if strate['title'].encode('utf-8') == params.title or params.title == 'none': + for content in strate['contents']: + title = content['title'].encode('utf-8') + try: + subtitle = content['subtitle'].encode('utf-8') + except: + subtitle = '' + img = content['URLImage'].encode('utf-8') + url_media = content['onClick']['URLPage'].encode('utf-8') + + info = { + 'video': { + 'title': title, + 'plot': subtitle, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': img, + 'fanart': fanart, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + url_media=url_media, + url_page=params.url_page, + title=title, + index_page=params.index_page + ), + 'info': info, + 'is_playable': True + }) + + if more_videos: + videos.append({ + 'label': common.addon.get_localized_string(30100), + 'url': common.plugin.get_url( + action='channel_entry', + next='list_videos', + url_media=url_media, + title=params.title, + url_page=params.url_page, + index_page=params.index_page + ), + + }) + + videos = common.plugin.create_listing( + videos, + content='tvshows', + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + return videos + + +@common.plugin.cached(common.cache_time) +def get_video_URL(params): + file_path = utils.get_webcontent(params.url_media) + media_json = json.loads(file_path) + url = media_json['detail']['informations']['VoD']['videoURL'].encode('utf-8') + return url diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/gulli.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/gulli.py new file mode 100755 index 0000000..b7ab8ee --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/gulli.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Copyright (C) 2017 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import json +from resources.lib import utils +from resources.lib import common + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +categories = {} + +categories['Dessins animés'] = 'http://sslreplay.gulli.fr/replay/api?' \ + 'call=%7B%22api_key%22:%22iphoner_a140e' \ + 'e8cb4b10fcd8b12a7fe688b34de%22,%22method' \ + '%22:%22programme.getLatestEpisodes%22,%' \ + '22params%22:%7B%22program_image_thumb%' \ + '22:%5B310,230%5D,%22category_id%22:%22' \ + 'dessins-animes%22%7D%7D' + +categories['Émissions'] = 'https://sslreplay.gulli.fr/replay/api?' \ + 'call=%7B%22api_key%22:%22iphoner_a140e' \ + 'e8cb4b10fcd8b12a7fe688b34de%22,%22method' \ + '%22:%22programme.getLatestEpisodes%22,%' \ + '22params%22:%7B%22program_image_thumb%' \ + '22:%5B310,230%5D,%22category_id%22:%22' \ + 'emissions%22%7D%7D' + +categories['Séries & films'] = 'https://sslreplay.gulli.fr/replay/api?' \ + 'call=%7B%22api_key%22:%22iphoner_a140e' \ + 'e8cb4b10fcd8b12a7fe688b34de%22,%22method' \ + '%22:%22programme.getLatestEpisodes%22,%' \ + '22params%22:%7B%22program_image_thumb%' \ + '22:%5B310,230%5D,%22category_id%22:%22' \ + 'series%22%7D%7D' + +url_list_show = 'https://sslreplay.gulli.fr/replay/api?call=%%7B%%22api_key' \ + '%%22:%%22iphoner_a140ee8cb4b10fcd8b12a7fe688b34de%%22,%%22' \ + 'method%%22:%%22programme.getEpisodesByProgramIds%%22,%%22' \ + 'params%%22:%%7B%%22program_id_list%%22:%%5B%%22%s%%22%%5D' \ + '%%7D%%7D' +# program_id + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + # Create categories list + shows = [] + + if params.next == 'list_shows_1': + for category_title, category_url in categories.iteritems(): + shows.append({ + 'label': category_title, + 'url': common.plugin.get_url( + action='channel_entry', + category_url=category_url, + next='list_shows_cat', + title=category_title + ) + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + elif params.next == 'list_shows_cat': + file_path = utils.download_catalog( + params.category_url, + '%s_%s.json' % (params.channel_name, params.title), + random_ua=True) + file = open(file_path).read() + json_category = json.loads(file) + + for show in json_category['res']: + program_title = show['program_title'.encode('utf-8')] + program_id = show['program_id'].encode('utf-8') + fanart = show['program_image'].encode('utf-8') + + shows.append({ + 'label': program_title, + 'thumb': fanart, + 'fanart': fanart, + 'url': common.plugin.get_url( + action='channel_entry', + program_id=program_id, + next='list_videos', + title=program_title + ) + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + + file_path = utils.download_catalog( + url_list_show % params.program_id, + '%s_%s.json' % (params.channel_name, params.program_id)) + file = open(file_path).read() + json_show = json.loads(file) + + for show in json_show['res']: + # media_id = show['media_id'].encode('utf-8') + # program_title = show['program_title'.encode('utf-8')] + # cat_id = show['cat_id'].encode('utf-8') + # program_id = show['program_id'].encode('utf-8') + fanart = show['program_image'].encode('utf-8') + thumb = show['episode_image'].encode('utf-8') + episode_title = show['episode_title'].encode('utf-8') + episode_number = show['episode_number'] + season_number = show['season_number'] + # total_episodes_in_season = show['total_episodes_in_season'] + url_ipad = show['url_ipad'].encode('utf-8') + # url_streaming = show['url_streaming'].encode('utf-8') + short_desc = show['short_desc'].encode('utf-8') + note = float(show['note'].encode('utf-8')) * 2 + date_debut = show['date_debut'].encode('utf-8') + # "2017-02-03 00:00:00" + year = int(date_debut[:4]) + day = date_debut[8:10] + month = date_debut[5:7] + date = '.'.join((day, month, str(year))) + aired = '-'.join((str(year), month, day)) + + info = { + 'video': { + 'title': episode_title, + 'plot': short_desc, + 'episode': episode_number, + 'season': season_number, + 'rating': note, + 'aired': aired, + 'date': date, + # 'duration': duration, + 'year': year, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': episode_title, + 'thumb': thumb, + 'fanart': fanart, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + url_ipad=url_ipad + ), + 'is_playable': True, + 'info': info + }) + + return common.plugin.create_listing( + videos, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_DATE, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE + ), + content='tvshows') + + +@common.plugin.cached(common.cache_time) +def get_video_URL(params): + return params.video_urlhd diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/itele.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/itele.py new file mode 100755 index 0000000..0963fb3 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/itele.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Copyright (C) 2017 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import json +from resources.lib import utils +from resources.lib import common + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +url_category_query = 'http://service.itele.fr/iphone/categorie_news?query=' + + +categories = { + 'http://service.itele.fr/iphone/topnews': 'La Une', + url_category_query + 'FRANCE': 'France', + url_category_query + 'MONDE': 'Monde', + url_category_query + 'POLITIQUE': 'Politique', + url_category_query + 'JUSTICE': 'Justice', + url_category_query + 'ECONOMIE': 'Économie', + url_category_query + 'SPORT': 'Sport', + url_category_query + 'CULTURE': 'Culture', + url_category_query + 'INSOLITE': 'Insolite' +} + +#@common.plugin.cached(common.cache_time) +def list_shows(params): + # Create categories list + shows = [] + + if params.next == 'list_shows_1': + for category_url, category_title in categories.iteritems(): + shows.append({ + 'label': category_title, + 'url': common.plugin.get_url( + action='channel_entry', + category_url=category_url, + next='list_videos_cat', + title=category_title + ) + }) + + shows.append({ + 'label': 'Les Émissions', + 'url': common.plugin.get_url( + action='channel_entry', + category_url='emissions', + next='list_shows_emissions', + title='Les Émissions' + ) + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + elif params.next == 'list_shows_emissions': + shows.append({ + 'label': 'À la Une', + 'url': common.plugin.get_url( + action='channel_entry', + category_url='http://service.itele.fr/iphone/dernieres_emissions?query=', + next='list_videos_cat', + title='À la Une' + ) + }) + + shows.append({ + 'label': 'Magazines', + 'url': common.plugin.get_url( + action='channel_entry', + category_url='http://service.itele.fr/iphone/emissions?query=magazines', + next='list_videos_cat', + title='Magazines' + ) + }) + + shows.append({ + 'label': 'Chroniques', + 'url': common.plugin.get_url( + action='channel_entry', + category_url='http://service.itele.fr/iphone/emissions?query=chroniques', + next='list_videos_cat', + title='Chroniques' + ) + }) + + return common.plugin.create_listing( + shows, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + ) + + +def list_videos(params): + videos = [] + if params.next == 'list_videos_cat': + file_path = utils.download_catalog( + params.category_url, + '%s_%s.json' % (params.channel_name, params.title)) + file = open(file_path).read() + json_category = json.loads(file) + + if 'news' in json_category: + json_category = json_category['news'] + elif 'videos' in json_category: + json_category = json_category['videos'] + elif 'topnews' in json_category: + json_category = json_category['topnews'] + for video in json_category: + video_id = video['id_pfv'].encode('utf-8') + category = video['category'].encode('utf-8') + date_time = video['date'].encode('utf-8') # 2017-02-10 22:05:02 + title = video['title'].encode('utf-8') + description = video['description'].encode('utf-8') + thumb = video['preview169'].encode('utf-8') + video_url = video['video_urlhd'].encode('utf-8') + if not video_url: + video_url = 'no_video' + + info = { + 'video': { + 'title': title, + 'plot': description, + # 'aired': aired, + # 'date': date, + #'duration': duration, + #'year': year, + 'genre': category, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': thumb, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + video_id=video_id, + video_urlhd=video_url + ), + 'is_playable': True, + 'info': info + }) + + return common.plugin.create_listing( + videos, + sort_methods=( + common.sp.xbmcplugin.SORT_METHOD_DATE, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, + common.sp.xbmcplugin.SORT_METHOD_GENRE, + common.sp.xbmcplugin.SORT_METHOD_UNSORTED + ), + content='tvshows') + + +def get_video_URL(params): + return params.video_urlhd + diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/pluzz.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/pluzz.py new file mode 100755 index 0000000..2c1ddaf --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/pluzz.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Original work (C) JUL1EN094, SPM, SylvainCecchetto + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import json +from resources.lib import utils +from resources.lib import common + +channelCatalog = 'http://pluzz.webservices.francetelevisions.fr/' \ + 'pluzz/liste/type/replay/nb/10000/chaine/%s' + +showInfo = 'http://webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/' \ + '?idDiffusion=%s&catalogue=Pluzz' + +imgURL = 'http://refonte.webservices.francetelevisions.fr%s' + +categories = {"france2": "France 2", + "france3": "France 3", + "france4": "France 4", + "france5": "France 5", + "franceo": "France Ô", + "guadeloupe": "Guadeloupe 1ère", + "guyane": "Guyane 1ère", + "martinique": "Martinique 1ère", + "mayotte": "Mayotte 1ère", + "nouvellecaledonie": "Nouvelle Calédonie 1ère", + "polynesie": "Polynésie 1ère", + "reunion": "Réunion 1ère", + "saintpierreetmiquelon": "St-Pierre et Miquelon 1ère", + "wallisetfutuna": "Wallis et Futuna 1ère", + "sport": "Sport", + "info": "Info", + "documentaire": "Documentaire", + "seriefiction": "Série & fiction", + "magazine": "Magazine", + "jeunesse": "Jeunesse", + "divertissement": "Divertissement", + "jeu": "Jeu", + "culture": "Culture"} + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_URL(params) + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + shows = [] + uniqueItem = dict() + + realChannel = params.channel_name + if params.channel_name == 'la_1ere': + realChannel = 'la_1ere_reunion%2C' \ + 'la_1ere_guyane%2C' \ + 'la_1ere_polynesie%2C' \ + 'la_1ere_martinique%2C' \ + 'la_1ere_mayotte%2C' \ + 'la_1ere_nouvellecaledonie%2C' \ + 'la_1ere_guadeloupe%2C' \ + 'la_1ere_wallisetfutuna%2C' \ + 'la_1ere_saintpierreetmiquelon' + + url_json = channelCatalog % (realChannel) + filePath = utils.download_catalog( + url_json, + '%s.json' % (params.channel_name)) + filPrgm = open(filePath).read() + jsonParser = json.loads(filPrgm) + emissions = jsonParser['reponse']['emissions'] + + if params.next == 'list_shows_1': + for emission in emissions: + rubrique = emission['rubrique'].encode('utf-8') + if rubrique not in uniqueItem: + uniqueItem[rubrique] = rubrique + rubrique_title = change_to_nicer_name(rubrique) + + shows.append({ + 'label': rubrique_title, + 'url': common.plugin.get_url( + action='channel_entry', + rubrique=rubrique, + next='list_shows_2' + ) + }) + + sort_methods = ( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + + shows = common.plugin.create_listing( + shows, + sort_methods=sort_methods + ) + + elif params.next == 'list_shows_2': + for emission in emissions: + rubrique = emission['rubrique'].encode('utf-8') + if rubrique == params.rubrique: + titre_programme = emission['titre_programme'].encode('utf-8') + if titre_programme != '': + id_programme = emission['id_programme'].encode('utf-8') + if id_programme == '': + id_programme = emission['id_emission'].encode('utf-8') + if id_programme not in uniqueItem: + uniqueItem[id_programme] = id_programme + icon = imgURL % (emission['image_large']) + genre = emission['genre'] + accroche_programme = emission['accroche_programme'] + + info = { + 'video': { + 'title': titre_programme, + 'plot': accroche_programme, + 'genre': genre + } + } + shows.append({ + 'label': titre_programme, + 'thumb': icon, + 'url': common.plugin.get_url( + action='channel_entry', + next='list_videos_1', + id_programme=id_programme, + ), + 'info': info + }) + + sort_methods = ( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL + ) + + shows = common.plugin.create_listing( + shows, + sort_methods=sort_methods + ) + + return shows + + +@common.plugin.cached(common.cache_time) +def change_to_nicer_name(original_name): + if original_name in categories: + return categories[original_name] + return original_name + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + filePath = utils.download_catalog( + channelCatalog % (params.channel_name), + '%s.json' % (params.channel_name) + ) + filPrgm = open(filePath).read() + jsonParser = json.loads(filPrgm) + emissions = jsonParser['reponse']['emissions'] + for emission in emissions: + id_programme = emission['id_programme'].encode('utf-8') + if id_programme == '': + id_programme = emission['id_emission'].encode('utf-8') + if id_programme == params.id_programme: + title = '' + plot = '' + duration = 0 + date = '' + genre = '' + id_diffusion = emission['id_diffusion'] + filPrgm = utils.get_webcontent( + showInfo % (emission['id_diffusion'])) + if(filPrgm != ''): + jsonParserShow = json.loads(filPrgm) + if jsonParserShow['synopsis']: + plot = jsonParserShow['synopsis'].encode('utf-8') + if jsonParserShow['diffusion']['date_debut']: + date = jsonParserShow['diffusion']['date_debut'] + date = date.encode('utf-8') + if jsonParserShow['real_duration']: + duration = int(jsonParserShow['real_duration']) + if jsonParserShow['titre']: + title = jsonParserShow['titre'].encode('utf-8') + if jsonParserShow['sous_titre']: + title = ' '.join(( + title, + '- [I]', + jsonParserShow['sous_titre'].encode('utf-8'), + '[/I]')) + + if jsonParserShow['genre'] != '': + genre = \ + jsonParserShow['genre'].encode('utf-8') + + year = int(date[6:10]) + day = date[:2] + month = date[3:5] + date = '.'.join((day, month, str(year))) + aired = '-'.join((str(year), month, day)) + # date : string (%d.%m.%Y / 01.01.2009) + # aired : string (2008-12-07) + image = imgURL % (jsonParserShow['image']) + info = { + 'video': { + 'title': title, + 'plot': plot, + 'aired': aired, + 'date': date, + 'duration': duration, + 'year': year, + 'genre': genre, + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': image, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + id_diffusion=id_diffusion + ), + 'is_playable': True, + 'info': info + }) + + sort_methods = ( + common.sp.xbmcplugin.SORT_METHOD_DATE, + common.sp.xbmcplugin.SORT_METHOD_DURATION, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, + common.sp.xbmcplugin.SORT_METHOD_UNSORTED + ) + return common.plugin.create_listing( + videos, + sort_methods=sort_methods, + content='tvshows') + + +@common.plugin.cached(common.cache_time) +def get_video_URL(params): + filPrgm = utils.get_webcontent(showInfo % (params.id_diffusion)) + jsonParser = json.loads(filPrgm) + url_HD = '' + url_SD = '' + for video in jsonParser['videos']: + if video['format'] == 'hls_v5_os': + url_HD = video['url'] + if video['format'] == 'm3u8-download': + url_SD = video['url'] + + desired_quality = common.plugin.get_setting( + params.channel_id + '.quality') + + if desired_quality == 'HD' and url_HD is not None: + return url_HD + else: + return url_SD diff --git a/plugin.video.catchuptvandmore/resources/lib/channels/fr/tf1.py b/plugin.video.catchuptvandmore/resources/lib/channels/fr/tf1.py new file mode 100755 index 0000000..c10c181 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/channels/fr/tf1.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Original work (C) JUL1EN094, SPM, SylvainCecchetto + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +from bs4 import BeautifulSoup as bs +from resources.lib import utils +from resources.lib import common + +url_root = "http://www.tf1.fr/" + + +def channel_entry(params): + if 'list_shows' in params.next: + return list_shows(params) + elif 'list_videos' in params.next: + return list_videos(params) + elif 'play' in params.next: + return get_video_url(params) + else: + return None + + +@common.plugin.cached(common.cache_time) +def list_shows(params): + shows = [] + + url = ''.join(( + url_root, + params.channel_name, + '/programmes-tv')) + file_path = utils.download_catalog( + url, + params.channel_name + '.html') + root_html = open(file_path).read() + root_soup = bs(root_html, 'html.parser') + + if params.next == 'list_shows_1': + categories_soup = root_soup.find( + 'ul', + attrs={'class': 'filters_2 contentopen'}) + for category in categories_soup.find_all('a'): + category_name = category.get_text().encode('utf-8') + category_url = category['data-target'].encode('utf-8') + + shows.append({ + 'label': category_name, + 'url': common.plugin.get_url( + action='channel_entry', + category=category_url, + next='list_shows_2')}) + + sort_methods = ( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL) + + shows = common.plugin.create_listing( + shows, + sort_methods=sort_methods) + + elif params.next == 'list_shows_2': + programs_soup = root_soup.find( + 'ul', + attrs={'id': 'js_filter_el_container'}) + for program in programs_soup.find_all('li'): + category = program['data-type'].encode('utf-8') + if params.category == category or params.category == 'all': + program_url = program.find( + 'div', + class_='description') + program_url = program_url.find('a')['href'].encode('utf-8') + program_name = program.find( + 'p', + class_='program').get_text().encode('utf-8') + img = program.find('img') + try: + img = img['data-srcset'].encode('utf-8') + except: + img = img['srcset'].encode('utf-8') + + img = 'http:' + img.split(',')[-1].split(' ')[0] + + shows.append({ + 'label': program_name, + 'thumb': img, + 'url': common.plugin.get_url( + action='channel_entry', + program_url=program_url, + next='list_videos')}) + + sort_methods = ( + common.sp.xbmcplugin.SORT_METHOD_UNSORTED, + common.sp.xbmcplugin.SORT_METHOD_LABEL) + + shows = common.plugin.create_listing( + shows, + sort_methods=sort_methods) + + return shows + + +@common.plugin.cached(common.cache_time) +def list_videos(params): + videos = [] + + url = ''.join(( + url_root, + params.program_url, + '/videos')) + program_html = utils.get_webcontent(url) + program_soup = bs(program_html, 'html.parser') + + grid = program_soup.find( + 'ul', + class_='grid') + + for li in grid.find_all('li'): + video_type_string = li.find('strong').get_text().encode('utf-8') + video_type = video_type_string.lower() + + if 'playlist' not in video_type: + if 'replay' in video_type or \ + 'video' in video_type or \ + common.plugin.get_setting(params.channel_id + '.bonus'): + + title = li.find( + 'p', + class_='title').get_text().encode('utf-8') + + try: + stitle = li.find( + 'p', + class_='stitle').get_text().encode('utf-8') + except: + stitle = '' + + try: + duration = li.find( + 'span', + attrs={'data-format': 'duration'}) + duration = int(duration.get_text().encode('utf-8')) + except: + duration = 0 + + img = li.find('img') + try: + img = img['data-srcset'].encode('utf-8') + except: + img = img['srcset'].encode('utf-8') + + img = 'http:' + img.split(',')[-1].split(' ')[0] + + aired = li.find( + 'span', + attrs={'data-format': None, 'class': 'momentDate'}) + aired = aired.get_text().encode('utf-8') + aired = aired.split('T')[0] + + day = aired.split('-')[2] + mounth = aired.split('-')[1] + year = aired.split('-')[0] + date = '.'.join((day, mounth, year)) + # date : string (%d.%m.%Y / 01.01.2009) + # aired : string (2008-12-07) + program_id = li.find('a')['href'].encode('utf-8') + + if 'replay' not in video_type and 'video' not in video_type: + title = title + ' - [I]' + video_type_string + '[/I]' + + info = { + 'video': { + 'title': title, + 'plot': stitle, + 'aired': aired, + 'date': date, + 'duration': duration, + 'year': int(aired[:4]), + 'mediatype': 'tvshow' + } + } + + videos.append({ + 'label': title, + 'thumb': img, + 'url': common.plugin.get_url( + action='channel_entry', + next='play', + program_id=program_id, + ), + 'is_playable': True, + 'info': info + }) + + sort_methods = ( + common.sp.xbmcplugin.SORT_METHOD_DATE, + common.sp.xbmcplugin.SORT_METHOD_DURATION, + common.sp.xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, + common.sp.xbmcplugin.SORT_METHOD_UNSORTED + ) + return common.plugin.create_listing( + videos, + sort_methods=sort_methods, + content='tvshows') + + +@common.plugin.cached(common.cache_time) +def get_video_url(params): + url = url_root + params.program_id + video_html = utils.get_webcontent(url) + video_html_soup = bs(video_html, 'html.parser') + + iframe_player_soup = video_html_soup.find( + 'div', + class_='iframe_player') + + data_src = iframe_player_soup['data-src'].encode('utf-8') + + video_id = data_src[-8:] + + return 'http://wat.tv/get/ipad/' + video_id + '.m3u8' diff --git a/plugin.video.catchuptvandmore/resources/lib/common.py b/plugin.video.catchuptvandmore/resources/lib/common.py new file mode 100755 index 0000000..5705b7f --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/common.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +from resources.lib import simpleplugin as sp +import os + +plugin = sp.Plugin() +addon = sp.Addon() + +cache_time = 10 + +plugin_name = 'Catch-up TV & More' diff --git a/plugin.video.catchuptvandmore/resources/lib/simpleplugin.py b/plugin.video.catchuptvandmore/resources/lib/simpleplugin.py new file mode 100755 index 0000000..f3b6f9d --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/simpleplugin.py @@ -0,0 +1,923 @@ +# -*- coding: utf-8 -*- +# Created on: 03.06.2015 +""" +SimplePlugin micro-framework for Kodi content plugins + +**Author**: Roman Miroshnychenko aka Roman V.M. + +**License**: `GPL v.3 `_ +""" + +import os +import sys +import re +from datetime import datetime, timedelta +import cPickle as pickle +from urlparse import parse_qs +from urllib import urlencode +from functools import wraps +from collections import MutableMapping, namedtuple +from copy import deepcopy +from types import GeneratorType +from hashlib import md5 +from shutil import move +import xbmcaddon +import xbmc +import xbmcplugin +import xbmcgui + +__all__ = ['SimplePluginError', 'Storage', 'Addon', 'Plugin', 'Params'] + +ListContext = namedtuple('ListContext', ['listing', 'succeeded', 'update_listing', 'cache_to_disk', + 'sort_methods', 'view_mode', 'content']) +PlayContext = namedtuple('PlayContext', ['path', 'play_item', 'succeeded']) + + +class SimplePluginError(Exception): + """Custom exception""" + pass + + +class Params(dict): + """ + Params(**kwargs) + + A class that stores parsed plugin call parameters + + Parameters can be accessed both through :class:`dict` keys and + instance properties. + + Example: + + .. code-block:: python + + @plugin.action('foo') + def action(params): + foo = params['foo'] # Access by key + bar = params.bar # Access through property. Both variants are equal + """ + def __getattr__(self, item): + if item not in self: + raise AttributeError('Invalid parameter: "{0}"!'.format(item)) + return self[item] + + def __str__(self): + return ''.format(super(Params, self).__repr__()) + + def __repr__(self): + return ''.format(super(Params, self).__repr__()) + + +class Storage(MutableMapping): + """ + Persistent storage for arbitrary data with a dictionary-like interface + + It is designed as a context manager and better be used + with 'with' statement. + + :param storage_dir: directory for storage + :type storage_dir: str + :param filename: the name of a storage file (optional) + :type filename: str + + Usage:: + + with Storage('/foo/bar/storage/') as storage: + storage['key1'] = value1 + value2 = storage['key2'] + + .. note:: After exiting :keyword:`with` block a :class:`Storage` instance is invalidated. + Storage contents are saved to disk only for a new storage or if the contents have been changed. + """ + def __init__(self, storage_dir, filename='storage.pcl'): + """ + Class constructor + """ + self._storage = {} + self._hash = None + self._filename = os.path.join(storage_dir, filename) + try: + with open(self._filename, 'rb') as fo: + contents = fo.read() + self._storage = pickle.loads(contents) + self._hash = md5(contents).hexdigest() + except (IOError, pickle.PickleError, EOFError): + pass + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.flush() + + def __getitem__(self, key): + return self._storage[key] + + def __setitem__(self, key, value): + self._storage[key] = value + + def __delitem__(self, key): + del self._storage[key] + + def __iter__(self): + return self._storage.__iter__() + + def __len__(self): + return len(self._storage) + + def __str__(self): + return ''.format(self._storage) + + def __repr__(self): + return ''.format(self._storage) + + def flush(self): + """ + Save storage contents to disk + + This method saves new and changed :class:`Storage` contents to disk + and invalidates the Storage instance. Unchanged Storage is not saved + but simply invalidated. + """ + contents = pickle.dumps(self._storage) + if self._hash is None or md5(contents).hexdigest() != self._hash: + tmp = self._filename + '.tmp' + try: + with open(tmp, 'wb') as fo: + fo.write(contents) + except: + os.remove(tmp) + raise + move(tmp, self._filename) # Atomic save + del self._storage + + def copy(self): + """ + Make a copy of storage contents + + .. note:: this method performs a *deep* copy operation. + + :return: a copy of storage contents + :rtype: dict + """ + return deepcopy(self._storage) + + +class Addon(object): + """ + Base addon class + + Provides access to basic addon parameters + + :param id_: addon id, e.g. 'plugin.video.foo' (optional) + :type id_: str + """ + def __init__(self, id_=''): + """ + Class constructor + """ + self._addon = xbmcaddon.Addon(id_) + self._configdir = xbmc.translatePath(self._addon.getAddonInfo('profile')).decode('utf-8') + self._ui_strings_map = None + if not os.path.exists(self._configdir): + os.mkdir(self._configdir) + + def __getattr__(self, item): + """ + Get addon setting as an Addon instance attribute + + E.g. addon.my_setting is equal to addon.get_setting('my_setting') + + :param item: + :type item: str + """ + return self.get_setting(item) + + def __str__(self): + return ''.format(self.id) + + def __repr__(self): + return ''.format(self.id) + + @property + def addon(self): + """ + Kodi Addon instance that represents this Addon + + :return: Addon instance + :rtype: xbmcaddon.Addon + """ + return self._addon + + @property + def id(self): + """ + Addon ID + + :return: Addon ID, e.g. 'plugin.video.foo' + :rtype: str + """ + return self._addon.getAddonInfo('id') + + @property + def path(self): + """ + Addon path + + :return: path to the addon folder + :rtype: str + """ + return self._addon.getAddonInfo('path').decode('utf-8') + + @property + def icon(self): + """ + Addon icon + + :return: path to the addon icon image + :rtype: str + """ + icon = os.path.join(self.path, 'icon.png') + if os.path.exists(icon): + return icon + else: + return '' + + @property + def fanart(self): + """ + Addon fanart + + :return: path to the addon fanart image + :rtype: str + """ + fanart = os.path.join(self.path, 'fanart.jpg') + if os.path.exists(fanart): + return fanart + else: + return '' + + @property + def config_dir(self): + """ + Addon config dir + + :return: path to the addon config dir + :rtype: str + """ + return self._configdir + + @property + def version(self): + """ + Addon version + + :return: addon version + :rtype: str + """ + return self._addon.getAddonInfo('version') + + def get_localized_string(self, id_): + """ + Get localized UI string + + :param id_: UI string ID + :type id_: int + :return: UI string in the current language + :rtype: str + """ + return self._addon.getLocalizedString(id_).encode('utf-8') + + def get_setting(self, id_, convert=True): + """ + Get addon setting + + If ``convert=True``, 'bool' settings are converted to Python :class:`bool` values, + and numeric strings to Python :class:`long` or :class:`float` depending on their format. + + .. note:: Settings can also be read via :class:`Addon` instance poperties named as the respective settings. + I.e. ``addon.foo`` is equal to ``addon.get_setting('foo')``. + + :param id_: setting ID + :type id_: str + :param convert: try to guess and convert the setting to an appropriate type + E.g. ``'1.0'`` will be converted to float ``1.0`` number, ``'true'`` to ``True`` and so on. + :type convert: bool + :return: setting value + """ + setting = self._addon.getSetting(id_) + if convert: + if setting == 'true': + return True # Convert boolean strings to bool + elif setting == 'false': + return False + elif re.search(r'^-?\d+$', setting) is not None: + return long(setting) # Convert numeric strings to long + elif re.search(r'^-?\d+\.\d+$', setting) is not None: + return float(setting) # Convert numeric strings with a dot to float + return setting + + def set_setting(self, id_, value): + """ + Set addon setting + + Python :class:`bool` type are converted to ``'true'`` or ``'false'`` + Non-string/non-unicode values are converted to strings. + + .. warning:: Setting values via :class:`Addon` instance properties is not supported! + Values can only be set using :meth:`Addon.set_setting` method. + + :param id_: setting ID + :type id_: str + :param value: setting value + """ + if isinstance(value, bool): + value = 'true' if value else 'false' + elif not isinstance(value, basestring): + value = str(value) + self._addon.setSetting(id_, value) + + def log(self, message, level=xbmc.LOGDEBUG): + """ + Add message to Kodi log starting with Addon ID + + :param message: message to be written into the Kodi log + :type message: str + :param level: log level. :mod:`xbmc` module provides the necessary symbolic constants. + Default: ``xbmc.LOGDEBUG`` + :type level: int + """ + if isinstance(message, unicode): + message = message.encode('utf-8') + xbmc.log('{0} [v.{1}]: {2}'.format(self.id, self.version, message), level) + + def log_notice(self, message): + """ + Add NOTICE message to the Kodi log + + :param message: message to write to the Kodi log + :type message: str + """ + self.log(message, xbmc.LOGINFO) + + def log_warning(self, message): + """ + Add WARNING message to the Kodi log + + :param message: message to write to the Kodi log + :type message: str + """ + self.log(message, xbmc.LOGWARNING) + + def log_error(self, message): + """ + Add ERROR message to the Kodi log + + :param message: message to write to the Kodi log + :type message: str + """ + self.log(message, xbmc.LOGERROR) + + def log_debug(self, message): + """ + Add debug message to the Kodi log + + :param message: message to write to the Kodi log + :type message: str + """ + self.log(message, xbmc.LOGDEBUG) + + def get_storage(self, filename='storage.pcl'): + """ + Get a persistent :class:`Storage` instance for storing arbitrary values between addon calls. + + A :class:`Storage` instance can be used as a context manager. + + Example:: + + with plugin.get_storage() as storage: + storage['param1'] = value1 + value2 = storage['param2'] + + .. note:: After exiting :keyword:`with` block a :class:`Storage` instance is invalidated. + + :param filename: the name of a storage file (optional) + :type filename: str + :return: Storage object + :rtype: Storage + """ + return Storage(self.config_dir, filename) + + def cached(self, duration=10): + """ + Cached decorator + + Used to cache function return data + + Usage:: + + @plugin.cached(30) + def my_func(*args, **kwargs): + # Do some stuff + return value + + :param duration: caching duration in min (positive values only) + :type duration: int + :raises ValueError: if duration is zero or negative + """ + def outer_wrapper(func): + @wraps(func) + def inner_wrapper(*args, **kwargs): + with self.get_storage('__cache__.pcl') as cache: + current_time = datetime.now() + key = func.__name__ + str(args) + str(kwargs) + try: + data, timestamp = cache[key] + if duration > 0 and current_time - timestamp > timedelta(minutes=duration): + raise KeyError + elif duration <= 0: + raise ValueError('Caching duration cannot be zero or negative!') + self.log_debug('Cache hit: {0}'.format(key)) + except KeyError: + self.log_debug('Cache miss: {0}'.format(key)) + data = func(*args, **kwargs) + cache[key] = (data, current_time) + return data + return inner_wrapper + return outer_wrapper + + def gettext(self, ui_string): + """ + Get a translated UI string from addon localization files. + + This function emulates GNU Gettext for more convenient access + to localized addon UI strings. To reduce typing this method object + can be assigned to a ``_`` (single underscore) variable. + + For using gettext emulation :meth:`Addon.initialize_gettext` method + needs to be called first. See documentation for that method for more info + about Gettext emulation. + + :param ui_string: a UI string from English :file:`strings.po`. + :type ui_string: str + :return: a UI string from translated :file:`strings.po`. + :rtype: unicode + :raises simpleplugin.SimplePluginError: if :meth:`Addon.initialize_gettext` wasn't called first + or if a string is not found in English :file:`strings.po`. + """ + if self._ui_strings_map is not None: + try: + return self.get_localized_string(self._ui_strings_map['strings'][ui_string]) + except KeyError: + raise SimplePluginError('UI string "{0}" is not found in strings.po!'.format(ui_string)) + else: + raise SimplePluginError('Addon localization is not initialized!') + + def initialize_gettext(self): + """ + Initialize GNU gettext emulation in addon + + Kodi localization system for addons is not very convenient + because you need to operate with numeric string codes instead + of UI strings themselves which reduces code readability and + may lead to errors. The :class:`Addon` class provides facilities + for emulating GNU Gettext localization system. + + This allows to use UI strings from addon's English :file:`strings.po` + file instead of numeric codes to return localized strings from + respective localized :file:`.po` files. + + This method returns :meth:`Addon.gettext` method object that + can be assigned to a short alias to reduce typing. Traditionally, + ``_`` (a single underscore) is used for this purpose. + + Example:: + + addon = simpleplugin.Addon() + _ = addon.initialize_gettext() + + xbmcgui.Dialog().notification(_('Warning!'), _('Something happened')) + + In the preceding example the notification strings will be replaced + with localized versions if these strings are translated. + + :return: :meth:`Addon.gettext` method object + :raises simpleplugin.SimplePluginError: if the addon's English :file:`strings.po` file is missing + """ + strings_po = os.path.join(self.path, 'resources', 'language', 'resource.language.en_gb', 'strings.po') + if os.path.exists(strings_po): + with open(strings_po, 'rb') as fo: + raw_strings = fo.read() + raw_strings_hash = md5(raw_strings).hexdigest() + gettext_pcl = '__gettext__.pcl' + with self.get_storage(gettext_pcl) as ui_strings_map: + if (not os.path.exists(os.path.join(self._configdir, gettext_pcl)) or + raw_strings_hash != ui_strings_map['hash']): + ui_strings = self._parse_po(raw_strings.split('\n')) + self._ui_strings_map = { + 'hash': raw_strings_hash, + 'strings': ui_strings + } + ui_strings_map['hash'] = raw_strings_hash + ui_strings_map['strings'] = ui_strings.copy() + else: + self._ui_strings_map = deepcopy(ui_strings_map) + else: + raise SimplePluginError('Unable to initialize localization because of missing English strings.po!') + return self.gettext + + def _parse_po(self, strings): + """ + Parses ``strings.po`` file into a dict of {'string': id} items. + """ + ui_strings = {} + string_id = None + for string in strings: + if string_id is None and 'msgctxt' in string: + string_id = int(re.search(r'"#(\d+)"', string).group(1)) + elif string_id is not None and 'msgid' in string: + ui_strings[re.search(r'"(.*?)"', string, re.U).group(1)] = string_id + string_id = None + return ui_strings + + +class Plugin(Addon): + """ + Plugin class + + :param id_: plugin's id, e.g. 'plugin.video.foo' (optional) + :type id_: str + + This class provides a simplified API to create virtual directories of playable items + for Kodi content plugins. + :class:`simpleplugin.Plugin` uses a concept of callable plugin actions (functions or methods) + that are defined using :meth:`Plugin.action` decorator. + A Plugin instance must have at least one action that is named ``'root'``. + + Minimal example: + + .. code-block:: python + + from simpleplugin import Plugin + + plugin = Plugin() + + @plugin.action() + def root(params): # Mandatory item! + return [{'label': 'Foo', + 'url': plugin.get_url(action='some_action', param='Foo')}, + {'label': 'Bar', + 'url': plugin.get_url(action='some_action', param='Bar')}] + + @plugin.action() + def some_action(params): + return [{'label': params['param']}] + + plugin.run() + + An action callable receives 1 parameter -- params. + params is a dict-like object containing plugin call parameters (including action string) + The action callable can return + either a list/generator of dictionaries representing Kodi virtual directory items + or a resolved playable path (:class:`str` or :obj:`unicode`) for Kodi to play. + + Example 1:: + + @plugin.action() + def list_action(params): + listing = get_listing(params) # Some external function to create listing + return listing + + The ``listing`` variable is a Python list/generator of dict items. + Example 2:: + + @plugin.action() + def play_action(params): + path = get_path(params) # Some external function to get a playable path + return path + + Each dict item can contain the following properties: + + - label -- item's label (default: ``''``). + - label2 -- item's label2 (default: ``''``). + - thumb -- item's thumbnail (default: ``''``). + - icon -- item's icon (default: ``''``). + - path -- item's path (default: ``''``). + - fanart -- item's fanart (optional). + - art -- a dict containing all item's graphic (see :meth:`xbmcgui.ListItem.setArt` for more info) -- optional. + - stream_info -- a dictionary of ``{stream_type: {param: value}}`` items + (see :meth:`xbmcgui.ListItem.addStreamInfo`) -- optional. + - info -- a dictionary of ``{media: {param: value}}`` items + (see :meth:`xbmcgui.ListItem.setInfo`) -- optional + - context_menu - a list that contains 2-item tuples ``('Menu label', 'Action')``. + The items from the tuples are added to the item's context menu. + - url -- a callback URL for this list item. + - is_playable -- if ``True``, then this item is playable and must return a playable path or + be resolved via :meth:`Plugin.resolve_url` (default: ``False``). + - is_folder -- if ``True`` then the item will open a lower-level sub-listing. if ``False``, + the item either is a playable media or a general-purpose script + which neither creates a virtual folder nor points to a playable media (default: C{True}). + if ``'is_playable'`` is set to ``True``, then ``'is_folder'`` value automatically assumed to be ``False``. + - subtitles -- the list of paths to subtitle files (optional). + - mime -- item's mime type (optional). + - list_item -- an 'class:`xbmcgui.ListItem` instance (optional). + It is used when you want to set all list item properties by yourself. + If ``'list_item'`` property is present, all other properties, + except for ``'url'`` and ``'is_folder'``, are ignored. + - properties -- a dictionary of list item properties + (see :meth:`xbmcgui.ListItem.setProperty`) -- optional. + + Example 3:: + + listing = [{ 'label': 'Label', + 'label2': 'Label 2', + 'thumb': 'thumb.png', + 'icon': 'icon.png', + 'fanart': 'fanart.jpg', + 'art': {'clearart': 'clearart.png'}, + 'stream_info': {'video': {'codec': 'h264', 'duration': 1200}, + 'audio': {'codec': 'ac3', 'language': 'en'}}, + 'info': {'video': {'genre': 'Comedy', 'year': 2005}}, + 'context_menu': [('Menu Item', 'Action')], + 'url': 'plugin:/plugin.video.test/?action=play', + 'is_playable': True, + 'is_folder': False, + 'subtitles': ['/path/to/subtitles.en.srt', '/path/to/subtitles.uk.srt'], + 'mime': 'video/mp4' + }] + + Alternatively, an action callable can use :meth:`Plugin.create_listing` and :meth:`Plugin.resolve_url` + static methods to pass additional parameters to Kodi. + + Example 4:: + + @plugin.action() + def list_action(params): + listing = get_listing(params) # Some external function to create listing + return Plugin.create_listing(listing, sort_methods=(2, 10, 17), view_mode=500) + + Example 5:: + + @plugin.action() + def play_action(params): + path = get_path(params) # Some external function to get a playable path + return Plugin.resolve_url(path, succeeded=True) + + If an action callable performs any actions other than creating a listing or + resolving a playable URL, it must return ``None``. + """ + def __init__(self, id_=''): + """ + Class constructor + """ + super(Plugin, self).__init__(id_) + self._url = 'plugin://{0}/'.format(self.id) + self._handle = None + self.actions = {} + + def __str__(self): + return ''.format(sys.argv) + + def __repr__(self): + return ''.format(sys.argv) + + @staticmethod + def get_params(paramstring): + """ + Convert a URL-encoded paramstring to a Python dict + + :param paramstring: URL-encoded paramstring + :type paramstring: str + :return: parsed paramstring + :rtype: Params + """ + raw_params = parse_qs(paramstring) + params = Params() + for key, value in raw_params.iteritems(): + params[key] = value[0] if len(value) == 1 else value + return params + + def get_url(self, plugin_url='', **kwargs): + """ + Construct a callable URL for a virtual directory item + + If plugin_url is empty, a current plugin URL is used. + kwargs are converted to a URL-encoded string of plugin call parameters + To call a plugin action, 'action' parameter must be used, + if 'action' parameter is missing, then the plugin root action is called + If the action is not added to :class:`Plugin` actions, :class:`PluginError` will be raised. + + :param plugin_url: plugin URL with trailing / (optional) + :type plugin_url: str + :param kwargs: pairs of key=value items + :return: a full plugin callback URL + :rtype: str + """ + url = plugin_url or self._url + if kwargs: + return '{0}?{1}'.format(url, urlencode(kwargs, doseq=True)) + return url + + def action(self, name=None): + """ + Action decorator + + Defines plugin callback action. If action's name is not defined explicitly, + then the action is named after the decorated function. + + .. warning:: Action's name must be unique. + + A plugin must have at least one action named ``'root'`` implicitly or explicitly. + + Example: + + .. code-block:: python + + @plugin.action() # The action is implicitly named 'root' after the decorated function + def root(params): + pass + + @plugin.action('foo') # The action name is set explicitly + def foo_action(params): + pass + + :param name: action's name (optional). + :type name: str + :raises simpleplugin.SimplePluginError: if the action with such name is already defined. + """ + def wrap(func, name=name): + if name is None: + name = func.__name__ + if name in self.actions: + raise SimplePluginError('Action "{0}" already defined!'.format(name)) + self.actions[name] = func + return func + return wrap + + def run(self, category=''): + """ + Run plugin + + :param category: str - plugin sub-category, e.g. 'Comedy'. + See :func:`xbmcplugin.setPluginCategory` for more info. + :type category: str + :raises simpleplugin.SimplePluginError: if unknown action string is provided. + """ + self._handle = int(sys.argv[1]) + if category: + xbmcplugin.setPluginCategory(self._handle, category) + params = self.get_params(sys.argv[2][1:]) + action = params.get('action', 'root') + self.log_debug(str(self)) + self.log_debug('Actions: {0}'.format(str(self.actions.keys()))) + self.log_debug('Called action "{0}" with params "{1}"'.format(action, str(params))) + try: + action_callable = self.actions[action] + except KeyError: + raise SimplePluginError('Invalid action: "{0}"!'.format(action)) + else: + result = action_callable(params) + self.log_debug('Action return value: {0}'.format(str(result))) + if isinstance(result, (list, GeneratorType)): + self._add_directory_items(self.create_listing(result)) + elif isinstance(result, basestring): + self._set_resolved_url(self.resolve_url(result)) + elif isinstance(result, tuple) and hasattr(result, 'listing'): + self._add_directory_items(result) + elif isinstance(result, tuple) and hasattr(result, 'path'): + self._set_resolved_url(result) + else: + self.log_debug('The action "{0}" has not returned any valid data to process.'.format(action)) + + @staticmethod + def create_listing(listing, succeeded=True, update_listing=False, cache_to_disk=False, sort_methods=None, + view_mode=None, content=None): + """ + Create and return a context dict for a virtual folder listing + + :param listing: the list of the plugin virtual folder items + :type listing: :class:`list` or :class:`types.GeneratorType` + :param succeeded: if ``False`` Kodi won't open a new listing and stays on the current level. + :type succeeded: bool + :param update_listing: if ``True``, Kodi won't open a sub-listing but refresh the current one. + :type update_listing: bool + :param cache_to_disk: cache this view to disk. + :type cache_to_disk: bool + :param sort_methods: the list of integer constants representing virtual folder sort methods. + :type sort_methods: tuple + :param view_mode: a numeric code for a skin view mode. + View mode codes are different in different skins except for ``50`` (basic listing). + :type view_mode: int + :param content: string - current plugin content, e.g. 'movies' or 'episodes'. + See :func:`xbmcplugin.setContent` for more info. + :type content: str + :return: context object containing necessary parameters + to create virtual folder listing in Kodi UI. + :rtype: ListContext + """ + return ListContext(listing, succeeded, update_listing, cache_to_disk, sort_methods, view_mode, content) + + @staticmethod + def resolve_url(path='', play_item=None, succeeded=True): + """ + Create and return a context dict to resolve a playable URL + + :param path: the path to a playable media. + :type path: str or unicode + :param play_item: a dict of item properties as described in the class docstring. + It allows to set additional properties for the item being played, like graphics, metadata etc. + if ``play_item`` parameter is present, then ``path`` value is ignored, and the path must be set via + ``'path'`` property of a ``play_item`` dict. + :type play_item: dict + :param succeeded: if ``False``, Kodi won't play anything + :type succeeded: bool + :return: context object containing necessary parameters + for Kodi to play the selected media. + :rtype: PlayContext + """ + return PlayContext(path, play_item, succeeded) + + @staticmethod + def create_list_item(item): + """ + Create an :class:`xbmcgui.ListItem` instance from an item dict + + :param item: a dict of ListItem properties + :type item: dict + :return: ListItem instance + :rtype: xbmcgui.ListItem + """ + list_item = xbmcgui.ListItem(label=item.get('label', ''), + label2=item.get('label2', ''), + path=item.get('path', '')) + if int(xbmc.getInfoLabel('System.BuildVersion')[:2]) >= 16: + art = item.get('art', {}) + art['thumb'] = item.get('thumb', '') + art['icon'] = item.get('icon', '') + art['fanart'] = item.get('fanart', '') + item['art'] = art + else: + list_item.setThumbnailImage(item.get('thumb', '')) + list_item.setIconImage(item.get('icon', '')) + list_item.setProperty('fanart_image', item.get('fanart', '')) + if item.get('art'): + list_item.setArt(item['art']) + if item.get('stream_info'): + for stream, stream_info in item['stream_info'].iteritems(): + list_item.addStreamInfo(stream, stream_info) + if item.get('info'): + for media, info in item['info'].iteritems(): + list_item.setInfo(media, info) + if item.get('context_menu') is not None: + list_item.addContextMenuItems(item['context_menu']) + if item.get('subtitles'): + list_item.setSubtitles(item['subtitles']) + if item.get('mime'): + list_item.setMimeType(item['mime']) + if item.get('properties'): + for key, value in item['properties'].iteritems(): + list_item.setProperty(key, value) + return list_item + + def _add_directory_items(self, context): + """ + Create a virtual folder listing + + :param context: context object + :type context: ListContext + """ + self.log_debug('Creating listing from {0}'.format(str(context))) + if context.content is not None: + xbmcplugin.setContent(self._handle, context.content) # This must be at the beginning + for item in context.listing: + is_folder = item.get('is_folder', True) + if item.get('list_item') is not None: + list_item = item['list_item'] + else: + list_item = self.create_list_item(item) + if item.get('is_playable'): + list_item.setProperty('IsPlayable', 'true') + is_folder = False + xbmcplugin.addDirectoryItem(self._handle, item['url'], list_item, is_folder) + if context.sort_methods is not None: + [xbmcplugin.addSortMethod(self._handle, method) for method in context.sort_methods] + xbmcplugin.endOfDirectory(self._handle, + context.succeeded, + context.update_listing, + context.cache_to_disk) + if context.view_mode is not None: + xbmc.executebuiltin('Container.SetViewMode({0})'.format(context.view_mode)) + + def _set_resolved_url(self, context): + """ + Resolve a playable URL + + :param context: context object + :type context: PlayContext + """ + self.log_debug('Resolving URL from {0}'.format(str(context))) + if context.play_item is None: + list_item = xbmcgui.ListItem(path=context.path) + else: + list_item = self.create_list_item(context.play_item) + xbmcplugin.setResolvedUrl(self._handle, context.succeeded, list_item) \ No newline at end of file diff --git a/plugin.video.catchuptvandmore/resources/lib/skeleton.py b/plugin.video.catchuptvandmore/resources/lib/skeleton.py new file mode 100755 index 0000000..cf1c510 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/skeleton.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + + +categories = { + 'main_menu.fr': 'French channels' +# 'main_menu.be': 'Belgian channels' +} + +channels = { + 'main_menu.fr': { + 'channels.fr.tf1.tf1': 'TF1', + 'channels.fr.pluzz.france2': 'France 2', + 'channels.fr.pluzz.france3': 'France 3', + 'channels.fr.canalplus.canalplus': 'Canal +', + 'channels.fr.pluzz.france5': 'France 5', + 'channels.fr.6play.m6': 'M6', + 'channels.fr.arte.arte': 'Arte', + 'channels.fr.c.c8': 'C8', + 'channels.fr.6play.w9': 'W9', + 'channels.fr.tf1.tmc': 'TMC', + 'channels.fr.tf1.nt1': 'NT1', + 'channels.fr.6play.nrj12': 'NRJ 12', + 'channels.fr.pluzz.france4': 'France 4', + 'channels.fr.bfmtv.bfmtv': 'BFM TV', + 'channels.fr.itele.itele': 'i-Télé', + 'channels.fr.c.cstar': 'CStar', + 'channels.fr.gulli.gulli': 'Gulli' + + } + + # 'main_menu.be': { + # 'channels.be.rtbf.rtbf': 'RTBF' + # } +} diff --git a/plugin.video.catchuptvandmore/resources/lib/utils.py b/plugin.video.catchuptvandmore/resources/lib/utils.py new file mode 100755 index 0000000..7545aa4 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/lib/utils.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +""" + Catch-up TV & More + Original work (C) JUL1EN094, SPM, SylvainCecchetto + Copyright (C) 2016 SylvainCecchetto + + This file is part of Catch-up TV & More. + + Catch-up TV & More is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Catch-up TV & More is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with Catch-up TV & More; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import os +import time +import requests +from random import randint +from resources.lib import common + + +user_data = common.sp.xbmc.translatePath( + os.path.join( + 'special://profile/addon_data', + common.addon.id)) + +cache_path = common.sp.xbmc.translatePath( + os.path.join( + user_data, + 'cache')) + +default_ua = "Mozilla/5.0 (Windows NT 6.1; WOW64) " \ + "AppleWebKit/537.36 (KHTML, like Gecko) " \ + "Chrome/55.0.2883.87 Safari/537.36" + +user_agents = [ + 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36' + ' (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14' + ' (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A', + 'Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/602.2.14' + ' (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14', + 'Mozilla/5.0 (Windows NT 6.1; WOW64) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/55.0.2883.87 Safari/537.36' +] + + +def get_random_ua_hdr(): + ua = user_agents[randint(0, len(user_agents) - 1)] + return { + 'User-Agent': ua + } + + +def download_catalog( + url, + file_name, + force_dl=False, + request_type='get', + post_dic={}, + random_ua=False, + specific_headers={}, + params={}): + file_name = format_filename(file_name) + common.addon.log('URL download_catalog : ' + url) + + if not os.path.exists(cache_path): + os.makedirs(cache_path, mode=0777) + file_path = os.path.join(cache_path, file_name) + + if os.path.exists(file_path): + mtime = os.stat(file_path).st_mtime + dl_file = (time.time() - mtime > 60) + else: + dl_file = True + if dl_file or force_dl: + if random_ua: + ua = user_agents[randint(0, len(user_agents) - 1)] + else: + ua = default_ua + + if specific_headers: + headers = specific_headers + if 'User-Agent' not in headers: + headers['User-Agent'] = ua + else: + headers = {'User-Agent': ua} + + if request_type == 'get': + r = requests.get(url, headers=headers, params=params) + + elif request_type == 'post': + r = requests.get( + url, headers=headers, data=post_dic, params=params) + + with open(file_path, 'wb') as f: + f.write(r.content) + + return file_path + + +def format_filename(filename): + keepcharacters = ('_', '.') + return "".join( + c for c in filename if c.isalnum() or c in keepcharacters).rstrip() + + +def get_webcontent( + url, + request_type='get', + post_dic={}, + random_ua=False, + specific_headers={}, + params={}): + common.addon.log('URL get_webcontent : ' + url) + if random_ua: + ua = user_agents[randint(0, len(user_agents) - 1)] + else: + ua = default_ua + + if specific_headers: + headers = specific_headers + if 'User-Agent' not in headers: + headers['User-Agent'] = ua + else: + headers = {'User-Agent': ua} + + if request_type == 'get': + r = requests.get(url, headers=headers, params=params) + + elif request_type == 'post': + r = requests.get(url, headers=headers, data=post_dic, params=params) + + return r.content + + +def get_redirected_url( + url, + random_ua=False, + specific_headers={}): + if random_ua: + ua = user_agents[randint(0, len(user_agents) - 1)] + else: + ua = default_ua + + if specific_headers: + headers = specific_headers + if 'User-Agent' not in headers: + headers['User-Agent'] = ua + else: + headers = {'User-Agent': ua} + + r = requests.head(url, allow_redirects=True, headers=headers) + return r.url + + +def send_notification( + message, title=common.plugin_name, time=5000, sound=True): + common.sp.xbmcgui.Dialog().notification( + title, message, common.addon.icon, time) diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/arte.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/arte.png new file mode 100755 index 0000000..3c003a2 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/arte.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv.png new file mode 100755 index 0000000..3ca5f2a Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv_fanart.png new file mode 100755 index 0000000..226487f Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/bfmtv_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/c8.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/c8.png new file mode 100755 index 0000000..e18d86c Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/c8.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/c8_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/c8_fanart.png new file mode 100755 index 0000000..05a9a6c Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/c8_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus.png new file mode 100755 index 0000000..6829f23 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus_fanart.png new file mode 100755 index 0000000..2a2e3b4 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/canalplus_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/cstar.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/cstar.png new file mode 100755 index 0000000..4d3a463 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/cstar.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/cstar_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/cstar_fanart.png new file mode 100755 index 0000000..f7c45db Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/cstar_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/france2.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/france2.png new file mode 100755 index 0000000..6f741cd Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/france2.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/france2_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/france2_fanart.png new file mode 100755 index 0000000..8184c2a Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/france2_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/france3.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/france3.png new file mode 100755 index 0000000..3309012 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/france3.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/france3_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/france3_fanart.png new file mode 100755 index 0000000..dede4f8 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/france3_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/france4.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/france4.png new file mode 100755 index 0000000..a8a1bba Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/france4.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/france5.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/france5.png new file mode 100755 index 0000000..bc1f6ea Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/france5.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/france5_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/france5_fanart.png new file mode 100755 index 0000000..3380814 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/france5_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/gulli.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/gulli.png new file mode 100755 index 0000000..b2e1522 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/gulli.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/gulli_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/gulli_fanart.png new file mode 100755 index 0000000..94739ab Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/gulli_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/itele.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/itele.png new file mode 100755 index 0000000..2e1d937 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/itele.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/itele_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/itele_fanart.png new file mode 100755 index 0000000..1bca166 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/itele_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/m6.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/m6.png new file mode 100755 index 0000000..24852b7 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/m6.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/m6_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/m6_fanart.png new file mode 100755 index 0000000..f5e8e8e Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/m6_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12.png new file mode 100755 index 0000000..0c4c9f4 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12_fanart.png new file mode 100755 index 0000000..848d9c6 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/nrj12_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/nt1.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/nt1.png new file mode 100755 index 0000000..59c8e33 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/nt1.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/nt1_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/nt1_fanart.png new file mode 100755 index 0000000..bc00729 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/nt1_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/tf1.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/tf1.png new file mode 100755 index 0000000..18b7490 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/tf1.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/tf1_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/tf1_fanart.png new file mode 100755 index 0000000..e23d730 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/tf1_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/tmc.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/tmc.png new file mode 100755 index 0000000..2ad9872 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/tmc.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/tmc_fanart.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/tmc_fanart.png new file mode 100755 index 0000000..de8fef7 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/tmc_fanart.png differ diff --git a/plugin.video.catchuptvandmore/resources/media/channels/fr/w9.png b/plugin.video.catchuptvandmore/resources/media/channels/fr/w9.png new file mode 100755 index 0000000..a0c27c4 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/media/channels/fr/w9.png differ diff --git a/plugin.video.catchuptvandmore/resources/screenshots/screenshot-01.jpg b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-01.jpg new file mode 100755 index 0000000..11c6c33 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-01.jpg differ diff --git a/plugin.video.catchuptvandmore/resources/screenshots/screenshot-02.jpg b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-02.jpg new file mode 100755 index 0000000..da9e325 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-02.jpg differ diff --git a/plugin.video.catchuptvandmore/resources/screenshots/screenshot-03.jpg b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-03.jpg new file mode 100755 index 0000000..a5c6b9c Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-03.jpg differ diff --git a/plugin.video.catchuptvandmore/resources/screenshots/screenshot-04.jpg b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-04.jpg new file mode 100755 index 0000000..05ee326 Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-04.jpg differ diff --git a/plugin.video.catchuptvandmore/resources/screenshots/screenshot-05.jpg b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-05.jpg new file mode 100755 index 0000000..f7d309f Binary files /dev/null and b/plugin.video.catchuptvandmore/resources/screenshots/screenshot-05.jpg differ diff --git a/plugin.video.catchuptvandmore/resources/settings.xml b/plugin.video.catchuptvandmore/resources/settings.xml new file mode 100755 index 0000000..31e8a65 --- /dev/null +++ b/plugin.video.catchuptvandmore/resources/settings.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3