diff --git a/plugin.video.orftvthek/README.md b/plugin.video.orftvthek/README.md
index e84c4d50dc..91a9cd9bf4 100644
--- a/plugin.video.orftvthek/README.md
+++ b/plugin.video.orftvthek/README.md
@@ -1,30 +1,41 @@
-ORF TVthek KODI Addon
-=======
-ORF TVthek is an addon that gives you access to the ORF TVthek Video Platform.
+# ORF ON Addon for Kodi (plugin.video.orftvthek)
+ORF ON is an addon that provides access to the ORF ON Video Platform (Austrian Television, formerly ORF TVthek)
-Supported platforms
--------------------
-Windows, Linux , Android and OSX
+[![Kodi version](https://img.shields.io/badge/kodi%20versions-20--21-blue)](https://kodi.tv/)
Current Features
----------------
* Livestream
-* All Shows
-* Schedule Search
-* HTTP Stream H264 (Stable)
-* Search Function
-* Missed Shows
-* Blacklist Shows
-* JSON(Service API V3) or HTML Scraper
-* Restart Livestream - inputstream.adaptive needed
+* Shows
+* Schedule
+* Search
+* DRM Streams
+* Accessibility Broadcasts
+* Simple IPTV Integration
+
+Todos
+----------------
+- [X] Subtitles
+- [X] Add Settings
+- [X] Add option to show related content
+- [X] Add a main menu entry for latest uploads
+- [X] Kodi translation still missing
+- [X] Accessibility
Known Issues
------------
-* you tell me
+* A curl bug (http2) on KODI 19 prevents the streaming therefore the Addon is only supported on KODI 20+ (A workaround on the advancedsettings.xml seems to fix the issue, but further testing will be required)
+```
+
+
+ true
+
+
+```
Simple IPTV Integration
-----------------
@@ -32,52 +43,50 @@ Simple IPTV Integration
Playlist Content
```
#EXTINF:-1 tvg-name="ORF 1" tvg-id="orf1" group-title="ORF",ORF 1
-plugin://plugin.video.orftvthek/?channel=orf1&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf1
#EXTINF:-1 tvg-name="ORF 2" tvg-id="orf2" group-title="ORF",ORF 2
-plugin://plugin.video.orftvthek/?channel=orf2&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2
#EXTINF:-1 tvg-name="ORF 3" tvg-id="orf3" group-title="ORF",ORF 3
-plugin://plugin.video.orftvthek/?channel=orf3&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf3
#EXTINF:-1 tvg-name="ORF Sport+" tvg-id="orfs" group-title="ORF",ORF Sport+
-plugin://plugin.video.orftvthek/?channel=orfs&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orfs
+#EXTINF:-1 tvg-name="ORF Kids" tvg-id="orfkids" group-title="ORF",ORF Kids
+plugin://plugin.video.orftvthek/pvr/orfkids
#EXTINF:-1 tvg-name="ORF 2 Burgenland" tvg-id="orf2b" group-title="ORF",ORF 2 Burgenland
-plugin://plugin.video.orftvthek/?channel=orf2b&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2b
#EXTINF:-1 tvg-name="ORF 2 Steiermark" tvg-id="orf2stmk" group-title="ORF",ORF 2 Steiermark
-plugin://plugin.video.orftvthek/?channel=orf2stmk&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2stmk
#EXTINF:-1 tvg-name="ORF 2 Wien" tvg-id="orf2w" group-title="ORF",ORF 2 Wien
-plugin://plugin.video.orftvthek/?channel=orf2w&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2w
#EXTINF:-1 tvg-name="ORF 2 Oberösterreich" tvg-id="orf2ooe" group-title="ORF",ORF 2 Oberösterreich
-plugin://plugin.video.orftvthek/?channel=orf2ooe&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2ooe
#EXTINF:-1 tvg-name="ORF 2 Kärnten" tvg-id="orf2k" group-title="ORF",ORF 2 Kärnten
-plugin://plugin.video.orftvthek/?channel=orf2k&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2k
#EXTINF:-1 tvg-name="ORF 2 Niederösterreich" tvg-id="orf2n" group-title="ORF",ORF 2 Niederösterreich
-plugin://plugin.video.orftvthek/?channel=orf2n&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2n
#EXTINF:-1 tvg-name="ORF 2 Salzburg" tvg-id="orf2s" group-title="ORF",ORF 2 Salzburg
-plugin://plugin.video.orftvthek/?channel=orf2s&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2s
#EXTINF:-1 tvg-name="ORF 2 Vorarlberg" tvg-id="orf2v" group-title="ORF",ORF 2 Vorarlberg
-plugin://plugin.video.orftvthek/?channel=orf2v&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2v
#EXTINF:-1 tvg-name="ORF 2 Tirol" tvg-id="orf2t" group-title="ORF",ORF 2 Tirol
-plugin://plugin.video.orftvthek/?channel=orf2t&mode=pvr
+plugin://plugin.video.orftvthek/pvr/orf2t
```
Legal
-----
-This addon provides access to videos on the ORF TVthek Website but is not endorsed, certified or otherwise approved in any way by ORF.
-
-Icons
------
-https://uxwing.com
+This addon provides access to videos on the ORF ON Website but is not endorsed, certified or otherwise approved in any way by ORF.
diff --git a/plugin.video.orftvthek/addon.xml b/plugin.video.orftvthek/addon.xml
index 225fd2d3de..1fd6808458 100644
--- a/plugin.video.orftvthek/addon.xml
+++ b/plugin.video.orftvthek/addon.xml
@@ -1,33 +1,37 @@
-
+
-
-
-
-
+
+
-
+
video
+
all
de en
- ORF TVthek
- ORF TVthek
- ORF TVthek - Ermöglicht Ihnen den Zugriff auf die ORF TVthek Video Platform
- ORF TVthek - This plugin provides access to the Austrian "ORF TVthek"
+ ORF ON
+ ORF ON
+ ORF ON - Dieses Plugin ermöglicht den Zugriff auf den österreichischen Streamingdienst ORF ON
+ ORF ON - This plugin provides access to the Austrian ORF ON streaming service
GPL-2.0-only
https://forum.kodi.tv/showthread.php?tid=159835
sofaking@gettingmoney.at
- https://tvthek.orf.at
+ https://on.orf.at
resources/icon.png
resources/fanart.jpg
- v0.12.9 (07/08/2023)
- [fix] fixed livestream endpoint changes serviceapi
+ v1.0.2
+ - new Livestream (timeshift)
+ - added setting to use old livestream format
+ - LF conversion
+ - list callback fix main menu
+ - add license
+ - forum fix addon.xml
diff --git a/plugin.video.orftvthek/changelog.txt b/plugin.video.orftvthek/changelog.txt
index b10b6e2aba..8256f7a5a1 100644
--- a/plugin.video.orftvthek/changelog.txt
+++ b/plugin.video.orftvthek/changelog.txt
@@ -1,3 +1,36 @@
+v1.0.2 (2024-06-08)
+- new Livestream (timeshift)
+- added setting to use old livestream format
+- LF conversion
+- list callback fix main menu
+- add license
+- forum fix addon.xml
+
+v1.0.1 beta (2024-06-05)
+- fix routing requirement
+- show segment option added
+- cache reload fix
+- related video option
+- fix UA stream errors (#6)
+- orf tvthek has been disabled
+
+v1.0.0 beta (2024-01-06)
+- inital ORF ON addon
+- beta version
+- most stuff is working but everything needs to be tested a little more
+- orf on is still in beta so stuff might change until the final release in april 2024
+- translation stuff is still missing
+
+* 0.12.12
+ - ORF TVthek is down and replaced by ORF On
+ - ServiceAPI is forced on this version
+
+* 0.12.11
+ - Major API Change on ServiceAPI regarding assets and json structure (#137)
+
+* 0.12.10
+ - fixed livestream multistream not available #133
+
* 0.12.9
- fixed livestream endpoint changes serviceapi
@@ -240,4 +273,4 @@
- Streaming Video from http://tvthek.orf.at (mms)
- Experimental High Quality MP4 Streaming from http://tvthek.orf.at (rtmp)
- Livestream
-- Missed Shows
+- Missed Shows
\ No newline at end of file
diff --git a/plugin.video.orftvthek/resources/banner.jpg b/plugin.video.orftvthek/resources/banner.jpg
new file mode 100644
index 0000000000..e123f0a48c
Binary files /dev/null and b/plugin.video.orftvthek/resources/banner.jpg differ
diff --git a/plugin.video.orftvthek/resources/fanart.jpg b/plugin.video.orftvthek/resources/fanart.jpg
index d0c00d9439..b4f8dcccdb 100644
Binary files a/plugin.video.orftvthek/resources/fanart.jpg and b/plugin.video.orftvthek/resources/fanart.jpg differ
diff --git a/plugin.video.orftvthek/resources/icon.png b/plugin.video.orftvthek/resources/icon.png
index 02e2da3676..3d519c2c19 100644
Binary files a/plugin.video.orftvthek/resources/icon.png and b/plugin.video.orftvthek/resources/icon.png differ
diff --git a/plugin.video.orftvthek/resources/landscape.jpg b/plugin.video.orftvthek/resources/landscape.jpg
new file mode 100644
index 0000000000..8c9ff23d09
Binary files /dev/null and b/plugin.video.orftvthek/resources/landscape.jpg differ
diff --git a/plugin.video.orftvthek/resources/language/resource.language.de_de/strings.po b/plugin.video.orftvthek/resources/language/resource.language.de_de/strings.po
index 18d601d6f8..1e86ad77ee 100644
--- a/plugin.video.orftvthek/resources/language/resource.language.de_de/strings.po
+++ b/plugin.video.orftvthek/resources/language/resource.language.de_de/strings.po
@@ -1,258 +1,193 @@
# XBMC Media Center language file
-# Addon Name: ORF TVthek
+# Addon Name: ORF ON
# Addon id: plugin.video.orftvthek
-# Addon version: 0.9.0
+# Addon version: 1.0.2
# Addon Provider: sofaking
msgid ""
msgstr ""
"Project-Id-Version: XBMC-Addons\n"
"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
-"POT-Creation-Date: 2017-02-05 22:17+0000\n"
-"PO-Revision-Date: 2019-11-09 14:05+0000\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE\n"
+"POT-Creation-Date: 2024-01-06 00:00+0000\n"
+"PO-Revision-Date: 2024-01-06 00:00+0000\n"
+"Last-Translator: sofaking \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-msgctxt "Addon Summary"
-msgid "ORF TVthek"
-msgstr "ORF TVthek"
-
-msgctxt "Addon Description"
-msgid "ORF TVthek - This plugin provides access to the Austrian \"ORF TVthek\""
-msgstr "ORF TVthek - Ermöglicht Ihnen den Zugriff auf die ORF TVthek Video Platform"
-
-msgctxt "#30000"
-msgid "Recently Added Shows"
-msgstr "Neuste Sendungen"
-
-msgctxt "#30001"
-msgid "Frontpage"
-msgstr "Startseite"
-
-msgctxt "#30002"
-msgid "Shows"
-msgstr "Sendungen"
-
-msgctxt "#30003"
-msgid "Topics"
-msgstr "Themen"
-
-msgctxt "#30004"
-msgid "Livestream"
-msgstr "Live"
-
-msgctxt "#30005"
-msgid "ORF Recommendations"
-msgstr "ORF Tipps"
-
-msgctxt "#30006"
-msgid "Most Viewed"
-msgstr "Meist gesehen"
-
-msgctxt "#30007"
-msgid "Search"
-msgstr "Suchen"
-
-msgctxt "#30009"
-msgid "Broadcasted: "
-msgstr "Sendung vom"
-
-msgctxt "#30011"
-msgid "Runtime"
-msgstr "Laufzeit"
-
-msgctxt "#30014"
-msgid "No Results"
-msgstr "Keine Ergebnisse"
-
-msgctxt "#30015"
-msgid "Play all"
-msgstr "Alle Beiträge abspielen"
-
-msgctxt "#30018"
-msgid "Missed a Show?"
-msgstr "Sendung verpasst?"
-
-msgctxt "#30019"
-msgid "Streaming"
-msgstr "Livestream läuft bereits"
-
-msgctxt "#30020"
-msgid "Stream Offline"
-msgstr "Livestream läuft noch nicht"
+msgctxt "#30101"
+msgid "General"
+msgstr "Allgemein"
-msgctxt "#30022"
-msgid "Video Resolution"
-msgstr "Videoqualität"
+msgctxt "#30102"
+msgid "Use subtitles"
+msgstr "Untertitel anzeigen"
-msgctxt "#30023"
-msgid "Low"
-msgstr "Niedrig"
+msgctxt "#30103"
+msgid "Hide sign language content"
+msgstr "Gebärdensprache Inhalte ausblenden"
-msgctxt "#30024"
-msgid "Medium"
-msgstr "Mittel"
+msgctxt "#30104"
+msgid "Hide Audio description content"
+msgstr "Audiodeskription Inhalte ausblenden"
-msgctxt "#30025"
-msgid "High"
-msgstr "Hoch"
+msgctxt "#30105"
+msgid "Items per page"
+msgstr "Inhalte pro Seite"
-msgctxt "#30026"
-msgid "Use ServiceAPI (mad props to @Rechi)"
-msgstr "ServiceAPI verwenden (mad props to @Rechi)"
+msgctxt "#30106"
+msgid "Cache"
+msgstr "Cache"
-msgctxt "#30027"
-msgid "Trailers"
-msgstr "TV-Vorschau"
+msgctxt "#30107"
+msgid "Reload channel/settings Cache"
+msgstr "Sender/Konfigurations Cache neu laden"
-msgctxt "#30028"
-msgid "Show Autoplay Prompt"
-msgstr "Beim Öffnen Aktion nachfragen"
+msgctxt "#30108"
+msgid "Cache validity (days)"
+msgstr "Cache Gültigkeit (Tage)"
-msgctxt "#30029"
-msgid "Use Subtitles"
-msgstr "Untertitel verwenden"
+msgctxt "#30109"
+msgid "Clear search history"
+msgstr "Suchverlauf löschen"
-msgctxt "#30030"
-msgid "The broadcast hasn't started yet"
-msgstr "Livestream noch nicht gestartet"
+msgctxt "#30110"
+msgid "Frontpage"
+msgstr "Startseite"
-msgctxt "#30031"
-msgid "The broadcast starts at"
-msgstr "Der Livestream startet erst um"
+msgctxt "#30111"
+msgid "Schedule"
+msgstr "Sendung verpasst"
-msgctxt "#30032"
-msgid "Do you want the stream to start automatically?"
-msgstr "Soll der Livesteam automatisch starten?"
+msgctxt "#30112"
+msgid "Shows"
+msgstr "Sendungen"
-msgctxt "#30033"
-msgid "Waiting for the broadcast to start"
-msgstr "Warte auf Sendungsbeginn"
+msgctxt "#30113"
+msgid "Livestream"
+msgstr "Livestream"
-msgctxt "#30034"
-msgid "Time till broadcast:"
-msgstr "Zeit bis zum Start:"
+msgctxt "#30114"
+msgid "Search"
+msgstr "Suche"
-msgctxt "#30035"
-msgid "Start the stream?"
-msgstr "Den Livestream Starten?"
+msgctxt "#30115"
+msgid "Highlights"
+msgstr "Highlights"
-msgctxt "#30037"
-msgid "Blacklist"
-msgstr "Ausgeblendete Sendungen"
+msgctxt "#30116"
+msgid "Categories"
+msgstr "Kategorien"
-msgctxt "#30038"
-msgid "# Blacklist"
-msgstr "# "
+msgctxt "#30124"
+msgid "All episode results"
+msgstr "Alle Episoden Ergebnisse"
-msgctxt "#30040"
-msgid "# Remove %s from the Blacklist #"
-msgstr "# %s von der Liste entfernen #"
+msgctxt "#30125"
+msgid "All chapter results"
+msgstr "Alle Kapitel Ergebnisse"
-msgctxt "#30042"
-msgid " #"
-msgstr "ausblenden #"
+msgctxt "#30126"
+msgid "All history results"
+msgstr "Alle ORF History Ergebnisse"
-msgctxt "#30043"
-msgid "Use Blacklist Feature"
-msgstr "\"Sendungen ausblenden\" aktivieren"
+msgctxt "#30127"
+msgid "Next page"
+msgstr "Nächste Seite"
-msgctxt "#30044"
-msgid "HD"
-msgstr "Sehr Hoch(HD)"
+msgctxt "#30128"
+msgid "Geo lock active"
+msgstr "Geo Lock aktiv"
-msgctxt "#30045"
-msgid "The ServiceAPI is currently offline"
-msgstr "Die ServiceAPI ist zurzeit Offline"
+msgctxt "#30129"
+msgid "Some content may not be available in your country"
+msgstr "Einige Inhalte sind in Ihrem Land unter Umständen nicht verfügbar"
-msgctxt "#30046"
-msgid "Switch back to HTML Parsing in the Addon Settings"
-msgstr "Deaktivieren Sie die Option in den Einstellungen"
+msgctxt "#30130"
+msgid "Select a date"
+msgstr "Datum auswählen"
-msgctxt "#30047"
-msgid "Play Video"
-msgstr "Video abspielen"
+msgctxt "#30131"
+msgid "Enter search"
+msgstr "Suchbegriff eingeben"
-msgctxt "#30048"
-msgid "Do you want the playlist to start immediately?"
-msgstr "Soll die Playlist sofort gestartet werden?"
+msgctxt "#30132"
+msgid "Clearing search history"
+msgstr "Suchverlauf löschen"
-msgctxt "#30049"
-msgid "Archive"
-msgstr "Archiv"
+msgctxt "#30133"
+msgid "Clearing"
+msgstr "Wird gelöscht"
-msgctxt "#30050"
-msgid "An Error occured while loading the selected playlist"
-msgstr "Die Playlist konnte nicht abgespielt werden."
+msgctxt "#30134"
+msgid "Done"
+msgstr "Fertig"
-msgctxt "#30051"
-msgid "Error"
-msgstr "Fehler"
+msgctxt "#30136"
+msgid "Reloading cache"
+msgstr "Lade Cache neu"
-msgctxt "#30052"
-msgid "This Video is offline"
-msgstr "Dieses Video ist offline"
+msgctxt "#30137"
+msgid "Loading channels"
+msgstr "Sender werden geladen"
-msgctxt "#30053"
-msgid "Adaptive"
-msgstr "Adaptiv"
+msgctxt "#30138"
+msgid "Loading settings"
+msgstr "Einstellungen werden geladen"
-msgctxt "#30054"
-msgid "Stream Method"
-msgstr "Stream Methode"
+msgctxt "#30139"
+msgid "Restart"
+msgstr "Restart"
-msgctxt "#30055"
-msgid "HLS"
-msgstr ""
+msgctxt "#30140"
+msgid "All episodes"
+msgstr "Alle Folgen"
-msgctxt "#30056"
-msgid "Enable Progressive Streaming Method"
-msgstr "Progressive Streaming aktivieren"
+msgctxt "#30141"
+msgid "Episodes"
+msgstr "Folgen"
-msgctxt "#30057"
-msgid "In Focus"
-msgstr "Im Fokus"
+msgctxt "#30142"
+msgid "Channel"
+msgstr "Sender"
-msgctxt "#30058"
-msgid "Show 'Play Chapters'"
-msgstr "'Kapitel abspielen' anzeigen"
+msgctxt "#30143"
+msgid "Starts in"
+msgstr "Startet in"
-msgctxt "#30059"
-msgid "Play Full Video"
-msgstr "Ganzes Video abspielen"
+msgctxt "#30144"
+msgid "Recently added"
+msgstr "Neuste Sendungen"
-msgctxt "#30060"
-msgid "Play Chapters"
-msgstr "Kapitel abspielen"
+msgctxt "#30145"
+msgid "Broadcasts using sign language"
+msgstr "Sendungen mit Gebärdensprache"
-msgctxt "#30061"
-msgid "Show Full Livestream Schedule"
-msgstr "Livestream Programm anzeigen"
+msgctxt "#30146"
+msgid "Broadcasts with audio description"
+msgstr "Sendungen mit Audiodeskription"
-msgctxt "#30062"
-msgid "General"
-msgstr "Allgemein"
+msgctxt "#30147"
+msgid "Accessibility"
+msgstr "Barrierefrei"
-msgctxt "#30063"
-msgid "Restart"
-msgstr "Restart"
+msgctxt "#30148"
+msgid "Broadcasts with subtitles"
+msgstr "Sendungen mit Untertitel"
-msgctxt "#30064"
-msgid "older than"
-msgstr "älter als"
+msgctxt "#30149"
+msgid "Use segements"
+msgstr "Kapitel verwenden"
-msgctxt "#30065"
-msgid "Aktivieren Sie Inputstream Adaptive um alle Livestreams zu sehen"
-msgstr ""
+msgctxt "#30150"
+msgid "Related content"
+msgstr "Ähnliche Inhalte"
-msgctxt "#30066"
-msgid "Inputstream Adaptive ist nicht aktiviert. DRM Inhalte können nicht abgespielt werden."
-msgstr ""
+msgctxt "#30151"
+msgid "Show segements"
+msgstr "Kapitel anzeigen"
-msgctxt "#30067"
-msgid "Inputstream Helper ist nicht aktiviert. DRM Inhalte können nicht abgespielt werden."
-msgstr "
\ No newline at end of file
+msgctxt "#30152"
+msgid "Use timeshift"
+msgstr "Timeshift verwenden"
\ No newline at end of file
diff --git a/plugin.video.orftvthek/resources/language/resource.language.en_gb/strings.po b/plugin.video.orftvthek/resources/language/resource.language.en_gb/strings.po
index 40f88bfebc..b291dc8bbd 100644
--- a/plugin.video.orftvthek/resources/language/resource.language.en_gb/strings.po
+++ b/plugin.video.orftvthek/resources/language/resource.language.en_gb/strings.po
@@ -1,258 +1,193 @@
# XBMC Media Center language file
-# Addon Name: ORF TVthek
+# Addon Name: ORF ON
# Addon id: plugin.video.orftvthek
-# Addon version: 0.9.0
+# Addon version: 1.0.2
# Addon Provider: sofaking
msgid ""
msgstr ""
"Project-Id-Version: XBMC-Addons\n"
"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
-"POT-Creation-Date: 2017-02-05 22:17+0000\n"
-"PO-Revision-Date: 2019-11-09 14:00+0000\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE\n"
+"POT-Creation-Date: 2024-01-06 00:00+0000\n"
+"PO-Revision-Date: 2024-01-06 00:00+0000\n"
+"Last-Translator: sofaking \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"
-msgctxt "Addon Summary"
-msgid "ORF TVthek"
-msgstr ""
-
-msgctxt "Addon Description"
-msgid "ORF TVthek - This plugin provides access to the Austrian \"ORF TVthek\""
-msgstr ""
-
-msgctxt "#30000"
-msgid "Recently Added Shows"
-msgstr ""
-
-msgctxt "#30001"
-msgid "Frontpage"
-msgstr ""
-
-msgctxt "#30002"
-msgid "Shows"
-msgstr ""
-
-msgctxt "#30003"
-msgid "Topics"
-msgstr ""
-
-msgctxt "#30004"
-msgid "Livestream"
-msgstr ""
-
-msgctxt "#30005"
-msgid "ORF Recommendations"
-msgstr ""
-
-msgctxt "#30006"
-msgid "Most Viewed"
-msgstr ""
-
-msgctxt "#30007"
-msgid "Search"
-msgstr ""
-
-msgctxt "#30009"
-msgid "Broadcasted: "
-msgstr ""
-
-msgctxt "#30011"
-msgid "Runtime"
-msgstr ""
-
-msgctxt "#30014"
-msgid "No Results"
-msgstr ""
-
-msgctxt "#30015"
-msgid "Play all"
-msgstr ""
-
-msgctxt "#30018"
-msgid "Missed a Show?"
-msgstr ""
-
-msgctxt "#30019"
-msgid "Streaming"
-msgstr ""
-
-msgctxt "#30020"
-msgid "Stream Offline"
+msgctxt "#30101"
+msgid "General"
msgstr ""
-msgctxt "#30022"
-msgid "Video Resolution"
+msgctxt "#30102"
+msgid "Use subtitles"
msgstr ""
-msgctxt "#30023"
-msgid "Low"
+msgctxt "#30103"
+msgid "Hide sign language content"
msgstr ""
-msgctxt "#30024"
-msgid "Medium"
+msgctxt "#30104"
+msgid "Hide Audio description content"
msgstr ""
-msgctxt "#30025"
-msgid "High"
+msgctxt "#30105"
+msgid "Items per page"
msgstr ""
-msgctxt "#30026"
-msgid "Use ServiceAPI (mad props to @Rechi)"
+msgctxt "#30106"
+msgid "Cache"
msgstr ""
-msgctxt "#30027"
-msgid "Trailers"
+msgctxt "#30107"
+msgid "Reload channel/settings Cache"
msgstr ""
-msgctxt "#30028"
-msgid "Show Autoplay Prompt"
+msgctxt "#30108"
+msgid "Cache validity (days)"
msgstr ""
-msgctxt "#30029"
-msgid "Use Subtitles"
+msgctxt "#30109"
+msgid "Clear search history"
msgstr ""
-msgctxt "#30030"
-msgid "The broadcast hasn't started yet"
+msgctxt "#30110"
+msgid "Frontpage"
msgstr ""
-msgctxt "#30031"
-msgid "The broadcast starts at"
+msgctxt "#30111"
+msgid "Schedule"
msgstr ""
-msgctxt "#30032"
-msgid "Do you want the stream to start automatically?"
+msgctxt "#30112"
+msgid "Shows"
msgstr ""
-msgctxt "#30033"
-msgid "Waiting for the broadcast to start"
+msgctxt "#30113"
+msgid "Livestream"
msgstr ""
-msgctxt "#30034"
-msgid "Time till broadcast:"
+msgctxt "#30114"
+msgid "Search"
msgstr ""
-msgctxt "#30035"
-msgid "Start the stream?"
+msgctxt "#30115"
+msgid "Highlights"
msgstr ""
-msgctxt "#30037"
-msgid "Blacklist"
+msgctxt "#30116"
+msgid "Categories"
msgstr ""
-msgctxt "#30038"
-msgid "# Blacklist"
+msgctxt "#30124"
+msgid "All episode results"
msgstr ""
-msgctxt "#30040"
-msgid "# Remove %s from the Blacklist #"
+msgctxt "#30125"
+msgid "All chapter results"
msgstr ""
-msgctxt "#30042"
-msgid " #"
+msgctxt "#30126"
+msgid "All history results"
msgstr ""
-msgctxt "#30043"
-msgid "Use Blacklist Feature"
+msgctxt "#30127"
+msgid "Next page"
msgstr ""
-msgctxt "#30044"
-msgid "HD"
+msgctxt "#30128"
+msgid "Geo lock active"
msgstr ""
-msgctxt "#30045"
-msgid "The ServiceAPI is currently offline"
+msgctxt "#30129"
+msgid "Some content may not be available in your country"
msgstr ""
-msgctxt "#30046"
-msgid "Switch back to HTML Parsing in the Addon Settings"
+msgctxt "#30130"
+msgid "Select a date"
msgstr ""
-msgctxt "#30047"
-msgid "Play Video"
+msgctxt "#30131"
+msgid "Enter search"
msgstr ""
-msgctxt "#30048"
-msgid "Do you want the playlist to start immediately?"
+msgctxt "#30132"
+msgid "Clearing search history"
msgstr ""
-msgctxt "#30049"
-msgid "Archive"
+msgctxt "#30133"
+msgid "Clearing"
msgstr ""
-msgctxt "#30050"
-msgid "An Error occured while loading the selected playlist"
+msgctxt "#30134"
+msgid "Done"
msgstr ""
-msgctxt "#30051"
-msgid "Error"
+msgctxt "#30136"
+msgid "Reloading cache"
msgstr ""
-msgctxt "#30052"
-msgid "This Video is offline"
+msgctxt "#30137"
+msgid "Loading channels"
msgstr ""
-msgctxt "#30053"
-msgid "Adaptive"
+msgctxt "#30138"
+msgid "Loading settings"
msgstr ""
-msgctxt "#30054"
-msgid "Stream Method"
+msgctxt "#30139"
+msgid "Restart"
msgstr ""
-msgctxt "#30055"
-msgid "HLS"
+msgctxt "#30140"
+msgid "All episodes"
msgstr ""
-msgctxt "#30056"
-msgid "Enable Progressive Streaming Method"
+msgctxt "#30141"
+msgid "Episodes"
msgstr ""
-msgctxt "#30057"
-msgid "In Focus"
+msgctxt "#30142"
+msgid "Channel"
msgstr ""
-msgctxt "#30058"
-msgid "Show 'Play Chapters'"
+msgctxt "#30143"
+msgid "Starts in"
msgstr ""
-msgctxt "#30059"
-msgid "Play Full Video"
+msgctxt "#30144"
+msgid "Recently added"
msgstr ""
-msgctxt "#30060"
-msgid "Play Chapters"
+msgctxt "#30145"
+msgid "Broadcasts using sign language"
msgstr ""
-msgctxt "#30061"
-msgid "Show Full Livestream Schedule"
+msgctxt "#30146"
+msgid "Broadcasts with audio description"
msgstr ""
-msgctxt "#30062"
-msgid "General"
+msgctxt "#30147"
+msgid "Accessibility"
msgstr ""
-msgctxt "#30063"
-msgid "Restart"
+msgctxt "#30148"
+msgid "Broadcasts with subtitles"
msgstr ""
-msgctxt "#30064"
-msgid "older than"
+msgctxt "#30149"
+msgid "Use segements"
msgstr ""
-msgctxt "#30065"
-msgid "Install Inputstream Adaptive to see more Livestreams"
+msgctxt "#30150"
+msgid "Related content"
msgstr ""
-msgctxt "#30066"
-msgid "Inputstream Adaptive not installed. Cant play DRM content."
+msgctxt "#30151"
+msgid "Show segements"
msgstr ""
-msgctxt "#30067"
-msgid "Inputstream Helper not installed. Cant play DRM content."
+msgctxt "#30152"
+msgid "Use timeshift"
msgstr ""
\ No newline at end of file
diff --git a/plugin.video.orftvthek/resources/lib/Addon.py b/plugin.video.orftvthek/resources/lib/Addon.py
deleted file mode 100644
index d6a03285a9..0000000000
--- a/plugin.video.orftvthek/resources/lib/Addon.py
+++ /dev/null
@@ -1,319 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-import socket
-import traceback
-import xbmcplugin
-
-from resources.lib.ServiceApi import *
-from resources.lib.HtmlScraper import *
-
-socket.setdefaulttimeout(30)
-
-plugin = "ORF-TVthek-" + xbmcaddon.Addon().getAddonInfo('version')
-
-# initial
-settings = xbmcaddon.Addon()
-pluginhandle = int(sys.argv[1])
-basepath = settings.getAddonInfo('path')
-translation = settings.getLocalizedString
-
-# hardcoded
-videoProtocol = "http"
-videoQuality = "QXB"
-videoDelivery = "HLS"
-
-input_stream_protocol = 'mpd'
-input_stream_drm_version = 'com.widevine.alpha'
-input_stream_mime = 'application/dash+xml'
-input_stream_lic_content_type = 'application/octet-stream'
-
-# media resources
-resource_path = os.path.join(basepath, "resources")
-media_path = os.path.join(resource_path, "media")
-defaultbanner = os.path.join(media_path, "default_banner_v2.jpg")
-news_banner = os.path.join(media_path, "news_banner_v2.jpg")
-recently_added_banner = os.path.join(media_path, "recently_added_banner_v2.jpg")
-shows_banner = os.path.join(media_path, "shows_banner_v2.jpg")
-topics_banner = os.path.join(media_path, "topics_banner_v2.jpg")
-live_banner = os.path.join(media_path, "live_banner_v2.jpg")
-tips_banner = os.path.join(media_path, "tips_banner_v2.jpg")
-most_popular_banner = os.path.join(media_path, "most_popular_banner_v2.jpg")
-schedule_banner = os.path.join(media_path, "schedule_banner_v2.jpg")
-archive_banner = os.path.join(media_path, "archive_banner_v2.jpg")
-search_banner = os.path.join(media_path, "search_banner_v2.jpg")
-trailer_banner = os.path.join(media_path, "trailer_banner_v2.jpg")
-blacklist_banner = os.path.join(media_path, "blacklist_banner_v2.jpg")
-focus_banner = os.path.join(media_path, "focus_banner_v2.jpg")
-defaultbackdrop = os.path.join(media_path, "fanart_v2.jpg")
-
-# load settings
-useServiceAPI = Settings.serviceAPI()
-autoPlayPrompt = Settings.autoPlayPrompt()
-usePlayAllPlaylist = Settings.playAllPlaylist()
-
-# init scrapers
-if useServiceAPI:
- debugLog("Service API activated")
- scraper = serviceAPI(xbmc, settings, pluginhandle, videoQuality, videoProtocol, videoDelivery, defaultbanner, defaultbackdrop, usePlayAllPlaylist)
-else:
- debugLog("HTML Scraper activated")
- scraper = htmlScraper(xbmc, settings, pluginhandle, videoQuality, videoProtocol, videoDelivery, defaultbanner, defaultbackdrop, usePlayAllPlaylist)
-
-
-def getMainMenu():
- debugLog("Building Main Menu")
- addDirectory((translation(30001)).encode("utf-8"), news_banner, defaultbackdrop, "", "", "getAktuelles", pluginhandle)
- addDirectory((translation(30000)).encode("utf-8"), recently_added_banner, defaultbackdrop, "", "", "getNewShows", pluginhandle)
- addDirectory((translation(30002)).encode("utf-8"), shows_banner, defaultbackdrop, "", "", "getSendungen", pluginhandle)
- if useServiceAPI:
- addDirectory((translation(30003)).encode("utf-8"), topics_banner, defaultbackdrop, "", "", "getThemen", pluginhandle)
- addDirectory((translation(30004)).encode("utf-8"), live_banner, defaultbackdrop, "", "", "getLive", pluginhandle)
- if not useServiceAPI:
- addDirectory((translation(30057)).encode("utf-8"), focus_banner, defaultbackdrop, "", "", "getFocus", pluginhandle)
- addDirectory((translation(30006)).encode("utf-8"), most_popular_banner, defaultbackdrop, "", "", "getMostViewed", pluginhandle)
- addDirectory((translation(30018)).encode("utf-8"), schedule_banner, defaultbackdrop, "", "", "getSchedule", pluginhandle)
- if not useServiceAPI:
- addDirectory((translation(30049)).encode("utf-8"), archive_banner, defaultbackdrop, "", "", "getArchiv", pluginhandle)
- addDirectory((translation(30027)).encode("utf-8"), trailer_banner, defaultbackdrop, "", "", "openTrailers", pluginhandle)
- if not useServiceAPI:
- addDirectory((translation(30007)).encode("utf-8"), search_banner, defaultbackdrop, "", "", "getSearchHistory", pluginhandle)
- if Settings.blacklist() and not useServiceAPI:
- addDirectory((translation(30037)).encode("utf-8"), blacklist_banner, defaultbackdrop, "", "", "openBlacklist", pluginhandle)
- listCallback(False, pluginhandle)
-
-
-def listCallback(sort, pluginhandle):
- xbmcplugin.setContent(pluginhandle, 'episodes')
- if sort:
- xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
- xbmcplugin.endOfDirectory(pluginhandle)
-
-
-def startPlaylist(player, playlist):
- if playlist is not None:
- player.play(playlist)
- else:
- d = xbmcgui.Dialog()
- d.ok((translation(30051)).encode("utf-8"), (translation(30050)).encode("utf-8"))
-
-
-def run():
- # video playback
- tvthekplayer = xbmc.Player()
- playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
-
- # parameters
- params = parameters_string_to_dict(sys.argv[2])
- mode = params.get('mode')
- link = params.get('link')
- if mode == 'openSeries':
- playlist.clear()
- playlist = scraper.getLinks(link, params.get('banner'), playlist)
- if autoPlayPrompt and playlist is not None:
- listCallback(False, pluginhandle)
- ok = xbmcgui.Dialog().yesno((translation(30047)).encode("utf-8"), (translation(30048)).encode("utf-8"))
- listCallback(False, pluginhandle)
- if ok:
- debugLog("Starting Playlist for %s" % unqoute_url(link))
- tvthekplayer.play(playlist)
- else:
- debugLog("Running Listcallback from no autoplay openseries")
- listCallback(False, pluginhandle)
- elif mode == 'unblacklistShow':
- heading = translation(30040).encode('UTF-8') % unqoute_url(link).replace('+', ' ').strip()
- if isBlacklisted(link) and xbmcgui.Dialog().yesno(heading, heading):
- unblacklistItem(link)
- xbmc.executebuiltin('Container.Refresh')
- elif mode == 'blacklistShow':
- blacklistItem(link)
- xbmc.executebuiltin('Container.Refresh')
- if mode == 'openBlacklist':
- printBlacklist(defaultbanner, defaultbackdrop, translation, pluginhandle)
- xbmcplugin.endOfDirectory(pluginhandle)
- elif mode == 'getSendungen':
- scraper.getCategories()
- listCallback(True, pluginhandle)
- elif mode == 'getAktuelles':
- scraper.getHighlights()
- listCallback(False, pluginhandle)
- elif mode == 'getLive':
- scraper.getLiveStreams()
- listCallback(False, pluginhandle)
- elif mode == 'getTipps':
- scraper.getTips()
- listCallback(False, pluginhandle)
- elif mode == 'getFocus':
- scraper.getFocus()
- listCallback(False, pluginhandle)
- elif mode == 'getNewShows':
- scraper.getNewest()
- listCallback(False, pluginhandle)
- elif mode == 'getMostViewed':
- scraper.getMostViewed()
- listCallback(False, pluginhandle)
- elif mode == 'getThemen':
- scraper.getThemen()
- listCallback(True, pluginhandle)
- elif mode == 'getSendungenDetail':
- scraper.getCategoriesDetail(link, params.get('banner'))
- listCallback(False, pluginhandle)
- elif mode == 'getThemenDetail':
- scraper.getArchiveDetail(link)
- listCallback(False, pluginhandle)
- elif mode == 'getArchiveDetail':
- scraper.getArchiveDetail(link)
- listCallback(False, pluginhandle)
- elif mode == 'getSchedule':
- scraper.getSchedule()
- listCallback(False, pluginhandle)
- elif mode == 'getArchiv':
- scraper.getArchiv()
- listCallback(False, pluginhandle)
- elif mode == 'getScheduleDetail':
- scraper.openArchiv(link)
- listCallback(True, pluginhandle)
- elif mode == 'openTrailers':
- scraper.getTrailers()
- listCallback(False, pluginhandle)
- elif mode == 'getSearchHistory':
- scraper.getSearchHistory()
- listCallback(False, pluginhandle)
- elif mode == 'getSearchResults':
- if link is not None:
- scraper.getSearchResults(unqoute_url(link))
- else:
- scraper.getSearchResults("")
- listCallback(False, pluginhandle)
- elif mode == 'openDate':
- scraper.getDate(link, params.get('from'))
- listCallback(False, pluginhandle)
- elif mode == 'openProgram':
- scraper.getProgram(link, playlist)
- listCallback(False, pluginhandle)
- elif mode == 'openTopic':
- scraper.getTopic(link)
- listCallback(False, pluginhandle)
- elif mode == 'openEpisode':
- scraper.getEpisode(link, playlist)
- listCallback(False, pluginhandle)
- elif mode == 'liveStreamRestart':
- try:
- import inputstreamhelper
- is_helper = inputstreamhelper.Helper(input_stream_protocol, drm=input_stream_drm_version)
- if is_helper.check_inputstream():
- link = unqoute_url(link)
- debugLog("Restart Source Link: %s" % link)
- headers = "User-Agent=%s&Content-Type=%s" % (Settings.userAgent(), input_stream_lic_content_type)
-
- if params.get('lic_url'):
- lic_url = unqoute_url(params.get('lic_url'))
- debugLog("Playing DRM protected Restart Stream")
- debugLog("Restart License URL: %s" % lic_url)
- streaming_url, play_item = scraper.liveStreamRestart(link, 'dash')
- play_item.setContentLookup(False)
- play_item.setMimeType(input_stream_mime)
- play_item.setProperty('inputstream.adaptive.stream_headers', headers)
- play_item.setProperty('inputstream', is_helper.inputstream_addon)
- play_item.setProperty('inputstream.adaptive.manifest_type', input_stream_protocol)
- play_item.setProperty('inputstream.adaptive.license_type', input_stream_drm_version)
- play_item.setProperty('inputstream.adaptive.license_key', lic_url + '|' + headers +'|R{SSM}|')
- else:
- streaming_url, play_item = scraper.liveStreamRestart(link, 'hls')
- debugLog("Playing Non-DRM protected Restart Stream")
- play_item.setProperty('inputstreamaddon', 'inputstream.adaptive')
- play_item.setProperty('inputstream.adaptive.stream_headers', headers)
- play_item.setProperty('inputstream.adaptive.manifest_type', 'hls')
- debugLog("Restart Stream Url: %s; play_item: %s" % (streaming_url, play_item))
- xbmc.Player().play(streaming_url, play_item)
- else:
- userNotification((translation(30066)).encode("utf-8"))
- except Exception as e:
- debugLog("Exception: %s" % ( e, ), xbmc.LOGINFO)
- debugLog("TB: %s" % ( traceback.format_exc(), ), xbmc.LOGINFO)
- userNotification((translation(30067)).encode("utf-8"))
- elif mode == 'playlist':
- startPlaylist(tvthekplayer, playlist)
- elif mode == 'play':
- link = "%s|User-Agent=%s" % (link, Settings.userAgent())
- play_item = xbmcgui.ListItem(path=link, offscreen=True)
- xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=play_item)
- listCallback(False, pluginhandle)
- elif mode == 'playDRM':
- try:
- import inputstreamhelper
- stream_url = unqoute_url(params.get('link'))
- lic_url = unqoute_url(params.get('lic_url'))
-
- is_helper = inputstreamhelper.Helper(input_stream_protocol, drm=input_stream_drm_version)
- if is_helper.check_inputstream():
- debugLog("Video Url: %s" % stream_url)
- debugLog("DRM License Url: %s" % lic_url)
- play_item = xbmcgui.ListItem(path=stream_url, offscreen=True)
- headers = "User-Agent=%s&Content-Type=%s" % (Settings.userAgent(), input_stream_lic_content_type)
-
- play_item.setContentLookup(False)
- play_item.setMimeType(input_stream_mime)
- play_item.setProperty('inputstream.adaptive.stream_headers', headers)
- play_item.setProperty('inputstream', is_helper.inputstream_addon)
- play_item.setProperty('inputstream.adaptive.manifest_type', input_stream_protocol)
- play_item.setProperty('inputstream.adaptive.license_type', input_stream_drm_version)
- play_item.setProperty('inputstream.adaptive.license_key', lic_url + '|' + headers + '|R{SSM}|')
- xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=play_item)
- else:
- userNotification((translation(30066)).encode("utf-8"))
- listCallback(False, pluginhandle)
- except:
- userNotification((translation(30067)).encode("utf-8"))
- elif mode == 'pvr':
- channel = params.get('channel')
- debugLog("Loading channel %s" % channel)
- data = scraper.getLivestreamByChannel(channel)
- if data:
- video_url = "%s|User-Agent=%s" % (data['url'], Settings.userAgent())
-
- if 'license' in data:
- import inputstreamhelper
- license = data['license']
-
- is_helper = inputstreamhelper.Helper(input_stream_protocol, drm=input_stream_drm_version)
- if is_helper.check_inputstream():
- debugLog("Video Url: %s" % video_url)
- debugLog("DRM License Url: %s" % license)
- play_item = xbmcgui.ListItem(path=video_url)
- play_item.setLabel(data['title'])
- play_item.setLabel2(channel)
- play_item.setProperty('IsPlayable', 'true')
- item_infos = {
- 'title': data['title'],
- 'plot': data['description'],
- 'plotoutline': data['description'],
- }
- play_item.setInfo(type="Video", infoLabels=item_infos)
-
- if 'logo' in data:
- item_art = {
- 'clearlogo': data['logo'],
- 'icon': data['logo'],
- }
- play_item.setArt(item_art)
-
- headers = "User-Agent=%s&Content-Type=%s" % (Settings.userAgent(), input_stream_lic_content_type)
-
- play_item.setContentLookup(False)
- play_item.setMimeType(input_stream_mime)
- play_item.setProperty('inputstream.adaptive.stream_headers', headers)
- play_item.setProperty('inputstream', is_helper.inputstream_addon)
- play_item.setProperty('inputstream.adaptive.manifest_type', input_stream_protocol)
- play_item.setProperty('inputstream.adaptive.license_type', input_stream_drm_version)
- play_item.setProperty('inputstream.adaptive.license_key', license + '|' + headers + '|R{SSM}|')
- xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=play_item)
- else:
- userNotification((translation(30066)).encode("utf-8"))
- else:
- play_item = xbmcgui.ListItem(path=video_url)
- xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=play_item)
- elif sys.argv[2] == '':
- getMainMenu()
- else:
- listCallback(False, pluginhandle)
diff --git a/plugin.video.orftvthek/resources/lib/Base.py b/plugin.video.orftvthek/resources/lib/Base.py
deleted file mode 100644
index 85803d1c8e..0000000000
--- a/plugin.video.orftvthek/resources/lib/Base.py
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-import os
-import re
-
-import simplejson as json
-from kodi_six import xbmcplugin, xbmcgui, xbmcvfs
-from kodi_six.utils import py2_encode, py2_decode
-from . import Settings
-from .Helpers import *
-
-
-def showDialog(title, description):
- xbmcgui.Dialog().notification(title, description, xbmcaddon.Addon().getAddonInfo('icon'))
-
-
-def addDirectory(title, banner, backdrop, description, link, mode, pluginhandle):
- parameters = {"link": link, "mode": mode}
- u = build_kodi_url(parameters)
- createListItem(title, banner, description, '', '', '', u, False, True, backdrop, pluginhandle, None)
-
-
-def generateAddonVideoUrl(videourl):
- videourl = buildLink(videourl)
- return "plugin://%s/?mode=play&link=%s" % (xbmcaddon.Addon().getAddonInfo('id'), videourl)
-
-
-def generateDRMVideoUrl(videourl, drm_lic_url):
- parameters = {"link": videourl, "mode": "playDRM", "lic_url": drm_lic_url}
- return build_kodi_url(parameters)
-
-
-def buildLink(link):
- link = link.replace("https://apasfpd.apa.at", "https://apasfpd.sf.apa.at")
- if link:
- return "%s|User-Agent=%s" % (link, Settings.userAgent())
- else:
- return link
-
-
-def createPlayAllItem(name, pluginhandle, stream_info=False):
- play_all_parameters = {"mode": "playlist"}
- play_all_url = build_kodi_url(play_all_parameters)
- play_all_item = xbmcgui.ListItem(label=name, offscreen=True)
- if stream_info:
- description = stream_info['description']
- play_all_item.setArt({'thumb': stream_info['teaser_image'], 'poster': stream_info['teaser_image']})
- else:
- description = ""
- play_all_item.setInfo(type="Video", infoLabels={"Title": name, "Plot": description})
- xbmcplugin.addDirectoryItem(pluginhandle, play_all_url, play_all_item, isFolder=False, totalItems=-1)
-
-
-def createListItem(title, banner, description, duration, date, channel, videourl, playable, folder, backdrop, pluginhandle, subtitles=None, blacklist=False, contextMenuItems=None):
- contextMenuItems = contextMenuItems or []
-
- liz = xbmcgui.ListItem(label=title, label2=channel, offscreen=True)
- liz.setInfo(type="Video", infoLabels={"Title": title})
- liz.setInfo(type="Video", infoLabels={"Tvshowtitle": title})
- liz.setInfo(type="Video", infoLabels={"Sorttitle": title})
- liz.setInfo(type="Video", infoLabels={"Plot": description})
- liz.setInfo(type="Video", infoLabels={"Plotoutline": description})
- liz.setInfo(type="Video", infoLabels={"Aired": date})
- liz.setInfo(type="Video", infoLabels={"Studio": channel})
- liz.setProperty('fanart_image', backdrop)
- liz.setProperty('IsPlayable', str(playable and not folder))
- liz.setArt({'thumb': banner, 'poster': banner, 'fanart': backdrop, "icon": banner})
-
- if not folder:
- liz.setInfo(type="Video", infoLabels={"mediatype": 'video'})
- videoStreamInfo = {'codec': 'h264', 'aspect': 1.78}
- try:
- videoStreamInfo.update({'duration': int(duration)})
- except (TypeError, ValueError):
- debugLog("No Duration found in Video")
- if videourl.lower().endswith('_qxb.mp4') or '_qxb' in videourl.lower():
- videoStreamInfo.update({'width': 1280, 'height': 720})
- if videourl.lower().endswith('_q8c.mp4') or '_q8c' in videourl.lower():
- videoStreamInfo.update({'width': 1280, 'height': 720})
- elif videourl.lower().endswith('_q6a.mp4') or '_q6a' in videourl.lower():
- videoStreamInfo.update({'width': 960, 'height': 540})
- elif videourl.lower().endswith('_q4a.mp4') or '_q4a' in videourl.lower():
- videoStreamInfo.update({'width': 640, 'height': 360})
- else:
- videoStreamInfo.update({'width': 320, 'height': 180})
- liz.addStreamInfo('video', videoStreamInfo)
-
- liz.addStreamInfo('audio', {"codec": "aac", "language": "de", "channels": 2})
- if subtitles is not None and Settings.subtitles():
- if len(subtitles) > 0 and subtitles[0].endswith('.srt'):
- subtitles.pop(0)
- liz.addStreamInfo('subtitle', {"language": "de"})
- try:
- liz.setSubtitles(subtitles)
- except AttributeError:
- # setSubtitles was introduced in Helix (v14)
- # catch the error in Gotham (v13)
- pass
-
- if blacklist:
- match = re.search(r'( - \w\w, \d\d.\d\d.\d\d\d\d)', title)
- if match is not None:
- bltitle = title.split(" - ")
- bltitle = bltitle[0].split(": ")
-
- bl_title = bltitle[0].replace("+", " ").strip()
- else:
- bl_title = title.replace("+", " ").strip()
-
- blparameters = {"mode": "blacklistShow", "link": bl_title}
- blurl = build_kodi_url(blparameters)
- contextMenuItems.append(('%s %s %s' % (Settings.localizedString(30038).encode("utf-8"), bl_title, Settings.localizedString(30042).encode("utf-8")), 'XBMC.RunPlugin(%s)' % blurl))
- if checkBlacklist(bl_title):
- return
-
- liz.addContextMenuItems(contextMenuItems)
- xbmcplugin.addDirectoryItem(pluginhandle, url=videourl, listitem=liz, isFolder=folder)
- return liz
-
-
-def checkBlacklist(title):
- addonUserDataFolder = xbmcvfs.translatePath("special://profile/addon_data/plugin.video.orftvthek")
- bl_json_file = os.path.join(addonUserDataFolder, 'blacklist.json')
- if os.path.exists(bl_json_file):
- if os.path.getsize(bl_json_file) > 0:
- data = getJsonFile(bl_json_file)
- tmp = data
- for item in tmp:
- if py2_decode(item) == py2_decode(title):
- return True
- return False
-
-
-def removeBlacklist(title):
- addonUserDataFolder = xbmcvfs.translatePath("special://profile/addon_data/plugin.video.orftvthek")
- bl_json_file = os.path.join(addonUserDataFolder, 'blacklist.json')
- if os.path.exists(bl_json_file):
- if os.path.getsize(bl_json_file) > 0:
- data = getJsonFile(bl_json_file)
- tmp = data
- for item in tmp:
- if item.encode('UTF-8') == title:
- tmp.remove(item)
- saveJsonFile(tmp, bl_json_file)
-
-
-def printBlacklist(banner, backdrop, translation, pluginhandle):
- addonUserDataFolder = xbmcvfs.translatePath("special://profile/addon_data/plugin.video.orftvthek")
- bl_json_file = os.path.join(addonUserDataFolder, 'blacklist.json')
- if os.path.exists(bl_json_file):
- if os.path.getsize(bl_json_file) > 0:
- data = getJsonFile(bl_json_file)
- for item in data:
- item = item.encode('UTF-8')
- description = translation(30040).encode('UTF-8') % item
- parameters = {'link': item, 'mode': 'unblacklistShow'}
- url = build_kodi_url(parameters)
- createListItem(item, banner, description, None, None, None, url, False, False, backdrop, pluginhandle)
-
-
-def saveJsonFile(data, file):
- with open(file, 'w') as data_file:
- data_file.write(json.dumps(data, 'utf-8'))
- data_file.close()
-
-
-def getJsonFile(file):
- with open(file, 'r') as data_file:
- data = json.load(data_file, 'UTF-8')
- return data
-
-
-def blacklistItem(title):
- addonUserDataFolder = xbmcvfs.translatePath("special://profile/addon_data/plugin.video.orftvthek")
- bl_json_file = os.path.join(addonUserDataFolder, 'blacklist.json')
- title = unqoute_url(title)
- title = title.replace("+", " ").strip()
- # check if file exists
- if os.path.exists(bl_json_file):
- # check if file already has an entry
- if os.path.getsize(bl_json_file) > 0:
- # append value to JSON File
- if not checkBlacklist(title):
- data = getJsonFile(bl_json_file)
- data.append(title)
- saveJsonFile(data, bl_json_file)
- # found empty file - writing first record
- else:
- data = []
- data.append(title)
- saveJsonFile(data, bl_json_file)
- # create json file
- else:
- if not os.path.exists(addonUserDataFolder):
- os.makedirs(addonUserDataFolder)
- data = []
- data.append(title)
- saveJsonFile(data, bl_json_file)
-
-
-def unblacklistItem(title):
- title = unqoute_url(title)
- title = title.replace("+", " ").strip()
- removeBlacklist(title)
-
-
-def isBlacklisted(title):
- title = unqoute_url(title)
- title = py2_decode(title.replace("+", " ").strip())
- return checkBlacklist(title)
-
-
-def searchHistoryPush(title):
- addonUserDataFolder = xbmcvfs.translatePath("special://profile/addon_data/plugin.video.orftvthek")
- json_file = os.path.join(addonUserDataFolder, 'searchhistory.json')
- title = unqoute_url(title)
- title = title.replace("+", " ").strip()
- # check if file exists
- if os.path.exists(json_file):
- # check if file already has an entry
- if os.path.getsize(json_file) > 0:
- # append value to JSON File
- data = getJsonFile(json_file)
- data.append(title)
- saveJsonFile(data, json_file)
- # found empty file - writing first record
- else:
- data = []
- data.append(title)
- saveJsonFile(data, json_file)
- # create json file
- else:
- if not os.path.exists(addonUserDataFolder):
- os.makedirs(addonUserDataFolder)
- data = []
- data.append(title)
- saveJsonFile(data, json_file)
-
-def searchHistoryGet():
- addonUserDataFolder = xbmcvfs.translatePath("special://profile/addon_data/plugin.video.orftvthek")
- json_file = os.path.join(addonUserDataFolder, 'searchhistory.json')
- if os.path.exists(json_file):
- if os.path.getsize(json_file) > 0:
- data = getJsonFile(json_file)
- return data
- return []
\ No newline at end of file
diff --git a/plugin.video.orftvthek/resources/lib/Common.py b/plugin.video.orftvthek/resources/lib/Common.py
deleted file mode 100644
index 456525c230..0000000000
--- a/plugin.video.orftvthek/resources/lib/Common.py
+++ /dev/null
@@ -1,295 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
- Parsedom for XBMC plugins
- Copyright (C) 2010-2011 Tobias Ussing And Henrik Mosgaard Jensen
-
- This program 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 3 of the License, or
- (at your option) any later version.
-
- This program 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 this program. If not, see .
-"""
-
-from kodi_six.utils import py2_encode, py2_decode
-from future.builtins import str
-from future.builtins import range
-import sys
-import xbmc
-
-PY3 = sys.version_info.major >=3
-if PY3:
- from urllib.parse import unquote, urlencode
- from urllib.request import urlopen as OpenRequest
- from urllib.request import Request as HTTPRequest
- from urllib.error import HTTPError
- from html.parser import HTMLParser
- from html import unescape
-else:
- from urllib import unquote, urlencode
- from urllib2 import urlopen as OpenRequest
- from urllib2 import Request as HTTPRequest
- from urllib2 import HTTPError
- from HTMLParser import HTMLParser
- parser = HTMLParser()
- unescape = parser.unescape
-
-import re
-
-USERAGENT = u"Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1"
-DEBUG_SCRAPER = False
-
-def replaceHTMLCodes(txt):
- log(repr(txt))
- txt = re.sub('([0-9]+)([^;^0-9]+)', '\\1;\\2', txt)
- txt = unescape(txt)
- txt = txt.replace("&", "&")
- log(repr(txt))
- return txt
-
-
-def stripTags(html):
- log(repr(html))
- sub_start = html.find("<")
- sub_end = html.find(">")
- while sub_end > sub_start > -1:
- html = html.replace(html[sub_start:sub_end + 1], "").strip()
- sub_start = html.find("<")
- sub_end = html.find(">")
-
- log(repr(html))
- return html
-
-
-def _getDOMContent(html, name, match, ret): # Cleanup
- log("match: " + match)
-
- endstr = u"" + name # + ">"
-
- start = html.find(match)
- end = html.find(endstr, start)
- pos = html.find("<" + name, start + 1 )
-
- log(str(start) + " < " + str(end) + ", pos = " + str(pos) + ", endpos: " + str(end))
-
- while pos < end and pos != -1: # Ignore too early return
- tend = html.find(endstr, end + len(endstr))
- if tend != -1:
- end = tend
- pos = html.find("<" + name, pos + 1)
- log("loop: " + str(start) + " < " + str(end) + " pos = " + str(pos))
-
- log("start: %s, len: %s, end: %s" % (start, len(match), end))
- if start == -1 and end == -1:
- result = u""
- elif start > -1 and end > -1:
- result = html[start + len(match):end]
- elif end > -1:
- result = html[:end]
- elif start > -1:
- result = html[start + len(match):]
-
- if ret:
- endstr = html[end:html.find(">", html.find(endstr)) + 1]
- result = match + result + endstr
-
- log("done result length: " + str(len(result)))
- return result
-
-def _getDOMAttributes(match, name, ret):
- lst = re.compile('<' + name + '.*?' + ret + '=([\'"].[^>]*?[\'"])>', re.M | re.S).findall(match)
- if len(lst) == 0:
- lst = re.compile('<' + name + '.*?' + ret + '=(.[^>]*?)>', re.M | re.S).findall(match)
- ret = []
- for tmp in lst:
- cont_char = tmp[0]
- if cont_char in "'\"":
- log("Using %s as quotation mark" % cont_char)
-
- # Limit down to next variable.
- if tmp.find('=' + cont_char, tmp.find(cont_char, 1)) > -1:
- tmp = tmp[:tmp.find('=' + cont_char, tmp.find(cont_char, 1))]
-
- # Limit to the last quotation mark
- if tmp.rfind(cont_char, 1) > -1:
- tmp = tmp[1:tmp.rfind(cont_char)]
- else:
- log("No quotation mark found")
- if tmp.find(" ") > 0:
- tmp = tmp[:tmp.find(" ")]
- elif tmp.find("/") > 0:
- tmp = tmp[:tmp.find("/")]
- elif tmp.find(">") > 0:
- tmp = tmp[:tmp.find(">")]
-
- ret.append(tmp.strip())
-
- log("Done: " + repr(ret))
- return ret
-
-def _getDOMElements(item, name, attrs):
- lst = []
- for key in attrs:
- lst2 = re.compile('(<' + name + '[^>]*?(?:' + key + '=[\'"]' + attrs[key] + '[\'"].*?>))', re.M | re.S).findall(item)
- if len(lst2) == 0 and attrs[key].find(" ") == -1: # Try matching without quotation marks
- lst2 = re.compile('(<' + name + '[^>]*?(?:' + key + '=' + attrs[key] + '.*?>))', re.M | re.S).findall(item)
- if len(lst) == 0:
- log("Setting main list " + repr(lst2))
- lst = lst2
- lst2 = []
- else:
- log("Setting new list " + repr(lst2))
- test = list(range(len(lst)))
- test.reverse()
- for i in test: # Delete anything missing from the next list.
- if not lst[i] in lst2:
- log("Purging mismatch " + str(len(lst)) + " - " + repr(lst[i]))
- del(lst[i])
-
- if len(lst) == 0 and attrs == {}:
- log("No list found, trying to match on name only")
- lst = re.compile('(<' + name + '>)', re.M | re.S).findall(item)
- if len(lst) == 0:
- lst = re.compile('(<' + name + ' .*?>)', re.M | re.S).findall(item)
-
- log("Done: " + str(type(lst)))
- return lst
-
-
-def parseDOM(html, name=u"", attrs={}, ret=False):
- log("Name: " + repr(name) + " - Attrs:" + repr(attrs) + " - Ret: " + repr(ret) + " - HTML: " + str(type(html)))
- ret = py2_decode(ret)
- if isinstance(name, str):
- try:
- name = name
- except:
- log("Couldn't decode name binary string: " + repr(name))
-
- if isinstance(html, str):
- try:
- html = [py2_decode(html)]
- except:
- log("Couldn't decode html binary string. Data length: " + repr(len(html)))
- html = [html]
- elif isinstance(html, bytes):
- html = [html.decode('ascii')]
- elif str(type(html)) == "":
- html = [str(html)]
- elif not isinstance(html, list):
- log("Input isn't list or string/unicode.")
- return u""
-
- if not name.strip():
- log("Missing tag name")
- return u""
-
- ret_lst = []
- for item in html:
- temp_item = re.compile('(<[^>]*?\n[^>]*?>)').findall(item)
- for match in temp_item:
- item = item.replace(match, match.replace("\n", " "))
-
- lst = _getDOMElements(item, name, attrs)
-
- if isinstance(ret, str):
- log("Getting attribute %s content for %s matches " % (ret, len(lst)))
- lst2 = []
- for match in lst:
- lst2 += _getDOMAttributes(match, name, ret)
- lst = lst2
- else:
- log("Getting element content for %s matches " % len(lst))
- lst2 = []
- for match in lst:
- log("Getting element content for %s" % match)
- temp = _getDOMContent(item, name, match, ret).strip()
- item = item[item.find(temp, item.find(match)) + len(temp):]
- lst2.append(temp)
- lst = lst2
- ret_lst += lst
-
- log("Done: " + repr(ret_lst))
- return ret_lst
-
-def fetchPage(params={}):
- get = params.get
- link = get("link")
- ret_obj = {}
- if get("post_data"):
- log("called for : " + repr(params['link']))
- else:
- log("called for : " + repr(params))
-
- if not link or int(get("error", "0")) > 2:
- log("giving up")
- ret_obj["status"] = 500
- return ret_obj
-
- if get("post_data"):
- if get("hide_post_data"):
- log("Posting data")
- else:
- log("Posting data: " + urlencode(get("post_data")))
-
- request = HTTPRequest(link, urlencode(get("post_data")))
- request.add_header('Content-Type', 'application/x-www-form-urlencoded')
- else:
- log("Got request")
- request = HTTPRequest(link)
-
- if get("headers"):
- for head in get("headers"):
- request.add_header(head[0], head[1])
-
- request.add_header('User-Agent', USERAGENT)
-
- if get("cookie"):
- request.add_header('Cookie', get("cookie"))
-
- if get("refering"):
- request.add_header('Referer', get("refering"))
-
- try:
- log("connecting to server...")
-
- con = OpenRequest(request)
- ret_obj["header"] = con.info()
- ret_obj["new_url"] = con.geturl()
- if get("no-content", "false") == u"false" or get("no-content", "false") == "false":
- inputdata = con.read()
- ret_obj["content"] = inputdata.decode("utf-8")
-
- con.close()
-
- log("Done")
- ret_obj["status"] = 200
- return ret_obj
-
- except HTTPError as e:
- err = str(e)
- log("HTTPError : " + err)
- log("HTTPError - Headers: " + str(e.headers) + " - Content: " + e.fp.read())
-
- params["error"] = str(int(get("error", "0")) + 1)
- ret = fetchPage(params)
-
- if not "content" in ret and e.fp:
- ret["content"] = e.fp.read()
- return ret
-
- ret_obj["status"] = 500
- return ret_obj
-
-
-def log(msg):
- if DEBUG_SCRAPER:
- output = py2_encode(msg)
- xbmc.log(output, xbmc.LOGDEBUG)
diff --git a/plugin.video.orftvthek/resources/lib/Helpers.py b/plugin.video.orftvthek/resources/lib/Helpers.py
deleted file mode 100644
index cc0165d5fb..0000000000
--- a/plugin.video.orftvthek/resources/lib/Helpers.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-from kodi_six.utils import py2_encode, py2_decode
-
-import xbmc
-import xbmcgui
-import xbmcaddon
-import sys
-
-PY3 = sys.version_info.major >=3
-if PY3:
- from urllib.parse import unquote, urlencode
- from urllib.request import urlopen as OpenRequest
- from urllib.request import Request as HTTPRequest
- from urllib.error import HTTPError
-else:
- from urllib import unquote, urlencode
- from urllib2 import HTTPError
- from urllib2 import urlopen as OpenRequest
- from urllib2 import Request as HTTPRequest
-
-
-def unqoute_url(url):
- try:
- return unquote(url)
- except:
- return unquote(url.encode('utf-8'))
-
-
-def build_kodi_url(parameters):
- return sys.argv[0] + '?' + encode_parameters(parameters)
-
-
-def encode_parameters(parameters):
- parameters = { k: v if v is not None else ""
- for k, v in parameters.items() }
- try:
- return urlencode(parameters)
- except:
- parameters = {k: unicode(v).encode("utf-8") for k, v in list(parameters.items())}
- return urlencode(parameters)
-
-
-def url_get_request(url, authorization=False):
- if authorization:
- request = HTTPRequest(url)
- request.add_header('Authorization', 'Basic %s' % authorization)
- else:
- request = url
- return OpenRequest(request)
-
-
-def parameters_string_to_dict(parameters):
- paramDict = {}
- if parameters:
- paramPairs = parameters[1:].split("&")
- for paramsPair in paramPairs:
- paramSplits = paramsPair.split('=')
- if (len(paramSplits)) == 2:
- paramDict[paramSplits[0]] = paramSplits[1]
- return paramDict
-
-
-def debugLog(message, loglevel=xbmc.LOGDEBUG):
- output = py2_encode(message)
- xbmc.log(msg=output, level=loglevel)
-
-def userNotification(message, title="ORF TVThek"):
- output = py2_encode(message)
- xbmcgui.Dialog().notification(title, output, icon=xbmcgui.NOTIFICATION_ERROR)
diff --git a/plugin.video.orftvthek/resources/lib/HtmlScraper.py b/plugin.video.orftvthek/resources/lib/HtmlScraper.py
deleted file mode 100644
index 8bf857b638..0000000000
--- a/plugin.video.orftvthek/resources/lib/HtmlScraper.py
+++ /dev/null
@@ -1,1249 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-import datetime
-from .Common import *
-
-from .Base import *
-from .Scraper import *
-
-
-class htmlScraper(Scraper):
- __urlBase = 'https://tvthek.orf.at'
- __urlLive = __urlBase + '/live'
- __urlMostViewed = __urlBase + '/most-viewed'
- __urlNewest = __urlBase + '/newest'
- __urlSchedule = __urlBase + '/schedule'
- __urlSearch = __urlBase + '/search'
- __urlShows = __urlBase + '/profiles'
- __urlTips = __urlBase + '/tips'
- __urlFocus = __urlBase + '/in-focus'
- __urlTopics = __urlBase + '/topics'
- __urlTopicLane = __urlBase + '/lane/topic/'
- __urlArchive = __urlBase + '/history'
- __urlTrailer = __urlBase + '/coming-soon'
-
- __videoQualities = ["Q1A", "Q4A", "Q6A", "Q8C", "QXB", "QXA"]
-
- _bundeslandMap = {
- 'orf2b': 'Burgenland',
- 'orf2stmk': 'Steiermark',
- 'orf2w': 'Wien',
- 'orf2ooe': 'Oberösterreich',
- 'orf2k': 'Kärnten',
- 'orf2n': 'Niederösterreich',
- 'orf2s': 'Salzburg',
- 'orf2v': 'Vorarlberg',
- 'orf2t': 'Tirol',
- }
-
- def __init__(self, xbmc, settings, pluginhandle, quality, protocol, delivery, defaultbanner, defaultbackdrop, usePlayAllPlaylist):
- self.translation = settings.getLocalizedString
- self.xbmc = xbmc
- self.videoQuality = quality
- self.videoDelivery = delivery
- self.videoProtocol = protocol
- self.pluginhandle = pluginhandle
- self.defaultbanner = defaultbanner
- self.defaultbackdrop = defaultbackdrop
- self.enableBlacklist = settings.getSetting("enableBlacklist") == "true"
- self.usePlayAllPlaylist = usePlayAllPlaylist
- debugLog('HTML Scraper - Init done')
-
- def getLivestreamByChannel(self, channel):
- html = fetchPage({'link': self.__urlLive})
- wrapper = parseDOM(html.get("content"), name='div', attrs={'class': 'b-livestream-per-channel'})
- channels = parseDOM(wrapper, name='div', attrs={'class': 'b-lane.*?'})
-
- for result in channels:
- channel_detail_url = False
- channel_logo = parseDOM(result, name='img', attrs={'class': 'channel-logo'}, ret='src')
- channel_id = re.findall(r'[^\/]+(?=\.)', str(channel_logo))[0].replace('-logo', '')
- channel_program = parseDOM(result, name='a', attrs={'class': 'js-link-box'}, ret='title')[0]
-
- if channel in self._bundeslandMap:
- channel = 'orf2'
- bundesland_article = parseDOM(result, name='li', attrs={'class': '.*?is-bundesland-heute.*?'}, ret='data-jsb')
- if len(bundesland_article):
- bundesland_data = replaceHTMLCodes(bundesland_article[0])
- bundesland_data = json.loads(bundesland_data)
- for bundesland_item_key in bundesland_data:
- bundesland_item = bundesland_data.get(bundesland_item_key)
- if bundesland_item and bundesland_item is not True and len(bundesland_item):
- if self._bundeslandMap[channel] == bundesland_item.get('bundesland'):
- channel_detail_url = bundesland_item.get('url')
-
- if not channel_detail_url and channel_id == channel:
- channel_detail_url = parseDOM(result, name='a', attrs={'class': 'js-link-box'}, ret='href')[0]
-
- if channel_detail_url:
- channel_html = fetchPage({'link': channel_detail_url})
- player = parseDOM(channel_html.get("content"), name='div', attrs={'class': "player_viewport.*?"})
- player_meta = parseDOM(channel_html.get("content"), name='section', attrs={'class': "b-video-details.*?"})
- if len(player):
- data = parseDOM(player[0], name='div', attrs={}, ret="data-jsb")
- channel_description = parseDOM(player_meta[0], name='p', attrs={'class': "description-text.*?"})[0]
- license_url = self.getLivestreamDRM(data)
- video_url = self.getLivestreamUrl(data, self.videoQuality)
- # Remove Get Parameters because InputStream Adaptive cant handle it.
- video_url = re.sub(r"\?[\S]+", '', video_url, 0)
-
- uhd_25_video_url = self.getLivestreamUrl(data, 'UHD', True)
- if uhd_25_video_url:
- uhd_50_video_url = uhd_25_video_url.replace('_uhd_25/', '_uhd_50/')
- video_url = uhd_50_video_url
- if license_url:
- return {'title': channel_program, 'description': channel_description, 'url': video_url,'license': license_url}
- else:
- return {'title': channel_program, 'description': channel_description, 'url': video_url}
- return []
-
- def getMostViewed(self):
- self.getTeaserList(self.__urlMostViewed, "b-teasers-list")
-
- def getNewest(self):
- self.getTeaserList(self.__urlNewest, "b-teasers-list")
-
- def getTips(self):
- self.getTeaserList(self.__urlTips, "b-teasers-list")
-
- # Parses the Frontpage Carousel
- def getHighlights(self):
- self.getTeaserSlideshow(self.__urlBase)
- self.getTeaserList(self.__urlBase, "stage-subteaser-list")
-
- def getTrailers(self):
- self.getTeaserList(self.__urlTrailer, "b-teasers-list")
-
- def getFocus(self):
- self.getLaneTopicOverview(self.__urlFocus)
-
- # Extracts VideoURL from JSON String
- def getVideoUrl(self, sources, drm_license=None):
- for source in sources:
- if drm_license and source['quality'].lower()[0:3] == self.videoQuality.lower() and source['delivery'].lower() == 'dash':
- debugLog("Found DRM Video Url %s" % source["src"])
- return generateDRMVideoUrl(source["src"], drm_license)
- elif source["protocol"].lower() == self.videoProtocol.lower() and source["delivery"].lower() == self.videoDelivery.lower() and source["quality"].lower() == self.videoQuality.lower():
- debugLog("Found Simple Video Url %s" % source["src"])
- return generateAddonVideoUrl(source["src"])
- return False
-
- # Parses teaser lists
- def getTeaserList(self, url, list_class, list_type="ul"):
- url = unqoute_url(url)
- html = fetchPage({'link': url})
- container = parseDOM(html.get("content"), name='main', attrs={'class': "main"}, ret=False)
- teasers = parseDOM(container, name=list_type, attrs={'class': list_class}, ret=False)
- items = parseDOM(teasers, name='article', attrs={'class': "b-teaser"}, ret=False)
-
- for item in items:
- subtitle = parseDOM(item, name='h4', attrs={'class': "profile"}, ret=False)
- subtitle = replaceHTMLCodes(subtitle[0])
-
- title = parseDOM(item, name='h5', attrs={'class': "teaser-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- desc = parseDOM(item, name='p', attrs={'class': "description.*?"}, ret=False)
- if len(desc):
- desc = replaceHTMLCodes(desc[0])
- else:
- desc = ""
-
- channel = parseDOM(item, name='p', attrs={'class': "channel"}, ret=False)
- if len(channel):
- channel = replaceHTMLCodes(channel[0])
- else:
- channel = ""
- date = parseDOM(item, name='span', attrs={'class': 'date'}, ret=False)
- date = date[0]
-
- time = parseDOM(item, name='span', attrs={'class': 'time'}, ret=False)
- time = time[0]
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='data-src')
- image = replaceHTMLCodes(image[0])
-
- link = parseDOM(item, name='a', attrs={'class': 'teaser-link.*?'}, ret='href')
- link = link[0]
-
- desc = self.formatDescription(title, channel, subtitle, desc, date, time)
-
- parameters = {"link": link, "banner": image, "mode": "openSeries"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- def getLaneTopicOverview(self, url):
- html = fetchPage({'link': url})
- container = parseDOM(html.get("content"), name='section', attrs={'class': "b-list-container"}, ret=False)
-
- items = parseDOM(container, name='div', attrs={'class': "b-lane.*?"}, ret=False)
-
- for item in items:
- title_link = parseDOM(item, name='h3', attrs={'class': "title"}, ret=False)
-
- title = parseDOM(title_link, name='a', attrs={}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- link = parseDOM(title_link, name='a', attrs={}, ret='href')
- link = link[0]
- link = "%s%s" % (self.__urlBase, link)
-
- desc = ""
- desc = self.formatDescription(title, "", "", desc, "", "")
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- parameters = {"link": link, "banner": image, "mode": "getArchiveDetail"}
-
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- def formatDescription(self, title, channel, subtitle, desc, date, time):
- date_prefix = self.translation(30009)
-
- # Reformat Title
- if subtitle != title:
- if len(subtitle):
- title = "%s | %s" % (title, subtitle)
- if date != "":
- title = "%s - %s" % (title, date)
-
- # Reformat
- if len(subtitle):
- subtitle = re.sub("\s\s+", " ", str(subtitle))
- if subtitle == title:
- subtitle = ""
- else:
- if len(channel):
- subtitle = " | [LIGHT]%s[/LIGHT]" % subtitle
- else:
- subtitle = "[LIGHT]%s[/LIGHT]" % subtitle
- else:
- subtitle = ""
-
- if len(desc):
- desc = "[CR]%s" % desc
- else:
- desc = ""
-
- if len(channel):
- channel = "[B]%s[/B]" % channel
- else:
- channel = ""
-
- if len(date):
- return "%s%s[CR]%s[CR][I]%s %s - %s[/I]" % (channel, subtitle, desc, date_prefix, date, time)
- else:
- return "%s%s[CR]%s" % (channel, subtitle, desc)
-
- # Parses the frontpage teaser slider
- def getTeaserSlideshow(self, url):
- url = unqoute_url(url)
- html = fetchPage({'link': url})
- container = parseDOM(html.get("content"), name='main', attrs={'class': "main"}, ret=False)
- teasers = parseDOM(container, name='div', attrs={'class': "stage-item-list.*?"}, ret=False)
- items = parseDOM(teasers, name='a', attrs={'class': "stage-item.*?"}, ret=False)
- items_href = parseDOM(teasers, name='a', attrs={'class': "stage-item.*?"}, ret='href')
- current = 0
- for item in items:
- subtitle = parseDOM(item, name='h2', attrs={'class': "stage-item-profile-title"}, ret=False)
- subtitle = replaceHTMLCodes(subtitle[0])
-
- title = parseDOM(item, name='h3', attrs={'class': "stage-item-teaser-title"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- figure = parseDOM(item, name='figure', attrs={'class': 'stage-item-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={'class': "lazyload"}, ret='data-src')
-
- image = replaceHTMLCodes(image[0])
-
- link = items_href[current]
- link = link
-
- # Reformat Title
- if subtitle != title:
- title = "%s | %s" % (subtitle, title)
-
- parameters = {"link": link, "banner": image, "mode": "openSeries"}
-
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", "", "", "", "", url)
- current += 1
-
- # Scrapes the detail page for a schedule day selection (missed a show)
- def openArchiv(self, url):
- url = unqoute_url(url)
- html = fetchPage({'link': url})
- container = parseDOM(html.get("content"), name='main', attrs={'class': "main"}, ret=False)
- teasers = parseDOM(container, name='div', attrs={'class': "b-schedule-list"}, ret=False)
- items = parseDOM(teasers, name='article', attrs={'class': "b-schedule-episode.*?"}, ret=False)
-
- date = parseDOM(teasers, name='h2', attrs={'class': 'day-title.*?'}, ret=False)
- if len(date):
- date = date[0]
- else:
- date = ""
-
- for item in items:
- title = parseDOM(item, name='h4', attrs={'class': "item-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- desc = parseDOM(item, name='div', attrs={'class': "item-description.*?"}, ret=False)
- if len(desc):
- desc = replaceHTMLCodes(desc[0])
- desc = stripTags(desc)
- else:
- desc = ""
-
- channel = parseDOM(item, name='span', attrs={'class': "small-information.meta.meta-channel-name"}, ret=False)
- if len(channel):
- channel = replaceHTMLCodes(channel[0])
- else:
- channel = ""
-
- time = parseDOM(item, name='span', attrs={'class': 'meta.meta-time'}, ret=False)
- time = time[0]
-
- title = "[%s] %s" % (time, title)
-
- subtitle = time
-
- image = parseDOM(item, name='img', attrs={}, ret='src')
- if len(image):
- image = replaceHTMLCodes(image[0])
- else:
- image = ""
-
- link = parseDOM(item, name='a', attrs={'class': 'episode-content'}, ret='href')
- link = link[0]
-
- desc = self.formatDescription(title, channel, subtitle, desc, date, time)
-
- parameters = {"link": link, "banner": image, "mode": "openSeries"}
-
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- # Parses the Frontpage Show Overview Carousel
- def getCategories(self):
- html = fetchPage({'link': self.__urlShows})
- container = parseDOM(html.get("content"), name='main', attrs={'class': "main"}, ret=False)
- teasers = parseDOM(container, name='div', attrs={'class': "b-profile-results-container.*?"}, ret=False)
- items = parseDOM(teasers, name='article', attrs={'class': "b-teaser"}, ret=False)
-
- for item in items:
- subtitle = parseDOM(item, name='h4', attrs={'class': "profile"}, ret=False)
- subtitle = replaceHTMLCodes(subtitle[0])
-
- title = parseDOM(item, name='h5', attrs={'class': "teaser-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- desc = parseDOM(item, name='p', attrs={'class': "description.*?"}, ret=False)
- if len(desc):
- desc = replaceHTMLCodes(desc[0])
- else:
- desc = ""
-
- channel = parseDOM(item, name='p', attrs={'class': "channel"}, ret=False)
- if len(channel):
- channel = replaceHTMLCodes(channel[0])
- else:
- channel = ""
- date = parseDOM(item, name='span', attrs={'class': 'date'}, ret=False)
- date = date[0]
-
- time = parseDOM(item, name='span', attrs={'class': 'time'}, ret=False)
- time = time[0]
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- link = parseDOM(item, name='a', attrs={'class': 'teaser-link.*?'}, ret='href')
- link = link[0]
-
- try:
- regex = r"https://tvthek.orf.at/profile/(.*)/(.*)/(.*)/(.*)"
- matches = re.search(regex, link)
- name_path = matches.group(1)
- id_path = matches.group(2)
- link = "%s/%s/%s/%s" % (self.__urlBase, "profile", name_path, id_path)
- except IndexError:
- debugLog("Not a standard show link. Using default url: %s" % link)
-
- desc = self.formatDescription(title, channel, subtitle, desc, date, time)
- debugLog("Link: %s" % link)
- parameters = {"link": link, "banner": image, "mode": "getSendungenDetail"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- # Parses Details for the selected Show
- def getCategoriesDetail(self, category, banner):
- url = unqoute_url(category)
- banner = unqoute_url(banner)
- html = fetchPage({'link': url})
- container = parseDOM(html.get("content"), name='main', attrs={'class': "main"}, ret=False)
-
- # Main Episode
- main_episode_container = parseDOM(container, name='section', attrs={'class': "b-video-details.*?"}, ret=False)
-
- title = parseDOM(main_episode_container, name='h2', attrs={'class': "description-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- subtitle = parseDOM(main_episode_container, name='span', attrs={'class': "js-subheadline"}, ret=False)
- if len(subtitle):
- subtitle = replaceHTMLCodes(subtitle[0])
- else:
- subtitle = ""
-
- desc = parseDOM(main_episode_container, name='p', attrs={'class': "description-text.*?"}, ret=False)
- if len(desc):
- desc = replaceHTMLCodes(desc[0])
- else:
- desc = ""
-
- channel = parseDOM(main_episode_container, name='span', attrs={'class': "channel.*?"}, ret="aria-label")
- if len(channel):
- channel = replaceHTMLCodes(channel[0])
- else:
- channel = ""
-
- date = parseDOM(main_episode_container, name='span', attrs={'class': 'date'}, ret=False)
- date = date[0]
-
- time = parseDOM(main_episode_container, name='span', attrs={'class': 'time'}, ret=False)
- time = time[0]
-
- image = banner
-
- if date != "":
- title = "%s - %s" % (title, date)
-
- desc = self.formatDescription(title, channel, subtitle, desc, date, time)
-
- parameters = {"link": url, "banner": image, "mode": "openSeries"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- # More Episodes
- more_episode_container = parseDOM(container, name='section', attrs={'class': "related-videos"}, ret=False)
- more_episode_json = parseDOM(more_episode_container, name="div", attrs={'class': 'more-episodes.*?'}, ret='data-jsb')
- if len(more_episode_json):
- more_episode_json_raw = replaceHTMLCodes(more_episode_json[0])
- more_episode_json_data = json.loads(more_episode_json_raw)
- more_episodes_url = "%s%s" % (self.__urlBase, more_episode_json_data.get('url'))
-
- additional_html = fetchPage({'link': more_episodes_url})
-
- items = parseDOM(additional_html.get("content"), name='article', attrs={'class': "b-teaser"}, ret=False)
-
- for item in items:
- subtitle = parseDOM(item, name='h4', attrs={'class': "profile"}, ret=False)
- subtitle = replaceHTMLCodes(subtitle[0])
-
- title = parseDOM(item, name='h5', attrs={'class': "teaser-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- desc = parseDOM(item, name='p', attrs={'class': "description.*?"}, ret=False)
- if len(desc):
- desc = replaceHTMLCodes(desc[0])
- else:
- desc = ""
-
- channel = parseDOM(item, name='p', attrs={'class': "channel"}, ret=False)
- if len(channel):
- channel = replaceHTMLCodes(channel[0])
- else:
- channel = ""
- date = parseDOM(item, name='span', attrs={'class': 'date'}, ret=False)
- date = date[0]
-
- time = parseDOM(item, name='span', attrs={'class': 'time'}, ret=False)
- time = time[0]
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- link = parseDOM(item, name='a', attrs={'class': 'teaser-link.*?'}, ret='href')
- link = link[0]
-
- if date != "":
- title = "%s - %s" % (title, date)
-
- desc = self.formatDescription(title, channel, subtitle, desc, date, time)
-
- parameters = {"link": link, "banner": image, "mode": "openSeries"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- def getLaneTeasers(self, html):
- items = parseDOM(html.get("content"), name='article', attrs={'class': "b-topic-teaser"}, ret=False)
-
- lane_title = parseDOM(html.get("content"), name='h3', attrs={'class': "title"}, ret=False)
- lane_title = replaceHTMLCodes(lane_title[0])
- lane_title = stripTags(lane_title)
-
- for item in items:
- title = parseDOM(item, name='h5', attrs={'class': "teaser-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
- title = "[%s] %s" % (lane_title, title)
-
- video_count = parseDOM(item, name='p', attrs={'class': "topic-video-count"}, ret=False)
- desc = replaceHTMLCodes(video_count[0])
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- link = parseDOM(item, name='a', ret='href')
- link = link[0]
- link = "%s%s" % (self.__urlBase, link)
-
- desc = self.formatDescription(title, "", "", desc, "", "")
-
- parameters = {"link": link, "banner": image, "mode": "getArchiveDetail"}
-
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- # Parses Teaserblock Titles and returns links for every category
- def getLaneItems(self, url):
- html = fetchPage({'link': url})
- items = parseDOM(html.get("content"), name='article', attrs={'class': "b-teaser"}, ret=False)
-
- if len(items) < 1:
- self.getLaneTeasers(html)
- else:
- lane_title = parseDOM(html.get("content"), name='h3', attrs={'class': "title"}, ret=False)
- lane_title = replaceHTMLCodes(lane_title[0])
- lane_title = stripTags(lane_title)
- for item in items:
- subtitle = parseDOM(item, name='h4', attrs={'class': "profile"}, ret=False)
- subtitle = replaceHTMLCodes(subtitle[0])
-
- title = parseDOM(item, name='h5', attrs={'class': "teaser-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
- title = "[%s] %s" % (lane_title, title)
-
- desc = parseDOM(item, name='p', attrs={'class': "description.*?"}, ret=False)
- if len(desc):
- desc = replaceHTMLCodes(desc[0])
- else:
- desc = ""
-
- channel = parseDOM(item, name='p', attrs={'class': "channel"}, ret=False)
- if len(channel):
- channel = replaceHTMLCodes(channel[0])
- else:
- channel = ""
- date = parseDOM(item, name='span', attrs={'class': 'date'}, ret=False)
- date = date[0]
-
- time = parseDOM(item, name='span', attrs={'class': 'time'}, ret=False)
- time = time[0]
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- link = parseDOM(item, name='a', attrs={'class': 'teaser-link.*?'}, ret='href')
- link = link[0]
-
- if date != "":
- title = "%s - %s" % (title, date)
-
- desc = self.formatDescription(title, channel, subtitle, desc, date, time)
-
- parameters = {"link": link, "banner": image, "mode": "openSeries"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- # Parses "Sendung verpasst?" Date Listing
- def getSchedule(self):
- html = fetchPage({'link': self.__urlSchedule})
- container = parseDOM(html.get("content"), name='div', attrs={'class': 'b-select-box.*?'})
- list_container = parseDOM(container, name='select', attrs={'class': 'select-box-list.*?'})
- items = parseDOM(list_container, name='option', attrs={'class': 'select-box-item.*?'})
- data_items = parseDOM(list_container, name='option', attrs={'class': 'select-box-item.*?'}, ret="data-custom-properties")
- i = 0
- for item in items:
- title = replaceHTMLCodes(item)
- link = replaceHTMLCodes(data_items[i])
- link = "%s%s" % (self.__urlBase, link)
-
- parameters = {"link": link, "mode": "getScheduleDetail"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, "", "", "", "", "", "", url)
- i += 1
-
- def getArchiv(self):
- html = fetchPage({'link': self.__urlArchive})
- html_content = html.get("content")
-
- wrapper = parseDOM(html_content, name='main', attrs={'class': 'main'})
- items = parseDOM(wrapper, name='article', attrs={'class': 'b-topic-teaser.*?'})
-
- for item in items:
- subtitle = parseDOM(item, name='h4', attrs={'class': "sub-headline"}, ret=False)
- subtitle = replaceHTMLCodes(subtitle[0])
-
- title = parseDOM(item, name='h5', attrs={'class': "teaser-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- video_count = parseDOM(item, name='p', attrs={'class': "topic-video-count"}, ret=False)
- desc = replaceHTMLCodes(video_count[0])
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- link = parseDOM(item, name='a', ret='href')
- link = link[0]
-
- desc = self.formatDescription(title, "", subtitle, desc, "", "")
-
- parameters = {"link": link, "banner": image, "mode": "getArchiveDetail"}
-
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- # Creates a XBMC List Item
- def html2ListItem(self, title, banner, backdrop, description, duration, date, channel, videourl, subtitles=None, folder=True, playable=False, contextMenuItems=None):
- if banner == '':
- banner = self.defaultbanner
- if backdrop == '':
- backdrop = self.defaultbackdrop
- params = parameters_string_to_dict(videourl)
- mode = params.get('mode')
- if not mode:
- mode = "play"
-
- blacklist = False
- if self.enableBlacklist:
- if mode == 'openSeries' or mode == 'getSendungenDetail':
- blacklist = True
- debugLog("Adding List Item")
- debugLog("Mode: %s" % mode)
- debugLog("Videourl: %s" % videourl)
- debugLog("Duration: %s" % duration)
- debugLog("Banner: %s" % banner)
- debugLog("Backdrop: %s" % backdrop)
- debugLog("Playable: %s" % playable)
-
- return createListItem(title, banner, description, duration, date, channel, videourl, playable, folder, backdrop, self.pluginhandle, subtitles, blacklist, contextMenuItems)
-
- def getMainStreamInfos(self, html, data_json, banner):
- stream_info = {}
- try:
- html_data = parseDOM(html.get("content"), name='section', attrs={'class': "b-video-details.*?"}, ret=False)
- playlist_json = data_json.get('playlist')
- drm_license_url = self.getDRMLicense(data_json)
-
- current_channel = parseDOM(html_data, name='span', attrs={'class': "channel.*?"}, ret='aria-label')
- if len(current_channel):
- stream_info['channel'] = replaceHTMLCodes(current_channel[0])
- else:
- stream_info['channel'] = ""
-
- current_date = parseDOM(html_data, name='span', attrs={'class': 'date'}, ret=False)
- stream_info['date'] = current_date[0]
-
- current_time = parseDOM(html_data, name='span', attrs={'class': 'time'}, ret=False)
- if len(current_time):
- stream_info['time'] = current_time[0]
- else:
- stream_info['time'] = ""
-
- stream_info['second_headline'] = ""
- current_subtitle = parseDOM(html_data, name='p', attrs={'class': "profile.*?"}, ret=False)
- current_subheadline = parseDOM(current_subtitle, name='span', attrs={'class': "js-subheadline"}, ret=False)
- if len(current_subheadline):
- stream_info['second_headline'] = stripTags(replaceHTMLCodes(current_subheadline[0]))
- else:
- if len(current_subtitle):
- stream_info['second_headline'] = stripTags(replaceHTMLCodes(current_subtitle[0]))
-
- if len(html_data):
- html_desc = parseDOM(html_data, name='p', attrs={'class': "description-text.*?"}, ret=False)
- stream_info['description'] = stripTags(replaceHTMLCodes(html_desc[0]))
-
- stream_info['main_title'] = playlist_json['title']
- if "preview_image_url" in playlist_json:
- stream_info['teaser_image'] = playlist_json['preview_image_url']
- else:
- stream_info['teaser_image'] = banner
-
- stream_info['title'] = data_json.get("selected_video")["title"]
- stream_info['full_description'] = self.formatDescription(stream_info['title'], stream_info['channel'], stream_info['second_headline'], stream_info['description'], stream_info['date'], stream_info['time'])
-
- if data_json.get("selected_video")["description"]:
- stream_info['description'] = data_json.get("selected_video")["description"]
-
- if data_json.get("selected_video")["duration"]:
- tmp_duration = float(data_json.get("selected_video")["duration"])
- stream_info['duration'] = int(tmp_duration / 1000)
-
- if "subtitles" in data_json.get("selected_video"):
- main_subtitles = []
- for sub in data_json.get("selected_video")["subtitles"]:
- main_subtitles.append(sub.get(u'src'))
- stream_info['subtitles'] = main_subtitles
- else:
- stream_info['subtitles'] = None
- stream_info['main_videourl'] = self.getVideoUrl(data_json.get("selected_video")["sources"], drm_license_url)
- except:
- debugLog("Error fetching stream infos from html")
- return stream_info
-
- # Parses a Video Page and extracts the Playlist/Description/...
- def getLinks(self, url, banner, playlist):
- url = unqoute_url(url)
- debugLog("Loading Videos from %s" % url)
- if banner is not None:
- banner = unqoute_url(banner)
-
- stream_infos = {}
- playlist_json = {}
- video_items = []
- html = fetchPage({'link': url})
- data = parseDOM(html.get("content"), name='div', attrs={'class': "jsb_ jsb_VideoPlaylist"}, ret='data-jsb')
-
- if len(data):
- try:
- data = data[0]
- data = replaceHTMLCodes(data)
- data_json = json.loads(data)
- playlist_json = data_json.get('playlist')
- stream_infos = self.getMainStreamInfos(html, data_json, banner)
- video_items = playlist_json["videos"]
- except Exception as e:
- debugLog("Error Loading Episode from %s" % url)
-
- # Add the gapless video if available
- try:
- drm_license_url = self.getDRMLicense(data_json)
- if "is_gapless" in playlist_json:
- gapless_subtitles = []
- gapless_name = '-- %s --' % self.translation(30059)
- if playlist_json['is_gapless']:
- gapless_videourl = self.getVideoUrl(playlist_json['gapless_video']['sources'], drm_license_url)
- if gapless_videourl:
- if "subtitles" in playlist_json['gapless_video']:
- for sub in playlist_json['gapless_video']["subtitles"]:
- gapless_subtitles.append(sub.get(u'src'))
- else:
- global_subtitles = None
- if "duration_in_seconds" in playlist_json:
- gapless_duration = playlist_json["duration_in_seconds"]
- else:
- gapless_duration = ""
- liz = self.html2ListItem(gapless_name, stream_infos['teaser_image'], "", stream_infos['full_description'], gapless_duration, '', '', gapless_videourl, gapless_subtitles, False, True)
- except IndexError as e:
- debugLog("No gapless video added for %s" % url)
-
-
- # Multiple chapters available
- if len(video_items) > 1:
- play_all_name = '-- %s --' % self.translation(30060)
- debugLog("Found Video Playlist with %d Items" % len(video_items))
- if self.usePlayAllPlaylist:
- createPlayAllItem(play_all_name, self.pluginhandle, stream_infos)
- for video_item in video_items:
- try:
- title = video_item["title"]
- if video_item["description"]:
- desc = video_item["description"]
- else:
- debugLog("No Video Description for %s" % title)
- desc = ""
-
- if video_item["duration"]:
- duration = float(video_item["duration"])
- duration = int(duration / 1000)
- else:
- duration = 0
-
- preview_img = video_item["preview_image_url"]
- sources = video_item["sources"]
- if "subtitles" in video_item:
- debugLog("Found Subtitles for %s" % title)
- subtitles = []
- for sub in video_item["subtitles"]:
- subtitles.append(sub.get(u'src'))
- else:
- subtitles = None
- videourl = self.getVideoUrl(sources, drm_license_url)
- liz = self.html2ListItem(title, preview_img, "", desc, duration, '', '', videourl, subtitles, False, True)
- playlist.add(videourl, liz)
- except Exception as e:
- debugLog("Error on getLinks")
- debugLog(str(e), self.xbmc.LOGERROR)
- continue
- return playlist
- else:
- debugLog("No Playlist Items found for %s. Setting up single video view." % stream_infos['title'])
- liz = self.html2ListItem(stream_infos['title'], stream_infos['teaser_image'], "", stream_infos['full_description'], stream_infos['duration'], '', '', stream_infos['main_videourl'], stream_infos['subtitles'], False, True)
- playlist.add(stream_infos['main_videourl'], liz)
- return playlist
- else:
- showDialog((self.translation(30052)))
- sys.exit()
-
- # Returns Livestream Specials
- def getLiveSpecials(self):
- html = fetchPage({'link': self.__urlLive})
- wrapper = parseDOM(html.get("content"), name='main', attrs={'class': 'main'})
- section = parseDOM(wrapper, name='div', attrs={'class': 'b-special-livestreams-container.*?'})
- items = parseDOM(section, name='div', attrs={'class': 'b-intro-teaser.*?'})
- try:
- xbmcaddon.Addon('inputstream.adaptive')
- except RuntimeError:
- self.html2ListItem("[COLOR red][I] -- %s -- [/I][/COLOR]" % self.translation(30067), self.defaultbanner, "", "", "", "", "Info", "addons://user/kodi.inputstream", None, True, False)
-
- if items:
- debugLog("Found %d Livestream Channels" % len(items))
- for item in items:
- channel = "Special"
-
- debugLog("Processing %s Livestream" % channel)
-
- figure = parseDOM(item, name='div', attrs={'class': 'img-container'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- time = parseDOM(item, name='span', attrs={'class': 'time'}, ret=False)
- time = replaceHTMLCodes(time[0])
- time = stripTags(time)
-
- title = parseDOM(item, name='h4', attrs={'class': 'special-livestream-headline.*?'})
- title = replaceHTMLCodes(title[0])
-
- desc = parseDOM(item, name='p', attrs={'class': 'description.*?'}, ret=False)
- desc = replaceHTMLCodes(desc[0])
- desc = stripTags(desc)
-
- link = parseDOM(figure, name='a', attrs={}, ret="href")
- link = replaceHTMLCodes(link[0])
-
- online = parseDOM(item, name='span', attrs={'class': 'status-online'})
- if len(online):
- online = True
- else:
- online = False
-
- restart = parseDOM(item, name='span', attrs={'class': 'is-restartable'})
- if len(restart):
- restart = True
- else:
- restart = False
- self.buildLivestream(title, link, time, restart, channel, image, online, desc)
-
- # Returns Live Stream Listing
- def getLiveStreams(self):
- html = fetchPage({'link': self.__urlBase})
- wrapper = parseDOM(html.get("content"), name='main', attrs={'class': 'main'})
- section = parseDOM(wrapper, name='section', attrs={'class': 'b-live-program.*?'})
- items = parseDOM(section, name='li', attrs={'class': 'channel orf.*?'})
-
- try:
- xbmcaddon.Addon('inputstream.adaptive')
- except RuntimeError:
- self.html2ListItem("[COLOR red][I] -- %s -- [/I][/COLOR]" % self.translation(30067), self.defaultbanner, "", "", "", "", "Info", "addons://user/kodi.inputstream", None, True, False)
-
- if items:
- debugLog("Found %d Livestream Channels" % len(items))
- for item in items:
- channel = parseDOM(item, name='img', attrs={'class': 'channel-logo'}, ret="alt")
- channel = replaceHTMLCodes(channel[0])
-
- debugLog("Processing %s Livestream" % channel)
- bundesland_article = parseDOM(item, name='li', attrs={'class': '.*?is-bundesland-heute.*?'}, ret='data-jsb')
- article = parseDOM(item, name='article', attrs={'class': 'b-livestream-teaser.*?'})
- if not len(bundesland_article) and len(article):
- figure = parseDOM(article, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='data-src')
- image = replaceHTMLCodes(image[0])
-
- time = parseDOM(article, name='h4', attrs={'class': 'time'}, ret=False)
- time = replaceHTMLCodes(time[0])
- time = stripTags(time)
-
- title = parseDOM(article, name='h4', attrs={'class': 'livestream-title.*?'})
- title = replaceHTMLCodes(title[0])
-
- link = parseDOM(item, name='a', attrs={'class': 'js-link-box'}, ret="href")
- link = replaceHTMLCodes(link[0])
-
- online = parseDOM(article, name='span', attrs={'class': 'status-online'})
- if len(online):
- online = True
- else:
- online = False
-
- restart = parseDOM(article, name='span', attrs={'class': 'is-restartable'})
- if len(restart):
- restart = True
- else:
- restart = False
-
- self.buildLivestream(title, link, time, restart, channel, image, online)
- elif len(bundesland_article):
- bundesland_data = replaceHTMLCodes(bundesland_article[0])
- bundesland_data = json.loads(bundesland_data)
- for bundesland_item_key in bundesland_data:
- bundesland_item = bundesland_data.get(bundesland_item_key)
- if bundesland_item and bundesland_item is not True and len(bundesland_item):
- bundesland_title = bundesland_item.get('title')
- bundesland_image = bundesland_item.get('img')
- bundesland_link = bundesland_item.get('url')
-
- self.buildLivestream(bundesland_title, bundesland_link, "", True, channel, bundesland_image, True)
- else:
- debugLog("Channel %s was skipped" % channel)
- self.getLiveSpecials()
-
- def buildLivestream(self, title, link, time, restart, channel, banner, online, description = ""):
- html = fetchPage({'link': link})
- debugLog("Loading Livestream Page %s for Channel %s" % (link, channel))
- container = parseDOM(html.get("content"), name='div', attrs={'class': "player_viewport.*?"})
- if len(container):
- data = parseDOM(container[0], name='div', attrs={}, ret="data-jsb")
-
- if online:
- state = (self.translation(30019))
- else:
- state = (self.translation(30020))
-
- if description:
- description = "%s \n\n %s" % (description, state)
- else:
- description = state
-
- if time:
- time_str = " (%s)" % time
- else:
- time_str = ""
-
- try:
- xbmcaddon.Addon('inputstream.adaptive')
- inputstreamAdaptive = True
- except RuntimeError:
- inputstreamAdaptive = False
-
- if channel:
- channel = "[%s]" % channel
- else:
- channel = "LIVE"
-
- streaming_url = self.getLivestreamUrl(data, self.videoQuality)
- # Remove Get Parameters because InputStream Adaptive cant handle it.
- streaming_url = re.sub(r"\?[\S]+", '', streaming_url, 0)
- drm_lic_url = self.getLivestreamDRM(data)
- uhd_streaming_url = self.getLivestreamUrl(data, 'UHD', True)
- if uhd_streaming_url:
- uhd50_streaming_url = uhd_streaming_url.replace('_uhd_25/', '_uhd_50/')
-
- final_title = "[%s] %s - %s%s" % (self.translation(30063), channel, title, time_str)
-
- debugLog("DRM License: %s" % drm_lic_url)
- if uhd_streaming_url:
- debugLog("Adding UHD Livestream from %s" % uhd_streaming_url)
- uhdContextMenuItems = []
- if inputstreamAdaptive and restart and online:
- uhd_restart_parameters = {"mode": "liveStreamRestart", "link": link, "lic_url": drm_lic_url}
- uhd_restart_url = build_kodi_url(uhd_restart_parameters)
- uhdContextMenuItems.append(('Restart', 'RunPlugin(%s)' % uhd_restart_url))
- uhd_final_title = "[%s] %s [UHD] - %s%s" % (self.translation(30063), channel, title, time_str)
- uhd50_final_title = "[%s] %s [UHD 50fps] - %s%s" % (self.translation(30063), channel, title, time_str)
- else:
- uhd_final_title = "%s[UHD] - %s%s" % (channel, title, time_str)
- uhd50_final_title = "%s[UHD 50fps] - %s%s" % (channel, title, time_str)
-
- if not drm_lic_url:
- self.html2ListItem(uhd_final_title, banner, "", description, time, channel, channel, generateAddonVideoUrl(uhd_streaming_url), None, False, True, uhdContextMenuItems)
- self.html2ListItem(uhd50_final_title, banner, "", description, time, channel, channel, generateAddonVideoUrl(uhd50_streaming_url), None, False, True, uhdContextMenuItems)
- elif inputstreamAdaptive:
- drm_video_url = generateDRMVideoUrl(uhd_streaming_url, drm_lic_url)
- self.html2ListItem(uhd_final_title, banner, "", description, time, channel, channel, drm_video_url, None, False, True, uhdContextMenuItems)
- drm50_video_url = generateDRMVideoUrl(uhd50_streaming_url, drm_lic_url)
- self.html2ListItem(uhd50_final_title, banner, "", description, time, channel, channel, drm50_video_url, None, False, True, uhdContextMenuItems)
-
- if streaming_url:
- contextMenuItems = []
- if inputstreamAdaptive and restart and online:
- debugLog("Adding DRM Restart %s" % drm_lic_url)
- restart_parameters = {"mode": "liveStreamRestart", "link": link, "lic_url": drm_lic_url}
- restart_url = build_kodi_url(restart_parameters)
- contextMenuItems.append((self.translation(30063), 'RunPlugin(%s)' % restart_url))
-
- else:
- final_title = "%s - %s%s" % (channel, title, time_str)
-
- if not drm_lic_url:
- self.html2ListItem(final_title, banner, "", description, time, channel, channel, generateAddonVideoUrl(streaming_url), None, False, True, contextMenuItems)
- elif inputstreamAdaptive:
- drm_video_url = generateDRMVideoUrl(streaming_url, drm_lic_url)
- self.html2ListItem(final_title, banner, "", description, time, channel, channel, drm_video_url, None, False,
- True, contextMenuItems)
-
- def getDRMLicense(self, data):
- try:
- if 'drm' in data and 'widevineUrl' in data['drm']:
- debugLog("Widevine Url found %s" % data['drm']['widevineUrl'])
- widevineUrl = data['drm']['widevineUrl']
- token = data['drm']['token']
- brand = data['drm']['brandGuid']
- return "%s?BrandGuid=%s&userToken=%s" % (widevineUrl, brand, token)
- except:
- debugLog("No License Url found")
-
- def getLivestreamDRM(self, data_sets):
- for data in data_sets:
- try:
- data = replaceHTMLCodes(data)
- data = json.loads(data)
- drm_lic = self.getDRMLicense(data)
- if drm_lic:
- return drm_lic
- except Exception as e:
- debugLog("Error getting Livestream DRM Keys")
-
- def liveStreamRestart(self, link, protocol):
- try:
- xbmcaddon.Addon('inputstream.adaptive')
- except RuntimeError:
- return
-
- html = fetchPage({'link': link})
- bitmovinStreamId = self.getLivestreamBitmovinID(html)
- stream_info = self.getLivestreamInformation(html)
-
- if bitmovinStreamId:
- title = stream_info['title']
- image = stream_info['image']
- description = stream_info['description']
- duration = stream_info['duration']
- date = stream_info['date']
- channel = stream_info['channel']
-
- ApiKey = '2e9f11608ede41f1826488f1e23c4a8d'
- response = url_get_request('https://playerapi-restarttv.ors.at/livestreams/%s/sections/?state=active&X-Api-Key=%s' % (bitmovinStreamId, ApiKey))
- try:
- charset = response.headers.get_content_charset()
- response_raw = response.read().decode(charset)
- except AttributeError:
- response_raw = response.read().decode('utf-8')
-
- section = json.loads(response_raw)
- if len(section):
- section = section[0]
- streamingURL = 'https://playerapi-restarttv.ors.at/livestreams/%s/sections/%s/manifests/%s/?startTime=%s&X-Api-Key=%s' % (bitmovinStreamId, section.get('id'), protocol, section.get('metaData').get('timestamp'), ApiKey)
-
- listItem = createListItem(title, image, description, duration, date, channel, streamingURL, True, False, self.defaultbackdrop, self.pluginhandle)
- return streamingURL, listItem
-
- def getLivestreamUrl(self, data_sets, preferred_quality, strict=False):
- fallback = {}
- for data in data_sets:
- try:
- data = replaceHTMLCodes(data)
- data = json.loads(data)
- if 'playlist' in data:
- if 'videos' in data['playlist']:
- for video_items in data['playlist']['videos']:
- for video_sources in video_items['sources']:
-
- if video_sources['quality'].lower() == preferred_quality.lower() and video_sources[
- 'protocol'].lower() == "http" and video_sources['delivery'].lower() == 'hls':
- return video_sources['src']
- elif video_sources['quality'].lower()[0:3] == preferred_quality.lower() and video_sources[
- 'protocol'].lower() == "http" and video_sources['delivery'].lower() == 'dash':
- return video_sources['src']
- elif video_sources['quality'] and video_sources['src'] and video_sources['quality'][0:3] in self.__videoQualities:
- debugLog("Adding Video Url %s (%s)" % (video_sources['src'], video_sources['delivery']))
- fallback[video_sources['quality'].lower()[0:3]] = video_sources['src']
- if not strict:
- for quality in reversed(self.__videoQualities):
- debugLog("Looking for Fallback Quality %s" % quality)
- if quality.lower() in fallback:
- debugLog("Returning Fallback Stream %s" % quality)
- return fallback[quality.lower()]
- except Exception as e:
- debugLog("Error getting Livestream")
-
- @staticmethod
- def getLivestreamJSON(html, key_check='restart_url'):
- container = parseDOM(html.get("content"), name='div', attrs={'class': "player_viewport.*?"})
- if len(container):
- data_sets = parseDOM(container[0], name='div', attrs={}, ret="data-jsb")
- if len(data_sets):
- for data in data_sets:
- try:
- data = replaceHTMLCodes(data)
- data = json.loads(data)
- if key_check in data:
- return data
- except Exception as e:
- debugLog("Error getting Livestream JSON for key %s" % key_check)
- return False
-
- def getLivestreamBitmovinID(self, html):
- data = self.getLivestreamJSON(html, 'restart_url')
- if data:
- try:
- bitmovin_id = data['restart_url'].replace("https://playerapi-restarttv.ors.at/livestreams/", "").replace("/sections/", "")
- return bitmovin_id.split("?")[0]
- except Exception as e:
- debugLog("Error getting Livestream Bitmovin ID")
-
- def getLivestreamLicenseData(self, html):
- data = self.getLivestreamJSON(html, 'drm')
- if data:
- try:
- return self.getLivestreamDRM(data)
- except Exception as e:
- debugLog("Error getting Livestream DRM License")
-
- @staticmethod
- def getLivestreamInformation(html):
- container = parseDOM(html.get("content"), name='div', attrs={'class': "player_viewport.*?"})
- data_sets = parseDOM(container[0], name='div', attrs={}, ret="data-jsb")
- title = "Titel"
- image = ""
- description = "Beschreibung"
- duration = ""
- date = ""
- channel = ""
-
- for data in data_sets:
- try:
- data = replaceHTMLCodes(data)
- data = json.loads(data)
-
- if 'playlist' in data:
- time_str = False
- time_str_end = False
- if 'title' in data['playlist']:
- title = data['playlist']['title']
- if 'preview_image_url' in data['playlist']:
- image = data['playlist']['preview_image_url']
- if 'livestream_start' in data['playlist']:
- date = data['playlist']['livestream_start']
- time_str = datetime.datetime.fromtimestamp(int(date)).strftime('%H:%M')
- if 'livestream_end' in data['playlist']:
- date = data['playlist']['livestream_end']
- time_str_end = datetime.datetime.fromtimestamp(int(date)).strftime('%H:%M')
- if 'videos' in data['playlist']:
- if 'description' in data['playlist']['videos']:
- description = data['playlist']['videos']['description']
- if time_str and time_str_end:
- return {"title": "%s (%s - %s)" % (title, time_str, time_str_end), "image": image, "description": description, "date": date, "duration": duration, "channel": channel}
- else:
- return {"title": title, "image": image, "description": description, "date": date, "duration": duration, "channel": channel}
- except Exception as e:
- debugLog("Error getting Livestream Infos")
-
- # Parses the Topic Overview Page
- def getThemen(self):
- html = fetchPage({'link': self.__urlTopics})
- html_content = html.get("content")
-
- content = parseDOM(html_content, name='section', attrs={})
-
- for topic in content:
- title = parseDOM(topic, name='h3', attrs={'class': 'item_wrapper_headline.subheadline'})
- if title:
- title = replaceHTMLCodes(title[0])
-
- link = parseDOM(topic, name='a', attrs={'class': 'more.service_link.service_link_more'}, ret="href")
- link = replaceHTMLCodes(link[0])
-
- image = parseDOM(topic, name='img', ret="src")
- image = replaceHTMLCodes(image[0]).replace("width=395", "width=500").replace("height=209.07070707071", "height=265")
-
- descs = parseDOM(topic, name='h4', attrs={'class': 'item_title'})
- description = ""
- for desc in descs:
- description += "* %s \n" % replaceHTMLCodes(desc)
-
- parameters = {"link": link, "mode": "getThemenDetail"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", description, "", "", "", url)
-
- # Parses the Archive Detail Page
- def getArchiveDetail(self, url):
- url = unqoute_url(url)
- html = fetchPage({'link': url})
- html_content = html.get("content")
-
- wrapper = parseDOM(html_content, name='main', attrs={'class': 'main'})
- items = parseDOM(wrapper, name='article', attrs={'class': 'b-teaser.*?'})
-
- for item in items:
- subtitle = parseDOM(item, name='h4', attrs={'class': "profile"}, ret=False)
- subtitle = replaceHTMLCodes(subtitle[0])
-
- title = parseDOM(item, name='h5', attrs={'class': "teaser-title.*?"}, ret=False)
- title = replaceHTMLCodes(title[0])
-
- desc = parseDOM(item, name='p', attrs={'class': "description.*?"}, ret=False)
- desc = replaceHTMLCodes(desc[0])
-
- figure = parseDOM(item, name='figure', attrs={'class': 'teaser-img'}, ret=False)
- image = parseDOM(figure, name='img', attrs={}, ret='src')
- image = replaceHTMLCodes(image[0])
-
- link = parseDOM(item, name='a', ret='href')
- link = link[0]
-
- channel = parseDOM(item, name='p', attrs={'class': "channel"}, ret=False)
- if len(channel):
- channel = replaceHTMLCodes(channel[0])
- else:
- channel = ""
-
- date = parseDOM(item, name='span', attrs={'class': 'date'}, ret=False)
- date = date[0]
-
- time = parseDOM(item, name='span', attrs={'class': 'time'}, ret=False)
- time = time[0]
-
- desc = self.formatDescription(title, channel, subtitle, desc, date, time)
-
- parameters = {"link": link, "banner": image, "mode": "openSeries"}
- url = build_kodi_url(parameters)
- self.html2ListItem(title, image, "", desc, "", "", "", url)
-
- def getSearchHistory(self):
- parameters = {'mode': 'getSearchResults'}
- u = build_kodi_url(parameters)
- createListItem((self.translation(30007)) + " ...", self.defaultbanner, "", "", "", '', u, False, True, self.defaultbackdrop, self.pluginhandle)
-
- history = searchHistoryGet()
- for str_val in reversed(history):
- if str_val.strip() != '':
- parameters = {'mode': 'getSearchResults', 'link': str_val.replace(" ", "+")}
- u = build_kodi_url(parameters)
- createListItem(str_val, self.defaultbanner, "", "", "", '', u, False, True, self.defaultbackdrop, self.pluginhandle)
-
- @staticmethod
- def removeUmlauts(str_val):
- return str_val.replace("Ö", "O").replace("ö", "o").replace("Ü", "U").replace("ü", "u").replace("Ä", "A").replace("ä","a")
-
- def getSearchResults(self, link):
- keyboard = self.xbmc.Keyboard(link)
- keyboard.doModal()
- if keyboard.isConfirmed():
- keyboard_in = keyboard.getText()
- if keyboard_in != link:
- searchHistoryPush(keyboard_in)
- searchurl = "%s?q=%s" % (self.__urlSearch, keyboard_in.replace(" ", "+"))
- self.getTeaserList(searchurl, 'b-search-results', 'section')
- else:
- parameters = {'mode': 'getSearchHistory'}
- u = build_kodi_url(parameters)
- createListItem((self.translation(30014)) + " ...", self.defaultbanner, "", "", "", '', u, False, True, self.defaultbackdrop, self.pluginhandle)
diff --git a/plugin.video.orftvthek/resources/lib/Scraper.py b/plugin.video.orftvthek/resources/lib/Scraper.py
deleted file mode 100644
index 7ee0f4a282..0000000000
--- a/plugin.video.orftvthek/resources/lib/Scraper.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import abc
-
-
-class Scraper(object):
- __metaclass__ = abc.ABCMeta
-
- @abc.abstractmethod
- def getCategories(self):
- pass
-
- @abc.abstractmethod
- def getHighlights(self):
- pass
-
- @abc.abstractmethod
- def getLiveStreams(self):
- pass
-
- @abc.abstractmethod
- def getMostViewed(self):
- pass
-
- @abc.abstractmethod
- def getNewest(self):
- pass
-
- @abc.abstractmethod
- def getThemen(self):
- pass
-
- @abc.abstractmethod
- def getTips(self):
- pass
-
- @abc.abstractmethod
- def getSchedule(self):
- pass
-
- @abc.abstractmethod
- def getArchiv(self):
- pass
-
- @abc.abstractmethod
- def getLivestreamByChannel(self, channel):
- pass
diff --git a/plugin.video.orftvthek/resources/lib/ServiceApi.py b/plugin.video.orftvthek/resources/lib/ServiceApi.py
deleted file mode 100644
index 832807927a..0000000000
--- a/plugin.video.orftvthek/resources/lib/ServiceApi.py
+++ /dev/null
@@ -1,541 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-import datetime
-import time
-import sys
-import re
-
-PY3 = sys.version_info.major >=3
-if PY3:
- from urllib.error import HTTPError
-else:
- from urllib2 import HTTPError
-
-from .Base import *
-from .Scraper import *
-
-
-class serviceAPI(Scraper):
- __urlBase = 'https://api-tvthek.orf.at/api/v3/'
- __urlBaseV4 = 'https://api-tvthek.orf.at/api/v4.2/'
- __urlLive = 'livestreams/24hours?limit=20'
- __urlLiveChannels = 'livestreams'
- __urlMostViewed = 'page/startpage'
- __urlNewest = 'page/startpage/newest'
- __urlSearch = __urlBase + 'search/%s?limit=1000'
- __urlShows = 'profiles?limit=1000'
- __urlTips = 'page/startpage/tips'
- __urlTopics = 'topics/overview?limit=1000'
- __urlChannel = 'channel/'
- __urlDRMLic = 'https://drm.ors.at/acquire-license/widevine'
- __brandIdDRM = '13f2e056-53fe-4469-ba6d-999970dbe549'
- __bundeslandMap = {
- 'orf2b': 'Burgenland',
- 'orf2stmk': 'Steiermark',
- 'orf2w': 'Wien',
- 'orf2ooe': 'Oberösterreich',
- 'orf2k': 'Kärnten',
- 'orf2n': 'Niederösterreich',
- 'orf2s': 'Salzburg',
- 'orf2v': 'Vorarlberg',
- 'orf2t': 'Tirol',
- }
- __channelMap = {
- 'orf1': 'ORF 1',
- 'orf2': 'ORF 2',
- 'orf3': 'ORF III',
- 'orfs': 'ORF Sport+',
- 'live_special': 'Special'
- }
-
- serviceAPIEpisode = 'episode/%s'
- serviceAPIDate = 'schedule/%s?limit=1000'
- serviceAPIDateFrom = 'schedule/%s/%d?limit=1000'
- serviceAPIProgram = 'profile/%s/episodes'
- servieAPITopic = 'topic/%s'
- serviceAPITrailers = 'page/preview?limit=100'
- serviceAPIHighlights = 'page/startpage'
-
- httpauth = 'cHNfYW5kcm9pZF92M19uZXc6MDY1MmU0NjZkMTk5MGQxZmRmNDBkYTA4ZTc5MzNlMDY=='
-
- def __init__(self, xbmc, settings, pluginhandle, quality, protocol, delivery, defaultbanner, defaultbackdrop, usePlayAllPlaylist):
- self.translation = settings.getLocalizedString
- self.xbmc = xbmc
- self.videoQuality = quality
- self.videoDelivery = delivery
- self.videoProtocol = protocol
- self.pluginhandle = pluginhandle
- self.defaultbanner = defaultbanner
- self.defaultbackdrop = defaultbackdrop
- self.usePlayAllPlaylist = usePlayAllPlaylist
- debugLog('ServiceAPI - Init done', xbmc.LOGDEBUG)
-
- def getLivestreamByChannel(self, channel):
- response = self.__makeRequestV4(self.__urlLiveChannels)
- response_raw = response.read().decode('UTF-8')
- channels = json.loads(response_raw)
-
- live_link = False
- for result in channels:
- if channel in self.__bundeslandMap:
- channel_items = channels[result].get('items')
- for channel_item in channel_items:
- if channel_item.get('title') == "%s heute" % self.__bundeslandMap[channel]:
- live_link = channel_item.get('_links').get('self').get('href')
- if result == channel or channel in self.__bundeslandMap and result == channel[0:4] and not live_link:
- live_link = channels[result].get('items')[0].get('_links').get('self').get('href')
-
- if live_link:
- response = url_get_request(live_link, self.httpauth)
- response_raw = response.read().decode('UTF-8')
- live_json = json.loads(response_raw)
- if live_json.get('is_drm_protected'):
- video_url = self.JSONStreamingDrmURL(live_json)
- uhd_25_video_url = self.JSONStreamingDrmURL(live_json, 'uhdbrowser')
- if uhd_25_video_url:
- video_url = uhd_25_video_url;
- uhd_50_video_url = self.JSONStreamingDrmURL(live_json, 'uhdsmarttv')
- if uhd_50_video_url:
- video_url = uhd_50_video_url
- license_url = self.JSONLicenseDrmURL(live_json)
- return {'title': live_json.get('title'), 'description': live_json.get('share_subject'), 'url': video_url,'license': license_url}
- else:
- video_url = self.JSONStreamingURL(live_json.get('sources'))
- return {'title': live_json.get('title'), 'description': live_json.get('share_subject'), 'url': video_url}
-
- def getHighlights(self):
- try:
- response = self.__makeRequest(self.serviceAPIHighlights)
- responseCode = response.getcode()
- except HTTPError as error:
- responseCode = error.getcode()
-
- if responseCode == 200:
- for result in json.loads(response.read().decode('UTF-8')).get('highlight_teasers'):
- if result.get('target').get('model') == 'Segment':
- self.JSONSegment2ListItem(result.get('target'))
-
- def getMostViewed(self):
- try:
- response = self.__makeRequest(self.__urlMostViewed)
- responseCode = response.getcode()
- except HTTPError as error:
- responseCode = error.getcode()
-
- if responseCode == 200:
- for result in json.loads(response.read().decode('UTF-8')).get('most_viewed_segments'):
- if result.get('model') == 'Segment':
- self.JSONSegment2ListItem(result)
-
- def getNewest(self):
- self.getTableResults(self.__urlNewest)
-
- def getTips(self):
- self.getTableResults(self.__urlTips)
-
- def getFocus(self):
- debugLog('"In Focus" not available', level=xbmc.LOGDEBUG)
-
- def getTableResults(self, urlAPI):
- try:
- response = self.__makeRequest(urlAPI)
- responseCode = response.getcode()
- except HTTPError as error:
- responseCode = error.getcode()
-
- if responseCode == 200:
- for result in json.loads(response.read().decode('UTF-8')):
- if result.get('model') == 'Episode':
- self.__JSONEpisode2ListItem(result)
- elif result.get('model') == 'Tip':
- self.__JSONVideoItem2ListItem(result.get('_embedded').get('video_item'))
-
- else:
- debugLog('ServiceAPI not available for %s ... switch back to HTML Parsing in the Addon Settings' % urlAPI, level=xbmc.LOGDEBUG)
- showDialog(self.translation(30045).encode('UTF-8'), self.translation(30046).encode('UTF-8'))
-
- # Useful Methods for JSON Parsing
- def JSONSegment2ListItem(self, JSONSegment):
- if JSONSegment.get('killdate') is not None and time.strptime(JSONSegment.get('killdate')[0:19], '%Y-%m-%dT%H:%M:%S') < time.localtime():
- return
- title = JSONSegment.get('title').encode('UTF-8')
- image = self.JSONImage(JSONSegment.get('_embedded').get('image'))
- description = JSONSegment.get('description')
- duration = JSONSegment.get('duration_seconds')
- date = time.strptime(JSONSegment.get('episode_date')[0:19], '%Y-%m-%dT%H:%M:%S')
- streamingURL = self.JSONStreamingURL(JSONSegment.get('sources'))
- subtitles = [x.get('src') for x in JSONSegment.get('playlist').get('subtitles')]
- return [streamingURL, createListItem(title, image, description, duration, time.strftime('%Y-%m-%d', date), '', streamingURL, True, False, self.defaultbackdrop, self.pluginhandle, subtitles)]
-
- @staticmethod
- def JSONImage(jsonImages, name='image_full'):
- return jsonImages.get('public_urls').get('highlight_teaser').get('url')
-
- def JSONStreamingURL(self, jsonVideos):
- source = None
- if jsonVideos.get('progressive_download') is not None:
- for streamingUrl in jsonVideos.get('progressive_download'):
- if streamingUrl.get('quality_key') == self.videoQuality:
- return generateAddonVideoUrl(streamingUrl.get('src'))
- source = streamingUrl.get('src')
-
- for streamingUrl in jsonVideos.get('hls'):
- if streamingUrl.get('quality_key') == self.videoQuality:
- # Remove Get Parameters because InputStream Adaptive cant handle it.
- source = re.sub(r"\?[\S]+", '', streamingUrl.get('src'), 0)
- return generateAddonVideoUrl(source)
- source = re.sub(r"\?[\S]+", '', streamingUrl.get('src'), 0)
- if source is not None:
- return generateAddonVideoUrl(source)
- else:
- showDialog(self.translation(30014).encode('UTF-8'), self.translation(30050).encode('UTF-8'))
- return
-
- def JSONLicenseDrmURL(self, jsonData):
- if jsonData.get('drm_token') is not None:
- token = jsonData.get('drm_token')
- license_url = "%s?BrandGuid=%s&userToken=%s" % (self.__urlDRMLic, self.__brandIdDRM, token)
- debugLog("DRM License Url %s" % license_url)
- return license_url
-
- def JSONStreamingDrmURL(self, jsonData, uhd_profile = False):
- if jsonData.get('drm_token') is not None:
- license_url = self.JSONLicenseDrmURL(jsonData)
- jsonVideos = jsonData.get('sources')
-
- if uhd_profile:
- for streamingUrl in jsonVideos.get('dash'):
- if streamingUrl.get('is_uhd') and streamingUrl.get('quality_key').lower() == uhd_profile:
- source = re.sub(r"\?[\S]+", '', streamingUrl.get('src'), 0)
- return generateDRMVideoUrl(source, license_url)
- return False
-
- for streamingUrl in jsonVideos.get('dash'):
- if streamingUrl.get('quality_key').lower()[0:3] == self.videoQuality:
- return generateDRMVideoUrl(streamingUrl.get('src'), license_url)
- source = streamingUrl.get('src')
- # Remove Get Parameters because InputStream Adaptive cant handle it.
- source = re.sub(r"\?[\S]+", '', source, 0)
- if source is not None:
- return generateDRMVideoUrl(source, license_url)
- else:
- showDialog(self.translation(30014).encode('UTF-8'), self.translation(30050).encode('UTF-8'))
- return
-
- # list all Categories
- def getCategories(self):
- try:
- response = self.__makeRequest(self.__urlShows)
- responseCode = response.getcode()
- except HTTPError as error:
- responseCode = error.getcode()
-
- if responseCode == 200:
- for result in json.loads(response.read().decode('UTF-8')).get('_embedded').get('items'):
- self.__JSONProfile2ListItem(result)
- else:
- showDialog(self.translation(30045).encode('UTF-8'), self.translation(30046).encode('UTF-8'))
-
- # list all Episodes for the given Date
- def getDate(self, date, dateFrom=None):
- if dateFrom is None:
- url = self.serviceAPIDate % date
- else:
- url = self.serviceAPIDateFrom % (date, 7)
- response = self.__makeRequest(url)
-
- episodes = json.loads(response.read().decode('UTF-8')).get('_embedded').get('items')
- if dateFrom is not None:
- episodes = reversed(episodes)
-
- for episode in episodes:
- self.__JSONEpisode2ListItem(episode)
-
- # list all Entries for the given Topic
- def getTopic(self, topicID):
- response = self.__makeRequest(self.servieAPITopic % topicID)
- for entrie in json.loads(response.read().decode('UTF-8')).get('_embedded').get('video_items'):
- self.__JSONVideoItem2ListItem(entrie)
-
- # list all Episodes for the given Broadcast
- def getProgram(self, programID, playlist):
- response = self.__makeRequest(self.serviceAPIProgram % programID)
- responseCode = response.getcode()
-
- if responseCode == 200:
- episodes = json.loads(response.read().decode('UTF-8')).get('_embedded').get('items')
- if len(episodes) == 1:
- for episode in episodes:
- self.getEpisode(episode.get('id'), playlist)
- return
-
- for episode in episodes:
- self.__JSONEpisode2ListItem(episode, 'teaser')
- else:
- showDialog(self.translation(30045).encode('UTF-8'), self.translation(30046).encode('UTF-8'))
-
- # listst all Segments for the Episode with the given episodeID
- # If the Episode only contains one Segment, that get played instantly.
- def getEpisode(self, episodeID, playlist):
- playlist.clear()
-
- response = self.__makeRequest(self.serviceAPIEpisode % episodeID)
- result = json.loads(response.read().decode('UTF-8'))
-
- if len(result.get('_embedded').get('segments')) == 1:
- listItem = self.JSONSegment2ListItem(result.get('_embedded').get('segments')[0])
- playlist.add(listItem[0], listItem[1])
- else:
- gapless_name = '-- %s --' % self.translation(30059)
- streamingURL = self.JSONStreamingURL(result.get('sources'))
- description = result.get('description')
- duration = result.get('duration_seconds')
- teaser_image = result.get('playlist').get('preview_image_url')
- date = time.strptime(result.get('date')[0:19], '%Y-%m-%dT%H:%M:%S')
- if result.get('playlist').get('is_gapless'):
- subtitles = [x.get('src') for x in result.get('playlist').get('gapless_video').get('subtitles')]
- createListItem(gapless_name, teaser_image, description, duration, time.strftime('%Y-%m-%d', date), '', streamingURL, True, False, self.defaultbackdrop, self.pluginhandle, subtitles)
-
- if self.usePlayAllPlaylist:
- play_all_name = '-- %s --' % self.translation(30060)
- stream_infos = {
- 'teaser_image': teaser_image,
- 'description': description
- }
- createPlayAllItem(play_all_name, self.pluginhandle, stream_infos)
-
- for segment in result.get('_embedded').get('segments'):
- listItem = self.JSONSegment2ListItem(segment)
- playlist.add(listItem[0], listItem[1])
-
- # Parses the Topic Overview Page
- def getThemen(self):
- try:
- response = self.__makeRequest(self.__urlTopics)
- responseCode = response.getcode()
- except ValueError as error:
- responseCode = 404
- except HTTPError as error:
- responseCode = error.getcode()
-
- if responseCode == 200:
- for topic in json.loads(response.read().decode('UTF-8')).get('_embedded').get('items'):
- title = topic.get('title').encode('UTF-8')
- description = topic.get('description')
- link = topic.get('id')
- addDirectory(title, None, self.defaultbackdrop, description, link, 'openTopic', self.pluginhandle)
-
- else:
- showDialog(self.translation(30045).encode('UTF-8'), self.translation(30046).encode('UTF-8'))
-
- # list all Trailers for further airings
- def getTrailers(self):
- try:
- response = self.__makeRequest(self.serviceAPITrailers)
- responseCode = response.getcode()
- except ValueError as error:
- responseCode = 404
- except HTTPError as error:
- responseCode = error.getcode()
-
- if responseCode == 200:
- for episode in json.loads(response.read().decode('UTF-8'))['_embedded']['items']:
- self.__JSONEpisode2ListItem(episode)
- else:
- showDialog(self.translation(30045).encode('UTF-8'), self.translation(30046).encode('UTF-8'))
-
- def getArchiv(self):
- pass
-
- # lists schedule overview (date listing)
- def getSchedule(self):
- for x in range(9):
- date = datetime.datetime.now() - datetime.timedelta(days=x)
- title = '%s' % (date.strftime('%A, %d.%m.%Y'))
- parameters = {'mode': 'openDate', 'link': date.strftime('%Y-%m-%d')}
- if x == 8:
- title = '%s %s' % (self.translation(30064), title)
- parameters = {'mode': 'openDate', 'link': date.strftime('%Y-%m-%d'), 'from': (date - datetime.timedelta(days=150)).strftime('%Y-%m-%d')}
- u = build_kodi_url(parameters)
- createListItem(title, None, None, None, date.strftime('%Y-%m-%d'), '', u, False, True, self.defaultbackdrop, self.pluginhandle)
-
- # Fetch stream details.
- def getStreamInfos(self, item, inputstreamAdaptive):
- infos = {}
- live_link = item.get('_links').get('self').get('href')
- response = url_get_request(live_link, self.httpauth)
- response_raw = response.read().decode('UTF-8')
- infos['live'] = json.loads(response_raw)
- infos['drmurl'] = self.JSONLicenseDrmURL(infos['live'])
- if inputstreamAdaptive and infos['live'].get('is_drm_protected'):
- infos['stream'] = self.JSONStreamingDrmURL(infos['live'])
- else:
- infos['stream'] = self.JSONStreamingURL(infos['live'].get('sources'))
-
- infos['uhd25_stream'] = self.JSONStreamingDrmURL(infos['live'], 'uhdbrowser')
- infos['uhd50_stream'] = self.JSONStreamingDrmURL(infos['live'], 'uhdsmarttv')
- infos['items'] = {}
- return infos
-
- # Builds a livestream item.
- def buildStreamItem(self, item, channel, stream_imfo, inputstreamAdaptive, use_restart=True):
- description = item.get('description')
- title = item.get('title')
- programName = channel
- if channel in self.__channelMap:
- programName = self.__channelMap[channel]
- livestreamStart = time.strptime(item.get('start')[0:19], '%Y-%m-%dT%H:%M:%S')
- livestreamEnd = time.strptime(item.get('end')[0:19], '%Y-%m-%dT%H:%M:%S')
- duration = max(time.mktime(livestreamEnd) - max(time.mktime(livestreamStart), time.mktime(time.localtime())), 1)
- contextMenuItems = []
- restart_url = False
- if inputstreamAdaptive and item.get('restart'):
- restart_parameters = {"mode": "liveStreamRestart", "link": item.get('id'), "lic_url": stream_imfo['drmurl']}
- restart_url = build_kodi_url(restart_parameters)
- contextMenuItems.append((self.translation(30063), 'RunPlugin(%s)' % restart_url))
-
- banner = self.JSONImage(item.get('_embedded').get('image'))
- item_title = "[%s] %s %s (%s)" % (programName, "[%s]" % self.translation(30063) if inputstreamAdaptive and restart_url else '', title, time.strftime('%H:%M', livestreamStart))
- if item.get('uhd') and stream_imfo['uhd25_stream']:
- createListItem("[UHD] %s" % item_title, banner, description, duration,time.strftime('%Y-%m-%d', livestreamStart), programName, stream_imfo['uhd25_stream'], True, False, self.defaultbackdrop, self.pluginhandle)
- if item.get('uhd') and stream_imfo['uhd50_stream']:
- createListItem("[UHD 50fps] %s" % item_title, banner, description, duration,time.strftime('%Y-%m-%d', livestreamStart), programName, stream_imfo['uhd50_stream'], True, False, self.defaultbackdrop, self.pluginhandle)
-
- createListItem(item_title, banner, description, duration, time.strftime('%Y-%m-%d', livestreamStart), programName, stream_imfo['stream'], True, False, self.defaultbackdrop, self.pluginhandle, contextMenuItems=contextMenuItems)
-
- # Returns Live Stream Listing
- def getLiveStreams(self):
- try:
- xbmcaddon.Addon('inputstream.adaptive')
- inputstreamAdaptive = True
- except RuntimeError:
- inputstreamAdaptive = False
-
- showFullSchedule = xbmcaddon.Addon().getSetting('showLiveStreamSchedule') == 'true'
-
- response = self.__makeRequestV4(self.__urlLiveChannels)
- response_raw = response.read().decode('UTF-8')
- channels = json.loads(response_raw)
- channelresults = {}
-
- # Prefetch the stream infos to limit request for each stream.
- for channel in channels:
- channelresults[channel] = {}
- channel_items = channels[channel].get('items')
- for channel_item in channel_items:
- channelresults[channel] = self.getStreamInfos(channel_item, inputstreamAdaptive)
- channelresults[channel]['items'] = channel_items
- break
-
- # Render current streams first.
- for channel in channels:
- for upcoming in channelresults[channel]['items']:
- if not 'upcoming' in channels[channel] or ('upcoming' in channels[channel] and upcoming.get('start')[0:17] == channels[channel]['upcoming'].get('start')[0:17]):
- if not 'upcoming' in channels[channel]:
- channels[channel]['upcoming'] = upcoming
- elif upcoming.get('start')[0:17] == channels[channel]['upcoming'].get('start')[0:17] and upcoming.get('id') != channels[channel]['upcoming'].get('id'):
- channelresults[channel] = self.getStreamInfos(upcoming, inputstreamAdaptive)
- self.buildStreamItem(upcoming, channel, channelresults[channel], inputstreamAdaptive)
-
- # Render upcoming streams last for better list item order.
- if showFullSchedule:
- addDirectory('[COLOR red]----------------[/COLOR]', None, self.defaultbackdrop, "", "", 'getLive', self.pluginhandle)
- for channel in channels:
- for upcoming in channelresults[channel]['items']:
- if not 'upcoming' in channels[channel] or channels[channel]['upcoming'].get('id') != upcoming.get('id'):
- self.buildStreamItem(upcoming, channel, channelresults[channel], inputstreamAdaptive, False)
-
- # Restart callback.
- def liveStreamRestart(self, link, protocol):
- try:
- xbmcaddon.Addon('inputstream.adaptive')
- except RuntimeError:
- return
-
- try:
- response = self.__makeRequest('livestream/' + link)
- responseCode = response.getcode()
- except HTTPError as error:
- responseCode = error.getcode()
-
- if responseCode == 200:
- result = json.loads(response.read().decode('UTF-8'))
- title = result.get('title').encode('UTF-8')
- image = self.JSONImage(result.get('_embedded').get('image'))
- description = result.get('description')
- duration = result.get('duration_seconds')
- date = time.strptime(result.get('start')[0:19], '%Y-%m-%dT%H:%M:%S')
- restart_urls = None
- restart_url = None
- try:
- restart_urls = result['_embedded']['channel']['restart_urls']
- except AttributeError:
- pass
- else:
- for x in ('android', 'default'):
- if x in restart_urls:
- restart_url = restart_urls[x]
- if restart_url:
- break
- if not restart_url:
- raise Exception("restart url not found in livestream/%s result" % (link, ))
- m = re.search(r"/livestreams/([^/]+)/sections/[^\?]*\?(?:.+&|)?X-Api-Key=([^&]+)", restart_url)
- if m:
- bitmovinStreamId, ApiKey = m.groups()
- else:
- raise Exception("unable to parse restart url: %s" % ( restart_url, ))
- response = url_get_request(restart_url) # nosec
- response_raw = response.read().decode('UTF-8')
- section = json.loads(response_raw)[0]
- section_id = section.get('id')
- timestamp = section.get('metaData').get('timestamp')
- streamingURL = 'https://playerapi-restarttv.ors.at/livestreams/%s/sections/%s/manifests/%s/?startTime=%s&X-Api-Key=%s' % (bitmovinStreamId, section_id, protocol, timestamp, ApiKey)
- listItem = createListItem(title, image, description, duration, time.strftime('%Y-%m-%d', date), result.get('SSA').get('channel').upper(), streamingURL, True, False, self.defaultbackdrop, self.pluginhandle)
- return streamingURL, listItem
-
- def __makeRequest(self, url):
- return url_get_request(self.__urlBase + url, self.httpauth)
-
- def __makeRequestV4(self, url):
- return url_get_request(self.__urlBaseV4 + url, self.httpauth)
-
- def __JSONEpisode2ListItem(self, JSONEpisode, ignoreEpisodeType=None):
- if JSONEpisode.get('killdate') is not None and time.strptime(JSONEpisode.get('killdate')[0:19], '%Y-%m-%dT%H:%M:%S') < time.localtime():
- return
-
- # Direcotory should be set to False, that the Duration is shown, but then there is an error with the Pluginhandle
- createListItem(
- JSONEpisode.get('title'),
- self.JSONImage(JSONEpisode.get('_embedded').get('image')),
- JSONEpisode.get('description'),
- JSONEpisode.get('duration_seconds'),
- time.strftime('%Y-%m-%d', time.strptime(JSONEpisode.get('date')[0:19], '%Y-%m-%dT%H:%M:%S')),
- JSONEpisode.get('_embedded').get('channel').get('name') if JSONEpisode.get('_embedded').get('channel') is not None else None,
- build_kodi_url({'mode': 'openEpisode', 'link': JSONEpisode.get('id')}),
- False,
- True,
- self.defaultbackdrop,
- self.pluginhandle,
- )
-
- def __JSONProfile2ListItem(self, jsonProfile):
- createListItem(
- jsonProfile.get('title'),
- self.JSONImage(jsonProfile.get('_embedded').get('image')),
- jsonProfile.get('description'),
- None,
- None,
- None,
- build_kodi_url({'mode': 'openProgram', 'link': jsonProfile.get('id')}),
- False,
- True,
- self.defaultbackdrop,
- self.pluginhandle
- )
-
- def __JSONVideoItem2ListItem(self, jsonVideoItem):
- if jsonVideoItem.get('_embedded').get('episode') is not None:
- self.__JSONEpisode2ListItem(jsonVideoItem.get('_embedded').get('episode'))
- elif jsonVideoItem.get('_embedded').get('segment') is not None:
- self.JSONSegment2ListItem(jsonVideoItem.get('_embedded').get('segment'))
diff --git a/plugin.video.orftvthek/resources/lib/Settings.py b/plugin.video.orftvthek/resources/lib/Settings.py
deleted file mode 100644
index 522168695e..0000000000
--- a/plugin.video.orftvthek/resources/lib/Settings.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import xbmcaddon
-
-__addon__ = xbmcaddon.Addon()
-
-
-def blacklist():
- return __addon__.getSetting('enableBlacklist') == 'true'
-
-
-def forceView():
- return __addon__.getSetting('forceView') == 'true'
-
-
-def localizedString(translation_id):
- return __addon__.getLocalizedString(translation_id)
-
-
-def serviceAPI():
- return __addon__.getSetting('useServiceAPI') == 'true'
-
-
-def subtitles():
- return __addon__.getSetting('useSubtitles') == 'true'
-
-
-def userAgent():
- return __addon__.getSetting('userAgent')
-
-
-def autoPlayPrompt():
- return __addon__.getSetting("autoPlayPrompt") == "true"
-
-
-def playAllPlaylist():
- return __addon__.getSetting('usePlayAllPlaylist') == 'true'
diff --git a/plugin.video.orftvthek/resources/lib/addon.py b/plugin.video.orftvthek/resources/lib/addon.py
new file mode 100644
index 0000000000..54a23a99a8
--- /dev/null
+++ b/plugin.video.orftvthek/resources/lib/addon.py
@@ -0,0 +1,287 @@
+import re
+import sys
+
+import routing
+
+from directory import Directory
+from kodi import Kodi
+from orf_on import OrfOn
+
+SETTINGS_FILE = 'settings.json'
+CHANNEL_MAP_FILE = 'channels.json'
+SEARCH_HISTORY_FILE = 'search_history'
+
+route_plugin = routing.Plugin()
+kodi_worker = Kodi(route_plugin)
+if not sys.argv[0].startswith('plugin://' + kodi_worker.addon_id + '/dialog'):
+ channel_map, channel_map_cached = kodi_worker.get_cached_file(CHANNEL_MAP_FILE)
+ settings, settings_cached = kodi_worker.get_cached_file(SETTINGS_FILE)
+ api = OrfOn(channel_map=channel_map, settings=settings, useragent=kodi_worker.useragent, kodi_worker=kodi_worker)
+ api.set_pager_limit(kodi_worker.pager_limit)
+ api.set_segments_behaviour(kodi_worker.use_segments)
+
+ kodi_worker.set_geo_lock(api.is_geo_locked())
+ channel_map = api.get_channel_map()
+ settings = api.get_settings()
+
+ # Only overwrite if cache was invalidated
+ if not channel_map_cached:
+ kodi_worker.save_json(channel_map, CHANNEL_MAP_FILE)
+
+ if not settings_cached:
+ kodi_worker.save_json(settings, SETTINGS_FILE)
+
+
+@route_plugin.route('/')
+def get_main_menu():
+ kodi_worker.log("Loading Main Menu", 'route')
+ if kodi_worker.is_geo_locked():
+ kodi_worker.render(
+ Directory(
+ kodi_worker.get_translation(30128, 'Geo lock active', ' [COLOR red]*** %s ***[/COLOR]'),
+ kodi_worker.get_translation(30129, 'Some content may not be available in your country'),
+ '/', translator=kodi_worker))
+ index_directories = api.get_main_menu()
+ for index_directory in index_directories:
+ kodi_worker.render(index_directory)
+ if not kodi_worker.hide_accessibility_menu():
+ kodi_worker.render(Directory(kodi_worker.get_translation(30147, 'Accessibility'), '', '/accessibility', '', 'accessibility', translator=kodi_worker))
+ kodi_worker.list_callback(None)
+
+
+@route_plugin.route('/page/start')
+def get_frontpage():
+ kodi_worker.log("Loading Frontpage Teasers", 'route')
+ teasers = api.get_frontpage()
+ for teaser in teasers:
+ kodi_worker.render(teaser)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/accessibility')
+def get_accessibility_menu():
+ if not kodi_worker.hide_sign_language():
+ kodi_worker.render(api.get_sign_language_menu())
+ if not kodi_worker.hide_audio_description():
+ kodi_worker.render(api.get_audio_description_menu())
+ if kodi_worker.use_subtitles:
+ kodi_worker.render(api.get_subtitles_menu())
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/livestreams')
+def get_livestreams():
+ kodi_worker.log("Loading Livestream Overview", 'route')
+ streams = api.get_live_schedule()
+ for stream in streams:
+ kodi_worker.render(stream)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/restart/')
+def get_live_restart(livestreamid):
+ kodi_worker.log("Playing Livestream Restart %s" % livestreamid, 'route')
+ livestream_item = api.get_livestream(livestreamid)
+ livestream_item = api.get_restart_stream(livestream_item)
+ kodi_worker.restart(livestream_item)
+
+
+@route_plugin.route('/profile/')
+def get_profile(profileid):
+ kodi_worker.log("Loading Profile %s" % profileid, 'route')
+ request_url = '/profile/%s' % profileid
+ directories = api.get_url(request_url)
+ if len(directories) > 1:
+ for directory in directories:
+ kodi_worker.render(directory)
+ kodi_worker.list_callback()
+ else:
+ videos = api.load_stream_data('/profile/%s/episodes' % profileid)
+ kodi_worker.play(videos)
+
+
+@route_plugin.route('/episode/')
+def get_episode(episodeid):
+ kodi_worker.log("Playing Episode %s" % episodeid, 'route')
+ videos = api.load_stream_data('/episode/%s' % episodeid)
+ kodi_worker.play(videos)
+
+
+@route_plugin.route('/episode//more')
+def get_show_from_episode(episodeid):
+ kodi_worker.log("Loading Shows from Episode %s" % episodeid, 'route')
+ other_episodes = api.get_related(episodeid)
+ for other_episode in other_episodes:
+ kodi_worker.render(other_episode)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/episode//segments')
+def get_segements(episodeid):
+ kodi_worker.log("Playing Episode %s" % episodeid, 'route')
+ videos = api.load_stream_data('/episode/%s/segments?limit=500' % episodeid)
+ if kodi_worker.use_segments and kodi_worker.show_segments:
+ for video in videos:
+ kodi_worker.render(video)
+ kodi_worker.list_callback()
+ else:
+ kodi_worker.play(videos)
+
+
+@route_plugin.route('/segment/')
+def get_segement(segmentid):
+ kodi_worker.log("Playing Segment %s" % segmentid, 'route')
+ videos = api.load_stream_data('/segment/%s' % segmentid)
+ kodi_worker.play(videos)
+
+
+@route_plugin.route('/videoitem/')
+def get_videoitem(videoid):
+ kodi_worker.log("Playing Video %s" % videoid, 'route')
+ videos = api.load_stream_data('/videoitem/%s' % videoid)
+ kodi_worker.play(videos)
+
+
+@route_plugin.route('/livestream/')
+def get_livestream(videoid):
+ kodi_worker.log("Playing Livestream %s" % videoid, 'route')
+ videos = api.load_stream_data('/livestream/%s' % videoid)
+ kodi_worker.play(videos)
+
+
+@route_plugin.route('/pvr/')
+def get_pvr_livestream(channelreel):
+ kodi_worker.log("Playing PVR Livestream %s" % channelreel, 'route')
+ livestream = api.get_pvr(channelreel)
+ if livestream:
+ kodi_worker.play([livestream])
+
+
+@route_plugin.route('/recent')
+def get_recently_added():
+ videos = api.get_last_uploads()
+ for video in videos:
+ kodi_worker.render(video)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/schedule')
+def get_schedule_selection():
+ kodi_worker.log("Opening Schedule Selection", 'route')
+ items, filters = api.get_schedule_dates()
+ selected = kodi_worker.select_dialog(kodi_worker.get_translation(30130, 'Select a date'), items)
+ api.log(selected)
+ if selected is not False and selected > -1:
+ api.log("Loading %s Schedule" % filters[selected])
+ request_url = api.api_endpoint_schedule % filters[selected]
+ target_url = kodi_worker.plugin.url_for_path(request_url)
+ kodi_worker.list_callback()
+ kodi_worker.execute('Container.Update(%s, replace)' % target_url)
+ else:
+ api.log("Canceled selection")
+
+
+@route_plugin.route('/schedule/')
+def get_schedule(scheduledate):
+ kodi_worker.log("Opening Schedule %s" % scheduledate, 'route')
+ request_url = api.api_endpoint_schedule % scheduledate
+ directories = api.get_url(request_url)
+ for directory in directories:
+ directory.annotate_channel()
+ directory.annotate_time()
+ kodi_worker.render(directory)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/search')
+def get_search():
+ kodi_worker.log("Opening Search History", 'route')
+ search_link = '/search/query'
+ search_dir = Directory(kodi_worker.get_translation(30131, 'Enter search ...', '%s ...'), "", search_link, translator=kodi_worker)
+ kodi_worker.render(search_dir)
+ directories = kodi_worker.get_stored_directories(SEARCH_HISTORY_FILE)
+ for directory in directories:
+ kodi_worker.render(directory)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/search/results/')
+def get_search_results(query):
+ directories = api.get_search(query)
+ for directory in directories:
+ kodi_worker.render(directory)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/search-partial//')
+def get_search_partial(section, query):
+ directories = api.get_search_partial(section, query, route_plugin.args)
+ for directory in directories:
+ kodi_worker.render(directory)
+ kodi_worker.list_callback()
+
+
+@route_plugin.route('/search/query')
+def get_search_dialog():
+ kodi_worker.log("Opening Search Dialog", 'route')
+ query = kodi_worker.get_keyboard_input()
+ search_url = "/search/results/%s" % query
+ search_history_dir = Directory(query, "", search_url, translator=kodi_worker)
+ kodi_worker.list_callback()
+ if query and query.strip() != "":
+ kodi_worker.store_directory(search_history_dir, 'search_history')
+ target_url = kodi_worker.plugin.url_for_path(search_url)
+ else:
+ error_url = '/search'
+ target_url = kodi_worker.plugin.url_for_path(error_url)
+ kodi_worker.execute('Container.Update(%s, replace)' % target_url)
+
+
+@route_plugin.route('/dialog/clear_search_history')
+def clear_search_history():
+ dialog = kodi_worker.get_progress_dialog(kodi_worker.get_translation(30132, 'Clearing search history'))
+ dialog.update(0, kodi_worker.get_translation(30133, 'Clearing ...', '%s ...'))
+ kodi_worker.clear_stored_directories(SEARCH_HISTORY_FILE)
+ dialog.update(100, kodi_worker.get_translation(30134, 'Done'))
+ dialog.close()
+
+
+@route_plugin.route('/dialog/reload_cache')
+def clear_cache():
+ dialog = kodi_worker.get_progress_dialog('Reloading cache')
+ dialog.update(0, kodi_worker.get_translation(30136, 'Reloading cache ...', '%s ...'))
+ kodi_worker.log("Reloading channel/settings cache", 'route')
+ tmp_channel_map, _ = kodi_worker.get_cached_file(CHANNEL_MAP_FILE)
+ tmp_settings, _ = kodi_worker.get_cached_file(SETTINGS_FILE)
+ kodi_worker.remove_file(SETTINGS_FILE)
+ kodi_worker.remove_file(CHANNEL_MAP_FILE)
+ tmp_api = OrfOn(channel_map=tmp_channel_map, settings=tmp_settings, useragent=kodi_worker.useragent, kodi_worker=kodi_worker)
+ tmp_api.channel_map = False
+ tmp_api.settings = False
+ dialog.update(33, kodi_worker.get_translation(30137, 'Loading channels'))
+ tmp_channel_map = tmp_api.get_channel_map()
+ kodi_worker.save_json(tmp_channel_map, CHANNEL_MAP_FILE)
+ dialog.update(66, kodi_worker.get_translation(30138, 'Loading settings'))
+ tmp_settings = tmp_api.get_settings()
+ kodi_worker.save_json(tmp_settings, SETTINGS_FILE)
+ dialog.update(100, kodi_worker.get_translation(30134, 'Done'))
+ dialog.close()
+
+
+@route_plugin.route('')
+def get_url(url):
+ if re.search(r"^/https?://", url):
+ url = url[1:]
+ kodi_worker.log("Opening Video Url %s" % url, 'route')
+ kodi_worker.play_url(url)
+ else:
+ kodi_worker.log("Opening Generic Url %s" % url, 'route')
+ request_url = kodi_worker.build_url(url, route_plugin.args)
+ directories = api.get_url(request_url)
+ for directory in directories:
+ kodi_worker.render(directory)
+ kodi_worker.list_callback()
+
+
+def run():
+ route_plugin.run()
diff --git a/plugin.video.orftvthek/default.py b/plugin.video.orftvthek/resources/lib/default.py
similarity index 50%
rename from plugin.video.orftvthek/default.py
rename to plugin.video.orftvthek/resources/lib/default.py
index 8f5ab320b4..76a95e8312 100644
--- a/plugin.video.orftvthek/default.py
+++ b/plugin.video.orftvthek/resources/lib/default.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-from resources.lib import Addon
+import addon
-Addon.run()
+addon.run()
diff --git a/plugin.video.orftvthek/resources/lib/directory.py b/plugin.video.orftvthek/resources/lib/directory.py
new file mode 100644
index 0000000000..0fcb85f498
--- /dev/null
+++ b/plugin.video.orftvthek/resources/lib/directory.py
@@ -0,0 +1,428 @@
+import re
+from datetime import datetime, timedelta
+
+
+class Directory:
+ def __init__(self, title, description, link, content_id="", content_type="", thumbnail="", backdrop="", poster="", source={}, translator=None):
+ self.translator = translator
+ self.title = title
+ if description:
+ self.description = description.strip()
+ else:
+ self.description = " "
+ self.link = link
+ self.content_id = content_id
+ self.content_type = content_type
+ self.thumbnail = thumbnail
+ self.backdrop = backdrop
+ self.poster = poster
+ self.meta = self.build_meta(source)
+ self.source = source
+ self.videos = {}
+ self.context_menu = self.build_content_menu()
+ self.pvr_mode = False
+
+ def has_segments(self):
+ seg_matcher = r"\/episode\/[1-10].*\/segments"
+ if re.search(seg_matcher, self.link):
+ return True
+ return False
+
+ def build_content_menu(self) -> list:
+ context_menu_items = []
+
+ if self.is_livestream():
+ restart_url = self.get_restart()
+ if restart_url:
+ context_menu_items.append({
+ 'title': self.translate_string(30139, 'Restart'),
+ 'url': "restart/%s" % self.source.get('id'),
+ 'type': 'run'
+ })
+
+ if self.type() == 'episode':
+ context_menu_items.append({
+ 'title': self.translate_string(30140, 'All episodes'),
+ 'url': "episode/%s/more" % self.source.get('id'),
+ 'type': 'update'
+ })
+
+ if self.type() == 'segment' and self.source.get('episode_id'):
+ context_menu_items.append({
+ 'title': self.translate_string(30140, 'All episodes'),
+ 'url': "episode/%s/more" % self.source.get('episode_id'),
+ 'type': 'update'
+ })
+
+ if 'genre_id' in self.source and 'id' in self.source:
+ related_link = "/lane/related_content/%s/%s" % (self.source.get('genre_id'), self.source.get('id'))
+ context_menu_items.append({
+ 'title': self.translate_string(30150, 'Related content'),
+ 'url': related_link,
+ 'type': 'update'
+ })
+
+ return context_menu_items
+
+ def get_context_menu(self) -> list:
+ return self.context_menu
+
+ def translate_string(self, translation_id, fallback, replace=None):
+ if self.translator:
+ return self.translator.get_translation(translation_id, fallback, replace)
+
+ return fallback
+
+ @staticmethod
+ def build_meta(item) -> dict:
+ meta = {}
+ if 'online_episode_count' in item:
+ meta['episodes'] = item['online_episode_count']
+
+ # Show Meta
+ if 'genre_title' in item and item['genre_title'] is not None:
+ if item['genre_title'] == 'Film & Serie' and 'sub_headline' in item and item['sub_headline'] is not None:
+ meta['genre'] = item['sub_headline']
+ else:
+ meta['genre'] = item['genre_title']
+
+ if 'genre_id' in item and item['genre_id'] is not None:
+ meta['genre_id'] = item['genre_id']
+ if 'production_year' in item and item['production_year'] is not None:
+ meta['year'] = item['production_year']
+ if 'production_country' in item and item['production_country'] is not None:
+ meta['country'] = item['production_country']
+
+ # Build Release Infos
+ if 'date' in item and item['date'] is not None:
+ meta['release_date'] = item['date']
+ elif 'episode_date' in item and item['episode_date'] is not None:
+ meta['release_date'] = item['episode_date']
+ elif 'updated_at' in item and item['updated_at'] is not None:
+ meta['release_date'] = item['updated_at']
+
+ # Build additional Title Infos
+ if 'headline' in item and item['headline'] is not None:
+ meta['headline'] = item['headline']
+ if 'sub_headline' in item and item['sub_headline'] is not None:
+ meta['sub_headline'] = item['sub_headline']
+ if 'episode_title' in item and item['episode_title'] is not None:
+ meta['episode'] = item['episode_title']
+
+ # Build Channel Info
+ if 'main_channel_id' in item and item['main_channel_id'] is not None:
+ if str(item['main_channel_id']) in item['channel_meta']:
+ meta['channel'] = item['channel_meta'][str(item['main_channel_id'])]
+ meta['channel_id'] = item['main_channel_id']
+ elif 'channel_id' in item and item['channel_id'] is not None:
+ if str(item['channel_id']) in item['channel_meta']:
+ meta['channel'] = item['channel_meta'][str(item['channel_id'])]
+ meta['channel_id'] = item['channel_id']
+ elif 'SSA' in item and 'channel' in item['SSA']:
+ for channel in item['channel_meta']:
+ if item['channel_meta'][channel]['reel'] == item['SSA']['channel']:
+ meta['channel'] = item['channel_meta'][channel]
+ break
+
+ # Build Accessibility infos
+ if 'audio_description_service_available' in item and item['audio_description_service_available'] is not None:
+ meta['audio_description_available'] = item['audio_description_service_available']
+ if 'has_subtitle' in item and item['has_subtitle'] is not None:
+ meta['subtitles'] = item['has_subtitle']
+ else:
+ meta['subtitles'] = False
+
+ # Stream Meta
+ if 'two_channel_audio' in item and item['two_channel_audio'] is not None:
+ meta['multiaudio'] = item['two_channel_audio']
+ if 'restart' in item and item['restart'] is not None:
+ meta['restart'] = item['restart']
+ if 'uhd' in item and item['uhd'] is not None:
+ meta['uhd'] = item['uhd']
+ if 'duration_seconds' in item and item['duration_seconds'] is not None:
+ meta['duration'] = int(item['duration_seconds'])
+ if 'audio_description' in item and item['audio_description'] is not None:
+ meta['audio_description'] = item['audio_description']
+ elif 'audio_description_service_available' in item and item.get('title').startswith("AD | "):
+ meta['audio_description'] = True
+ else:
+ meta['audio_description'] = False
+ if 'oegs' in item and item['oegs'] is not None:
+ meta['oegs'] = item['oegs']
+ elif 'is_oegs' in item and item['is_oegs'] is not None:
+ meta['oegs'] = item['is_oegs']
+ else:
+ meta['oegs'] = False
+
+ if 'right' in item and item['right'] is not None and item['right'] == 'austria':
+ meta['geo_lock'] = True
+ else:
+ meta['geo_lock'] = False
+ return meta
+
+ def label(self) -> str:
+ if self.meta.get('headline') and self.meta.get('headline') != self.title and self.meta.get('headline') not in self.title:
+ label = "%s | %s" % (self.title, self.meta.get('headline'))
+ else:
+ label = self.title
+
+ if self.is_livestream() and self.get_channel() and not self.is_pvr_mode():
+ channel = self.get_channel()
+ if self.meta.get('restart'):
+ label = "[LIVE] [R] [%s] %s" % (channel, label)
+ else:
+ label = "[LIVE] [%s] %s" % (channel, label)
+ elif self.is_pvr_mode():
+ channel = self.get_channel()
+ label = "%s | %s" % (channel, label)
+ return label
+
+ def label2(self) -> str:
+ if self.meta.get('sub_headline') and self.meta.get('sub_headline') != self.title and self.meta.get('sub_headline') not in self.title:
+ return self.meta.get('sub_headline')
+
+ def is_livestream(self) -> bool:
+ return self.type() == 'timeshift' or self.type() == 'livestream'
+
+ def livestream_active(self) -> bool:
+ current_time = datetime.now()
+ start_time = self.get_start_time()
+ end_time = self.get_end_time()
+ if start_time < current_time < end_time:
+ return True
+
+ def is_geo_locked(self):
+ return self.meta.get('geo_lock')
+
+ def has_audio_description(self):
+ return self.meta.get('audio_description')
+
+ def has_sign_language(self):
+ return self.meta.get('oegs')
+
+ def get_start_time(self):
+ return datetime.fromisoformat(self.get_source().get('start')).replace(tzinfo=None)
+
+ def get_start_time_iso(self):
+ ref_date = datetime.fromisoformat(self.get_source().get('start')) - timedelta(hours=2)
+ d_date = ref_date.strftime("%Y%m%d")
+ d_time = ref_date.strftime("%H%M%S")
+ return "%sT%s" % (d_date, d_time)
+
+ def get_end_time(self):
+ return datetime.fromisoformat(self.get_source().get('end')).replace(tzinfo=None)
+
+ def set_channel(self, channel_reel):
+ for channel in self.source['channel_meta']:
+ if self.source['channel_meta'][channel]['reel'] == channel_reel:
+ self.meta['channel'] = self.source['channel_meta'][channel]
+ break
+
+ def has_timeshift(self):
+ if 'timeshift_available_livestream' in self.source and 'video_type' in self.source:
+ if self.source['timeshift_available_livestream'] and self.source['video_type'] == 'timeshift':
+ return True
+ return False
+
+ def get_restart(self):
+ if 'restart' in self.source:
+ if self.source['restart'] and '_embedded' in self.source and 'channel' in self.source['_embedded']:
+ restart_url = self.source['_embedded']['channel']['channel_restart_url_hbbtv']
+ return restart_url
+ return False
+
+ def get_channel(self):
+ special_regex = r"^web\d*"
+ if self.meta.get('channel'):
+ if re.search(special_regex, self.meta.get('channel').get('name')):
+ return "Special"
+ return self.meta.get('channel').get('name')
+
+ def get_channel_logo(self):
+ if self.meta.get('channel'):
+ return self.meta.get('channel').get('logo')
+
+ def get_resolution(self):
+ if self.meta.get('uhd'):
+ return 3840, 2160
+
+ return 1280, 720
+
+ def set_stream(self, sources):
+ self.videos = sources
+
+ def get_stream(self):
+ return self.videos
+
+ def date(self):
+ return self.meta.get('release_date')
+
+ def time(self):
+ try:
+ return datetime.fromisoformat(self.date()).strftime("%H:%M")
+ except TypeError:
+ self.log('No time set for %s' % self.label())
+
+ def get_description(self) -> str:
+ if self.description is not None:
+ return self.description
+
+ return ""
+
+ def get_meta_description(self):
+ meta_description = {}
+ if not self.is_pvr_mode():
+ if self.get_episodes() > 1:
+ meta_description[self.translate_string(30141, 'Episodes')] = self.get_episodes()
+ if self.get_channel():
+ meta_description[self.translate_string(30142, 'Channel')] = self.get_channel()
+ if self.is_livestream() and self.get_stream_runtime():
+ meta_description[self.translate_string(30113, 'Livestream')] = self.get_stream_runtime()
+ if not self.livestream_active():
+ meta_description[self.translate_string(30114, 'Starts in')] = "%d min" % self.get_stream_start_delta()
+ if 'episode_title' in self.get_source() and 'sub_headline' in self.get_source():
+ meta_description[self.meta.get('sub_headline')] = ""
+
+ return meta_description
+
+ def get_stream_start_delta(self):
+ current_time = datetime.now()
+ start_time = self.get_start_time()
+ return int((start_time - current_time).total_seconds() / 60)
+
+ def get_stream_runtime(self):
+ start_time = self.get_start_time().strftime("%H:%M")
+ end_time = self.get_start_time().strftime("%H:%M")
+ if start_time != end_time:
+ return "%s - %s" % (start_time, end_time)
+
+ def annotate_time(self):
+ self.title = "%s | %s" % (self.time(), self.title)
+
+ def annotate_channel(self):
+ self.title = "[%s] %s" % (self.get_channel(), self.title)
+
+ def country(self):
+ return self.meta.get('country')
+
+ def year(self):
+ return self.meta.get('year')
+
+ def genre(self) -> str:
+ if 'genre' in self.meta:
+ return self.meta.get('genre')
+
+ def has_art(self) -> bool:
+ if self.poster or self.thumbnail or self.backdrop:
+ return True
+ return False
+
+ def is_playable(self) -> bool:
+ if self.type() == 'episode':
+ return True
+ if self.type() == 'segment':
+ return True
+ if self.is_livestream():
+ return True
+ if self.get_episodes() == 1 and self.type() == 'temporary':
+ return True
+ return False
+
+ def get_episodes(self):
+ if self.meta.get('episodes') is not None:
+ return int(self.meta.get('episodes'))
+ return 1
+
+ def get_cast(self):
+ cast = []
+ part = None
+ cast_extract_pattern = r'(?P.*?)(\s\((?P.*?)\)|,|u.v.m| u.a.|u. a.)'
+ if 'Besetzung:' in self.description:
+ part = self.description.split('Besetzung:')
+ elif 'Hauptdarsteller:' in self.description:
+ part = self.description.split('Hauptdarsteller:')
+ elif 'Besetzung\r\n' in self.description:
+ part = self.description.split('Besetzung\r\n')
+ elif 'Hauptdarsteller\r\n' in self.description:
+ part = self.description.split('Hauptdarsteller\r\n')
+ elif 'Mit:' in self.description:
+ part = self.description.split('Mit:')
+ elif '\r\nMit ' in self.description:
+ part = self.description.split('\r\nMit ')
+
+ try:
+ if part is not None and len(part) > 1:
+ matches = re.findall(cast_extract_pattern, part[1], re.DOTALL)
+ for name, _, role in matches:
+ if name.strip() != "":
+ if '\r\n' in name.strip() or 'Regie:' in name.strip():
+ break
+ if role.strip() != "":
+ cast.append((name.strip(), role.strip()))
+ else:
+ cast.append(name.strip())
+ return cast
+ except re.error:
+ return cast
+
+ def url(self) -> str:
+ return self.link
+
+ def set_url(self, url):
+ self.link = url
+
+ def type(self) -> str:
+ return self.content_type
+
+ def get_duration(self):
+ if self.meta.get('duration') is not None:
+ return int(self.meta.get('duration'))
+
+ def is_pvr_mode(self):
+ return self.pvr_mode
+
+ def set_pvr_mode(self):
+ self.pvr_mode = True
+
+ def media_type(self) -> str:
+ contenttype = self.type()
+ if self.label2() == 'Fernsehfilm':
+ return 'movie'
+ if self.get_duration() is not None and self.get_duration() > 60 * 60:
+ return 'movie'
+ if contenttype == 'lane':
+ return 'video'
+ if contenttype == 'episode':
+ return 'episode'
+ if contenttype == 'segment':
+ return 'episode'
+ if contenttype == 'temporary':
+ return 'tvshow'
+ return 'movie'
+
+ def get_source(self):
+ return self.source
+
+ def debug(self):
+ self.log('Title: %s' % self.title)
+ self.log('Playable: %s' % self.is_playable())
+ self.log('Description: %s' % self.description)
+ self.log('Link: %s' % self.link)
+ self.log('ID: %s' % self.content_id)
+ self.log('Type: %s' % self.content_type)
+ self.log('Playable: %s' % self.is_playable())
+ self.log('Thumbnail: %s' % self.thumbnail)
+ self.log('Backdrop: %s' % self.backdrop)
+ self.log('Poster: %s' % self.poster)
+ for (key, value) in self.meta.items():
+ self.log("%s: %s" % (key.capitalize().replace("_", " "), value))
+
+ for context_menu_item in self.context_menu:
+ self.log('Context Menu Item: %s' % context_menu_item.get('title'))
+
+ if len(self.get_stream()):
+ self.log('Stream Data available %d' % len(self.get_stream()))
+
+ def log(self, msg, msg_type='info'):
+ if self.translator:
+ self.translator.log("[%s][ORFON][DIRECTORY] %s" % (msg_type.upper(), msg))
diff --git a/plugin.video.orftvthek/resources/lib/kodi.py b/plugin.video.orftvthek/resources/lib/kodi.py
new file mode 100644
index 0000000000..d2750a2084
--- /dev/null
+++ b/plugin.video.orftvthek/resources/lib/kodi.py
@@ -0,0 +1,420 @@
+import json
+import os
+import re
+import sys
+import time
+from urllib.parse import unquote
+
+import xbmcaddon
+from xbmc import PlayList, PLAYLIST_VIDEO, Player, Keyboard, executebuiltin, log, LOGDEBUG
+from xbmcgui import ListItem, Dialog, DialogProgress
+from xbmcaddon import Addon
+from xbmcplugin import addDirectoryItem, endOfDirectory, setContent, setResolvedUrl, addSortMethod, SORT_METHOD_VIDEO_TITLE, SORT_METHOD_DATE
+import xbmcvfs
+import inputstreamhelper
+
+from directory import Directory
+
+
+class Kodi:
+ version_regex = r"plugin:\/\/([^\/]+)"
+ addon_id = re.search(version_regex, sys.argv[0]).groups()[0]
+ addon = Addon()
+ data_folder = xbmcvfs.translatePath("special://profile/addon_data/%s" % addon_id)
+
+ input_stream_protocol = 'mpd'
+ input_stream_drm_version = 'com.widevine.alpha'
+ input_stream_mime = 'application/dash+xml'
+ input_stream_license_contenttype = 'application/octet-stream'
+
+ geo_lock = False
+ max_cache_age = 60 * 60 * 24
+
+ def __init__(self, plugin):
+ self.plugin = plugin
+ self.init_storage()
+ self.base_path = self.addon.getAddonInfo('path')
+ self.resource_path = os.path.join(self.base_path, "resources")
+ self.use_subtitles = self.addon.getSetting('useSubtitles') == 'true'
+ self.use_segments = self.addon.getSetting('useSegments') == 'true'
+ self.show_segments = self.addon.getSetting('showSegments') == 'true'
+ self.use_timeshift = self.addon.getSetting('useTimeshift') == 'true'
+ self.hide_audio_description_content = self.addon.getSetting('hideAD') == 'true'
+ self.hide_sign_language_content = self.addon.getSetting('hideOEGS') == 'true'
+ self.useragent = self.addon.getSetting('userAgent')
+ self.pager_limit = int(self.addon.getSetting('pagerLimit'))
+ self.max_cache_age = int(self.addon.getSetting('maxCacheAge')) * 60 * 60 * 24
+
+ def init_storage(self):
+ if not os.path.exists(self.data_folder):
+ os.makedirs(self.data_folder)
+
+ def translate(self, translation_id):
+ translation = self.addon.getLocalizedString
+ return translation(translation_id)
+
+ def get_translation(self, translation_id, fallback, replace=None):
+ translation = self.translate(translation_id)
+ if translation:
+ if replace is not None:
+ return replace % translation
+
+ return translation
+ return fallback
+
+ def is_geo_locked(self) -> bool:
+ return self.geo_lock
+
+ def hide_audio_description(self) -> bool:
+ return self.hide_audio_description_content
+
+ def hide_sign_language(self) -> bool:
+ return self.hide_sign_language_content
+
+ def hide_accessibility_menu(self) -> bool:
+ return self.hide_sign_language_content and self.hide_audio_description_content and not self.use_subtitles
+
+ def set_geo_lock(self, lock):
+ self.geo_lock = lock
+
+ def hide_content(self, item) -> bool:
+ if self.hide_audio_description_content and item.has_audio_description():
+ self.log("Hiding %s because AD content hide is enabled in settings" % item.label())
+ return True
+ if self.hide_sign_language_content and item.has_sign_language():
+ self.log("Hiding %s because OEGS content hide is enabled in settings" % item.label())
+ return True
+ if self.geo_lock and item.is_geo_locked():
+ self.log("Hiding %s because GEO Lock is active for your ISP" % item.label())
+ return True
+ return False
+
+ def render(self, item):
+ if not self.hide_content(item):
+ if item.is_playable():
+ list_item = self.render_video(item)
+ link = item.url()
+ route = self.plugin.url_for_path(link)
+ folder = self.use_segments and self.show_segments and item.has_segments()
+ addDirectoryItem(self.plugin.handle, url=route, listitem=list_item, isFolder=folder)
+ else:
+ list_item = self.render_directory(item)
+ link = item.url()
+ route = self.plugin.url_for_path(link)
+ addDirectoryItem(self.plugin.handle, url=route, listitem=list_item, isFolder=True)
+
+ def restart(self, video):
+ self.log("Running Restart Play Action")
+ play_item = self.render_video(video)
+ play_item.setProperty('inputstream.adaptive.play_timeshift_buffer', 'true')
+ streaming_url = video.get_stream().get('url')
+ Player().play(streaming_url, play_item)
+
+ def build_stream_url(self, url):
+ return "%s|User-Agent=%s" % (url, self.useragent)
+
+ def play_url(self, url):
+ url = self.build_stream_url(unquote(url))
+ play_item = ListItem(path=url, offscreen=True)
+ setResolvedUrl(self.plugin.handle, True, play_item)
+
+ def play(self, videos):
+ if len(videos) < 1:
+ Dialog().notification('No Stream available', 'Unable to find a stream for this content', xbmcaddon.Addon().getAddonInfo('icon'))
+ self.log("Running Play Action")
+ playlist = PlayList(PLAYLIST_VIDEO)
+ tracks = []
+ for video in videos:
+ tracks.append(video)
+
+ if len(tracks) > 1:
+ if self.show_segments:
+ for track in tracks:
+ self.render(track)
+ else:
+ for track in tracks:
+ play_item = self.render_video(track)
+ play_stream = self.build_stream_url(unquote(track.get_stream().get('url')))
+ playlist.add(play_stream, play_item)
+ self.log("Playing Playlist %s from position %d" % (playlist.size(), playlist.getposition()))
+ else:
+ self.log("Playing Single Video")
+ for track in tracks:
+ play_item = self.render_video(track)
+ setResolvedUrl(self.plugin.handle, True, play_item)
+ break
+
+ def render_directory(self, directory) -> ListItem:
+ title = directory.label()
+ title2 = directory.label2()
+
+ list_item = ListItem(offscreen=True)
+ list_item.setContentLookup(False)
+ list_item.setLabel(title)
+ list_item.setLabel2(title2)
+ item_info = self.build_info(directory)
+ list_item.setInfo(type="Video", infoLabels=item_info)
+ list_item.setIsFolder(not directory.is_playable())
+ list_item.setProperty("IsPlayable", str(directory.is_playable()))
+ item_art = self.build_art(directory)
+ list_item.setArt(item_art)
+ return list_item
+
+ def render_video(self, teaser) -> ListItem:
+ title = teaser.label()
+ title2 = teaser.label2()
+ stream_url = self.build_stream_url(unquote(teaser.url()))
+
+ headers = "User-Agent=%s&Content-Type=%s" % (self.useragent, self.input_stream_license_contenttype)
+ is_helper = inputstreamhelper.Helper(self.input_stream_protocol, drm=self.input_stream_drm_version)
+ if is_helper.check_inputstream():
+ list_item = ListItem(path=stream_url, offscreen=True)
+ list_item.setContentLookup(False)
+
+ if teaser.get_stream():
+ self.log("Found Stream for Video %s" % teaser.label())
+ self.log("Stream: (%s)" % teaser.url())
+ stream_data = teaser.get_stream()
+ list_item.setProperty('inputstream', 'inputstream.adaptive')
+ list_item.setProperty('inputstream.adaptive.stream_headers', headers)
+ list_item.setProperty('inputstream.adaptive.manifest_type', self.input_stream_protocol)
+
+ if self.use_subtitles and stream_data.get('subtitle') and stream_data.get('subtitle') is not None:
+ list_item.setSubtitles([stream_data.get('subtitle')])
+ list_item.addStreamInfo('subtitle', {'language': 'deu'})
+
+ if stream_data['drm']:
+ self.log("Video %s is DRM protected. Adding DRM relevant parameters" % teaser.label())
+ list_item.setMimeType(self.input_stream_mime)
+ list_item.setProperty('inputstream', 'inputstream.adaptive')
+ list_item.setProperty('inputstream.adaptive.stream_headers', headers)
+ list_item.setProperty('inputstream', is_helper.inputstream_addon)
+ list_item.setProperty('inputstream.adaptive.manifest_type', self.input_stream_protocol)
+ license_url = "%s?BrandGuid=%s&userToken=%s" % (stream_data.get('drm_widewine_url'), stream_data.get('drm_widewine_brand'), stream_data.get('drm_token'))
+ list_item.setProperty('inputstream.adaptive.license_type', self.input_stream_drm_version)
+ list_item.setProperty('inputstream.adaptive.license_key', license_url + '|' + headers + '|R{SSM}|')
+ else:
+ self.log("No Stream for Video %s (%s)" % (teaser.label(), teaser.url()), 'error')
+
+ list_item.setLabel(title)
+ list_item.setLabel2(title2)
+ item_info = self.build_info(teaser)
+
+ list_item.setInfo(type="Video", infoLabels=item_info)
+ list_item.setIsFolder(not teaser.is_playable())
+ list_item.setProperty("IsPlayable", str(teaser.is_playable()))
+ video_w, video_h = teaser.get_resolution()
+ list_item.addStreamInfo('video', {'aspect': '1.78', 'codec': 'h264', 'width': video_w, 'height': video_h, 'duration': teaser.get_duration()})
+ list_item.addStreamInfo('audio', {'codec': 'aac', 'language': 'deu', 'channels': 2})
+
+ item_art = self.build_art(teaser)
+ list_item.setArt(item_art)
+
+ context_menu = []
+ context_menu_items = teaser.get_context_menu()
+ for context_menu_item in context_menu_items:
+ context_menu.append(self.build_context_menu(context_menu_item))
+ list_item.addContextMenuItems(context_menu, replaceItems=True)
+ return list_item
+ if not teaser.get_stream():
+ Dialog().notification('No Stream available', 'Unable to find a stream for %s' % title, xbmcaddon.Addon().getAddonInfo('icon'))
+ elif not is_helper.check_inputstream():
+ Dialog().notification('Inputstream Adaptive not available', 'Install Inputstream Adaptive and Inputstream Helper', xbmcaddon.Addon().getAddonInfo('icon'))
+
+ def build_info(self, item) -> dict:
+ desc_prefix = self.build_meta_description(item)
+ if desc_prefix is not None and item.get_description():
+ generated_description = desc_prefix + item.get_description()
+ generated_outline = desc_prefix + self.truncate_string(item.get_description())
+ else:
+ generated_description = item.get_description()
+ generated_outline = self.truncate_string(item.get_description())
+ return {
+ 'title': item.label(),
+ 'originaltitle': item.label(),
+ 'sorttitle': item.label(),
+ 'tvshowtitle': item.label(),
+ 'plot': generated_description,
+ 'plotoutline': generated_outline,
+ 'genre': item.genre(),
+ 'tag': item.genre(),
+ 'aired': item.date(),
+ 'country': item.country(),
+ 'year': item.year(),
+ 'mediatype': item.media_type(),
+ 'cast': item.get_cast()
+ }
+
+ def build_art(self, item) -> dict:
+ return {
+ 'thumb': item.thumbnail or self.get_media('icon.jpg'),
+ 'poster': item.poster or self.get_media('poster.jpg'),
+ 'fanart': item.backdrop or self.get_media('fanart.jpg'),
+ }
+
+ def build_context_menu(self, item):
+ route = self.plugin.url_for_path(item.get('url'))
+ if item.get('type') == 'run':
+ return item.get('title'), 'RunPlugin(%s)' % route
+
+ return item.get('title'), 'Container.Update(%s)' % route
+
+ def list_callback(self, content_type="movies", sort=False) -> None:
+ if content_type:
+ setContent(self.plugin.handle, content_type)
+ if sort:
+ addSortMethod(int(sys.argv[1]), SORT_METHOD_DATE)
+ addSortMethod(int(sys.argv[1]), SORT_METHOD_VIDEO_TITLE)
+ endOfDirectory(self.plugin.handle, True)
+
+ def get_media(self, filename):
+ return os.path.join(self.resource_path, filename)
+
+ def clear_stored_directories(self, storage_key):
+ target_file = '%s.json' % storage_key
+ self.remove_file(target_file)
+
+ def get_stored_directories(self, storage_key):
+ target_file = '%s.json' % storage_key
+ self.init_storage()
+ json_data = self.load_json(target_file)
+ directories = []
+ for json_item in json_data:
+ directory = Directory(json_item.get('title'), json_item.get('description'), json_item.get('link'), translator=self)
+ directories.append(directory)
+ return directories
+
+ def store_directory(self, directory, storage_key):
+ target_file = '%s.json' % storage_key
+ self.init_storage()
+
+ json_data = self.load_json(target_file)
+ directory_json = {
+ 'title': directory.title,
+ 'description': directory.description,
+ 'link': directory.url()
+ }
+
+ if json_data:
+ json_data.append(directory_json)
+ else:
+ json_data = [directory_json]
+ self.save_json(json_data, target_file)
+
+ def remove_file(self, file) -> bool:
+ file = "%s/%s" % (self.data_folder, file)
+ try:
+ os.remove(file)
+ return True
+ except FileNotFoundError:
+ self.log("File %s could not be found. Skipping remove action." % file, 'warning')
+ return False
+
+ def get_cached_file(self, file) -> tuple:
+ channel_map_age = self.get_file_age(file)
+
+ if self.max_cache_age > channel_map_age >= 0:
+ self.log("Channel Cache is valid. File age lower than %s seconds (%d seconds)" % (self.max_cache_age, channel_map_age))
+ data = self.load_json(file)
+ if not len(data):
+ cached = False
+ else:
+ cached = True
+ else:
+ self.log("Channel Cache is invalid. Reloading because file age larger than %s seconds (%d seconds)" % (self.max_cache_age, channel_map_age))
+ cached = False
+ self.remove_file(file)
+ data = {}
+ return data, cached
+
+ def get_file_age(self, file) -> int:
+ file = "%s/%s" % (self.data_folder, file)
+ try:
+ st = os.stat(file)
+ age_seconds = int(time.time() - st.st_mtime)
+ self.log("Cache Age %d seconds" % age_seconds)
+ return age_seconds
+ except FileNotFoundError:
+ self.log("File %s could not be found" % file, 'warning')
+ return -1
+
+ def save_json(self, json_data, file) -> bool:
+ file = "%s/%s" % (self.data_folder, file)
+ try:
+ with open(file, 'w') as data_file:
+ data_file.write(json.dumps(json_data))
+ data_file.close()
+ return True
+ except TypeError:
+ self.log("Json file format for %s was invalid. Removing file ..." % file, 'warning')
+ os.remove(file)
+ return False
+ except PermissionError:
+ self.log("Permission to File %s was denied" % file, 'warning')
+ return False
+
+ def load_json(self, file) -> list:
+ file = "%s/%s" % (self.data_folder, file)
+ self.log("Loading JSON from %s" % file)
+ try:
+ with open(file, 'r') as data_file:
+ data = json.load(data_file)
+ return data
+ except FileNotFoundError:
+ self.log("File %s could not be found" % file, 'warning')
+ return []
+
+ @staticmethod
+ def log(msg, msg_type='info'):
+ log("[%s][ORFON][KODI] %s" % (msg_type.upper(), msg), LOGDEBUG)
+
+ @staticmethod
+ def execute(command):
+ executebuiltin(command)
+
+ @staticmethod
+ def select_dialog(title, items):
+ select_dialog = Dialog()
+ selected = select_dialog.select(title, items)
+ if selected != -1:
+ return selected
+ return False
+
+ @staticmethod
+ def get_progress_dialog(title, description=""):
+ progress = DialogProgress()
+ progress.create(title, description)
+ return progress
+
+ @staticmethod
+ def build_meta_description(item):
+ desc = ""
+ meta_desc = item.get_meta_description()
+ for label in meta_desc:
+ desc += "\n[COLOR blue][LIGHT]%s[/LIGHT][/COLOR] %s" % (label, meta_desc[label])
+ if desc != "":
+ desc += "\n\n"
+ return desc
+
+ @staticmethod
+ def truncate_string(str_value, max_len=400) -> str:
+ if str_value:
+ return str_value[:max_len] + (str_value[max_len:] and ' ...')
+
+ @staticmethod
+ def build_url(url, args) -> str:
+ arg_str = ""
+ for arg in args:
+ if not arg_str:
+ arg_str = "?%s=%s" % (arg, args.get(arg)[0])
+ else:
+ arg_str += "&%s=%s" % (arg, args.get(arg)[0])
+ return "%s%s" % (url, arg_str)
+
+ @staticmethod
+ def get_keyboard_input() -> str:
+ keyboard = Keyboard()
+ keyboard.doModal()
+ if keyboard.isConfirmed():
+ return keyboard.getText()
+ return ""
diff --git a/plugin.video.orftvthek/resources/lib/orf_on.py b/plugin.video.orftvthek/resources/lib/orf_on.py
new file mode 100644
index 0000000000..8b51556e94
--- /dev/null
+++ b/plugin.video.orftvthek/resources/lib/orf_on.py
@@ -0,0 +1,687 @@
+import json
+import re
+from datetime import date, datetime, timedelta
+
+from urllib.request import Request as urllib_Request
+from urllib.request import urlopen as urllib_urlopen
+from urllib.error import HTTPError as urllib_HTTPError
+from urllib.error import URLError as urllib_URLError
+from urllib.parse import quote_plus
+
+from directory import Directory
+
+
+class OrfOn:
+ useragent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
+ api_auth = 'Basic b3JmX29uX3Y0MzpqRlJzYk5QRmlQU3h1d25MYllEZkNMVU41WU5aMjhtdA=='
+ api_version = '4.3'
+ api_pager_limit = 50
+ geo_lock_url = 'https://apasfiis.sf.apa.at/admin/proxycheck/'
+ api_base = 'https://api-tvthek.orf.at/api/v%s' % api_version
+
+ api_endpoint_settings = '/settings'
+ api_endpoint_home = '/page/start'
+ api_endpoint_recently_added = '/page/startpage/newest'
+ api_endpoint_schedule = '/schedule/%s'
+ api_endpoint_shows = '/profiles?limit=%d'
+ api_endpoint_shows_letter = '/profiles/lettergroup/%s'
+ api_endpoint_history = '/history'
+ api_endpoint_search = '/search/%s'
+ api_endpoint_search_partial = '/search-partial/%s/%s?limit=%d'
+ api_endpoint_livestreams = '/livestreams'
+ api_endpoint_livestream = '/livestream/%s'
+ api_endpoint_timeshift = '/timeshift/channel/%d/sources'
+ api_endpoint_channels = '/channels?limit=200'
+ api_endpoint_channel_livestream = '/livestreams/channel/%s'
+
+ channel_map = False
+ settings = False
+
+ use_segments = True
+
+ supported_delivery = 'dash'
+ quality_definitions = {
+ 'UHD': {
+ 'name': 'UHD',
+ 'width': 3840,
+ 'height': 2160,
+ },
+ 'QXB': {
+ 'name': 'Adaptive',
+ 'width': 1280,
+ 'height': 720,
+ },
+ 'QXA': {
+ 'name': 'Adaptive',
+ 'width': 1280,
+ 'height': 720,
+ }
+ }
+ drm_widewine_brand = '13f2e056-53fe-4469-ba6d-999970dbe549'
+ drm_widewine_brand_ts = '319f2ca9-0d0c-4e5b-bb70-72efae61dad7'
+
+ def __init__(self, channel_map=None, settings=None, useragent=False, kodi_worker=None):
+ self.kodi_worker = kodi_worker
+ if useragent:
+ self.useragent = useragent
+
+ self.log("Loading ORF On API")
+ if not channel_map:
+ self.channel_map = self.get_channel_map()
+ else:
+ self.channel_map = channel_map
+
+ if not settings:
+ self.settings = self.get_settings()
+ else:
+ self.settings = settings
+
+ self.type_map = {
+ 'highlights': self.translate_string(30115, 'Highlights'),
+ 'genres': self.translate_string(30116, 'Categories'),
+ 'orflive': self.translate_string(30113, 'Livestream')
+ }
+
+ def is_geo_locked(self):
+ headers = self.get_headers()
+ url = self.geo_lock_url
+ try:
+ self.log("Loading %s" % url)
+ request = urllib_urlopen(urllib_Request(url, headers=headers))
+ except urllib_HTTPError as error:
+ self.log('%s (%s)' % (error, url), 'error')
+ return False
+ except urllib_URLError as error:
+ self.log('%s (%s)' % (error, url), 'error')
+ return False
+
+ try:
+ xml_data = request.read()
+ pattern = r'isallowed="(\w+)"'
+ match = re.search(pattern, xml_data.decode('utf-8'))
+ if match:
+ is_allowed = match.group(1)
+ return is_allowed.lower() != 'true'
+ except re.error as error:
+ self.log('%s (%s)' % (error, url), 'error')
+ return False
+
+ def translate_string(self, translation_id, fallback, replace=None):
+ if self.kodi_worker:
+ return self.kodi_worker.get_translation(translation_id, fallback, replace)
+
+ return fallback
+
+ def set_pager_limit(self, limit):
+ self.api_pager_limit = limit
+
+ def set_segments_behaviour(self, use_segments):
+ self.use_segments = use_segments
+
+ def get_auth_headers(self) -> dict:
+ headers = self.get_headers()
+ headers.update({'Authorization': self.api_auth})
+ return headers
+
+ def get_headers(self) -> dict:
+ headers = {}
+ headers.update({'User-Agent': self.useragent})
+ return headers
+
+ def get_widevine_url(self) -> str:
+ return self.settings.get('drm_endpoints').get('widevine')
+
+ def get_widevine_brand(self, timeshift=False) -> str:
+ if timeshift:
+ return self.drm_widewine_brand_ts
+ return self.drm_widewine_brand
+
+ def get_replay_days(self) -> int:
+ return int(self.settings.get('max_viewing_time'))
+
+ def auth_request(self, url):
+ headers = self.get_auth_headers()
+ try:
+ url = self.api_base + url
+ self.log("Loading %s" % url)
+ request = urllib_urlopen(urllib_Request(url, headers=headers))
+ except urllib_HTTPError as error:
+ self.log('%s (%s)' % (error, url), 'error')
+ return {}
+ data = request.read()
+ return json.loads(data)
+
+ def get_main_menu(self) -> list:
+ items = [Directory(self.translate_string(30144, 'Recently added'), '', '/recent', '', 'new', translator=self.kodi_worker),
+ Directory(self.translate_string(30110, 'Frontpage'), '', self.api_endpoint_home, '', 'home', translator=self.kodi_worker),
+ Directory(self.translate_string(30111, 'Schedule'), '', '/schedule', '', 'schedule', translator=self.kodi_worker),
+ Directory(self.translate_string(30112, 'Shows'), '', self.api_endpoint_shows % self.api_pager_limit, '', 'shows', translator=self.kodi_worker),
+ Directory(self.translate_string(30113, 'Livestream'), '', self.api_endpoint_livestreams, '', 'live', translator=self.kodi_worker),
+ Directory(self.translate_string(30114, 'Search'), '', '/search', '', 'search', translator=self.kodi_worker)]
+ items += self.get_frontpage(lanes=False)
+ return items
+
+ def get_sign_language_menu(self):
+ return Directory(self.translate_string(30145, 'Broadcasts using sign language'), '', '/episodes/sign-language', '', 'oegscontent', translator=self.kodi_worker)
+
+ def get_audio_description_menu(self):
+ return Directory(self.translate_string(30146, 'Broadcasts with audio description'), '', '/episodes/visually-impaired', '', 'adcontent', translator=self.kodi_worker)
+
+ def get_subtitles_menu(self):
+ return Directory(self.translate_string(30148, 'Broadcasts with subtitles'), '', '/episodes/subtitles', '', 'adcontent', translator=self.kodi_worker)
+
+ def get_settings(self) -> dict:
+ # Return cached settings
+ if self.settings:
+ self.log("Found cached settings")
+ return self.settings
+
+ self.log("Fetching fresh settings")
+ url = self.api_endpoint_settings
+ data = self.auth_request(url)
+ return data
+
+ def get_channel_map(self) -> dict:
+ # Return cached channel map
+ if self.channel_map:
+ self.log("Found cached channel map")
+ return self.channel_map
+
+ self.log("Fetching new channel map")
+ url = self.api_endpoint_channels
+ data = self.auth_request(url)
+ channel_map = {}
+ for channel in data['_embedded']['items']:
+ name = channel['name']
+ channel_id = channel['id']
+ reel = channel['reel']
+ if 'color_logo' in channel['_links']:
+ media_url = self.clean_url(channel['_links']['color_logo']['href'])
+ logo_data = self.auth_request(media_url)
+ color_logo = logo_data['public_urls']['tiny']['url']
+ else:
+ color_logo = ""
+ if 'black_and_white_logo' in channel['_links']:
+ media_url = self.clean_url(channel['_links']['black_and_white_logo']['href'])
+ logo_data = self.auth_request(media_url)
+ logo = logo_data['public_urls']['tiny']['url']
+ else:
+ logo = ""
+ channel_map[channel_id] = {
+ 'name': name,
+ 'color_logo': color_logo,
+ 'logo': logo,
+ 'reel': reel
+ }
+ return channel_map
+
+ def get_last_uploads(self, last_upload_range=12):
+ current_date = datetime.now()
+ current_delta = current_date - timedelta(hours=last_upload_range)
+
+ today_filter = current_date.strftime("%Y-%m-%d")
+ yesterday_filter = current_delta.strftime("%Y-%m-%d")
+
+ recently_added = []
+ if current_delta.strftime("%d.%m.%Y") != current_date.strftime("%d.%m.%Y"):
+ self.log("Also fetching videos from yesterday")
+ request_url = self.api_endpoint_schedule % yesterday_filter
+ more_uploads = self.get_url(request_url)
+ for item in more_uploads:
+ released = item.date()
+ released_datetime = datetime.fromisoformat(released).replace(tzinfo=None)
+ if released_datetime > current_delta:
+ recently_added.append(item)
+ request_url = self.api_endpoint_schedule % today_filter
+ uploads = self.get_url(request_url)
+ for item in uploads:
+ released = item.date()
+ released_datetime = datetime.fromisoformat(released).replace(tzinfo=None)
+ if released_datetime > current_delta:
+ recently_added.append(item)
+ return reversed(recently_added)
+
+ def get_schedule_dates(self) -> tuple:
+ replay_days = self.get_replay_days()
+ current_date = date.today()
+ day_items = []
+ filter_items = []
+ for day in range(replay_days):
+ days_before = current_date - timedelta(days=day)
+ isodate = days_before.isoformat()
+ prettydate = days_before.strftime("%A, %d.%m.%Y")
+ day_items.append(prettydate)
+ filter_items.append(isodate)
+ return day_items, filter_items
+
+ def get_frontpage(self, lanes=True) -> list:
+ url = self.api_endpoint_home
+ data = self.auth_request(url)
+ items = self.render(data)
+ if not lanes:
+ for item in items[:]:
+ if item.type() == 'lane':
+ items.remove(item)
+ else:
+ for item in items[:]:
+ if item.type() != 'lane':
+ items.remove(item)
+ return items
+
+ def get_live_schedule(self) -> list:
+ url = self.api_endpoint_livestreams
+ data = self.auth_request(url)
+ streams = []
+ for channel in data:
+ channel_streams = data[channel]
+ for stream_item in channel_streams['items']:
+ stream_dir = self.build(stream_item)
+ stream_dir.set_channel(channel)
+ if stream_dir and stream_dir.livestream_active():
+ streams.append(stream_dir)
+ return streams
+
+ def get_pvr(self, channel_reel) -> Directory:
+ channel_infos = self.get_channel_map()
+ for channel_id in channel_infos:
+ if channel_infos[channel_id].get('reel') == channel_reel:
+ request_url = self.api_endpoint_channel_livestream % channel_id
+ data = self.auth_request(request_url)
+ if data and '_embedded' in data and 'items' in data['_embedded']:
+ for livestream_item in data['_embedded']['items']:
+ stream_dir = self.build(livestream_item)
+ stream_dir.set_channel(channel_reel)
+ if stream_dir:
+ stream_detail_url = stream_dir.url()
+ stream_detail_data = self.load_stream_data(stream_detail_url)
+ if stream_detail_data and len(stream_detail_data):
+ pvr_stream = stream_detail_data[0]
+ pvr_stream.set_pvr_mode()
+ return pvr_stream
+
+ def get_search(self, query) -> list:
+ request_url = self.api_endpoint_search % quote_plus(query)
+ data = self.auth_request(request_url)
+ results = []
+ if 'search' in data:
+ if 'episodes' in data['search']:
+ if data['search']['episodes']['total'] > 0:
+ title = ' - ' + self.translate_string(30124, 'All episode results') + ' (%d) -' % data['search']['episodes']['total']
+ desc = ""
+ link = self.api_endpoint_search_partial % ('episodes', query, self.api_pager_limit)
+ results.append(Directory(title, desc, link))
+
+ if 'segments' in data['search']:
+ if data['search']['segments']['total'] > 0:
+ title = ' - ' + self.translate_string(30125, 'All chapter results') + ' (%d) -' % data['search']['segments']['total']
+ desc = ""
+ link = self.api_endpoint_search_partial % ('segments', query, self.api_pager_limit)
+ results.append(Directory(title, desc, link))
+
+ if 'history' in data['search']:
+ if data['search']['history']['total'] > 0:
+ title = ' - ' + self.translate_string(30126, 'All history results') + ' (%d) -' % data['search']['history']['total']
+ desc = ""
+ link = self.api_endpoint_search_partial % ('history', query, self.api_pager_limit)
+ results.append(Directory(title, desc, link))
+
+ if 'suggestions' in data:
+ if 'episodes' in data['suggestions']:
+ for episode in data['suggestions']['episodes']:
+ results.append(self.build(episode))
+ if 'segments' in data['suggestions']:
+ for segment in data['suggestions']['segments']:
+ results.append(self.build(segment))
+ if 'history' in data['suggestions']:
+ for history in data['suggestions']['history']:
+ results.append(self.build(history))
+ return results
+
+ def get_search_partial(self, section, query, args):
+ request_url = self.api_endpoint_search_partial % (section, quote_plus(query), self.api_pager_limit)
+ if args.get('page'):
+ request_url += "&page=%d" % int(args.get('page')[0])
+ data = self.auth_request(request_url)
+ results = []
+ if data and 'items' in data:
+ for item in data['items']:
+ results.append(self.build(item))
+ if 'next' in data and data['next'] and data['next'] != "":
+ next_page_url = self.clean_url(data['next'])
+ results.append(Directory(self.translate_string(30127, 'Next page', '[COLOR blue][B]%s[/B][/COLOR]'), '', next_page_url, '', 'pager'))
+ return results
+
+ def get_url(self, url) -> list:
+ data = self.auth_request(url)
+ return self.render(data)
+
+ def get_listing(self, item) -> list:
+ url = item.url()
+ data = self.auth_request(url)
+ return self.render(data)
+
+ def get_livestream(self, livestream_id) -> Directory:
+ url = self.api_endpoint_livestream % livestream_id
+ data = self.auth_request(url)
+ return self.build(data)
+
+ def get_related(self, episodeid) -> list:
+ episode_details = self.get_url('/episode/%s' % episodeid)
+ for episode_detail in episode_details:
+ episode_source = episode_detail.get_source()
+ if 'profile' in episode_source.get('_links'):
+ profile_url = self.clean_url(episode_source.get('_links').get('profile').get('href'))
+ profile_details = self.get_url(profile_url)
+ return profile_details
+
+ def get_timeshift_stream_url(self, item) -> str:
+ if '_embedded' in item.source and 'channel' in item.source['_embedded']:
+ channel_id = item.source['_embedded']['channel']['id']
+ timeshift_url = self.api_endpoint_timeshift % channel_id
+ timeshift_data = self.auth_request(timeshift_url)
+ if timeshift_data and 'sources' in timeshift_data and self.supported_delivery in timeshift_data['sources']:
+ source = timeshift_data['sources'][self.supported_delivery]
+ source['drm_token'] = timeshift_data['drm_token']
+ return source
+
+ def get_restart_stream_url(self, item) -> str:
+ timeshift_sources = self.get_timeshift_stream_url(item)
+ if item.has_timeshift() and timeshift_sources:
+ start_time = item.get_start_time_iso()
+ return "%s?begin=%s" % (timeshift_sources['src'], start_time)
+
+ def get_restart_stream(self, item) -> Directory:
+ source = self.get_timeshift_stream_url(item)
+ start_time = item.get_start_time_iso()
+ item.set_stream({
+ 'url': "%s&begin=%s" % (source['src'], start_time),
+ 'drm': source['is_drm_protected'],
+ 'drm_token': source['drm_token'],
+ 'drm_widewine_url': self.get_widevine_url(),
+ 'drm_widewine_brand': self.get_widevine_brand(True)
+ })
+ return item
+
+ def get_subtitle_url(self, playitem, subtitle_type='srt'):
+ if '_links' in playitem and 'subtitle' in playitem['_links']:
+ subtitle_url = self.clean_url(playitem['_links']['subtitle']['href'])
+ data = self.auth_request(subtitle_url)
+ if data and '%s_url' % subtitle_type in data:
+ return data['%s_url' % subtitle_type]
+
+ def load_stream_data(self, url) -> list:
+ self.log("Loading Stream Details from %s" % url)
+ data = self.auth_request(url)
+
+ playlist = []
+ if '_embedded' in data and 'items' in data['_embedded']:
+ for playitem in data['_embedded']['items']:
+ source = self.get_preferred_source(playitem)
+ if source:
+ video = self.build_video(playitem, source['src'])
+ video.set_stream({
+ 'url': source['src'],
+ 'drm': source['is_drm_protected'],
+ 'drm_token': playitem['drm_token'],
+ 'drm_widewine_url': self.get_widevine_url(),
+ 'drm_widewine_brand': self.get_widevine_brand(),
+ 'subtitle': self.get_subtitle_url(playitem, 'srt')
+ })
+ playlist.append(video)
+ elif 'segments' in playitem.get('_embedded'):
+ for segment in playitem.get('_embedded').get('segments'):
+ source = self.get_preferred_source(segment)
+ if source:
+ video = self.build_video(segment, source['src'])
+ video.set_stream({
+ 'url': source['src'],
+ 'drm': source['is_drm_protected'],
+ 'drm_token': segment['drm_token'],
+ 'drm_widewine_url': self.get_widevine_url(),
+ 'drm_widewine_brand': self.get_widevine_brand(),
+ 'subtitle': self.get_subtitle_url(segment, 'srt')
+ })
+ playlist.append(video)
+ elif '_embedded' in data and 'item' in data['_embedded']:
+ item = data['_embedded']['item']
+ source = self.get_preferred_source(item)
+ if not source:
+ self.log("No video available yet.")
+ return []
+ video = self.build_video(item, source['src'])
+ video.set_stream({
+ 'url': source['src'],
+ 'drm': source['is_drm_protected'],
+ 'drm_token': item['drm_token'],
+ 'drm_widewine_url': self.get_widevine_url(),
+ 'drm_widewine_brand': self.get_widevine_brand(),
+ 'subtitle': self.get_subtitle_url(item, 'srt')
+ })
+ playlist.append(video)
+ elif 'sources' in data:
+ source = self.get_preferred_source(data)
+ if not source:
+ self.log("No video available yet.")
+ return []
+ video = self.build_video(data, source['src'])
+ if self.kodi_worker.use_timeshift and '_embedded' in data and 'channel' in data['_embedded'] and data['timeshift_available_livestream']:
+ source = self.get_timeshift_stream_url(video)
+ start_time = video.get_start_time_iso()
+ ts_url = "%s&begin=%s" % (source['src'], start_time)
+ video.set_url(ts_url)
+ video.set_stream({
+ 'url': ts_url,
+ 'drm': source['is_drm_protected'],
+ 'drm_token': source['drm_token'],
+ 'drm_widewine_url': self.get_widevine_url(),
+ 'drm_widewine_brand': self.get_widevine_brand(True),
+ 'subtitle': self.get_subtitle_url(data, 'srt')
+ })
+ else:
+ video.set_stream({
+ 'url': source['src'],
+ 'drm': source['is_drm_protected'],
+ 'drm_token': data['drm_token'],
+ 'drm_widewine_url': self.get_widevine_url(),
+ 'drm_widewine_brand': self.get_widevine_brand(),
+ 'subtitle': self.get_subtitle_url(data, 'srt')
+ })
+ playlist.append(video)
+ return playlist
+
+ def get_preferred_source(self, item):
+ if self.supported_delivery in item['sources']:
+ for source in item['sources'][self.supported_delivery]:
+ for (quality, values) in self.quality_definitions.items():
+ if quality in source['quality_key']:
+ self.log("Found Stream %s" % values['name'])
+ return source
+
+ def render(self, data) -> list:
+ content = []
+ if isinstance(data, list):
+ for item in data:
+ result = self.build(item)
+ if result:
+ content.append(result)
+
+ elif 'page' in data and '_items' in data:
+ item = {}
+ for item in data['_items']:
+ result = self.build(item)
+ if result:
+ content.append(result)
+ if 'next' in item['_links']:
+ next_page_url = self.clean_url(item['_links']['next']['href'])
+ content.append(Directory(self.translate_string(30127, 'Next page', '[COLOR blue][B]%s[/B][/COLOR]'), '', next_page_url, '', 'pager'))
+
+ elif 'page' in data and '_embedded' in data and 'items' in data['_embedded']:
+ for item in data['_embedded']['items']:
+ result = self.build(item)
+ if result:
+ content.append(result)
+
+ if 'next' in data['_links']:
+ next_page_url = self.clean_url(data['_links']['next']['href'])
+ content.append(Directory(self.translate_string(30127, 'Next page', '[COLOR blue][B]%s[/B][/COLOR]'), '', next_page_url, '', 'pager'))
+
+ elif 'history_highlights' in data:
+ for item in data['history_highlights']:
+ result = self.build(item)
+ if result:
+ content.append(result)
+ for item in data['history_items']:
+ result = self.build(item)
+ if result:
+ content.append(result)
+
+ elif 'timeShift' in data:
+ for item in data['timeShift']:
+ result = self.build(data['timeShift'][item])
+ if result:
+ content.append(result)
+
+ elif 'children_count' in data:
+ if data['children_count'] > 0:
+ for item in data['children']:
+ result = self.build(item)
+ if result:
+ content.append(result)
+ else:
+ for item in data['video_items']['_items']:
+ result = self.build(item)
+ if result:
+ content.append(result)
+ elif '_links' in data and 'episodes' in data['_links']:
+ episode_url = self.clean_url(data['_links']['episodes']['href'])
+ return self.get_url(episode_url)
+ elif isinstance(data, dict) and 'video_type' in data:
+ result = self.build(data)
+ if result:
+ content.append(result)
+ else:
+ self.log("Unknown Render Type", 'error')
+ self.print_obj(data)
+
+ return content
+
+ def build(self, item) -> Directory:
+ if '_embedded' in item and 'video_item' in item['_embedded']:
+ video_item = item['_embedded']['video_item']['_embedded']['item']
+ link = item['_embedded']['video_item']['_links']['self']['href']
+ return self.build_video(video_item, link)
+ if 'sources' in item and 'segments' in item['_links']:
+ link = item['_links']['segments']['href']
+ return self.build_video(item, link)
+ if 'sources' in item and 'playlist' in item['_links']:
+ link = item['_links']['playlist']['href']
+ return self.build_video(item, link)
+ if 'id' in item and 'type' in item:
+ return self.build_directory(item)
+ if 'id' in item and 'videos' in item:
+ return self.build_directory(item)
+ if 'video_type' in item:
+ video_item = item
+ link = item['_links']['self']['href']
+ return self.build_video(video_item, link)
+
+ self.log("Unknown Type", 'error')
+ self.print_obj(item)
+
+ def build_directory(self, item) -> Directory:
+ self.log("Building Directory %s (%s)" % (item['title'], item['id']))
+ banner, backdrop, poster = self.get_images(item)
+ item['channel_meta'] = self.channel_map
+ item_id = item['id']
+ if 'type' in item:
+ item_type = item['type']
+ else:
+ item_type = 'generic'
+
+ if 'description' in item and item['description'] is not None and item['description'] != "":
+ description = item['description']
+ elif 'share_subject' in item:
+ description = item['share_subject']
+ elif 'episode_title' in item:
+ description = item['episode_title']
+ else:
+ description = ""
+
+ if 'self' in item['_links'] and 'href' in item['_links']['self']:
+ link = self.clean_url(item['_links']['self']['href'])
+ elif '_self' in item['_links'] and isinstance(item['_links']['_self'], str):
+ link = self.clean_url(item['_links']['_self'])
+ else:
+ link = self.clean_url(item['_links']['self'])
+
+ if item_type == 'genre':
+ link = "%s/profiles?limit=%d" % (link, self.api_pager_limit)
+ return Directory(item['title'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker)
+ if item_id == 'lane':
+ return Directory(item['title'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker)
+ if item_id == 'highlights':
+ return Directory(self.type_map['highlights'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker)
+ if item_id == 'genres':
+ return Directory(self.type_map['genres'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker)
+ if item_id == 'orflive':
+ return Directory(self.type_map['orflive'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker)
+ if 'title' in item and item['title'] and 'type' in item:
+ return Directory(item['title'], description, link, item['id'], item['type'], banner, backdrop, poster, item, translator=self.kodi_worker)
+ if 'title' in item and item['title'] and 'children_count' in item:
+ return Directory(item['title'], description, link, item['id'], 'directory', banner, backdrop, poster, item, translator=self.kodi_worker)
+
+ def build_video(self, item, link) -> Directory:
+ self.log("Building Video %s (%s)" % (item['title'], item['id']))
+ title = item['title']
+ link = self.clean_url(link)
+
+ # Try to get the segements if available and activated for the api.
+ if self.use_segments:
+ if 'segments_complete' in item and 'video_type' in item and item['video_type'] == 'episode' and '/segments' not in link and 'episode' in link:
+ self.log("Found video with segments.")
+ link = self.clean_url(link + "/segments")
+ else:
+ if 'episode' in link and link.endswith('/segments'):
+ link = link.replace('/segments', '')
+
+ if 'description' in item and item['description'] is not None and item['description'] != "":
+ description = item['description']
+ elif 'share_subject' in item:
+ description = item['share_subject']
+ elif 'episode_title' in item:
+ description = item['episode_title']
+ else:
+ description = ""
+ video_type = item['video_type']
+ video_id = item['id']
+ banner, backdrop, poster = self.get_images(item)
+ item['channel_meta'] = self.channel_map
+ self.log("Video Link %s" % link)
+ return Directory(title, description, link, video_id, video_type, banner, backdrop, poster, item, translator=self.kodi_worker)
+
+ def clean_url(self, url):
+ return url.replace(self.api_base, "")
+
+ def get_images(self, item) -> tuple:
+ try:
+ if '_embedded' in item:
+ banner = item['_embedded']['image']['public_urls']['highlight_teaser']['url']
+ backdrop = item['_embedded']['image']['public_urls']['reference']['url']
+ if 'image2x3_with_logo' in item['_embedded'] and '_default_' not in item['_embedded']['image2x3_with_logo']['public_urls']['highlight_teaser']['url']:
+ poster = item['_embedded']['image2x3_with_logo']['public_urls']['highlight_teaser']['url']
+ elif '_default_' not in item['_embedded']['image2x3']['public_urls']['highlight_teaser']['url']:
+ poster = item['_embedded']['image2x3']['public_urls']['highlight_teaser']['url']
+ else:
+ poster = banner
+ return banner, backdrop, poster
+ except IndexError:
+ self.log("No images found for %s (%s)" % (item['title'], item['id']), 'warning')
+ except KeyError:
+ self.log("No images found for %s (%s)" % (item['title'], item['id']), 'warning')
+ return "", "", ""
+
+ def log(self, msg, msg_type='info'):
+ self.kodi_worker.log("[%s][ORFON][API] %s" % (msg_type.upper(), msg))
+
+ def print_obj(self, obj):
+ self.log(json.dumps(obj, indent=4))
diff --git a/plugin.video.orftvthek/resources/media/archive_banner_v2.jpg b/plugin.video.orftvthek/resources/media/archive_banner_v2.jpg
deleted file mode 100644
index 4479ffa396..0000000000
Binary files a/plugin.video.orftvthek/resources/media/archive_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/blacklist_banner_v2.jpg b/plugin.video.orftvthek/resources/media/blacklist_banner_v2.jpg
deleted file mode 100644
index 6b55cf85b4..0000000000
Binary files a/plugin.video.orftvthek/resources/media/blacklist_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/default_banner_v2.jpg b/plugin.video.orftvthek/resources/media/default_banner_v2.jpg
deleted file mode 100644
index 78968e4318..0000000000
Binary files a/plugin.video.orftvthek/resources/media/default_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/fanart_v2.jpg b/plugin.video.orftvthek/resources/media/fanart_v2.jpg
deleted file mode 100644
index d0c00d9439..0000000000
Binary files a/plugin.video.orftvthek/resources/media/fanart_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/focus_banner_v2.jpg b/plugin.video.orftvthek/resources/media/focus_banner_v2.jpg
deleted file mode 100644
index 42e2d8340a..0000000000
Binary files a/plugin.video.orftvthek/resources/media/focus_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/live_banner_v2.jpg b/plugin.video.orftvthek/resources/media/live_banner_v2.jpg
deleted file mode 100644
index c5e1ef137c..0000000000
Binary files a/plugin.video.orftvthek/resources/media/live_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/most_popular_banner_v2.jpg b/plugin.video.orftvthek/resources/media/most_popular_banner_v2.jpg
deleted file mode 100644
index 870c382e99..0000000000
Binary files a/plugin.video.orftvthek/resources/media/most_popular_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/news_banner_v2.jpg b/plugin.video.orftvthek/resources/media/news_banner_v2.jpg
deleted file mode 100644
index 2bdd922758..0000000000
Binary files a/plugin.video.orftvthek/resources/media/news_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/recently_added_banner_v2.jpg b/plugin.video.orftvthek/resources/media/recently_added_banner_v2.jpg
deleted file mode 100644
index 6727b7ad61..0000000000
Binary files a/plugin.video.orftvthek/resources/media/recently_added_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/schedule_banner_v2.jpg b/plugin.video.orftvthek/resources/media/schedule_banner_v2.jpg
deleted file mode 100644
index d17f803b81..0000000000
Binary files a/plugin.video.orftvthek/resources/media/schedule_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/search_banner_v2.jpg b/plugin.video.orftvthek/resources/media/search_banner_v2.jpg
deleted file mode 100644
index 7c0194da52..0000000000
Binary files a/plugin.video.orftvthek/resources/media/search_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/shows_banner_v2.jpg b/plugin.video.orftvthek/resources/media/shows_banner_v2.jpg
deleted file mode 100644
index 9d2af3ef4f..0000000000
Binary files a/plugin.video.orftvthek/resources/media/shows_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/tips_banner_v2.jpg b/plugin.video.orftvthek/resources/media/tips_banner_v2.jpg
deleted file mode 100644
index 786c15b5fe..0000000000
Binary files a/plugin.video.orftvthek/resources/media/tips_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/topics_banner_v2.jpg b/plugin.video.orftvthek/resources/media/topics_banner_v2.jpg
deleted file mode 100644
index bd5fbf397c..0000000000
Binary files a/plugin.video.orftvthek/resources/media/topics_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/media/trailer_banner_v2.jpg b/plugin.video.orftvthek/resources/media/trailer_banner_v2.jpg
deleted file mode 100644
index 9a36963356..0000000000
Binary files a/plugin.video.orftvthek/resources/media/trailer_banner_v2.jpg and /dev/null differ
diff --git a/plugin.video.orftvthek/resources/poster.jpg b/plugin.video.orftvthek/resources/poster.jpg
new file mode 100644
index 0000000000..d904e10dd1
Binary files /dev/null and b/plugin.video.orftvthek/resources/poster.jpg differ
diff --git a/plugin.video.orftvthek/resources/settings.xml b/plugin.video.orftvthek/resources/settings.xml
index 97ac09b474..3e7339527e 100644
--- a/plugin.video.orftvthek/resources/settings.xml
+++ b/plugin.video.orftvthek/resources/settings.xml
@@ -1,12 +1,19 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
\ No newline at end of file