diff --git a/addon.xml b/addon.xml index 6eb7a9b81..0644d5679 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/changelog.txt b/changelog.txt index ae9fa26df..4bf07b948 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,18 +1,33 @@ +## v7.0.3+beta.4 +### Fixed +- Fix handling of isPostLiveDrv streams #579 +- Fix infotagger error with Kodi v19 +- Fix errors with creating and deleting temporary folders +- Fix listing more than 50 items in local history list +- Fix context menus in local history and local watch later list +- Fix not using user API key when not logged in #585, #587 +- Fix unofficial version not using correct content type for videos #586, #589 + +### New +- Add setting to disable label details #559 +- Allow searching by Youtube url to access unlisted videos that can't be searched for +- New option to list all subtitles in Kodi subtitle dialog when MPEG-DASH is enabled #121, #201, #247, #305, #489 + ## v7.0.3+beta.3 ### Fixed -Fix invalid error when removing subscriptions #568 -Fix removing item from playlist #570 -Fix related videos and respect pagination limits #572 -Fix not correctly including visitor data in continuation requests -Fix for possible database locks during setup -Fix incorrect timezone details for premiered time #574 +- Fix invalid error when removing subscriptions #568 +- Fix removing item from playlist #570 +- Fix related videos and respect pagination limits #572 +- Fix not correctly including visitor data in continuation requests +- Fix for possible database locks during setup +- Fix incorrect timezone details for premiered time #574 ### Changed -Don't show subscribe context menu item in My Subscriptions #568 +- Don't show subscribe context menu item in My Subscriptions #568 ### New -Add ability to unsubscribe from My Subscriptions #240, #568 -Make MPEG-DASH frame rate details configurable #336 +- Add ability to unsubscribe from My Subscriptions #240, #568 +- Make MPEG-DASH frame rate details configurable #336 ## v7.0.3+beta.2 ### Changed diff --git a/resources/language/resource.language.af_za/strings.po b/resources/language/resource.language.af_za/strings.po index 934f465db..f51531e4e 100644 --- a/resources/language/resource.language.af_za/strings.po +++ b/resources/language/resource.language.af_za/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.am_et/strings.po b/resources/language/resource.language.am_et/strings.po index bed11ebe0..688a3eca6 100644 --- a/resources/language/resource.language.am_et/strings.po +++ b/resources/language/resource.language.am_et/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ar_sa/strings.po b/resources/language/resource.language.ar_sa/strings.po index 9ef08c9e2..57657d8d4 100644 --- a/resources/language/resource.language.ar_sa/strings.po +++ b/resources/language/resource.language.ar_sa/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ast_es/strings.po b/resources/language/resource.language.ast_es/strings.po index 4f19e7508..d7ba529c8 100644 --- a/resources/language/resource.language.ast_es/strings.po +++ b/resources/language/resource.language.ast_es/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.az_az/strings.po b/resources/language/resource.language.az_az/strings.po index 09ea39261..5704eb65f 100644 --- a/resources/language/resource.language.az_az/strings.po +++ b/resources/language/resource.language.az_az/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.be_by/strings.po b/resources/language/resource.language.be_by/strings.po index 98c0859ca..e55048e4a 100644 --- a/resources/language/resource.language.be_by/strings.po +++ b/resources/language/resource.language.be_by/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.bg_bg/strings.po b/resources/language/resource.language.bg_bg/strings.po index 05391323a..865789679 100644 --- a/resources/language/resource.language.bg_bg/strings.po +++ b/resources/language/resource.language.bg_bg/strings.po @@ -1206,7 +1206,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.bs_ba/strings.po b/resources/language/resource.language.bs_ba/strings.po index c4053acaa..5a363af96 100644 --- a/resources/language/resource.language.bs_ba/strings.po +++ b/resources/language/resource.language.bs_ba/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ca_es/strings.po b/resources/language/resource.language.ca_es/strings.po index f5a5af63e..a573176a4 100644 --- a/resources/language/resource.language.ca_es/strings.po +++ b/resources/language/resource.language.ca_es/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.cs_cz/strings.po b/resources/language/resource.language.cs_cz/strings.po index ca819c284..c3d45cba4 100644 --- a/resources/language/resource.language.cs_cz/strings.po +++ b/resources/language/resource.language.cs_cz/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Vyhledávání uzpůsobené pro dálkové ovladače" msgctxt "#30730" diff --git a/resources/language/resource.language.cy_gb/strings.po b/resources/language/resource.language.cy_gb/strings.po index 809c2f92e..dc5bd9dc4 100644 --- a/resources/language/resource.language.cy_gb/strings.po +++ b/resources/language/resource.language.cy_gb/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.da_dk/strings.po b/resources/language/resource.language.da_dk/strings.po index a85040c08..088d679f1 100644 --- a/resources/language/resource.language.da_dk/strings.po +++ b/resources/language/resource.language.da_dk/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Fjernvenlig søgning" msgctxt "#30730" diff --git a/resources/language/resource.language.de_de/strings.po b/resources/language/resource.language.de_de/strings.po index e02b44206..af19bf37a 100644 --- a/resources/language/resource.language.de_de/strings.po +++ b/resources/language/resource.language.de_de/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "VP9-Video aktivieren" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Fernbedienungsfreundliche Suche" msgctxt "#30730" diff --git a/resources/language/resource.language.el_gr/strings.po b/resources/language/resource.language.el_gr/strings.po index 34fdb6c3e..36265216a 100644 --- a/resources/language/resource.language.el_gr/strings.po +++ b/resources/language/resource.language.el_gr/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Αναζήτηση φιλική για χειριστήριο" msgctxt "#30730" diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po index e6835655f..b5f2756eb 100644 --- a/resources/language/resource.language.en_au/strings.po +++ b/resources/language/resource.language.en_au/strings.po @@ -1218,7 +1218,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" @@ -1392,3 +1392,11 @@ msgstr "" msgctxt "#30772" msgid "Disable all framerate hinting" msgstr "" + +msgctxt "#30773" +msgid "Show video details in video lists" +msgstr "" + +msgctxt "#30774" +msgid "All available" +msgstr "" diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 9a3788533..0a5dd59c0 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1218,7 +1218,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" @@ -1392,3 +1392,11 @@ msgstr "" msgctxt "#30772" msgid "Disable all framerate hinting" msgstr "" + +msgctxt "#30773" +msgid "Show video details in video lists" +msgstr "" + +msgctxt "#30774" +msgid "All available" +msgstr "" diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po index 9bbf340c8..a52043a25 100644 --- a/resources/language/resource.language.en_nz/strings.po +++ b/resources/language/resource.language.en_nz/strings.po @@ -1214,7 +1214,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" @@ -1388,3 +1388,11 @@ msgstr "" msgctxt "#30772" msgid "Disable all framerate hinting" msgstr "" + +msgctxt "#30773" +msgid "Show video details in video lists" +msgstr "" + +msgctxt "#30774" +msgid "All available" +msgstr "" diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po index 77d46b799..13a859a6b 100644 --- a/resources/language/resource.language.en_us/strings.po +++ b/resources/language/resource.language.en_us/strings.po @@ -1219,7 +1219,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" @@ -1393,3 +1393,11 @@ msgstr "" msgctxt "#30772" msgid "Disable all framerate hinting" msgstr "" + +msgctxt "#30773" +msgid "Show video details in video lists" +msgstr "" + +msgctxt "#30774" +msgid "All available" +msgstr "" diff --git a/resources/language/resource.language.eo/strings.po b/resources/language/resource.language.eo/strings.po index c31ae9a47..a203fcbe9 100644 --- a/resources/language/resource.language.eo/strings.po +++ b/resources/language/resource.language.eo/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.es_ar/strings.po b/resources/language/resource.language.es_ar/strings.po index 27d3e23ab..fbc852f96 100644 --- a/resources/language/resource.language.es_ar/strings.po +++ b/resources/language/resource.language.es_ar/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.es_es/strings.po b/resources/language/resource.language.es_es/strings.po index 8b829373f..983bac41b 100644 --- a/resources/language/resource.language.es_es/strings.po +++ b/resources/language/resource.language.es_es/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "Habilitar vídeo VP9" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Búsqueda remota" msgctxt "#30730" diff --git a/resources/language/resource.language.es_mx/strings.po b/resources/language/resource.language.es_mx/strings.po index 539c03434..f5a088f50 100644 --- a/resources/language/resource.language.es_mx/strings.po +++ b/resources/language/resource.language.es_mx/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Búsqueda remota amigable" msgctxt "#30730" diff --git a/resources/language/resource.language.et_ee/strings.po b/resources/language/resource.language.et_ee/strings.po index 39a439c6b..b7f62b4b5 100644 --- a/resources/language/resource.language.et_ee/strings.po +++ b/resources/language/resource.language.et_ee/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.eu_es/strings.po b/resources/language/resource.language.eu_es/strings.po index 4a36884f8..fad93a913 100644 --- a/resources/language/resource.language.eu_es/strings.po +++ b/resources/language/resource.language.eu_es/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.fa_af/strings.po b/resources/language/resource.language.fa_af/strings.po index 0f06ef16f..b6b99be8e 100644 --- a/resources/language/resource.language.fa_af/strings.po +++ b/resources/language/resource.language.fa_af/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.fa_ir/strings.po b/resources/language/resource.language.fa_ir/strings.po index 3c2ae297b..4b790aa05 100644 --- a/resources/language/resource.language.fa_ir/strings.po +++ b/resources/language/resource.language.fa_ir/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.fi_fi/strings.po b/resources/language/resource.language.fi_fi/strings.po index e89c1edbc..6d40202eb 100644 --- a/resources/language/resource.language.fi_fi/strings.po +++ b/resources/language/resource.language.fi_fi/strings.po @@ -1204,7 +1204,7 @@ msgid "Enable VP9 video" msgstr "Tue VP9-videota" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Kaukosäätimelle soveltuva haku" msgctxt "#30730" diff --git a/resources/language/resource.language.fo_fo/strings.po b/resources/language/resource.language.fo_fo/strings.po index 33b5a603d..66f2417e8 100644 --- a/resources/language/resource.language.fo_fo/strings.po +++ b/resources/language/resource.language.fo_fo/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.fr_ca/strings.po b/resources/language/resource.language.fr_ca/strings.po index c41cee6fa..292ea83ea 100644 --- a/resources/language/resource.language.fr_ca/strings.po +++ b/resources/language/resource.language.fr_ca/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.fr_fr/strings.po b/resources/language/resource.language.fr_fr/strings.po index 982e0138b..de50d63ff 100644 --- a/resources/language/resource.language.fr_fr/strings.po +++ b/resources/language/resource.language.fr_fr/strings.po @@ -1204,7 +1204,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Recherche conviviale à distance" msgctxt "#30730" diff --git a/resources/language/resource.language.gl_es/strings.po b/resources/language/resource.language.gl_es/strings.po index 37d113aee..ef36c3627 100644 --- a/resources/language/resource.language.gl_es/strings.po +++ b/resources/language/resource.language.gl_es/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.he_il/strings.po b/resources/language/resource.language.he_il/strings.po index cf7fb2d85..b375446d3 100644 --- a/resources/language/resource.language.he_il/strings.po +++ b/resources/language/resource.language.he_il/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.hi_in/strings.po b/resources/language/resource.language.hi_in/strings.po index 5ac65123f..c946e78ff 100644 --- a/resources/language/resource.language.hi_in/strings.po +++ b/resources/language/resource.language.hi_in/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.hr_hr/strings.po b/resources/language/resource.language.hr_hr/strings.po index 7d5d61396..a5fc5a19b 100644 --- a/resources/language/resource.language.hr_hr/strings.po +++ b/resources/language/resource.language.hr_hr/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Prijateljska pretraga na daljinu" msgctxt "#30730" diff --git a/resources/language/resource.language.hu_hu/strings.po b/resources/language/resource.language.hu_hu/strings.po index 3b23d81e3..81480e76f 100644 --- a/resources/language/resource.language.hu_hu/strings.po +++ b/resources/language/resource.language.hu_hu/strings.po @@ -1207,7 +1207,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Külső barátságos keresés" msgctxt "#30730" diff --git a/resources/language/resource.language.hy_am/strings.po b/resources/language/resource.language.hy_am/strings.po index 5ce4452c4..b76fc088f 100644 --- a/resources/language/resource.language.hy_am/strings.po +++ b/resources/language/resource.language.hy_am/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.id_id/strings.po b/resources/language/resource.language.id_id/strings.po index 15b13057d..67813a4dc 100644 --- a/resources/language/resource.language.id_id/strings.po +++ b/resources/language/resource.language.id_id/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Pencarian ramah jarak jauh" msgctxt "#30730" diff --git a/resources/language/resource.language.is_is/strings.po b/resources/language/resource.language.is_is/strings.po index 34f59a53b..d11b03b56 100644 --- a/resources/language/resource.language.is_is/strings.po +++ b/resources/language/resource.language.is_is/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.it_it/strings.po b/resources/language/resource.language.it_it/strings.po index e099dbe1f..dd0d9bb21 100644 --- a/resources/language/resource.language.it_it/strings.po +++ b/resources/language/resource.language.it_it/strings.po @@ -1211,7 +1211,7 @@ msgid "Enable VP9 video" msgstr "Abilita video VP9" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Ricerca amichevole a distanza" msgctxt "#30730" diff --git a/resources/language/resource.language.ja_jp/strings.po b/resources/language/resource.language.ja_jp/strings.po index e7da305f4..90bae3f7d 100644 --- a/resources/language/resource.language.ja_jp/strings.po +++ b/resources/language/resource.language.ja_jp/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.kn_in/strings.po b/resources/language/resource.language.kn_in/strings.po index a2d08d5af..37f7ae743 100644 --- a/resources/language/resource.language.kn_in/strings.po +++ b/resources/language/resource.language.kn_in/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ko_kr/strings.po b/resources/language/resource.language.ko_kr/strings.po index ed2b0e356..ff6383c80 100644 --- a/resources/language/resource.language.ko_kr/strings.po +++ b/resources/language/resource.language.ko_kr/strings.po @@ -1207,7 +1207,7 @@ msgid "Enable VP9 video" msgstr "VP9 비디오 사용" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "원격 친화적 검색" msgctxt "#30730" diff --git a/resources/language/resource.language.lt_lt/strings.po b/resources/language/resource.language.lt_lt/strings.po index 133d8c827..918fdc2ce 100644 --- a/resources/language/resource.language.lt_lt/strings.po +++ b/resources/language/resource.language.lt_lt/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.lv_lv/strings.po b/resources/language/resource.language.lv_lv/strings.po index 2a7a36e99..e316035a6 100644 --- a/resources/language/resource.language.lv_lv/strings.po +++ b/resources/language/resource.language.lv_lv/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.mi/strings.po b/resources/language/resource.language.mi/strings.po index f105c9f3a..71e9961c2 100644 --- a/resources/language/resource.language.mi/strings.po +++ b/resources/language/resource.language.mi/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.mk_mk/strings.po b/resources/language/resource.language.mk_mk/strings.po index 8190a1910..ab716e3f5 100644 --- a/resources/language/resource.language.mk_mk/strings.po +++ b/resources/language/resource.language.mk_mk/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ml_in/strings.po b/resources/language/resource.language.ml_in/strings.po index 7509c941d..f25a0d4e5 100644 --- a/resources/language/resource.language.ml_in/strings.po +++ b/resources/language/resource.language.ml_in/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.mn_mn/strings.po b/resources/language/resource.language.mn_mn/strings.po index 08aeca96b..9716c4f36 100644 --- a/resources/language/resource.language.mn_mn/strings.po +++ b/resources/language/resource.language.mn_mn/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ms_my/strings.po b/resources/language/resource.language.ms_my/strings.po index 2bbd02b47..9646cb08b 100644 --- a/resources/language/resource.language.ms_my/strings.po +++ b/resources/language/resource.language.ms_my/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.mt_mt/strings.po b/resources/language/resource.language.mt_mt/strings.po index 0780ad1a3..fa68b422e 100644 --- a/resources/language/resource.language.mt_mt/strings.po +++ b/resources/language/resource.language.mt_mt/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.my_mm/strings.po b/resources/language/resource.language.my_mm/strings.po index e1465d525..c13177a7b 100644 --- a/resources/language/resource.language.my_mm/strings.po +++ b/resources/language/resource.language.my_mm/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.nb_no/strings.po b/resources/language/resource.language.nb_no/strings.po index c405f1114..e9008e4bb 100644 --- a/resources/language/resource.language.nb_no/strings.po +++ b/resources/language/resource.language.nb_no/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po index 9fdb6ec25..18946e6a2 100644 --- a/resources/language/resource.language.nl_nl/strings.po +++ b/resources/language/resource.language.nl_nl/strings.po @@ -1210,7 +1210,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Afstandvriendelijk zoeken" msgctxt "#30730" diff --git a/resources/language/resource.language.os_os/strings.po b/resources/language/resource.language.os_os/strings.po index dad224691..c14f25f03 100644 --- a/resources/language/resource.language.os_os/strings.po +++ b/resources/language/resource.language.os_os/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.pl_pl/strings.po b/resources/language/resource.language.pl_pl/strings.po index 9889dd2f8..5ac2ce563 100644 --- a/resources/language/resource.language.pl_pl/strings.po +++ b/resources/language/resource.language.pl_pl/strings.po @@ -1207,7 +1207,7 @@ msgid "Enable VP9 video" msgstr "Włącz wideo VP9" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Zdalne wyszukiwanie przyjazne" msgctxt "#30730" diff --git a/resources/language/resource.language.pt_br/strings.po b/resources/language/resource.language.pt_br/strings.po index b110c6927..cb77e4314 100644 --- a/resources/language/resource.language.pt_br/strings.po +++ b/resources/language/resource.language.pt_br/strings.po @@ -1204,7 +1204,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.pt_pt/strings.po b/resources/language/resource.language.pt_pt/strings.po index 081996752..089276b9c 100644 --- a/resources/language/resource.language.pt_pt/strings.po +++ b/resources/language/resource.language.pt_pt/strings.po @@ -1207,7 +1207,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Pesquisa amigável remota" msgctxt "#30730" diff --git a/resources/language/resource.language.ro_ro/strings.po b/resources/language/resource.language.ro_ro/strings.po index 795cc1c22..41b78c0e9 100644 --- a/resources/language/resource.language.ro_ro/strings.po +++ b/resources/language/resource.language.ro_ro/strings.po @@ -1207,7 +1207,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ru_ru/strings.po b/resources/language/resource.language.ru_ru/strings.po index f229feb2a..cbb023606 100644 --- a/resources/language/resource.language.ru_ru/strings.po +++ b/resources/language/resource.language.ru_ru/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Удалённый удобный поиск" msgctxt "#30730" diff --git a/resources/language/resource.language.si_lk/strings.po b/resources/language/resource.language.si_lk/strings.po index 5f998e16d..ada1f7d3b 100644 --- a/resources/language/resource.language.si_lk/strings.po +++ b/resources/language/resource.language.si_lk/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.sk_sk/strings.po b/resources/language/resource.language.sk_sk/strings.po index bb3873fdb..dd29983f5 100644 --- a/resources/language/resource.language.sk_sk/strings.po +++ b/resources/language/resource.language.sk_sk/strings.po @@ -1207,7 +1207,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Vyhľadávanie prispôsobené pre diaľkové ovládače" msgctxt "#30730" diff --git a/resources/language/resource.language.sl_si/strings.po b/resources/language/resource.language.sl_si/strings.po index d304b4d0d..a7616a47b 100644 --- a/resources/language/resource.language.sl_si/strings.po +++ b/resources/language/resource.language.sl_si/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.sq_al/strings.po b/resources/language/resource.language.sq_al/strings.po index d6b56299f..2ad8fb572 100644 --- a/resources/language/resource.language.sq_al/strings.po +++ b/resources/language/resource.language.sq_al/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.sr_rs/strings.po b/resources/language/resource.language.sr_rs/strings.po index cb7d9e823..22e1b1b98 100644 --- a/resources/language/resource.language.sr_rs/strings.po +++ b/resources/language/resource.language.sr_rs/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.sr_rs@latin/strings.po b/resources/language/resource.language.sr_rs@latin/strings.po index ef0343298..b3ee1b68e 100644 --- a/resources/language/resource.language.sr_rs@latin/strings.po +++ b/resources/language/resource.language.sr_rs@latin/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.sv_se/strings.po b/resources/language/resource.language.sv_se/strings.po index fd8ceb49d..c5ed9f94f 100644 --- a/resources/language/resource.language.sv_se/strings.po +++ b/resources/language/resource.language.sv_se/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.szl/strings.po b/resources/language/resource.language.szl/strings.po index 2403e8015..7041369be 100644 --- a/resources/language/resource.language.szl/strings.po +++ b/resources/language/resource.language.szl/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.ta_in/strings.po b/resources/language/resource.language.ta_in/strings.po index ae00238d4..69ea1cbc1 100644 --- a/resources/language/resource.language.ta_in/strings.po +++ b/resources/language/resource.language.ta_in/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.te_in/strings.po b/resources/language/resource.language.te_in/strings.po index ce035052d..e3f83d05d 100644 --- a/resources/language/resource.language.te_in/strings.po +++ b/resources/language/resource.language.te_in/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.tg_tj/strings.po b/resources/language/resource.language.tg_tj/strings.po index 8d2f91639..781213a2f 100644 --- a/resources/language/resource.language.tg_tj/strings.po +++ b/resources/language/resource.language.tg_tj/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.th_th/strings.po b/resources/language/resource.language.th_th/strings.po index 78fb1f9c8..faf0732bf 100644 --- a/resources/language/resource.language.th_th/strings.po +++ b/resources/language/resource.language.th_th/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.tr_tr/strings.po b/resources/language/resource.language.tr_tr/strings.po index b22ed50a5..7f92c7550 100644 --- a/resources/language/resource.language.tr_tr/strings.po +++ b/resources/language/resource.language.tr_tr/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "Uzaktan kumanda uyumlu arama" msgctxt "#30730" diff --git a/resources/language/resource.language.uk_ua/strings.po b/resources/language/resource.language.uk_ua/strings.po index ec62f2d9a..50ce04dab 100644 --- a/resources/language/resource.language.uk_ua/strings.po +++ b/resources/language/resource.language.uk_ua/strings.po @@ -1206,7 +1206,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.uz_uz/strings.po b/resources/language/resource.language.uz_uz/strings.po index a2e59dfd2..0ef6beb4a 100644 --- a/resources/language/resource.language.uz_uz/strings.po +++ b/resources/language/resource.language.uz_uz/strings.po @@ -1208,7 +1208,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.vi_vn/strings.po b/resources/language/resource.language.vi_vn/strings.po index 5ed2b1304..236d94531 100644 --- a/resources/language/resource.language.vi_vn/strings.po +++ b/resources/language/resource.language.vi_vn/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "" msgctxt "#30730" diff --git a/resources/language/resource.language.zh_cn/strings.po b/resources/language/resource.language.zh_cn/strings.po index 6c9f658a5..a0bd170e7 100644 --- a/resources/language/resource.language.zh_cn/strings.po +++ b/resources/language/resource.language.zh_cn/strings.po @@ -1207,7 +1207,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "遥控友好的搜索" msgctxt "#30730" diff --git a/resources/language/resource.language.zh_tw/strings.po b/resources/language/resource.language.zh_tw/strings.po index 3cde9c784..2cc4f9b77 100644 --- a/resources/language/resource.language.zh_tw/strings.po +++ b/resources/language/resource.language.zh_tw/strings.po @@ -1209,7 +1209,7 @@ msgid "Enable VP9 video" msgstr "" msgctxt "#30729" -msgid "Remote friendly search" +msgid "" msgstr "易於用遙控器的搜尋" msgctxt "#30730" diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py index 2c7daf8bc..aba1ec6a3 100644 --- a/resources/lib/__init__.py +++ b/resources/lib/__init__.py @@ -11,4 +11,4 @@ from __future__ import absolute_import, division, unicode_literals -__all__ = ['youtube_plugin'] +__all__ = ('youtube_plugin',) diff --git a/resources/lib/plugin.py b/resources/lib/plugin.py index ea6555469..ae5348210 100644 --- a/resources/lib/plugin.py +++ b/resources/lib/plugin.py @@ -11,7 +11,7 @@ from __future__ import absolute_import, division, unicode_literals from youtube_plugin import youtube -from youtube_plugin.kodion import runner +from youtube_plugin.kodion import plugin_runner -__provider__ = youtube.Provider() -runner.run(__provider__) + +plugin_runner.run(youtube.Provider()) diff --git a/resources/lib/script.py b/resources/lib/script.py index 98b3dd30b..477c5848a 100644 --- a/resources/lib/script.py +++ b/resources/lib/script.py @@ -11,7 +11,7 @@ import sys -from youtube_plugin import script_actions +from youtube_plugin.kodion import script_actions script_actions.run(sys.argv) diff --git a/resources/lib/service.py b/resources/lib/service.py index 31b84ed00..a14157745 100644 --- a/resources/lib/service.py +++ b/resources/lib/service.py @@ -10,6 +10,7 @@ from __future__ import absolute_import, division, unicode_literals -from youtube_plugin.kodion import service +from youtube_plugin.kodion import service_runner -service.run() + +service_runner.run() diff --git a/resources/lib/youtube_authentication.py b/resources/lib/youtube_authentication.py index 531640692..50d73d803 100644 --- a/resources/lib/youtube_authentication.py +++ b/resources/lib/youtube_authentication.py @@ -10,7 +10,7 @@ from __future__ import absolute_import, division, unicode_literals from youtube_plugin.kodion.constants import ADDON_ID -from youtube_plugin.kodion.context import Context +from youtube_plugin.kodion.context import XbmcContext from youtube_plugin.youtube.helper import yt_login from youtube_plugin.youtube.provider import Provider from youtube_plugin.youtube.youtube_exceptions import LoginException @@ -33,7 +33,7 @@ def __add_new_developer(addon_id): :param addon_id: id of the add-on being added :return: """ - context = Context(params={'addon_id': addon_id}) + context = XbmcContext(params={'addon_id': addon_id}) access_manager = context.get_access_manager() developers = access_manager.get_developers() @@ -51,12 +51,12 @@ def __auth(addon_id, mode=_SIGN_IN): :return: addon provider, context and client """ if not addon_id or addon_id == ADDON_ID: - context = Context() + context = XbmcContext() context.log_error('Developer authentication: |%s| Invalid addon_id' % addon_id) return __add_new_developer(addon_id) provider = Provider() - context = Context(params={'addon_id': addon_id}) + context = XbmcContext(params={'addon_id': addon_id}) _ = provider.get_client(context=context) logged_in = provider.is_logged_in() @@ -161,10 +161,10 @@ def reset_access_tokens(addon_id): :return: """ if not addon_id or addon_id == ADDON_ID: - context = Context() + context = XbmcContext() context.log_error('Developer reset access tokens: |%s| Invalid addon_id' % addon_id) return - context = Context(params={'addon_id': addon_id}) + context = XbmcContext(params={'addon_id': addon_id}) access_manager = context.get_access_manager() access_manager.update_dev_access_token(addon_id, access_token='', refresh_token='') diff --git a/resources/lib/youtube_plugin/__init__.py b/resources/lib/youtube_plugin/__init__.py index 502077bea..a7a23e288 100644 --- a/resources/lib/youtube_plugin/__init__.py +++ b/resources/lib/youtube_plugin/__init__.py @@ -26,4 +26,4 @@ } } -__all__ = ['kodion', 'youtube', 'key_sets', 'script_actions'] +__all__ = ('kodion', 'youtube', 'key_sets',) diff --git a/resources/lib/youtube_plugin/kodion/__init__.py b/resources/lib/youtube_plugin/kodion/__init__.py index 9df0afa43..a34689ed1 100644 --- a/resources/lib/youtube_plugin/kodion/__init__.py +++ b/resources/lib/youtube_plugin/kodion/__init__.py @@ -8,29 +8,23 @@ See LICENSES/GPL-2.0-only for more information. """ -# import base exception of kodion directly into the kodion namespace -from .exceptions import KodionException - -# decorator for registering paths for navigating of a provider -from .register_provider_path import RegisterProviderPath - -# Abstract provider for implementation by the user -from .abstract_provider import AbstractProvider - -# import specialized implementation into the kodion namespace -from .context import Context +from __future__ import absolute_import, division, unicode_literals from . import logger +from .abstract_provider import ( + # Abstract provider for implementation by the user + AbstractProvider, + # Decorator for registering paths for navigating of a provider + RegisterProviderPath, +) +# import base exception of kodion directly into the kodion namespace +from .exceptions import KodionException __all__ = ( 'AbstractProvider', - 'Context', 'KodionException', 'RegisterProviderPath', - 'json_store', - 'logger', - 'utils', ) __version__ = '1.5.4' diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py index 1998c20b6..167adebb5 100644 --- a/resources/lib/youtube_plugin/kodion/abstract_provider.py +++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py @@ -12,14 +12,13 @@ import re -from .constants import paths, content -from .compatibility import quote, unquote +from .constants import content, paths from .exceptions import KodionException from .items import ( DirectoryItem, NewSearchItem, SearchHistoryItem, - menu_items + menu_items, ) from .utils import to_unicode @@ -32,8 +31,6 @@ def __init__(self): # map for regular expression (path) to method (names) self._dict_path = {} - self._data_cache = None - # register some default paths self.register_path(r'^/$', '_internal_root') @@ -41,7 +38,7 @@ def __init__(self): '^', paths.WATCH_LATER, '/(?Padd|clear|list|remove)/?$' - )), '_internal_watch_later') + )), 'on_watch_later') self.register_path(r''.join(( '^', @@ -52,7 +49,7 @@ def __init__(self): self.register_path(r''.join(( '^', paths.SEARCH, - '/(?Pinput|query|list|remove|clear|rename)/?$' + '/(?Pinput|query|list|remove|clear|rename)?/?$' )), '_internal_search') self.register_path(r''.join(( @@ -162,8 +159,9 @@ def _internal_favorite(context, re_match): menu_items.favorites_remove( context, item.video_id ), + ('--------', 'noop'), ] - item.set_context_menu(context_menu) + item.add_context_menu(context_menu) return items @@ -184,63 +182,8 @@ def _internal_favorite(context, re_match): return False - @staticmethod - def _internal_watch_later(context, re_match): - params = context.get_params() - command = re_match.group('command') - if not command: - return False - - if command == 'list': - context.set_content(content.VIDEO_CONTENT, sub_type='watch_later') - video_items = context.get_watch_later_list().get_items() - - for video_item in video_items: - context_menu = [ - menu_items.watch_later_local_remove( - context, video_item.video_id - ), - menu_items.watch_later_local_clear( - context - ) - ] - video_item.set_context_menu(context_menu) - - return video_items - - if (command == 'clear' and context.get_ui().on_yes_no_input( - context.get_name(), - context.localize('watch_later.clear.confirm') - )): - context.get_watch_later_list().clear() - context.get_ui().refresh_container() - return True - - video_id = params.get('video_id') - if not video_id: - return False - - if command == 'add': - item = params.get('item') - if item: - context.get_watch_later_list().add(video_id, item) - return True - - if command == 'remove': - context.get_watch_later_list().remove(video_id) - context.get_ui().refresh_container() - return True - - return False - - @property - def data_cache(self): - return self._data_cache - - @data_cache.setter - def data_cache(self, context): - if not self._data_cache: - self._data_cache = context.get_data_cache() + def on_watch_later(self, context, re_match): + raise NotImplementedError() def _internal_search(self, context, re_match): params = context.get_params() @@ -249,14 +192,20 @@ def _internal_search(self, context, re_match): command = re_match.group('command') search_history = context.get_search_history() + if not command or command == 'query': + query = to_unicode(params.get('q', '')) + if not params.get('incognito') and not params.get('channel_id'): + search_history.update(query) + return self.on_search(query, context, re_match) + if command == 'remove': - query = params['q'] + query = params.get('q', '') search_history.remove(query) ui.refresh_container() return True if command == 'rename': - query = params['q'] + query = params.get('q', '') result, new_query = ui.on_keyboard_input( context.localize('search.rename'), query ) @@ -270,23 +219,8 @@ def _internal_search(self, context, re_match): ui.refresh_container() return True - if command == 'query': - incognito = context.get_param('incognito', False) - channel_id = context.get_param('channel_id', '') - query = params['q'] - query = to_unicode(query) - - if not incognito and not channel_id: - try: - search_history.update(query) - except: - pass - if isinstance(query, bytes): - query = query.decode('utf-8') - return self.on_search(query, context, re_match) - if command == 'input': - self.data_cache = context + data_cache = context.get_data_cache() folder_path = context.get_infolabel('Container.FolderPath') query = None @@ -294,11 +228,9 @@ def _internal_search(self, context, re_match): # user doesn't want to input on this path if (folder_path.startswith('plugin://%s' % context.get_id()) and re.match('.+/(?:query|input)/.*', folder_path)): - cached = self.data_cache.get_item('search_query', - self.data_cache.ONE_DAY) - cached = cached and cached.get('query') + cached = data_cache.get_item('search_query', data_cache.ONE_DAY) if cached: - query = unquote(to_unicode(cached)) + query = to_unicode(cached) else: result, input_query = ui.on_keyboard_input( context.localize('search.title') @@ -309,20 +241,11 @@ def _internal_search(self, context, re_match): if not query: return False - incognito = context.get_param('incognito', False) - channel_id = context.get_param('channel_id', '') + data_cache.set_item('search_query', query) - self._data_cache.set_item('search_query', - {'query': quote(query)}) - - if not incognito and not channel_id: - try: - search_history.update(query) - except: - pass + if not params.get('incognito') and not params.get('channel_id'): + search_history.update(query) context.set_path(paths.SEARCH, 'query') - if isinstance(query, bytes): - query = query.decode('utf-8') return self.on_search(query, context, re_match) context.set_content(content.VIDEO_CONTENT) @@ -354,3 +277,16 @@ def handle_exception(self, context, exception_to_handle): def tear_down(self, context): pass + + +class RegisterProviderPath(object): + def __init__(self, re_path): + self._kodion_re_path = re_path + + def __call__(self, func): + def wrapper(*args, **kwargs): + # only use a wrapper if you need extra code to be run here + return func(*args, **kwargs) + + wrapper.kodion_re_path = self._kodion_re_path + return wrapper diff --git a/resources/lib/youtube_plugin/kodion/compatibility/__init__.py b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py index 19cc2ebcd..d93cdf531 100644 --- a/resources/lib/youtube_plugin/kodion/compatibility/__init__.py +++ b/resources/lib/youtube_plugin/kodion/compatibility/__init__.py @@ -7,6 +7,16 @@ See LICENSES/GPL-2.0-only for more information. """ +# Kodi v20+ +try: + from infotagger.listitem import set_info_tag +# Compatibility shims for Kodi v18 and v19 +except ImportError: + def set_info_tag(listitem, infolabels, tag_type, *_args, **_kwargs): + listitem.setInfo(tag_type, infolabels) + return ListItemInfoTag(listitem, tag_type) + +# Kodi v19+ and Python v3.x try: from html import unescape from http import server as BaseHTTPServer @@ -26,13 +36,12 @@ import xbmcplugin import xbmcvfs - from infotagger.listitem import set_info_tag - xbmc.LOGNOTICE = xbmc.LOGINFO xbmc.LOGSEVERE = xbmc.LOGFATAL string_type = str - + byte_string_type = bytes +# Compatibility shims for Kodi v18 and Python v2.7 except ImportError: import BaseHTTPServer from contextlib import contextmanager as _contextmanager @@ -99,11 +108,6 @@ def _file_closer(*args, **kwargs): xbmcvfs.translatePath = xbmc.translatePath - def set_info_tag(listitem, infolabels, tag_type, *_args, **_kwargs): - listitem.setInfo(tag_type, infolabels) - return ListItemInfoTag(listitem, tag_type) - - class ListItemInfoTag(object): __slots__ = ('__li__',) @@ -124,9 +128,11 @@ def set_resume_point(self, string_type = basestring + byte_string_type = (bytes, str) __all__ = ( 'BaseHTTPServer', + 'byte_string_type', 'parse_qs', 'parse_qsl', 'quote', diff --git a/resources/lib/youtube_plugin/kodion/constants/const_settings.py b/resources/lib/youtube_plugin/kodion/constants/const_settings.py index ee74bdb40..d88340cef 100644 --- a/resources/lib/youtube_plugin/kodion/constants/const_settings.py +++ b/resources/lib/youtube_plugin/kodion/constants/const_settings.py @@ -28,9 +28,9 @@ PLAY_COUNT_MIN_PERCENT = 'kodion.play_count.percent' # (int) USE_LOCAL_HISTORY = 'kodion.history.local' # (bool) USE_REMOTE_HISTORY = 'kodion.history.remote' # (bool) -REMOTE_FRIENDLY_SEARCH = 'youtube.search.remote.friendly' # (bool) HIDE_SHORT_VIDEOS = 'youtube.hide_shorts' # (bool) DETAILED_DESCRIPTION = 'youtube.view.description.details' # (bool) +DETAILED_LABELS = 'youtube.view.label.details' # (bool) SUPPORT_ALTERNATIVE_PLAYER = 'kodion.support.alternative_player' # (bool) ALTERNATIVE_PLAYER_WEB_URLS = 'kodion.alternative_player.web.urls' # (bool) diff --git a/resources/lib/youtube_plugin/kodion/context/__init__.py b/resources/lib/youtube_plugin/kodion/context/__init__.py index 1d90e85c3..30a986980 100644 --- a/resources/lib/youtube_plugin/kodion/context/__init__.py +++ b/resources/lib/youtube_plugin/kodion/context/__init__.py @@ -9,7 +9,7 @@ from __future__ import absolute_import, division, unicode_literals -from .xbmc.xbmc_context import XbmcContext as Context +from .xbmc.xbmc_context import XbmcContext -__all__ = ('Context',) +__all__ = ('XbmcContext',) diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index 26f8187a5..5bf52ef9a 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -23,7 +23,7 @@ SearchHistory, WatchLaterList, ) -from ..utils import create_path, create_uri_path, current_system_version +from ..utils import create_path, current_system_version class AbstractContext(object): @@ -218,18 +218,19 @@ def get_audio_player(self): def get_ui(self): raise NotImplementedError() - def get_system_version(self): + @staticmethod + def get_system_version(): return current_system_version - def create_uri(self, path='/', params=None): - if not params: - params = {} - - uri = create_uri_path(path) - if uri: - uri = "%s://%s%s" % ('plugin', str(self._plugin_id), uri) + def create_uri(self, path=None, params=None): + if isinstance(path, (list, tuple)): + uri = create_path(*path, is_uri=True) + elif path: + uri = path else: - uri = "%s://%s/" % ('plugin', str(self._plugin_id)) + uri = '/' + + uri = self._plugin_id.join(('plugin://', uri)) if params: uri = '?'.join((uri, urlencode(params))) diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py index 0e51d6a02..5e5858ee1 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -27,10 +27,9 @@ xbmcvfs, ) from ...constants import ADDON_ID, content, sort -from ...player.xbmc.xbmc_player import XbmcPlayer -from ...player.xbmc.xbmc_playlist import XbmcPlaylist -from ...settings.xbmc.xbmc_plugin_settings import XbmcPluginSettings -from ...ui.xbmc.xbmc_context_ui import XbmcContextUI +from ...player import XbmcPlayer, XbmcPlaylist +from ...settings import XbmcPluginSettings +from ...ui import XbmcContextUI from ...utils import ( current_system_version, loose_version, @@ -192,6 +191,7 @@ class XbmcContext(AbstractContext): 'subscriptions': 30504, 'subtitles.download': 30705, 'subtitles.download.pre': 30706, + 'subtitles.all': 30774, 'subtitles.language': 30560, 'subtitles.no_auto_generated': 30602, 'subtitles.with_fallback': 30601, @@ -417,17 +417,26 @@ def set_content(self, content_type, sub_type=None, category_label=None): category_label = self.get_param('category_label') if category_label: xbmcplugin.setPluginCategory(self._plugin_handle, category_label) + detailed_labels = self.get_settings().show_detailed_labels() if sub_type == 'history': self.add_sort_method( (sort.LASTPLAYED, '%T \u2022 %P', '%D | %J'), (sort.PLAYCOUNT, '%T \u2022 %P', '%D | %J'), (sort.UNSORTED, '%T \u2022 %P', '%D | %J'), (sort.LABEL_IGNORE_THE, '%T \u2022 %P', '%D | %J'), + ) if detailed_labels else self.add_sort_method( + (sort.LASTPLAYED,), + (sort.PLAYCOUNT,), + (sort.UNSORTED,), + (sort.LABEL_IGNORE_THE,), ) else: self.add_sort_method( (sort.UNSORTED, '%T \u2022 %P', '%D | %J'), (sort.LABEL_IGNORE_THE, '%T \u2022 %P', '%D | %J'), + ) if detailed_labels else self.add_sort_method( + (sort.UNSORTED,), + (sort.LABEL_IGNORE_THE,), ) if content_type == content.VIDEO_CONTENT: self.add_sort_method( @@ -437,6 +446,13 @@ def set_content(self, content_type, sub_type=None, category_label=None): (sort.DATEADDED, '%T \u2022 %P | %D', '%a'), (sort.VIDEO_RUNTIME, '%T \u2022 %P | %J', '%D'), (sort.TRACKNUM, '[%N. ]%T \u2022 %P', '%D | %J'), + ) if detailed_labels else self.add_sort_method( + (sort.PROGRAM_COUNT,), + (sort.VIDEO_RATING,), + (sort.DATE,), + (sort.DATEADDED,), + (sort.VIDEO_RUNTIME,), + (sort.TRACKNUM,), ) def add_sort_method(self, *sort_methods): @@ -530,31 +546,32 @@ def use_inputstream_adaptive(self): return success # Values of capability map can be any of the following: - # - required version number, as string for comparison with actual installed - # InputStream.Adaptive version + # - required version number, as string param to loose_version() to compare + # against installed InputStream.Adaptive version # - any Falsy value to exclude capability regardless of version # - True to include capability regardless of version _ISA_CAPABILITIES = { - 'live': '2.0.12', - 'drm': '2.2.12', + 'live': loose_version('2.0.12'), + 'drm': loose_version('2.2.12'), # audio codecs - 'vorbis': '2.3.14', - 'opus': '19.0.0', # unknown when Opus audio support was implemented + 'vorbis': loose_version('2.3.14'), + # unknown when Opus audio support was implemented + 'opus': loose_version('19.0.0'), 'mp4a': True, - 'ac-3': '2.1.15', - 'ec-3': '2.1.15', - 'dts': '2.1.15', + 'ac-3': loose_version('2.1.15'), + 'ec-3': loose_version('2.1.15'), + 'dts': loose_version('2.1.15'), # video codecs 'avc1': True, - 'av01': '20.3.0', + 'av01': loose_version('20.3.0'), 'vp8': False, - 'vp9': '2.3.14', + 'vp9': loose_version('2.3.14'), } def inputstream_adaptive_capabilities(self, capability=None): - # Returns a list of inputstream.adaptive capabilities - # If capability param is provided, returns version of ISA where the - # capability is available + # Returns a frozenset of capabilities supported by installed ISA version + # If capability param is provided, returns True if the installed version + # of ISA supports the nominated capability, False otherwise try: addon = xbmcaddon.Addon('inputstream.adaptive') @@ -566,17 +583,16 @@ def inputstream_adaptive_capabilities(self, capability=None): return frozenset() if capability is None else None isa_loose_version = loose_version(inputstream_version) - if capability is None: - capabilities = frozenset( - capability - for (capability, version) in self._ISA_CAPABILITIES.items() - if version is True - or version and isa_loose_version >= loose_version(version) - ) - return capabilities - version = self._ISA_CAPABILITIES.get(capability) - return (version is True - or version and isa_loose_version >= loose_version(version)) + + if capability: + version = self._ISA_CAPABILITIES.get(capability) + return version is True or version and isa_loose_version >= version + + return frozenset( + capability + for (capability, version) in self._ISA_CAPABILITIES.items() + if version is True or version and isa_loose_version >= version + ) @staticmethod def inputstream_adaptive_auto_stream_selection(): diff --git a/resources/lib/youtube_plugin/kodion/items/__init__.py b/resources/lib/youtube_plugin/kodion/items/__init__.py index 948f32655..1bee2d32e 100644 --- a/resources/lib/youtube_plugin/kodion/items/__init__.py +++ b/resources/lib/youtube_plugin/kodion/items/__init__.py @@ -24,19 +24,37 @@ from .utils import from_json from .video_item import VideoItem from .watch_later_item import WatchLaterItem +from .xbmc.xbmc_items import ( + audio_listitem, + directory_listitem, + image_listitem, + playback_item, + uri_listitem, + video_listitem, + video_playback_item, +) -__all__ = ('AudioItem', - 'BaseItem', - 'DirectoryItem', - 'FavoritesItem', - 'ImageItem', - 'NewSearchItem', - 'NextPageItem', - 'SearchHistoryItem', - 'SearchItem', - 'UriItem', - 'VideoItem', - 'WatchLaterItem', - 'from_json', - 'menu_items',) +__all__ = ( + 'AudioItem', + 'BaseItem', + 'DirectoryItem', + 'FavoritesItem', + 'ImageItem', + 'NewSearchItem', + 'NextPageItem', + 'SearchHistoryItem', + 'SearchItem', + 'UriItem', + 'VideoItem', + 'WatchLaterItem', + 'from_json', + 'menu_items', + 'audio_listitem', + 'directory_listitem', + 'image_listitem', + 'playback_item', + 'uri_listitem', + 'video_listitem', + 'video_playback_item', +) diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py index 3de3291e2..28bb6f29e 100644 --- a/resources/lib/youtube_plugin/kodion/items/base_item.py +++ b/resources/lib/youtube_plugin/kodion/items/base_item.py @@ -150,6 +150,16 @@ def set_context_menu(self, context_menu, replace=False): self._context_menu = context_menu self._replace_context_menu = replace + def add_context_menu(self, context_menu, position=0, replace=None): + if self._context_menu is None: + self._context_menu = context_menu + elif position == 'end': + self._context_menu.extend(context_menu) + else: + self._context_menu[position:position] = context_menu + if replace is not None: + self._replace_context_menu = replace + def get_context_menu(self): return self._context_menu diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/video_item.py index 258b6ab05..9e37e51c8 100644 --- a/resources/lib/youtube_plugin/kodion/items/video_item.py +++ b/resources/lib/youtube_plugin/kodion/items/video_item.py @@ -87,8 +87,7 @@ def set_title(self, title): title = unescape(title) except: pass - self._title = title - self._name = self._title + self._name = self._title = title def get_title(self): return self._title diff --git a/resources/lib/youtube_plugin/kodion/items/xbmc/__init__.py b/resources/lib/youtube_plugin/kodion/items/xbmc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py similarity index 66% rename from resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py rename to resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py index 50de065b0..e9c2a624a 100644 --- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py +++ b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py @@ -10,12 +10,185 @@ from __future__ import absolute_import, division, unicode_literals -from . import info_labels +from .. import AudioItem, DirectoryItem, ImageItem, UriItem, VideoItem from ...compatibility import set_info_tag, xbmcgui -from ...items import AudioItem, UriItem, VideoItem from ...utils import current_system_version, datetime_parser +def _process_date_value(info_labels, name, param): + if param: + info_labels[name] = param.isoformat() + + +def _process_datetime_value(info_labels, name, param): + if not param: + return + info_labels[name] = (param.replace(microsecond=0, tzinfo=None).isoformat() + if current_system_version.compatible(19, 0) else + param.strftime('%d.%m.%Y')) + + +def _process_int_value(info_labels, name, param): + if param is not None: + info_labels[name] = int(param) + + +def _process_string_value(info_labels, name, param): + if param is not None: + info_labels[name] = param + + +def _process_studios(info_labels, name, param): + if param is not None: + info_labels[name] = [param] + + +def _process_audio_rating(info_labels, param): + if param is not None: + rating = int(param) + if rating > 5: + rating = 5 + elif rating < 0: + rating = 0 + info_labels['rating'] = rating + + +def _process_video_duration(info_labels, param): + if param is not None: + info_labels['duration'] = '%d' % param + + +def _process_video_rating(info_labels, param): + if param is not None: + rating = float(param) + if rating > 10.0: + rating = 10.0 + elif rating < 0.0: + rating = 0.0 + info_labels['rating'] = rating + + +def _process_date_string(info_labels, name, param): + if param: + date = datetime_parser.parse(param) + info_labels[name] = date.isoformat() + + +def _process_list_value(info_labels, name, param): + if param is not None and isinstance(param, list): + info_labels[name] = param + + +def _process_mediatype(info_labels, name, param): + info_labels[name] = param + + +def create_info_labels(base_item): + info_labels = {} + + # 'date' = '1982-03-09' (string) + _process_datetime_value(info_labels, 'date', base_item.get_date()) + + # Directory + if isinstance(base_item, DirectoryItem): + _process_string_value(info_labels, 'plot', base_item.get_plot()) + + # Image + elif isinstance(base_item, ImageItem): + # 'title' = 'Blow Your Head Off' (string) + _process_string_value(info_labels, 'title', base_item.get_title()) + + # Audio + elif isinstance(base_item, AudioItem): + # 'duration' = 79 (int) + _process_int_value(info_labels, 'duration', base_item.get_duration()) + + # 'album' = 'Buckle Up' (string) + _process_string_value(info_labels, 'album', base_item.get_album_name()) + + # 'artist' = 'Angerfist' (string) + _process_string_value(info_labels, 'artist', base_item.get_artist_name()) + + # 'rating' = '0' - '5' (string) + _process_audio_rating(info_labels, base_item.get_rating()) + + # Video + elif isinstance(base_item, VideoItem): + # mediatype + _process_mediatype(info_labels, 'mediatype', base_item.get_mediatype()) + + # play count + _process_int_value(info_labels, 'playcount', base_item.get_play_count()) + + # 'count' = 12 (integer) + # Can be used to store an id for later, or for sorting purposes + # Used for Youtube video view count + _process_int_value(info_labels, 'count', base_item.get_count()) + + # studio + _process_studios(info_labels, 'studio', base_item.get_studio()) + + # 'artist' = [] (list) + _process_list_value(info_labels, 'artist', base_item.get_artist()) + + # 'dateadded' = '2014-08-11 13:08:56' (string) will be taken from 'dateadded' + _process_datetime_value(info_labels, 'dateadded', base_item.get_dateadded()) + + # TODO: starting with Helix this could be seconds + # 'duration' = '3:18' (string) + _process_video_duration(info_labels, base_item.get_duration()) + + _process_datetime_value(info_labels, 'lastplayed', base_item.get_last_played()) + + # 'rating' = 4.5 (float) + _process_video_rating(info_labels, base_item.get_rating()) + + # 'aired' = '2013-12-12' (string) + _process_date_value(info_labels, 'aired', base_item.get_aired(as_text=False)) + + # 'director' = 'Steven Spielberg' (string) + _process_string_value(info_labels, 'director', base_item.get_director()) + + # 'premiered' = '2013-12-12' (string) + _process_date_value(info_labels, 'premiered', base_item.get_premiered(as_text=False)) + + # 'episode' = 12 (int) + _process_int_value(info_labels, 'episode', base_item.get_episode()) + + # 'season' = 12 (int) + _process_int_value(info_labels, 'season', base_item.get_season()) + + # 'plot' = '...' (string) + _process_string_value(info_labels, 'plot', base_item.get_plot()) + + # 'imdbnumber' = 'tt3458353' (string) - imdb id + _process_string_value(info_labels, 'imdbnumber', base_item.get_imdb_id()) + + # 'cast' = [] (list) + _process_list_value(info_labels, 'cast', base_item.get_cast()) + + # 'code' = '101' (string) + # Production code, currently used to store misc video data for label + # formatting + _process_string_value(info_labels, 'code', base_item.get_code()) + + # Audio and Video + if isinstance(base_item, (AudioItem, VideoItem)): + # 'title' = 'Blow Your Head Off' (string) + _process_string_value(info_labels, 'title', base_item.get_title()) + + # 'tracknumber' = 12 (int) + _process_int_value(info_labels, 'tracknumber', base_item.get_track_number()) + + # 'year' = 1994 (int) + _process_int_value(info_labels, 'year', base_item.get_year()) + + # 'genre' = 'Hardcore' (string) + _process_string_value(info_labels, 'genre', base_item.get_genre()) + + return info_labels + + def video_playback_item(context, video_item, show_fanart=None): uri = video_item.get_uri() context.log_debug('Converting VideoItem |%s|' % uri) @@ -43,6 +216,7 @@ def video_playback_item(context, video_item, show_fanart=None): 'isPlayable': str(video_item.playable).lower(), } + manifest_type = None if (alternative_player and settings.alternative_player_web_urls() and not license_key): @@ -118,10 +292,10 @@ def video_playback_item(context, video_item, show_fanart=None): 'thumb': image, }) - if video_item.subtitles: + if video_item.subtitles and manifest_type != 'mpd': list_item.setSubtitles(video_item.subtitles) - item_info = info_labels.create_from_item(video_item) + item_info = create_info_labels(video_item) info_tag = set_info_tag(list_item, item_info, 'video') info_tag.set_resume_point(props) @@ -160,7 +334,7 @@ def audio_listitem(context, audio_item, show_fanart=None): 'thumb': image, }) - item_info = info_labels.create_from_item(audio_item) + item_info = create_info_labels(audio_item) set_info_tag(list_item, item_info, 'music') list_item.setProperties(props) @@ -204,7 +378,7 @@ def directory_listitem(context, directory_item, show_fanart=None): 'thumb': image, }) - item_info = info_labels.create_from_item(directory_item) + item_info = create_info_labels(directory_item) set_info_tag(list_item, item_info, 'video') """ @@ -254,7 +428,7 @@ def image_listitem(context, image_item, show_fanart=None): 'thumb': image, }) - item_info = info_labels.create_from_item(image_item) + item_info = create_info_labels(image_item) set_info_tag(list_item, item_info, 'picture') list_item.setProperties(props) @@ -358,7 +532,7 @@ def video_listitem(context, video_item, show_fanart=None): if video_item.subtitles: list_item.setSubtitles(video_item.subtitles) - item_info = info_labels.create_from_item(video_item) + item_info = create_info_labels(video_item) info_tag = set_info_tag(list_item, item_info, 'video') info_tag.set_resume_point(props) diff --git a/resources/lib/youtube_plugin/kodion/network/http_server.py b/resources/lib/youtube_plugin/kodion/network/http_server.py index aa20ca176..5caf7c3e6 100644 --- a/resources/lib/youtube_plugin/kodion/network/http_server.py +++ b/resources/lib/youtube_plugin/kodion/network/http_server.py @@ -28,11 +28,11 @@ ) from ..constants import ADDON_ID, TEMP_PATH, paths from ..logger import log_debug, log_error -from ..settings import Settings +from ..settings import XbmcPluginSettings _addon = xbmcaddon.Addon(ADDON_ID) -_settings = Settings(_addon) +_settings = XbmcPluginSettings(_addon) _i18n = _addon.getLocalizedString _addon_name = _addon.getAddonInfo('name') _addon_icon = _addon.getAddonInfo('icon') diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py index 694a838ae..7b5c14e25 100644 --- a/resources/lib/youtube_plugin/kodion/network/requests.py +++ b/resources/lib/youtube_plugin/kodion/network/requests.py @@ -19,7 +19,7 @@ from ..compatibility import xbmcaddon from ..constants import ADDON_ID from ..logger import log_error -from ..settings import Settings +from ..settings import XbmcPluginSettings __all__ = ( @@ -27,7 +27,7 @@ 'InvalidJSONError' ) -_settings = Settings(xbmcaddon.Addon(id=ADDON_ID)) +_settings = XbmcPluginSettings(xbmcaddon.Addon(id=ADDON_ID)) class BaseRequestsClass(object): diff --git a/resources/lib/youtube_plugin/kodion/player/__init__.py b/resources/lib/youtube_plugin/kodion/player/__init__.py index 4c8c94b61..62e814d12 100644 --- a/resources/lib/youtube_plugin/kodion/player/__init__.py +++ b/resources/lib/youtube_plugin/kodion/player/__init__.py @@ -9,8 +9,8 @@ from __future__ import absolute_import, division, unicode_literals -from .xbmc.xbmc_player import XbmcPlayer as Player -from .xbmc.xbmc_playlist import XbmcPlaylist as Playlist +from .xbmc.xbmc_player import XbmcPlayer +from .xbmc.xbmc_playlist import XbmcPlaylist -__all__ = ('Player', 'Playlist',) +__all__ = ('XbmcPlayer', 'XbmcPlaylist',) diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py index e3b61af48..ea437a972 100644 --- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py +++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py @@ -14,8 +14,7 @@ from ..abstract_playlist import AbstractPlaylist from ...compatibility import xbmc -from ...items import VideoItem -from ...ui.xbmc import xbmc_items +from ...items import VideoItem, video_listitem class XbmcPlaylist(AbstractPlaylist): @@ -33,7 +32,7 @@ def clear(self): self._playlist.clear() def add(self, base_item): - uri, item, _ = xbmc_items.video_listitem(self._context, base_item) + uri, item, _ = video_listitem(self._context, base_item) if item: self._playlist.add(uri, listitem=item) diff --git a/resources/lib/youtube_plugin/kodion/plugin/__init__.py b/resources/lib/youtube_plugin/kodion/plugin/__init__.py index 19fca2ffd..7c0a50d99 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/__init__.py +++ b/resources/lib/youtube_plugin/kodion/plugin/__init__.py @@ -9,7 +9,7 @@ from __future__ import absolute_import, division, unicode_literals -from .xbmc.xbmc_runner import XbmcRunner as Runner +from .xbmc.xbmc_plugin import XbmcPlugin -__all__ = ('Runner',) +__all__ = ('XbmcPlugin',) diff --git a/resources/lib/youtube_plugin/kodion/plugin/abstract_provider_runner.py b/resources/lib/youtube_plugin/kodion/plugin/abstract_plugin.py similarity index 78% rename from resources/lib/youtube_plugin/kodion/plugin/abstract_provider_runner.py rename to resources/lib/youtube_plugin/kodion/plugin/abstract_plugin.py index d1aed3959..5be271e46 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/abstract_provider_runner.py +++ b/resources/lib/youtube_plugin/kodion/plugin/abstract_plugin.py @@ -8,8 +8,10 @@ See LICENSES/GPL-2.0-only for more information. """ +from __future__ import absolute_import, division, unicode_literals -class AbstractProviderRunner(object): + +class AbstractPlugin(object): def __init__(self): pass diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py similarity index 91% rename from resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py rename to resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py index c06ae42d4..76da42618 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py +++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py @@ -12,23 +12,27 @@ from traceback import format_stack -from ..abstract_provider_runner import AbstractProviderRunner +from ..abstract_plugin import AbstractPlugin from ...compatibility import xbmcgui, xbmcplugin from ...exceptions import KodionException -from ...items import AudioItem, DirectoryItem, ImageItem, UriItem, VideoItem -from ...player import Playlist -from ...ui.xbmc.xbmc_items import ( +from ...items import ( + AudioItem, + DirectoryItem, + ImageItem, + UriItem, + VideoItem, audio_listitem, directory_listitem, image_listitem, playback_item, - video_listitem + video_listitem, ) +from ...player import XbmcPlaylist -class XbmcRunner(AbstractProviderRunner): +class XbmcPlugin(AbstractPlugin): def __init__(self): - super(XbmcRunner, self).__init__() + super(XbmcPlugin, self).__init__() self.handle = None def run(self, provider, context): @@ -39,7 +43,7 @@ def run(self, provider, context): if ui.get_property('busy').lower() == 'true': ui.clear_property('busy') if ui.busy_dialog_active(): - playlist = Playlist('video', context) + playlist = XbmcPlaylist('video', context) playlist.clear() xbmcplugin.endOfDirectory(self.handle, succeeded=False) @@ -115,7 +119,7 @@ def _set_resolved_url(self, context, base_item, show_fanart): ui = context.get_ui() if not context.is_plugin_path(uri) and ui.busy_dialog_active(): ui.set_property('busy', 'true') - playlist = Playlist('video', context) + playlist = XbmcPlaylist('video', context) ui.set_property('playlist', playlist.get_items(dumps=True)) item = playback_item(context, base_item, show_fanart) diff --git a/resources/lib/youtube_plugin/kodion/runner.py b/resources/lib/youtube_plugin/kodion/plugin_runner.py similarity index 91% rename from resources/lib/youtube_plugin/kodion/runner.py rename to resources/lib/youtube_plugin/kodion/plugin_runner.py index 9f62065e2..00180f4d4 100644 --- a/resources/lib/youtube_plugin/kodion/runner.py +++ b/resources/lib/youtube_plugin/kodion/plugin_runner.py @@ -15,23 +15,23 @@ import timeit from . import debug -from .context import Context -from .plugin import Runner +from .context import XbmcContext +from .plugin import XbmcPlugin -__all__ = ['run'] +__all__ = ('run',) __DEBUG_RUNTIME = False __DEBUG_RUNTIME_SINGLE_FILE = False -__RUNNER__ = Runner() +__PLUGIN__ = XbmcPlugin() def run(provider, context=None): start_time = timeit.default_timer() if not context: - context = Context() + context = XbmcContext() context.log_debug('Starting Kodion framework by bromix...') @@ -57,7 +57,7 @@ def run(provider, context=None): path=context.get_path(), params=params)) - __RUNNER__.run(provider, context) + __PLUGIN__.run(provider, context) provider.tear_down(context) elapsed = timeit.default_timer() - start_time diff --git a/resources/lib/youtube_plugin/kodion/register_provider_path.py b/resources/lib/youtube_plugin/kodion/register_provider_path.py deleted file mode 100644 index aa2e43346..000000000 --- a/resources/lib/youtube_plugin/kodion/register_provider_path.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -""" - - Copyright (C) 2014-2016 bromix (plugin.video.youtube) - Copyright (C) 2016-2018 plugin.video.youtube - - SPDX-License-Identifier: GPL-2.0-only - See LICENSES/GPL-2.0-only for more information. -""" - - -class RegisterProviderPath(object): - def __init__(self, re_path): - self._kodion_re_path = re_path - - def __call__(self, func): - def wrapper(*args, **kwargs): - # only use a wrapper if you need extra code to be run here - return func(*args, **kwargs) - - wrapper.kodion_re_path = self._kodion_re_path - return wrapper diff --git a/resources/lib/youtube_plugin/script_actions.py b/resources/lib/youtube_plugin/kodion/script_actions.py similarity index 91% rename from resources/lib/youtube_plugin/script_actions.py rename to resources/lib/youtube_plugin/kodion/script_actions.py index 8e0401a33..3a48c09a9 100644 --- a/resources/lib/youtube_plugin/script_actions.py +++ b/resources/lib/youtube_plugin/kodion/script_actions.py @@ -12,15 +12,15 @@ import os import socket -from .kodion.compatibility import parse_qsl, xbmc, xbmcaddon, xbmcvfs -from .kodion.constants import DATA_PATH, TEMP_PATH -from .kodion.context import Context -from .kodion.network import get_client_ip_address, is_httpd_live -from .kodion.utils import rm_dir +from .compatibility import parse_qsl, xbmc, xbmcaddon, xbmcvfs +from .constants import DATA_PATH, TEMP_PATH +from .context import XbmcContext +from .network import get_client_ip_address, is_httpd_live +from .utils import rm_dir def _config_actions(action, *_args): - context = Context() + context = XbmcContext() localize = context.localize settings = context.get_settings() ui = context.get_ui() @@ -54,22 +54,33 @@ def _config_actions(action, *_args): (language, 'en') )), language, - '%s (%s)' % (language, localize('subtitles.no_auto_generated')) + '%s (%s)' % (language, localize('subtitles.no_auto_generated')), ] + + if settings.use_mpd_videos(): + sub_opts.append(localize('subtitles.all')) + elif sub_setting == 5: + sub_setting = 0 + settings.set_subtitle_languages(sub_setting) + sub_opts[sub_setting] = ui.bold(sub_opts[sub_setting]) result = ui.on_select(localize('subtitles.language'), sub_opts, preselect=sub_setting) if result > -1: - settings.set_subtitle_languages(result) + sub_setting = result + settings.set_subtitle_languages(sub_setting) - result = ui.on_yes_no_input( - localize('subtitles.download'), - localize('subtitles.download.pre') - ) - if result > -1: - settings.set_subtitle_download(result == 1) + if not sub_setting or sub_setting == 5: + settings.set_subtitle_download(False) + else: + result = ui.on_yes_no_input( + localize('subtitles.download'), + localize('subtitles.download.pre') + ) + if result > -1: + settings.set_subtitle_download(result == 1) elif action == 'listen_ip': local_ranges = ('10.', '172.16.', '192.168.') @@ -96,7 +107,7 @@ def _config_actions(action, *_args): def _maintenance_actions(action, target): - context = Context() + context = XbmcContext() ui = context.get_ui() localize = context.localize @@ -164,7 +175,7 @@ def _maintenance_actions(action, target): def _user_actions(action, params): - context = Context() + context = XbmcContext() if params: context.parse_params(dict(parse_qsl(params))) localize = context.localize diff --git a/resources/lib/youtube_plugin/kodion/service.py b/resources/lib/youtube_plugin/kodion/service_runner.py similarity index 72% rename from resources/lib/youtube_plugin/kodion/service.py rename to resources/lib/youtube_plugin/kodion/service_runner.py index 4761b4ea2..c3961b016 100644 --- a/resources/lib/youtube_plugin/kodion/service.py +++ b/resources/lib/youtube_plugin/kodion/service_runner.py @@ -10,38 +10,40 @@ from __future__ import absolute_import, division, unicode_literals -from datetime import datetime - -from .context import Context from .constants import TEMP_PATH +from .context import XbmcContext from .utils import PlayerMonitor, ServiceMonitor, rm_dir from ..youtube.provider import Provider +__all__ = ('run',) + + def run(): - context = Context() + context = XbmcContext() context.log_debug('YouTube service initialization...') context.get_ui().clear_property('abort_requested') monitor = ServiceMonitor() - player = PlayerMonitor(provider=Provider(), context=context) + player = PlayerMonitor(provider=Provider(), + context=context, + monitor=monitor) # wipe add-on temp folder on updates/restarts (subtitles, and mpd files) rm_dir(TEMP_PATH) - sleep_time = 10 - ping_delay = 60 - ping_time = None + wait_interval = 10 + ping_period = waited = 60 while not monitor.abortRequested(): - now = datetime.now() - if not ping_time or (ping_time - now).total_seconds() >= ping_delay: - ping_time = now + if waited >= ping_period: + waited = 0 if monitor.httpd and not monitor.ping_httpd(): monitor.restart_httpd() - if monitor.waitForAbort(sleep_time): + if monitor.waitForAbort(wait_interval): break + waited += wait_interval context.get_ui().set_property('abort_requested', 'true') diff --git a/resources/lib/youtube_plugin/kodion/settings/__init__.py b/resources/lib/youtube_plugin/kodion/settings/__init__.py index d5432f4b3..ed3f6d9a8 100644 --- a/resources/lib/youtube_plugin/kodion/settings/__init__.py +++ b/resources/lib/youtube_plugin/kodion/settings/__init__.py @@ -9,7 +9,7 @@ from __future__ import absolute_import, division, unicode_literals -from .xbmc.xbmc_plugin_settings import XbmcPluginSettings as Settings +from .xbmc.xbmc_plugin_settings import XbmcPluginSettings -__all__ = ('Settings',) +__all__ = ('XbmcPluginSettings',) diff --git a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py index 25082c10a..9ff3e5b6d 100644 --- a/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py +++ b/resources/lib/youtube_plugin/kodion/settings/abstract_settings.py @@ -304,9 +304,6 @@ def stream_select(self): select_type = self.get_int(settings.MPD_STREAM_SELECT, 1) return self._STREAM_SELECT.get(select_type) or self._STREAM_SELECT[1] - def remote_friendly_search(self): - return self.get_bool(settings.REMOTE_FRIENDLY_SEARCH, False) - def hide_short_videos(self): return self.get_bool(settings.HIDE_SHORT_VIDEOS, False) @@ -316,6 +313,9 @@ def client_selection(self): def show_detailed_description(self): return self.get_bool(settings.DETAILED_DESCRIPTION, True) + def show_detailed_labels(self): + return self.get_bool(settings.DETAILED_LABELS, True) + def get_language(self): return self.get_string(settings.LANGUAGE, 'en_US').replace('_', '-') diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py index b98ebdafe..1efa3b935 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py @@ -24,7 +24,7 @@ def __init__(self, filepath): super(WatchLaterList, self).__init__(filepath) def get_items(self): - result = self._get_by_ids(process=from_json, values_only=True) + result = self._get_by_ids(process=from_json, as_dict=True) return result def add(self, video_id, item): diff --git a/resources/lib/youtube_plugin/kodion/ui/__init__.py b/resources/lib/youtube_plugin/kodion/ui/__init__.py index 8f09da745..003c27de8 100644 --- a/resources/lib/youtube_plugin/kodion/ui/__init__.py +++ b/resources/lib/youtube_plugin/kodion/ui/__init__.py @@ -9,7 +9,7 @@ from __future__ import absolute_import, division, unicode_literals -from .xbmc.xbmc_context_ui import XbmcContextUI as ContextUI +from .xbmc.xbmc_context_ui import XbmcContextUI -__all__ = ('ContextUI',) +__all__ = ('XbmcContextUI',) diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py deleted file mode 100644 index 191a4379f..000000000 --- a/resources/lib/youtube_plugin/kodion/ui/xbmc/info_labels.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- -""" - - Copyright (C) 2014-2016 bromix (plugin.video.youtube) - Copyright (C) 2016-2018 plugin.video.youtube - - SPDX-License-Identifier: GPL-2.0-only - See LICENSES/GPL-2.0-only for more information. -""" - -from __future__ import absolute_import, division, unicode_literals - -from ...items import AudioItem, DirectoryItem, ImageItem, VideoItem -from ...utils import current_system_version, datetime_parser - - -def _process_date_value(info_labels, name, param): - if param: - info_labels[name] = param.isoformat() - - -def _process_datetime_value(info_labels, name, param): - if not param: - return - info_labels[name] = (param.replace(microsecond=0, tzinfo=None).isoformat() - if current_system_version.compatible(19, 0) else - param.strftime('%d.%m.%Y')) - - -def _process_int_value(info_labels, name, param): - if param is not None: - info_labels[name] = int(param) - - -def _process_string_value(info_labels, name, param): - if param is not None: - info_labels[name] = param - - -def _process_studios(info_labels, name, param): - if param is not None: - info_labels[name] = [param] - - -def _process_audio_rating(info_labels, param): - if param is not None: - rating = int(param) - if rating > 5: - rating = 5 - elif rating < 0: - rating = 0 - info_labels['rating'] = rating - - -def _process_video_duration(info_labels, param): - if param is not None: - info_labels['duration'] = '%d' % param - - -def _process_video_rating(info_labels, param): - if param is not None: - rating = float(param) - if rating > 10.0: - rating = 10.0 - elif rating < 0.0: - rating = 0.0 - info_labels['rating'] = rating - - -def _process_date_string(info_labels, name, param): - if param: - date = datetime_parser.parse(param) - info_labels[name] = date.isoformat() - - -def _process_list_value(info_labels, name, param): - if param is not None and isinstance(param, list): - info_labels[name] = param - - -def _process_mediatype(info_labels, name, param): - info_labels[name] = param - - -def create_from_item(base_item): - info_labels = {} - - # 'date' = '1982-03-09' (string) - _process_datetime_value(info_labels, 'date', base_item.get_date()) - - # Directory - if isinstance(base_item, DirectoryItem): - _process_string_value(info_labels, 'plot', base_item.get_plot()) - - # Image - elif isinstance(base_item, ImageItem): - # 'title' = 'Blow Your Head Off' (string) - _process_string_value(info_labels, 'title', base_item.get_title()) - - # Audio - elif isinstance(base_item, AudioItem): - # 'duration' = 79 (int) - _process_int_value(info_labels, 'duration', base_item.get_duration()) - - # 'album' = 'Buckle Up' (string) - _process_string_value(info_labels, 'album', base_item.get_album_name()) - - # 'artist' = 'Angerfist' (string) - _process_string_value(info_labels, 'artist', base_item.get_artist_name()) - - # 'rating' = '0' - '5' (string) - _process_audio_rating(info_labels, base_item.get_rating()) - - # Video - elif isinstance(base_item, VideoItem): - # mediatype - _process_mediatype(info_labels, 'mediatype', base_item.get_mediatype()) - - # play count - _process_int_value(info_labels, 'playcount', base_item.get_play_count()) - - # 'count' = 12 (integer) - # Can be used to store an id for later, or for sorting purposes - # Used for Youtube video view count - _process_int_value(info_labels, 'count', base_item.get_count()) - - # studio - _process_studios(info_labels, 'studio', base_item.get_studio()) - - # 'artist' = [] (list) - _process_list_value(info_labels, 'artist', base_item.get_artist()) - - # 'dateadded' = '2014-08-11 13:08:56' (string) will be taken from 'dateadded' - _process_datetime_value(info_labels, 'dateadded', base_item.get_dateadded()) - - # TODO: starting with Helix this could be seconds - # 'duration' = '3:18' (string) - _process_video_duration(info_labels, base_item.get_duration()) - - _process_datetime_value(info_labels, 'lastplayed', base_item.get_last_played()) - - # 'rating' = 4.5 (float) - _process_video_rating(info_labels, base_item.get_rating()) - - # 'aired' = '2013-12-12' (string) - _process_date_value(info_labels, 'aired', base_item.get_aired(as_text=False)) - - # 'director' = 'Steven Spielberg' (string) - _process_string_value(info_labels, 'director', base_item.get_director()) - - # 'premiered' = '2013-12-12' (string) - _process_date_value(info_labels, 'premiered', base_item.get_premiered(as_text=False)) - - # 'episode' = 12 (int) - _process_int_value(info_labels, 'episode', base_item.get_episode()) - - # 'season' = 12 (int) - _process_int_value(info_labels, 'season', base_item.get_season()) - - # 'plot' = '...' (string) - _process_string_value(info_labels, 'plot', base_item.get_plot()) - - # 'imdbnumber' = 'tt3458353' (string) - imdb id - _process_string_value(info_labels, 'imdbnumber', base_item.get_imdb_id()) - - # 'cast' = [] (list) - _process_list_value(info_labels, 'cast', base_item.get_cast()) - - # 'code' = '101' (string) - # Production code, currently used to store misc video data for label - # formatting - _process_string_value(info_labels, 'code', base_item.get_code()) - - # Audio and Video - if isinstance(base_item, (AudioItem, VideoItem)): - # 'title' = 'Blow Your Head Off' (string) - _process_string_value(info_labels, 'title', base_item.get_title()) - - # 'tracknumber' = 12 (int) - _process_int_value(info_labels, 'tracknumber', base_item.get_track_number()) - - # 'year' = 1994 (int) - _process_int_value(info_labels, 'year', base_item.get_year()) - - # 'genre' = 'Hardcore' (string) - _process_string_value(info_labels, 'genre', base_item.get_genre()) - - return info_labels diff --git a/resources/lib/youtube_plugin/kodion/utils/__init__.py b/resources/lib/youtube_plugin/kodion/utils/__init__.py index 7861ebf0a..874302b44 100644 --- a/resources/lib/youtube_plugin/kodion/utils/__init__.py +++ b/resources/lib/youtube_plugin/kodion/utils/__init__.py @@ -13,7 +13,6 @@ from . import datetime_parser from .methods import ( create_path, - create_uri_path, duration_to_seconds, find_best_fit, find_video_id, @@ -26,7 +25,6 @@ seconds_to_duration, select_stream, strip_html_from_text, - to_str, to_unicode, ) from .player_monitor import PlayerMonitor @@ -38,7 +36,6 @@ 'PlayerMonitor', 'ServiceMonitor', 'create_path', - 'create_uri_path', 'current_system_version', 'datetime_parser', 'duration_to_seconds', @@ -53,6 +50,5 @@ 'seconds_to_duration', 'select_stream', 'strip_html_from_text', - 'to_str', 'to_unicode', ) diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py index a96d827b7..2466cdf6f 100644 --- a/resources/lib/youtube_plugin/kodion/utils/methods.py +++ b/resources/lib/youtube_plugin/kodion/utils/methods.py @@ -18,13 +18,12 @@ from datetime import timedelta from math import floor, log -from ..compatibility import quote, string_type, xbmc, xbmcvfs +from ..compatibility import byte_string_type, quote, string_type, xbmc, xbmcvfs from ..logger import log_error __all__ = ( 'create_path', - 'create_uri_path', 'duration_to_seconds', 'find_best_fit', 'find_video_id', @@ -38,33 +37,21 @@ 'seconds_to_duration', 'select_stream', 'strip_html_from_text', - 'to_str', 'to_unicode', ) def loose_version(v): - filled = [] - for point in v.split("."): - filled.append(point.zfill(8)) - return tuple(filled) - - -def to_str(text): - if isinstance(text, bytes): - return text.decode('utf-8', 'ignore') - return text + return [point.zfill(8) for point in v.split('.')] def to_unicode(text): - result = text - if isinstance(text, (bytes, str)): + if isinstance(text, byte_string_type): try: - result = text.decode('utf-8', 'ignore') - except (AttributeError, UnicodeError): + return text.decode('utf-8', 'ignore') + except UnicodeError: pass - - return result + return text def find_best_fit(data, compare_method=None): @@ -178,34 +165,22 @@ def _find_best_fit_video(_stream_data): return selected_stream_data -def create_path(*args): - comps = [] - for arg in args: - if isinstance(arg, (list, tuple)): - return create_path(*arg) - - comps.append(str(arg).strip('/').replace('\\', '/').replace('//', '/')) - - uri_path = '/'.join(comps) - if uri_path: - return '/%s/' % uri_path - - return '/' - - -def create_uri_path(*args): - comps = [] - for arg in args: - if isinstance(arg, (list, tuple)): - return create_uri_path(*arg) - - comps.append(str(arg).strip('/').replace('\\', '/').replace('//', '/')) - - uri_path = '/'.join(comps) - if uri_path: - return quote('/%s/' % uri_path) +def create_path(*args, is_uri=False): + path = '/'.join([ + part + for part in [ + str(arg).strip('/').replace('\\', '/').replace('//', '/') + for arg in args + ] if part + ]) + if path: + path = path.join(('/', '/')) + else: + return '/' - return '/' + if is_uri: + return quote(path) + return path def strip_html_from_text(text): @@ -233,17 +208,17 @@ def print_items(items): def make_dirs(path): if not path.endswith('/'): path = ''.join((path, '/')) + path = xbmcvfs.translatePath(path) succeeded = xbmcvfs.exists(path) or xbmcvfs.mkdirs(path) if succeeded: - return xbmcvfs.translatePath(path) + return path - path = xbmcvfs.translatePath(path) try: os.makedirs(path) succeeded = True except OSError: - pass + succeeded = xbmcvfs.exists(path) if succeeded: return path @@ -252,21 +227,25 @@ def make_dirs(path): def rm_dir(path): + if not path.endswith('/'): + path = ''.join((path, '/')) + path = xbmcvfs.translatePath(path) + succeeded = (not xbmcvfs.exists(path) or xbmcvfs.rmdir(path, force=True)) if not succeeded: - path = xbmcvfs.translatePath(path) try: shutil.rmtree(path) - succeeded = not xbmcvfs.exists(path) except OSError: pass + succeeded = not xbmcvfs.exists(path) if succeeded: return True log_error('Failed to remove directory: {0}'.format(path)) return False + def find_video_id(plugin_path): match = re.search(r'.*video_id=(?P[a-zA-Z0-9_\-]{11}).*', plugin_path) if match: @@ -329,6 +308,7 @@ def merge_dicts(item1, item2, templates=None, _=Ellipsis): new[key] = value return new or _ + def get_kodi_setting(setting): json_query = xbmc.executeJSONRPC(json.dumps({ 'jsonrpc': '2.0', diff --git a/resources/lib/youtube_plugin/kodion/utils/player_monitor.py b/resources/lib/youtube_plugin/kodion/utils/player_monitor.py index d2e5fafa8..a9afb3650 100644 --- a/resources/lib/youtube_plugin/kodion/utils/player_monitor.py +++ b/resources/lib/youtube_plugin/kodion/utils/player_monitor.py @@ -17,17 +17,16 @@ class PlayerMonitorThread(threading.Thread): - def __init__(self, player, provider, context, playback_json): + def __init__(self, player, provider, context, monitor, playback_json): super(PlayerMonitorThread, self).__init__() self._stopped = threading.Event() self._ended = threading.Event() + self._player = player + self._provider = provider self._context = context - self.provider = provider - self.ui = self._context.get_ui() - - self.player = player + self._monitor = monitor self.playback_json = playback_json self.video_id = self.playback_json.get('video_id') @@ -37,23 +36,13 @@ def __init__(self, player, provider, context, playback_json): self.total_time = 0.0 self.current_time = 0.0 self.segment_start = 0.0 - self.percent_complete = 0 + self.progress = 0 self.daemon = True self.start() - def update_times(self, - total_time, - current_time, - segment_start, - percent_complete): - self.total_time = total_time - self.current_time = current_time - self.segment_start = segment_start - self.percent_complete = percent_complete - def abort_now(self): - return (not self.player.isPlaying() + return (not self._player.isPlaying() or self._context.abort_requested() or self.stopped()) @@ -62,27 +51,28 @@ def run(self): play_count = self.playback_json.get('play_count', 0) use_remote_history = self.playback_json.get('use_remote_history', False) use_local_history = self.playback_json.get('use_local_history', False) - playback_stats = self.playback_json.get('playback_stats') + playback_stats = self.playback_json.get('playback_stats', {}) refresh_only = self.playback_json.get('refresh_only', False) clip = self.playback_json.get('clip', False) self._context.log_debug('PlayerMonitorThread[{0}]: Starting' .format(self.video_id)) - player = self.player + player = self._player - wait_time = 0.2 - waited = 0.0 + timeout_period = 5 + waited = 0 + wait_interval = 0.2 while not player.isPlaying(): if self._context.abort_requested(): break - if waited >= 5: + if waited >= timeout_period: self.end() return self._context.log_debug('Waiting for playback to start') - xbmc.sleep(int(wait_time * 1000)) - waited += wait_time + self._monitor.waitForAbort(wait_interval) + waited += wait_interval else: self._context.send_notification('PlaybackStarted', { 'video_id': self.video_id, @@ -90,15 +80,12 @@ def run(self): 'status': self.video_status, }) - client = self.provider.get_client(self._context) - logged_in = self.provider.is_logged_in() - report_url = playback_stats.get('playback_url', '') - if playback_stats is None: - playback_stats = {} + client = self._provider.get_client(self._context) + logged_in = self._provider.is_logged_in() + report_url = use_remote_history and playback_stats.get('playback_url') state = 'playing' - last_state = 'playing' - if logged_in and report_url and use_remote_history: + if report_url: client.update_watch_history( self._context, self.video_id, @@ -108,81 +95,47 @@ def run(self): state=state ) - report_url = playback_stats.get('watchtime_url', '') - report_interval = 10.0 - first_report = True - access_manager = self._context.get_access_manager() settings = self._context.get_settings() video_id_param = 'video_id=%s' % self.video_id + report_url = use_remote_history and playback_stats.get('watchtime_url') played_time = -1.0 - wait_time = 0.5 - waited = 0.0 + wait_interval = 0.5 + report_period = waited = 10 while not self.abort_now(): - last_total_time = self.total_time - last_current_time = self.current_time - last_segment_start = self.segment_start - last_percent_complete = self.percent_complete - try: current_file = player.getPlayingFile() + self.current_time = player.getTime() + self.total_time = player.getTotalTime() except RuntimeError: - current_file = None - - if (not current_file - or (current_file != playing_file and not ( - self._context.is_plugin_path(current_file, 'play/') - and video_id_param in current_file)) - or self.stopped()): self.stop() break - if self.abort_now(): - self.update_times(last_total_time, - last_current_time, - last_segment_start, - last_percent_complete) + if (current_file != playing_file and not ( + self._context.is_plugin_path(current_file, 'play/') + and video_id_param in current_file)): + self.stop() break - try: - self.current_time = float(player.getTime()) - self.total_time = float(player.getTotalTime()) - except RuntimeError: - pass - if self.current_time < 0: self.current_time = 0.0 - if self.abort_now(): - self.update_times(last_total_time, - last_current_time, - last_segment_start, - last_percent_complete) - break - - try: - self.percent_complete = int(100 * self.current_time - / self.total_time) - except ZeroDivisionError: - self.percent_complete = 0 - - if self.abort_now(): - self.update_times(last_total_time, - last_current_time, - last_segment_start, - last_percent_complete) + if self.total_time <= 0: + self.stop() break + self.progress = int(100 * self.current_time / self.total_time) if player.start_time or player.seek_time: _seek_time = player.start_time or player.seek_time if self.current_time < _seek_time: player.seekTime(_seek_time) try: - self.current_time = float(player.getTime()) + self.current_time = player.getTime() except RuntimeError: - pass + self.stop() + break if player.end_time and self.current_time >= player.end_time: if clip and player.start_time: @@ -190,90 +143,53 @@ def run(self): else: player.stop() - if self.abort_now(): - self.update_times(last_total_time, - last_current_time, - last_segment_start, - last_percent_complete) - break - - if waited >= report_interval: - # refresh client, tokens may need refreshing - if logged_in: - self.provider.reset_client() - client = self.provider.get_client(self._context) - logged_in = self.provider.is_logged_in() + if waited >= report_period: + waited = 0 + last_state = state if self.current_time == played_time: - last_state = state state = 'paused' else: - last_state = state state = 'playing' - played_time = self.current_time - if self.abort_now(): - self.update_times(last_total_time, - last_current_time, - last_segment_start, - last_percent_complete) - break - - if (logged_in and report_url and use_remote_history - and (first_report or waited >= report_interval)): - if first_report: - first_report = False - self.segment_start = 0.0 - self.current_time = 0.0 - self.percent_complete = 0 - - waited = 0.0 + # refresh client, tokens may need refreshing + if logged_in and report_url: + self._provider.reset_client() + client = self._provider.get_client(self._context) + logged_in = self._provider.is_logged_in() - if self.segment_start < 0: - self.segment_start = 0.0 + if self.segment_start < 0: + self.segment_start = 0.0 - if state == 'playing': - segment_end = self.current_time - else: - segment_end = self.segment_start - - if segment_end > float(self.total_time): - segment_end = float(self.total_time) - - if self.segment_start > segment_end: - segment_end = self.segment_start + 10.0 - - # only report state='paused' once - if state == 'playing' or last_state == 'playing': - client.update_watch_history( - self._context, - self.video_id, - report_url, - st=format(self.segment_start, '.3f'), - et=format(segment_end, '.3f'), - state=state - ) + if state == 'playing': + segment_end = self.current_time + else: + segment_end = self.segment_start - self.segment_start = segment_end + if self.segment_start > segment_end: + segment_end = self.segment_start + report_period - if self.abort_now(): - break + if segment_end > self.total_time: + segment_end = self.total_time - xbmc.sleep(int(wait_time * 1000)) + # only report state='paused' once + if state == 'playing' or last_state == 'playing': + client.update_watch_history( + self._context, + self.video_id, + report_url, + st=format(self.segment_start, '.3f'), + et=format(segment_end, '.3f'), + state=state + ) - waited += wait_time + self.segment_start = segment_end - if logged_in and report_url and use_remote_history: - client.update_watch_history( - self._context, - self.video_id, - report_url, - st=format(self.segment_start, '.3f'), - et=format(self.current_time, '.3f'), - state=state - ) + self._monitor.waitForAbort(wait_interval) + waited += wait_interval + state = 'stopped' self._context.send_notification('PlaybackStopped', { 'video_id': self.video_id, 'channel_id': self.channel_id, @@ -285,47 +201,37 @@ def run(self): .format(video_id=self.video_id, current=self.current_time, total=self.total_time, - percent=self.percent_complete)) + percent=self.progress)) - state = 'stopped' # refresh client, tokens may need refreshing if logged_in: - self.provider.reset_client() - client = self.provider.get_client(self._context) - logged_in = self.provider.is_logged_in() + self._provider.reset_client() + client = self._provider.get_client(self._context) + logged_in = self._provider.is_logged_in() - if self.percent_complete >= settings.get_play_count_min_percent(): + if self.progress >= settings.get_play_count_min_percent(): play_count += 1 self.current_time = 0.0 - if logged_in and report_url and use_remote_history: - client.update_watch_history( - self._context, - self.video_id, - report_url, - st=format(self.total_time, '.3f'), - et=format(self.total_time, '.3f'), - state=state - ) - + segment_end = format(self.total_time, '.3f') else: - if logged_in and report_url and use_remote_history: - client.update_watch_history( - self._context, - self.video_id, - report_url, - st=format(self.current_time, '.3f'), - et=format(self.current_time, '.3f'), - state=state - ) - + segment_end = format(self.current_time, '.3f') refresh_only = True + if logged_in and report_url: + client.update_watch_history( + self._context, + self.video_id, + report_url, + st=segment_end, + et=segment_end, + state=state + ) if use_local_history: play_data = { 'play_count': play_count, 'total_time': self.total_time, 'played_time': self.current_time, - 'played_percent': self.percent_complete, + 'played_percent': self.progress, } self._context.get_playback_history().update(self.video_id, play_data) @@ -339,45 +245,41 @@ def run(self): playlist_id=watch_later_id, video_id=self.video_id ) if playlist_item_id: - _ = client.remove_video_from_playlist( + client.remove_video_from_playlist( watch_later_id, playlist_item_id ) else: self._context.get_watch_later_list().remove(self.video_id) + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + in_playlist = playlist.size() >= 2 + if logged_in and not refresh_only: history_id = access_manager.get_watch_history_id() if history_id: - _ = client.add_video_to_playlist(history_id, self.video_id) + client.add_video_to_playlist(history_id, self.video_id) # rate video - if settings.get_bool('youtube.post.play.rate', False): - do_rating = True - if not settings.get_bool('youtube.post.play.rate.playlists', - False): - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - do_rating = int(playlist.size()) < 2 - - if do_rating: - json_data = client.get_video_rating(self.video_id) - if json_data: - items = json_data.get('items', [{'rating': 'none'}]) - rating = items[0].get('rating', 'none') - if rating == 'none': - rating_match = \ - re.search(r'/(?P[^/]+)' - r'/(?P[^/]+)', - '/{0}/{1}/'.format(self.video_id, - rating)) - self.provider.yt_video.process('rate', - self.provider, - self._context, - rating_match) - - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - do_refresh = playlist.size() < 2 or playlist.getposition() == -1 - if do_refresh and settings.get_bool('youtube.post.play.refresh', False): - self.ui.refresh_container() + if (settings.get_bool('youtube.post.play.rate') and + (not in_playlist or + settings.get_bool('youtube.post.play.rate.playlists'))): + json_data = client.get_video_rating(self.video_id) + if json_data: + items = json_data.get('items', [{'rating': 'none'}]) + rating = items[0].get('rating', 'none') + if rating == 'none': + rating_match = re.search( + r'/(?P[^/]+)/(?P[^/]+)', + '/{0}/{1}/'.format(self.video_id, rating) + ) + self._provider.yt_video.process('rate', + self._provider, + self._context, + rating_match) + + if ((not in_playlist or playlist.getposition() == -1) + and settings.get_bool('youtube.post.play.refresh', False)): + self._context.get_ui().refresh_container() self.end() @@ -399,11 +301,12 @@ def ended(self): class PlayerMonitor(xbmc.Player): - def __init__(self, *_args, **kwargs): + def __init__(self, provider, context, monitor): super(PlayerMonitor, self).__init__() - self._context = kwargs.get('context') - self.provider = kwargs.get('provider') - self.ui = self._context.get_ui() + self._provider = provider + self._context = context + self._monitor = monitor + self._ui = self._context.get_ui() self.threads = [] self.seek_time = None self.start_time = None @@ -452,10 +355,10 @@ def cleanup_threads(self, only_ended=True): self.threads = active_threads def onAVStarted(self): - if not self.ui.busy_dialog_active(): - self.ui.clear_property('busy') + if not self._ui.busy_dialog_active(): + self._ui.clear_property('busy') - playback_json = self.ui.get_property('playback_json') + playback_json = self._ui.get_property('playback_json') if not playback_json: return @@ -469,16 +372,17 @@ def onAVStarted(self): self.start_time = None self.end_time = None - self.ui.clear_property('playback_json') + self._ui.clear_property('playback_json') self.cleanup_threads() self.threads.append(PlayerMonitorThread(self, - self.provider, + self._provider, self._context, + self._monitor, playback_json)) def onPlayBackEnded(self): - if not self.ui.busy_dialog_active(): - self.ui.clear_property('busy') + if not self._ui.busy_dialog_active(): + self._ui.clear_property('busy') self.stop_threads() self.cleanup_threads() diff --git a/resources/lib/youtube_plugin/kodion/utils/service_monitor.py b/resources/lib/youtube_plugin/kodion/utils/service_monitor.py index cddab114f..b4c78e24a 100644 --- a/resources/lib/youtube_plugin/kodion/utils/service_monitor.py +++ b/resources/lib/youtube_plugin/kodion/utils/service_monitor.py @@ -16,11 +16,11 @@ from ..constants import ADDON_ID from ..logger import log_debug from ..network import get_http_server, is_httpd_live -from ..settings import Settings +from ..settings import XbmcPluginSettings class ServiceMonitor(xbmc.Monitor): - _settings = Settings(xbmcaddon.Addon(ADDON_ID)) + _settings = XbmcPluginSettings(xbmcaddon.Addon(ADDON_ID)) def __init__(self): settings = self._settings diff --git a/resources/lib/youtube_plugin/youtube/client/__config__.py b/resources/lib/youtube_plugin/youtube/client/__config__.py index 545becf4e..eab114bde 100644 --- a/resources/lib/youtube_plugin/youtube/client/__config__.py +++ b/resources/lib/youtube_plugin/youtube/client/__config__.py @@ -12,7 +12,7 @@ from base64 import b64decode from ... import key_sets -from ...kodion import Context +from ...kodion.context import XbmcContext from ...kodion.json_store import APIKeyStore, AccessManager @@ -192,7 +192,7 @@ def _strip_api_keys(self, api_key, client_id, client_secret): return return_key, return_id, return_secret -_api_check = APICheck(Context()) +_api_check = APICheck(XbmcContext()) keys_changed = _api_check.changed current_user = _api_check.get_current_user() diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py index e21972ce5..e67ac3005 100644 --- a/resources/lib/youtube_plugin/youtube/client/request_client.py +++ b/resources/lib/youtube_plugin/youtube/client/request_client.py @@ -49,6 +49,7 @@ class YouTubeRequestClient(BaseRequestsClass): }, 'android': { '_id': 3, + '_query_subtitles': True, 'json': { 'params': '2AMBCgIQBg', 'context': { @@ -79,6 +80,7 @@ class YouTubeRequestClient(BaseRequestsClass): # Limited to 720p on some videos 'android_embedded': { '_id': 55, + '_query_subtitles': True, 'json': { 'params': '2AMBCgIQBg', 'context': { diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py index 0ea54d265..45c7fd1bc 100644 --- a/resources/lib/youtube_plugin/youtube/client/youtube.py +++ b/resources/lib/youtube_plugin/youtube/client/youtube.py @@ -560,7 +560,7 @@ def get_recommended_for_home(self, 'kind': 'youtube#activityListResponse', 'items': [ { - 'kind': "youtube#video", + 'kind': 'youtube#video', 'id': video['videoId'], 'partial': True, 'snippet': { @@ -1809,7 +1809,8 @@ def api_request(self, client = self.build_client(version, client_data) if 'key' in client['params'] and not client['params']['key']: - client['params']['key'] = self._config_tv['key'] + client['params']['key'] = (self._config.get('key') + or self._config_tv['key']) if method != 'POST' and 'json' in client: del client['json'] diff --git a/resources/lib/youtube_plugin/youtube/helper/subtitles.py b/resources/lib/youtube_plugin/youtube/helper/subtitles.py index 8effeada9..485c3f5a0 100644 --- a/resources/lib/youtube_plugin/youtube/helper/subtitles.py +++ b/resources/lib/youtube_plugin/youtube/helper/subtitles.py @@ -29,6 +29,7 @@ class Subtitles(object): LANG_CURR_FALLBACK = 2 LANG_CURR = 3 LANG_CURR_NO_ASR = 4 + LANG_ALL = 5 BASE_PATH = make_dirs(TEMP_PATH) @@ -37,7 +38,7 @@ def __init__(self, context, video_id, captions, headers=None): self._context = context settings = context.get_settings() - self.language = settings.get_language() + self.lang_code = settings.get_language() self.pre_download = settings.subtitle_download() self.subtitle_languages = settings.subtitle_languages() @@ -48,7 +49,7 @@ def __init__(self, context, video_id, captions, headers=None): headers.pop('Content-Type', None) self.headers = headers - ui = self._context.get_ui() + ui = context.get_ui() self.prompt_override = (ui.get_property('prompt_for_subtitles') == video_id) ui.clear_property('prompt_for_subtitles') @@ -67,6 +68,7 @@ def __init__(self, context, video_id, captions, headers=None): 'caption': {}, 'lang_code': 'und', 'is_asr': False, + 'translation_base': None, } if default_audio is None: return @@ -93,13 +95,22 @@ def __init__(self, context, video_id, captions, headers=None): 'caption': default_caption, 'lang_code': default_caption.get('languageCode') or 'und', 'is_asr': default_caption.get('kind') == 'asr', + 'translation_base': None, } + if default_caption.get('isTranslatable'): + self.defaults['translation_base'] = default_caption + else: + for track in self.caption_tracks: + if track.get('isTranslatable'): + self.defaults['translation_base'] = track + break def _unescape(self, text): try: text = unescape(text) except: - self._context.log_debug('Subtitle unescape: failed to unescape text') + self._context.log_error('Subtitles._unescape - failed: |{text}|' + .format(text=text)) return text def get_default_lang(self): @@ -113,56 +124,113 @@ def get_subtitles(self): languages = self.LANG_PROMPT else: languages = self.subtitle_languages - self._context.log_debug('Subtitle get_subtitles: for setting |%s|' % str(languages)) + if languages == self.LANG_NONE: - return [] - if languages == self.LANG_CURR: - list_of_subs = [] - list_of_subs.extend(self._get(self.language)) - list_of_subs.extend(self._get(language=self.language.split('-')[0])) - return list(set(list_of_subs)) - if languages == self.LANG_CURR_NO_ASR: - list_of_subs = [] - list_of_subs.extend(self._get(self.language, no_asr=True)) - list_of_subs.extend(self._get(language=self.language.split('-')[0], no_asr=True)) - return list(set(list_of_subs)) + return None + + if languages == self.LANG_ALL: + return self.get_all(sub_format='vtt') + if languages == self.LANG_PROMPT: return self._prompt() + + no_asr = languages == self.LANG_CURR_NO_ASR if languages == self.LANG_CURR_FALLBACK: - list_of_subs = [] - list_of_subs.extend(self._get(language=self.language)) - list_of_subs.extend(self._get(language=self.language.split('-')[0])) - list_of_subs.extend(self._get('en')) - list_of_subs.extend(self._get('en-US')) - list_of_subs.extend(self._get('en-GB')) - return list(set(list_of_subs)) - self._context.log_debug('Unknown language_enum: %s for subtitles' % str(languages)) - return [] - - def _get_all(self, download=False): - list_of_subs = [] + lang_codes = ('en', 'en-US', 'en-GB') + else: + lang_codes = {self.lang_code, self.lang_code.partition('-')[0]} + + subtitles = {} + default_lang_code = self.defaults['lang_code'] + for lang_code in lang_codes: + track, language, kind, is_translation = self._get_track( + lang_code, no_asr=no_asr + ) + if not track: + continue + subtitles[lang_code] = { + 'default': lang_code == default_lang_code, + 'kind': kind, + 'language': language, + 'mime_type': 'text/vtt', + 'url': self._get_url( + caption_track=track, + lang_code=lang_code, + is_translation=is_translation, + sub_format='vtt', + ), + } + return subtitles + + def get_all(self, sub_format='vtt'): + if sub_format == 'vtt': + mime_type = 'text/vtt' + else: + sub_format = 'ttml' + mime_type = 'application/ttml+xml' + + subtitles = {} + + default_lang_code = self.defaults['lang_code'] for track in self.caption_tracks: - list_of_subs.extend(self._get(track.get('languageCode'), - self._get_language_name(track), - download=download)) - for track in self.translation_langs: - list_of_subs.extend(self._get(track.get('languageCode'), - self._get_language_name(track), - download=download)) - return list(set(list_of_subs)) + track_lang_code = track.get('languageCode') + track_language = self._get_language_name(track) + url = self._get_url( + caption_track=track, + lang_code=track_lang_code, + sub_format=sub_format, + download=False, + ) + if url: + subtitles[track_lang_code] = { + 'default': track_lang_code == default_lang_code, + 'kind': track.get('kind'), + 'language': track_language, + 'mime_type': mime_type, + 'url': url, + } + + translation_base = self.defaults['translation_base'] + if translation_base: + for track in self.translation_langs: + track_lang_code = track.get('languageCode') + if track_lang_code in subtitles: + continue + track_language = self._get_language_name(track) + url = self._get_url( + caption_track=translation_base, + lang_code=track_lang_code, + is_translation=True, + sub_format=sub_format, + download=False, + ) + if url: + subtitles[track_lang_code] = { + 'default': track_lang_code == default_lang_code, + 'kind': 'translation', + 'language': track_language, + 'mime_type': mime_type, + 'url': url, + } + + return subtitles def _prompt(self): - captions = [(track.get('languageCode'), - self._get_language_name(track)) - for track in self.caption_tracks] - translations = [(track.get('languageCode'), - self._get_language_name(track)) - for track in self.translation_langs] + captions = [ + (track.get('languageCode'), self._get_language_name(track)) + for track in self.caption_tracks + ] + translations = [ + (track.get('languageCode'), self._get_language_name(track)) + for track in self.translation_langs + ] if self.defaults['translation_base'] else [] num_captions = len(captions) num_translations = len(translations) num_total = num_captions + num_translations - if num_total: + if not num_total: + self._context.log_debug('No subtitles found for prompt') + else: choice = self._context.get_ui().on_select( self._context.localize('subtitles.language'), [name for _, name in captions] + @@ -170,101 +238,91 @@ def _prompt(self): ) if choice == -1: self._context.log_debug('Subtitle selection cancelled') - return [] + return None - subtitle = None + track = None if 0 <= choice < num_captions: - choice = captions[choice] - subtitle = self._get(lang_code=choice[0], language=choice[1]) + track = self.caption_tracks[choice] + kind = track.get('kind') + choice = translations[choice - num_captions] + is_translation = False elif num_captions <= choice < num_total: + track = self.defaults['translation_base'] + kind = 'translation' choice = translations[choice - num_captions] - subtitle = self._get(lang_code=choice[0], language=choice[1]) - - if subtitle: - return subtitle - self._context.log_debug('No subtitles found for prompt') - return [] - - def _get(self, lang_code='en', language=None, no_asr=False, download=None): - filename = '.'.join((self.video_id, lang_code, 'srt')) - if not self.BASE_PATH: - self._context.log_error('Subtitles._get - ' - 'unable to access temp directory') - return [] - - filepath = os.path.join(self.BASE_PATH, filename) - if xbmcvfs.exists(filepath): - self._context.log_debug('Subtitle exists for |{lang}| - |{file}|' - .format(lang=lang_code, file=filepath)) - return [filepath] + is_translation = True - if download is None: - download = self.pre_download - - caption_track = None - asr_track = None - has_translation = False - for track in self.caption_tracks: - if lang_code == track.get('languageCode'): - if language is not None: - if language == self._get_language_name(track): - caption_track = track - break - elif no_asr and (track.get('kind') == 'asr'): - continue - elif track.get('kind') == 'asr': - asr_track = track - else: - caption_track = track - - if (caption_track is None) and (asr_track is not None): - caption_track = asr_track - - for lang in self.translation_langs: - if lang_code == lang.get('languageCode'): - has_translation = True - break - - if (lang_code != self.defaults['lang_code'] and not has_translation - and caption_track is None): - self._context.log_debug('No subtitles found for: %s' % lang_code) - return [] - - subtitle_url = None - if caption_track is not None: - base_url = self._normalize_url(caption_track.get('baseUrl')) - has_translation = False - elif has_translation: - base_url = self._normalize_url( - self.defaults['caption'].get('baseUrl') - ) - else: - base_url = None - - if base_url: - subtitle_url = self._set_query_param( - base_url, - ('type', 'track'), - ('fmt', 'vtt'), - ('tlang', lang_code) if has_translation else (None, None), + url = self._get_url( + caption_track=track, + lang_code=choice[0], + is_translation=is_translation, + sub_format='vtt', ) + if url: + return { + choice[0]: { + 'default': True, + 'kind': kind, + 'language': choice[1], + 'mime_type': 'text/vtt', + 'url': url, + }, + } + self._context.log_debug('No subtitle found for selection: |{lang}|' + .format(lang=choice[0])) + return None - if not subtitle_url: - self._context.log_debug('No subtitles found for: %s' % lang_code) - return [] + def _get_url(self, + caption_track, + lang_code, + is_translation=False, + sub_format='vtt', + download=None): + if download is None: + download = self.pre_download + if download: + sub_format = 'vtt' + filename = '.'.join((self.video_id, lang_code, 'srt')) + if not self.BASE_PATH: + self._context.log_error('Subtitles._get_url' + ' - unable to access temp directory') + return None + + filepath = os.path.join(self.BASE_PATH, filename) + if xbmcvfs.exists(filepath): + self._context.log_debug('Subtitles._get_url' + ' - use existing: |{lang}: {file}|' + .format(lang=lang_code, file=filepath)) + return filepath + + base_url = self._normalize_url(caption_track.get('baseUrl')) + if not base_url: + self._context.log_error('Subtitles._get_url - no url for: |{lang}|' + .format(lang=lang_code)) + return None + + subtitle_url = self._set_query_param( + base_url, + ('type', 'track'), + ('fmt', sub_format), + ('tlang', lang_code) if is_translation else (None, None), + ) + if not is_translation: + self._context.log_debug('Subtitles._get_url: |{lang}: {url}|' + .format(lang=lang_code, url=subtitle_url)) if not download: - return [subtitle_url] + return subtitle_url response = BaseRequestsClass().request( subtitle_url, headers=self.headers, - error_info=('Failed to retrieve subtitles for: {lang}: {{exc}}' + error_info=('Subtitles._get_url - GET failed for: {lang}: {{exc}}' .format(lang=lang_code)) ) response = response and response.text if not response: - return [] + return None output = bytearray(self._unescape(response), encoding='utf8', @@ -273,25 +331,73 @@ def _get(self, lang_code='en', language=None, no_asr=False, download=None): with xbmcvfs.File(filepath, 'w') as srt_file: success = srt_file.write(output) except (IOError, OSError): - self._context.log_error('Subtitles._get - ' - 'file write failed for: {file}' + self._context.log_error('Subtitles._get_url' + ' - write failed for: {file}' .format(file=filepath)) if success: - return [filepath] - return [] + return filepath + return None + + def _get_track(self, + lang_code='en', + language=None, + no_asr=False): + caption_track = caption_language = caption_kind = None + is_translation = False + + for track in self.caption_tracks: + track_language = self._get_language_name(track) + track_kind = track.get('kind') + if lang_code == track.get('languageCode'): + if language is not None: + if language == track_language: + caption_track = track + caption_language = track_language + caption_kind = track_kind + break + elif no_asr and track_kind == 'asr': + continue + else: + caption_track = track + caption_language = track_language + caption_kind = track_kind + + if not caption_track and self.defaults['translation_base']: + for track in self.translation_langs: + if lang_code == track.get('languageCode'): + caption_track = self.defaults['translation_base'] + caption_language = self._get_language_name(track) + caption_kind = 'translation' + is_translation = True + break + + if caption_track: + return caption_track, caption_language, caption_kind, is_translation + + self._context.log_debug('Subtitles._get - no subtitle for: |{lang}|' + .format(lang=lang_code)) + return None, None, None, None @staticmethod def _get_language_name(track): - key = 'languageName' if 'languageName' in track else 'name' - lang_name = track.get(key, {}).get('simpleText') - if not lang_name: - track_name = track.get(key, {}).get('runs', [{}]) - if isinstance(track_name, (list, tuple)) and len(track_name) >= 1: - lang_name = track_name[0].get('text') + lang_obj = None + if 'languageName' in track: + lang_obj = track['languageName'] + elif 'name' in track: + lang_obj = track['name'] + + if not lang_obj: + return None + lang_name = lang_obj.get('simpleText') if lang_name: return lang_name - return None + + track_name = lang_obj.get('runs') + if isinstance(track_name, (list, tuple)) and len(track_name) >= 1: + lang_name = track_name[0].get('text') + + return lang_name @staticmethod def _set_query_param(url, *pairs): diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py index 8d744560d..88ce47cc9 100644 --- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py +++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py @@ -206,20 +206,15 @@ class UrlResolver(object): def __init__(self, context): self._context = context self._function_cache = context.get_function_cache() - self._resolver_map = { - 'common_resolver': CommonResolver(context), - 'youtube_resolver': YouTubeResolver(context), - } - self._resolvers = [ - 'common_resolver', - 'youtube_resolver', - ] + self._resolvers = ( + ('common_resolver', CommonResolver(context)), + ('youtube_resolver', YouTubeResolver(context)), + ) def _resolve(self, url): # try one of the resolvers resolved_url = url - for resolver_name in self._resolvers: - resolver = self._resolver_map[resolver_name] + for resolver_name, resolver in self._resolvers: url_components = urlsplit(resolved_url) method = resolver.supports_url(resolved_url, url_components) if not method: diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index 8f7b52b0b..31c94abd1 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -373,15 +373,22 @@ def update_video_infos(provider, context, video_id_dict, show_details = settings.show_detailed_description() thumb_size = settings.use_thumbnail_size() thumb_stamp = get_thumb_timestamp() + untitled = context.localize('untitled') path = context.get_path() ui = context.get_ui() if path.startswith(paths.MY_SUBSCRIPTIONS): in_my_subscriptions_list = True + in_watched_later_list = False + playlist_match = False + elif path.startswith(paths.WATCH_LATER): + in_my_subscriptions_list = False + in_watched_later_list = True playlist_match = False else: in_my_subscriptions_list = False + in_watched_later_list = False playlist_match = __RE_PLAYLIST_MATCH.match(path) for video_id, yt_item in data.items(): @@ -500,7 +507,9 @@ def update_video_infos(provider, context, video_id_dict, video_item.set_code(label_stats) # update and set the title - title = video_item.get_title() or snippet['title'] or '' + title = video_item.get_title() + if not title or title == untitled: + title = snippet.get('title') or untitled video_item.set_title(ui.italic(title) if video_item.upcoming else title) """ @@ -606,7 +615,7 @@ def update_video_infos(provider, context, video_id_dict, context, watch_later_id, video_id ) ) - else: + elif not in_watched_later_list: context_menu.append( menu_items.watch_later_local_add( context, video_item @@ -689,6 +698,7 @@ def update_video_infos(provider, context, video_id_dict, menu_items.play_ask_for_quality( context, video_id ), + ('--------', 'noop'), )) if context_menu: diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py index 43d0afae8..18d37684b 100644 --- a/resources/lib/youtube_plugin/youtube/helper/v3.py +++ b/resources/lib/youtube_plugin/youtube/helper/v3.py @@ -89,11 +89,11 @@ def _process_list_response(provider, context, json_data): channel_id_dict[item_id] = item # if logged in => provide subscribing to the channel if provider.is_logged_in(): - context_menu = ( + context_menu = [ menu_items.subscribe_to_channel( context, item_id ), - ) + ] item.set_context_menu(context_menu) elif kind == 'guidecategory': diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py index 7647b2823..386227ffc 100644 --- a/resources/lib/youtube_plugin/youtube/helper/video_info.py +++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py @@ -756,7 +756,7 @@ def _make_curl_headers(headers, cookies=None): '{0.name}={0.value}'.format(cookie) for cookie in cookies )))) - # Headers to be used in function 'to_play_item' of 'xbmc_items.py' + # Headers used in xbmc_items.video_playback_item' output.extend('{0}={1}'.format(key, quote(value)) for key, value in headers.items()) return '&'.join(output) @@ -1123,12 +1123,10 @@ def _get_video_info(self): microformat = (response.get('microformat', {}) .get('playerMicroformatRenderer', {})) streaming_data = response.get('streamingData', {}) - is_live = '_live' if video_details.get('isLiveContent') else '' + is_live = video_details.get('isLiveContent', False) + thumb_suffix = '_live' if is_live else '' - captions = response.get('captions') - if captions: - captions['headers'] = client['headers'] - elif client.get('_query_subtitles'): + if client.get('_query_subtitles'): result = self.request( video_info_url, 'POST', error_msg=('Caption request failed to get player response for' @@ -1139,14 +1137,19 @@ def _get_video_info(self): captions = response.get('captions') if captions: captions['headers'] = result.request.headers + else: + captions = response.get('captions') + if captions: + captions['headers'] = client['headers'] if captions: captions = Subtitles( self._context, self.video_id, captions ) default_lang = captions.get_default_lang() - captions = captions.get_subtitles() + subs_data = captions.get_subtitles() else: default_lang = {'code': 'und', 'is_asr': False} + subs_data = None meta_info = { 'video': { @@ -1159,7 +1162,7 @@ def _get_video_info(self): 'private': video_details.get('isPrivate', False), 'crawlable': video_details.get('isCrawlable', False), 'family_safe': microformat.get('isFamilySafe', False), - 'live': bool(is_live), + 'live': is_live, }, }, 'channel': { @@ -1170,15 +1173,17 @@ def _get_video_info(self): }, 'images': { 'high': ('https://i.ytimg.com/vi/{0}/hqdefault{1}.jpg' - .format(self.video_id, is_live)), + .format(self.video_id, thumb_suffix)), 'medium': ('https://i.ytimg.com/vi/{0}/mqdefault{1}.jpg' - .format(self.video_id, is_live)), + .format(self.video_id, thumb_suffix)), 'standard': ('https://i.ytimg.com/vi/{0}/sddefault{1}.jpg' - .format(self.video_id, is_live)), + .format(self.video_id, thumb_suffix)), 'default': ('https://i.ytimg.com/vi/{0}/default{1}.jpg' - .format(self.video_id, is_live)), + .format(self.video_id, thumb_suffix)), }, - 'subtitles': captions, + 'subtitles': [ + subtitle['url'] for subtitle in subs_data.values() + ] if subs_data else None, } if _settings.use_remote_history(): @@ -1249,7 +1254,7 @@ def _get_video_info(self): self._cipher = Cipher(self._context, javascript=self._player_js) manifest_url = main_stream = None - live_type = is_live and _settings.get_live_stream_type() + live_type = _settings.get_live_stream_type() if is_live else None if live_type == 'isa_mpd' and 'dashManifestUrl' in streaming_data: manifest_url = streaming_data['dashManifestUrl'] @@ -1258,8 +1263,6 @@ def _get_video_info(self): streaming_data['hlsManifestUrl'], live_type, meta_info, client['headers'], playback_stats )) - else: - live_type = None # extract adaptive streams and create MPEG-DASH manifest if not manifest_url and httpd_is_live and adaptive_fmts: @@ -1267,8 +1270,9 @@ def _get_video_info(self): adaptive_fmts, default_lang['code'] ) manifest_url, main_stream = self._generate_mpd_manifest( - video_data, audio_data, license_info.get('url') + video_data, audio_data, subs_data, license_info.get('url') ) + live_type = None # extract non-adaptive streams if all_fmts: @@ -1628,7 +1632,11 @@ def _group_sort(item): return video_data, audio_data - def _generate_mpd_manifest(self, video_data, audio_data, license_url): + def _generate_mpd_manifest(self, + video_data, + audio_data, + subs_data, + license_url): if not video_data or not audio_data: return None, None @@ -1855,6 +1863,45 @@ def _filter_group(previous_group, previous_stream, item): output.append('\t\t\n') set_id += 1 + if subs_data: + for lang_code, subtitle in subs_data.items(): + label = language = subtitle['language'] + kind = subtitle['kind'] + if kind: + if kind == 'translation': + label = '{0} ({1})'.format(language, kind) + kind = '_'.join((lang_code, kind)) + else: + kind = lang_code + + output.extend(( + '\t\t\n' + # AdaptationSet Label element not currently used by ISA + '\t\t\t\n' + '\t\t\t\n' + '\t\t\t\n' + '\t\t\t\t', subtitle['url'], '\n' + '\t\t\t\n' + '\t\t\n' + )) + set_id += 1 + output.append('\t\n' '\n') output = ''.join(output) diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index d083d1ccd..be0e2cca9 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -262,8 +262,15 @@ def get_resource_manager(self, context): # noinspection PyUnusedLocal @RegisterProviderPath('^/uri2addon/$') - def on_uri2addon(self, context, re_match): - uri = context.get_param('uri') + def on_uri2addon(self, context, re_match, uri=None): + if uri is None: + uri = context.get_param('uri') + skip_title = True + listing = False + else: + skip_title = False + listing = True + if not uri: return False @@ -271,9 +278,9 @@ def on_uri2addon(self, context, re_match): res_url = resolver.resolve(uri) url_converter = UrlToItemConverter(flatten=True) url_converter.add_url(res_url, context) - items = url_converter.get_items(self, context, skip_title=True) + items = url_converter.get_items(self, context, skip_title=skip_title) if items: - return items[0] + return items if listing else items[0] return False @@ -441,7 +448,7 @@ def _on_channel(self, context, re_match): if method == 'user': return False - channel_fanarts = resource_manager.get_fanarts((channel_id, )) + channel_fanarts = resource_manager.get_fanarts((channel_id,)) page = params.get('page', 1) page_token = params.get('page_token', '') @@ -674,9 +681,6 @@ def _on_subscriptions(self, context, re_match): if method == 'list': context.set_content(content.LIST_CONTENT) - channel_ids = [] - for subscription in subscriptions: - channel_ids.append(subscription.get_channel_id()) channel_ids = {subscription.get_channel_id(): subscription for subscription in subscriptions} channel_fanarts = resource_manager.get_fanarts(channel_ids) @@ -714,14 +718,6 @@ def _on_sign(self, context, re_match): yt_login.process(mode, self, context) return False - @RegisterProviderPath('^/search/$') - def endpoint_search(self, context, re_match): - query = context.get_param('q') - if not query: - return [] - - return self.on_search(query, context, re_match) - def _search_channel_or_playlist(self, context, id_string): json_data = {} result = [] @@ -739,6 +735,10 @@ def _search_channel_or_playlist(self, context, id_string): return result def on_search(self, search_text, context, re_match): + # Search by url to access unlisted videos + if search_text.startswith(('https://', 'http://')): + return self.on_uri2addon(context, None, search_text) + result = self._search_channel_or_playlist(context, search_text) if result: # found a channel or playlist matching search_text return result @@ -966,33 +966,36 @@ def on_playback_history(self, context, re_match): if action == 'list': context.set_content(content.VIDEO_CONTENT, sub_type='history') - play_data = playback_history.get_items() - if not play_data: + items = playback_history.get_items() + if not items: return True - json_data = self.get_client(context).get_videos(play_data) - if not json_data: - return True - items = v3.response_to_items(self, context, json_data) - for item in items: - video_id = item.video_id + v3_response = { + 'kind': 'youtube#videoListResponse', + 'items': [ + { + 'kind': 'youtube#video', + 'id': video_id, + 'partial': True, + } + for video_id in items.keys() + ] + } + video_items = v3.response_to_items(self, context, v3_response) + + for video_item in video_items: context_menu = [ menu_items.history_remove( - context, video_id - ), - menu_items.history_mark_unwatched( - context, video_id - ) if play_data[video_id]['play_count'] else - menu_items.history_mark_watched( - context, video_id + context, video_item.video_id ), menu_items.history_clear( context ), + ('--------', 'noop'), ] - item.set_context_menu(context_menu) + video_item.add_context_menu(context_menu) - return items + return video_items if (action == 'clear' and context.get_ui().on_yes_no_input( context.get_name(), @@ -1046,6 +1049,7 @@ def on_root(self, context, re_match): _ = self.get_client(context) # required for self.is_logged_in() logged_in = self.is_logged_in() + # _.get_my_playlists() # context.set_content(content.LIST_CONTENT) @@ -1325,6 +1329,70 @@ def on_root(self, context, re_match): return result + def on_watch_later(self, context, re_match): + params = context.get_params() + command = re_match.group('command') + if not command: + return False + + if command == 'list': + context.set_content(content.VIDEO_CONTENT, sub_type='watch_later') + items = context.get_watch_later_list().get_items() + if not items: + return True + + v3_response = { + 'kind': 'youtube#videoListResponse', + 'items': [ + { + 'kind': 'youtube#video', + 'id': video_id, + 'partial': True, + } + for video_id in items.keys() + ] + } + video_items = v3.response_to_items(self, context, v3_response) + + for video_item in video_items: + context_menu = [ + menu_items.watch_later_local_remove( + context, video_item.video_id + ), + menu_items.watch_later_local_clear( + context + ), + ('--------', 'noop'), + ] + video_item.add_context_menu(context_menu) + + return video_items + + if (command == 'clear' and context.get_ui().on_yes_no_input( + context.get_name(), + context.localize('watch_later.clear.confirm') + )): + context.get_watch_later_list().clear() + context.get_ui().refresh_container() + return True + + video_id = params.get('video_id') + if not video_id: + return False + + if command == 'add': + item = params.get('item') + if item: + context.get_watch_later_list().add(video_id, item) + return True + + if command == 'remove': + context.get_watch_later_list().remove(video_id) + context.get_ui().refresh_container() + return True + + return False + def handle_exception(self, context, exception_to_handle): if isinstance(exception_to_handle, (InvalidGrant, LoginException)): ok_dialog = False diff --git a/resources/lib/youtube_registration.py b/resources/lib/youtube_registration.py index 4a8dbb7fc..f43ecfe3e 100644 --- a/resources/lib/youtube_registration.py +++ b/resources/lib/youtube_registration.py @@ -12,7 +12,7 @@ from base64 import b64encode from youtube_plugin.kodion.constants import ADDON_ID -from youtube_plugin.kodion.context import Context +from youtube_plugin.kodion.context import XbmcContext from youtube_plugin.kodion.json_store import APIKeyStore @@ -44,7 +44,7 @@ def register_api_keys(addon_id, api_key, client_id, client_secret): :param client_secret: YouTube Data v3 Client secret """ - context = Context() + context = XbmcContext() if not addon_id or addon_id == ADDON_ID: context.log_error('Register API Keys: |%s| Invalid addon_id' % addon_id) diff --git a/resources/lib/youtube_requests.py b/resources/lib/youtube_requests.py index 822a04c38..054c20392 100644 --- a/resources/lib/youtube_requests.py +++ b/resources/lib/youtube_requests.py @@ -12,7 +12,7 @@ import re from youtube_plugin.youtube.provider import Provider -from youtube_plugin.kodion.context import Context +from youtube_plugin.kodion.context import XbmcContext def __get_core_components(addon_id=None): @@ -22,9 +22,9 @@ def __get_core_components(addon_id=None): """ provider = Provider() if addon_id is not None: - context = Context(params={'addon_id': addon_id}) + context = XbmcContext(params={'addon_id': addon_id}) else: - context = Context() + context = XbmcContext() client = provider.get_client(context=context) return provider, context, client diff --git a/resources/lib/youtube_resolver.py b/resources/lib/youtube_resolver.py index 82429150f..fd7648f01 100644 --- a/resources/lib/youtube_resolver.py +++ b/resources/lib/youtube_resolver.py @@ -12,15 +12,15 @@ import re from youtube_plugin.youtube.provider import Provider -from youtube_plugin.kodion.context import Context +from youtube_plugin.kodion.context import XbmcContext def _get_core_components(addon_id=None): provider = Provider() if addon_id is not None: - context = Context(params={'addon_id': addon_id}) + context = XbmcContext(params={'addon_id': addon_id}) else: - context = Context() + context = XbmcContext() client = provider.get_client(context=context) return provider, context, client diff --git a/resources/settings.xml b/resources/settings.xml index 45e7a332d..c07d24083 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -262,16 +262,16 @@ - 0 - - avc1,vp9,av01,hdr,hfr,vorbis,mp4a,ssa,ac-3,ec-3,dts,filter - + --> + avc1,vp9,av01,hdr,hfr,vorbis,mp4a,ssa,ac-3,ec-3,dts,filter + @@ -292,16 +292,16 @@ , 1 - - + + true - - true - true - + + true + true + 0 @@ -689,6 +689,11 @@ true + + 0 + true + +