commit 37fd45c19a6b7b976140d7067a6547b29f80ef27
Author: Richard Shaw <hobbes1069(a)gmail.com>
Date: Mon Sep 9 16:29:30 2019 -0500
Update to v30.0-69-g5cde0578d8.
Initial update for Python 3 compatibility using upstream pull request.
mythdb-optimize.timer | 3 +-
mythtv-ChangeLog | 688 ++++
mythtv-py3_configure.patch | 21 +
mythtv-py3_urllib.patch | 592 ++++
mythtv-python3.patch | 3519 ++++++++++++++++++++
mythtv-space_in_GB.patch | 57 +-
mythtv.spec | 27 +-
v30.0..6bd8cd4993.patch => v30.0..5cde0578d8.patch | 961 +++++-
8 files changed, 5767 insertions(+), 101 deletions(-)
---
diff --git a/mythdb-optimize.timer b/mythdb-optimize.timer
index 76cd3ea..fe53106 100644
--- a/mythdb-optimize.timer
+++ b/mythdb-optimize.timer
@@ -23,6 +23,7 @@ Requires=mariadb.service
# weekly → Mon *-*-* 00:00:00
# By default, run about midnight:
OnCalendar=daily
+Persistent=yes
[Install]
-WantedBy=timer.target
+WantedBy=timers.target
diff --git a/mythtv-ChangeLog b/mythtv-ChangeLog
index 899b2ec..7f2d96a 100644
--- a/mythtv-ChangeLog
+++ b/mythtv-ChangeLog
@@ -1,3 +1,691 @@
+commit 5cde0578d84926171b20c8f7e95a101e9b0b9457
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Thu Aug 8 12:45:52 2019 -0400
+
+ dbcheck: Enforce correct recgroupids for special recording groups
+
+ If the mysql server is set up with auto_increment_increment=2 or
+ some value other than 1, the wrong values were set for the special
+ recording groups.
+
+ This only fixes it for new database setups or upgrades from schema
+ id 1320.
+
+ Refs #13473
+
+ (cherry picked from commit da558ca5f531b8074155946b96c82ef4fe0ddefa)
+
+commit e094a020179aba2d955a21b8fd067231a87e3334
+Author: Paul B. Henson <henson(a)acm.org>
+Date: Thu Jul 25 11:44:28 2019 +0200
+
+ DVB symbol errros with gcc 8.3
+
+ Include of dvbchannel.h in channelscanner.cpp is now
+ conditional on USING_DVB.
+ This does solve the compilation issue for gcc 8.3.
+ Note that there are no compilation issues with
+ newer gcc versions, as tested with gcc 9.1.
+
+ Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+ (cherry picked from commit 06a1158ead9aafd6c394e90860049b53e1e16978)
+ Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+
+commit a4bc92e244d815351bd6de6f8d4c2569f22bc403
+Author: Tom Dexter <digitalaudiorock(a)gmail.com>
+Date: Thu Jul 11 16:34:08 2019 +0100
+
+ Gentoo users may not have nvidia-settings utils installed. From
https://bugs.gentoo.org/688084
+
+ Signed-off-by: Stuart Auchterlonie <stuarta(a)mythtv.org>
+ (cherry picked from commit 13ec89b65ea4a95dafbdfc31ff27a621ca126860)
+
+commit 83e27017203abfac6958bd3a6ce3284c372fa2d4
+Author: Paul Harrison <pharrison(a)mythtv.org>
+Date: Fri Jul 5 21:18:04 2019 +0100
+
+ mythzmserver: fix merge error after 84e5caebaa
+
+ Refs #13466
+
+commit 84e5caebaa9650b88a6082f19cb46d46b831b566
+Author: Paul Harrison <pharrison(a)mythtv.org>
+Date: Fri Jul 5 19:02:40 2019 +0100
+
+ mythzmserver: don't try to get the shared memory pointers for a disabled Monitor
+
+ Also allow for a NULL result for the Host field in the Monitors table.
+ Fixes #13466
+
+ (cherry picked from commit 7924ad992c75c2dad04eef992d51c349d9e4a64d)
+
+commit d794f3108195eb8fe5333a73db38275185ff73c2
+Author: Paul Harrison <pharrison(a)mythtv.org>
+Date: Wed Jun 19 01:34:36 2019 +0100
+
+ ZMClient: use locking to make sure only one command runs at a time
+
+ (cherry picked from commit b57372d484c1c9306ecb26b5e18906708980b4fe)
+
+commit 80434249139b36701b050c7990cf20a09de268a1
+Author: Paul Harrison <pharrison(a)mythtv.org>
+Date: Fri Apr 19 16:43:49 2019 +0100
+
+ MythZoneMinder: fix saving the enabled/disabled monitor notification setting
+
+ (cherry picked from commit cca393352f1df6ef97b655f048b18c2f0b98732a)
+
+commit 4dae2f4106d537babfe8ddca0c7871a113a139e5
+Author: Paul Harrison <pharrison(a)mythtv.org>
+Date: Tue Feb 5 11:41:09 2019 +0000
+
+ MythWeather: fix some of the weather grabber scripts
+
+ Remove the no longer supported "use encoding 'utf8'" directive.
+
+ (cherry picked from commit 4f58fd152f56089ac15db90433ea952d11d7b026)
+
+commit dac22b7f1043d4b212d1d73045ada3b8b113e090
+Author: Stuart Auchterlonie <stuarta(a)mythtv.org>
+Date: Thu Jul 4 11:57:25 2019 +0100
+
+ Fixes #13467 - Handle null blocks in DSMCC
+
+ (cherry picked from commit 9dfa5c7a70bb422d748ab327f4b1a0cb3e435794)
+
+commit b9c1b41a6c01a387bad8f325e2b8000327045cc6
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Mon Jun 17 15:20:19 2019 -0400
+
+ android: Fix problem with guide grid colors.
+
+ assets file system does not support file handles.
+
+ Refs #13454
+
+ (cherry picked from commit 9ee9894bf540a6838fe67545de205ffd23acc63f)
+
+commit ef55cb239b27741954b1ed026297a0c59b4ac0e1
+Author: Robert Watson <robertabcdefgwatson(a)gmail.com>
+Date: Thu Jun 13 10:48:32 2019 +0200
+
+ Compute seek offset 64 bits in playback of Blu-Ray iso.
+
+ Fixes #13461
+
+ Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+ (cherry picked from commit f0ed96407e6a20db3c6b6ca1a59cb8498736a5cc)
+ Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+
+commit 4761e7a6d42a86c11f10bd4dea83ca8eb022b016
+Author: Jan Ceuleers <jan.ceuleers(a)gmail.com>
+Date: Thu May 30 16:14:27 2019 -0500
+
+ tmdb3.py: Prevent program from stalling
+
+ From the -users list. If one (or more) requests stall, no
+ more metadata is retrieved until all stalled requests are
+ killed (or the backend is rebooted.)
+
+ Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+ (cherry picked from commit 98f4fc8c47040b1afb8cbca1886765cc06943923)
+
+commit 402e8274435cfab094cd902105a11231b7eadfbc
+Author: Bill Meek <billmeek(a)mythtv.org>
+Date: Fri May 10 11:28:31 2019 -0500
+
+ HTTP Status: Restore thousands separators and use proper LOCALE choice
+
+ In #3957 the LOCALE was fixed to QLocale::C, and a report on the Forum
+ pointed out the commas were lost after upgrading to Ubuntu 18.04 from
+ 16.04. I haven't found any Qt documentation that says the existing
+ method is wrong, but this fix puts commas back and should have the
+ side affect of using the correct separator in other LOCALS.
+
+ (cherry picked from commit 9f6230f1dc9db1d34be9c5ef3986ac640e7f4316)
+
+commit aa26a6b76bf00fcd5ddb051c76f9994757e09913
+Author: Bill Meek <billmeek(a)mythtv.org>
+Date: Fri May 10 14:34:14 2019 -0500
+
+ Python Bindings: Fix version test for Frontend services
+
+ Use Frontend/version to 'prime' the session for POSTs (was always
+ using Myth/version.)
+
+ (cherry picked from commit c12ff7cac94d570f9c6b566b455811c5a90735d6)
+
+commit 8d04874b1ecffebe0085997a3cd797b99643bc11
+Author: Bill Meek <billmeek(a)mythtv.org>
+Date: Tue Jun 4 11:02:03 2019 -0500
+
+ mythwelcome: Final --override-settings fix
+
+ This adds to a previous commit and allows libCECEnabled=0 to take
+ affect. Needed to be done before the Context was initialized.
+
+ Thanks to trx-913 on the Forum for testing both fixes.
+
+ (cherry picked from commit ee893973bb9f4b1e3c0803917ed1850061fa054e)
+
+commit 8325b0780455cd7b90bc6e676b47d6a2aa32250c
+Author: Bill Meek <billmeek(a)mythtv.org>
+Date: Thu May 30 14:33:25 2019 -0500
+
+ mythwelcome: --override-settings choice(s) don't take affect
+
+ mythwelcome --override-settings is valid, but overrides weren't
+ being used. 139919a (2011) claimed to fix it, but never called
+ ApplySettingsOverride(). Applies to override-settings-file too.
+
+ Thanks to trx-913 on the Forum for spotting this.
+
+ (cherry picked from commit 807c09b44a970b95cfb04d2112deb44f717f0d36)
+
+commit 6bd8cd499382fd8b132218274fb4ae326c2b0243
+Author: Klaas de Waal <klaas(a)modu.home.lan>
+Date: Thu May 30 10:26:43 2019 +0200
+
+ Remove m_ in class member names for backport to fixes-30
+
+ Refs #13415
+
+commit 57ffe65dbc1a80b0f4289f7c6a2b8f2e73e2cf51
+Author: Simon Hyde <simon(a)icedrop.net>
+Date: Tue Feb 26 21:32:19 2019 +0000
+
+ Prevent data corruption in dvbchannel.cpp causing hang
+
+ m_is_open was being corrupted by multiple simultaneous modifications,
+ leading to it managing to create infinite loops within its underlying
+ data structure. This patch extends the locking of m_hw_lock to cover
+ all uses of m_is_open, preventing issues.
+
+commit a32ec4bdea1380ecf587c717bc882cbe9469e5c7
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Tue Apr 30 14:10:13 2019 -0400
+
+ Fix mythgame configuration with minizip 2.8.6
+
+ Minizip apparently moved its header files in/before version 2.0.
+ Update the mythplugins configure script to look in both the old and
+ new locations.
+
+ Fixes #13453.
+
+commit fc79822ce4c8775361053d1bdd8d9dce2dc44ec1
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Tue Apr 30 13:32:08 2019 -0400
+
+ Fix compilation with exiv2 > 0.27.0.
+
+ Remove an old workaround that was introduced so that MythTV would
+ continue to compile after the introduction of exiv2 version 0.21. The
+ exiv2 file this workaround required was removed in 0.27.1.
+
+ MythTV now requires upon exiv2 0.21 or better.
+
+ Fixes #13452.
+
+commit f92255ce0620978cb0fffecd37832df3d6c56565
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Fri Apr 26 12:40:56 2019 -0400
+
+ Playback: Fix DB Update that caused bookmark key bindings to be lost
+
+ The database update is clearing the key binding before it can be
+ converted. This fixes it for people upgrading from 29.
+
+ (cherry picked from commit cdb895e92e30c48b62498e262ecff50e1adcede6)
+
+commit 30a59af73f16c7b97c4704a721b3a366b5b96175
+Author: David Engel <dengel(a)mythtv.org>
+Date: Thu Apr 25 09:35:24 2019 -0500
+
+ Account for very, large pre-roll values in AssignGroupInput.
+
+ Refs #13423
+
+ (cherry picked from commit b71875f16c156abeb347e6035c8b7bb72c457904)
+
+commit 97786a019daa7973207149d728a0b9da978bec1f
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Fri Apr 19 19:04:20 2019 -0400
+
+ Lengthen timers in test_mythtimer.
+
+ This allows some slop for build systems being busy while the tests are
+ running, but still keeps the same ratio of the timers so the test
+ should still be valid.
+
+ (cherry picked from commit b3e9db8c06c90f81fb7a430679a14b4271e8dc7b)
+
+commit 0bd850a54244d81dd086556d5425890b44dc2018
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Fri Apr 19 16:13:43 2019 -0400
+
+ Add delay to handle race condition in FreeBSD testing.
+
+ Calls to read the data output by the shell generate an EBADF error.
+ Apparently FreeBSD is fast enough that the file descriptors have often
+ been closed and cleaned up before the test code tries to read from
+ them. Add a one second delay after output so that stdout and stderr
+ can be read consistently.
+
+ (cherry picked from commit 1593c82103b10c4243e18de00469c5881e9399cb)
+
+commit d41d45ed6f50620a8836580c97bfd6fa25214633
+Author: David Engel <dengel(a)mythtv.org>
+Date: Mon Apr 15 15:10:39 2019 -0500
+
+ Enable Schedule as Group by default.
+
+ This also includes the case when the maximum recordings is set to 1.
+ Both the core feature scheduling feature and the on-demand addition of
+ inputs feature are stable and and should be enabled by default for all
+ users.
+
+ (cherry picked from commit 895daa592802ea3b16d8f1e583d69cc8fea5f2c3)
+
+commit b61283d7d1ffcdb17a909216220283e005e52a47
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Wed Apr 24 09:51:35 2019 -0400
+
+ Restore code that fixes up key bindings.
+
+ This partially reverts commit 453178430d86f7fdf4030cc1a76d7a135df60aa0.
+
+ (cherry picked from commit 3be5b080f8fb447645ff3ddcf174aa998108a41b)
+
+commit 5a67176bf907ca0686a7bd44c791a7003a544cfc
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sun Apr 14 16:35:42 2019 -0400
+
+ android: Fix for android OpenGL ES failures
+
+ Android devices that do not support high precision were failing
+ on an OpenGL error due to no precision specifiction. The result
+ was no GUI display, just a black screen.
+
+ (cherry picked from commit b60b8678904bb36349a97ce3e8e7370f8c9c8a13)
+
+commit 4d995941851b277b6365fed4ea003b8706432c2d
+Author: Dag Nygren <dag(a)newtech.fi>
+Date: Mon Apr 8 11:33:42 2019 -0400
+
+ Update Finish EIT fixups.
+
+ 1) Remove 'Film' or 'Elokuva' at the start of titles. 2) Remove age
+ limit at the end of the title.
+
+ Fixes #13215.
+
+ Signed-off-by: David Hampton <mythtv(a)love2code.net>
+ (cherry picked from commit 753a062b10d93485830a4dbf7a52d8336e2edf96)
+
+commit 09393940d6b448d74fcf5f983b21641f972a38bd
+Author: Bill Meek <billmeek(a)mythtv.org>
+Date: Sun Apr 7 11:01:24 2019 -0500
+
+ WebFrontend: Fix TV->Program Search (by Channel)
+
+ Only a single channel is presented with v30. Commit 63a5a20e
+ added extra filtering options to Channel/GetChannelInfoList.
+ Adjusted programsearch.qsp file to account for this.
+
+ Bug reported by user tekdoc on the Forum and thanks for testing
+ the fix too.
+
+ (cherry picked from commit 56cb2e4db98d96380258334cea8f7911363cb603)
+
+commit 3f494f57e27fef4285e094e919d6f795a8583e33
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Tue Apr 2 22:56:37 2019 -0400
+
+ Fix "in progress" check in mythplayer.
+
+ The clang-tidy "implicit boolean conversion" check pointed out a
+ couple of nonsensical implicit conversion from TVState to boolean.
+ Fix the code to properly test for one of two TV watching states.
+
+ Fixes #13442.
+
+
https://clang.llvm.org/extra/clang-tidy/checks/readability-implicit-bool-...
+ (cherry picked from commit 0bf0880d3e7ff0f78d1b90953e1f48618421e62d)
+
+commit 8e50fcf60bf9aaaddb5c8dbca4c957a0117d62c9
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Thu Mar 28 08:55:36 2019 -0400
+
+ Fix timeout on mediaserver announcements.
+
+ The socket code was converted in 2009 from taking a boolean to indicate
+ a 'short' timeout, to taking an actual timeout in milliseconds. Fix
+ one location that didn't get converted.
+
+ Fixes #13439.
+
+ (cherry picked from commit 66d8960da3e47023344a4caa1edaa37eff6f64b8)
+
+commit 3af3cad3d74735a561ab594910a3400b2472645c
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Mon Mar 25 19:32:11 2019 -0400
+
+ tidy: Fix memory leak in HLSSegment::DecodeData.
+
+ The clang-tidy "memory leak" checker pointed out that the
+ RecorderBase::CheckForRingBufferSwitch function would leak the memory
+ pointed to by the 'recq' variable if the 'm_tvrec' variable contains
+ nullptr. Add a line to free the memory pointed to by the 'recq'
+ variable in this case.
+
+ Fixes #13438.
+
+ (cherry picked from commit e6a39281b112fb03ef2f804c13bc6ebe9d336c90)
+
+commit 0999fd0ff08d21bf7873daf78fe9e453925cd83d
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Sun Mar 24 14:51:17 2019 -0400
+
+ tidy: Fix test to retrieve picture from FLAC file.
+
+ The clang-tidy "call and message" check pointed out the inevitability
+ of calling a function through a nullptr. The getPictureFromFile
+ function was testing the wrong variable when determining whether or
+ not a picture was the requested type. While there, make a variable
+ 'auto' to tighten up the code.
+
+ Fixes #13437.
+
+ (cherry picked from commit 6a21dd100e453d24437e980ef01c5525a53efbc1)
+
+commit ff4c7158f4fb241760e77fad7c2a8eef1a4eb5c3
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Mon Mar 25 14:02:11 2019 -0400
+
+ tidy: Fix rare null pointer dereference in cc707decoder.cpp.
+
+ The clang-tidy "non-null parameter checker" pointed out to possibility
+ of the cc708 decoder dereferencing through a null pointer. This code
+ starts with a default buffer size of 512 and then attempts to allocate
+ a larger buffer when necessary (in this case, if a single subtitle is
+ more than 512 characters.) If the reallocation fails, the code would
+ end up storing the nullptr error return into the buffer pointer, and
+ then dereference it an an attempt to store the new characters. Catch
+ this rare condition and drop the characters that won't fit into the
+ existing buffer.
+
+ Fixes #13436.
+
+ (cherry picked from commit d0356aba3c9394200a86dcab7d1cf17a558c5eab)
+
+commit 094e0d0c8038dac291dc4634e268c5fdc9dc0502
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Mon Mar 25 17:43:51 2019 -0400
+
+ tidy: Fix memory leak in HLSSegment::DecodeData.
+
+ The clang-tidy "memory leak" checker pointed out that
+ HLSSegment::DecodeData function would leak memory if it read an
+ invalid padding size from the end of the buffer. Add a line to free
+ the temporary buffer in this error condition.
+
+ Fixes #13435.
+
+ (cherry picked from commit 221e867bcbf5a79ff5f68eb5d30aa78538a2dbfb)
+
+commit 81081957a0cbb83e7419269129b4babfe306a8ac
+Author: Yianni Vidalis <yiannividalis(a)hotmail.com>
+Date: Wed Mar 20 12:50:31 2019 -0400
+
+ EIT fixes/enhancements for greek TV. (dvb-s and dvb-t)
+
+ Fixes #13426.
+
+ Signed-off-by: David Hampton <mythtv(a)love2code.net>
+ (cherry picked from commit 2887266263db0ed423d697ea2d7e74915e2a4c68)
+
+commit df4b1b78ae4e6d551b9a5ce07a0af33d436b96fd
+Author: David Engel <dengel(a)mythtv.org>
+Date: Tue Mar 19 14:05:09 2019 -0500
+
+ Include episode information in Scheduler-generated ProgramInfos.
+
+ Also, include the generic, episode indication when writing to the
+ oldrecorded table.
+
+ Refs #13425
+
+ (cherry picked from commit f9794e0ac7d4214599bb9b43f77b46c8dfcfa8fc)
+
+commit 363d00440163d55456bcb2eb353eb8a3ac9dc93d
+Author: John Poet <jpoet(a)mythtv.org>
+Date: Tue Feb 26 13:57:12 2019 -0700
+
+ mythexternrecorder: Allow config to pass logging options.
+
+ (cherry picked from commit 5b63846899946c0c150d1bf3ca4c90289293ca9f)
+
+commit 068e7e1958c46bcce65cca71603eeb537dbd9a1a
+Author: John Poet <jpoet(a)mythtv.org>
+Date: Fri Feb 1 20:33:46 2019 -0700
+
+ ChannelUtil::GetChannelDatast: Make sure results are more predictable.
+
+
+ Order results so lower numbered visible channels are prefered. If no
+ visible channels are available, then return an invisible one.
+
+ (cherry picked from commit cf7e9cc8293719ee7d726dfa44b2501e5a38f80a)
+
+commit 2327369fd558fcf00af6a280017560801534b44d
+Author: John Poet <jpoet(a)mythtv.org>
+Date: Tue Feb 5 14:04:00 2019 -0700
+
+ RemoteFile::Read: Try and "resume" if read fails.
+
+
+ When a remote frontend is paused for more than 5 minutes, the "remotefile"
+ connection can end up in a state where no data is returned. Attempt to use
+ RemoteFile::Resume to reestablish the connection.
+
+ (cherry picked from commit b7b737044eff2fff1090c582c1bf3bd61f6c2f26)
+
+commit 01cde24f00573a44f7c62f8ddf6cdcccb0a865a8
+Author: John Poet <jpoet(a)mythtv.org>
+Date: Mon Feb 4 15:14:50 2019 -0700
+
+ ExternalStreamhandler: Don't spam log files due to very low bitrate streams
+
+
+ (cherry picked from commit fa86a6a2935c305c97fa75fcd8fd82fe25eae66a)
+
+commit 60cfb7c05ae21d0049537aaa66f8f70a836a1138
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sun Mar 10 15:06:41 2019 -0400
+
+ Fix compile error from bceea69806d
+
+ Rename of member variables cause the cherry-pick to use the old
+ variable name.
+
+commit 76ea9755b69cad0eb6595407a7dec3c63df9f8c5
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Wed Mar 6 13:45:46 2019 -0500
+
+ Playback: Fix jerkiness with codec-deinterlaced content
+
+ Remove unneeded setting of m_fps that was overwriting the correct
+ value. m_fps is suppsed to contain the framerate before deinterlacing.
+
+ Fixes #13419
+
+ (cherry picked from commit 0e7e57f9c2eb0e2c4f47dcb30788a4e54d6808ba)
+
+commit 13f5fd83072a5b57c3aa3409d6db2038feb1f573
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Mon Mar 4 16:09:49 2019 -0500
+
+ Playback: Improve "Music Choice" detection
+
+ Some recordings could be incorrectly detected as "Music Choice",
+ resulting in audio-video sync problems. This improves detection to
+ reduce the likelihood of that.
+
+ (cherry picked from commit e0534ab0964bbec10e52de62dce87aa3cf76165a)
+
+commit bceea69806d9d24fd1662edca23c2215bee2e8cf
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Mon Mar 4 15:53:39 2019 -0500
+
+ AVSync2: Use frame timestamps to determine elapsed play time
+
+ MythTV uses frame count to determine the elapsed time of a recording
+ for display, jumps, etc. This can be inaccurate with variable rate
+ content and also with frame doubling being done in the decoder.
+ This change recalculates the frame count based on the time code.
+ This ensures MythTV elapsed time will be accurate.
+
+ Fixes #13416
+
+ (cherry picked from commit 6a5fb07fa17d7a7da09bd8a7be0394c8e4948122)
+
+commit 90468ec49a0116068fffb11d799ae67e100ef1fe
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sun Feb 24 14:28:56 2019 -0500
+
+ Playback: AvSync2 fix fast forward/rewind
+
+ Fast forward and rewind were not handling frame timing correctly so that
+ they were dropping frames, resulting in no frames displayed in some cases.
+
+ (cherry picked from commit 4089bf1ff031224fa1fefe990ef376628ef7589e)
+
+commit 042c180902bcdcf58ac12cef45a9cf0e5f348912
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Thu Mar 7 14:38:13 2019 -0500
+
+ Previously Recorded List: Fix 2 bugs
+
+ 1. Time zone bug - In a USA time zone at 11 PM on tha last day of
+ the month you can see the following month listed, albeit with
+ no entries.
+
+ 2. Program Guide->select a program that has not been recorded, then
+ Menu->Schedule Info->Previously Recorded, then press left or right.
+ This causes a seg fault.
+
+ Fixes #13397
+ Fixes #13421
+
+ (cherry picked from commit c08b7ae0e7589dbe54d817a1d6296688e576d675)
+
+commit e3474f8afb7191d5593d5fa5baac24611842bbec
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Mon Feb 25 14:45:13 2019 -0500
+
+ Program Guide: Fix for current selection text not showing
+
+ With certain themes (e.g. Blue Abstract) the current selection program name
+ was not showing in the guide grid.
+
+ (cherry picked from commit d6252135d684a9f6c1aa486fd9b684025f3d23d7)
+
+commit 26034244e8b664f2ae0e74027653293625615a86
+Author: Roland Ernst <rcrernst(a)gmail.com>
+Date: Mon Feb 18 13:51:10 2019 -0600
+
+ Services API: Allow images/icons immediately after BE startup. Refs #13404
+
+ Register <QFileInfo> to QMetaType at backend startup.
+
+ Tested on master with:
+
+ curl --header Accept:Application/JSON --output /tmp/preview.out \
+ --silent localhost:6544/Content/GetPreviewImage?RecordedId=<validRID>
+
+ curl --header Accept:Application/JSON --output /tmp/icon.out \
+ --silent localhost:6544/Guide/GetChannelIcon?ChanId=<validChanID>
+
+ Also, without --header ... for XML responses.
+
+ Required because Services need this type already registered. Without this
+ fix expect:
+
+ JSON: {'': ''}
+ XML: <?xml version="1.0"
encoding="UTF-8"?><></>
+
+ Thanks Roland.
+
+ Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+ (cherry picked from commit f7b5fd704be1e75071f8452b669d303ab053a245)
+
+commit 70a58c0a38c637657f74f28ad86e04a157163f57
+Author: Klaas de Waal <klaas.de.waal(a)gmail.com>
+Date: Sun Feb 17 13:35:42 2019 -0600
+
+ Don't leave HDHomeRun channels open after EIT scans.
+
+ The HDHomeRun unlocks the tuner due to inactivity but MythTV still
+ thinks it's open. The result is the next recording or EIT scan fails.
+ The fix is to always close the channel immediately after finishing
+ with it.
+
+ Refs #13407
+
+ Signed-off-by: David Engel <dengel(a)mythtv.org>
+ (cherry picked from commit e7e2270d68838bf72c8a7a12a1f027905a12e199)
+
+commit 5100ef5870eed4012e02c0d62d4adb1f20c0f312
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Fri Feb 15 14:50:29 2019 -0500
+
+ AVSync2: Improve resync speed and accuracy
+
+ 1. If out of sync by more than 200 ms fix the sync more quickly.
+ 2. Measure audio and video timecode at the same instant.
+ 3. Base audio / video adjustment on the prior frame timecodes.
+ 4. When there is excess audio (more than 200 ms) discard it.
+ 5. No longer limit adjustments to when error is over 40 ms.
+
+ Fixes #13383
+
+ (cherry picked from commit 6b402ca5a3b8c45c9ee190aaaf0e63a0d5548855)
+
+commit f0644ebd2a33f096dc1b8d97b36a22804d913c5f
+Author: Peter Bennett <pbennett(a)mythtv.org>
+Date: Fri Feb 15 14:49:37 2019 -0500
+
+ AVSync2: Fix never-ending stutter on Live TV
+
+ With AVSync2, Live TV could continue to stutter until pause
+ is pressed. Added a timecode reset so that it can add a small
+ delay to allow catch up.
+
+ (cherry picked from commit 90b4ec347fd01e40f97427de8ab261f74e9b2d68)
+
+commit f7b66345c5da82915e9db25817c59f42182766ab
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Sat Feb 2 14:42:40 2019 -0500
+
+ Update custom FreeBSD type declarations in videodev2.h.
+
+ (cherry picked from commit 209c15535056407ffd9af9fbf557b0f1c98dad8f)
+
+commit 7c0dc9b25906e0bdf9f6cb878268c9d0fc2316bb
+Author: David Hampton <mythtv(a)love2code.net>
+Date: Fri Jan 25 15:20:30 2019 -0500
+
+ Fix spurious font mismatch errors.
+
+ Strip out any text between square brackets before comparing font
+ names. This will allow a font family name like "Droid Sans [MONO]" to
+ match the request for "Droid Sans".
+
+ Fixes #13385.
+
+ (cherry picked from commit 60a1db79f36e4f0734c79c4783864340d4e695bf)
+
commit b774c4140b2b9e148ccf699d1ad746bc4c84289c
Author: Peter Bennett <pbennett(a)mythtv.org>
Date: Sun Feb 10 12:17:53 2019 -0500
diff --git a/mythtv-py3_configure.patch b/mythtv-py3_configure.patch
new file mode 100644
index 0000000..24dcdc9
--- /dev/null
+++ b/mythtv-py3_configure.patch
@@ -0,0 +1,21 @@
+--- a/mythtv/configure
++++ b/mythtv/configure
+@@ -6958,7 +6958,7 @@ fi
+
+ # Check for python dependencies
+ if enabled bindings_python; then
+- is_python3 && python=python2
++ is_python3 || python=python2
+ check_python || disable_bindings_python "Python 2.6"
+ check_py_lib MySQLdb || disable_bindings_python "MySQLdb"
+ check_py_lib lxml || disable_bindings_python "lxml"
+--- a/mythplugins/configure
++++ b/mythplugins/configure
+@@ -669,7 +669,6 @@ if enabled netvision; then
+ check_py_lib lxml || disable_netvision "Python lxml library (lxml)"
+ check_py_lib xml || disable_netvision "Python XML library (xml)"
+ check_py_lib urllib || disable_netvision "Python URL library (urllib)"
+- check_py_lib urllib2 || disable_netvision "Python URL library 2
(urllib2)"
+ check_py_lib oauth || disable_netvision "Python OAuth library (oauth)"
+
+ disabled netvision && echo "Disabling MythNetvision due to missing
dependencies."
diff --git a/mythtv-py3_urllib.patch b/mythtv-py3_urllib.patch
new file mode 100644
index 0000000..6720628
--- /dev/null
+++ b/mythtv-py3_urllib.patch
@@ -0,0 +1,592 @@
+--- mythtv/programs/scripts/internetcontent/nv_python_libs/mtv/mtv_api.py (original)
++++ mythtv/programs/scripts/internetcontent/nv_python_libs/mtv/mtv_api.py (refactored)
+@@ -37,7 +37,7 @@
+
+ import os, struct, sys, re, time
+ from datetime import datetime, timedelta
+-import urllib, urllib2
++import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error,
urllib.parse
+ import logging
+
+ try:
+@@ -84,7 +84,7 @@
+
+ def _grabUrl(self, url):
+ try:
+- urlhandle = urllib.urlopen(url)
++ urlhandle = urllib.request.urlopen(url)
+ except IOError, errormsg:
+ raise MtvHttpError(errormsg)
+ return urlhandle.read()
+@@ -355,7 +355,7 @@
+ return an array of matching item dictionaries
+ return
+ '''
+- url = self.config[u'urls'][u'video.search'] %
(urllib.quote_plus(title.encode("utf-8")), pagenumber , pagelen,)
++ url = self.config[u'urls'][u'video.search'] %
(urllib.parse.quote_plus(title.encode("utf-8")), pagenumber , pagelen,)
+ if self.config['debug_enabled']:
+ print url
+ print
+@@ -403,7 +403,7 @@
+
+ video_details = None
+ try:
+- video_details = self.videoDetails(item['id'],
urllib.quote(item['title'].encode("utf-8")))
++ video_details = self.videoDetails(item['id'],
urllib.parse.quote(item['title'].encode("utf-8")))
+ except MtvUrlError, msg:
+ sys.stderr.write(self.error_messages['MtvUrlError'] % msg)
+ except MtvVideoDetailError, msg:
+@@ -752,7 +752,7 @@
+ metadata['video'] = metadata['link']
+ else:
+ index = metadata['video'].rindex(u':')
+- metadata['video'] = self.mtvHtmlPath %
(urllib.quote(metadata['title'].encode("utf-8")),
metadata['video'][index+1:])
++ metadata['video'] = self.mtvHtmlPath %
(urllib.parse.quote(metadata['title'].encode("utf-8")),
metadata['video'][index+1:])
+ metadata['link'] = metadata['video']
+ # !! This tag will need to be added at a later date
+ # metadata['customhtml'] = u'true'
+---
mythtv/programs/scripts/internetcontent/nv_python_libs/youtube/youtube_api.py (original)
++++
mythtv/programs/scripts/internetcontent/nv_python_libs/youtube/youtube_api.py (refactored)
+@@ -37,7 +37,7 @@
+ # 0.3.0 Adapted to the v3 API
+
+ import os, struct, sys, re, time, shutil
+-import urllib, urllib2
++import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error,
urllib.parse
+ import json
+ import logging
+ from MythTV import MythXML
+@@ -61,7 +61,7 @@
+
+ def getJson(self):
+ try:
+- urlhandle = urllib.urlopen(self.url)
++ urlhandle = urllib.request.urlopen(self.url)
+ return json.load(urlhandle)
+ except IOError, errormsg:
+ raise YouTubeHttpError(errormsg)
+@@ -246,7 +246,7 @@
+ def getExternalIP():
+ '''Find the external IP address of this computer.
+ '''
+- url = urllib.URLopener()
++ url = urllib.request.URLopener()
+ try:
+ resp =
url.open('http://www.whatismyip.com/automation/n09230945.asp')
+ return resp.read()
+@@ -260,15 +260,15 @@
+ return {}
+
+ try:
+- gs =
urllib.urlopen('http://blogama.org/ip_query.php?ip=%s&output=xml&... % ip)
++ gs =
urllib.request.urlopen('http://blogama.org/ip_query.php?ip=%s&out... %
ip)
+ txt = gs.read()
+ except:
+ try:
+- gs =
urllib.urlopen('http://www.seomoz.org/ip2location/look.php?ip=%s' % ip)
++ gs =
urllib.request.urlopen('http://www.seomoz.org/ip2location/look.php?ip... % ip)
+ txt = gs.read()
+ except:
+ try:
+- gs = urllib.urlopen('http://api.hostip.info/?ip=%s' % ip)
++ gs = urllib.request.urlopen('http://api.hostip.info/?ip=%s'
% ip)
+ txt = gs.read()
+ except:
+ logging.error('GeoIP servers not available')
+@@ -403,7 +403,7 @@
+ url = ('https://www.googleapis.com/youtube/v3/search?part=snippet&'
+ \
+ 'type=video&q=%s&maxResults=%s&order=relevance&'
+ \
+ 'videoEmbeddable=true&key=%s&pageToken=%s') % \
+- (urllib.quote_plus(title.encode("utf-8")), pagelen,
self.apikey,
++ (urllib.parse.quote_plus(title.encode("utf-8")), pagelen,
self.apikey,
+ pagenumber)
+ if self.config['debug_enabled']:
+ print url
+---
mythtv/programs/scripts/internetcontent/nv_python_libs/dailymotion/dailymotion_api.py (original)
++++
mythtv/programs/scripts/internetcontent/nv_python_libs/dailymotion/dailymotion_api.py (refactored)
+@@ -35,7 +35,7 @@
+
+
+ import os, struct, sys, re, time
+-import urllib, urllib2
++import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error,
urllib.parse
+ import logging
+ from MythTV import MythXML
+
+@@ -83,7 +83,7 @@
+
+ def _grabUrl(self, url):
+ try:
+- urlhandle = urllib.urlopen(url)
++ urlhandle = urllib.request.urlopen(url)
+ except IOError, errormsg:
+ raise DailymotionHttpError(errormsg)
+ return urlhandle.read()
+@@ -556,7 +556,7 @@
+ def getExternalIP():
+ '''Find the external IP address of this computer.
+ '''
+- url = urllib.URLopener()
++ url = urllib.request.URLopener()
+ try:
+ resp =
url.open('http://www.whatismyip.com/automation/n09230945.asp')
+ return resp.read()
+@@ -570,15 +570,15 @@
+ return {}
+
+ try:
+- gs =
urllib.urlopen('http://blogama.org/ip_query.php?ip=%s&output=xml&... % ip)
++ gs =
urllib.request.urlopen('http://blogama.org/ip_query.php?ip=%s&out... %
ip)
+ txt = gs.read()
+ except:
+ try:
+- gs =
urllib.urlopen('http://www.seomoz.org/ip2location/look.php?ip=%s' % ip)
++ gs =
urllib.request.urlopen('http://www.seomoz.org/ip2location/look.php?ip... % ip)
+ txt = gs.read()
+ except:
+ try:
+- gs = urllib.urlopen('http://api.hostip.info/?ip=%s' % ip)
++ gs = urllib.request.urlopen('http://api.hostip.info/?ip=%s'
% ip)
+ txt = gs.read()
+ except:
+ logging.error('GeoIP servers not available')
+@@ -709,7 +709,7 @@
+ return an array of matching item dictionaries
+ return
+ '''
+- url = self.config[u'urls'][u'video.search'] %
(urllib.quote_plus(title.encode("utf-8")), pagenumber)
++ url = self.config[u'urls'][u'video.search'] %
(urllib.parse.quote_plus(title.encode("utf-8")), pagenumber)
+ if self.config['debug_enabled']:
+ print url
+ print
+--- mythtv/programs/scripts/internetcontent/nv_python_libs/vimeo/vimeo_api.py (original)
++++
mythtv/programs/scripts/internetcontent/nv_python_libs/vimeo/vimeo_api.py (refactored)
+@@ -55,7 +55,7 @@
+ Python module to interact with Vimeo through its API (version 2)
+ """
+ import os, struct, sys, re, time, datetime
+-import urllib, urllib2
++import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error,
urllib.parse
+ import logging
+ import pycurl
+ import xml.etree.ElementTree as ET
+@@ -908,7 +908,7 @@
+ # print xml_data
+
+ try:
+- xml_data =
self.client.vimeo_videos_search(urllib.quote_plus(title.encode("utf-8")),
++ xml_data =
self.client.vimeo_videos_search(urllib.parse.quote_plus(title.encode("utf-8")),
+ sort='most_liked',
+ per_page=pagelen,
+ page=pagenumber)
+---
mythtv/programs/scripts/internetcontent/nv_python_libs/bliptv/bliptv_api.py (original)
++++
mythtv/programs/scripts/internetcontent/nv_python_libs/bliptv/bliptv_api.py (refactored)
+@@ -38,7 +38,7 @@
+ # Removed a subdirectory level as the "Featured" RSS feed has been
discontinued
+
+ import os, struct, sys, re, time
+-import urllib, urllib2
++import urllib.request, urllib.parse, urllib.error, urllib.request, urllib.error,
urllib.parse
+ import logging
+ from MythTV import MythXML
+
+@@ -86,7 +86,7 @@
+
+ def _grabUrl(self, url):
+ try:
+- urlhandle = urllib.urlopen(url)
++ urlhandle = urllib.request.urlopen(url)
+ except IOError, errormsg:
+ raise BliptvHttpError(errormsg)
+ return urlhandle.read()
+@@ -269,7 +269,7 @@
+ def getExternalIP():
+ '''Find the external IP address of this computer.
+ '''
+- url = urllib.URLopener()
++ url = urllib.request.URLopener()
+ try:
+ resp =
url.open('http://www.whatismyip.com/automation/n09230945.asp')
+ return resp.read()
+@@ -283,15 +283,15 @@
+ return {}
+
+ try:
+- gs =
urllib.urlopen('http://blogama.org/ip_query.php?ip=%s&output=xml&... % ip)
++ gs =
urllib.request.urlopen('http://blogama.org/ip_query.php?ip=%s&out... %
ip)
+ txt = gs.read()
+ except:
+ try:
+- gs =
urllib.urlopen('http://www.seomoz.org/ip2location/look.php?ip=%s' % ip)
++ gs =
urllib.request.urlopen('http://www.seomoz.org/ip2location/look.php?ip... % ip)
+ txt = gs.read()
+ except:
+ try:
+- gs = urllib.urlopen('http://api.hostip.info/?ip=%s' % ip)
++ gs = urllib.request.urlopen('http://api.hostip.info/?ip=%s'
% ip)
+ txt = gs.read()
+ except:
+ logging.error('GeoIP servers not available')
+@@ -422,7 +422,7 @@
+ return an array of matching item dictionaries
+ return
+ '''
+- url = self.config[u'urls'][u'video.search'] %
(urllib.quote_plus(title.encode("utf-8")), pagenumber, pagelen,
self.config['language'])
++ url = self.config[u'urls'][u'video.search'] %
(urllib.parse.quote_plus(title.encode("utf-8")), pagenumber, pagelen,
self.config['language'])
+
+ if self.config['debug_enabled']:
+ print "Search URL:"
+--- mythtv/programs/scripts/hardwareprofile/request.py (original)
++++ mythtv/programs/scripts/hardwareprofile/request.py (refactored)
+@@ -21,15 +21,15 @@
+ # providing the base url, user agent, and proxy information.
+ # The object returned is slightly modified, with a shortcut to urlopen.
+
+-import urllib2
++import urllib.request, urllib.error, urllib.parse
+ import urlparse
+
+-class _Request( urllib2.Request ):
++class _Request( urllib.request.Request ):
+ timeout = None
+ def open(self):
+ if self.timeout:
+- return urllib2.urlopen(self, None, self.timeout)
+- return urllib2.urlopen(self)
++ return urllib.request.urlopen(self, None, self.timeout)
++ return urllib.request.urlopen(self)
+
+ class _RequestFactory( object ):
+ def __init__(self, baseurl, user_agent, timeout, proxy):
+--- mythtv/programs/scripts/hardwareprofile/smolt.py (original)
++++ mythtv/programs/scripts/hardwareprofile/smolt.py (refactored)
+@@ -42,8 +42,8 @@
+ import os
+ from urlparse import urljoin
+ from urlparse import urlparse
+-from urllib import urlencode
+-import urllib
++from urllib.parse import urlencode
++import urllib.request, urllib.parse, urllib.error
+ import json
+ from json import JSONEncoder
+ import datetime
+@@ -59,7 +59,7 @@
+ from logging.handlers import RotatingFileHandler
+ import codecs
+ import MultipartPostHandler
+-import urllib2
++import urllib.request, urllib.error, urllib.parse
+
+ try:
+ import subprocess
+@@ -730,7 +730,7 @@
+ request_url = urljoin(smoonURL + "/", entry_point, False)
+ logging.debug('Sending request to %s' % request_url)
+ try:
+- opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
++ opener =
urllib.request.build_opener(MultipartPostHandler.MultipartPostHandler)
+ params = { 'uuid':self.host.UUID,
+ 'host':serialized_host_obj_machine,
+ 'token':tok,
+--- mythtv/programs/scripts/hardwareprofile/distros/mythtv_data/request.py (original)
++++ mythtv/programs/scripts/hardwareprofile/distros/mythtv_data/request.py (refactored)
+@@ -21,15 +21,15 @@
+ # providing the base url, user agent, and proxy information.
+ # The object returned is slightly modified, with a shortcut to urlopen.
+
+-import urllib2
++import urllib.request, urllib.error, urllib.parse
+ import urlparse
+
+-class _Request( urllib2.Request ):
++class _Request( urllib.request.Request ):
+ timeout = None
+ def open(self):
+ if self.timeout:
+- return urllib2.urlopen(self, None, self.timeout)
+- return urllib2.urlopen(self)
++ return urllib.request.urlopen(self, None, self.timeout)
++ return urllib.request.urlopen(self)
+
+ class _RequestFactory( object ):
+ def __init__(self, baseurl, user_agent, timeout, proxy):
+--- mythtv/programs/scripts/hardwareprofile/MultipartPostHandler.py (original)
++++ mythtv/programs/scripts/hardwareprofile/MultipartPostHandler.py (refactored)
+@@ -41,8 +41,8 @@
+ then uploads it to the W3C validator.
+ """
+
+-import urllib
+-import urllib2
++import urllib.request, urllib.parse, urllib.error
++import urllib.request, urllib.error, urllib.parse
+ import mimetools, mimetypes
+ import os, stat
+ from cStringIO import StringIO
+@@ -55,8 +55,8 @@
+ # assigning a sequence.
+ doseq = 1
+
+-class MultipartPostHandler(urllib2.BaseHandler):
+- handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
++class MultipartPostHandler(urllib.request.BaseHandler):
++ handler_order = urllib.request.HTTPHandler.handler_order - 10 # needs to run first
+
+ def http_request(self, request):
+ data = request.get_data()
+@@ -74,7 +74,7 @@
+ raise TypeError, "not a valid non-string sequence or mapping
object", traceback
+
+ if len(v_files) == 0:
+- data = urllib.urlencode(v_vars, doseq)
++ data = urllib.parse.urlencode(v_vars, doseq)
+ else:
+ boundary, data = self.multipart_encode(v_vars, v_files)
+
+@@ -118,7 +118,7 @@
+ import tempfile, sys
+
+ validatorURL = "http://validator.w3.org/check"
+- opener = urllib2.build_opener(MultipartPostHandler)
++ opener = urllib.request.build_opener(MultipartPostHandler)
+
+ def validateFile(url):
+ temp = tempfile.mkstemp(suffix=".html")
+--- mythtv/programs/scripts/metadata/Music/lyrics/lyricswiki.py (original)
++++ mythtv/programs/scripts/metadata/Music/lyrics/lyricswiki.py (refactored)
+@@ -1,5 +1,5 @@
+ #-*- coding: UTF-8 -*-
+-import sys, re, urllib2, socket, HTMLParser
++import sys, re, urllib.request, urllib.error, urllib.parse, socket, HTMLParser
+ from optparse import OptionParser
+
+ if sys.version_info < (2, 7):
+@@ -31,7 +31,7 @@
+ utilities.log(debug, "%s: searching lyrics for %s - %s - %s" %
(__title__, lyrics.artist, lyrics.album, lyrics.title))
+
+ try:
+- req = urllib2.urlopen(self.url % (urllib2.quote(lyrics.artist),
urllib2.quote(lyrics.title)))
++ req = urllib.request.urlopen(self.url % (urllib.parse.quote(lyrics.artist),
urllib.parse.quote(lyrics.title)))
+ response = req.read()
+ except:
+ return False
+@@ -44,9 +44,9 @@
+ if not self.page.endswith('action=edit'):
+ utilities.log(debug, "%s: search url: %s" % (__title__,
self.page))
+ try:
+- req = urllib2.urlopen(self.page)
++ req = urllib.request.urlopen(self.page)
+ response = req.read()
+- except urllib2.HTTPError, error: # strange... sometimes lyrics are returned
with a 404 error
++ except urllib.error.HTTPError, error: # strange... sometimes lyrics are
returned with a 404 error
+ if error.code == 404:
+ response = error.read()
+ else:
+--- mythtv/programs/scripts/metadata/Music/lyrics/genius.py (original)
++++ mythtv/programs/scripts/metadata/Music/lyrics/genius.py (refactored)
+@@ -5,8 +5,8 @@
+ taxigps
+ """
+ import sys
+-import urllib
+-import urllib2
++import urllib.request, urllib.parse, urllib.error
++import urllib.request, urllib.error, urllib.parse
+ import socket
+ import re
+ import chardet
+@@ -42,9 +42,9 @@
+ utilities.log(debug, "%s: searching lyrics for %s - %s - %s" %
(__title__, lyrics.artist, lyrics.album, lyrics.title))
+
+ try:
+- request = urllib2.Request(self.url % (urllib2.quote(lyrics.artist),
'%20', urllib2.quote(lyrics.title)))
++ request = urllib.request.Request(self.url %
(urllib.parse.quote(lyrics.artist), '%20', urllib.parse.quote(lyrics.title)))
+ request.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1;
rv:25.0) Gecko/20100101 Firefox/25.0')
+- req = urllib2.urlopen(request)
++ req = urllib.request.urlopen(request)
+ response = req.read()
+ except:
+ return False
+@@ -60,9 +60,9 @@
+ utilities.log(debug, "%s: search url: %s" % (__title__, self.page))
+
+ try:
+- request = urllib2.Request(self.page)
++ request = urllib.request.Request(self.page)
+ request.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1;
rv:25.0) Gecko/20100101 Firefox/25.0')
+- req = urllib2.urlopen(request)
++ req = urllib.request.urlopen(request)
+ response = req.read()
+ except:
+ return False
+--- mythtv/programs/scripts/metadata/Music/lyrics/darklyrics.py (original)
++++ mythtv/programs/scripts/metadata/Music/lyrics/darklyrics.py (refactored)
+@@ -6,7 +6,7 @@
+ """
+
+ import hashlib
+-import urllib2
++import urllib.request, urllib.error, urllib.parse
+ import re
+ import chardet
+ import sys
+@@ -29,10 +29,10 @@
+ self.searchUrl = "http://www.darklyrics.com/search?q=%term%"
+
+ def search(self, artist, title):
+- term = urllib2.quote((artist if artist else "") + " " +
(title if title else ""));
++ term = urllib.parse.quote((artist if artist else "") + " " +
(title if title else ""));
+
+ try:
+- request = urllib2.urlopen(self.searchUrl.replace("%term%", term))
++ request = urllib.request.urlopen(self.searchUrl.replace("%term%",
term))
+ searchResponse = request.read();
+ except:
+ return None
+@@ -59,7 +59,7 @@
+
+ def findLyrics(self, url, index):
+ try:
+- request = urllib2.urlopen(url);
++ request = urllib.request.urlopen(url);
+ res = request.read();
+ except:
+ return None
+@@ -81,7 +81,7 @@
+
+ def getAlbumName(self, url):
+ try:
+- request = urllib2.urlopen(url);
++ request = urllib.request.urlopen(url);
+ res = request.read();
+ except:
+ return "";
+--- mythtv/programs/scripts/metadata/Music/lyrics/lyricscom.py (original)
++++ mythtv/programs/scripts/metadata/Music/lyrics/lyricscom.py (refactored)
+@@ -7,8 +7,8 @@
+
+ import sys
+ import re
+-import urllib
+-import urllib2
++import urllib.request, urllib.parse, urllib.error
++import urllib.request, urllib.error, urllib.parse
+ import socket
+ import difflib
+ from optparse import OptionParser
+@@ -39,7 +39,7 @@
+ return False
+
+ try:
+- request = urllib2.urlopen(self.url % urllib.quote_plus(lyrics.artist))
++ request = urllib.request.urlopen(self.url %
urllib.parse.quote_plus(lyrics.artist))
+ response = request.read()
+ except:
+ return False
+@@ -54,7 +54,7 @@
+ if url:
+ utilities.log(debug, "%s: Artist url is %s" % (__title__, url))
+ try:
+- req = urllib2.urlopen(url)
++ req = urllib.request.urlopen(url)
+ resp = req.read()
+ except:
+ return False
+@@ -70,7 +70,7 @@
+ utilities.log(debug, "%s: Song url is %s" % (__title__,
url))
+
+ try:
+- req2 = urllib2.urlopen(url)
++ req2 = urllib.request.urlopen(url)
+ resp2 = req2.read()
+ except:
+ return False
+--- mythtv/programs/scripts/metadata/Music/lyrics/alsong.py (original)
++++ mythtv/programs/scripts/metadata/Music/lyrics/alsong.py (refactored)
+@@ -6,7 +6,7 @@
+
+ import sys
+ import socket
+-import urllib2
++import urllib.request, urllib.error, urllib.parse
+ import difflib
+ import xml.dom.minidom as xml
+ from optparse import OptionParser
+@@ -49,8 +49,8 @@
+
+ try:
+ headers = {'Content-Type':'text/xml; charset=utf-8'}
+- request = urllib2.Request(ALSONG_URL, ALSONG_TMPL % (lyrics.title,
lyrics.artist), headers)
+- response = urllib2.urlopen(request)
++ request = urllib.request.Request(ALSONG_URL, ALSONG_TMPL % (lyrics.title,
lyrics.artist), headers)
++ response = urllib.request.urlopen(request)
+ Page = response.read()
+ except:
+ return False
+--- mythtv/programs/scripts/metadata/Music/musicbrainzngs/compat.py (original)
++++ mythtv/programs/scripts/metadata/Music/musicbrainzngs/compat.py (refactored)
+@@ -39,12 +39,11 @@
+
+ if is_py2:
+ from StringIO import StringIO
+- from urllib2 import HTTPPasswordMgr, HTTPDigestAuthHandler, Request,\
+- HTTPHandler, build_opener, HTTPError, URLError,\
+- build_opener
++ from urllib.request import HTTPPasswordMgr, HTTPDigestAuthHandler, Request,
HTTPHandler, build_opener, build_opener
++ from urllib.error import HTTPError, URLError
+ from httplib import BadStatusLine, HTTPException
+ from urlparse import urlunparse
+- from urllib import urlencode
++ from urllib.parse import urlencode
+
+ bytes = str
+ unicode = unicode
+--- mythtv/bindings/python/tmdb3/tmdb3/request.py (original)
++++ mythtv/bindings/python/tmdb3/tmdb3/request.py (refactored)
+@@ -14,10 +14,10 @@
+
+ # supports python2 and python3
+ try:
+- from urllib import urlencode
+- from urllib2 import Request as _py23Request
+- from urllib2 import urlopen as _py23urlopen
+- from urllib2 import HTTPError as _py23HTTPError
++ from urllib.parse import urlencode
++ from urllib.request import Request as _py23Request
++ from urllib.request import urlopen as _py23urlopen
++ from urllib.error import HTTPError as _py23HTTPError
+
+ except (NameError, ImportError):
+ from urllib.parse import urlencode
+--- mythtv/bindings/python/MythTV/connections.py (original)
++++ mythtv/bindings/python/MythTV/connections.py (refactored)
+@@ -17,7 +17,7 @@
+ import lxml.etree as etree
+ import weakref
+ try:
+- import urllib2
++ import urllib.request, urllib.error, urllib.parse
+ except ImportError:
+ import urllib.request as urllib2
+ import socket
+@@ -575,8 +575,8 @@
+ either 'backend' or 'port' is not defined.
+ """
+
+- class _Request( urllib2.Request ):
+- def open(self): return urllib2.urlopen(self)
++ class _Request( urllib.request.Request ):
++ def open(self): return urllib.request.urlopen(self)
+ def read(self): return self.open().read()
+
+ def setJSON(self):
+@@ -620,7 +620,7 @@
+ url = 'http://{0.host}:{0.port}/{1}'.format(self, path)
+ if keyvars:
+ url += '?' + '&'.join(
+- ['{0}={1}'.format(k,urllib2.quote(v))
++ ['{0}={1}'.format(k,urllib.parse.quote(v))
+ for k,v in keyvars.items()])
+ self.log(self.log.NETWORK, self.log.DEBUG, 'Generating request', url)
+ return self._Request(url)
diff --git a/mythtv-python3.patch b/mythtv-python3.patch
new file mode 100644
index 0000000..0483290
--- /dev/null
+++ b/mythtv-python3.patch
@@ -0,0 +1,3519 @@
+From 5a39bb25bfec2c0131fab04023c99ab89397c44b Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:23:43 +0200
+Subject: [PATCH 01/28] MythTV python: Class System.system: correct typo.
+
+When calling System.system, the following traceback bails:
+
+Traceback
+ File "MythTV/system.py", line 56, in system
+ command = command.lsplit(' ',1)
+AttributeError: 'str' object has no attribute 'lsplit'
+
+Note the typo: 'lsplit' --> 'split'.
+Python has [lr]strip methods, but provides only 'split()' and
'rsplit()'.
+---
+ mythtv/bindings/python/MythTV/system.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/system.py
b/mythtv/bindings/python/MythTV/system.py
+index 44803bd02b4..62f1e72c0f4 100644
+--- a/mythtv/bindings/python/MythTV/system.py
++++ b/mythtv/bindings/python/MythTV/system.py
+@@ -53,7 +53,7 @@ def wait(self):
+
+ @classmethod
+ def system(cls, command, db=None):
+- command = command.lsplit(' ',1)
++ command = command.split(' ',1)
+ path = command[0]
+ args = ''
+ if len(command) > 1:
+@@ -114,7 +114,7 @@ def __init__(self, path=None, setting=None, db=None, useshell=True,
prefix=''):
+ break
+ else:
+ raise MythFileError('Defined executable path does not exist.')
+-
++
+ self.returncode = 0
+ self.stderr = ''
+ self.useshell = useshell
+@@ -349,13 +349,13 @@ def _processMetadata(self, xml):
+
+ def command(self, *args):
+ return self._processMetadata(super(Grabber, self).command(*args))
+-
++
+ def search(self, phrase, subtitle=None, tolerance=None, func=None):
+ """
+ obj.search(phrase, subtitle=None, tolerance=None) -> result generator
+
+ Returns a generator of results matching the given search
+- phrase. A secondary phrase can be given through the
++ phrase. A secondary phrase can be given through the
+ 'subtitle' parameter, and an optional levenshtein
+ tolerance value can be given for filtering results.
+ """
+@@ -384,7 +384,7 @@ def search(self, phrase, subtitle=None, tolerance=None, func=None):
+
+ def sortedSearch(self, phrase, subtitle=None, tolerance=None):
+ """
+- Behaves like obj.search(), but sorts results based off
++ Behaves like obj.search(), but sorts results based off
+ levenshtein distance.
+ """
+ return sorted(self.search(phrase, subtitle, tolerance), \
+
+From ba782e53e501e75d5760c86da53f65e61170511a Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:28:46 +0200
+Subject: [PATCH 02/28] Update MythTV's python binding 'utility/dt.py'
+ according patch from Ticket #13299:
+ Handle-timezone-files-with-no-modern-transitions-as-well_fixes_30_master.patch
+
+Note: This handle the corner cases where the zone-info files have no
+ 'modern transitions' or no transitions at all.
+---
+ mythtv/bindings/python/MythTV/utility/dt.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/mythtv/bindings/python/MythTV/utility/dt.py
b/mythtv/bindings/python/MythTV/utility/dt.py
+index c4c128b1cb8..3f411406d73 100644
+--- a/mythtv/bindings/python/MythTV/utility/dt.py
++++ b/mythtv/bindings/python/MythTV/utility/dt.py
+@@ -163,6 +163,7 @@ def _process(self, fd, version=1, skip=False):
+ # files have massively negative leading entries for e.g. the
+ # big bang which gmtime() cannot cope with.
+ first_modern_transition = None
++ i = 0 # assign i, in case the for-loop is not executed:
+ for i in range(counts.transitions): # read in epoch time data
+ t = unpack(ttmfmt, fd.read(calcsize(ttmfmt)))[0]
+
+@@ -177,6 +178,10 @@ def _process(self, fd, version=1, skip=False):
+ if first_modern_transition is not None:
+ raise e
+
++ # Special case if there are no modern transitions, like e.g. UTC timezone:
++ if ( (i == 0) and first_modern_transition is None ):
++ first_modern_transition = counts.transitions
++
+ # read in transition type indexes
+ types = [None]*counts.transitions
+ for i in range(counts.transitions):
+
+From 7985289ca168875b12ce7975fa6063807defa1e5 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:32:15 +0200
+Subject: [PATCH 03/28] Correct typo in MythTV's python binding
+ datetime.fromRfc().
+
+When calling the method utility.datetime.fromRfc(), the following trace back occurs:
+Traceback (most recent call last):
+ File "test/test_datetime_001.py", line 232, in test_datetime_001_12
+ t_fromrfc = datetime.fromRfc(t)
+ File "MythTV/utility/dt.py", line 433, in fromRfc
+ return cls(*tz)
+TypeError: type object argument after * must be an iterable, not posixtzinfo
+
+Solution: Return the correct datetime object.
+---
+ mythtv/bindings/python/MythTV/utility/dt.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/utility/dt.py
b/mythtv/bindings/python/MythTV/utility/dt.py
+index 3f411406d73..9efa4e1e17a 100644
+--- a/mythtv/bindings/python/MythTV/utility/dt.py
++++ b/mythtv/bindings/python/MythTV/utility/dt.py
+@@ -430,7 +430,7 @@ def fromRfc(cls, rfctime, tz=None):
+ tz = cls.localTZ()
+ dt.append(tz)
+
+- return cls(*tz)
++ return cls(*dt)
+
+ @classmethod
+ def duck(cls, t):
+
+From ca9752841d990027df1502ee5d629db47aa29384 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:38:59 +0200
+Subject: [PATCH 04/28] The conversion to/from bool in DictData from altdict.py
+ does not work
+
+as originally assumed: The string '0' or '1' should be converted to
+ 'True' or 'False', but python interprets 'bool('x')' as
follows:
+
+$ python
+>>> bool('0')
+True
+>>> bool('1')
+True
+
+We need to convert the string to integer before castin to 'bool':
+
+$ python
+>>> bool(int('0'))
+False
+>>> bool(int('1'))
+True
+>>>
+---
+ mythtv/bindings/python/MythTV/altdict.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/altdict.py
b/mythtv/bindings/python/MythTV/altdict.py
+index 2c9a5e81c1f..ed41c2c0f4f 100644
+--- a/mythtv/bindings/python/MythTV/altdict.py
++++ b/mythtv/bindings/python/MythTV/altdict.py
+@@ -99,7 +99,7 @@ class DictData( OrdDict ):
+ _field_type = None
+ _trans = [ int,
+ locale.atof,
+- bool,
++ lambda x: bool(int(x)),
+ lambda x: x,
+ lambda x: datetime.fromtimestamp(x if x != '4294967295' else
'0', datetime.UTCTZ())\
+ .astimezone(datetime.localTZ()),
+
+From 6355f51ef38cd24e7f53261f22eee21799f34979 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:42:08 +0200
+Subject: [PATCH 05/28] Fix logging in Mythtv's python bindings msearch.py:
+
+Logging is defined with those parameters:
+MythLog.log(self, mask, level, message, detail=None)
+---
+ mythtv/bindings/python/MythTV/msearch.py | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/msearch.py
b/mythtv/bindings/python/MythTV/msearch.py
+index a5d1f3d1236..ca89a0928d4 100644
+--- a/mythtv/bindings/python/MythTV/msearch.py
++++ b/mythtv/bindings/python/MythTV/msearch.py
+@@ -30,7 +30,7 @@ def __init__(self):
+ port += 1
+ else:
+ raise MythError(MythError.SOCKET, e)
+- self.log(MythLog.DEBUG, MythLog.UPNP|MythLog.SOCKET,
++ self.log(MythLog.UPNP|MythLog.SOCKET, MythLog.DEBUG,
+ 'Port %d opened for UPnP search.' % port)
+ self.sock.setblocking(0.1)
+
+@@ -49,7 +49,8 @@ def search(self, timeout=5.0, filter=None):
+ content-length, request, date, usn, location,
+ cache-control, server, ext, st
+ """
+- self.log(MythLog.DEBUG, MythLog.UPNP, 'running UPnP search')
++ self.log(MythLog.UPNP, MythLog.DEBUG, 'running UPnP search')
++
+ sock = self.sock
+ sreq = '\r\n'.join(['M-SEARCH * HTTP/1.1',
+ 'HOST: %s:%s' % self.addr,
+@@ -81,7 +82,7 @@ def search(self, timeout=5.0, filter=None):
+ if sdict['st'] not in filter:
+ continue
+
+- self.log(MythLog.UPNP, sdict['st'], sdict['location'])
++ self.log(MythLog.UPNP, MythLog.DEBUG, sdict['st'],
sdict['location'])
+ yield sdict
+
+ def searchMythBE(self, timeout=5.0):
+
+From 784b9626978128df523b5ecd59d7df9b298289b6 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:45:12 +0200
+Subject: [PATCH 06/28] Adapt mythpython's MythXML class to new Services/API.
+
+Note: Integers and Bollean values need to be converted to strings.
+---
+ mythtv/bindings/python/MythTV/methodheap.py | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/methodheap.py
b/mythtv/bindings/python/MythTV/methodheap.py
+index cbbc8615096..21f49165b21 100644
+--- a/mythtv/bindings/python/MythTV/methodheap.py
++++ b/mythtv/bindings/python/MythTV/methodheap.py
+@@ -44,7 +44,7 @@ class MythBE( FileOps ):
+ getLoad() - returns a tuple of load averages
+ getRecordings() - returns a list of all recordings
+ getSGFile() - returns information on a single file
+- getSGList() - returns lists of directories,
++ getSGList() - returns lists of directories,
+ files, and sizes
+ getUptime() - returns system uptime in seconds
+ isActiveBackend() - determines whether backend is
+@@ -1136,7 +1136,7 @@ def getSetting(self, key, hostname=None, default=None):
+ if default:
+ args['Default'] = default
+ return self._request('Myth/GetSetting', **args)\
+-
.readJSON()['SettingList']['Settings'][0]['Value']
++ .readJSON()['String']
+
+ def getProgramGuide(self, starttime, endtime, startchan, numchan=None):
+ """
+@@ -1146,11 +1146,11 @@ def getProgramGuide(self, starttime, endtime, startchan,
numchan=None):
+ endtime = datetime.duck(endtime)
+ args = {'StartTime':starttime.utcisoformat().rsplit('.',1)[0],
+ 'EndTime':endtime.utcisoformat().rsplit('.',1)[0],
+- 'StartChanId':startchan, 'Details':1}
++ 'StartChanId':startchan, 'Details':'1'}
+ if numchan:
+ args['NumOfChannels'] = numchan
+ else:
+- args['NumOfChannels'] = 1
++ args['NumOfChannels'] = '1'
+
+ dat = self._request('Guide/GetProgramGuide', **args).readJSON()
+ for chan in dat['ProgramGuide']['Channels']:
+@@ -1177,7 +1177,8 @@ def getRecorded(self, descending=True):
+ """
+ Returns a list of Program objects for recorded shows on the backend.
+ """
+- for prog in self._request('Dvr/GetRecorded', Descending=descending)\
++ descendingstr = 'true' if descending else 'false'
++ for prog in self._request('Dvr/GetRecordedList',
Descending=descendingstr)\
+ .readJSON()['ProgramList']['Programs']:
+ yield Program.fromJSON(prog, self.db)
+
+
+From b50f0cc6cd5f74280174817f16838994de007b15 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:47:30 +0200
+Subject: [PATCH 07/28] The conversion to timestamps from MythTV's datetime
+ object and vice versa does not work if current time is in daylight saving
+ zone (dst).
+
+Because of this bug, the methods 'fromEtree' and 'fromJSON' of the class
'Program' of the Python bindings
+cannot convert fetched time values of format '2016-03-14T01:59:21Z' to correct
timestamps.
+
+For additional info, see
+https://stackoverflow.com/questions/8777753/converting-datetime-date-to-utc-timestamp-in-python
+
+I added a revised 'timestamp' method to MythTV's python binding
'datetime' and compared the output:
+
+ def revised_timestamp(self):
+ # utc time = local time - utc offset
+ utc_naive = self.replace(tzinfo=None) - self.utcoffset()
+ utc_epoch = self.utcfromtimestamp(0).replace(tzinfo=None)
+ return ((utc_naive - utc_epoch).total_seconds())
+
+ def timestamp(self):
+ return time.mktime(self.timetuple()) + self.microsecond/1000000.
+
+In the follwing example, times are taken at
+Saturday, June 1, 2019 2:03:48.066 PM GMT+02:00 DST (for variable 'now') and
+Saturday, June 1, 2019 2:04:07.954 PM GMT+02:00 DST (for variable('now_utc')
+
+$ python2
+>>> from MythTV import datetime
+>>> now = datetime.now()
+>>> now
+datetime(2019, 6, 1, 14, 3, 48, 66210, tzinfo=<MythTV.utility.dt.posixtzinfo object at
0x7ff760301910>)
+>>> now.utcoffset()
+datetime.timedelta(0, 7200)
+>>> now.timestamp()
+1559390628.06621
+
+Check
https://www.epochconverter.com/ :
+Convert epoch to human readable date and vice versa
+1559390628.06621
+GMT: Saturday, June 1, 2019 12:03:48.066 PM
+Your time zone: Saturday, June 1, 2019 2:03:48.066 PM GMT+02:00 DST
+Relative: 4 minutes ago
+
+---> That's correct.
+
+>>> now_utc = datetime.utcnow()
+>>> now_utc
+datetime(2019, 6, 1, 12, 4, 7, 954273, tzinfo=<MythTV.utility.dt.posixtzinfo object at
0x7ff7602f22d0>)
+>>> now_utc.utcoffset()
+datetime.timedelta(0)
+>>> now_utc.timestamp()
+1559387047.954273
+
+Check
https://www.epochconverter.com/ :
+Convert epoch to human readable date and vice versa
+1559387047.954273
+GMT: Saturday, June 1, 2019 11:04:07.954 AM
+Your time zone: Saturday, June 1, 2019 1:04:07.954 PM GMT+02:00 DST
+
+---> That's wrong !
+
+Now let's do the same with the 'revised' method of datetime:
+>>> now.revised_timestamp()
+1559390628.06621
+https://www.epochconverter.com/
+Convert epoch to human readable date and vice versa
+1559390628.06621
+GMT: Saturday, June 1, 2019 12:03:48.066 PM
+Your time zone: Saturday, June 1, 2019 2:03:48.066 PM GMT+02:00 DST
+
+---> That's correct.
+
+>>> now_utc.revised_timestamp()
+1559390647.954273
+
+https://www.epochconverter.com/
+1559390647.954273
+GMT: Saturday, June 1, 2019 12:04:07.954 PM
+Your time zone: Saturday, June 1, 2019 2:04:07.954 PM GMT+02:00 DST
+
+---> That's correct as well!
+---
+ mythtv/bindings/python/MythTV/utility/dt.py | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/utility/dt.py
b/mythtv/bindings/python/MythTV/utility/dt.py
+index 9efa4e1e17a..a651aed2e2f 100644
+--- a/mythtv/bindings/python/MythTV/utility/dt.py
++++ b/mythtv/bindings/python/MythTV/utility/dt.py
+@@ -474,7 +474,10 @@ def mythformat(self):
+ return self.astimezone(self.UTCTZ()).strftime('%Y%m%d%H%M%S')
+
+ def timestamp(self):
+- return time.mktime(self.timetuple()) + self.microsecond/1000000.
++ # utc time = local time - utc offset
++ utc_naive = self.replace(tzinfo=None) - self.utcoffset()
++ utc_epoch = self.utcfromtimestamp(0).replace(tzinfo=None)
++ return ((utc_naive - utc_epoch).total_seconds())
+
+ def rfcformat(self):
+ return self.strftime('%a, %d %b %Y %H:%M:%S %z')
+
+From ca80770a86c8e1b40ac58f71919ae1158cd14e44 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 14:52:31 +0200
+Subject: [PATCH 08/28] Update 'MythBE.getFreeRecorderList' to Myth Protocol
+ 91.
+
+Myth Protocol 87 added GET_FREE_INPUT_INFO, and removed GET_FREE_RECORDER_LIST.
+Myth Protocols 89,90,91 changed return values of command GET_FREE_INPUT_INFO.
+See definition InputInfo in inputinfo.h.
+The Backend Command 'GET_FREE_INPUT_INFO' (Myth Protocol) returns:
+A variable length list of InputInfo entries in preferred live TV order.
+Returns an empty list if no recorders are available.
+
+This commit changes the method 'getFreeRecorderList' of the class
'MythBE'
+to use GET_FREE_INPUT_INFO.
+The return type (list of integers) keeps unchanged.
+
+Additionally, it introduces a a method 'getFreeInputInfo' to return
+a list of 'InputInfo' tuples of free recorders in preferred live TV order.
+The backend command 'GET_FREE_INPUT_INFO 0' returns a list of variable length
+containing one or more 'InputInfo' objects.
+
+See definition of InputInfo in inputinfo.h.
+
+InputInfo is a named tuple containing:
+('InputInfo', ('name', 'sourceid', 'inputid',
'mplexid', 'chanid', 'displayName',
+'recPriority', 'scheduleOrder', 'livetvorder',
'quickTune')).
+
+Usage examples:
+
+MythBE.getFreeInputInfo()[3].displayName ---> 'Eingang 13:MPEG2TS'
+
+[x.inputid for x in getFreeInputInfo()] ---> list of free recorders
+---
+ mythtv/bindings/python/MythTV/methodheap.py | 43 +++++++++++++++++++--
+ 1 file changed, 40 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/methodheap.py
b/mythtv/bindings/python/MythTV/methodheap.py
+index 21f49165b21..43a3e74d768 100644
+--- a/mythtv/bindings/python/MythTV/methodheap.py
++++ b/mythtv/bindings/python/MythTV/methodheap.py
+@@ -16,6 +16,8 @@
+
+ from datetime import timedelta
+ from weakref import proxy
++from collections import namedtuple
++
+ try:
+ from urllib import urlopen
+ except ImportError:
+@@ -58,6 +60,11 @@ class MythBE( FileOps ):
+
+ locked_tuners = {}
+
++ # Prepare tuple for backend command 'GET_FREE_INPUT_INFO'
++ _i_info = ('name', 'sourceid', 'inputid', 'mplexid',
'chanid', 'displayName',
++ 'recPriority', 'scheduleOrder', 'livetvorder',
'quickTune')
++ _InputInfo = namedtuple('InputInfo', _i_info)
++
+ def __del__(self):
+ self.freeTuner()
+
+@@ -107,12 +114,42 @@ def getRecorderList(self):
+
+ def getFreeRecorderList(self):
+ """
+- Returns a list of free recorders, or an empty list if none.
++ Returns a list of free recorders in preferred live TV order,
++ or an empty list if none.
+ """
+- res = self.backendCommand('GET_FREE_RECORDER_LIST').split(BACKEND_SEP)
+- recorders = [int(d) for d in res]
++ res = self.backendCommand('GET_FREE_INPUT_INFO 0').split(BACKEND_SEP)
++ # this maps the list of variable length ('res') to InputInfo tuples:
++ res_inputinfo = \
++ [self._InputInfo(*el) for el in zip(*[iter(res)]*len(self._i_info))]
++ recorders = [x.inputid for x in res_inputinfo]
+ return recorders
+
++ def getFreeInputInfo(self):
++ """
++ Return a list of 'InputInfo' tuples of free recorders in preferred
++ live TV order.
++ Returns an empty list if no recorders are available.
++ Introduced in protocol 87, changed in protocols 89, 90, 91.
++
++ InputInfo is a named tuple containing:
++ ('InputInfo', ('name', 'sourceid', 'inputid',
'mplexid', 'chanid',
++ 'displayName', 'recPriority',
'scheduleOrder',
++ 'livetvorder', 'quickTune')).
++
++ See definition of InputInfo in inputinfo.h.
++
++ Usage examples:
++
++ MythBE.getFreeInputInfo()[3].displayName ---> 'Eingang 13:MPEG2TS'
++
++ [x.inputid for x in getFreeInputInfo()] ---> list of free recorders
++ """
++ res = self.backendCommand('GET_FREE_INPUT_INFO 0').split(BACKEND_SEP)
++ # this maps the list of variable length ('res') to InputInfo tuples:
++ res_inputinfo = \
++ [self._InputInfo(*el) for el in zip(*[iter(res)]*len(self._i_info))]
++ return res_inputinfo
++
+ def lockTuner(self,id=None):
+ """
+ Request a tuner be locked from use, optionally specifying which tuner
+
+From 0994cc622ae6a21c6d6be5efc4a75bcf41571c23 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:05:34 +0200
+Subject: [PATCH 09/28] Fix MythSytemEvent class of MythTV's python bindings
+
+MythSytemEvent:
+
+An event is identified by the regex [A-Z0-9_]* .
+This includes the system event `KEY_01`.
+
+BEEventConnection:
+
+The class `BEEventConnection` has optional arguements like
+`timeout` an `level`. Name then if the arguement order is not followed strictly.
+
+Typo in the call of
+"SystemEvent(event['event'], inst.db).command(event)"
+
+Note:
+'inst.db' is not a valid class at this level
+
+Uppercase of system event scripts:
+
+According wiki, the default substitution of parameters is given by uppercase,
+like '%STARTTIMEUTC%' or %SENDER%'. Stick to that convention.
+---
+ mythtv/bindings/python/MythTV/methodheap.py | 6 +++---
+ mythtv/bindings/python/MythTV/system.py | 2 +-
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/methodheap.py
b/mythtv/bindings/python/MythTV/methodheap.py
+index 43a3e74d768..737fecd4d00 100644
+--- a/mythtv/bindings/python/MythTV/methodheap.py
++++ b/mythtv/bindings/python/MythTV/methodheap.py
+@@ -448,7 +448,7 @@ class systemeventhandler( object ):
+ bs =
BACKEND_SEP.replace('[','\[').replace(']','\]')
+ re_process = re.compile(bs.join([
+ 'BACKEND_MESSAGE',
+- 'SYSTEM_EVENT (?P<event>[A-Z_]*)'
++ 'SYSTEM_EVENT (?P<event>[A-Z0-9_]*)'
+ '( HOSTNAME (?P<hostname>[a-zA-Z0-9_\.]*))?'
+ '( SENDER (?P<sender>[a-zA-Z0-9_\.]*))?'
+ '( CARDID (?P<cardid>[0-9]*))?'
+@@ -516,11 +516,11 @@ def __init__(self, backend=None, blockshutdown=False, db=None,
+ self.registerevent(self._generic_handler)
+
+ def _neweventconn(self):
+- return BEEventConnection(self.host, self.port, self.db.gethostname(), 3)
++ return BEEventConnection(self.host, self.port, self.db.gethostname(), level=3)
+
+ @systemeventhandler
+ def _generic_handler(self, event):
+- SystemEvent(event['event'], inst.db).command(event)
++ SystemEvent(event['event'], self.db).command(event)
+
+ class Frontend( FEConnection ):
+ _db = None
+diff --git a/mythtv/bindings/python/MythTV/system.py
b/mythtv/bindings/python/MythTV/system.py
+index 62f1e72c0f4..aeeda8e401a 100644
+--- a/mythtv/bindings/python/MythTV/system.py
++++ b/mythtv/bindings/python/MythTV/system.py
+@@ -445,5 +445,5 @@ def command(self, eventdata):
+ cmd = eventdata['program'].formatJob(cmd)
+ for a in ('sender','cardid','secs'):
+ if a in eventdata:
+- cmd = cmd.replace('%%%s%%' % a, eventdata[a])
++ cmd = cmd.replace('%%%s%%' % a.upper(), eventdata[a])
+ return self._runcmd(cmd)
+
+From fe1d1acba3a30d60431ff934212daf773ae19395 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:08:33 +0200
+Subject: [PATCH 10/28] Allow storage group paths without trailing slashes in
+ Python Binding findfile method.
+
+Use 'os.path.join instead of simply adding strings (path, filename).
+This covers both options, paths with or without trailing slashes.
+---
+ mythtv/bindings/python/MythTV/mythproto.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py
b/mythtv/bindings/python/MythTV/mythproto.py
+index aa45d326b75..949efc1b22f 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -185,12 +185,12 @@ def findfile(filename, sgroup, db=None):
+ for sg in db.getStorageGroup(groupname=sgroup):
+ # search given group
+ if sg.local:
+- if os.access(sg.dirname+filename, os.F_OK):
++ if os.access(os.path.join(sg.dirname, filename), os.F_OK):
+ return sg
+ for sg in db.getStorageGroup():
+ # not found, search all other groups
+ if sg.local:
+- if os.access(sg.dirname+filename, os.F_OK):
++ if os.access(os.path.join(sg.dirname, filename), os.F_OK):
+ return sg
+ return None
+
+@@ -627,7 +627,7 @@ class FileOps( BECache ):
+ reschedule() - trigger a run of the scheduler
+ fileExists() - check whether a file can be found on a backend
+ download() - issue a download by the backend
+- downloadTo() - issue a download by the backend to a defined
++ downloadTo() - issue a download by the backend to a defined
+ location
+ allocateEventLock() - create an EventLock object that will be locked
+ until a requested regular expression is
+
+From 3ea7bd014307a03cd65265191edf85ff2520db0a Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:10:55 +0200
+Subject: [PATCH 11/28] Various fixes of MythTV Python Bindings in
+ mythproto.py.
+
+Commit eebe25c introduced the new setting 'BackendServerAddr' instead
+of 'BackendServerIP[6]'. See ticket #13082.
+Fix another occurence of 'BackendServerIP[6]' in class BECache.
+
+In mythtv's protocol #84 the event 'UPDATE_FILE_SIZE' changed to use
+'recordedid' instead of tuple ('chanid', 'starttime').
+This commit adds an optional parameter 'recordedid' to ftopen(),
+but still allows the optional ('chanid', 'starttime') tuple, and
+fixes the handling of this event.
+See ticket #12365, comment:5.
+
+Fix the optional arguements when openening the control socket in the
+class 'FileTransfer'. We need to enable receive events, if we want
+listen to them. Long time ago, the commit cd23715 changed this
+behaviour of the BEEvent class.
+
+The method 'FileOps.downloadTo()' listen only for events
+'DOWNLOAD_FILE UPDATE'. In short file transfers, the event
+'DOWNLOAD_FILE FINISHED' is sent without an update event.
+Listen for both events when allocating an eventlock.
+---
+ mythtv/bindings/python/MythTV/mythproto.py | 53 ++++++++++++----------
+ 1 file changed, 28 insertions(+), 25 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py
b/mythtv/bindings/python/MythTV/mythproto.py
+index 949efc1b22f..9aa8d725a75 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -81,12 +81,12 @@ def __init__(self, backend=None, blockshutdown=False, events=False,
db=None):
+ # given backend is IP address
+ self.host = backend
+ self.hostname = self.db._gethostfromaddr(
+- backend, 'BackendServerIP')
++ backend, 'BackendServerAddr')
+ elif check_ipv6(backend):
+ # given backend is IPv6 address
+ self.host = backend
+ self.hostname = self.db._gethostfromaddr(
+- backend, 'BackendServerIP6')
++ backend, 'BackendServerAddr')
+ else:
+ # given backend is hostname, pull address from database
+ self.hostname = backend
+@@ -195,7 +195,7 @@ def findfile(filename, sgroup, db=None):
+ return None
+
+ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+- chanid=None, starttime=None, download=False):
++ chanid=None, starttime=None, recordedid=None, download=False):
+ """
+ ftopen(file, mode, forceremote=False, nooverwrite=False, db=None)
+ -> FileTransfer object
+@@ -218,17 +218,6 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False,
db=None, \
+ if mode not in ('r','w'):
+ raise TypeError("File I/O must be of type 'r' or
'w'")
+
+- if chanid and starttime:
+- protoopen = lambda host, file, storagegroup: \
+- RecordFileTransfer(host, file, storagegroup,\
+- mode, chanid, starttime, db)
+- elif download:
+- protoopen = lambda host, lfile, storagegroup: \
+- DownloadFileTransfer(host, lfile, storagegroup, \
+- mode, file, db)
+- else:
+- protoopen = lambda host, file, storagegroup: \
+- FileTransfer(host, file, storagegroup, mode, db)
+
+ # process URI
(myth://<group>@<host>[:<port>]/<path/to/file>)
+ match = None
+@@ -253,6 +242,24 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False,
db=None, \
+ if reip.match(host) or check_ipv6(host):
+ host = db._gethostfromaddr(host)
+
++ # select the correct transfer function:
++ if chanid and starttime and not recordedid:
++ # get recordedid from FileOps class
++ recordedid = \
++ FileOps(host, db=db).getRecording(chanid, starttime).recordedid
++ if recordedid:
++ protoopen = lambda host, file, storagegroup: \
++ RecordFileTransfer(host, file, storagegroup,\
++ mode, recordedid, db)
++ elif download:
++ protoopen = lambda host, lfile, storagegroup: \
++ DownloadFileTransfer(host, lfile, storagegroup, \
++ mode, file, db)
++ else:
++ protoopen = lambda host, file, storagegroup: \
++ FileTransfer(host, file, storagegroup, mode, db)
++
++
+ # user forced to remote access
+ if forceremote:
+ if (mode == 'w') and (filename.find('/') != -1):
+@@ -370,7 +377,7 @@ def __init__(self, host, filename, sgroup, mode, db=None):
+ self.mode = mode
+
+ # open control socket
+- BEEvent.__init__(self, host, True, db=db)
++ BEEvent.__init__(self, host, blockshutdown=True, events=True, db=db)
+ # open transfer socket
+ self.ftsock = self.BETransConn(self.host, self.port,
+ self._conn.command.localname, self.filename,
+@@ -549,18 +556,16 @@ def updatesize(self, event=None):
+ self.re_update = re.compile(\
+ re.escape(BACKEND_SEP).\
+ join(['BACKEND_MESSAGE',
+- 'UPDATE_FILE_SIZE %s %s (?P<size>[0-9]*)' %\
+- (self.chanid, \
+- self.starttime.isoformat()),
++ 'UPDATE_FILE_SIZE %s (?P<size>[0-9]*)' %\
++ (self.recordedid),
+ 'empty']))
+ return self.re_update
+ match = self.re_update.match(event)
+ self._size = int(match.group('size'))
+
+ def __init__(self, host, filename, sgroup, mode,
+- chanid, starttime, db=None):
+- self.chanid = chanid
+- self.starttime = starttime
++ recordedid, db=None):
++ self.recordedid = recordedid
+ FileTransfer.__init__(self, host, filename, sgroup, mode, db)
+
+ class DownloadFileTransfer( FileTransfer ):
+@@ -721,7 +726,7 @@ def downloadTo(self, url, storagegroup, filename, \
+ eventlock = self.allocateEventLock(\
+ re.escape(BACKEND_SEP).\
+ join(['BACKEND_MESSAGE',
+- 'DOWNLOAD_FILE UPDATE',
++ 'DOWNLOAD_FILE (FINISHED|UPDATE)',
+ re.escape(url)]))
+ if filename[0] != '/':
+ filename = '/'+filename
+@@ -970,9 +975,7 @@ def delete(self, force=False, rerecord=False):
+ def _openProto(self):
+ filename = self.filename if self.filename.startswith('myth://') else \
+ (self.hostname, self.storagegroup, self.filename)
+- return ftopen(filename, 'r', db=self._db, chanid=self.chanid, \
+- starttime=self.recstartts)
+-
++ return ftopen(filename, 'r', db=self._db, recordedid = self.recordedid)
+ def _openXML(self):
+ xml = XMLConnection(self.hostname, 6544)
+ return xml._request('Content/GetRecording', ChanId=self.chanid, \
+
+From 7ac4e9e21f830d9ad0bfeabcb5fc3287267dda84 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:13:20 +0200
+Subject: [PATCH 12/28] Python Bindings Recorded.formatPath() bails if airdate
+ is not set.
+
+Calling 'Recorded.formatPath("%U/%T/%pY-%pm-%pd %pH.%pi %T")' on a
valid
+Recorded instance, gives a traceback.
+
+Recorded.formatPath without valid airdate:
+
+Traceback (most recent call last):
+ File "test/test_Dataheap_Recorded_001.py", line 116, in
test_Dataheap_Recorded_001_04
+ print(rec.formatPath("%U/%T/%pY-%pm-%pd %pH.%pi %T"))
+ File "MythTV/dataheap.py", line 440, in formatPath
+ path = path.replace('%o'+tag, airdate.strftime(format))
+ValueError: year=1 is before 1900; the datetime strftime() methods require year >=
1900
+
+Setting 'airdate' to
+_default_datetime = datetime(1900,1,1, tzinfo=datetime.UTCTZ())
+cures it.
+---
+ mythtv/bindings/python/MythTV/dataheap.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/dataheap.py
b/mythtv/bindings/python/MythTV/dataheap.py
+index 946015c4129..f4f182276c1 100644
+--- a/mythtv/bindings/python/MythTV/dataheap.py
++++ b/mythtv/bindings/python/MythTV/dataheap.py
+@@ -428,7 +428,8 @@ def formatPath(self, path, replace=None):
+ ('a','%p'),('A','%p')
):
+ path = path.replace(pre+tag, self[data].strftime(format))
+ if self.originalairdate is None:
+- airdate = date(1,1,1)
++ airdate = _default_datetime
++
+ else:
+ airdate = self.originalairdate
+ for (tag, format) in
(('y','%y'),('Y','%Y'),('n','%m'),('m','%m'),
+
+From 7e02d1e815131a5f0bc028b876c8e8cb1f6be95c Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:14:43 +0200
+Subject: [PATCH 13/28] The class methods Job.from[Recorded,Program] should
+ return the created class.
+
+Python Bindings: When creating a Job instance with 'Job.fromRecorded' or
+Job.fromProgram, the new created instances are not returned, like in the other
+'Class.fromSomething' methods of the bindings.
+---
+ mythtv/bindings/python/MythTV/dataheap.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/dataheap.py
b/mythtv/bindings/python/MythTV/dataheap.py
+index f4f182276c1..653c779e4a2 100644
+--- a/mythtv/bindings/python/MythTV/dataheap.py
++++ b/mythtv/bindings/python/MythTV/dataheap.py
+@@ -708,7 +708,7 @@ def fromRecorded(cls, rec, type, status=None, schedruntime=None,
+ job.args = args
+ if flags:
+ job.flags = flags
+- job.create()
++ return job.create()
+
+ @classmethod
+ def fromProgram(cls, prog, type, status=None, schedruntime=None,
+@@ -729,7 +729,7 @@ def fromProgram(cls, prog, type, status=None, schedruntime=None,
+ job.args = args
+ if flags:
+ job.flags = flags
+- job.create()
++ return job.create()
+
+ class Channel( DBDataWrite ):
+ """Channel(chanid=None, db=None) -> Channel
object"""
+
+From 6d32cd38a795f85383b7146edcc3d4f1462c5a32 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:16:47 +0200
+Subject: [PATCH 14/28] Fix check in Python's Job.fromProgram classmethod.
+
+We want to check the status of a recorded programg, and not the type.
+In file static.py, the 'rsRecorded' is a member of RECSTATUS,
+therefore check for 'recstatus' instead of 'rectype'.
+---
+ mythtv/bindings/python/MythTV/dataheap.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/dataheap.py
b/mythtv/bindings/python/MythTV/dataheap.py
+index 653c779e4a2..8ff7e843f92 100644
+--- a/mythtv/bindings/python/MythTV/dataheap.py
++++ b/mythtv/bindings/python/MythTV/dataheap.py
+@@ -713,7 +713,7 @@ def fromRecorded(cls, rec, type, status=None, schedruntime=None,
+ @classmethod
+ def fromProgram(cls, prog, type, status=None, schedruntime=None,
+ hostname=None, args=None, flags=None):
+- if prog.rectype != prog.rsRecorded:
++ if prog.recstatus != prog.rsRecorded:
+ raise MythError('Invalid recording type for Job.')
+ job = cls(db=prog._db)
+ job.type = type
+
+From 1262662004100182e1476b8b56e5810ed5a23d49 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:19:42 +0200
+Subject: [PATCH 15/28] Logging in the 'deadlinesocket' fails if dealing with
+ utf-8 encoded strings.
+
+The logging of the 'sendheader' and 'recvheader' methods needs unicode,
+but the methods itself need to be fed with 'utf-8' encoded data.
+
+See commit e20bd8b for further details.
+
+Since thees methods itself deal with 'utf-8' encoded data, we need to
+decode these data to unicode before we send them to the logging instance.
+
+This commit provides and uses a generic function to decode strings to unicode for
+python2 and python3, as well.
+
+How to reproduce:
+
+Start python from a PC that has mythtv frontend installed, but it is not currently
+running:
+
+When running the MythTV's python binding `BEEventMonitor`
+and log events to a file, a traceback occurs:
+
+$ python2 - --nodblog --loglevel debug --verbose all --logfile /tmp/my_logfile
+>>> from MythTV import BEEventMonitor
+>>> bemon = BEEventMonitor(systemevents=True)
+>>> while (True):
+.... continue
+
+From another frontend, start playback of a recording that has a description
+with German umlauts or French accents:
+
+Enjoy the traceback:
+
+Unhandled exception in thread started by
+ <bound method BEEventConnection.eventloop
+ of <MythTV.connections.BEEventConnection object at 0x7f6f922c1ad0>>
+Traceback (most recent call last):
+ File "MythTV/connections.py", line 427, in eventloop
+ self.queueEvents()
+ File "MythTV/connections.py", line 396, in queueEvents
+ event = self.socket.recvheader(deadline=0.0)
+ File "MythTV/utility/other.py", line 387, in recvheader
+ 'read <-- %d' % size, data)
+ File "MythTV/logging.py", line 431, in __call__
+ self.log(mask, level, message, detail)
+ File "MythTV/logging.py", line 376, in log
+ self._logwrite(mask, level, message, detail)
+ File "MythTV/logging.py", line 408, in _logfile
+ self._LOGFILE.write(buff.getvalue())
+ File "/usr/lib/python2.7/codecs.py", line 708, in write
+ return self.writer.write(data)
+ File "/usr/lib/python2.7/codecs.py", line 369, in write
+ data, consumed = self.encode(object, self.errors)
+UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 260:
+ ordinal not in range(128)
+---
+ mythtv/bindings/python/MythTV/utility/other.py | 18 +++++++++++++++---
+ 1 file changed, 15 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/utility/other.py
b/mythtv/bindings/python/MythTV/utility/other.py
+index 543bef38a00..740e81b107b 100644
+--- a/mythtv/bindings/python/MythTV/utility/other.py
++++ b/mythtv/bindings/python/MythTV/utility/other.py
+@@ -55,7 +55,7 @@ def run(self):
+ except AttributeError as e:
+ self.log(MythLog.GENERAL, MythLog.CRIT,
+ 'failed at %d' % schema, 'no handler method')
+- raise MythDBError('Schema update failed, '
++ raise MythDBError('Schema update failed, '
+ "SchemaUpdate has no function 'up%s'" % schema)
+
+ except StopIteration:
+@@ -383,15 +383,16 @@ def recvheader(self, flags=0, deadline=None):
+ """
+ size = int(self.dlrecv(8, flags, deadline))
+ data = self.dlrecv(size, flags, deadline)
++ # data is utf-8 encoded, convert to unicode for logging and ignore errors
+ self.log(MythLog.SOCKET|MythLog.NETWORK, MythLog.DEBUG, \
+- 'read <-- %d' % size, data)
++ 'read <-- %d' % size, py23_str(data, True))
+ return data
+
+ def sendheader(self, data, flags=0):
+ """Send data, prepending the length in the first 8
bytes."""
+ try:
+ self.log(MythLog.SOCKET|MythLog.NETWORK, MythLog.DEBUG, \
+- 'write --> %d' % len(data), data)
++ 'write --> %d' % len(data), py23_str(data,
True))
+ data = '%-8d%s' % (len(data), data)
+ self.send(data, flags)
+ except socket.error as e:
+@@ -572,6 +573,17 @@ def check_ipv6(n):
+ except socket.error:
+ return False
+
++def py23_str(value, ignore_errors=False):
++ error_methods = ('strict', 'ignore')
++ error_method = error_methods[ignore_errors]
++ try: # Python 2
++ return unicode(value, errors=error_method, encoding='utf-8')
++ except NameError: # Python 3
++ try:
++ return str(value,errors=error_method, encoding='utf-8')
++ except TypeError: # Wasn't a bytes object, no need to decode
++ return str(value)
++
+ class QuickProperty( object ):
+ def __init__(self, maskedvar, default=None, handler=None):
+ self.varname = maskedvar
+
+From 89a1567ebc0982551c5d4efb80c0c28c2cc1e062 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:23:34 +0200
+Subject: [PATCH 16/28] Fix mysql connection of the Python Bindings for
+ python3.
+
+The ticket #11938 shows a bug when connectiong to mysql using the python bindings.
+
+This initial commit shows the initial intention of the author:
+
+[Commit]4b3f512b[/Commit]
+or
+https://github.com/MythTV/mythtv/commit/980ae04
+
+Let's stick to it.
+
+Note: This will close #11938.
+---
+ mythtv/bindings/python/MythTV/_conn_mysqldb.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/_conn_mysqldb.py
b/mythtv/bindings/python/MythTV/_conn_mysqldb.py
+index 676d147a62a..177a880a121 100644
+--- a/mythtv/bindings/python/MythTV/_conn_mysqldb.py
++++ b/mythtv/bindings/python/MythTV/_conn_mysqldb.py
+@@ -34,12 +34,12 @@ def __init__(self, connection):
+ super(LoggedCursor, self).__init__(connection)
+ self.log = None
+ self.ping = ref(self._ping121)
+- if MySQLdb.version_info >= ('1','2','2'):
++ if MySQLdb.version_info >= (1, 2, 2):
+ self.ping = ref(self._ping122)
+ self.ping()
+
+- def _ping121(self): self._get_db().ping(True)
+- def _ping122(self): self._get_db().ping()
++ def _ping121(self): self._get_db().ping()
++ def _ping122(self): self._get_db().ping(True)
+
+ def _sanitize(self, query): return query.replace('?', '%s')
+
+
+From 27dd6ad92b063bc1ca893fb10a4682dd13742c25 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 15:40:22 +0200
+Subject: [PATCH 17/28] Fix Python Bindings to be compatible to python3 as
+ well.
+
+ - Make deadlinesocket of python bindings compatible to python3, too.
+
+ MythTV's python bindings based on `deadlinesocket` need a
+ byte like object in python3.
+ Convert everything to/from utf-8 when sending/receiving data from
+ the socket.
+
+ - Update iterators and dictionaries of Python Bindings to be compatible
+ to python3 as well.
+
+ According porting guides to python3 [1], the methods
+ `dict.keys()`, `dict.items()` and `dict.values()` now return
+ views instead of lists.
+ Unlike lists, a view does not hold copy the data.
+ Updates to the underlying dict are reflected in the view.
+ Use `list(dict.keys(), dict.items() and dict.values()` as appropriate.
+
+ - Additionally, python2's `next()` method changed to python3 built-in
+ function `next()`.
+
+ - Convert `xrange` (python2) to `range` (python3).
+
+ - Fix logging, convert to 'utf-8'.
+
+ - Add a generic function `py23_str` to convert data to unicode/str.
+
+ - Fix python3 handling of time-zone files after applying
+ Handle-timezone-files-with-no-modern-transitions-as-well[29/master].patch
+ from ticket #13299
+---
+ mythtv/bindings/python/MythTV/connections.py | 14 ++++-----
+ mythtv/bindings/python/MythTV/database.py | 6 ++--
+ mythtv/bindings/python/MythTV/dataheap.py | 7 +++--
+ mythtv/bindings/python/MythTV/logging.py | 14 ++++-----
+ mythtv/bindings/python/MythTV/methodheap.py | 30 +++++++++++--------
+ mythtv/bindings/python/MythTV/msearch.py | 3 +-
+ mythtv/bindings/python/MythTV/mythproto.py | 7 ++++-
+ mythtv/bindings/python/MythTV/system.py | 4 +--
+ .../python/MythTV/utility/__init__.py | 2 +-
+ mythtv/bindings/python/MythTV/utility/dt.py | 13 ++++----
+ .../bindings/python/MythTV/utility/other.py | 10 ++++---
+ 11 files changed, 63 insertions(+), 47 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/connections.py
b/mythtv/bindings/python/MythTV/connections.py
+index e541d2cca1e..b83a445ea1c 100644
+--- a/mythtv/bindings/python/MythTV/connections.py
++++ b/mythtv/bindings/python/MythTV/connections.py
+@@ -313,7 +313,7 @@ def backendCommand(self, data, deadline=None):
+
+ # convert to unicode
+ try:
+- res = str(''.join([res]), 'utf8')
++ res = str(b''.join([res]), 'utf-8')
+ except:
+ res = u''.join([res])
+
+@@ -396,7 +396,7 @@ def queueEvents(self):
+ event = self.socket.recvheader(deadline=0.0)
+
+ try:
+- event = str(''.join([event]), 'utf8')
++ event = str(b''.join([event]), 'utf-8')
+ except:
+ event = u''.join([event])
+
+@@ -526,7 +526,7 @@ def connect(self, test=True):
+ def disconnect(self):
+ if not self.isConnected:
+ return
+- self.socket.send("exit")
++ self.socket.send(b"exit")
+ self.socket.close()
+ self.socket = None
+ self.isConnected = False
+@@ -542,25 +542,25 @@ def send(self, mode, command=None):
+ if not self.isConnected:
+ self.connect()
+ if command is None:
+- self.socket.send("help %s\n" % mode)
++ self.socket.send(("help %s\n" % mode).encode('utf-8'))
+ res = self.recv()
+ try:
+ return self._res_help[mode].findall(res)
+ except:
+ return self._res_help[mode](res)
+ else:
+- self.socket.send("%s %s\n" % (mode, command))
++ self.socket.send((u"%s %s\n" % (mode,
command)).encode('utf-8'))
+ return self._res_handler[mode](self.recv())
+
+ def recv(self, deadline=None):
+- prompt = re.compile('([\r\n.]*)\r\n# ')
++ prompt = re.compile(b'([\r\n.]*)\r\n# ')
+ try:
+ res = self.socket.dlexpect(prompt, deadline=deadline)
+ except socket.error:
+ raise MythFEError(MythError.FE_CONNECTION, self.host, self.port)
+ except KeyboardInterrupt:
+ raise
+- return prompt.split(res)[0]
++ return prompt.split(res)[0].decode('utf-8')
+
+ class XMLConnection( object ):
+ """
+diff --git a/mythtv/bindings/python/MythTV/database.py
b/mythtv/bindings/python/MythTV/database.py
+index f7f23b9a806..885cd4d310e 100644
+--- a/mythtv/bindings/python/MythTV/database.py
++++ b/mythtv/bindings/python/MythTV/database.py
+@@ -346,7 +346,7 @@ def _create(self, data=None, cursor=None):
+
+ self._import(data)
+ data = self._sanitize(dict(self))
+- for key,val in data.items():
++ for key,val in list(data.items()):
+ if val is None:
+ del data[key]
+ elif isinstance(val, datetime):
+@@ -392,7 +392,7 @@ def _push(self):
+ if (self._where is None) or (self._wheredat is None):
+ return
+ data = self._sanitize(dict(self))
+- for key, value in data.items():
++ for key, value in list(data.items()):
+ if value == self._origdata[key]:
+ # filter unchanged data
+ del data[key]
+@@ -402,7 +402,7 @@ def _push(self):
+ # no updates
+ return
+ format_string = ', '.join(['%s = ?' % d for d in data])
+- sql_values = data.values()
++ sql_values = list(data.values())
+ sql_values.extend(self._getwheredat())
+ with self._db.cursor(self._log) as cursor:
+ cursor.execute("""UPDATE %s SET %s WHERE %s"""
\
+diff --git a/mythtv/bindings/python/MythTV/dataheap.py
b/mythtv/bindings/python/MythTV/dataheap.py
+index 8ff7e843f92..e3338fbb9de 100644
+--- a/mythtv/bindings/python/MythTV/dataheap.py
++++ b/mythtv/bindings/python/MythTV/dataheap.py
+@@ -126,17 +126,18 @@ def _setClassDefs(cls, db=None):
+ db = DBCache(db)
+ super(Record, cls)._setClassDefs(db)
+ defaults = cls._template('Default', db=db)
+- for k,v in defaults.iteritems():
++ for k,v in list(defaults.items()):
+ cls._defaults[k] = v
+
++
+ _stored_templates = {}
+ @classmethod
+ def _template(cls, name, db=None):
+ if name not in cls._stored_templates:
+ db = DBCache(db)
+ cls._setClassDefs(db)
+- tmp = cls._fromQuery("WHERE title=?", (name + "
(Template)",))\
+- .next().iteritems()
++ tmp = next(cls._fromQuery("WHERE title=?", (name + "
(Template)",)))\
++ .iteritems()
+ data = {}
+ for k,v in tmp:
+ if k in ['type', 'category', 'profile',
'recpriority',
+diff --git a/mythtv/bindings/python/MythTV/logging.py
b/mythtv/bindings/python/MythTV/logging.py
+index 7304be4d717..63cac19affa 100644
+--- a/mythtv/bindings/python/MythTV/logging.py
++++ b/mythtv/bindings/python/MythTV/logging.py
+@@ -110,24 +110,24 @@ def _initlogger(cls):
+ @classmethod
+ def _parseinput(cls):
+ args = iter(argv)
+- args.next()
++ next(args)
+ try:
+ while True:
+- arg = args.next()
++ arg = next(args)
+ if arg == '--quiet':
+ cls._QUIET += 1
+ elif arg == '--nodblog':
+ cls._DBLOG = False
+ elif arg == '--loglevel':
+- cls._setlevel(args.next())
++ cls._setlevel(next(args))
+ elif arg == '--verbose':
+- cls._setmask(args.next())
++ cls._setmask(next(args))
+ elif arg == '--logfile':
+- cls._setfile(args.next())
++ cls._setfile(next(args))
+ elif arg == '--logpath':
+- cls._setpath(args.next())
++ cls._setpath(next(args))
+ elif arg == '--syslog':
+- cls._setsyslog(args.next())
++ cls._setsyslog(next(args))
+ elif arg == '--':
+ break
+
+diff --git a/mythtv/bindings/python/MythTV/methodheap.py
b/mythtv/bindings/python/MythTV/methodheap.py
+index 737fecd4d00..aa581d7cf09 100644
+--- a/mythtv/bindings/python/MythTV/methodheap.py
++++ b/mythtv/bindings/python/MythTV/methodheap.py
+@@ -25,6 +25,11 @@
+
+ import re
+
++try:
++ xrange
++except NameError:
++ xrange = range
++
+ class CaptureCard( DBData ):
+ pass
+
+@@ -294,7 +299,7 @@ def getFreeSpace(self,all=False):
+ command = 'QUERY_FREE_SPACE_LIST'
+ res = self.backendCommand(command).split(BACKEND_SEP)
+ l = len(FreeSpace._field_order)
+- for i in xrange(len(res)/l):
++ for i in xrange(len(res)//l):
+ yield FreeSpace(res[i*l:i*l+l])
+
+ def getFreeSpaceSummary(self):
+@@ -327,17 +332,18 @@ def walkSG(self, host, sg, top=None):
+ """
+ def walk(self, host, sg, root, path):
+ res = self.getSGList(host, sg, root+path+'/')
+- if res < 0:
+- return {}
+- dlist = list(res[0])
+- res = [dlist, dict(zip(res[1], res[2]))]
+- if path == '':
+- res = {'/':res}
++ if isinstance(res, tuple):
++ dlist = list(res[0])
++ res = [dlist, dict(zip(res[1], res[2]))]
++ if path == '':
++ res = {'/':res}
++ else:
++ res = {path:res}
++ for d in dlist:
++ res.update(walk(self, host, sg, root, path+'/'+d))
++ return res
+ else:
+- res = {path:res}
+- for d in dlist:
+- res.update(walk(self, host, sg, root, path+'/'+d))
+- return res
++ return {}
+
+ bases = self.getSGList(host, sg, '')
+ if (top == '/') or (top is None): top = ''
+@@ -352,7 +358,7 @@ def walk(self, host, sg, root, path):
+ walked[d] = res[d]
+
+ res = []
+- for key, val in walked.iteritems():
++ for key, val in list(walked.items()):
+ res.append((key, tuple(val[0]), val[1]))
+ res.sort()
+ return res
+diff --git a/mythtv/bindings/python/MythTV/msearch.py
b/mythtv/bindings/python/MythTV/msearch.py
+index ca89a0928d4..b884679ce21 100644
+--- a/mythtv/bindings/python/MythTV/msearch.py
++++ b/mythtv/bindings/python/MythTV/msearch.py
+@@ -59,7 +59,7 @@ def search(self, timeout=5.0, filter=None):
+ 'ST: ssdp:all',''])
+ self._runsearch = True
+ # spam the request a couple times
+- [sock.sendto(sreq, self.dest) for i in range(3)]
++ [sock.sendto(sreq.encode('utf-8'), self.dest) for i in range(3)]
+
+ atime = time()+timeout
+ while (time()<atime) and self._runsearch:
+@@ -67,6 +67,7 @@ def search(self, timeout=5.0, filter=None):
+ sdata, saddr = sock.recvfrom(2048)
+ except socket.error:
+ continue #no data, continue
++ sdata = sdata.decode('utf-8')
+
+ lines = sdata.split('\n')
+ sdict = {'request':lines[0].strip()}
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py
b/mythtv/bindings/python/MythTV/mythproto.py
+index 9aa8d725a75..08a62546647 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -26,6 +26,11 @@
+ import re
+ import os
+
++try:
++ xrange
++except NameError:
++ xrange = range
++
+ class BECache( object ):
+ """
+ BECache(backend=None, noshutdown=False, db=None)
+@@ -424,7 +429,7 @@ def read(self, size):
+ if self._pos + size > self._size:
+ size = self._size - self._pos
+
+- buff = ''
++ buff = b''
+ while len(buff) < size:
+ ct = size - len(buff)
+ if ct > self._tsize:
+diff --git a/mythtv/bindings/python/MythTV/system.py
b/mythtv/bindings/python/MythTV/system.py
+index aeeda8e401a..821faf5f128 100644
+--- a/mythtv/bindings/python/MythTV/system.py
++++ b/mythtv/bindings/python/MythTV/system.py
+@@ -63,10 +63,10 @@ def system(cls, command, db=None):
+ s = cls(path, db=db)
+ res = s(args)
+ if len(res):
+- s.log(MythLog.SYSTEM, MythLog.DEBUG, '---- Output ----', res)
++ s.log(MythLog.SYSTEM, MythLog.DEBUG, '---- Output ----',
res.decode('utf-8'))
+ if len(s.stderr):
+ s.log(MythLog.SYSTEM, MythLog.DEBUG,
+- '---- Error ----', s.stderr)
++ '---- Error ----',
s.stderr.decode('utf-8'))
+ return 0
+ except (MythDBError,MythFileError):
+ return -1
+diff --git a/mythtv/bindings/python/MythTV/utility/__init__.py
b/mythtv/bindings/python/MythTV/utility/__init__.py
+index 31d2afae28b..9e8913adc51 100644
+--- a/mythtv/bindings/python/MythTV/utility/__init__.py
++++ b/mythtv/bindings/python/MythTV/utility/__init__.py
+@@ -7,4 +7,4 @@
+
+ from .other import _donothing, SchemaUpdate, databaseSearch, deadlinesocket, \
+ MARKUPLIST, levenshtein, ParseEnum, ParseSet, CopyData, \
+- CopyData2, check_ipv6, QuickProperty
++ CopyData2, check_ipv6, QuickProperty, py23_str
+diff --git a/mythtv/bindings/python/MythTV/utility/dt.py
b/mythtv/bindings/python/MythTV/utility/dt.py
+index a651aed2e2f..c669e3d767a 100644
+--- a/mythtv/bindings/python/MythTV/utility/dt.py
++++ b/mythtv/bindings/python/MythTV/utility/dt.py
+@@ -129,7 +129,7 @@ class posixtzinfo( basetzinfo ):
+ @staticmethod
+ def _get_version(fd):
+ """Confirm zoneinfo file magic string, and return version
number."""
+- if fd.read(4) != 'TZif':
++ if fd.read(4) != b'TZif':
+ raise MythTZError(MythTZError.TZ_INVALID_FILE)
+ version = fd.read(1) # read version number
+ fd.seek(15, 1) # skip reserved bytes
+@@ -172,7 +172,7 @@ def _process(self, fd, version=1, skip=False):
+ transitions[i] = ([t, tt, None, None, None, None])
+ if first_modern_transition is None:
+ first_modern_transition = i
+- except ValueError as e:
++ except (ValueError, OSError) as e:
+ # ValueError is only accepted until we have seen a modern
+ # transition.
+ if first_modern_transition is not None:
+@@ -202,7 +202,7 @@ def _process(self, fd, version=1, skip=False):
+ transitions[i][5] = isdst
+
+ # read in type names
+- for i, name in enumerate(fd.read(counts.abbrevs)[:-1].split('\0')):
++ for i, name in enumerate(fd.read(counts.abbrevs)[:-1].split(b'\0')):
+ for j in range(first_modern_transition, counts.transitions):
+ if types[j] == i:
+ transitions[j][4] = name
+@@ -222,17 +222,18 @@ def _process(self, fd, version=1, skip=False):
+
+ def __init__(self, name=None):
+ if name:
+- fd = open('/usr/share/zoneinfo/' + name)
++ fd = open('/usr/share/zoneinfo/' + name, 'rb')
+ elif os.getenv('TZ'):
+- fd = open('/usr/share/zoneinfo/' + os.getenv('TZ'))
++ fd = open('/usr/share/zoneinfo/' + os.getenv('TZ'),
'rb')
+ else:
+- fd = open('/etc/localtime')
++ fd = open('/etc/localtime', 'rb')
+
+ version = self._get_version(fd)
+ if version == 2:
+ self._process(fd, skip=True)
+ self._get_version(fd)
+ self._process(fd, version)
++ fd.close()
+
+ class offsettzinfo( _pytzinfo ):
+ """Customized timezone class that provides a simple static
offset."""
+diff --git a/mythtv/bindings/python/MythTV/utility/other.py
b/mythtv/bindings/python/MythTV/utility/other.py
+index 740e81b107b..d96cccf53f0 100644
+--- a/mythtv/bindings/python/MythTV/utility/other.py
++++ b/mythtv/bindings/python/MythTV/utility/other.py
+@@ -328,7 +328,7 @@ def dlrecv(self, bufsize, flags=0, deadline=None):
+ timeout = (deadline-t) if (deadline-t>0) else 0.0
+ if len(select([self],[],[], timeout)[0]) == 0:
+ # deadline reached, terminate
+- return u''
++ return b''
+
+ # append response to buffer
+ p = buff.tell()
+@@ -360,7 +360,7 @@ def dlexpect(self, pattern, flags=0, deadline=None):
+ timeout = (deadline-t) if (deadline-t>0) else 0.0
+ if len(select([self],[],[], timeout)[0]) == 0:
+ # deadline reached, terminate
+- return ''
++ return b''
+
+ # append response to buffer
+ p = buff.tell()
+@@ -393,7 +393,9 @@ def sendheader(self, data, flags=0):
+ try:
+ self.log(MythLog.SOCKET|MythLog.NETWORK, MythLog.DEBUG, \
+ 'write --> %d' % len(data), py23_str(data,
True))
+- data = '%-8d%s' % (len(data), data)
++ # build the byte array:
++ length = b'%-8d' % len(data)
++ data = b"".join([length, data])
+ self.send(data, flags)
+ except socket.error as e:
+ raise MythError(MythError.SOCKET, e.args)
+@@ -580,7 +582,7 @@ def py23_str(value, ignore_errors=False):
+ return unicode(value, errors=error_method, encoding='utf-8')
+ except NameError: # Python 3
+ try:
+- return str(value,errors=error_method, encoding='utf-8')
++ return str(value, errors=error_method, encoding='utf-8')
+ except TypeError: # Wasn't a bytes object, no need to decode
+ return str(value)
+
+
+From cfd78f3d2aa85d1e34bc7a9d0ac8d68d884b97cb Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 16:11:45 +0200
+Subject: [PATCH 18/28] Make mythwikiscripts compatible to python3.
+
+Caching files is now different to python2:
+Remove /tmp/mythwikiscripts.pickle* prior to testing mythwikiscripts.
+---
+ .../python/MythTV/wikiscripts/__init__.py | 2 +-
+ .../python/MythTV/wikiscripts/wikiscripts.py | 64 +++++++++++++------
+ .../bindings/python/scripts/mythwikiscripts | 26 ++++----
+ 3 files changed, 59 insertions(+), 33 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/wikiscripts/__init__.py
b/mythtv/bindings/python/MythTV/wikiscripts/__init__.py
+index 212a8bf54ad..94718a5409c 100644
+--- a/mythtv/bindings/python/MythTV/wikiscripts/__init__.py
++++ b/mythtv/bindings/python/MythTV/wikiscripts/__init__.py
+@@ -1,6 +1,6 @@
+ #!/usr/bin/env python
+
+-from wikiscripts import *
++from .wikiscripts import *
+
+
+ if __name__ == '__main__':
+diff --git a/mythtv/bindings/python/MythTV/wikiscripts/wikiscripts.py
b/mythtv/bindings/python/MythTV/wikiscripts/wikiscripts.py
+index 2bc70177c8a..2f02d1923e6 100644
+--- a/mythtv/bindings/python/MythTV/wikiscripts/wikiscripts.py
++++ b/mythtv/bindings/python/MythTV/wikiscripts/wikiscripts.py
+@@ -2,19 +2,34 @@
+ # -*- coding: UTF-8 -*-
+ #----------------------
+
+-from urllib import urlopen
++try:
++ from urllib import urlopen
++except ImportError:
++ from urllib.request import urlopen
+ from time import time
+-from thread import start_new_thread, allocate_lock
++try:
++ from thread import start_new_thread, allocate_lock
++except ImportError:
++ from _thread import start_new_thread, allocate_lock
+ from time import sleep, time
+-from MythTV import OrdDict
+ import threading
+-import cPickle
+-import Queue
++try:
++ import cPickle as pickle
++except:
++ import pickle
++from MythTV import OrdDict
++try:
++ import Queue
++except ImportError:
++ import queue as Queue
+ import lxml
+ import lxml.html
+ import os
++import sys
+ import stat
+
++from builtins import input
++
+ BASEURL = 'https://www.mythtv.org/wiki'
+
+ def getScripts():
+@@ -22,7 +37,7 @@ def getScripts():
+
+ def getPage(**kwargs):
+ url = "{0}?{1}".format(BASEURL,
+- '&'.join(['{0}={1}'.format(k,v) for k,v in
kwargs.items()]))
++ '&'.join(['{0}={1}'.format(k,v) for k,v in
list(kwargs.items())]))
+ return lxml.html.parse(urlopen(url)).getroot()
+
+ def getWhatLinksHere(page):
+@@ -38,7 +53,7 @@ class Script( object ):
+ _queue = Queue.Queue()
+ _pool = []
+ _running = True
+-
++
+
+ _valid = False
+ _xp_info = lxml.etree.XPath("//span[@id='script-info']/text()")
+@@ -59,7 +74,8 @@ def getAll(cls, refresh=False):
+ return []
+ cls._dumpCache()
+ scripts = [s for s in scripts if s.isValid()]
+- scripts.sort()
++ #scripts.sort() ### does not work in python3
++ scripts.sort(key=lambda s: s.url)
+ return scripts
+
+ @classmethod
+@@ -107,7 +123,7 @@ def __init__(self, url, refresh=False, pool=4):
+ t = threading.Thread(target=self.processQueue)
+ t.start()
+ self._pool.append(t)
+-
++
+ def isValid(self): return self._valid
+
+ def processPage(self):
+@@ -123,7 +139,7 @@ def processPage(self):
+
+ def getInfo(self, etree):
+ text = self._xp_info(etree)[0].strip().split('\n')
+- for i in reversed(range(len(text))):
++ for i in reversed(list(range(len(text)))):
+ if '=' not in text[i]:
+ text[i-1] += text.pop(i)
+ self.info = OrdDict([a.split('=') for a in text])
+@@ -144,14 +160,14 @@ def getScript(self, etree):
+ code = self._xp_code(etree)
+ name = ''
+ size = 0
+- for i in range(len(code)):
++ for i in list(range(len(code))):
+ names[i] = str(names[i])
+ if size < len(code[i]):
+ size = len(code[i])
+ name = names[i]
+ code[i] = code[i].lstrip()
+ code[i] = code[i].replace(u'\xa0',' ')
+- self.code = dict(zip(names,code))
++ self.code = dict(list(zip(names,code)))
+ if self.info.name == 'unnamed':
+ self.info.name = name
+
+@@ -187,12 +203,16 @@ def _loadCache(cls, refresh):
+ if refresh:
+ cls._cache = {}
+ return
+-
+- path = '/tmp/mythwikiscripts.pickle'
++ if (sys.version_info[0] == 2):
++ path = '/tmp/mythwikiscripts.pickle'
++ fmode = 'r'
++ else:
++ path = '/tmp/mythwikiscripts.pickle3'
++ fmode = 'rb'
+ if os.access(path, os.F_OK):
+ try:
+- fd = open(path,'r')
+- cls._cache = cPickle.load(fd)
++ fd = open(path, fmode)
++ cls._cache = pickle.load(fd)
+ fd.close()
+ except:
+ os.remove(path)
+@@ -202,10 +222,16 @@ def _loadCache(cls, refresh):
+
+ @classmethod
+ def _dumpCache(cls):
+- path = '/tmp/mythwikiscripts.pickle'
++ ### XXX ToDo allign pickle protocol versions
+ try:
+- fd = open(path,'w')
+- cls._cache = cPickle.dump(cls._cache,fd)
++ if (sys.version_info[0] == 2):
++ path = '/tmp/mythwikiscripts.pickle'
++ fd = open(path,'w')
++ cls._cache = pickle.dump(cls._cache,fd)
++ else:
++ path = '/tmp/mythwikiscripts.pickle3'
++ fd = open(path,'wb')
++ cls._cache = pickle.dump(cls._cache,fd, protocol=1)
+ fd.close()
+ except:
+ os.remove(path)
+diff --git a/mythtv/bindings/python/scripts/mythwikiscripts
b/mythtv/bindings/python/scripts/mythwikiscripts
+index 673ebf2a708..8de0fff120c 100755
+--- a/mythtv/bindings/python/scripts/mythwikiscripts
++++ b/mythtv/bindings/python/scripts/mythwikiscripts
+@@ -7,14 +7,14 @@ import sys
+
+ def get_input(comment, items):
+ while True:
+- print ' 0: Exit'
++ print(' 0: Exit')
+ for i,item in enumerate(items):
+- print ' {0}: {1}'.format(i+1, item)
+- print comment
++ print(' {0}: {1}'.format(i+1, item))
++ print(comment)
+
+ while True:
+ try:
+- i = raw_input('> ')
++ i = input('> ')
+ if i == 'list':
+ break
+ elif i in ('exit','0'):
+@@ -23,17 +23,17 @@ def get_input(comment, items):
+ i = int(i)-1
+ if i in range(len(items)):
+ return i
+- print "Selection is outside range. Use 'list' to list
options."
++ print("Selection is outside range. Use 'list' to list
options.")
+
+ except KeyboardInterrupt:
+ sys.exit()
+ except EOFError:
+ sys.exit()
+ except:
+- print 'This input only accepts integers. Use Ctrl-C to exit'
++ print('This input only accepts integers. Use Ctrl-C to exit')
+
+ def selectCat(catlist):
+- keys = catlist.keys()
++ keys = list(catlist.keys())
+ while True:
+ inp = get_input('Please choose a category.', keys)
+ if inp == -1:
+@@ -49,18 +49,18 @@ def selectScript(scriptlist):
+ saveScript(scriptlist[inp])
+
+ def saveScript(script):
+- print 'Author: '+script.info.author
+- print 'Description: '+script.info.long
+- print 'Webpage: '+script.info.webpage
++ print('Author: '+script.info.author)
++ print('Description: '+script.info.long)
++ print('Webpage: '+script.info.webpage)
+
+- for name in script.code.keys():
++ for name in list(script.code.keys()):
+ if name[0] in ('/', '~'):
+ path = name
+ else:
+ path = '~/bin/'+name
+
+ try:
+- inp = raw_input('Save to [{0}]:'.format(path))
++ inp = input('Save to [{0}]:'.format(path))
+ except EOFError:
+ sys.exit()
+ except KeyboardInterrupt:
+@@ -81,7 +81,7 @@ if __name__ == '__main__':
+ print "This script parses the wiki, and may take several minutes."
+ tic = time()
+ tmp = Script.getAll()
+- print 'Loading took %0.2f seconds' % (time()-tic)
++ print('Loading took %0.2f seconds' % (time()-tic))
+
+ scripts = {}
+ for script in tmp:
+
+From a90e2db32896ba1ca0851144006ba01a3fd6ae56 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 16:47:37 +0200
+Subject: [PATCH 19/28] Add compatibility to python3 to tmdb3 module.
+
+How to check and reproduce:
+
+Check out mythtv from git.
+
+Set the pythonpath to the folder holding the bindings "MythTV":
+Example:
+export PYTHONPATH=$HOME/dnalor_mythtv/mythtv/mythtv/bindings/python
+
+Create a symlink for tmdb3
+$ cd MythTV
+$ ln -s ../tmdb3/tmdb3 tmdb3
+
+Copy the folder
+mythtv/mythtv/programs/scripts/metadata/Movie
+to
+$PYTHONPATH/share/mythtv/metadata/Movie
+
+Note: This is noa absolutely necessary, but it is needed for testing
+the 'tmdb3' api together with the 'MythTV' bindings.
+
+Clear the tmdb3 cache:
+Create a script:
+CF=$HOME/.mythtv/cache/pytmdb3.cache
+rm $CF
+touch $CF
+
+Delete all '*.pyc' files:
+find . -name "*.pyc" -type f -delete
+
+Switch to python3:
+
+Make python3 the default when called via `python` or `#!/usr/bin/env python`:
+Put a symlink from python3 to python on top of the `PATH` environment:
+As root, type
+ mkdir -p /opt/python3
+ ln -s `which python3` /opt/python3/python
+
+Then in the terminal add the path as first entry:
+$ export PATH=/opt/python3/:$PATH
+
+test with
+$ env python --version
+
+Run the following tests from the path $PYTHONPATH :
+
+$ python ./share/mythtv/metadata/Movie/tmdb3.py --version
+$ python ./share/mythtv/metadata/Movie/tmdb3.py --test
+$ python ./share/mythtv/metadata/Movie/tmdb3.py -M -l en 'Indiana Jones'
+$ python ./share/mythtv/metadata/Movie/tmdb3.py -M -l de 'Indiana Jones'
+$ python ./share/mythtv/metadata/Movie/tmdb3.py -D -l de 217
+$ python ./share/mythtv/metadata/Movie/tmdb3.py -l en -C 10
+$ python ./share/mythtv/metadata/Movie/tmdb3.py -l en -a US -C 10
+$ python ./share/mythtv/metadata/Movie/tmdb3.py -l en -a de -C 10
+$ python ./share/mythtv/metadata/Movie/tmdb3.py -l en -a de -C 10 --debug
+---
+ .../python/tmdb3/scripts/populate_locale.py | 4 +-
+ .../bindings/python/tmdb3/scripts/pytmdb3.py | 8 +--
+ .../bindings/python/tmdb3/tmdb3/__init__.py | 12 ++---
+ mythtv/bindings/python/tmdb3/tmdb3/cache.py | 10 ++--
+ .../python/tmdb3/tmdb3/cache_engine.py | 6 ++-
+ .../bindings/python/tmdb3/tmdb3/cache_file.py | 20 ++++---
+ .../bindings/python/tmdb3/tmdb3/cache_null.py | 2 +-
+ mythtv/bindings/python/tmdb3/tmdb3/locales.py | 2 +-
+ mythtv/bindings/python/tmdb3/tmdb3/pager.py | 9 +++-
+ mythtv/bindings/python/tmdb3/tmdb3/request.py | 53 ++++++++++++-------
+ .../bindings/python/tmdb3/tmdb3/tmdb_api.py | 31 ++++++-----
+ .../bindings/python/tmdb3/tmdb3/tmdb_auth.py | 6 +--
+ mythtv/bindings/python/tmdb3/tmdb3/util.py | 24 ++++-----
+ .../programs/scripts/metadata/Movie/tmdb3.py | 43 +++++++++------
+ 14 files changed, 135 insertions(+), 95 deletions(-)
+
+diff --git a/mythtv/bindings/python/tmdb3/scripts/populate_locale.py
b/mythtv/bindings/python/tmdb3/scripts/populate_locale.py
+index d1aee4dd1b6..2f6ec6e5533 100755
+--- a/mythtv/bindings/python/tmdb3/scripts/populate_locale.py
++++ b/mythtv/bindings/python/tmdb3/scripts/populate_locale.py
+@@ -23,7 +23,7 @@ def sanitize(name):
+ while True:
+ line = fd.readline()
+ if len(line) == 0:
+- print "code endpoint not found, aborting!"
++ print ("code endpoint not found, aborting!")
+ sys.exit(1)
+ if line.startswith('########'):
+ endpt = fd.tell()
+@@ -44,7 +44,7 @@ def sanitize(name):
+ # skip empty 639-1 code
+ continue
+ name, _, _, iso639_2, iso639_1 = [t.text for t in row]
+-
++
+ fd.write('Language("{0}", "{1}",
u"{2}")\n'.format(iso639_1, iso639_2,
sanitize(name).encode('utf8')))
+
+ root =
lxml.html.parse('http://www.iso.org/iso/country_codes/iso_3166_code_l...
+diff --git a/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
b/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
+index afce681e2cb..0bc68d1a025 100755
+--- a/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
++++ b/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
+@@ -23,10 +23,10 @@
+
+ if opts.version:
+ from tmdb3.tmdb_api import __title__, __purpose__, __version__, __author__
+- print __title__
+- print ""
+- print __purpose__
+- print "Version: "+__version__
++ print (__title__)
++ print ("")
++ print (__purpose)__
++ print ("Version: "+__version__)
+ sys.exit(0)
+
+ if opts.nocache:
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
b/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
+index d5e35b30e5a..d84e15ffeed 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
+@@ -1,12 +1,12 @@
+ #!/usr/bin/env python
+
+-from tmdb_api import Configuration, searchMovie, searchMovieWithYear, \
++from .tmdb_api import Configuration, searchMovie, searchMovieWithYear, \
+ searchPerson, searchStudio, searchList, searchCollection, \
+ searchSeries, Person, Movie, Collection, Genre, List, \
+ Series, Studio, Network, Episode, Season, __version__
+-from request import set_key, set_cache
+-from locales import get_locale, set_locale
+-from tmdb_auth import get_session, set_session
+-from cache_engine import CacheEngine
+-from tmdb_exceptions import *
++from .request import Request, set_key, set_cache
++from .locales import get_locale, set_locale
++from .tmdb_auth import get_session, set_session
++from .cache_engine import CacheEngine
++from .tmdb_exceptions import *
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/cache.py
b/mythtv/bindings/python/tmdb3/tmdb3/cache.py
+index 463d7a2c424..9122ef21edc 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/cache.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/cache.py
+@@ -10,11 +10,11 @@
+ import time
+ import os
+
+-from tmdb_exceptions import *
+-from cache_engine import Engines
++from .tmdb_exceptions import *
++from .cache_engine import Engines
+
+-import cache_null
+-import cache_file
++from . import cache_null
++from . import cache_file
+
+
+ class Cache(object):
+@@ -44,7 +44,7 @@ def _import(self, data=None):
+ self._age = max(self._age, obj.creation)
+
+ def _expire(self):
+- for k, v in self._data.items():
++ for k, v in list(self._data.items()):
+ if v.expired:
+ del self._data[k]
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/cache_engine.py
b/mythtv/bindings/python/tmdb3/tmdb3/cache_engine.py
+index 11019551cbf..150bb1eab12 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/cache_engine.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/cache_engine.py
+@@ -9,7 +9,9 @@
+
+ import time
+ from weakref import ref
++import sys
+
++from future.utils import with_metaclass
+
+ class Engines(object):
+ """
+@@ -43,8 +45,8 @@ def __init__(cls, name, bases, attrs):
+ Engines.register(cls)
+
+
+-class CacheEngine(object):
+- __metaclass__ = CacheEngineType
++class CacheEngine(with_metaclass(CacheEngineType, object)):
++
+ name = 'unspecified'
+
+ def __init__(self, parent):
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py
b/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py
+index 4e965815c34..85455b7c554 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py
+@@ -16,10 +16,14 @@
+ import os
+ import io
+
+-from cStringIO import StringIO
++try:
++ from cStringIO import StringIO
++except ImportError:
++ # from io import BytesIO as StringIO
++ from io import StringIO
+
+-from tmdb_exceptions import *
+-from cache_engine import CacheEngine, CacheObject
++from .tmdb_exceptions import *
++from .cache_engine import CacheEngine, CacheObject
+
+ ####################
+ # Cache File Format
+@@ -123,7 +127,7 @@ def parse_filename(filename):
+ if filename.startswith('~'):
+ # check for home directory
+ return os.path.expanduser(filename)
+- elif (ord(filename[0]) in (range(65, 91) + range(99, 123))) \
++ elif (ord(filename[0]) in (list(range(65, 91) + range(99, 123)))) \
+ and (filename[1:3] == ':\\'):
+ # check for absolute drive path (e.g. C:\...)
+ return filename
+@@ -195,7 +199,7 @@ def data(self, value):
+ def load(self, fd):
+ fd.seek(self.position)
+ self._buff.seek(0)
+- self._buff.write(fd.read(self.size))
++ self._buff.write(fd.read(self.size).decode('utf-8'))
+
+ def dumpslot(self, fd):
+ pos = fd.tell()
+@@ -204,7 +208,7 @@ def dumpslot(self, fd):
+ def dumpdata(self, fd):
+ self.size
+ fd.seek(self.position)
+- fd.write(self._buff.getvalue())
++ fd.write(self._buff.getvalue().encode('utf-8'))
+
+
+ class FileEngine( CacheEngine ):
+@@ -266,7 +270,7 @@ def _init_cache(self):
+ def get(self, date):
+ self._init_cache()
+ self._open('r+b')
+-
++
+ with Flock(self.cachefd, Flock.LOCK_SH):
+ # return any new objects in the cache
+ return self._read(date)
+@@ -371,7 +375,7 @@ def _write(self, data):
+ else:
+ # rewrite cache file from scratch
+ # pull data from parent cache
+- data.extend(self.parent()._data.values())
++ data.extend(list(self.parent()._data.values()))
+ data.sort(key=lambda x: x.creation)
+ # write header
+ size = len(data) + self.preallocate
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/cache_null.py
b/mythtv/bindings/python/tmdb3/tmdb3/cache_null.py
+index 8c360da83a0..0a20ae0d18d 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/cache_null.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/cache_null.py
+@@ -7,7 +7,7 @@
+ # Purpose: Null caching engine for debugging purposes
+ #-----------------------
+
+-from cache_engine import CacheEngine
++from .cache_engine import CacheEngine
+
+
+ class NullEngine(CacheEngine):
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/locales.py
b/mythtv/bindings/python/tmdb3/tmdb3/locales.py
+index 0ef0310bb1c..319f26251ac 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/locales.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/locales.py
+@@ -6,7 +6,7 @@
+ # Author: Raymond Wagner
+ #-----------------------
+
+-from tmdb_exceptions import *
++from .tmdb_exceptions import *
+ import locale
+
+ syslocale = None
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/pager.py
b/mythtv/bindings/python/tmdb3/tmdb3/pager.py
+index ebcb9d2fa0c..64c0a583f51 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/pager.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/pager.py
+@@ -8,6 +8,11 @@
+
+ from collections import Sequence, Iterator
+
++try:
++ xrange
++except NameError:
++ xrange = range
++
+
+ class PagedIterator(Iterator):
+ def __init__(self, parent):
+@@ -18,12 +23,14 @@ def __init__(self, parent):
+ def __iter__(self):
+ return self
+
+- def next(self):
++ def __next__(self):
+ self._index += 1
+ if self._index == self._len:
+ raise StopIteration
+ return self._parent[self._index]
+
++ next = __next__ # for Python2
++
+
+ class UnpagedData(object):
+ def copy(self):
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/request.py
b/mythtv/bindings/python/tmdb3/tmdb3/request.py
+index 1b6663dcfd7..d87503e5a80 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/request.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/request.py
+@@ -8,18 +8,31 @@
+ # TMDb v3 API
+ #-----------------------
+
+-from tmdb_exceptions import *
+-from locales import get_locale
+-from cache import Cache
++from .tmdb_exceptions import *
++from .locales import get_locale
++from .cache import Cache
++
++# supports python2 and python3
++try:
++ from urllib import urlencode
++ from urllib2 import Request as _py23Request
++ from urllib2 import urlopen as _py23urlopen
++ from urllib2 import HTTPError as _py23HTTPError
++
++except (NameError, ImportError):
++ from urllib.parse import urlencode
++ from urllib.request import Request as _py23Request
++ from urllib.request import urlopen as _py23urlopen
++ from urllib.error import HTTPError as _py23HTTPError
++ import urllib.error
++ import urllib.parse
+
+-from urllib import urlencode
+-import urllib2
+ import json
+ import os
+ import time
+
+ DEBUG = False
+-cache = Cache(filename='pytmdb3.cache')
++cache = Cache(filename='pytmdb3.cache') # split cache file into py2 and py3 ?
+
+ #DEBUG = True
+ #cache = Cache(engine='null')
+@@ -44,7 +57,7 @@ def set_cache(engine=None, *args, **kwargs):
+ cache.configure(engine, *args, **kwargs)
+
+
+-class Request(urllib2.Request):
++class Request(_py23Request):
+ _api_key = None
+ _base_url = "http://api.themoviedb.org/3/"
+
+@@ -62,17 +75,17 @@ def __init__(self, url, **kwargs):
+ """
+ kwargs['api_key'] = self.api_key
+ self._url = url.lstrip('/')
+- self._kwargs = dict([(kwa, kwv) for kwa, kwv in kwargs.items()
++ self._kwargs = dict([(kwa, kwv) for kwa, kwv in list(kwargs.items())
+ if kwv is not None])
+
+ locale = get_locale()
+ kwargs = {}
+- for k, v in self._kwargs.items():
++ for k, v in list(self._kwargs.items()):
+ kwargs[k] = locale.encode(v)
+ url = '{0}{1}?{2}'\
+ .format(self._base_url, self._url, urlencode(kwargs))
+
+- urllib2.Request.__init__(self, url)
++ _py23Request.__init__(self, url)
+ self.add_header('Accept', 'application/json')
+ self.lifetime = 3600 # 1hr
+
+@@ -81,7 +94,7 @@ def new(self, **kwargs):
+ Create a new instance of the request, with tweaked arguments.
+ """
+ args = dict(self._kwargs)
+- for k, v in kwargs.items():
++ for k, v in list(kwargs.items()):
+ if v is None:
+ if k in args:
+ del args[k]
+@@ -93,24 +106,24 @@ def new(self, **kwargs):
+
+ def add_data(self, data):
+ """Provide data to be sent with POST."""
+- urllib2.Request.add_data(self, urlencode(data))
++ _py23Request.data = (self, urllib.parse.urlencode(data))
+
+ def open(self):
+ """Open a file object to the specified URL."""
+ try:
+ if DEBUG:
+- print 'loading '+self.get_full_url()
+- if self.has_data():
+- print ' '+self.get_data()
+- return urllib2.urlopen(self)
+- except urllib2.HTTPError, e:
++ print('loading '+self.get_full_url())
++ if self.data:
++ print(' '+self.data)
++ return _py23urlopen(self)
++ except _py23HTTPError as e:
+ raise TMDBHTTPError(e)
+
+ def read(self):
+ """Return result from specified URL as a
string."""
+ return self.open().read()
+
+- @cache.cached(urllib2.Request.get_full_url)
++ @cache.cached(_py23Request.get_full_url)
+ def readJSON(self):
+ """Parse result from specified URL as JSON
data."""
+ url = self.get_full_url()
+@@ -120,7 +133,7 @@ def readJSON(self):
+ # catch HTTP error from open()
+ data = json.load(self.open())
+ break
+- except TMDBHTTPError, e:
++ except TMDBHTTPError as e:
+ try:
+ # try to load whatever was returned
+ data = json.loads(e.response)
+@@ -132,7 +145,7 @@ def readJSON(self):
+ if data.get('status_code', 1) ==25:
+ # Sleep and retry query.
+ if DEBUG:
+- print 'Retry after {0}
seconds'.format(max(float(e.headers['retry-after']),10))
++ print('Retry after {0}
seconds'.format(max(float(e.headers['retry-after']),10)))
+ time.sleep(max(float(e.headers['retry-after']),10))
+ continue
+ else:
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
+index a28fda8e2d8..b8c44f6f702 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
+@@ -59,21 +59,23 @@
+ # 0.6.14 Add support for Lists
+ # 0.6.15 Add ability to search Collections
+ # 0.6.16 Make absent primary images return None (previously u'')
+-# 0.6.17 Add userrating/votes to Image, add overview to Collection, remove
++# 0.6.17 Add userrating/votes to Image, add overview to Collection, remove
+ # releasedate sorting from Collection Movies
+ # 0.7.0 Add support for television series data
++# 0.7.0.a Added compatibility to python3, tested with python 3.6 and 2.7
+
+-from request import set_key, Request
+-from util import Datapoint, Datalist, Datadict, Element, NameRepr, SearchRepr
+-from pager import PagedRequest
+-from locales import get_locale, set_locale
+-from tmdb_auth import get_session, set_session
+-from tmdb_exceptions import *
++from .request import set_key, Request
++from .util import Datapoint, Datalist, Datadict, Element, NameRepr, SearchRepr
++from .pager import PagedRequest
++from .locales import get_locale, set_locale
++from .tmdb_auth import get_session, set_session
++from .tmdb_exceptions import *
+
+ import json
+-import urllib
+-import urllib2
+ import datetime
++import sys
++
++IS_PY3 = sys.version_info[0] == 3
+
+ DEBUG = False
+
+@@ -234,7 +236,7 @@ class Image(Element):
+ width = Datapoint('width')
+ language = Datapoint('iso_639_1')
+ userrating = Datapoint('vote_average')
+- votes = Datapoint('vote_count')
++ votes = Datapoint('vote_count')
+
+ def sizes(self):
+ return ['original']
+@@ -265,11 +267,14 @@ def __eq__(self, other):
+ return self.filename == other.filename
+
+ # special handling for boolean to see if exists
+- def __nonzero__(self):
++ def __bool__(self):
+ if len(self.filename) == 0:
+ return False
+ return True
+
++ __nonzero__ = __bool__ # for python2
++
++
+ def __repr__(self):
+ # BASE62 encoded filename, no need to worry about unicode
+ return u"<{0.__class__.__name__}
'{0.filename}'>".format(self)
+@@ -405,7 +410,7 @@ class AppleTrailer(Element):
+ sources = Datadict('sources', handler=Trailer, attr='size')
+
+ def sizes(self):
+- return self.sources.keys()
++ return list(self.sources.keys())
+
+ def geturl(self, size=None):
+ if size is None:
+@@ -454,7 +459,7 @@ class GenreList(Element):
+ def _populate(self):
+ return Request('genre/list', language=self._locale.language)
+ return GenreList(locale=locale).genres
+-
++
+
+ class Studio(NameRepr, Element):
+ id = Datapoint('id', initarg=1)
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_auth.py
b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_auth.py
+index b447b5ab8da..030838c7f5a 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_auth.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_auth.py
+@@ -4,7 +4,7 @@
+ # Name: tmdb_auth.py
+ # Python Library
+ # Author: Raymond Wagner
+-# Purpose: Provide authentication and session services for
++# Purpose: Provide authentication and session services for
+ # calls against the TMDB v3 API
+ #-----------------------
+
+@@ -65,8 +65,8 @@ def fromIso(cls, isotime, sep='T'):
+ dt.append(tz)
+ return cls(*dt)
+
+-from request import Request
+-from tmdb_exceptions import *
++from .request import Request
++from .tmdb_exceptions import *
+
+ syssession = None
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/util.py
b/mythtv/bindings/python/tmdb3/tmdb3/util.py
+index a0d2e28a333..ee3cb4558a2 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/util.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/util.py
+@@ -7,9 +7,10 @@
+ #-----------------------
+
+ from copy import copy
+-from locales import get_locale
+-from tmdb_auth import get_session
++from .locales import get_locale
++from .tmdb_auth import get_session
+
++from future.utils import with_metaclass
+
+ class NameRepr(object):
+ """Mixin for __repr__ methods using 'name'
attribute."""
+@@ -74,7 +75,7 @@ def __call__(self):
+ self.apply(req.new(language=None, country=None).readJSON())
+ # re-apply the filtered first pass data over top the second
+ # unfiltered set. this is to work around the issue that the
+- # properties have no way of knowing when they should or
++ # properties have no way of knowing when they should or
+ # should not overwrite existing data. the cache engine will
+ # take care of the duplicate query
+ self.apply(req.readJSON())
+@@ -82,7 +83,7 @@ def __call__(self):
+ def apply(self, data, set_nones=True):
+ # apply data directly, bypassing callable function
+ unfilled = False
+- for k, v in self.lookup.items():
++ for k, v in list(self.lookup.items()):
+ if (k in data) and \
+ ((data[k] is not None) if callable(self.func) else True):
+ # argument received data, populate it
+@@ -222,7 +223,7 @@ def __set__(self, inst, value):
+ val._locale = inst._locale
+ val._session = inst._session
+
+- for source, dest in self.passthrough.items():
++ for source, dest in list(self.passthrough.items()):
+ setattr(val, dest, getattr(inst, source))
+
+ data.append(val)
+@@ -288,7 +289,7 @@ def __set__(self, inst, value):
+ val._locale = inst._locale
+ val._session = inst._session
+
+- for source, dest in self.passthrough.items():
++ for source, dest in list(self.passthrough.items()):
+ setattr(val, dest, getattr(inst, source))
+
+ data[self.getkey(val)] = val
+@@ -310,7 +311,7 @@ def __new__(mcs, name, bases, attrs):
+
+ for base in reversed(bases):
+ if isinstance(base, mcs):
+- for k, attr in base.__dict__.items():
++ for k, attr in list(base.__dict__.items()):
+ if isinstance(attr, Data):
+ # extract copies of each defined Data element from
+ # parent classes
+@@ -321,7 +322,7 @@ def __new__(mcs, name, bases, attrs):
+ # extract copies of each defined Poller function
+ # from parent classes
+ pollers[k] = attr.func
+- for k, attr in attrs.items():
++ for k, attr in list(attrs.items()):
+ if isinstance(attr, Data):
+ data[k] = attr
+ if '_populate' in attrs:
+@@ -332,7 +333,7 @@ def __new__(mcs, name, bases, attrs):
+ # which Data points
+ pollermap = dict([(k, []) for k in pollers])
+ initargs = []
+- for k, v in data.items():
++ for k, v in list(data.items()):
+ v.name = k
+ if v.initarg:
+ initargs.append(v)
+@@ -348,7 +349,7 @@ def __new__(mcs, name, bases, attrs):
+
+ # wrap each used poller function with a Poller class, and push into
+ # the new class attributes
+- for k, v in pollermap.items():
++ for k, v in list(pollermap.items()):
+ if len(v) == 0:
+ continue
+ lookup = dict([(attr.field, attr.name) for attr in v])
+@@ -398,6 +399,5 @@ def __call__(cls, *args, **kwargs):
+ return obj
+
+
+-class Element( object ):
+- __metaclass__ = ElementType
++class Element( with_metaclass( ElementType, object ) ):
+ _lang = 'en'
+diff --git a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+index 9739ecf320e..9bc812faa04 100755
+--- a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
++++ b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+@@ -26,11 +26,20 @@
+ # resolved upstream.
+ # 0.3.7 Add handling for TMDB site returning insufficient results from a
+ # query
++# 0.3.7.a : Added compatibiliy to python3, tested with python 3.6 and 2.7
+
+ from optparse import OptionParser
+ import sys
+ import signal
+
++def print_etree(etostr):
++ """lxml.etree.tostring is a bytes object in python3, and a str in
python2.
++ """
++ if sys.version_info[0] == 3:
++ sys.stdout.write(etostr.decode())
++ else:
++ sys.stdout.write(etostr)
++
+ def timeouthandler(signal, frame):
+ raise RuntimeError("Timed out")
+
+@@ -58,14 +67,14 @@ def buildSingle(inetref, opts):
+ if getattr(movie, j):
+ setattr(m, i, getattr(movie, j))
+ except TMDBRequestInvalid:
+- sys.stdout.write(etree.tostring(tree, encoding='UTF-8',
pretty_print=True,
++ print_etree(etree.tostring(tree, encoding='UTF-8',
pretty_print=True,
+ xml_declaration=True))
+ sys.exit()
+
+ if movie.title:
+ m.title = movie.title
+
+- releases = movie.releases.items()
++ releases = list(movie.releases.items())
+
+ # get the release date for the wanted country
+ # TODO if that is not part of the reply use the primary release date (Primary=true)
+@@ -73,14 +82,14 @@ def buildSingle(inetref, opts):
+ # if there is not a single release date in the reply, then leave it empty
+ if len(releases) > 0:
+ if opts.country:
+- # resort releases with selected country at top to ensure it
+- # is selected by the metadata libraries
+- r = zip(*releases)
+- if opts.country in r[0]:
+- index = r[0].index(opts.country)
+- releases.insert(0, releases.pop(index))
++ # resort releases with selected country at top to ensure it
++ # is selected by the metadata libraries
++ r = list(zip(*releases))
++ if opts.country in r[0]:
++ index = r[0].index(opts.country)
++ releases.insert(0, releases.pop(index))
+
+- m.releasedate = releases[0][1].releasedate
++ m.releasedate = releases[0][1].releasedate
+
+ m.inetref = str(movie.id)
+ if movie.collection:
+@@ -117,7 +126,7 @@ def buildSingle(inetref, opts):
+ 'height':str(poster.height),
+ 'width':str(poster.width)})
+ tree.append(m.toXML())
+- sys.stdout.write(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+ xml_declaration=True))
+ sys.exit()
+
+@@ -182,7 +191,7 @@ def buildList(query, opts):
+ # page limiter, dont want to overload the server
+ break
+
+- sys.stdout.write(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+ xml_declaration=True))
+ sys.exit(0)
+
+@@ -198,7 +207,7 @@ def buildCollection(inetref, opts):
+ try:
+ m.title = collection.name
+ except TMDBRequestInvalid:
+- sys.stdout.write(etree.tostring(tree, encoding='UTF-8',
pretty_print=True,
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+ xml_declaration=True))
+ sys.exit()
+ if collection.backdrop:
+@@ -210,7 +219,7 @@ def buildCollection(inetref, opts):
+ m.images.append({'type':'coverart', 'url':p.geturl(),
+ 'thumb':p.geturl(p.sizes()[0])})
+ tree.append(m.toXML())
+- sys.stdout.write(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+ xml_declaration=True))
+ sys.exit()
+
+@@ -227,7 +236,7 @@ def buildVersion():
+ etree.SubElement(version, "version").text = __version__
+ etree.SubElement(version, "accepts").text = 'tmdb.py'
+ etree.SubElement(version, "accepts").text = 'tmdb.pl'
+- sys.stdout.write(etree.tostring(version, encoding='UTF-8',
pretty_print=True,
++ print_etree(etree.tostring(version, encoding='UTF-8', pretty_print=True,
+ xml_declaration=True))
+ sys.exit(0)
+
+@@ -250,10 +259,10 @@ def performSelfTest():
+ import lxml
+ except:
+ err = 1
+- print "Failed to import python lxml library."
++ print ("Failed to import python lxml library.")
+
+ if not err:
+- print "Everything appears in order."
++ print ("Everything appears in order.")
+ sys.exit(err)
+
+ def main():
+@@ -301,7 +310,7 @@ def main():
+ if (not confdir) or (confdir == '/'):
+ confdir = os.environ.get('HOME', '')
+ if (not confdir) or (confdir == '/'):
+- print "Unable to find MythTV directory for metadata cache."
++ print ("Unable to find MythTV directory for metadata cache.")
+ sys.exit(1)
+ confdir = os.path.join(confdir, '.mythtv')
+ cachedir = os.path.join(confdir, 'cache')
+
+From 6c912c1f2f8d994e9fdf1ad96f11776a9b5f4d7d Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 17 Aug 2019 17:09:31 +0200
+Subject: [PATCH 20/28] Make mythpython script compatible to python3.
+
+---
+ mythtv/bindings/python/scripts/mythpython | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/bindings/python/scripts/mythpython
b/mythtv/bindings/python/scripts/mythpython
+index 8e52e996e02..e6176f313e9 100755
+--- a/mythtv/bindings/python/scripts/mythpython
++++ b/mythtv/bindings/python/scripts/mythpython
+@@ -9,20 +9,20 @@ if __name__ == '__main__':
+ name = ' {0}:'.format(name)
+ if s: ver = str(s)
+ elif j: ver = '.'.join([str(i) for i in j])
+- print '{0:<30}{1}'.format(name, ver)
++ print('{0:<30}{1}'.format(name, ver))
+
+ import MythTV.ttvdb.tvdb_api
+ import MythTV.tmdb3
+ import lxml.etree
+- print 'MythTV Python Bindings'
+- print ' local versions'
++ print('MythTV Python Bindings')
++ print( ' local versions')
+ print_version('bindings version', j=static.OWN_VERSION)
+ print_version('ttvdb version', MythTV.ttvdb.tvdb_api.__version__)
+ print_version('tmdb version', MythTV.tmdb3.__version__)
+- print ' external versions'
++ print(' external versions')
+ print_version('lxml version', lxml.etree.__version__)
+ print_version('{0} version'.format(static.dbmodule[0]),
j=static.dbmodule[1:])
+- print ' protocol versions'
++ print(' protocol versions')
+ print_version('backend', static.PROTO_VERSION)
+ print_version('schema', static.SCHEMA_VERSION)
+ print_version('music schema', static.MUSICSCHEMA_VERSION)
+
+From 4fddaee8bab691b535b55faa70b2edd8242aa753 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Thu, 22 Aug 2019 19:58:23 +0200
+Subject: [PATCH 21/28] Fix missing conversion to python3 in mythwikiscripts.
+
+---
+ mythtv/bindings/python/scripts/mythwikiscripts | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/bindings/python/scripts/mythwikiscripts
b/mythtv/bindings/python/scripts/mythwikiscripts
+index 8de0fff120c..2d614dd149d 100755
+--- a/mythtv/bindings/python/scripts/mythwikiscripts
++++ b/mythtv/bindings/python/scripts/mythwikiscripts
+@@ -77,8 +77,8 @@ def saveScript(script):
+ script.saveScript(name, os.path.expanduser(path))
+
+ if __name__ == '__main__':
+- print "MythTV Interactive Script Downloader."
+- print "This script parses the wiki, and may take several minutes."
++ print("MythTV Interactive Script Downloader.")
++ print("This script parses the wiki, and may take several minutes.")
+ tic = time()
+ tmp = Script.getAll()
+ print('Loading took %0.2f seconds' % (time()-tic))
+
+From a6462977030503afe0adfba7db00dd1247b5fc37 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Thu, 22 Aug 2019 20:05:38 +0200
+Subject: [PATCH 22/28] Fix compatibility to python3 in
+ 'VideoGrabber.grabInetref'.
+
+During testing of the use case shown in 'mythvidexport.py',
+I found an additional incompatibility to python3.
+
+This patch fixes #12243 as well, by providing the correct
+'inetref' id to the grabber script.
+---
+ mythtv/bindings/python/MythTV/system.py | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/system.py
b/mythtv/bindings/python/MythTV/system.py
+index 821faf5f128..9b89c3ae066 100644
+--- a/mythtv/bindings/python/MythTV/system.py
++++ b/mythtv/bindings/python/MythTV/system.py
+@@ -408,7 +408,10 @@ def grabInetref(self, inetref, season=None, episode=None):
+ args = (inetref, season, episode)
+ else:
+ args = (inetref,)
+- return self.command('-D', *args).next()
++ # inetref may expand to "my_grabber_script.xyz_1234" or
"9876"
++ args = list(args)
++ args[0] = args[0].split("_")[-1]
++ return next(self.command('-D', *args))
+
+ class SystemEvent( System ):
+ """
+
+From 8883b8204e797612f12a1e7f0f33599627acf234 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 24 Aug 2019 15:18:42 +0200
+Subject: [PATCH 23/28] Fix a leftover in tmdb3 binding and a typo introduced
+ in commit a90e2db
+
+This fixes a typo introduced in one of the previous commits and
+corrects python3 compatibiliy in the grabber script tmdb3.py.
+---
+ mythtv/bindings/python/tmdb3/scripts/pytmdb3.py | 2 +-
+ mythtv/programs/scripts/metadata/Movie/tmdb3.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
b/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
+index 0bc68d1a025..00df92cd9ad 100755
+--- a/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
++++ b/mythtv/bindings/python/tmdb3/scripts/pytmdb3.py
+@@ -25,7 +25,7 @@
+ from tmdb3.tmdb_api import __title__, __purpose__, __version__, __author__
+ print (__title__)
+ print ("")
+- print (__purpose)__
++ print (__purpose__)
+ print ("Version: "+__version__)
+ sys.exit(0)
+
+diff --git a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+index 9bc812faa04..52dd7f0352b 100755
+--- a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
++++ b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+@@ -337,7 +337,7 @@ def main():
+
+ if opts.collectiondata:
+ buildCollection(args[0], opts)
+- except RuntimeError, exc:
++ except RuntimeError as exc:
+ sys.stdout.write('ERROR: ' + str(exc) + ' exception')
+ sys.exit(1)
+
+
+From 5ab055e1a9da87dd0b5c2e78b832e399775256bf Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 24 Aug 2019 16:34:33 +0200
+Subject: [PATCH 24/28] Python Bindings: Remove deprecation warnings (python3)
+
+Python3 throws deprecation warnings about 'invalid escape sequences'
+when runnig from console with the '-W' switch.
+
+Note: Future python 3.x versions expose these warnings to the user and will
+treat them as errors, later on.
+
+This commit removes the deprecation warnings, beeing compatible
+to python2 and python3
+
+How to check: Run on the devel/python3 branch:
+
+$ python3 -Wd -m compileall -f -q MythTV
+ MythTV/connections.py:461: DeprecationWarning: invalid escape sequence \w
+ _res_help = {'jump': re.compile('(\w+)[ ]+- ([\w /,]+)'),
+ MythTV/connections.py:463: DeprecationWarning: invalid escape sequence \w
+ 'query': re.compile('query ([\w ]*\w+)[ \r\n]+- ([\w /,]+)'),
+ MythTV/connections.py:464: DeprecationWarning: invalid escape sequence \w
+ 'play': re.compile('play ([\w -:]*\w+)[ \r\n]+- ([\w
/:,\(\)]+)')}
+ MythTV/connections.py:476: DeprecationWarning: invalid escape sequence \.
+ reLOC =
re.compile('http://(?P<ip>[0-9\.]+):(?P<port>[0-9]+)/.*')
+ MythTV/connections.py:604: DeprecationWarning: invalid escape sequence \.
+ reLOC =
re.compile('http://(?P<ip>[0-9\.]+):(?P<port>[0-9]+)/.*')
+ MythTV/dataheap.py:998: DeprecationWarning: invalid escape sequence \s
+ sep = '(?:\s?(?:-|/)?\s?)?'
+ MythTV/dataheap.py:1002: DeprecationWarning: invalid escape sequence \d
+ '(\d{1,4})',
+ MythTV/dataheap.py:1004: DeprecationWarning: invalid escape sequence \d
+ '(\d{1,3})',
+ MythTV/dataheap.py:1007: DeprecationWarning: invalid escape sequence \d
+ regex2 = re.compile('(%s(?:Season%s\d*%s)*%s)$' \
+ MythTV/methodheap.py:454: DeprecationWarning: invalid escape sequence \[
+ bs =
BACKEND_SEP.replace('[','\[').replace(']','\]')
+ MythTV/methodheap.py:454: DeprecationWarning: invalid escape sequence \]
+ bs =
BACKEND_SEP.replace('[','\[').replace(']','\]')
+ MythTV/methodheap.py:458: DeprecationWarning: invalid escape sequence \.
+ '( HOSTNAME (?P<hostname>[a-zA-Z0-9_\.]*))?'
+ MythTV/methodheap.py:459: DeprecationWarning: invalid escape sequence \.
+ '( SENDER (?P<sender>[a-zA-Z0-9_\.]*))?'
+ MythTV/methodheap.py:1146: DeprecationWarning: invalid escape sequence \d
+ if re.match('(?:\d{1,3}\.){3}\d{1,3}',backend) or \
+ MythTV/mythproto.py:61: DeprecationWarning: invalid escape sequence \d
+ _reip = re.compile('(?:\d{1,3}\.){3}\d{1,3}')
+ MythTV/mythproto.py:220: DeprecationWarning: invalid escape sequence \[
+
'myth://((?P<group>.*)@)?(?P<host>[\[\]a-zA-Z0-9_\-\.]*)(:[0-9]*)?/(?P<file>.*)')
+ MythTV/mythproto.py:221: DeprecationWarning: invalid escape sequence \d
+ reip = re.compile('(?:\d{1,3}\.){3}\d{1,3}')
+---
+ mythtv/bindings/python/MythTV/connections.py | 10 ++++-----
+ mythtv/bindings/python/MythTV/dataheap.py | 20 +++++++++---------
+ mythtv/bindings/python/MythTV/methodheap.py | 22 ++++++++++----------
+ mythtv/bindings/python/MythTV/mythproto.py | 6 +++---
+ 4 files changed, 29 insertions(+), 29 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/connections.py
b/mythtv/bindings/python/MythTV/connections.py
+index b83a445ea1c..871b9604450 100644
+--- a/mythtv/bindings/python/MythTV/connections.py
++++ b/mythtv/bindings/python/MythTV/connections.py
+@@ -458,10 +458,10 @@ class FEConnection( object ):
+ 'key': lambda r: r=='OK',
+ 'query': lambda r: r,
+ 'play': lambda r: r=='OK'}
+- _res_help = {'jump': re.compile('(\w+)[ ]+- ([\w /,]+)'),
++ _res_help = {'jump': re.compile(r'(\w+)[ ]+- ([\w /,]+)'),
+ 'key': lambda r: r.split('\r\n')[4].split(',
'),
+- 'query': re.compile('query ([\w ]*\w+)[ \r\n]+- ([\w
/,]+)'),
+- 'play': re.compile('play ([\w -:]*\w+)[ \r\n]+- ([\w
/:,\(\)]+)')}
++ 'query': re.compile(r'query ([\w ]*\w+)[ \r\n]+- ([\w
/,]+)'),
++ 'play': re.compile(r'play ([\w -:]*\w+)[ \r\n]+- ([\w
/:,\(\)]+)')}
+
+ def __init__(self, host, port, deadline=10.0, test=True):
+ self.isConnected = False
+@@ -473,7 +473,7 @@ def __init__(self, host, port, deadline=10.0, test=True):
+
+ @classmethod
+ def fromUPNP(cls, timeout=5):
+- reLOC =
re.compile('http://(?P<ip>[0-9\.]+):(?P<port>[0-9]+)/.*')
++ reLOC =
re.compile(r'http://(?P<ip>[0-9\.]+):(?P<port>[0-9]+)/.*')
+ msearch = MSearch()
+ for res in msearch.searchMythFE(timeout):
+ ip, port = reLOC.match(res['location']).group(1,2)
+@@ -601,7 +601,7 @@ def __init__(self, host, port):
+
+ @classmethod
+ def fromUPNP(cls, timeout=5.0):
+- reLOC =
re.compile('http://(?P<ip>[0-9\.]+):(?P<port>[0-9]+)/.*')
++ reLOC =
re.compile(r'http://(?P<ip>[0-9\.]+):(?P<port>[0-9]+)/.*')
+ msearch = MSearch()
+ for res in msearch.searchMythBE(timeout):
+ ip, port = reLOC.match(res['location']).group(1,2)
+diff --git a/mythtv/bindings/python/MythTV/dataheap.py
b/mythtv/bindings/python/MythTV/dataheap.py
+index e3338fbb9de..2e6c6c294ee 100644
+--- a/mythtv/bindings/python/MythTV/dataheap.py
++++ b/mythtv/bindings/python/MythTV/dataheap.py
+@@ -995,17 +995,17 @@ def parseFilename(self):
+ for old in ('%20','_','.'):
+ filename = filename.replace(old, ' ')
+
+- sep = '(?:\s?(?:-|/)?\s?)?'
++ sep = r'(?:\s?(?:-|/)?\s?)?'
+ regex1 = re.compile(
+- sep.join(['^(.*[^s0-9])',
+- '(?:s|(?:Season))?',
+- '(\d{1,4})',
+- '(?:[ex/]|Episode)',
+- '(\d{1,3})',
+- '(.*)$']), re.I)
+-
+- regex2 = re.compile('(%s(?:Season%s\d*%s)*%s)$' \
+- % (sep, sep, sep, sep), re.I)
++ sep.join([r'^(.*[^s0-9])',
++ r'(?:s|(?:Season))?',
++ r'(\d{1,4})',
++ r'(?:[ex/]|Episode)',
++ r'(\d{1,3})',
++ r'(.*)$']), re.I)
++
++ title_pattern = r'(%s(?:Season%s\d*%s)*%s)$' % (sep, sep, sep, sep)
++ regex2 = re.compile(title_pattern, re.I)
+
+ match1 = regex1.search(filename)
+ if match1:
+diff --git a/mythtv/bindings/python/MythTV/methodheap.py
b/mythtv/bindings/python/MythTV/methodheap.py
+index aa581d7cf09..6cfa252063a 100644
+--- a/mythtv/bindings/python/MythTV/methodheap.py
++++ b/mythtv/bindings/python/MythTV/methodheap.py
+@@ -451,17 +451,17 @@ def eventMonitor(self, event=None):
+ class MythSystemEvent( BECache ):
+ class systemeventhandler( object ):
+ # decorator class for system events
+- bs =
BACKEND_SEP.replace('[','\[').replace(']','\]')
++ bs =
BACKEND_SEP.replace('[',r'\[').replace(']',r'\]')
+ re_process = re.compile(bs.join([
+- 'BACKEND_MESSAGE',
+- 'SYSTEM_EVENT (?P<event>[A-Z0-9_]*)'
+- '( HOSTNAME (?P<hostname>[a-zA-Z0-9_\.]*))?'
+- '( SENDER (?P<sender>[a-zA-Z0-9_\.]*))?'
+- '( CARDID (?P<cardid>[0-9]*))?'
+- '( CHANID (?P<chanid>[0-9]*))?'
+- '( STARTTIME (?P<starttime>[0-9-]*T[0-9-]))?'
+- '( SECS (?P<secs>[0-9]*))?',
+- 'empty']))
++ r'BACKEND_MESSAGE',
++ r'SYSTEM_EVENT (?P<event>[A-Z0-9_]*)'
++ r'( HOSTNAME (?P<hostname>[a-zA-Z0-9_\.]*))?'
++ r'( SENDER (?P<sender>[a-zA-Z0-9_\.]*))?'
++ r'( CARDID (?P<cardid>[0-9]*))?'
++ r'( CHANID (?P<chanid>[0-9]*))?'
++ r'( STARTTIME (?P<starttime>[0-9-]*T[0-9-]))?'
++ r'( SECS (?P<secs>[0-9]*))?',
++ r'empty']))
+
+ def __init__(self, func):
+ self.func = func
+@@ -1143,7 +1143,7 @@ def __init__(self, backend=None, port=None, db=None):
+ if backend is None:
+ # use master backend
+ backend = self.db.settings.NULL.MasterServerIP
+- if re.match('(?:\d{1,3}\.){3}\d{1,3}',backend) or \
++ if re.match(r'(?:\d{1,3}\.){3}\d{1,3}',backend) or \
+ check_ipv6(backend):
+ # process ip address
+ host = self.db._gethostfromaddr(backend)
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py
b/mythtv/bindings/python/MythTV/mythproto.py
+index 08a62546647..9fda5f10e8c 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -58,7 +58,7 @@ class _ConnHolder( object ):
+
+ logmodule = 'Python Backend Connection'
+ _shared = weakref.WeakValueDictionary()
+- _reip = re.compile('(?:\d{1,3}\.){3}\d{1,3}')
++ _reip = re.compile(r'(?:\d{1,3}\.){3}\d{1,3}')
+
+ def __repr__(self):
+ return "<%s 'myth://%s:%d/' at %s>" % \
+@@ -217,8 +217,8 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None,
\
+ db = DBCache(db)
+ log = MythLog('Python File Transfer', db=db)
+ reuri = re.compile(\
+-
'myth://((?P<group>.*)@)?(?P<host>[\[\]a-zA-Z0-9_\-\.]*)(:[0-9]*)?/(?P<file>.*)')
+- reip = re.compile('(?:\d{1,3}\.){3}\d{1,3}')
++
r'myth://((?P<group>.*)@)?(?P<host>[\[\]a-zA-Z0-9_\-\.]*)(:[0-9]*)?/(?P<file>.*)')
++ reip = re.compile(r'(?:\d{1,3}\.){3}\d{1,3}')
+
+ if mode not in ('r','w'):
+ raise TypeError("File I/O must be of type 'r' or
'w'")
+
+From 9dea0fa0cf0e8ac5f63abddb8055b034bfcc6ff3 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 31 Aug 2019 15:40:33 +0200
+Subject: [PATCH 25/28] Update Movie Grabber tmdb3.py to meet python3
+ compatibility, again
+
+- Fix another occurence of deprecated next() method
+
+- Check against python2 instead of python3 to be future proof.
+
+Tested with
+$ env python3 --version
+Python 3.6.8
+
+$ python ....metadata/Movie/tmdb3.py --version
+$ python ..../metadata/Movie/tmdb3.py --test
+$ python ..../metadata/Movie/tmdb3.py -M -l en 'Indiana Jones'
+$ python ..../metadata/Movie/tmdb3.py -M -l de 'Indiana Jones'
+$ python ..../metadata/Movie/tmdb3.py -D -l de 217
+$ python ..../metadata/Movie/tmdb3.py -l en -C 10
+$ python ..../metadata/Movie/tmdb3.py -l en -a US -C 10
+$ python ..../metadata/Movie/tmdb3.py -l en -a de -C 10
+$ python ..../metadata/Movie/tmdb3.py -l en -a de -C 10 --debug
+
+and python2 as well.
+---
+ mythtv/programs/scripts/metadata/Movie/tmdb3.py | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+index 52dd7f0352b..2757e2fa7a4 100755
+--- a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
++++ b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+@@ -35,10 +35,10 @@
+ def print_etree(etostr):
+ """lxml.etree.tostring is a bytes object in python3, and a str in
python2.
+ """
+- if sys.version_info[0] == 3:
+- sys.stdout.write(etostr.decode())
+- else:
++ if sys.version_info[0] == 2:
+ sys.stdout.write(etostr)
++ else:
++ sys.stdout.write(etostr.decode())
+
+ def timeouthandler(signal, frame):
+ raise RuntimeError("Timed out")
+@@ -149,7 +149,7 @@ def buildList(query, opts):
+ count = 0
+ while True:
+ try:
+- res = results.next()
++ res = next(results)
+ except StopIteration:
+ # end of results
+ break
+
+From 7e95039d622b39bfb06f639ea9f107b9a5cf5ff0 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sun, 1 Sep 2019 10:05:35 +0200
+Subject: [PATCH 26/28] TMDB3: Add compatibility to python3 for '__repr__'
+ methods
+
+This method is mostly used on terminal sessions like
+
+ >>> from MythTV.tmdb3 import searchMovie
+ >>> res = searchMovie('A New Hope')
+ >>> res
+ <Search Results: A New Hope>
+ >>> len(res)
+ 5
+ >>> res[0]
+ <Movie 'Star Wars' (1977)>
+ >>> res[1]
+ <Movie 'New Hope' (2012)>
+
+With python3, one will get an error response like
+"TypeError: __repr__ returned non-string (type bytes)"
+---
+ .../bindings/python/tmdb3/tmdb3/__init__.py | 3 ++
+ .../bindings/python/tmdb3/tmdb3/tmdb_api.py | 46 +++++++++----------
+ mythtv/bindings/python/tmdb3/tmdb3/util.py | 16 +++++--
+ 3 files changed, 38 insertions(+), 27 deletions(-)
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
b/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
+index d84e15ffeed..83c20cf3fe5 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/__init__.py
+@@ -1,5 +1,8 @@
+ #!/usr/bin/env python
+
++import sys
++IS_PY2 = sys.version_info[0] == 2
++
+ from .tmdb_api import Configuration, searchMovie, searchMovieWithYear, \
+ searchPerson, searchStudio, searchList, searchCollection, \
+ searchSeries, Person, Movie, Collection, Genre, List, \
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
+index b8c44f6f702..89ceb84983a 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
+@@ -64,8 +64,10 @@
+ # 0.7.0 Add support for television series data
+ # 0.7.0.a Added compatibility to python3, tested with python 3.6 and 2.7
+
++from . import IS_PY2
++
+ from .request import set_key, Request
+-from .util import Datapoint, Datalist, Datadict, Element, NameRepr, SearchRepr
++from .util import Datapoint, Datalist, Datadict, Element, NameRepr, SearchRepr,
tmdb3_repr
+ from .pager import PagedRequest
+ from .locales import get_locale, set_locale
+ from .tmdb_auth import get_session, set_session
+@@ -73,9 +75,6 @@
+
+ import json
+ import datetime
+-import sys
+-
+-IS_PY3 = sys.version_info[0] == 3
+
+ DEBUG = False
+
+@@ -317,8 +316,8 @@ def __eq__(self, other):
+ return self.country == other.country
+
+ def __repr__(self):
+- return u"<{0.__class__.__name__} '{0.title}'
({0.country})>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__} '{0.title}'
({0.country})>"\
++ .format(self))
+
+
+ class Person(Element):
+@@ -335,8 +334,8 @@ class Person(Element):
+ aliases = Datalist('also_known_as')
+
+ def __repr__(self):
+- return u"<{0.__class__.__name__} '{0.name}'>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__}
'{0.name}'>"\
++ .format(self))
+
+ def _populate(self):
+ return Request('person/{0}'.format(self.id))
+@@ -359,8 +358,8 @@ class Cast(Person):
+ order = Datapoint('order')
+
+ def __repr__(self):
+- return u"<{0.__class__.__name__} '{0.name}' as
'{0.character}'>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__} '{0.name}' as
'{0.character}'>"\
++ .format(self))
+
+
+ class Crew(Person):
+@@ -368,8 +367,8 @@ class Crew(Person):
+ department = Datapoint('department')
+
+ def __repr__(self):
+- return u"<{0.__class__.__name__}
'{0.name}','{0.job}'>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__}
'{0.name}','{0.job}'>"\
++ .format(self))
+
+
+ class Keyword(Element):
+@@ -377,8 +376,7 @@ class Keyword(Element):
+ name = Datapoint('name')
+
+ def __repr__(self):
+- return u"<{0.__class__.__name__} {0.name}>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__}
{0.name}>".format(self))
+
+
+ class Release(Element):
+@@ -386,8 +384,8 @@ class Release(Element):
+ country = Datapoint('iso_3166_1')
+ releasedate = Datapoint('release_date', handler=process_date)
+ def __repr__(self):
+- return u"<{0.__class__.__name__} {0.country},
{0.releasedate}>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__} {0.country},
{0.releasedate}>"\
++ .format(self))
+
+
+ class Trailer(Element):
+@@ -430,8 +428,8 @@ class Translation(Element):
+ englishname = Datapoint('english_name')
+
+ def __repr__(self):
+- return u"<{0.__class__.__name__} '{0.name}'
({0.language})>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__} '{0.name}'
({0.language})>"\
++ .format(self))
+
+
+ class Genre(NameRepr, Element):
+@@ -719,16 +717,16 @@ def _printable_name(self):
+ return s
+
+ def __repr__(self):
+- return u"<{0} {1}>".format(self.__class__.__name__,
+- self._printable_name()).encode('utf-8')
++ return tmdb3_repr(u"<{0} {1}>".format(self.__class__.__name__,
++ self._printable_name()))
+
+
+ class ReverseCast( Movie ):
+ character = Datapoint('character')
+
+ def __repr__(self):
+- return (u"<{0.__class__.__name__} '{0.character}' on
{1}>"
+- .format(self, self._printable_name()).encode('utf-8'))
++ return tmdb3_repr(u"<{0.__class__.__name__} '{0.character}' on
{1}>"
++ .format(self, self._printable_name()))
+
+
+ class ReverseCrew( Movie ):
+@@ -736,8 +734,8 @@ class ReverseCrew( Movie ):
+ job = Datapoint('job')
+
+ def __repr__(self):
+- return (u"<{0.__class__.__name__} '{0.job}' for {1}>"
+- .format(self, self._printable_name()).encode('utf-8'))
++ return tmdb3_repr(u"<{0.__class__.__name__} '{0.job}' for
{1}>"
++ .format(self, self._printable_name()))
+
+
+ class Collection(NameRepr, Element):
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/util.py
b/mythtv/bindings/python/tmdb3/tmdb3/util.py
+index ee3cb4558a2..18f88347fd5 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/util.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/util.py
+@@ -6,17 +6,26 @@
+ # Author: Raymond Wagner
+ #-----------------------
+
++from . import IS_PY2
++
+ from copy import copy
+ from .locales import get_locale
+ from .tmdb_auth import get_session
+
+ from future.utils import with_metaclass
+
++
++def tmdb3_repr(x):
++ if IS_PY2:
++ return(x.encode('utf-8'))
++ else:
++ return(x)
++
++
+ class NameRepr(object):
+ """Mixin for __repr__ methods using 'name'
attribute."""
+ def __repr__(self):
+- return u"<{0.__class__.__name__} '{0.name}'>"\
+- .format(self).encode('utf-8')
++ return tmdb3_repr(u"<{0.__class__.__name__}
'{0.name}'>".format(self))
+
+
+ class SearchRepr(object):
+@@ -26,7 +35,7 @@ class SearchRepr(object):
+ """
+ def __repr__(self):
+ name = self._name if self._name else self._request._kwargs['query']
+- return u"<Search Results:
{0}>".format(name).encode('utf-8')
++ return tmdb3_repr(u"<Search Results: {0}>".format(name))
+
+
+ class Poller(object):
+@@ -366,6 +375,7 @@ def __new__(mcs, name, bases, attrs):
+ [a.name for a in sorted(initargs, key=lambda x: x.initarg)])
+ return type.__new__(mcs, name, bases, attrs)
+
++
+ def __call__(cls, *args, **kwargs):
+ obj = cls.__new__(cls)
+ if ('locale' in kwargs) and (kwargs['locale'] is not None):
+
+From 1297759229ba345d26936a7e57cfb2cc3b7a9d1b Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sun, 1 Sep 2019 20:46:27 +0200
+Subject: [PATCH 27/28] MythTV: Add compatibility to python3 for '__repr__'
+ methods
+
+The 'repr()' method calls the '__repr__' method of an instanciated class
in
+python and is used mostly in the interactive python shell, like
+
+$ python2
+Python 2.7.15+ (default, Nov 27 2018, 23:36:35)
+
+>>> from MythTV import MythDB, Recorded
+>>> db = MythDB()
+>>> rec = Recorded((3002, 20190622125000), db = db)
+>>> rec # -----> Note: this calls __repr__ under the hood.
+b'<Recorded 'Reisezelt - Kurztrip','2019-06-47 12:50:00+02:00' at
0x7fcdb0faabe0>'
+>>>
+
+With Python3, this 'repr()' method returns a 'str' type (i.e. unicode),
+in contrast to Python2, where this method may return a native 'str' type (ascii)
+or an 'utf-8' encoded string.
+
+Using this in a Python3 shell, leads to the following Type-Error:
+
+ $ python3
+ Python 3.6.8 (default, Jan 14 2019, 11:02:34)
+ >>> from MythTV import MythDB, Recorded
+ >>> db = MythDB()
+ >>> rec = Recorded((3002, 20190622125000), db = db)
+ >>> rec
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ TypeError: __repr__ returned non-string (type NoneType)
+ >>>
+
+This patch adds compatibility to Python3, it simply selects the current
+python version when returning data from the '__repr__()' method.
+---
+ mythtv/bindings/python/MythTV/database.py | 16 ++++++++--------
+ mythtv/bindings/python/MythTV/dataheap.py | 19 ++++++++++---------
+ mythtv/bindings/python/MythTV/mythproto.py | 8 ++++----
+ mythtv/bindings/python/MythTV/system.py | 4 ++--
+ .../python/MythTV/utility/__init__.py | 3 ++-
+ .../bindings/python/MythTV/utility/other.py | 7 +++++++
+ 6 files changed, 33 insertions(+), 24 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/database.py
b/mythtv/bindings/python/MythTV/database.py
+index 885cd4d310e..86294414320 100644
+--- a/mythtv/bindings/python/MythTV/database.py
++++ b/mythtv/bindings/python/MythTV/database.py
+@@ -8,7 +8,7 @@
+ from MythTV.altdict import OrdDict, DictData
+ from MythTV.logging import MythLog
+ from MythTV.msearch import MSearch
+-from MythTV.utility import datetime, dt, _donothing, QuickProperty
++from MythTV.utility import datetime, dt, _donothing, QuickProperty, py23_repr
+ from MythTV.exceptions import MythError, MythDBError, MythTZError
+ from MythTV.connections import DBConnection, LoggedCursor, XMLConnection
+
+@@ -220,7 +220,7 @@ def __str__(self):
+ hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def __getstate__(self):
+ data = {'data':DictData.__getstate__(self)}
+@@ -463,7 +463,7 @@ def __init__(self, data):
+ self._changed = True
+ self.__hash__()
+ def __str__(self): return str(self.items())
+- def __repr__(self): return str(self).encode('utf-8')
++ def __repr__(self): return py23_repr(str(self))
+ def __hash__(self):
+ if self._changed:
+ self._hash = hash(sum(map(hash,self.values())))
+@@ -1116,7 +1116,7 @@ class _TableFields( OrdDict ):
+ """Provides a dictionary-like list of table
fieldnames"""
+ class _FieldData( OrdDict ):
+ def __str__(self): return str(list(self))
+- def __repr__(self): return str(self).encode('utf-8')
++ def __repr__(self): return py23_repr(str(self))
+ def __iter__(self): return self.iterkeys()
+ def __init__(self, result):
+ data = [(row[0],
+@@ -1135,7 +1135,7 @@ def __getitem__(self,key):
+ raise KeyError(str(key))
+ _localvars = ['_field_order','_db','_log']
+ def __str__(self): return str(list(self))
+- def __repr__(self): return str(self).encode('utf-8')
++ def __repr__(self): return py23_repr(str(self))
+ def __iter__(self): return self.iterkeys()
+ def __init__(self, db, log):
+ OrdDict.__init__(self)
+@@ -1160,7 +1160,7 @@ class _Settings( OrdDict ):
+ class _HostSettings( OrdDict ):
+ _localvars =
['_field_order','_log','_db','_host','_insert','_where']
+ def __str__(self): return str(list(self))
+- def __repr__(self): return str(self).encode('utf-8')
++ def __repr__(self): return py23_repr(str(self))
+ def __iter__(self): return self.iterkeys()
+ def __init__(self, db, log, host):
+ OrdDict.__init__(self)
+@@ -1234,7 +1234,7 @@ def getall(self):
+
+ _localvars = ['_field_order','_log','_db']
+ def __str__(self): return str(list(self))
+- def __repr__(self): return str(self).encode('utf-8')
++ def __repr__(self): return py23_repr(str(self))
+ def __iter__(self): return self.iterkeys()
+ def __init__(self, db, log):
+ OrdDict.__init__(self)
+@@ -1440,7 +1440,7 @@ def __str__(self):
+ (self.groupname, self.hostname,
+ self.dirname, hex(id(self)))
+
+- def __repr__(self): return str(self).encode('utf-8')
++ def __repr__(self): return py23_repr(str(self))
+
+ def _evalwheredat(self, wheredat=None):
+ DBData._evalwheredat(self, wheredat)
+diff --git a/mythtv/bindings/python/MythTV/dataheap.py
b/mythtv/bindings/python/MythTV/dataheap.py
+index 2e6c6c294ee..12c91ebaa48 100644
+--- a/mythtv/bindings/python/MythTV/dataheap.py
++++ b/mythtv/bindings/python/MythTV/dataheap.py
+@@ -10,7 +10,8 @@
+ from MythTV.database import *
+ from MythTV.system import Grabber, InternetMetadata, VideoMetadata
+ from MythTV.mythproto import ftopen, FileOps, Program
+-from MythTV.utility import CMPRecord, CMPVideo, MARKUPLIST, datetime, ParseSet
++from MythTV.utility import CMPRecord, CMPVideo, MARKUPLIST, datetime, ParseSet,\
++ py23_repr
+
+ import re
+ import locale
+@@ -166,7 +167,7 @@ def __str__(self):
+ % (self.title, self.type, hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def __init__(self, data=None, db=None, template=None):
+ DBDataWrite.__init__(self, data, db)
+@@ -334,7 +335,7 @@ def __str__(self):
+ self.starttime.isoformat(' '), hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def __init__(self, data=None, db=None):
+ if data is not None:
+@@ -576,7 +577,7 @@ def __str__(self):
+ self.starttime.isoformat(' '), hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def __init__(self, data=None, db=None):
+ if data is not None:
+@@ -614,7 +615,7 @@ def __str__(self):
+ self.starttime.isoformat(' '), hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def __init__(self, data=None, db=None):
+ if data is not None:
+@@ -658,7 +659,7 @@ def __str__(self):
+ (self.inetref, self.season, hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ coverart = Artwork('coverart')
+ fanart = Artwork('fanart')
+@@ -680,7 +681,7 @@ def __str__(self):
+ return u"<Job '%s' at %s>" % (self.id, hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def setComment(self,comment):
+ """Job.setComment(comment) -> None, updates
comment"""
+@@ -750,7 +751,7 @@ def __str__(self):
+ (self.chanid, self.name, hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ class Guide( CMPRecord, DBData ):
+ """
+@@ -767,7 +768,7 @@ def __str__(self):
+ self.starttime.isoformat(' '), hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def getRecStatus(self):
+ be = FileOps(db=self._db)
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py
b/mythtv/bindings/python/MythTV/mythproto.py
+index 9fda5f10e8c..1672ba22bdf 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -11,8 +11,8 @@
+ from MythTV.altdict import DictData
+ from MythTV.connections import BEConnection, BEEventConnection
+ from MythTV.database import DBCache
+-from MythTV.utility import CMPRecord, datetime, \
+- ParseEnum, CopyData, CopyData2, check_ipv6
++from MythTV.utility import CMPRecord, datetime, ParseEnum, \
++ CopyData, CopyData2, check_ipv6, py23_repr
+
+ from datetime import date
+ from time import sleep
+@@ -828,7 +828,7 @@ def __str__(self):
+ % (self.path, self.host, hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def __init__(self, raw):
+ DictData.__init__(self, raw)
+@@ -879,7 +879,7 @@ def __str__(self):
+ self.starttime.isoformat(' '), hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def __init__(self, raw, db=None):
+ DictData.__init__(self, raw)
+diff --git a/mythtv/bindings/python/MythTV/system.py
b/mythtv/bindings/python/MythTV/system.py
+index 9b89c3ae066..d1d7546c0eb 100644
+--- a/mythtv/bindings/python/MythTV/system.py
++++ b/mythtv/bindings/python/MythTV/system.py
+@@ -7,7 +7,7 @@
+ from MythTV.exceptions import MythError, MythDBError, MythFileError
+ from MythTV.logging import MythLog
+ from MythTV.altdict import DictData, OrdDict
+-from MythTV.utility import levenshtein, DequeBuffer
++from MythTV.utility import levenshtein, DequeBuffer, py23_repr
+ from MythTV.database import DBCache
+
+ from subprocess import Popen
+@@ -153,7 +153,7 @@ def __str__(self):
+ self.path, hex(id(self)))
+
+ def __repr__(self):
+- return str(self).encode('utf-8')
++ return py23_repr(str(self))
+
+ def append(self, *args):
+ """
+diff --git a/mythtv/bindings/python/MythTV/utility/__init__.py
b/mythtv/bindings/python/MythTV/utility/__init__.py
+index 9e8913adc51..4f8d060a23f 100644
+--- a/mythtv/bindings/python/MythTV/utility/__init__.py
++++ b/mythtv/bindings/python/MythTV/utility/__init__.py
+@@ -7,4 +7,5 @@
+
+ from .other import _donothing, SchemaUpdate, databaseSearch, deadlinesocket, \
+ MARKUPLIST, levenshtein, ParseEnum, ParseSet, CopyData, \
+- CopyData2, check_ipv6, QuickProperty, py23_str
++ CopyData2, check_ipv6, QuickProperty, py23_str, py23_repr
++
+diff --git a/mythtv/bindings/python/MythTV/utility/other.py
b/mythtv/bindings/python/MythTV/utility/other.py
+index d96cccf53f0..c8995e2251f 100644
+--- a/mythtv/bindings/python/MythTV/utility/other.py
++++ b/mythtv/bindings/python/MythTV/utility/other.py
+@@ -15,6 +15,7 @@
+ import weakref
+ import socket
+ import re
++import sys
+ from builtins import range
+
+ def _donothing(*args, **kwargs):
+@@ -586,6 +587,12 @@ def py23_str(value, ignore_errors=False):
+ except TypeError: # Wasn't a bytes object, no need to decode
+ return str(value)
+
++def py23_repr(x):
++ if sys.version_info[0] == 2:
++ return(x.encode('utf-8'))
++ else:
++ return(x)
++
+ class QuickProperty( object ):
+ def __init__(self, maskedvar, default=None, handler=None):
+ self.varname = maskedvar
+
+From 1466f61e5a515db6065cf97d3017d3d935284111 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Mon, 2 Sep 2019 23:37:50 +0200
+Subject: [PATCH 28/28] Add Compatibility to Python3 for metaclasses
+
+Python3 changed the way how to use a metaclass:
+
+See
+https://python-future.org/compatible_idioms.html#metaclasses
+
+On python2, one can test it on classes using the 'InputSingleton':
+
+MythTV is using metaclasses for getting the time zone info
+in utility/dt.py: The `class posixtzinfo` is designed as
+`InputSingleton`, which means that every subsequent call
+with the same parameter returns the same object.
+With the patches applied from #13299, one gets on python2:
+
+ $ python
+ Python 2.7.15+ (default, Nov 27 2018, 23:36:35)
+ >>> from MythTV.utility.dt import *
+ >>> from MythTV.utility.singleton import InputSingleton
+
+ >>> a = posixtzinfo()
+ >>> b= posixtzinfo('Etc/UTC')
+ >>> c = posixtzinfo("America/Anchorage")
+
+ >>> x = posixtzinfo()
+ >>> y = posixtzinfo('Etc/UTC')
+ >>> z = posixtzinfo("America/Anchorage")
+
+ >>> c == z
+ True
+ >>> a == x
+ True
+ >>> b == y
+ True
+
+With python3, i get:
+
+ >>> a == x
+ False
+ >>> b == y
+ False
+ >>> c == z
+ False
+
+which means, the 'InputSingleton' is not applied in the correct way.
+
+Python3 simply ignores the '__metaclass__' property of Python2.
+
+This patch adds the compatibility layer from python-future.
+
+Additionally, this patch fixes an error
+
+ 'RuntimeError: dictionary changed size during iteration'
+
+which is needed for python2 to python3 conversion and only occurs if
+the '__metaclass__' is not applied correctly (on python3).
+---
+ mythtv/bindings/python/MythTV/utility/dt.py | 6 +++---
+ mythtv/bindings/python/MythTV/utility/enum.py | 6 +++---
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/utility/dt.py
b/mythtv/bindings/python/MythTV/utility/dt.py
+index c669e3d767a..ef61749a56f 100644
+--- a/mythtv/bindings/python/MythTV/utility/dt.py
++++ b/mythtv/bindings/python/MythTV/utility/dt.py
+@@ -11,10 +11,11 @@
+ tzinfo as _pytzinfo, \
+ timedelta
+ from collections import namedtuple
++from future.utils import with_metaclass
+ import os
+ import re
+ import time
+-from . import singleton
++from .singleton import InputSingleton
+ time.tzset()
+
+ class basetzinfo( _pytzinfo ):
+@@ -116,12 +117,11 @@ def tzname(self, dt=None):
+ # database.
+ # """
+
+-class posixtzinfo( basetzinfo ):
++class posixtzinfo( with_metaclass(InputSingleton, basetzinfo) ):
+ """
+ Customized timezone class that can import timezone data from the local
+ POSIX zoneinfo files.
+ """
+- __metaclass__ = singleton.InputSingleton
+ _Count = namedtuple('Count', \
+ 'gmt_indicators std_indicators leap_seconds '+\
+ 'transitions types abbrevs')
+diff --git a/mythtv/bindings/python/MythTV/utility/enum.py
b/mythtv/bindings/python/MythTV/utility/enum.py
+index 3875054c7b1..a5dbec5abf1 100644
+--- a/mythtv/bindings/python/MythTV/utility/enum.py
++++ b/mythtv/bindings/python/MythTV/utility/enum.py
+@@ -7,6 +7,7 @@
+ #------------------------------
+
+ from builtins import int
++from future.utils import with_metaclass
+
+ class EnumValue( object ):
+ _next = 0
+@@ -32,7 +33,7 @@ def _incr(cls, value):
+
+ class EnumType( type ):
+ def __new__(mcs, name, bases, attrs):
+- for k,v in attrs.items():
++ for k,v in list(attrs.items()):
+ if isinstance(v, int):
+ EnumValue(k, v)
+ del attrs[k]
+@@ -51,8 +52,7 @@ def __getattr__(cls, key):
+ return cls(cls._values[key].value)
+ raise AttributeError(key)
+
+-class BaseEnum( object ):
+- __metaclass__ = EnumType
++class BaseEnum( with_metaclass( EnumType, object )):
+
+ def __init__(self, mode):
+ self.mode = mode
diff --git a/mythtv-space_in_GB.patch b/mythtv-space_in_GB.patch
index 97762fa..86130e0 100644
--- a/mythtv-space_in_GB.patch
+++ b/mythtv-space_in_GB.patch
@@ -1,6 +1,6 @@
--- a/mythtv/programs/mythbackend/httpstatus.cpp
+++ b/mythtv/programs/mythbackend/httpstatus.cpp
-@@ -467,9 +467,9 @@ void HttpStatus::FillStatusXML( QDomDocu
+@@ -466,9 +466,9 @@ void HttpStatus::FillStatusXML( QDomDocu
QDomElement group = pDoc->createElement("Group");
group.setAttribute("id" , fsID );
@@ -13,7 +13,7 @@
group.setAttribute("dir" , directory );
if (fsID == "total")
-@@ -496,9 +496,9 @@ void HttpStatus::FillStatusXML( QDomDocu
+@@ -495,9 +495,9 @@ void HttpStatus::FillStatusXML( QDomDocu
{
iExpirable = query.value(0).toLongLong();
}
@@ -26,65 +26,48 @@
total = group;
}
else
-@@ -1269,15 +1269,15 @@ int HttpStatus::PrintMachineInfo( QTextS
- QLocale c(QLocale::C);
+@@ -1267,36 +1267,36 @@ int HttpStatus::PrintMachineInfo( QTextS
+ << " <ul>\r\n";
os << " <li>Total Space: ";
-- sRep = c.toString(nTotal) + " MB";
-+ sRep = c.toString(nTotal) + " GB";
+- sRep = QString("%L1").arg(nTotal) + " MB";
++ sRep = QString("%L1").arg(nTotal) + " GB";
os << sRep << "</li>\r\n";
os << " <li>Space Used: ";
-- sRep = c.toString(nUsed) + " MB";
-+ sRep = c.toString(nUsed) + " GB";
+- sRep = QString("%L1").arg(nUsed) + " MB";
++ sRep = QString("%L1").arg(nUsed) + " GB";
os << sRep << "</li>\r\n";
os << " <li>Space Free: ";
-- sRep = c.toString(nFree) + " MB";
-+ sRep = c.toString(nFree) + " GB";
+- sRep = QString("%L1").arg(nFree) + " MB";
++ sRep = QString("%L1").arg(nFree) + " GB";
os << sRep << "</li>\r\n";
if ((nLiveTV + nDeleted + nExpirable) > 0)
-@@ -1285,19 +1285,19 @@ int HttpStatus::PrintMachineInfo( QTextS
+ {
os << " <li>Space Available "
"After Auto-expire: ";
- sRep = c.toString(nFree + nLiveTV +
+- sRep = QString("%L1").arg(nUsed) + " MB";
++ sRep = QString("%L1").arg(nUsed) + " GB";
+ sRep = QString("%L1").arg(nFree + nLiveTV +
- nDeleted + nExpirable) + " MB";
+ nDeleted + nExpirable) + " GB";
os << sRep << "\r\n";
os << " <ul>\r\n";
os << " <li>Space Used by LiveTV:
";
-- sRep = c.toString(nLiveTV) + " MB";
-+ sRep = c.toString(nLiveTV) + " GB";
+- sRep = QString("%L1").arg(nLiveTV) + " MB";
++ sRep = QString("%L1").arg(nLiveTV) + " GB";
os << sRep << "</li>\r\n";
os << " <li>Space Used by "
"Deleted Recordings: ";
-- sRep = c.toString(nDeleted) + " MB";
-+ sRep = c.toString(nDeleted) + " GB";
+- sRep = QString("%L1").arg(nDeleted) + " MB";
++ sRep = QString("%L1").arg(nDeleted) + " GB";
os << sRep << "</li>\r\n";
os << " <li>Space Used by "
"Auto-expirable Recordings: ";
-- sRep = c.toString(nExpirable) + " MB";
-+ sRep = c.toString(nExpirable) + " GB";
+- sRep = QString("%L1").arg(nExpirable) + " MB";
++ sRep = QString("%L1").arg(nExpirable) + " GB";
os << sRep << "</li>\r\n";
os << " </ul>\r\n";
os << " </li>\r\n";
-@@ -1353,15 +1353,15 @@ int HttpStatus::PrintMachineInfo( QTextS
- QLocale c(QLocale::C);
-
- os << " <li>Total Space: ";
-- sRep = c.toString(nTotal) + " MB";
-+ sRep = c.toString(nTotal) + " GB";
- os << sRep << "</li>\r\n";
-
- os << " <li>Space Used: ";
-- sRep = c.toString(nUsed) + " MB";
-+ sRep = c.toString(nUsed) + " GB";
- os << sRep << "</li>\r\n";
-
- os << " <li>Space Free: ";
-- sRep = c.toString(nFree) + " MB";
-+ sRep = c.toString(nFree) + " GB";
- os << sRep << "</li>\r\n";
-
- os << " </ul>\r\n"
diff --git a/mythtv.spec b/mythtv.spec
index 4babd03..eca929e 100644
--- a/mythtv.spec
+++ b/mythtv.spec
@@ -57,12 +57,12 @@
%define desktop_applications mythfrontend mythtv-setup
# git has used to fetch fixes diff
-%define githash 6bd8cd499382fd8b132218274fb4ae326c2b0243
+%define githash 5cde0578d84926171b20c8f7e95a101e9b0b9457
%define shorthash %(c=%{githash}; echo ${c:0:10})
# MythTV Version string -- preferably the output from git describe
-%define vers_string v30.0-53-g6bd8cd4993
-%define rel_date 20190601
+%define vers_string v30.0-69-g5cde0578d8
+%define rel_date 20190904
%define rel_string .%{rel_date}git%{shorthash}
%define branch fixes/30
@@ -75,7 +75,7 @@
#
Name: mythtv
Version: 30.0
-Release: 8%{?rel_string}%{?dist}
+Release: 9%{?rel_string}%{?dist}
Summary: A digital video recorder (DVR) application
# The primary license is GPLv2+, but bits are borrowed from a number of
@@ -86,6 +86,8 @@ Source0:
https://github.com/MythTV/%{name}/archive/v%{version}/%{name}-%{
Patch0:
https://github.com/MythTV/%{name}/compare/v%{version}..%{shorthash}.patch
Patch1: %{name}-space_in_GB.patch
Patch2: %{name}-php72_fix.patch
+Patch3: mythtv-python3.patch
+Patch4: mythtv-py3_configure.patch
################################################################################
@@ -130,7 +132,7 @@ Patch2: %{name}-php72_fix.patch
%global py_prefix python
%endif
-%if 0%{?fedora} && 0%{?fedora} > 32
+%if 0%{?fedora} && 0%{?fedora} > 30
%global py_prefix python3
%else
%global py_prefix python2
@@ -315,6 +317,7 @@ BuildRequires: perl(IO::Socket::INET6)
%if %{with python}
BuildRequires: %{py_prefix}-devel
+BuildRequires: %{py_prefix}-lxml
%if 0%{?fedora} || 0%{?rhel} > 7
BuildRequires: %{py_prefix}-mysql
BuildRequires: %{py_prefix}-urlgrabber
@@ -680,7 +683,7 @@ Provides a PHP-based interface to interacting with MythTV.
%package -n %{py_prefix}-MythTV
Summary: Python2 bindings for MythTV
-%if 0%{?fedora} > 32
+%if 0%{?fedora} > 30
%{?python_provide:%python_provide python3-%{name}}
%else
%{?python_provide:%python_provide python2-%{name}}
@@ -947,7 +950,7 @@ pushd mythtv
--disable-vaapi \
%endif
--enable-bdjava \
-%if 0%{?fedora} > 32
+%if 0%{?fedora} > 30
--python=%{__python3} \
%else
--python=%{__python2} \
@@ -1059,7 +1062,7 @@ pushd mythplugins
--disable-mythnetvision \
%endif
--enable-opengl \
-%if 0%{?fedora} > 32
+%if 0%{?fedora} > 30
--python=%{__python3} \
%else
--python=%{__python2} \
@@ -1162,7 +1165,7 @@ popd
%endif
# Fixes ERROR: ambiguous python shebang in F30
-%if 0%{?fedora} > 32
+%if 0%{?fedora} > 30
find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/env python$:#!%{__python3}:' {} ';'
find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/python$:#!%{__python3}:' {} ';'
%else
@@ -1345,7 +1348,7 @@ exit 0
%if %{with python}
%files -n %{py_prefix}-MythTV
%{_bindir}/mythpython
-%if 0%{?fedora} > 32
+%if 0%{?fedora} > 30
%{python3_sitelib}/MythTV/
%{python3_sitelib}/MythTV-*.egg-info
%else
@@ -1464,6 +1467,10 @@ exit 0
%changelog
+* Wed Sep 04 2019 Richard Shaw <hobbes1069(a)gmail.com> -
30.0-9.20190904git5cde0578d8
+- Update to v30.0-69-g5cde0578d8.
+- Initial update for Python 3 compatibility using upstream pull request.
+
* Tue Jul 02 2019 Nicolas Chauvet <kwizart(a)gmail.com> -
30.0-8.20190601git6bd8cd4993
- Rebuilt for x265
diff --git a/v30.0..6bd8cd4993.patch b/v30.0..5cde0578d8.patch
similarity index 80%
rename from v30.0..6bd8cd4993.patch
rename to v30.0..5cde0578d8.patch
index 32ea09d..8cd52a2 100644
--- a/v30.0..6bd8cd4993.patch
+++ b/v30.0..5cde0578d8.patch
@@ -1,7 +1,7 @@
From 07fc9359b383b97d84cc756825db9c0f97583d33 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Wed, 16 Jan 2019 23:36:51 -0600
-Subject: [PATCH 01/53] Fix issues with recent live TV channels commit.
+Subject: [PATCH 01/69] Fix issues with recent live TV channels commit.
(cherry picked from commit ccf39fad9213c80ce8b7b4fd79dfa0ff63c090ca)
---
@@ -34,7 +34,7 @@ index 1c35d7ca4f0..dc2e412e7d6 100644
From c62e2733944311f3a0adf17e718f5fb60b006991 Mon Sep 17 00:00:00 2001
From: John Poet <jpoet(a)mythtv.org>
Date: Thu, 17 Jan 2019 21:25:51 -0700
-Subject: [PATCH 02/53] Move BottomLine: Allow user to save horizontal
+Subject: [PATCH 02/69] Move BottomLine: Allow user to save horizontal
adjustments as well.
---
@@ -99,7 +99,7 @@ index a38a2786e53..2d7b2a34082 100644
From 44fee4bc8a4a69b2ae2ee09fb6568f47d1f8269d Mon Sep 17 00:00:00 2001
From: Mark Kendall <mark.kendall(a)gmail.com>
Date: Sat, 19 Jan 2019 15:15:15 +0000
-Subject: [PATCH 03/53] OpenGLVideo: Fix texture filtering
+Subject: [PATCH 03/69] OpenGLVideo: Fix texture filtering
- this was most apparent using the default OpenGL profile and the kernel
deinterlacer but would have been impacting quality elsewhere.
@@ -131,7 +131,7 @@ index 8b572b8d767..484c3f9a667 100644
From 57e69de0ba76333d9cb6a08c51cd0837c155d94d Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Mon, 21 Jan 2019 17:31:18 -0600
-Subject: [PATCH 04/53] Fix libass logging.
+Subject: [PATCH 04/69] Fix libass logging.
Previously, logs from libass were accumulated until one happened to
end with a newline and then all were printed without newlines between
@@ -193,7 +193,7 @@ index c210b28efd9..6a48414c036 100644
From 18c9bc9c35f7cb58a8b67d7d53ed73a71aaf4994 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Mon, 21 Jan 2019 17:59:58 -0600
-Subject: [PATCH 05/53] Work around to get A/V subtitles working on Android.
+Subject: [PATCH 05/69] Work around to get A/V subtitles working on Android.
fontconfig doesn't yet work for us on Android. For the time being,
more explicitly set a font we know should exist. This was adapted
@@ -231,7 +231,7 @@ index 6a48414c036..f0322fe1ff8 100644
From 6b5490bfab0301c70ab79c313cf6b8369ed3507a Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Mon, 21 Jan 2019 20:55:52 -0600
-Subject: [PATCH 06/53] Cache FileSystemInfo data to avoid unwanted blocking.
+Subject: [PATCH 06/69] Cache FileSystemInfo data to avoid unwanted blocking.
Change the MainServer to cache updated, FileSystemInfo data and
optionally return the cached data when requested. The Scheduler uses
@@ -439,7 +439,7 @@ index b583161483f..5fd1d2fee74 100644
From 2b8211e8e5d1cd512f96dd31f7cecffc353c19ba Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Wed, 23 Jan 2019 17:12:11 -0600
-Subject: [PATCH 07/53] Simpify previous FileSystemInfo caching change.
+Subject: [PATCH 07/69] Simpify previous FileSystemInfo caching change.
(cherry picked from commit ab65230280cbeda555b0b25d79933d27a823540b)
---
@@ -526,7 +526,7 @@ index 5fd1d2fee74..db956207422 100644
From 39794c9b6cfa83a03d0f8b0589f67924a6d0f5a0 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Thu, 24 Jan 2019 23:39:20 -0600
-Subject: [PATCH 08/53] Don't double print the log timestamp and level on
+Subject: [PATCH 08/69] Don't double print the log timestamp and level on
Android.
(cherry picked from commit 86ad64b4ef6a044c3e5c439c06cbd2cb3ba5dcb6)
@@ -596,7 +596,7 @@ index 92de0eb539b..e127f40ae4d 100644
From 4fd4d574c6dacba22233d3eb3e6c9c4564a3c7ac Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Fri, 25 Jan 2019 10:09:50 -0600
-Subject: [PATCH 09/53] Restore (cached) call to GetFilesystemInfos() in
+Subject: [PATCH 09/69] Restore (cached) call to GetFilesystemInfos() in
AutoExpire.
A previous change removed it because it was believed to be redundant.
@@ -629,7 +629,7 @@ index 415fa1ae25a..52463638a55 100644
From b06510032478ec887fd2491b5f9a5dad415c30f0 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Sat, 26 Jan 2019 11:28:54 -0600
-Subject: [PATCH 10/53] Don't start RingBuffer thread if it's not open.
+Subject: [PATCH 10/69] Don't start RingBuffer thread if it's not open.
The RingBuffer thread will exit immediately if the underlying file
isn't open. That can cause the thread waiting for it to start to
@@ -657,7 +657,7 @@ index 29b226f6112..4a879f22341 100644
From f11c5aba6ed1e88c22f1b3b6828fc085e1699b97 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Sat, 26 Jan 2019 11:48:15 -0600
-Subject: [PATCH 11/53] Tweak some VB_FILE log levels to make it more useful at
+Subject: [PATCH 11/69] Tweak some VB_FILE log levels to make it more useful at
loglevel INTO.
(cherry picked from commit 85dc2a48dff419fe78cc6b9371bc46333247d06e)
@@ -705,7 +705,7 @@ index bd8aa4f18ed..36ad31099b0 100644
From e5672657dce54a8059a2437f5a4899dcc07d3d10 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Fri, 1 Feb 2019 18:37:43 -0600
-Subject: [PATCH 12/53] Completely move GetPlaybackURL() to libmythprotoserver.
+Subject: [PATCH 12/69] Completely move GetPlaybackURL() to libmythprotoserver.
It was copied previously but not completely marked as static in both
locations. This change completely removes it from the old location so
@@ -927,7 +927,7 @@ index 3fddaed4675..cb3afc320c7 100644
From 1c2068c62bd67f5458009a49412e8b50a4bafbe5 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Fri, 1 Feb 2019 20:04:46 -0600
-Subject: [PATCH 13/53] Missed a locking difference in the previous
+Subject: [PATCH 13/69] Missed a locking difference in the previous
GetPlaybackURL commit.
(cherry picked from commit 068c9664724122d0b116d29aea31850a4ae85ed6)
@@ -953,7 +953,7 @@ index 99ed5507318..bb79ae3e329 100644
From c6760c6210597453c140079628a509683b738f54 Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Sun, 10 Feb 2019 11:25:55 -0500
-Subject: [PATCH 14/53] Guide Grid: Fix missing text when using QT painter
+Subject: [PATCH 14/69] Guide Grid: Fix missing text when using QT painter
Grid icons, arrows and record status, caused text to show as blank.
Changed to paint the text first, which resolves the issue.
@@ -1003,7 +1003,7 @@ index 4fbd523e4a3..3115f675622 100644
From b774c4140b2b9e148ccf699d1ad746bc4c84289c Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Sun, 10 Feb 2019 12:17:53 -0500
-Subject: [PATCH 15/53] Remove debugging code.
+Subject: [PATCH 15/69] Remove debugging code.
Program guide fix included some debugging code.
@@ -1030,7 +1030,7 @@ index 3115f675622..106314344c8 100644
From 7c0dc9b25906e0bdf9f6cb878268c9d0fc2316bb Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Fri, 25 Jan 2019 15:20:30 -0500
-Subject: [PATCH 16/53] Fix spurious font mismatch errors.
+Subject: [PATCH 16/69] Fix spurious font mismatch errors.
Strip out any text between square brackets before comparing font
names. This will allow a font family name like "Droid Sans [MONO]" to
@@ -1070,7 +1070,7 @@ index 4e7fa3516a6..5354f9229f9 100644
From f7b66345c5da82915e9db25817c59f42182766ab Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Sat, 2 Feb 2019 14:42:40 -0500
-Subject: [PATCH 17/53] Update custom FreeBSD type declarations in videodev2.h.
+Subject: [PATCH 17/69] Update custom FreeBSD type declarations in videodev2.h.
(cherry picked from commit 209c15535056407ffd9af9fbf557b0f1c98dad8f)
---
@@ -1118,7 +1118,7 @@ index c64b5e5a305..6c2e9bb41c0 100644
From f0644ebd2a33f096dc1b8d97b36a22804d913c5f Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Fri, 15 Feb 2019 14:49:37 -0500
-Subject: [PATCH 18/53] AVSync2: Fix never-ending stutter on Live TV
+Subject: [PATCH 18/69] AVSync2: Fix never-ending stutter on Live TV
With AVSync2, Live TV could continue to stutter until pause
is pressed. Added a timecode reset so that it can add a small
@@ -1171,7 +1171,7 @@ index 5b80c452a3b..6c0c41ef565 100644
From 5100ef5870eed4012e02c0d62d4adb1f20c0f312 Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Fri, 15 Feb 2019 14:50:29 -0500
-Subject: [PATCH 19/53] AVSync2: Improve resync speed and accuracy
+Subject: [PATCH 19/69] AVSync2: Improve resync speed and accuracy
1. If out of sync by more than 200 ms fix the sync more quickly.
2. Measure audio and video timecode at the same instant.
@@ -1378,7 +1378,7 @@ index 7358d617a9d..4b295af28fe 100644
From 70a58c0a38c637657f74f28ad86e04a157163f57 Mon Sep 17 00:00:00 2001
From: Klaas de Waal <klaas.de.waal(a)gmail.com>
Date: Sun, 17 Feb 2019 13:35:42 -0600
-Subject: [PATCH 20/53] Don't leave HDHomeRun channels open after EIT scans.
+Subject: [PATCH 20/69] Don't leave HDHomeRun channels open after EIT scans.
The HDHomeRun unlocks the tuner due to inactivity but MythTV still
thinks it's open. The result is the next recording or EIT scan fails.
@@ -1412,7 +1412,7 @@ index 421aff6823a..00c7968a35a 100644
From 26034244e8b664f2ae0e74027653293625615a86 Mon Sep 17 00:00:00 2001
From: Roland Ernst <rcrernst(a)gmail.com>
Date: Mon, 18 Feb 2019 13:51:10 -0600
-Subject: [PATCH 21/53] Services API: Allow images/icons immediately after BE
+Subject: [PATCH 21/69] Services API: Allow images/icons immediately after BE
startup. Refs #13404
Register <QFileInfo> to QMetaType at backend startup.
@@ -1461,7 +1461,7 @@ index b87995d94aa..fed7eb30476 100644
From e3474f8afb7191d5593d5fa5baac24611842bbec Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Mon, 25 Feb 2019 14:45:13 -0500
-Subject: [PATCH 22/53] Program Guide: Fix for current selection text not
+Subject: [PATCH 22/69] Program Guide: Fix for current selection text not
showing
With certain themes (e.g. Blue Abstract) the current selection program name
@@ -1489,7 +1489,7 @@ index 106314344c8..dda428461ac 100644
From 042c180902bcdcf58ac12cef45a9cf0e5f348912 Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Thu, 7 Mar 2019 14:38:13 -0500
-Subject: [PATCH 23/53] Previously Recorded List: Fix 2 bugs
+Subject: [PATCH 23/69] Previously Recorded List: Fix 2 bugs
1. Time zone bug - In a USA time zone at 11 PM on tha last day of
the month you can see the following month listed, albeit with
@@ -1540,7 +1540,7 @@ index d084efff92b..33288df9af4 100644
From 90468ec49a0116068fffb11d799ae67e100ef1fe Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Sun, 24 Feb 2019 14:28:56 -0500
-Subject: [PATCH 24/53] Playback: AvSync2 fix fast forward/rewind
+Subject: [PATCH 24/69] Playback: AvSync2 fix fast forward/rewind
Fast forward and rewind were not handling frame timing correctly so that
they were dropping frames, resulting in no frames displayed in some cases.
@@ -1594,7 +1594,7 @@ index db5bb6f06eb..7abd60341f8 100644
From bceea69806d9d24fd1662edca23c2215bee2e8cf Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Mon, 4 Mar 2019 15:53:39 -0500
-Subject: [PATCH 25/53] AVSync2: Use frame timestamps to determine elapsed play
+Subject: [PATCH 25/69] AVSync2: Use frame timestamps to determine elapsed play
time
MythTV uses frame count to determine the elapsed time of a recording
@@ -1671,7 +1671,7 @@ index 4b295af28fe..088da8a04ea 100644
From 13f5fd83072a5b57c3aa3409d6db2038feb1f573 Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Mon, 4 Mar 2019 16:09:49 -0500
-Subject: [PATCH 26/53] Playback: Improve "Music Choice" detection
+Subject: [PATCH 26/69] Playback: Improve "Music Choice" detection
Some recordings could be incorrectly detected as "Music Choice",
resulting in audio-video sync problems. This improves detection to
@@ -1699,7 +1699,7 @@ index e735bd354d6..e428a19a503 100644
From 76ea9755b69cad0eb6595407a7dec3c63df9f8c5 Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Wed, 6 Mar 2019 13:45:46 -0500
-Subject: [PATCH 27/53] Playback: Fix jerkiness with codec-deinterlaced content
+Subject: [PATCH 27/69] Playback: Fix jerkiness with codec-deinterlaced content
Remove unneeded setting of m_fps that was overwriting the correct
value. m_fps is suppsed to contain the framerate before deinterlacing.
@@ -1727,7 +1727,7 @@ index b25516c7953..3641c2fc4fb 100644
From 60cfb7c05ae21d0049537aaa66f8f70a836a1138 Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Sun, 10 Mar 2019 15:06:41 -0400
-Subject: [PATCH 28/53] Fix compile error from bceea69806d
+Subject: [PATCH 28/69] Fix compile error from bceea69806d
Rename of member variables cause the cherry-pick to use the old
variable name.
@@ -1752,7 +1752,7 @@ index 64018b501d7..4ee216d02c4 100644
From 01cde24f00573a44f7c62f8ddf6cdcccb0a865a8 Mon Sep 17 00:00:00 2001
From: John Poet <jpoet(a)mythtv.org>
Date: Mon, 4 Feb 2019 15:14:50 -0700
-Subject: [PATCH 29/53] ExternalStreamhandler: Don't spam log files due to very
+Subject: [PATCH 29/69] ExternalStreamhandler: Don't spam log files due to very
low bitrate streams
(cherry picked from commit fa86a6a2935c305c97fa75fcd8fd82fe25eae66a)
@@ -1801,7 +1801,7 @@ index 4f541d30e5d..6de71f7ed94 100644
From 2327369fd558fcf00af6a280017560801534b44d Mon Sep 17 00:00:00 2001
From: John Poet <jpoet(a)mythtv.org>
Date: Tue, 5 Feb 2019 14:04:00 -0700
-Subject: [PATCH 30/53] RemoteFile::Read: Try and "resume" if read fails.
+Subject: [PATCH 30/69] RemoteFile::Read: Try and "resume" if read fails.
When a remote frontend is paused for more than 5 minutes, the "remotefile"
connection can end up in a state where no data is returned. Attempt to use
@@ -1840,7 +1840,7 @@ index 3bbfcff0340..d3ee79895ce 100644
From 068e7e1958c46bcce65cca71603eeb537dbd9a1a Mon Sep 17 00:00:00 2001
From: John Poet <jpoet(a)mythtv.org>
Date: Fri, 1 Feb 2019 20:33:46 -0700
-Subject: [PATCH 31/53] ChannelUtil::GetChannelDatast: Make sure results are
+Subject: [PATCH 31/69] ChannelUtil::GetChannelDatast: Make sure results are
more predictable.
Order results so lower numbered visible channels are prefered. If no
@@ -1927,7 +1927,7 @@ index dc2e412e7d6..bec67d02364 100644
From 363d00440163d55456bcb2eb353eb8a3ac9dc93d Mon Sep 17 00:00:00 2001
From: John Poet <jpoet(a)mythtv.org>
Date: Tue, 26 Feb 2019 13:57:12 -0700
-Subject: [PATCH 32/53] mythexternrecorder: Allow config to pass logging
+Subject: [PATCH 32/69] mythexternrecorder: Allow config to pass logging
options.
(cherry picked from commit 5b63846899946c0c150d1bf3ca4c90289293ca9f)
@@ -2143,7 +2143,7 @@ index 77ba6fc16e4..11a97d1ddad 100644
From df4b1b78ae4e6d551b9a5ce07a0af33d436b96fd Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Tue, 19 Mar 2019 14:05:09 -0500
-Subject: [PATCH 33/53] Include episode information in Scheduler-generated
+Subject: [PATCH 33/69] Include episode information in Scheduler-generated
ProgramInfos.
Also, include the generic, episode indication when writing to the
@@ -2226,7 +2226,7 @@ index 29aa293a24e..6bb9b1e512e 100644
From 81081957a0cbb83e7419269129b4babfe306a8ac Mon Sep 17 00:00:00 2001
From: Yianni Vidalis <yiannividalis(a)hotmail.com>
Date: Wed, 20 Mar 2019 12:50:31 -0400
-Subject: [PATCH 34/53] EIT fixes/enhancements for greek TV. (dvb-s and dvb-t)
+Subject: [PATCH 34/69] EIT fixes/enhancements for greek TV. (dvb-s and dvb-t)
Fixes #13426.
@@ -2522,7 +2522,7 @@ index 1caa158d9f4..91b2af2e927 100644
From 094e0d0c8038dac291dc4634e268c5fdc9dc0502 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Mon, 25 Mar 2019 17:43:51 -0400
-Subject: [PATCH 35/53] tidy: Fix memory leak in HLSSegment::DecodeData.
+Subject: [PATCH 35/69] tidy: Fix memory leak in HLSSegment::DecodeData.
The clang-tidy "memory leak" checker pointed out that
HLSSegment::DecodeData function would leak memory if it read an
@@ -2552,7 +2552,7 @@ index 6067814cce3..43a0e9d2199 100644
From ff4c7158f4fb241760e77fad7c2a8eef1a4eb5c3 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Mon, 25 Mar 2019 14:02:11 -0400
-Subject: [PATCH 36/53] tidy: Fix rare null pointer dereference in
+Subject: [PATCH 36/69] tidy: Fix rare null pointer dereference in
cc707decoder.cpp.
The clang-tidy "non-null parameter checker" pointed out to possibility
@@ -2682,7 +2682,7 @@ index 718bdbeef7b..f8638a78307 100644
From 0999fd0ff08d21bf7873daf78fe9e453925cd83d Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Sun, 24 Mar 2019 14:51:17 -0400
-Subject: [PATCH 37/53] tidy: Fix test to retrieve picture from FLAC file.
+Subject: [PATCH 37/69] tidy: Fix test to retrieve picture from FLAC file.
The clang-tidy "call and message" check pointed out the inevitability
of calling a function through a nullptr. The getPictureFromFile
@@ -2718,7 +2718,7 @@ index 3ac353dbc42..8452ffe71fa 100644
From 3af3cad3d74735a561ab594910a3400b2472645c Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Mon, 25 Mar 2019 19:32:11 -0400
-Subject: [PATCH 38/53] tidy: Fix memory leak in HLSSegment::DecodeData.
+Subject: [PATCH 38/69] tidy: Fix memory leak in HLSSegment::DecodeData.
The clang-tidy "memory leak" checker pointed out that the
RecorderBase::CheckForRingBufferSwitch function would leak the memory
@@ -2756,7 +2756,7 @@ index 24a5ff46306..024d81efbc0 100644
From 8e50fcf60bf9aaaddb5c8dbca4c957a0117d62c9 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Thu, 28 Mar 2019 08:55:36 -0400
-Subject: [PATCH 39/53] Fix timeout on mediaserver announcements.
+Subject: [PATCH 39/69] Fix timeout on mediaserver announcements.
The socket code was converted in 2009 from taking a boolean to indicate
a 'short' timeout, to taking an actual timeout in milliseconds. Fix
@@ -2786,7 +2786,7 @@ index 69cba584485..61fbba0f664 100644
From 3f494f57e27fef4285e094e919d6f795a8583e33 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Tue, 2 Apr 2019 22:56:37 -0400
-Subject: [PATCH 40/53] Fix "in progress" check in mythplayer.
+Subject: [PATCH 40/69] Fix "in progress" check in mythplayer.
The clang-tidy "implicit boolean conversion" check pointed out a
couple of nonsensical implicit conversion from TVState to boolean.
@@ -2835,7 +2835,7 @@ index a373d690f86..8e113c1e81f 100644
From 09393940d6b448d74fcf5f983b21641f972a38bd Mon Sep 17 00:00:00 2001
From: Bill Meek <billmeek(a)mythtv.org>
Date: Sun, 7 Apr 2019 11:01:24 -0500
-Subject: [PATCH 41/53] WebFrontend: Fix TV->Program Search (by Channel)
+Subject: [PATCH 41/69] WebFrontend: Fix TV->Program Search (by Channel)
Only a single channel is presented with v30. Commit 63a5a20e
added extra filtering options to Channel/GetChannelInfoList.
@@ -2866,7 +2866,7 @@ index cfce0300ca5..6441e769a8a 100644
From 4d995941851b277b6365fed4ea003b8706432c2d Mon Sep 17 00:00:00 2001
From: Dag Nygren <dag(a)newtech.fi>
Date: Mon, 8 Apr 2019 11:33:42 -0400
-Subject: [PATCH 42/53] Update Finish EIT fixups.
+Subject: [PATCH 42/69] Update Finish EIT fixups.
1) Remove 'Film' or 'Elokuva' at the start of titles. 2) Remove age
limit at the end of the title.
@@ -2932,7 +2932,7 @@ index c7f3df15c60..35ed11753ac 100644
From 5a67176bf907ca0686a7bd44c791a7003a544cfc Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Sun, 14 Apr 2019 16:35:42 -0400
-Subject: [PATCH 43/53] android: Fix for android OpenGL ES failures
+Subject: [PATCH 43/69] android: Fix for android OpenGL ES failures
Android devices that do not support high precision were failing
on an OpenGL error due to no precision specifiction. The result
@@ -2960,7 +2960,7 @@ index e27ec76f0fb..65e2fcc1687 100644
From b61283d7d1ffcdb17a909216220283e005e52a47 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Wed, 24 Apr 2019 09:51:35 -0400
-Subject: [PATCH 44/53] Restore code that fixes up key bindings.
+Subject: [PATCH 44/69] Restore code that fixes up key bindings.
This partially reverts commit 453178430d86f7fdf4030cc1a76d7a135df60aa0.
@@ -3017,7 +3017,7 @@ index ea0aa806d59..d40bfb3f64d 100644
From d41d45ed6f50620a8836580c97bfd6fa25214633 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Mon, 15 Apr 2019 15:10:39 -0500
-Subject: [PATCH 45/53] Enable Schedule as Group by default.
+Subject: [PATCH 45/69] Enable Schedule as Group by default.
This also includes the case when the maximum recordings is set to 1.
Both the core feature scheduling feature and the on-demand addition of
@@ -3084,7 +3084,7 @@ index 51e15e1decf..bb17f61e82d 100644
From 0bd850a54244d81dd086556d5425890b44dc2018 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Fri, 19 Apr 2019 16:13:43 -0400
-Subject: [PATCH 46/53] Add delay to handle race condition in FreeBSD testing.
+Subject: [PATCH 46/69] Add delay to handle race condition in FreeBSD testing.
Calls to read the data output by the shell generate an EBADF error.
Apparently FreeBSD is fast enough that the file descriptors have often
@@ -3205,7 +3205,7 @@ index 53a4601989c..51e669eb8bd 100644
From 97786a019daa7973207149d728a0b9da978bec1f Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Fri, 19 Apr 2019 19:04:20 -0400
-Subject: [PATCH 47/53] Lengthen timers in test_mythtimer.
+Subject: [PATCH 47/69] Lengthen timers in test_mythtimer.
This allows some slop for build systems being busy while the tests are
running, but still keeps the same ratio of the timers so the test
@@ -3325,7 +3325,7 @@ index 2c4dddfeca6..6d0b8705c75 100644
From 30a59af73f16c7b97c4704a721b3a366b5b96175 Mon Sep 17 00:00:00 2001
From: David Engel <dengel(a)mythtv.org>
Date: Thu, 25 Apr 2019 09:35:24 -0500
-Subject: [PATCH 48/53] Account for very, large pre-roll values in
+Subject: [PATCH 48/69] Account for very, large pre-roll values in
AssignGroupInput.
Refs #13423
@@ -3417,7 +3417,7 @@ index db956207422..a739d774920 100644
From f92255ce0620978cb0fffecd37832df3d6c56565 Mon Sep 17 00:00:00 2001
From: Peter Bennett <pbennett(a)mythtv.org>
Date: Fri, 26 Apr 2019 12:40:56 -0400
-Subject: [PATCH 49/53] Playback: Fix DB Update that caused bookmark key
+Subject: [PATCH 49/69] Playback: Fix DB Update that caused bookmark key
bindings to be lost
The database update is clearing the key binding before it can be
@@ -3459,7 +3459,7 @@ index d40bfb3f64d..7a58929fed9 100644
From fc79822ce4c8775361053d1bdd8d9dce2dc44ec1 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Tue, 30 Apr 2019 13:32:08 -0400
-Subject: [PATCH 50/53] Fix compilation with exiv2 > 0.27.0.
+Subject: [PATCH 50/69] Fix compilation with exiv2 > 0.27.0.
Remove an old workaround that was introduced so that MythTV would
continue to compile after the introduction of exiv2 version 0.21. The
@@ -3496,7 +3496,7 @@ index f1a57d4f8ea..b7c71d2ed80 100644
From a32ec4bdea1380ecf587c717bc882cbe9469e5c7 Mon Sep 17 00:00:00 2001
From: David Hampton <mythtv(a)love2code.net>
Date: Tue, 30 Apr 2019 14:10:13 -0400
-Subject: [PATCH 51/53] Fix mythgame configuration with minizip 2.8.6
+Subject: [PATCH 51/69] Fix mythgame configuration with minizip 2.8.6
Minizip apparently moved its header files in/before version 2.0.
Update the mythplugins configure script to look in both the old and
@@ -3569,7 +3569,7 @@ index b9838769c38..9bc8ed61eec 100644
From 57ffe65dbc1a80b0f4289f7c6a2b8f2e73e2cf51 Mon Sep 17 00:00:00 2001
From: Simon Hyde <simon(a)icedrop.net>
Date: Tue, 26 Feb 2019 21:32:19 +0000
-Subject: [PATCH 52/53] Prevent data corruption in dvbchannel.cpp causing hang
+Subject: [PATCH 52/69] Prevent data corruption in dvbchannel.cpp causing hang
m_is_open was being corrupted by multiple simultaneous modifications,
leading to it managing to create infinite loops within its underlying
@@ -3632,7 +3632,7 @@ index a21d35df3f2..677165b5bd7 100644
From 6bd8cd499382fd8b132218274fb4ae326c2b0243 Mon Sep 17 00:00:00 2001
From: Klaas de Waal <klaas(a)modu.home.lan>
Date: Thu, 30 May 2019 10:26:43 +0200
-Subject: [PATCH 53/53] Remove m_ in class member names for backport to
+Subject: [PATCH 53/69] Remove m_ in class member names for backport to
fixes-30
Refs #13415
@@ -3684,3 +3684,858 @@ index 677165b5bd7..70a354c67a7 100644
}
bool DVBChannel::Init(QString &startchannel, bool setchan)
+
+From 8325b0780455cd7b90bc6e676b47d6a2aa32250c Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Thu, 30 May 2019 14:33:25 -0500
+Subject: [PATCH 54/69] mythwelcome: --override-settings choice(s) don't take
+ affect
+
+mythwelcome --override-settings is valid, but overrides weren't
+being used. 139919a (2011) claimed to fix it, but never called
+ApplySettingsOverride(). Applies to override-settings-file too.
+
+Thanks to trx-913 on the Forum for spotting this.
+
+(cherry picked from commit 807c09b44a970b95cfb04d2112deb44f717f0d36)
+---
+ mythtv/programs/mythwelcome/main.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/programs/mythwelcome/main.cpp b/mythtv/programs/mythwelcome/main.cpp
+index 8459b04a8a6..36df6a6b43c 100644
+--- a/mythtv/programs/mythwelcome/main.cpp
++++ b/mythtv/programs/mythwelcome/main.cpp
+@@ -96,6 +96,8 @@ int main(int argc, char **argv)
+ return GENERIC_EXIT_NO_MYTHCONTEXT;
+ }
+
++ cmdline.ApplySettingsOverride();
++
+ if (!MSqlQuery::testDBConnection())
+ {
+ LOG(VB_GENERAL, LOG_ERR,
+
+From 8d04874b1ecffebe0085997a3cd797b99643bc11 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Tue, 4 Jun 2019 11:02:03 -0500
+Subject: [PATCH 55/69] mythwelcome: Final --override-settings fix
+
+This adds to a previous commit and allows libCECEnabled=0 to take
+affect. Needed to be done before the Context was initialized.
+
+Thanks to trx-913 on the Forum for testing both fixes.
+
+(cherry picked from commit ee893973bb9f4b1e3c0803917ed1850061fa054e)
+---
+ mythtv/programs/mythwelcome/main.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/programs/mythwelcome/main.cpp b/mythtv/programs/mythwelcome/main.cpp
+index 36df6a6b43c..4c1dbe90610 100644
+--- a/mythtv/programs/mythwelcome/main.cpp
++++ b/mythtv/programs/mythwelcome/main.cpp
+@@ -88,6 +88,8 @@ int main(int argc, char **argv)
+ #endif
+
+ gContext = new MythContext(MYTH_BINARY_VERSION, true);
++
++ cmdline.ApplySettingsOverride();
+ if (!gContext->Init())
+ {
+ LOG(VB_GENERAL, LOG_ERR,
+
+From aa26a6b76bf00fcd5ddb051c76f9994757e09913 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Fri, 10 May 2019 14:34:14 -0500
+Subject: [PATCH 56/69] Python Bindings: Fix version test for Frontend services
+
+Use Frontend/version to 'prime' the session for POSTs (was always
+using Myth/version.)
+
+(cherry picked from commit c12ff7cac94d570f9c6b566b455811c5a90735d6)
+---
+ mythtv/bindings/python/MythTV/services_api/send.py | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/services_api/send.py
b/mythtv/bindings/python/MythTV/services_api/send.py
+index 0d5946a8c0d..4c30aec8ca5 100644
+--- a/mythtv/bindings/python/MythTV/services_api/send.py
++++ b/mythtv/bindings/python/MythTV/services_api/send.py
+@@ -397,8 +397,6 @@ def _create_session(self):
+
+ # TODO: Problem with the BE not accepting postdata in the initial
+ # authorized query, Send a GET first as a workaround.
+- #
+- # Looks like a bug, Myth/version works for the backend.
+
+ try:
+ if self.opts['user'] and self.opts['pass']:
+@@ -407,7 +405,11 @@ def _create_session(self):
+ if self.postdata:
+ saved_endpoint = self.endpoint
+ saved_postdata = self.postdata
+- self.send(endpoint='Myth/version', opts=self.opts)
++ # Need to adjust this if a service other than Frontend is
++ # added.
++ self.send(endpoint='{}/version'.format(
++ 'Myth' if self.endpoint[:8] != 'Frontend'
++ else 'Frontend'), opts=self.opts)
+ self.endpoint = saved_endpoint
+ self.postdata = saved_postdata
+ except KeyError:
+
+From 402e8274435cfab094cd902105a11231b7eadfbc Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Fri, 10 May 2019 11:28:31 -0500
+Subject: [PATCH 57/69] HTTP Status: Restore thousands separators and use
+ proper LOCALE choice
+
+In #3957 the LOCALE was fixed to QLocale::C, and a report on the Forum
+pointed out the commas were lost after upgrading to Ubuntu 18.04 from
+16.04. I haven't found any Qt documentation that says the existing
+method is wrong, but this fix puts commas back and should have the
+side affect of using the correct separator in other LOCALS.
+
+(cherry picked from commit 9f6230f1dc9db1d34be9c5ef3986ac640e7f4316)
+---
+ mythtv/programs/mythbackend/httpstatus.cpp | 25 ++++++++++------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/httpstatus.cpp
b/mythtv/programs/mythbackend/httpstatus.cpp
+index 10cbbf2c30b..24224ef0c6f 100644
+--- a/mythtv/programs/mythbackend/httpstatus.cpp
++++ b/mythtv/programs/mythbackend/httpstatus.cpp
+@@ -19,7 +19,6 @@
+ // Qt headers
+ #include <QTextStream>
+ #include <QRegExp>
+-#include <QLocale>
+
+ // MythTV headers
+ #include "httpstatus.h"
+@@ -1266,38 +1265,38 @@ int HttpStatus::PrintMachineInfo( QTextStream &os,
QDomElement info )
+
+ os << " <li>Total Disk Space:\r\n"
+ << " <ul>\r\n";
+- QLocale c(QLocale::C);
+
+ os << " <li>Total Space: ";
+- sRep = c.toString(nTotal) + " MB";
++ sRep = QString("%L1").arg(nTotal) + " MB";
+ os << sRep << "</li>\r\n";
+
+ os << " <li>Space Used: ";
+- sRep = c.toString(nUsed) + " MB";
++ sRep = QString("%L1").arg(nUsed) + " MB";
+ os << sRep << "</li>\r\n";
+
+ os << " <li>Space Free: ";
+- sRep = c.toString(nFree) + " MB";
++ sRep = QString("%L1").arg(nFree) + " MB";
+ os << sRep << "</li>\r\n";
+
+ if ((nLiveTV + nDeleted + nExpirable) > 0)
+ {
+ os << " <li>Space Available "
+ "After Auto-expire: ";
+- sRep = c.toString(nFree + nLiveTV +
++ sRep = QString("%L1").arg(nUsed) + " MB";
++ sRep = QString("%L1").arg(nFree + nLiveTV +
+ nDeleted + nExpirable) + " MB";
+ os << sRep << "\r\n";
+ os << " <ul>\r\n";
+ os << " <li>Space Used by LiveTV:
";
+- sRep = c.toString(nLiveTV) + " MB";
++ sRep = QString("%L1").arg(nLiveTV) + " MB";
+ os << sRep << "</li>\r\n";
+ os << " <li>Space Used by "
+ "Deleted Recordings: ";
+- sRep = c.toString(nDeleted) + " MB";
++ sRep = QString("%L1").arg(nDeleted) + " MB";
+ os << sRep << "</li>\r\n";
+ os << " <li>Space Used by "
+ "Auto-expirable Recordings: ";
+- sRep = c.toString(nExpirable) + " MB";
++ sRep = QString("%L1").arg(nExpirable) + " MB";
+ os << sRep << "</li>\r\n";
+ os << " </ul>\r\n";
+ os << " </li>\r\n";
+@@ -1350,18 +1349,16 @@ int HttpStatus::PrintMachineInfo( QTextStream &os,
QDomElement info )
+
+ os << nDir << "</li>\r\n";
+
+- QLocale c(QLocale::C);
+-
+ os << " <li>Total Space: ";
+- sRep = c.toString(nTotal) + " MB";
++ sRep = QString("%L1").arg(nTotal) + " MB";
+ os << sRep << "</li>\r\n";
+
+ os << " <li>Space Used: ";
+- sRep = c.toString(nUsed) + " MB";
++ sRep = QString("%L1").arg(nUsed) + " MB";
+ os << sRep << "</li>\r\n";
+
+ os << " <li>Space Free: ";
+- sRep = c.toString(nFree) + " MB";
++ sRep = QString("%L1").arg(nFree) + " MB";
+ os << sRep << "</li>\r\n";
+
+ os << " </ul>\r\n"
+
+From 4761e7a6d42a86c11f10bd4dea83ca8eb022b016 Mon Sep 17 00:00:00 2001
+From: Jan Ceuleers <jan.ceuleers(a)gmail.com>
+Date: Thu, 30 May 2019 16:14:27 -0500
+Subject: [PATCH 58/69] tmdb3.py: Prevent program from stalling
+
+From the -users list. If one (or more) requests stall, no
+more metadata is retrieved until all stalled requests are
+killed (or the backend is rebooted.)
+
+Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+(cherry picked from commit 98f4fc8c47040b1afb8cbca1886765cc06943923)
+---
+ .../programs/scripts/metadata/Movie/tmdb3.py | 23 ++++++++++++++-----
+ 1 file changed, 17 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+index 3dd893d8b74..9739ecf320e 100755
+--- a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
++++ b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+@@ -29,6 +29,10 @@
+
+ from optparse import OptionParser
+ import sys
++import signal
++
++def timeouthandler(signal, frame):
++ raise RuntimeError("Timed out")
+
+ def buildSingle(inetref, opts):
+ from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
+@@ -275,6 +279,9 @@ def main():
+
+ opts, args = parser.parse_args()
+
++ signal.signal(signal.SIGALRM, timeouthandler)
++ signal.alarm(30)
++
+ if opts.version:
+ buildVersion()
+
+@@ -312,14 +319,18 @@ def main():
+ sys.stdout.write('ERROR: tmdb3.py requires exactly one non-empty
argument')
+ sys.exit(1)
+
+- if opts.movielist:
+- buildList(args[0], opts)
++ try:
++ if opts.movielist:
++ buildList(args[0], opts)
+
+- if opts.moviedata:
+- buildSingle(args[0], opts)
++ if opts.moviedata:
++ buildSingle(args[0], opts)
+
+- if opts.collectiondata:
+- buildCollection(args[0], opts)
++ if opts.collectiondata:
++ buildCollection(args[0], opts)
++ except RuntimeError, exc:
++ sys.stdout.write('ERROR: ' + str(exc) + ' exception')
++ sys.exit(1)
+
+ if __name__ == '__main__':
+ main()
+
+From ef55cb239b27741954b1ed026297a0c59b4ac0e1 Mon Sep 17 00:00:00 2001
+From: Robert Watson <robertabcdefgwatson(a)gmail.com>
+Date: Thu, 13 Jun 2019 10:48:32 +0200
+Subject: [PATCH 59/69] Compute seek offset 64 bits in playback of Blu-Ray iso.
+
+Fixes #13461
+
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+(cherry picked from commit f0ed96407e6a20db3c6b6ca1a59cb8498736a5cc)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/Bluray/bdringbuffer.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/Bluray/bdringbuffer.cpp
b/mythtv/libs/libmythtv/Bluray/bdringbuffer.cpp
+index 06258070e11..d7e4707d40f 100644
+--- a/mythtv/libs/libmythtv/Bluray/bdringbuffer.cpp
++++ b/mythtv/libs/libmythtv/Bluray/bdringbuffer.cpp
+@@ -142,7 +142,7 @@ static int _img_read(void *handle, void *buf, int lba, int
num_blocks)
+ {
+ int result = -1;
+
+- if (mythfile_seek(*((int*)handle), lba * 2048, SEEK_SET) != -1)
++ if (mythfile_seek(*((int*)handle), lba * 2048LL, SEEK_SET) != -1)
+ result = mythfile_read(*((int*)handle), buf, num_blocks * 2048) / 2048;
+
+ return result;
+
+From b9c1b41a6c01a387bad8f325e2b8000327045cc6 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Mon, 17 Jun 2019 15:20:19 -0400
+Subject: [PATCH 60/69] android: Fix problem with guide grid colors.
+
+assets file system does not support file handles.
+
+Refs #13454
+
+(cherry picked from commit 9ee9894bf540a6838fe67545de205ffd23acc63f)
+---
+ mythtv/libs/libmythui/mythuiguidegrid.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/libs/libmythui/mythuiguidegrid.cpp
b/mythtv/libs/libmythui/mythuiguidegrid.cpp
+index dda428461ac..994401adb41 100644
+--- a/mythtv/libs/libmythui/mythuiguidegrid.cpp
++++ b/mythtv/libs/libmythui/mythuiguidegrid.cpp
+@@ -747,12 +747,14 @@ bool MythUIGuideGrid::parseDefaultCategoryColors(QMap<QString,
QString> &catColo
+ break;
+ }
+
++#ifndef Q_OS_ANDROID // Android does not get a file handle for assets file system
+ if (f.handle() == -1)
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unable to open '%1'")
+ .arg(f.fileName()));
+ return false;
+ }
++#endif
+
+ QDomDocument doc;
+ QString errorMsg;
+
+From dac22b7f1043d4b212d1d73045ada3b8b113e090 Mon Sep 17 00:00:00 2001
+From: Stuart Auchterlonie <stuarta(a)mythtv.org>
+Date: Thu, 4 Jul 2019 11:57:25 +0100
+Subject: [PATCH 61/69] Fixes #13467 - Handle null blocks in DSMCC
+
+(cherry picked from commit 9dfa5c7a70bb422d748ab327f4b1a0cb3e435794)
+---
+ mythtv/libs/libmythtv/dsmccobjcarousel.cpp | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/dsmccobjcarousel.cpp
b/mythtv/libs/libmythtv/dsmccobjcarousel.cpp
+index 0ace9e6a29b..4109a52cc5a 100644
+--- a/mythtv/libs/libmythtv/dsmccobjcarousel.cpp
++++ b/mythtv/libs/libmythtv/dsmccobjcarousel.cpp
+@@ -105,6 +105,13 @@ unsigned char *DSMCCCacheModuleData::AddModuleData(DsmccDb *ddb,
+ for (uint i = 0; i < m_blocks.size(); i++)
+ {
+ QByteArray *block = m_blocks[i];
++ if (block == nullptr)
++ {
++ LOG(VB_DSMCC, LOG_INFO,
++ QString("[dsmcc] Null data found, aborting reconstruction"));
++ free(tmp_data);
++ return nullptr;
++ }
+ m_blocks[i] = nullptr;
+ uint size = block->size();
+ memcpy(tmp_data + curp, block->data(), size);
+
+From 4dae2f4106d537babfe8ddca0c7871a113a139e5 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <pharrison(a)mythtv.org>
+Date: Tue, 5 Feb 2019 11:41:09 +0000
+Subject: [PATCH 62/69] MythWeather: fix some of the weather grabber scripts
+
+Remove the no longer supported "use encoding 'utf8'" directive.
+
+(cherry picked from commit 4f58fd152f56089ac15db90433ea952d11d7b026)
+---
+ mythplugins/mythweather/mythweather/scripts/no_yrno/yrnoxml.pl | 2 +-
+ .../mythweather/scripts/uk_metoffice/MetOffCommon.pm | 2 +-
+ .../mythweather/scripts/uk_metoffice/metoffice_fivedayapi.pl | 2 +-
+ .../mythweather/scripts/uk_metoffice/metoffice_threehourapi.pl | 2 +-
+ .../mythweather/scripts/wunderground/wunderground-animaps.pl | 2 +-
+ .../mythweather/scripts/wunderground/wunderground-maps.pl | 2 +-
+ .../mythweather/scripts/wunderground/wunderground.pl | 2 +-
+ 7 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/mythplugins/mythweather/mythweather/scripts/no_yrno/yrnoxml.pl
b/mythplugins/mythweather/mythweather/scripts/no_yrno/yrnoxml.pl
+index b66376c0801..d17c8e0cc0d 100755
+--- a/mythplugins/mythweather/mythweather/scripts/no_yrno/yrnoxml.pl
++++ b/mythplugins/mythweather/mythweather/scripts/no_yrno/yrnoxml.pl
+@@ -5,7 +5,7 @@
+ use warnings;
+
+ use utf8;
+-use encoding 'utf8';
++
+ use LWP::UserAgent;
+ use Getopt::Std;
+ use URI::Escape;
+diff --git a/mythplugins/mythweather/mythweather/scripts/uk_metoffice/MetOffCommon.pm
b/mythplugins/mythweather/mythweather/scripts/uk_metoffice/MetOffCommon.pm
+index 43eb2a99096..099a4e6bb9d 100644
+--- a/mythplugins/mythweather/mythweather/scripts/uk_metoffice/MetOffCommon.pm
++++ b/mythplugins/mythweather/mythweather/scripts/uk_metoffice/MetOffCommon.pm
+@@ -7,7 +7,7 @@ use warnings;
+ require Exporter;
+
+ use utf8;
+-use encoding 'utf8';
++
+ use LWP::UserAgent;
+ use LWP::Simple;
+ use XML::Simple;
+diff --git
a/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_fivedayapi.pl
b/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_fivedayapi.pl
+index 853d900e1c3..ee4ef9ec2b2 100755
+--- a/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_fivedayapi.pl
++++ b/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_fivedayapi.pl
+@@ -5,7 +5,7 @@
+ use warnings;
+
+ use utf8;
+-use encoding 'utf8';
++
+ use English;
+
+ use File::Basename;
+diff --git
a/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_threehourapi.pl
b/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_threehourapi.pl
+index 9ec77f89643..6c566207c27 100755
+--- a/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_threehourapi.pl
++++ b/mythplugins/mythweather/mythweather/scripts/uk_metoffice/metoffice_threehourapi.pl
+@@ -5,7 +5,7 @@
+ use warnings;
+
+ use utf8;
+-use encoding 'utf8';
++
+ use English;
+
+ use File::Basename;
+diff --git
a/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-animaps.pl
b/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-animaps.pl
+index 382dda730bd..5cb74644fc0 100755
+--- a/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-animaps.pl
++++ b/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-animaps.pl
+@@ -13,7 +13,7 @@
+ '/usr/local/share/mythtv/mythweather/scripts/wunderground';
+
+ use utf8;
+-use encoding 'utf8';
++
+ use LWP::UserAgent;
+ use Getopt::Std;
+ use URI::Escape;
+diff --git
a/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-maps.pl
b/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-maps.pl
+index b7797b5e493..9fb35616206 100755
+--- a/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-maps.pl
++++ b/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground-maps.pl
+@@ -13,7 +13,7 @@
+ '/usr/local/share/mythtv/mythweather/scripts/wunderground';
+
+ use utf8;
+-use encoding 'utf8';
++
+ use LWP::UserAgent;
+ use Getopt::Std;
+ use URI::Escape;
+diff --git a/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground.pl
b/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground.pl
+index 2ce69c250e1..c9a7896894d 100755
+--- a/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground.pl
++++ b/mythplugins/mythweather/mythweather/scripts/wunderground/wunderground.pl
+@@ -5,7 +5,7 @@
+ use warnings;
+
+ use utf8;
+-use encoding 'utf8';
++
+ use LWP::UserAgent;
+ use Getopt::Std;
+ use URI::Escape;
+
+From 80434249139b36701b050c7990cf20a09de268a1 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <pharrison(a)mythtv.org>
+Date: Fri, 19 Apr 2019 16:43:49 +0100
+Subject: [PATCH 63/69] MythZoneMinder: fix saving the enabled/disabled monitor
+ notification setting
+
+(cherry picked from commit cca393352f1df6ef97b655f048b18c2f0b98732a)
+---
+ .../mythzoneminder/mythzoneminder/zmclient.cpp | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
b/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
+index 0a99c93ac90..2bc78cab68e 100644
+--- a/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
++++ b/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
+@@ -942,10 +942,13 @@ void ZMClient::saveNotificationMonitors(void)
+ for (int x = 0; x < m_monitorList.count(); x++)
+ {
+ Monitor *mon = m_monitorList.at(x);
+- if (!s.isEmpty())
+- s += QString(",%1").arg(mon->id);
+- else
+- s = QString("%1").arg(mon->id);
++ if (mon->showNotifications)
++ {
++ if (!s.isEmpty())
++ s += QString(",%1").arg(mon->id);
++ else
++ s = QString("%1").arg(mon->id);
++ }
+ }
+
+ gCoreContext->SaveSetting("ZoneMinderNotificationMonitors", s);
+
+From d794f3108195eb8fe5333a73db38275185ff73c2 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <pharrison(a)mythtv.org>
+Date: Wed, 19 Jun 2019 01:34:36 +0100
+Subject: [PATCH 64/69] ZMClient: use locking to make sure only one command
+ runs at a time
+
+(cherry picked from commit b57372d484c1c9306ecb26b5e18906708980b4fe)
+---
+ .../mythzoneminder/zmclient.cpp | 32 +++++++++++++++++--
+ .../mythzoneminder/mythzoneminder/zmclient.h | 2 ++
+ 2 files changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
b/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
+index 2bc78cab68e..068a2dc0c9e 100644
+--- a/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
++++ b/mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
+@@ -26,6 +26,7 @@
+ ZMClient::ZMClient()
+ : QObject(nullptr),
+ m_listLock(QMutex::Recursive),
++ m_commandLock(QMutex::Recursive),
+ m_socket(nullptr),
+ m_socketLock(QMutex::Recursive),
+ m_hostname("localhost"),
+@@ -135,8 +136,6 @@ bool ZMClient::connectToHost(const QString &lhostname, unsigned
int lport)
+
+ bool ZMClient::sendReceiveStringList(QStringList &strList)
+ {
+- QMutexLocker locker(&m_socketLock);
+-
+ QStringList origStrList = strList;
+
+ bool ok = false;
+@@ -196,6 +195,8 @@ bool ZMClient::sendReceiveStringList(QStringList &strList)
+
+ bool ZMClient::checkProtoVersion(void)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ QStringList strList("HELLO");
+ if (!sendReceiveStringList(strList))
+ {
+@@ -272,6 +273,8 @@ ZMClient::~ZMClient()
+
+ void ZMClient::getServerStatus(QString &status, QString &cpuStat, QString
&diskStat)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ QStringList strList("GET_SERVER_STATUS");
+ if (!sendReceiveStringList(strList))
+ return;
+@@ -290,6 +293,8 @@ void ZMClient::getServerStatus(QString &status, QString
&cpuStat, QString &diskS
+
+ void ZMClient::updateMonitorStatus(void)
+ {
++ QMutexLocker clocker(&m_commandLock);
++
+ QStringList strList("GET_MONITOR_STATUS");
+ if (!sendReceiveStringList(strList))
+ return;
+@@ -359,6 +364,8 @@ static QString stateToString(State state)
+
+ bool ZMClient::updateAlarmStates(void)
+ {
++ QMutexLocker clocker(&m_commandLock);
++
+ QStringList strList("GET_ALARM_STATES");
+ if (!sendReceiveStringList(strList))
+ return false;
+@@ -410,6 +417,8 @@ void ZMClient::getEventList(const QString &monitorName, bool
oldestFirst,
+ const QString &date, bool includeContinuous,
+ vector<Event*> *eventList)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ eventList->clear();
+
+ QStringList strList("GET_EVENT_LIST");
+@@ -462,6 +471,8 @@ void ZMClient::getEventList(const QString &monitorName, bool
oldestFirst,
+ void ZMClient::getEventDates(const QString &monitorName, bool oldestFirst,
+ QStringList &dateList)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ dateList.clear();
+
+ QStringList strList("GET_EVENT_DATES");
+@@ -505,6 +516,8 @@ void ZMClient::getEventDates(const QString &monitorName, bool
oldestFirst,
+
+ void ZMClient::getFrameList(int eventID, vector<Frame*> *frameList)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ frameList->clear();
+
+ QStringList strList("GET_FRAME_LIST");
+@@ -549,6 +562,8 @@ void ZMClient::getFrameList(int eventID, vector<Frame*>
*frameList)
+
+ void ZMClient::deleteEvent(int eventID)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ QStringList strList("DELETE_EVENT");
+ strList << QString::number(eventID);
+ sendReceiveStringList(strList);
+@@ -556,6 +571,8 @@ void ZMClient::deleteEvent(int eventID)
+
+ void ZMClient::deleteEventList(vector<Event*> *eventList)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ // delete events in 100 event chunks
+ QStringList strList("DELETE_EVENT_LIST");
+ int count = 0;
+@@ -641,6 +658,8 @@ bool ZMClient::readData(unsigned char *data, int dataSize)
+
+ void ZMClient::getEventFrame(Event *event, int frameNo, MythImage **image)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ if (*image)
+ {
+ (*image)->DecrRef();
+@@ -690,6 +709,8 @@ void ZMClient::getEventFrame(Event *event, int frameNo, MythImage
**image)
+
+ void ZMClient::getAnalyseFrame(Event *event, int frameNo, QImage &image)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ QStringList strList("GET_ANALYSE_FRAME");
+ strList << QString::number(event->monitorID());
+ strList << QString::number(event->eventID());
+@@ -735,6 +756,8 @@ void ZMClient::getAnalyseFrame(Event *event, int frameNo, QImage
&image)
+
+ int ZMClient::getLiveFrame(int monitorID, QString &status, unsigned char* buffer,
int bufferSize)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ QStringList strList("GET_LIVE_FRAME");
+ strList << QString::number(monitorID);
+ if (!sendReceiveStringList(strList))
+@@ -794,6 +817,8 @@ int ZMClient::getLiveFrame(int monitorID, QString &status,
unsigned char* buffer
+
+ void ZMClient::getCameraList(QStringList &cameraList)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ cameraList.clear();
+
+ QStringList strList("GET_CAMERA_LIST");
+@@ -860,6 +885,7 @@ Monitor* ZMClient::getMonitorByID(int monID)
+
+ void ZMClient::doGetMonitorList(void)
+ {
++ QMutexLocker clocker(&m_commandLock);
+ QMutexLocker locker(&m_listLock);
+
+ for (int x = 0; x < m_monitorList.count(); x++)
+@@ -926,6 +952,8 @@ void ZMClient::doGetMonitorList(void)
+
+ void ZMClient::setMonitorFunction(const int monitorID, const QString &function,
const int enabled)
+ {
++ QMutexLocker locker(&m_commandLock);
++
+ QStringList strList("SET_MONITOR_FUNCTION");
+ strList << QString::number(monitorID);
+ strList << function;
+diff --git a/mythplugins/mythzoneminder/mythzoneminder/zmclient.h
b/mythplugins/mythzoneminder/mythzoneminder/zmclient.h
+index 3dd5c20faae..75369f0f924 100644
+--- a/mythplugins/mythzoneminder/mythzoneminder/zmclient.h
++++ b/mythplugins/mythzoneminder/mythzoneminder/zmclient.h
+@@ -76,6 +76,8 @@ class MPUBLIC ZMClient : public QObject
+ bool sendReceiveStringList(QStringList &strList);
+
+ QMutex m_listLock;
++ QMutex m_commandLock;
++
+ QList<Monitor*> m_monitorList;
+ QMap<int, Monitor*> m_monitorMap;
+
+
+From 84e5caebaa9650b88a6082f19cb46d46b831b566 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <pharrison(a)mythtv.org>
+Date: Fri, 5 Jul 2019 19:02:40 +0100
+Subject: [PATCH 65/69] mythzmserver: don't try to get the shared memory
+ pointers for a disabled Monitor
+
+Also allow for a NULL result for the Host field in the Monitors table.
+Fixes #13466
+
+(cherry picked from commit 7924ad992c75c2dad04eef992d51c349d9e4a64d)
+---
+ mythplugins/mythzoneminder/mythzmserver/zmserver.cpp | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
b/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
+index eefe9fbbbe5..c890c82eb58 100644
+--- a/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
++++ b/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
+@@ -241,6 +241,9 @@ void MONITOR::initMonitor(bool debug, const string &mmapPath, int
shmKey)
+ int shared_data_size;
+ int frame_size = width * height * bytes_per_pixel;
+
++ if (!m_enabled)
++ return;
++
+ if (checkVersion(1, 26, 0))
+ {
+ shared_data_size = sizeof(SharedData26) +
+@@ -956,7 +959,7 @@ void ZMServer::handleGetMonitorStatus(void)
+ string id = row[0];
+ string type = row[2];
+ string device = row[3];
+- string host = row[4];
++ string host = row[4] ? row[4] : "";
+ string channel = row[5];
+ string function = row[6];
+ string enabled = row[7];
+@@ -1675,7 +1678,7 @@ void ZMServer::getMonitorList(void)
+ m->function = row[8];
+ m->enabled = atoi(row[9]);
+ m->device = row[10];
+- m->host = row[11];
++ m->host = row[11] ? row[11] : "";
+ m->controllable = atoi(row[12]);
+ m->trackMotion = atoi(row[13]);
+
+
+From 83e27017203abfac6958bd3a6ce3284c372fa2d4 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <pharrison(a)mythtv.org>
+Date: Fri, 5 Jul 2019 21:18:04 +0100
+Subject: [PATCH 66/69] mythzmserver: fix merge error after 84e5caebaa
+
+Refs #13466
+---
+ mythplugins/mythzoneminder/mythzmserver/zmserver.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
b/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
+index c890c82eb58..daea2fbc5de 100644
+--- a/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
++++ b/mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
+@@ -241,7 +241,7 @@ void MONITOR::initMonitor(bool debug, const string &mmapPath, int
shmKey)
+ int shared_data_size;
+ int frame_size = width * height * bytes_per_pixel;
+
+- if (!m_enabled)
++ if (!enabled)
+ return;
+
+ if (checkVersion(1, 26, 0))
+
+From a4bc92e244d815351bd6de6f8d4c2569f22bc403 Mon Sep 17 00:00:00 2001
+From: Tom Dexter <digitalaudiorock(a)gmail.com>
+Date: Thu, 11 Jul 2019 16:34:08 +0100
+Subject: [PATCH 67/69] Gentoo users may not have nvidia-settings utils
+ installed. From
https://bugs.gentoo.org/688084
+
+Signed-off-by: Stuart Auchterlonie <stuarta(a)mythtv.org>
+(cherry picked from commit 13ec89b65ea4a95dafbdfc31ff27a621ca126860)
+---
+ mythtv/configure | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/mythtv/configure b/mythtv/configure
+index a80828efc7f..4f526864a5f 100755
+--- a/mythtv/configure
++++ b/mythtv/configure
+@@ -6651,6 +6651,14 @@ if enabled x11; then
+ # This is hopefully temporary.
+ disable xnvctrl_external
+ ;;
++ *gentoo*)
++ if [ -e "/usr/bin/nvidia-settings" ]
++ then
++ require XNVctrl "X11/Xlib.h NVCtrl/NVCtrl.h
NVCtrl/NVCtrlLib.h" XNVCTRLIsNvScreen -lXNVCtrl || disable xnvctrl
++ else
++ disable xnvctrl_external
++ fi
++ ;;
+ *)
+ require XNVctrl "X11/Xlib.h NVCtrl/NVCtrl.h
NVCtrl/NVCtrlLib.h" XNVCTRLIsNvScreen -lXNVCtrl || disable xnvctrl
+ ;;
+
+From e094a020179aba2d955a21b8fd067231a87e3334 Mon Sep 17 00:00:00 2001
+From: "Paul B. Henson" <henson(a)acm.org>
+Date: Thu, 25 Jul 2019 11:44:28 +0200
+Subject: [PATCH 68/69] DVB symbol errros with gcc 8.3
+
+Include of dvbchannel.h in channelscanner.cpp is now
+conditional on USING_DVB.
+This does solve the compilation issue for gcc 8.3.
+Note that there are no compilation issues with
+newer gcc versions, as tested with gcc 9.1.
+
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+(cherry picked from commit 06a1158ead9aafd6c394e90860049b53e1e16978)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/channelscan/channelscanner.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
+index d4532cd226b..a6a55340d90 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
+@@ -39,7 +39,9 @@ using namespace std;
+ #include "hdhrchannel.h"
+ #include "scanmonitor.h"
+ #include "asichannel.h"
++#ifdef USING_DVB // for bug in gcc 8.3
+ #include "dvbchannel.h"
++#endif
+ #include "v4lchannel.h"
+ #include "iptvchannel.h"
+ #include "ExternalChannel.h"
+
+From 5cde0578d84926171b20c8f7e95a101e9b0b9457 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Thu, 8 Aug 2019 12:45:52 -0400
+Subject: [PATCH 69/69] dbcheck: Enforce correct recgroupids for special
+ recording groups
+
+If the mysql server is set up with auto_increment_increment=2 or
+some value other than 1, the wrong values were set for the special
+recording groups.
+
+This only fixes it for new database setups or upgrades from schema
+id 1320.
+
+Refs #13473
+
+(cherry picked from commit da558ca5f531b8074155946b96c82ef4fe0ddefa)
+---
+ mythtv/libs/libmythtv/dbcheck.cpp | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/dbcheck.cpp b/mythtv/libs/libmythtv/dbcheck.cpp
+index 6970a385bff..af4581ae345 100644
+--- a/mythtv/libs/libmythtv/dbcheck.cpp
++++ b/mythtv/libs/libmythtv/dbcheck.cpp
+@@ -2636,9 +2636,9 @@ nullptr
+ "UNIQUE KEY recgroup ( recgroup )"
+ ") ENGINE=MyISAM DEFAULT CHARSET=utf8;",
+ // Create the built-in, 'special', groups
+- "INSERT INTO recgroups ( recgroup, special ) VALUES (
'Default', '1' );",
+- "INSERT INTO recgroups ( recgroup, special ) VALUES ( 'LiveTV',
'1' );",
+- "INSERT INTO recgroups ( recgroup, special ) VALUES (
'Deleted', '1' );",
++ "INSERT INTO recgroups ( recgroupid, recgroup, special ) VALUES ( 1,
'Default', '1' );",
++ "INSERT INTO recgroups ( recgroupid, recgroup, special ) VALUES ( 2,
'LiveTV', '1' );",
++ "INSERT INTO recgroups ( recgroupid, recgroup, special ) VALUES ( 3,
'Deleted', '1' );",
+ // Copy in the passwords for the built-in groups
+ "DELETE FROM recgrouppassword WHERE password = '';",
+ "UPDATE recgroups r, recgrouppassword p SET r.password = p.password
WHERE r.recgroup = p.recgroup;",