[ffmpeg] Add recommends for vmaf
by Leigh Scott
commit 1bfbe95d6a91683ff1ca021346fcd09a2b6dd0e7
Author: Leigh Scott <leigh123linux(a)gmail.com>
Date: Sat Dec 11 19:57:43 2021 +0000
Add recommends for vmaf
ffmpeg.spec | 1 +
1 file changed, 1 insertion(+)
---
diff --git a/ffmpeg.spec b/ffmpeg.spec
index e1d130d..04061f2 100644
--- a/ffmpeg.spec
+++ b/ffmpeg.spec
@@ -236,6 +236,7 @@ and video, MPEG4, h263, ac3, asf, avi, real, mjpeg, and flash.
%package libs
Summary: Libraries for %{name}
+%{?_with_vmaf:Recommends: vmaf-models}
%description libs
FFmpeg is a complete and free Internet live audio and video
2 years, 11 months
[mythtv/el7] (14 commits) ...Update to latest fixes/31
by Andrew Bauer
Summary of changes:
ae4e69d... Fix typo (*)
8094a5c... Disable libcec on el8 (*)
02c811f... - Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass (*)
a754140... Update to latest fixes/31. (*)
39d596a... Rebuild for new x265 (*)
d3a6e45... Update to latest fixes/31. Auto compute the version string (*)
e7f1124... Rebuild for python-3.10 (*)
738162c... Drop lame binary on els (*)
10eafa1... Update to latest fixes/31 Don't require lame binary on el8 (*)
f682196... Add one missing occurence for lame (*)
dfcba76... Mass rebuild for x264-0.163 (*)
3bdb83a... Fix git date on builds names (*)
2427adc... - Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass (*)
0fe6183... Update to latest fixes/31 (*)
(*) This commit already existed in another branch; no separate mail sent
2 years, 11 months
[mythtv] Update to latest fixes/31
by Andrew Bauer
commit 0fe6183e6bb74f87882f82e2ad48b3599587f399
Author: Andrew Bauer <zonexpertconsulting(a)outlook.com>
Date: Sat Dec 11 12:17:47 2021 -0600
Update to latest fixes/31
mythtv.spec | 10 +-
v31.0..25f1bb1d12.patch | 30232 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 30238 insertions(+), 4 deletions(-)
---
diff --git a/mythtv.spec b/mythtv.spec
index a21ccbb..258fab7 100644
--- a/mythtv.spec
+++ b/mythtv.spec
@@ -1,9 +1,9 @@
# The full MythTV Version string is computed from the output of git describe.
-%global vers_string v31.0-158-g0680b37c68
+%global vers_string v31.0-167-g25f1bb1d12
# The git date of last commit on mythtv repo
# git_date=$(git log -1 --format=%cd --date=format:"%Y%m%d")
-%global git_date 20210610
+%global git_date 20211108
# Specfile for building MythTV and MythPlugins RPMs from a git checkout.
@@ -76,7 +76,7 @@
#
Name: mythtv
Version: 31.0
-Release: 21%{rel_string}%{?dist}
+Release: 22%{rel_string}%{?dist}
Summary: A digital video recorder (DVR) application
# The primary license is GPLv2+, but bits are borrowed from a number of
@@ -1396,8 +1396,10 @@ exit 0
################################################################################
-
%changelog
+* Sat Dec 11 2021 Andrew Bauer <zonexpertconsulting(a)outlook.com> - 31.0-22.167.20211108git25f1bb1d12
+- Update to latest fixes/31
+
* Tue Aug 03 2021 RPM Fusion Release Engineering <leigh123linux(a)gmail.com> - 31.0-21.158.20210610git0680b37c68
- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
diff --git a/v31.0..25f1bb1d12.patch b/v31.0..25f1bb1d12.patch
new file mode 100644
index 0000000..c69d62c
--- /dev/null
+++ b/v31.0..25f1bb1d12.patch
@@ -0,0 +1,30232 @@
+From 32ae89ef505c77cb520f1e601efef58890bdcb57 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Thu, 26 Mar 2020 13:49:53 +0000
+Subject: [PATCH 001/165] MythCodecContext: Ignore hardware decoders when there
+ is no GUI
+
+(cherry picked from commit 1e06407c6edb2f73eacb3b7bb9782bd9375912cd)
+---
+ mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp b/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp
+index 0880bf8212f..5932fafc780 100644
+--- a/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp
+@@ -141,6 +141,12 @@ QStringList MythCodecContext::GetDecoderDescription(void)
+
+ void MythCodecContext::GetDecoders(RenderOptions &Opts)
+ {
++ if (!HasMythMainWindow())
++ {
++ LOG(VB_GENERAL, LOG_INFO, LOC + "No window: Ignoring hardware decoders");
++ return;
++ }
++
+ #ifdef USING_VDPAU
+ // Only enable VDPAU support if it is actually present
+ if (MythVDPAUHelper::HaveVDPAU())
+
+From f496eb12ea1eab92d9ca0e57856da66b54e9a0fa Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Fri, 27 Mar 2020 17:28:58 +0000
+Subject: [PATCH 002/165] libmythtv.pro: Typo
+
+(cherry picked from commit 59b00df23e73d842552c0105a7f51c6a12de7796)
+---
+ mythtv/libs/libmythtv/libmythtv.pro | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro
+index 036228f2d31..f1b0248d220 100644
+--- a/mythtv/libs/libmythtv/libmythtv.pro
++++ b/mythtv/libs/libmythtv/libmythtv.pro
+@@ -169,7 +169,7 @@ SOURCES += channelgroup.cpp
+ SOURCES += recordingrule.cpp
+ SOURCES += mythsystemevent.cpp
+ SOURCES += avfringbuffer.cpp
+-SOURCES += ringbuffer.cpp fileringBuffer.cpp
++SOURCES += ringbuffer.cpp fileringbuffer.cpp
+ SOURCES += streamingringbuffer.cpp metadataimagehelper.cpp
+ SOURCES += icringbuffer.cpp
+ SOURCES += mythframe.cpp mythavutil.cpp
+
+From 81d4056c2402882621590e7cd88ae8af5ba134aa Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Wed, 4 Mar 2020 22:08:23 +0100
+Subject: [PATCH 003/165] Wait for NIT or MGT when scanning
+
+In mythtv-setup channel scan, wait for a NIT or a MGT when the
+PAT/PMT have been found. This solves the problem that sometimes
+channels are found but the channel names are missing.
+
+Refs #13472
+
+(cherry picked from commit ac67d5837062ab47aa6f9b93df001a2a245d32ad)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+index 52c6bb1d606..0f2ebae0744 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+@@ -907,6 +907,12 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ if (transport_tune_complete)
+ {
+ transport_tune_complete &= !m_currentInfo->m_pmts.empty();
++
++ if (!(sd->HasCachedMGT() || sd->HasCachedAnyNIT()))
++ {
++ transport_tune_complete = false;
++ }
++
+ if (sd->HasCachedMGT() || sd->HasCachedAnyVCTs())
+ {
+ transport_tune_complete &= sd->HasCachedMGT();
+@@ -926,7 +932,7 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ {
+ uint tsid = dtv_sm->GetTransportID();
+ LOG(VB_CHANSCAN, LOG_INFO, LOC +
+- QString("transport_tune_complete: ") +
++ QString("transport_tune_complete: wait_until_complete %1").arg(wait_until_complete) +
+ QString("\n\t\t\tsd->HasCachedAnyNIT(): %1").arg(sd->HasCachedAnyNIT()) +
+ QString("\n\t\t\tsd->HasCachedAnySDTs(): %1").arg(sd->HasCachedAnySDTs()) +
+ QString("\n\t\t\tsd->HasCachedAnyBATs(): %1").arg(sd->HasCachedAnyBATs()) +
+@@ -951,8 +957,7 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ if (transport_tune_complete)
+ {
+ LOG(VB_CHANSCAN, LOG_INFO, LOC +
+- QString("transport_tune_complete: wait_until_complete %1")
+- .arg(wait_until_complete));
++ QString("transport_tune_complete: wait_until_complete %1").arg(wait_until_complete));
+ }
+
+ if (transport_tune_complete &&
+
+From 2ef589a8d742613ebe247362366cc045855b195c Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sun, 15 Mar 2020 22:10:01 +0100
+Subject: [PATCH 004/165] T2_terrestrial_delivery_system debug output
+
+Debug output of the T2 terrestrial delivery system descriptor added.
+First version with only the mandatory fields.
+
+(cherry picked from commit 8bde08adc702f05344c12d6eeb2b6c6b37255924)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/mpeg/dvbdescriptors.cpp | 12 +++++++
+ mythtv/libs/libmythtv/mpeg/dvbdescriptors.h | 31 +++++++++++++++++++
+ .../libs/libmythtv/mpeg/mpegdescriptors.cpp | 4 +++
+ 3 files changed, 47 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/mpeg/dvbdescriptors.cpp b/mythtv/libs/libmythtv/mpeg/dvbdescriptors.cpp
+index 1212bbc9cf5..58f5b372df0 100644
+--- a/mythtv/libs/libmythtv/mpeg/dvbdescriptors.cpp
++++ b/mythtv/libs/libmythtv/mpeg/dvbdescriptors.cpp
+@@ -595,6 +595,18 @@ QString TerrestrialDeliverySystemDescriptor::toString() const
+ return str;
+ }
+
++QString T2TerrestrialDeliverySystemDescriptor::toString() const
++{
++ QString str = QString("T2TerrestrialDeliverySystemDescriptor: ");
++ str.append(QString("plp_id(%1) T2_system_id(%2)")
++ .arg(PlpID())
++ .arg(T2SystemID()));
++ //
++ // TBD
++ //
++ return str;
++}
++
+ QString DVBLogicalChannelDescriptor::toString() const
+ {
+ QString ret = "UKChannelListDescriptor sid->chan_num: ";
+diff --git a/mythtv/libs/libmythtv/mpeg/dvbdescriptors.h b/mythtv/libs/libmythtv/mpeg/dvbdescriptors.h
+index b6d57187628..4ccf33bcd82 100644
+--- a/mythtv/libs/libmythtv/mpeg/dvbdescriptors.h
++++ b/mythtv/libs/libmythtv/mpeg/dvbdescriptors.h
+@@ -1041,6 +1041,37 @@ class TerrestrialDeliverySystemDescriptor : public MPEGDescriptor
+ QString toString(void) const override; // MPEGDescriptor
+ };
+
++// DVB Bluebook A038 (Feb 2019) p 104
++class T2TerrestrialDeliverySystemDescriptor : public MPEGDescriptor
++{
++ public:
++ explicit T2TerrestrialDeliverySystemDescriptor(
++ const unsigned char *data, int len = 300) :
++ MPEGDescriptor(data, len, DescriptorID::t2_terrestrial_delivery_system) { }
++ // Name bits loc expected value
++ // descriptor_tag 8 0.0 0x7f
++ // descriptor_length 8 1.0
++ // descriptor_tag_extension 8 2.0 0x4
++
++ // plp_id 8 3.0
++ uint PlpID(void) const
++ {
++ return m_data[3];
++ }
++
++ // T2_system_id 16 4.0
++ uint T2SystemID(void) const
++ {
++ return ((m_data[4]<<8) | (m_data[5]));
++ }
++
++ //
++ // TBD
++ //
++
++ QString toString(void) const override; // MPEGDescriptor
++};
++
+ // DVB Bluebook A038 (Sept 2011) p 58
+ class DSNGDescriptor : public MPEGDescriptor
+ {
+diff --git a/mythtv/libs/libmythtv/mpeg/mpegdescriptors.cpp b/mythtv/libs/libmythtv/mpeg/mpegdescriptors.cpp
+index e53a24e2c15..7d026ef10b1 100644
+--- a/mythtv/libs/libmythtv/mpeg/mpegdescriptors.cpp
++++ b/mythtv/libs/libmythtv/mpeg/mpegdescriptors.cpp
+@@ -491,6 +491,10 @@ QString MPEGDescriptor::toStringPD(uint priv_dsid) const
+ {
+ SET_STRING(DefaultAuthorityDescriptor);
+ }
++ else if (DescriptorID::t2_terrestrial_delivery_system == DescriptorTag())
++ {
++ SET_STRING(T2TerrestrialDeliverySystemDescriptor);
++ }
+ //
+ // User Defined DVB descriptors, range 0x80-0xFE
+ else if (priv_dsid == PrivateDataSpecifierID::BSB1 &&
+
+From d052cbc41cce4201b7a578f3a0820a9c9d3771d9 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sun, 15 Mar 2020 22:26:28 +0100
+Subject: [PATCH 005/165] Signal strength of scanned transports
+
+Show the signal strength of the scanned transports in the transport list.
+The transport list is shown if the "-v chanscan" option is given when running mythtv-setup.
+The signal strengths are useful to determine which transport to choose when identical
+transports can be received from different transmitters on different frequencies.
+
+Refs #13472
+
+(cherry picked from commit 31129946b719ff21b1a6cad86b2580ef8043a10f)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ .../libmythtv/channelscan/channelimporter.cpp | 1 +
+ .../libmythtv/channelscan/channelscan_sm.cpp | 36 ++++++++++---------
+ .../libmythtv/channelscan/channelscan_sm.h | 1 +
+ .../channelscan/frequencytablesetting.cpp | 1 +
+ mythtv/libs/libmythtv/dtvmultiplex.cpp | 4 +--
+ mythtv/libs/libmythtv/dtvmultiplex.h | 1 +
+ mythtv/libs/libmythtv/frequencytables.h | 1 +
+ .../libs/libmythtv/recorders/signalmonitor.h | 1 +
+ mythtv/libs/libmythtv/ringbuffer.cpp | 2 +-
+ 9 files changed, 28 insertions(+), 20 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+index 5670c1e7ad8..623200543d1 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+@@ -1447,6 +1447,7 @@ QString ChannelImporter::FormatTransport(
+ QString msg;
+ QTextStream ssMsg(&msg);
+ ssMsg << transport.toString();
++ ssMsg << QString(" ss:%1").arg(transport.m_signalStrength);
+ return msg;
+ }
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+index 0f2ebae0744..c2ec52d199c 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+@@ -188,7 +188,8 @@ ChannelScanSM::ChannelScanSM(ScanMonitor *_scan_monitor,
+ QString("Setting NIT-ID to %1").arg(nitid));
+
+ m_bouquetId = query.value(1).toUInt();
+- m_regionId = query.value(2).toUInt();
++ m_regionId = query.value(2).toUInt();
++ m_nitId = nitid > 0 ? nitid : 0;
+ }
+
+ LOG(VB_CHANSCAN, LOG_INFO, LOC +
+@@ -352,15 +353,17 @@ bool ChannelScanSM::ScanExistingTransports(uint sourceid, bool follow_nit)
+ return false;
+ }
+
+-
+ return m_scanning;
+ }
+
+ void ChannelScanSM::LogLines(const QString& string)
+ {
+- QStringList lines = string.split('\n');
+- for (int i = 0; i < lines.size(); ++i)
+- LOG(VB_CHANSCAN, LOG_DEBUG, lines[i]);
++ if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_DEBUG))
++ {
++ QStringList lines = string.split('\n');
++ for (int i = 0; i < lines.size(); ++i)
++ LOG(VB_CHANSCAN, LOG_DEBUG, lines[i]);
++ }
+ }
+
+ void ChannelScanSM::HandlePAT(const ProgramAssociationTable *pat)
+@@ -932,7 +935,6 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ {
+ uint tsid = dtv_sm->GetTransportID();
+ LOG(VB_CHANSCAN, LOG_INFO, LOC +
+- QString("transport_tune_complete: wait_until_complete %1").arg(wait_until_complete) +
+ QString("\n\t\t\tsd->HasCachedAnyNIT(): %1").arg(sd->HasCachedAnyNIT()) +
+ QString("\n\t\t\tsd->HasCachedAnySDTs(): %1").arg(sd->HasCachedAnySDTs()) +
+ QString("\n\t\t\tsd->HasCachedAnyBATs(): %1").arg(sd->HasCachedAnyBATs()) +
+@@ -1026,6 +1028,7 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ {
+ TransportScanItem &item = *m_current;
+ item.m_tuning.m_frequency = item.freq_offset(m_current.offset());
++ item.m_signalStrength = m_signalMonitor->GetSignalStrength();
+
+ if (m_scanDTVTunerType == DTVTunerType::kTunerTypeDVBT2)
+ {
+@@ -1036,8 +1039,9 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ }
+
+ LOG(VB_CHANSCAN, LOG_INFO, LOC +
+- QString("Adding %1 offset %2 to m_channelList.")
+- .arg((*m_current).m_tuning.toString()).arg(m_current.offset()));
++ QString("Adding %1 offset %2 ss %3 to m_channelList.")
++ .arg(item.m_tuning.toString()).arg(m_current.offset())
++ .arg(item.m_signalStrength));
+
+ LOG(VB_CHANSCAN, LOG_DEBUG, LOC +
+ QString("%1(%2) m_inputName: %3 ").arg(__FUNCTION__).arg(__LINE__).arg(m_inputName) +
+@@ -1135,9 +1139,9 @@ static void update_info(ChannelInsertInfo &info,
+
+ info.m_chanNum.clear();
+
+- info.m_serviceId = vct->ProgramNumber(i);
+- info.m_atscMajorChannel = vct->MajorChannel(i);
+- info.m_atscMinorChannel = vct->MinorChannel(i);
++ info.m_serviceId = vct->ProgramNumber(i);
++ info.m_atscMajorChannel = vct->MajorChannel(i);
++ info.m_atscMinorChannel = vct->MinorChannel(i);
+
+ info.m_useOnAirGuide = !vct->IsHidden(i) || !vct->IsHiddenInGuide(i);
+
+@@ -1742,6 +1746,7 @@ ScanDTVTransportList ChannelScanSM::GetChannelList(bool addFullTS) const
+
+ ScanDTVTransport item((*it.first).m_tuning, tuner_type, cardid);
+ item.m_iptvTuning = (*(it.first)).m_iptvTuning;
++ item.m_signalStrength = (*(it.first)).m_signalStrength;
+
+ QMap<uint,ChannelInsertInfo>::iterator dbchan_it;
+ for (dbchan_it = pnum_to_dbchan.begin();
+@@ -1814,7 +1819,6 @@ ScanDTVTransportList ChannelScanSM::GetChannelList(bool addFullTS) const
+ return list;
+ }
+
+-
+ DTVSignalMonitor* ChannelScanSM::GetDTVSignalMonitor(void)
+ {
+ return dynamic_cast<DTVSignalMonitor*>(m_signalMonitor);
+@@ -1945,7 +1949,6 @@ bool ChannelScanSM::HasTimedOut(void)
+ }
+ #endif // USING_DVB
+
+-
+ // have the tables have timed out?
+ if (m_timer.hasExpired(m_channelTimeout))
+ {
+@@ -2052,8 +2055,7 @@ void ChannelScanSM::HandleActiveScan(void)
+ {
+ QString name = QString("TransportID %1").arg(it.key() & 0xffff);
+ TransportScanItem item(m_sourceID, name, *it, m_signalTimeout);
+- LOG(VB_CHANSCAN, LOG_INFO, LOC + "Adding " + name + " - " +
+- item.m_tuning.toString());
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Adding " + name + ' ' + item.m_tuning.toString());
+ m_scanTransports.push_back(item);
+ m_tsScanned.insert(it.key());
+ }
+@@ -2364,8 +2366,8 @@ bool ChannelScanSM::ScanIPTVChannels(uint sourceid,
+ bool ChannelScanSM::ScanTransportsStartingOn(
+ int sourceid, const QMap<QString,QString> &startChan)
+ {
+- if (startChan.find("std") == startChan.end() ||
+- startChan.find("type") == startChan.end())
++ if (startChan.find("std") == startChan.end() ||
++ startChan.find("type") == startChan.end())
+ {
+ return false;
+ }
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.h b/mythtv/libs/libmythtv/channelscan/channelscan_sm.h
+index f0e50e65aaa..d809b0ccec8 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.h
++++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.h
+@@ -221,6 +221,7 @@ class ChannelScanSM : public MPEGStreamListener,
+ uint m_frequency {0};
+ uint m_bouquetId {0};
+ uint m_regionId {0};
++ uint m_nitId {0};
+
+ // Optional info
+ DTVTunerType m_scanDTVTunerType {DTVTunerType::kTunerTypeUnknown};
+diff --git a/mythtv/libs/libmythtv/channelscan/frequencytablesetting.cpp b/mythtv/libs/libmythtv/channelscan/frequencytablesetting.cpp
+index cd7027917a5..99ca0649b85 100644
+--- a/mythtv/libs/libmythtv/channelscan/frequencytablesetting.cpp
++++ b/mythtv/libs/libmythtv/channelscan/frequencytablesetting.cpp
+@@ -87,5 +87,6 @@ ScanNetwork::ScanNetwork()
+
+ setLabel(QObject::tr("Country"));
+ addSelection(QObject::tr("Germany"), "de", country == "de");
++ addSelection(QObject::tr("Netherlands"), "nl", country == "nl");
+ addSelection(QObject::tr("United Kingdom"), "gb", country == "gb");
+ }
+diff --git a/mythtv/libs/libmythtv/dtvmultiplex.cpp b/mythtv/libs/libmythtv/dtvmultiplex.cpp
+index 26ac36c3d39..6477d526e1e 100644
+--- a/mythtv/libs/libmythtv/dtvmultiplex.cpp
++++ b/mythtv/libs/libmythtv/dtvmultiplex.cpp
+@@ -41,8 +41,8 @@ QString DTVMultiplex::toString() const
+ .arg(m_bandwidth.toString()).arg(m_transMode.toString())
+ .arg(m_guardInterval.toString()).arg(m_hierarchy.toString())
+ .arg(m_polarity.toString());
+- ret += QString(" fec: %1 msys: %2 rolloff: %3")
+- .arg(m_fec.toString()).arg(m_modSys.toString()).arg(m_rolloff.toString());
++ ret += QString(" fec:%1 msys:%2 rolloff:%3")
++ .arg(m_fec.toString(),-4).arg(m_modSys.toString(),-6).arg(m_rolloff.toString());
+
+ return ret;
+ }
+diff --git a/mythtv/libs/libmythtv/dtvmultiplex.h b/mythtv/libs/libmythtv/dtvmultiplex.h
+index 8bb3c2db4c7..608f9bf37a7 100644
+--- a/mythtv/libs/libmythtv/dtvmultiplex.h
++++ b/mythtv/libs/libmythtv/dtvmultiplex.h
+@@ -136,6 +136,7 @@ class MTV_PUBLIC ScanDTVTransport : public DTVMultiplex
+ DTVTunerType m_tuner_type {DTVTunerType::kTunerTypeUnknown};
+ uint m_cardid {0};
+ ChannelInsertInfoList m_channels;
++ int m_signalStrength {0};
+ };
+ using ScanDTVTransportList = vector<ScanDTVTransport>;
+
+diff --git a/mythtv/libs/libmythtv/frequencytables.h b/mythtv/libs/libmythtv/frequencytables.h
+index 7eead33d7cc..e7ba30e6756 100644
+--- a/mythtv/libs/libmythtv/frequencytables.h
++++ b/mythtv/libs/libmythtv/frequencytables.h
+@@ -179,6 +179,7 @@ class TransportScanItem
+ bool m_scanning {false}; ///< Probably Unnecessary
+ int m_freqOffsets[3] {0,0,0}; ///< Frequency offsets
+ unsigned m_timeoutTune {1000}; ///< Timeout to tune to a frequency
++ int m_signalStrength {0};
+
+ DTVMultiplex m_tuning; ///< Tuning info
+ IPTVTuningData m_iptvTuning; ///< IPTV Tuning info
+diff --git a/mythtv/libs/libmythtv/recorders/signalmonitor.h b/mythtv/libs/libmythtv/recorders/signalmonitor.h
+index fbe55da09b9..bae34ecf7b8 100644
+--- a/mythtv/libs/libmythtv/recorders/signalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/signalmonitor.h
+@@ -70,6 +70,7 @@ class SignalMonitor : protected MThread
+ /// \brief Returns milliseconds between signal monitoring events.
+ int GetUpdateRate() const { return m_update_rate; }
+ virtual QStringList GetStatusList(void) const;
++ int GetSignalStrength(void) { return m_signalStrength.GetNormalizedValue(0,100); }
+
+ /// \brief Returns true iff scriptStatus.IsGood() and signalLock.IsGood()
+ /// return true
+diff --git a/mythtv/libs/libmythtv/ringbuffer.cpp b/mythtv/libs/libmythtv/ringbuffer.cpp
+index d51a33ef087..a6c1e2c1681 100644
+--- a/mythtv/libs/libmythtv/ringbuffer.cpp
++++ b/mythtv/libs/libmythtv/ringbuffer.cpp
+@@ -344,7 +344,7 @@ void RingBuffer::UpdatePlaySpeed(float play_speed)
+ }
+
+ /** \fn RingBuffer::SetBufferSizeFactors(bool, bool)
+- * \brief Tells RingBuffer that the raw bitrate may be innacurate and the
++ * \brief Tells RingBuffer that the raw bitrate may be inaccurate and the
+ * underlying container is matroska, both of which may require a larger
+ * buffer size.
+ */
+
+From f48478b4772547cfb67cea011a962f068a057ff6 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Mon, 16 Mar 2020 23:24:11 +0100
+Subject: [PATCH 006/165] Add Full Scan option for DVB-C Netherlands
+
+Add an entry in the frequency tables for a "Full Scan" option
+for DVB-C in The Netherlands. There is currently only one entry
+which is the initial tuning frequency of the Ziggo network.
+
+(cherry picked from commit a74700c34657ef0cb99b4207f069e7881b4d948c)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/cardutil.cpp | 2 ++
+ mythtv/libs/libmythtv/dtvmultiplex.h | 2 +-
+ mythtv/libs/libmythtv/frequencytables.cpp | 11 ++++++++++-
+ 3 files changed, 13 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/cardutil.cpp b/mythtv/libs/libmythtv/cardutil.cpp
+index cdc9aea470a..f9098e7d911 100644
+--- a/mythtv/libs/libmythtv/cardutil.cpp
++++ b/mythtv/libs/libmythtv/cardutil.cpp
+@@ -2704,7 +2704,9 @@ vector<uint> CardUtil::GetLiveTVInputList(void)
+ QString CardUtil::GetDeviceName(dvb_dev_type_t type, const QString &device)
+ {
+ QString devname = QString(device);
++#if 0
+ LOG(VB_RECORD, LOG_DEBUG, LOC + QString("DVB Device (%1)").arg(devname));
++#endif
+ QString tmp = devname;
+
+ if (DVB_DEV_FRONTEND == type)
+diff --git a/mythtv/libs/libmythtv/dtvmultiplex.h b/mythtv/libs/libmythtv/dtvmultiplex.h
+index 608f9bf37a7..0e018d92776 100644
+--- a/mythtv/libs/libmythtv/dtvmultiplex.h
++++ b/mythtv/libs/libmythtv/dtvmultiplex.h
+@@ -103,7 +103,7 @@ class MTV_PUBLIC DTVMultiplex
+ DTVHierarchy m_hierarchy;
+ DTVPolarity m_polarity;
+ DTVCodeRate m_fec; ///< Inner Forward Error Correction rate
+- DTVModulationSystem m_modSys; ///< Modulation system
++ DTVModulationSystem m_modSys; ///< Modulation system
+ DTVRollOff m_rolloff;
+
+ // Optional additional info
+diff --git a/mythtv/libs/libmythtv/frequencytables.cpp b/mythtv/libs/libmythtv/frequencytables.cpp
+index 55e021fa5cb..26416adfb74 100644
+--- a/mythtv/libs/libmythtv/frequencytables.cpp
++++ b/mythtv/libs/libmythtv/frequencytables.cpp
+@@ -194,8 +194,11 @@ QString TransportScanItem::toString() const
+ .arg(m_tuning.m_transMode)
+ .arg(m_tuning.m_guardInterval)
+ .arg(m_tuning.m_hierarchy);
++ str += QString("\t symbol_rate(%1) fec(%2)\n")
++ .arg(m_tuning.m_symbolRate)
++ .arg(m_tuning.m_fec);
+ }
+- str += QString("\t offset[0..2]: %1 %2 %3")
++ str += QString("\toffset[0..2]: %1 %2 %3")
+ .arg(m_freqOffsets[0]).arg(m_freqOffsets[1]).arg(m_freqOffsets[2]);
+ return str;
+ }
+@@ -526,6 +529,12 @@ static void init_freq_tables(freq_table_map_t &fmap)
+ DTVCodeRate::kFECAuto, DTVModulation::kModulationQAMAuto,
+ 6900000, 0, 0);
+
++ // DVB-C Netherlands
++ fmap["dvbc_qam_nl0"] = new FrequencyTable(
++ 474000000, 474000000, 8000000, "Channel %1", 21,
++ DTVCodeRate::kFECAuto, DTVModulation::kModulationQAM64,
++ 6875000, 0, 0);
++
+ // DVB-C United Kingdom
+ fmap["dvbc_qam_gb0"] = new FrequencyTable(
+ 12324000, 12324000+1, 10, "Channel %1", 1,
+
+From e0e09b6b69c8e95fb45d97f1a2a56d625cb2df77 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Thu, 19 Mar 2020 23:55:26 +0100
+Subject: [PATCH 007/165] Scan option "Remove duplicate channels"
+
+Add new scan option to remove duplicate transports and duplicate
+channels based on signal strength of the received signal.
+This can be useful when receiving DVB-T2 and other OTA signals
+when the same channels can sometimes be received from more than
+one transmitter on different frequencies.
+
+Refs #13472
+
+(cherry picked from commit d0626e90287427408b28e2b0eabe12c0cb835118)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ .../libmythtv/channelscan/channelimporter.cpp | 232 +++++++++++++++++-
+ .../libmythtv/channelscan/channelimporter.h | 40 ++-
+ .../channelscan/channelscanmiscsettings.h | 16 ++
+ .../libmythtv/channelscan/channelscanner.cpp | 2 +
+ .../libmythtv/channelscan/channelscanner.h | 4 +
+ .../channelscan/channelscanner_cli.cpp | 2 +-
+ .../channelscan/channelscanner_gui.cpp | 2 +-
+ .../channelscan/scanwizardconfig.cpp | 7 +
+ .../libmythtv/channelscan/scanwizardconfig.h | 1 +
+ mythtv/libs/libmythtv/scanwizard.cpp | 2 +
+ mythtv/libs/libmythtv/scanwizard.h | 2 +
+ mythtv/programs/mythtv-setup/main.cpp | 11 +-
+ 12 files changed, 284 insertions(+), 37 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+index 623200543d1..5f15d1f1b16 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+@@ -76,6 +76,18 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ cout << "Logical Channel Numbers only: " << (m_lcnOnly ? "yes" : "no") << endl;
+ cout << "Complete scan data required : " << (m_completeOnly ? "yes" : "no") << endl;
+ cout << "Full search for old channels: " << (m_fullChannelSearch ? "yes" : "no") << endl;
++ cout << "Remove duplicate channels : " << (m_removeDuplicates ? "yes" : "no") << endl;
++ }
++
++ // List of transports
++ if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
++ {
++ if (transports.size() > 0)
++ {
++ cout << endl;
++ cout << "Transport list before processing (" << transports.size() << "):" << endl;
++ cout << FormatTransports(transports).toLatin1().constData() << endl;
++ }
+ }
+
+ // Print out each channel
+@@ -92,17 +104,50 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ if (m_doSave)
+ saved_scan = SaveScan(transports);
+
+- CleanupDuplicates(transports);
++ // Merge transports with the same frequency into one
++ MergeSameFrequency(transports);
++
++ // Remove duplicate transports with a lower signal strength.
++ ScanDTVTransportList duplicateTransports;
++ if (m_removeDuplicates)
++ {
++ ScanDTVTransportList duplicates;
++ RemoveDuplicateTransports(transports, duplicates);
++ if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
++ {
++ if (duplicates.size() > 0)
++ {
++ cout << endl;
++ cout << "Discarded duplicate transports (" << duplicates.size() << "):" << endl;
++ cout << FormatTransports(duplicates).toLatin1().constData() << endl;
++ }
++ }
++ }
+
++ // Remove the channels that do not pass various criteria.
+ FilterServices(transports);
+
+- // Print out each transport
+- uint transports_scanned_size = transports.size();
+- if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
++ // When there are duplicate channels remove the channels that are received
++ // on the transport with the lowest signal strength.
++ if (m_removeDuplicates)
+ {
+- cout << endl;
+- cout << "Transport list (" << transports_scanned_size << "):" << endl;
+- cout << FormatTransports(transports).toLatin1().constData() << endl;
++ ScanDTVTransportList duplicates;
++ RemoveDuplicateChannels(transports, duplicates);
++ if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
++ {
++ if (duplicates.size() > 0)
++ {
++ cout << endl;
++ cout << "Transports with discarded duplicate channels (" << duplicates.size() << "):" << endl;
++ cout << FormatTransports(duplicates).toLatin1().constData() << endl;
++ cout << endl;
++ cout << "Discarded duplicate channels (";
++ cout << SimpleCountChannels(duplicates) << "):" << endl;
++ ChannelImporterBasicStats infoA = CollectStats(duplicates);
++ cout << FormatChannels(transports, &infoA).toLatin1().constData() << endl;
++ cout << endl;
++ }
++ }
+ }
+
+ // Pull in DB info in transports
+@@ -114,7 +159,7 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ if (!db_trans.empty())
+ {
+ cout << endl;
+- cout << "Transport list of transports with channels in DB but not in scan (";
++ cout << "Transports with channels in DB but not in scan (";
+ cout << db_trans.size() << "):" << endl;
+ cout << FormatTransports(db_trans).toLatin1().constData() << endl;
+ }
+@@ -124,7 +169,7 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ FixUpOpenCable(transports);
+
+ // All channels in the scan after comparing with the database
+- if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
++ if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_DEBUG))
+ {
+ cout << endl << "Channel list after compare with database (";
+ cout << SimpleCountChannels(transports) << "):" << endl;
+@@ -158,7 +203,7 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ cout << FormatChannels(transports, &info).toLatin1().constData() << endl;
+
+ // Create summary
+- QString msg = GetSummary(transports_scanned_size, info, stats);
++ QString msg = GetSummary(transports.size(), info, stats);
+ cout << msg.toLatin1().constData() << endl << endl;
+
+ if (m_doInsert)
+@@ -905,7 +950,12 @@ void ChannelImporter::AddChanToCopy(
+ transport_copy.m_channels.push_back(chan);
+ }
+
+-void ChannelImporter::CleanupDuplicates(ScanDTVTransportList &transports)
++// ChannelImporter::MergeSameFrequency
++//
++// Merge transports that are on the same frequency by
++// combining all channels of both transports into one transport
++//
++void ChannelImporter::MergeSameFrequency(ScanDTVTransportList &transports)
+ {
+ ScanDTVTransportList no_dups;
+
+@@ -950,15 +1000,173 @@ void ChannelImporter::CleanupDuplicates(ScanDTVTransportList &transports)
+ transports[i].m_channels.push_back(transports[j].m_channels[k]);
+ }
+ LOG(VB_CHANSCAN, LOG_INFO, LOC +
+- QString("Duplicate transport ") + FormatTransport(transports[j]));
++ QString("Transport on same frequency:") + FormatTransport(transports[j]));
+ ignore[j] = true;
+ }
+ no_dups.push_back(transports[i]);
+ }
++ transports = no_dups;
++}
++
++// ChannelImporter::RemoveDuplicateTransports
++//
++// When there are two transports that have the same list of channels
++// but that are received on different frequencies then remove
++// the transport with the weakest signal.
++//
++void ChannelImporter::RemoveDuplicateTransports(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
++{
++ LOG(VB_CHANSCAN, LOG_INFO, LOC +
++ QString("Number of transports:%1").arg(transports.size()));
++
++ ScanDTVTransportList no_dups;
++ vector<bool> ignore;
++ ignore.resize(transports.size());
++ for (size_t i = 0; i < transports.size(); ++i)
++ {
++ ScanDTVTransport &ta = transports[i];
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport " +
++ FormatTransport(ta) + QString(" size(%1)").arg(ta.m_channels.size()));
++
++ if (!ignore[i])
++ {
++ for (size_t j = i+1; j < transports.size(); ++j)
++ {
++ ScanDTVTransport &tb = transports[j];
++ bool found_same = true;
++ bool found_diff = true;
++ if (ta.m_channels.size() == tb.m_channels.size())
++ {
++ LOG(VB_CHANSCAN, LOG_DEBUG, LOC + "Comparing transports " +
++ FormatTransport(ta) + QString(" size(%1)").arg(ta.m_channels.size()) +
++ FormatTransport(tb) + QString(" size(%1)").arg(tb.m_channels.size()));
++
++ for (size_t k = 0; found_same && k < tb.m_channels.size(); ++k)
++ {
++ if (tb.m_channels[k].IsSameChannel(ta.m_channels[k]), 0)
++ {
++ found_diff = false;
++ }
++ else
++ {
++ found_same = false;
++ }
++ }
++ }
++
++ // Transport with the lowest signal strength is duplicate
++ if (found_same && !found_diff)
++ {
++ size_t lowss = transports[i].m_signalStrength < transports[j].m_signalStrength ? i : j;
++ ignore[lowss] = true;
++ duplicates.push_back(transports[lowss]);
++
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Duplicate transports found");
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport A " + FormatTransport(transports[i]));
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport B " + FormatTransport(transports[j]));
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Discarding " + FormatTransport(transports[lowss]));
++ }
++ }
++ }
++ if (!ignore[i])
++ {
++ no_dups.push_back(transports[i]);
++ }
++ }
+
+ transports = no_dups;
+ }
+
++// ChannelImporter::RemoveDuplicateChannels
++//
++// When there are identical channels that are present on different transports
++// then remove the channel that is received on the transport with the weakest signal.
++//
++void ChannelImporter::RemoveDuplicateChannels(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
++{
++ LOG(VB_CHANSCAN, LOG_INFO, LOC +
++ QString("%1 for %2 transports").arg(__func__).arg(transports.size()));
++
++ // Flag the duplicate channels in this map.
++ // The key is transport index (16 bits, shifted to the left) plus channel index (16 bits).
++ // This works if there are max 65536 transports and max 65535 channels per transport.
++ QMap<uint,bool> dup_chan;
++
++ // Compare each channel with every channel in the other transports.
++ // We do not compare against channels in the same transport as it is unlikely
++ // to find a duplicate in the same transport and also we cannot make a selection
++ // based on the signal strength when there is a duplicate in the same transport.
++ for (size_t ita = 0; ita < transports.size(); ++ita) // All transports in the list
++ {
++ ScanDTVTransport &ta = transports[ita]; // Transport A is one transport from the list
++ for (size_t ica = 0; ica < ta.m_channels.size(); ++ica) // All channels in transport A
++ {
++ ChannelInsertInfo &ca = ta.m_channels[ica]; // Channel A is one channel from transport A
++ for (size_t itb = ita + 1; itb < transports.size(); ++itb) // All transports above transport A
++ {
++ ScanDTVTransport &tb = transports[itb]; // Transport B is one transport from the list
++ for (size_t icb = 0; icb < tb.m_channels.size(); ++icb) // All channels in transport B
++ {
++ ChannelInsertInfo &cb = tb.m_channels[icb]; // Channel B is one channel from transport B
++ if (ca.IsSameChannel(cb, 1)) // Are Channel A and Channel B duplicate?
++ {
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Duplicate channels: " +
++ "\n\t" + FormatTransport(ta) + " " + FormatChannel(ta, ca) +
++ "\n\t" + FormatTransport(tb) + " " + FormatChannel(tb, cb));
++ if (ta.m_signalStrength < tb.m_signalStrength) // Yes, compare signal strength of transports
++ {
++ dup_chan[(ita<<16)+ica] = true; // Flag Channel A as duplicate
++ }
++ else
++ {
++ dup_chan[(itb<<16)+icb] = true; // Flag Channel B as duplicate
++ }
++ }
++ }
++ }
++ }
++ }
++
++ // Go throught the list and copy the channels we keep to no_duplicates and
++ // copy the channels that are discarded to the duplicates.
++ ScanDTVTransportList no_duplicates;
++ for (size_t ita = 0; ita < transports.size(); ++ita) // All transports in the list
++ {
++ ScanDTVTransport &ta = transports[ita]; // One transport from the list
++ ChannelInsertInfoList ch_dup;
++ ChannelInsertInfoList ch_nodup;
++ for (size_t ica = 0; ica < ta.m_channels.size(); ++ica) // All channels in this transport
++ {
++ ChannelInsertInfo &ca = ta.m_channels[ica]; // One channel from this transport
++ if (dup_chan[(ita<<16)+ica]) // Channel flagged as duplicate?
++ {
++ ch_dup.push_back(ca); // Copy the channel to the duplicates list
++ LOG(VB_CHANSCAN, LOG_INFO, LOC +
++ "Discard duplicate channel " +
++ FormatChannel(ta, ca));
++ }
++ else
++ {
++ ch_nodup.push_back(ca); // Copy the channel to the no_duplicates list
++ }
++ }
++ if (ch_dup.size() > 0) // At least one channel in this transport?
++ {
++ ScanDTVTransport tmp = ta; // Yes, put the transport with the
++ ta.m_channels = ch_dup; // duplicate channels in the list.
++ duplicates.push_back(tmp);
++ }
++ if (ch_nodup.size() > 0) // At leat one non-duplicate channel in this transport?
++ {
++ ScanDTVTransport tmp = ta; // Yes, put the transport with the
++ ta.m_channels = ch_nodup; // non-duplicate channels in the list
++ no_duplicates.push_back(tmp);
++ }
++ }
++
++ transports = no_duplicates;
++}
++
+ void ChannelImporter::FilterServices(ScanDTVTransportList &transports) const
+ {
+ bool require_av = (m_serviceRequirements & kRequireAV) == kRequireAV;
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.h b/mythtv/libs/libmythtv/channelscan/channelimporter.h
+index 92f56b1ba84..806bf8eb62d 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.h
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.h
+@@ -79,6 +79,7 @@ class MTV_PUBLIC ChannelImporter
+ bool _delete, bool insert, bool save,
+ bool fta_only, bool lcn_only, bool complete_only,
+ bool full_channel_search,
++ bool remove_duplicates,
+ ServiceRequirements service_requirements,
+ bool success = false) :
+ m_useGui(gui),
+@@ -90,6 +91,7 @@ class MTV_PUBLIC ChannelImporter
+ m_lcnOnly(lcn_only),
+ m_completeOnly(complete_only),
+ m_fullChannelSearch(full_channel_search),
++ m_removeDuplicates(remove_duplicates),
+ m_success(success),
+ m_serviceRequirements(service_requirements) { }
+
+@@ -140,7 +142,9 @@ class MTV_PUBLIC ChannelImporter
+
+ static QString toString(ChannelType type);
+
+- static void CleanupDuplicates(ScanDTVTransportList &transports);
++ static void MergeSameFrequency(ScanDTVTransportList &transports);
++ static void RemoveDuplicateTransports(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates);
++ static void RemoveDuplicateChannels(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates);
+ void FilterServices(ScanDTVTransportList &transports) const;
+ ScanDTVTransportList GetDBTransports(
+ uint sourceid, ScanDTVTransportList &transports) const;
+@@ -252,26 +256,20 @@ class MTV_PUBLIC ChannelImporter
+ const ChannelInsertInfo &chan);
+
+ private:
+- bool m_useGui;
+- bool m_isInteractive;
+- bool m_doDelete;
+- bool m_doInsert;
+- bool m_doSave;
+- /// Only FreeToAir (non-encrypted) channels desired post scan?
+- bool m_ftaOnly;
+- /// Only services with logical channel numbers desired post scan?
+- bool m_lcnOnly;
+- /// Only services with complete scandata desired post scan?
+- bool m_completeOnly;
+- /// Keep existing channel numbers on channel update
+- bool m_keepChannelNumbers {true};
+- /// Full search for old channels
+- bool m_fullChannelSearch {false};
+- /// To pass information IPTV channel scan succeeded
+- bool m_success {false};
+- /// Services desired post scan
+- ServiceRequirements m_serviceRequirements;
+-
++ bool m_useGui;
++ bool m_isInteractive;
++ bool m_doDelete;
++ bool m_doInsert;
++ bool m_doSave;
++ bool m_ftaOnly {true}; // Only FreeToAir (non-encrypted) channels desired post scan?
++ bool m_lcnOnly {false}; // Only services with logical channel numbers desired post scan?
++ bool m_completeOnly {true}; // Only services with complete scandata desired post scan?
++ bool m_keepChannelNumbers {true}; // Keep existing channel numbers on channel update
++ bool m_fullChannelSearch {false}; // Full search for old channels across transports in database
++ bool m_removeDuplicates {false}; // Remove duplicate transports and channels in scan
++ bool m_success {false}; // To pass information IPTV channel scan succeeded
++
++ ServiceRequirements m_serviceRequirements; // Services desired post scan
+ QEventLoop m_eventLoop;
+ };
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h b/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h
+index e0d395f47d3..f1bd65b3a38 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h
++++ b/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h
+@@ -171,6 +171,22 @@ class FullChannelSearch : public TransMythUICheckBoxSetting
+ };
+ };
+
++class RemoveDuplicates : public TransMythUICheckBoxSetting
++{
++ public:
++ RemoveDuplicates()
++ {
++ setLabel(QObject::tr("Remove duplicate channels"));
++ setHelpText(
++ QObject::tr(
++ "If set, select the channel with the strongest signal when "
++ "there are duplicate transports and channels. "
++ "This option is useful for DVB-T2 and ATSC/OTA when the same channel "
++ "can sometimes be received from different transmitters."));
++ setValue(false);
++ };
++};
++
+ class AddFullTS : public TransMythUICheckBoxSetting
+ {
+ public:
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
+index a4b816a7f01..dbd671c6579 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
+@@ -120,6 +120,7 @@ void ChannelScanner::Scan(
+ bool do_lcn_only,
+ bool do_complete_only,
+ bool do_full_channel_search,
++ bool do_remove_duplicates,
+ bool do_add_full_ts,
+ ServiceRequirements service_requirements,
+ // stuff needed for particular scans
+@@ -135,6 +136,7 @@ void ChannelScanner::Scan(
+ m_channelNumbersOnly = do_lcn_only;
+ m_completeOnly = do_complete_only;
+ m_fullSearch = do_full_channel_search;
++ m_removeDuplicates = do_remove_duplicates;
+ m_addFullTS = do_add_full_ts;
+ m_serviceRequirements = service_requirements;
+ m_sourceid = sourceid;
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner.h b/mythtv/libs/libmythtv/channelscan/channelscanner.h
+index 66e0f9b7d90..9afb92354f3 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner.h
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner.h
+@@ -79,6 +79,7 @@ class MTV_PUBLIC ChannelScanner
+ bool do_lcn_only,
+ bool do_complete_only,
+ bool do_full_channel_search,
++ bool do_remove_duplicates,
+ bool do_add_full_ts,
+ ServiceRequirements service_requirements,
+ // stuff needed for particular scans
+@@ -147,6 +148,9 @@ class MTV_PUBLIC ChannelScanner
+ /// Extended search for old channels post scan?
+ bool m_fullSearch {false};
+
++ /// Remove duplicate transports and channels?
++ bool m_removeDuplicates {false};
++
+ /// Add MPTS "full transport stream" channels
+ bool m_addFullTS {false};
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp b/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp
+index eae66d82165..cd7cafc6576 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp
+@@ -137,7 +137,7 @@ void ChannelScannerCLI::Process(const ScanDTVTransportList &_transports)
+ {
+ ChannelImporter ci(false, m_interactive, !m_onlysavescan, !m_onlysavescan, true,
+ m_freeToAirOnly, m_channelNumbersOnly, m_completeOnly,
+- m_fullSearch, m_serviceRequirements);
++ m_fullSearch, m_removeDuplicates, m_serviceRequirements);
+ ci.Process(_transports, m_sourceid);
+ }
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp b/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
+index bc89b80264e..c1f7f6437e4 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
+@@ -136,7 +136,7 @@ void ChannelScannerGUI::Process(const ScanDTVTransportList &_transports,
+ {
+ ChannelImporter ci(true, true, true, true, true,
+ m_freeToAirOnly, m_channelNumbersOnly, m_completeOnly,
+- m_fullSearch, m_serviceRequirements, success);
++ m_fullSearch, m_removeDuplicates, m_serviceRequirements, success);
+ ci.Process(_transports, m_sourceid);
+ }
+
+diff --git a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp
+index 5b7b5b34e0f..3834cb1492d 100644
+--- a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp
++++ b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp
+@@ -35,6 +35,7 @@ void ScanWizard::SetupConfig(
+ m_lcnOnly = new ChannelNumbersOnly();
+ m_completeOnly = new CompleteChannelsOnly();
+ m_fullSearch = new FullChannelSearch();
++ m_removeDuplicates = new RemoveDuplicates();
+ m_addFullTS = new AddFullTS();
+ m_trustEncSI = new TrustEncSISetting();
+
+@@ -45,6 +46,7 @@ void ScanWizard::SetupConfig(
+ addChild(m_lcnOnly);
+ addChild(m_completeOnly);
+ addChild(m_fullSearch);
++ addChild(m_removeDuplicates);
+ addChild(m_addFullTS);
+ addChild(m_trustEncSI);
+
+@@ -100,6 +102,11 @@ bool ScanWizard::DoFullChannelSearch(void) const
+ return m_fullSearch->boolValue();
+ }
+
++bool ScanWizard::DoRemoveDuplicates(void) const
++{
++ return m_removeDuplicates->boolValue();
++}
++
+ bool ScanWizard::DoAddFullTS(void) const
+ {
+ return m_addFullTS->boolValue();
+diff --git a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h
+index 6451da309bd..8d6438c8904 100644
+--- a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h
++++ b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h
+@@ -47,6 +47,7 @@ class FreeToAirOnly;
+ class ChannelNumbersOnly;
+ class CompleteChannelsOnly;
+ class FullChannelSearch;
++class RemoveDuplicates;
+ class AddFullTS;
+ class TrustEncSISetting;
+
+diff --git a/mythtv/libs/libmythtv/scanwizard.cpp b/mythtv/libs/libmythtv/scanwizard.cpp
+index 8d25b51910b..0c1fd9a0e8f 100644
+--- a/mythtv/libs/libmythtv/scanwizard.cpp
++++ b/mythtv/libs/libmythtv/scanwizard.cpp
+@@ -140,6 +140,7 @@ void ScanWizard::Scan()
+ DoChannelNumbersOnly(),
+ DoCompleteChannelsOnly(),
+ DoFullChannelSearch(),
++ DoRemoveDuplicates(),
+ GetServiceRequirements());
+ ci.Process(transports, sourceid);
+ }
+@@ -185,6 +186,7 @@ void ScanWizard::Scan()
+ DoTestDecryption(), DoFreeToAirOnly(),
+ DoChannelNumbersOnly(), DoCompleteChannelsOnly(),
+ DoFullChannelSearch(),
++ DoRemoveDuplicates(),
+ DoAddFullTS(),
+ GetServiceRequirements(),
+
+diff --git a/mythtv/libs/libmythtv/scanwizard.h b/mythtv/libs/libmythtv/scanwizard.h
+index 9e035282570..3c680ea1929 100644
+--- a/mythtv/libs/libmythtv/scanwizard.h
++++ b/mythtv/libs/libmythtv/scanwizard.h
+@@ -92,6 +92,7 @@ class MTV_PUBLIC ScanWizard : public GroupSetting
+ bool DoChannelNumbersOnly(void) const;
+ bool DoCompleteChannelsOnly(void) const;
+ bool DoFullChannelSearch(void) const;
++ bool DoRemoveDuplicates(void) const;
+ bool DoAddFullTS(void) const;
+ bool DoTestDecryption(void) const;
+ bool DoScanOpenTV(void) const;
+@@ -106,6 +107,7 @@ class MTV_PUBLIC ScanWizard : public GroupSetting
+ ChannelNumbersOnly *m_lcnOnly {nullptr};
+ CompleteChannelsOnly *m_completeOnly {nullptr};
+ FullChannelSearch *m_fullSearch {nullptr};
++ RemoveDuplicates *m_removeDuplicates {nullptr};
+ AddFullTS *m_addFullTS {nullptr};
+ TrustEncSISetting *m_trustEncSI {nullptr};
+ // End of members moved from ScanWizardConfig
+diff --git a/mythtv/programs/mythtv-setup/main.cpp b/mythtv/programs/mythtv-setup/main.cpp
+index 67ac7d54335..5f612cc2536 100644
+--- a/mythtv/programs/mythtv-setup/main.cpp
++++ b/mythtv/programs/mythtv-setup/main.cpp
+@@ -258,6 +258,7 @@ int main(int argc, char *argv[])
+ bool scanLCNOnly = false;
+ bool scanCompleteOnly = false;
+ bool scanFullChannelSearch = false;
++ bool scanRemoveDuplicates = false;
+ bool addFullTS = false;
+ ServiceRequirements scanServiceRequirements = kRequireAV;
+ uint scanCardId = 0;
+@@ -346,6 +347,8 @@ int main(int argc, char *argv[])
+ scanCompleteOnly = true;
+ if (cmdline.toBool("fullsearch"))
+ scanFullChannelSearch = true;
++ if (cmdline.toBool("removeduplicates"))
++ scanRemoveDuplicates = true;
+ if (cmdline.toBool("addfullts"))
+ addFullTS = true;
+ if (cmdline.toBool("servicetype"))
+@@ -501,6 +504,7 @@ int main(int argc, char *argv[])
+ scanLCNOnly,
+ scanCompleteOnly,
+ scanFullChannelSearch,
++ scanRemoveDuplicates,
+ addFullTS,
+ scanServiceRequirements,
+ // stuff needed for particular scans
+@@ -535,8 +539,11 @@ int main(int argc, char *argv[])
+ {
+ ScanDTVTransportList list = LoadScan(scanImport);
+ ChannelImporter ci(false, true, true, true, false,
+- scanFTAOnly, scanLCNOnly, scanCompleteOnly,
+- scanFullChannelSearch, scanServiceRequirements);
++ scanFTAOnly, scanLCNOnly,
++ scanCompleteOnly,
++ scanFullChannelSearch,
++ scanRemoveDuplicates,
++ scanServiceRequirements);
+ ci.Process(list);
+ }
+ cout<<"*** SCAN IMPORT END ***"<<endl;
+
+From fec7309d231992cc88156e7fe80fd060f5639142 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Fri, 20 Mar 2020 23:37:32 +0100
+Subject: [PATCH 008/165] Fix for "Remove duplicate channels" scan option
+
+Fix counting bug in this new feature.
+Fixed corner case in updating existing channels where
+the same channel was present more than once in the database.
+Improved debug output.
+
+(cherry picked from commit e9931870756c32d3c0ba85e6ab6a6d71130a571a)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ .../libmythtv/channelscan/channelimporter.cpp | 98 ++++++++++---------
+ 1 file changed, 54 insertions(+), 44 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+index 5f15d1f1b16..bfec5028844 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+@@ -108,7 +108,6 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ MergeSameFrequency(transports);
+
+ // Remove duplicate transports with a lower signal strength.
+- ScanDTVTransportList duplicateTransports;
+ if (m_removeDuplicates)
+ {
+ ScanDTVTransportList duplicates;
+@@ -143,8 +142,7 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ cout << endl;
+ cout << "Discarded duplicate channels (";
+ cout << SimpleCountChannels(duplicates) << "):" << endl;
+- ChannelImporterBasicStats infoA = CollectStats(duplicates);
+- cout << FormatChannels(transports, &infoA).toLatin1().constData() << endl;
++ cout << FormatChannels(duplicates).toLatin1().constData() << endl;
+ cout << endl;
+ }
+ }
+@@ -200,7 +198,7 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ // Print out each channel
+ cout << endl;
+ cout << "Channel list (" << SimpleCountChannels(transports) << "):" << endl;
+- cout << FormatChannels(transports, &info).toLatin1().constData() << endl;
++ cout << FormatChannels(transports).toLatin1().constData() << endl;
+
+ // Create summary
+ QString msg = GetSummary(transports.size(), info, stats);
+@@ -1037,13 +1035,13 @@ void ChannelImporter::RemoveDuplicateTransports(ScanDTVTransportList &transports
+ bool found_diff = true;
+ if (ta.m_channels.size() == tb.m_channels.size())
+ {
+- LOG(VB_CHANSCAN, LOG_DEBUG, LOC + "Comparing transports " +
++ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Comparing transports " +
+ FormatTransport(ta) + QString(" size(%1)").arg(ta.m_channels.size()) +
+ FormatTransport(tb) + QString(" size(%1)").arg(tb.m_channels.size()));
+
+ for (size_t k = 0; found_same && k < tb.m_channels.size(); ++k)
+ {
+- if (tb.m_channels[k].IsSameChannel(ta.m_channels[k]), 0)
++ if (tb.m_channels[k].IsSameChannel(ta.m_channels[k], 1))
+ {
+ found_diff = false;
+ }
+@@ -1061,10 +1059,11 @@ void ChannelImporter::RemoveDuplicateTransports(ScanDTVTransportList &transports
+ ignore[lowss] = true;
+ duplicates.push_back(transports[lowss]);
+
+- LOG(VB_CHANSCAN, LOG_INFO, LOC + "Duplicate transports found");
+- LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport A " + FormatTransport(transports[i]));
+- LOG(VB_CHANSCAN, LOG_INFO, LOC + "Transport B " + FormatTransport(transports[j]));
+- LOG(VB_CHANSCAN, LOG_INFO, LOC + "Discarding " + FormatTransport(transports[lowss]));
++ LOG(VB_CHANSCAN, LOG_INFO, LOC +
++ "Duplicate transports found:" +
++ "\n\t" + "Transport A " + FormatTransport(transports[i]) +
++ "\n\t" + "Transport B " + FormatTransport(transports[j]) +
++ "\n\t" + "Discarding " + FormatTransport(transports[lowss]));
+ }
+ }
+ }
+@@ -1111,8 +1110,8 @@ void ChannelImporter::RemoveDuplicateChannels(ScanDTVTransportList &transports,
+ if (ca.IsSameChannel(cb, 1)) // Are Channel A and Channel B duplicate?
+ {
+ LOG(VB_CHANSCAN, LOG_INFO, LOC + "Duplicate channels: " +
+- "\n\t" + FormatTransport(ta) + " " + FormatChannel(ta, ca) +
+- "\n\t" + FormatTransport(tb) + " " + FormatChannel(tb, cb));
++ "\n\t" + FormatTransport(ta) + " " + FormatChannel(ta, ca) +
++ "\n\t" + FormatTransport(tb) + " " + FormatChannel(tb, cb));
+ if (ta.m_signalStrength < tb.m_signalStrength) // Yes, compare signal strength of transports
+ {
+ dup_chan[(ita<<16)+ica] = true; // Flag Channel A as duplicate
+@@ -1153,13 +1152,13 @@ void ChannelImporter::RemoveDuplicateChannels(ScanDTVTransportList &transports,
+ if (ch_dup.size() > 0) // At least one channel in this transport?
+ {
+ ScanDTVTransport tmp = ta; // Yes, put the transport with the
+- ta.m_channels = ch_dup; // duplicate channels in the list.
++ tmp.m_channels = ch_dup; // duplicate channels in the list.
+ duplicates.push_back(tmp);
+ }
+ if (ch_nodup.size() > 0) // At leat one non-duplicate channel in this transport?
+ {
+ ScanDTVTransport tmp = ta; // Yes, put the transport with the
+- ta.m_channels = ch_nodup; // non-duplicate channels in the list
++ tmp.m_channels = ch_nodup; // non-duplicate channels in the list
+ no_duplicates.push_back(tmp);
+ }
+ }
+@@ -1283,6 +1282,7 @@ ScanDTVTransportList ChannelImporter::GetDBTransports(
+ return not_in_scan;
+ }
+
++ QMap<uint,bool> found_in_scan;
+ while (query.next())
+ {
+ ScanDTVTransport db_transport;
+@@ -1296,31 +1296,35 @@ ScanDTVTransportList ChannelImporter::GetDBTransports(
+ }
+
+ bool found_transport = false;
+- QMap<uint,bool> found_chan;
++ QMap<uint,bool> found_in_database;
+
+ // Search for old channels in the same transport of the scan.
+- for (auto & transport : transports) // All transports in scan
+- { // Scanned transport
+- if (transport.IsEqual(tuner_type, db_transport, 500 * freq_mult, true)) // Same transport?
++ for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
++ {
++ ScanDTVTransport &scan_transport = transports[ist]; // Transport from the scan
++ if (scan_transport.IsEqual(tuner_type, db_transport, 500 * freq_mult, true)) // Same transport?
+ {
+- found_transport = true;
+- transport.m_mplex = db_transport.m_mplex; // Found multiplex
+-
++ found_transport = true; // Yes
++ scan_transport.m_mplex = db_transport.m_mplex; // Found multiplex
+ for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
+ {
+- if (!found_chan[jdc]) // Channel not found yet?
++ if (!found_in_database[jdc]) // Channel not found yet?
+ {
+ ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
+-
+- for (auto & chan : transport.m_channels) // All channels in scanned transport
++ for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
+ { // Channel in scanned transport
+- if (db_chan.IsSameChannel(chan, 2)) // Same transport, relaxed check
++ if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
+ {
+- found_in_same_transport++;
+- found_chan[jdc] = true; // Found channel from database in scan
+- chan.m_dbMplexId = mplexid; // Found multiplex
+- chan.m_channelId = db_chan.m_channelId; // This is the crucial field
+- break; // Ready with scanned transport
++ ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
++ if (db_chan.IsSameChannel(scan_chan, 2)) // Same transport, relaxed check
++ {
++ found_in_same_transport++;
++ found_in_database[jdc] = true; // Channel from db found in scan
++ found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
++ scan_chan.m_dbMplexId = db_transport.m_mplex; // Found multiplex
++ scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
++ break; // Ready with scanned transport
++ }
+ }
+ }
+ }
+@@ -1333,22 +1337,28 @@ ScanDTVTransportList ChannelImporter::GetDBTransports(
+ // This can identify the channels that have moved to another transport.
+ if (m_fullChannelSearch)
+ {
+- for (size_t idc = 0; idc < db_transport.m_channels.size(); ++idc) // All channels in database transport
++ for (size_t ist = 0; ist < transports.size(); ++ist) // All transports in scan
+ {
+- ChannelInsertInfo &db_chan = db_transport.m_channels[idc]; // Channel in database transport
+-
+- for (size_t jst = 0; jst < transports.size() && !found_chan[idc]; ++jst) // All transports in scan until found
++ ScanDTVTransport &scan_transport = transports[ist]; // Scanned transport
++ for (size_t jdc = 0; jdc < db_transport.m_channels.size(); ++jdc) // All channels in database transport
+ {
+- ScanDTVTransport &transport = transports[jst]; // Scanned transport
+- for (auto & chan : transport.m_channels) // All channels in scanned transport
++ if (!found_in_database[jdc]) // Channel not found yet?
+ {
+- // Channel in scanned transport
+- if (db_chan.IsSameChannel(chan, 1)) // Different transport, check
+- { // network id and service id
+- found_in_other_transport++;
+- found_chan[idc] = true; // Found channel from database in scan
+- chan.m_channelId = db_chan.m_channelId; // This is the crucial field
+- break; // Ready with scanned transport
++ ChannelInsertInfo &db_chan = db_transport.m_channels[jdc]; // Channel in database transport
++ for (size_t ksc = 0; ksc < scan_transport.m_channels.size(); ++ksc) // All channels in scanned transport
++ {
++ if (!found_in_scan[(ist<<16)+ksc]) // Scanned channel not yet found?
++ {
++ ChannelInsertInfo &scan_chan = scan_transport.m_channels[ksc];
++ if (db_chan.IsSameChannel(scan_chan, 1)) // Other transport, check
++ { // network id and service id
++ found_in_other_transport++;
++ found_in_database[jdc] = true; // Channel from db found in scan
++ found_in_scan[(ist<<16)+ksc] = true; // Channel from scan found in db
++ scan_chan.m_channelId = db_chan.m_channelId; // This is the crucial field
++ break; // Ready with scanned transport
++ }
++ }
+ }
+ }
+ }
+@@ -1365,7 +1375,7 @@ ScanDTVTransportList ChannelImporter::GetDBTransports(
+
+ for (size_t idc = 0; idc < db_transport.m_channels.size(); ++idc)
+ {
+- if (!found_chan[idc])
++ if (!found_in_database[idc])
+ {
+ tmp.m_channels.push_back(db_transport.m_channels[idc]);
+ found_nowhere++;
+
+From c8c59f5548ce99d1248cb52e467e4c9e1100476e Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sun, 22 Mar 2020 19:00:37 +0100
+Subject: [PATCH 009/165] Updated "Remove duplicates" channel scan option
+
+Renamed the feature from "Remove duplicate channels" to "Remove duplicates".
+Changed the default for this option to Selected/Checked.
+Removed the check on individual channels across all scanned channels.
+The implementation does not check for original network ID plus transport ID
+on a per-transport basis, as suggested in ticket #12107 for DVB, but it checks
+this on all channels in the transport. The implementation is also expected to work for ATSC.
+Thanks to John Pilkington for numerous tests in the daily changing UK Freeview landscape.
+
+Refs #13472
+Fixes #12107
+
+(cherry picked from commit 1b4d44b468de0a8c7ad2c25a1d779ce1dc2c06b8)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/channelinfo.cpp | 2 +-
+ .../libmythtv/channelscan/channelimporter.cpp | 132 ++----------------
+ .../libmythtv/channelscan/channelimporter.h | 3 +-
+ .../libmythtv/channelscan/channelscan_sm.cpp | 4 +
+ .../channelscan/channelscanmiscsettings.h | 10 +-
+ mythtv/libs/libmythtv/dtvmultiplex.h | 6 +-
+ mythtv/libs/libmythtv/frequencytables.h | 5 +-
+ 7 files changed, 34 insertions(+), 128 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelinfo.cpp b/mythtv/libs/libmythtv/channelinfo.cpp
+index 6a12848ba4b..114c2db6cee 100644
+--- a/mythtv/libs/libmythtv/channelinfo.cpp
++++ b/mythtv/libs/libmythtv/channelinfo.cpp
+@@ -487,7 +487,7 @@ bool ChannelInsertInfo::IsSameChannel(
+ if (relaxed > 1)
+ {
+ if (("mpeg" == m_siStandard || "mpeg" == other.m_siStandard ||
+- "dvb" == m_siStandard || "dvb" == other.m_siStandard ||
++ "dvb" == m_siStandard || "dvb" == other.m_siStandard ||
+ m_siStandard.isEmpty() || other.m_siStandard.isEmpty()) &&
+ (m_serviceId == other.m_serviceId))
+ {
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+index bfec5028844..9573feb9344 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+@@ -111,7 +111,7 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ if (m_removeDuplicates)
+ {
+ ScanDTVTransportList duplicates;
+- RemoveDuplicateTransports(transports, duplicates);
++ RemoveDuplicates(transports, duplicates);
+ if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
+ {
+ if (duplicates.size() > 0)
+@@ -119,28 +119,8 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ cout << endl;
+ cout << "Discarded duplicate transports (" << duplicates.size() << "):" << endl;
+ cout << FormatTransports(duplicates).toLatin1().constData() << endl;
+- }
+- }
+- }
+-
+- // Remove the channels that do not pass various criteria.
+- FilterServices(transports);
+-
+- // When there are duplicate channels remove the channels that are received
+- // on the transport with the lowest signal strength.
+- if (m_removeDuplicates)
+- {
+- ScanDTVTransportList duplicates;
+- RemoveDuplicateChannels(transports, duplicates);
+- if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
+- {
+- if (duplicates.size() > 0)
+- {
+ cout << endl;
+- cout << "Transports with discarded duplicate channels (" << duplicates.size() << "):" << endl;
+- cout << FormatTransports(duplicates).toLatin1().constData() << endl;
+- cout << endl;
+- cout << "Discarded duplicate channels (";
++ cout << "With channels (";
+ cout << SimpleCountChannels(duplicates) << "):" << endl;
+ cout << FormatChannels(duplicates).toLatin1().constData() << endl;
+ cout << endl;
+@@ -148,6 +128,9 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+ }
+ }
+
++ // Remove the channels that do not pass various criteria.
++ FilterServices(transports);
++
+ // Pull in DB info in transports
+ // Channels not found in scan but only in DB are returned in db_trans
+ sourceid = transports[0].m_channels[0].m_sourceId;
+@@ -1006,13 +989,17 @@ void ChannelImporter::MergeSameFrequency(ScanDTVTransportList &transports)
+ transports = no_dups;
+ }
+
+-// ChannelImporter::RemoveDuplicateTransports
++// ChannelImporter::RemoveDuplicates
+ //
+ // When there are two transports that have the same list of channels
+ // but that are received on different frequencies then remove
+ // the transport with the weakest signal.
+ //
+-void ChannelImporter::RemoveDuplicateTransports(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
++// In DVB two transports are duplicates when the original network ID and the
++// transport ID are the same. This is possibly different in ATSC.
++// Here all channels of both transports are compared.
++//
++void ChannelImporter::RemoveDuplicates(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
+ {
+ LOG(VB_CHANSCAN, LOG_INFO, LOC +
+ QString("Number of transports:%1").arg(transports.size()));
+@@ -1041,7 +1028,7 @@ void ChannelImporter::RemoveDuplicateTransports(ScanDTVTransportList &transports
+
+ for (size_t k = 0; found_same && k < tb.m_channels.size(); ++k)
+ {
+- if (tb.m_channels[k].IsSameChannel(ta.m_channels[k], 1))
++ if (tb.m_channels[k].IsSameChannel(ta.m_channels[k], 0))
+ {
+ found_diff = false;
+ }
+@@ -1076,96 +1063,6 @@ void ChannelImporter::RemoveDuplicateTransports(ScanDTVTransportList &transports
+ transports = no_dups;
+ }
+
+-// ChannelImporter::RemoveDuplicateChannels
+-//
+-// When there are identical channels that are present on different transports
+-// then remove the channel that is received on the transport with the weakest signal.
+-//
+-void ChannelImporter::RemoveDuplicateChannels(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates)
+-{
+- LOG(VB_CHANSCAN, LOG_INFO, LOC +
+- QString("%1 for %2 transports").arg(__func__).arg(transports.size()));
+-
+- // Flag the duplicate channels in this map.
+- // The key is transport index (16 bits, shifted to the left) plus channel index (16 bits).
+- // This works if there are max 65536 transports and max 65535 channels per transport.
+- QMap<uint,bool> dup_chan;
+-
+- // Compare each channel with every channel in the other transports.
+- // We do not compare against channels in the same transport as it is unlikely
+- // to find a duplicate in the same transport and also we cannot make a selection
+- // based on the signal strength when there is a duplicate in the same transport.
+- for (size_t ita = 0; ita < transports.size(); ++ita) // All transports in the list
+- {
+- ScanDTVTransport &ta = transports[ita]; // Transport A is one transport from the list
+- for (size_t ica = 0; ica < ta.m_channels.size(); ++ica) // All channels in transport A
+- {
+- ChannelInsertInfo &ca = ta.m_channels[ica]; // Channel A is one channel from transport A
+- for (size_t itb = ita + 1; itb < transports.size(); ++itb) // All transports above transport A
+- {
+- ScanDTVTransport &tb = transports[itb]; // Transport B is one transport from the list
+- for (size_t icb = 0; icb < tb.m_channels.size(); ++icb) // All channels in transport B
+- {
+- ChannelInsertInfo &cb = tb.m_channels[icb]; // Channel B is one channel from transport B
+- if (ca.IsSameChannel(cb, 1)) // Are Channel A and Channel B duplicate?
+- {
+- LOG(VB_CHANSCAN, LOG_INFO, LOC + "Duplicate channels: " +
+- "\n\t" + FormatTransport(ta) + " " + FormatChannel(ta, ca) +
+- "\n\t" + FormatTransport(tb) + " " + FormatChannel(tb, cb));
+- if (ta.m_signalStrength < tb.m_signalStrength) // Yes, compare signal strength of transports
+- {
+- dup_chan[(ita<<16)+ica] = true; // Flag Channel A as duplicate
+- }
+- else
+- {
+- dup_chan[(itb<<16)+icb] = true; // Flag Channel B as duplicate
+- }
+- }
+- }
+- }
+- }
+- }
+-
+- // Go throught the list and copy the channels we keep to no_duplicates and
+- // copy the channels that are discarded to the duplicates.
+- ScanDTVTransportList no_duplicates;
+- for (size_t ita = 0; ita < transports.size(); ++ita) // All transports in the list
+- {
+- ScanDTVTransport &ta = transports[ita]; // One transport from the list
+- ChannelInsertInfoList ch_dup;
+- ChannelInsertInfoList ch_nodup;
+- for (size_t ica = 0; ica < ta.m_channels.size(); ++ica) // All channels in this transport
+- {
+- ChannelInsertInfo &ca = ta.m_channels[ica]; // One channel from this transport
+- if (dup_chan[(ita<<16)+ica]) // Channel flagged as duplicate?
+- {
+- ch_dup.push_back(ca); // Copy the channel to the duplicates list
+- LOG(VB_CHANSCAN, LOG_INFO, LOC +
+- "Discard duplicate channel " +
+- FormatChannel(ta, ca));
+- }
+- else
+- {
+- ch_nodup.push_back(ca); // Copy the channel to the no_duplicates list
+- }
+- }
+- if (ch_dup.size() > 0) // At least one channel in this transport?
+- {
+- ScanDTVTransport tmp = ta; // Yes, put the transport with the
+- tmp.m_channels = ch_dup; // duplicate channels in the list.
+- duplicates.push_back(tmp);
+- }
+- if (ch_nodup.size() > 0) // At leat one non-duplicate channel in this transport?
+- {
+- ScanDTVTransport tmp = ta; // Yes, put the transport with the
+- tmp.m_channels = ch_nodup; // non-duplicate channels in the list
+- no_duplicates.push_back(tmp);
+- }
+- }
+-
+- transports = no_duplicates;
+-}
+-
+ void ChannelImporter::FilterServices(ScanDTVTransportList &transports) const
+ {
+ bool require_av = (m_serviceRequirements & kRequireAV) == kRequireAV;
+@@ -1497,8 +1394,7 @@ QString ChannelImporter::FormatChannel(
+ QString msg;
+ QTextStream ssMsg(&msg);
+
+- ssMsg << transport.m_modulation.toString().toLatin1().constData()
+- << ":";
++ ssMsg << transport.m_modulation.toString().toLatin1().constData() << ":";
+ ssMsg << transport.m_frequency << ":";
+
+ QString si_standard = (chan.m_siStandard=="opencable") ?
+@@ -1665,6 +1561,8 @@ QString ChannelImporter::FormatTransport(
+ QString msg;
+ QTextStream ssMsg(&msg);
+ ssMsg << transport.toString();
++ ssMsg << QString(" onid:%1").arg(transport.m_networkID);
++ ssMsg << QString(" tsid:%1").arg(transport.m_transportID);
+ ssMsg << QString(" ss:%1").arg(transport.m_signalStrength);
+ return msg;
+ }
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.h b/mythtv/libs/libmythtv/channelscan/channelimporter.h
+index 806bf8eb62d..84ec87cb41f 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.h
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.h
+@@ -143,8 +143,7 @@ class MTV_PUBLIC ChannelImporter
+ static QString toString(ChannelType type);
+
+ static void MergeSameFrequency(ScanDTVTransportList &transports);
+- static void RemoveDuplicateTransports(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates);
+- static void RemoveDuplicateChannels(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates);
++ static void RemoveDuplicates(ScanDTVTransportList &transports, ScanDTVTransportList &duplicates);
+ void FilterServices(ScanDTVTransportList &transports) const;
+ ScanDTVTransportList GetDBTransports(
+ uint sourceid, ScanDTVTransportList &transports) const;
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+index c2ec52d199c..543c7e1a9cd 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+@@ -1029,6 +1029,8 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ TransportScanItem &item = *m_current;
+ item.m_tuning.m_frequency = item.freq_offset(m_current.offset());
+ item.m_signalStrength = m_signalMonitor->GetSignalStrength();
++ item.m_networkID = dtv_sm->GetNetworkID();
++ item.m_transportID = dtv_sm->GetTransportID();
+
+ if (m_scanDTVTunerType == DTVTunerType::kTunerTypeDVBT2)
+ {
+@@ -1747,6 +1749,8 @@ ScanDTVTransportList ChannelScanSM::GetChannelList(bool addFullTS) const
+ ScanDTVTransport item((*it.first).m_tuning, tuner_type, cardid);
+ item.m_iptvTuning = (*(it.first)).m_iptvTuning;
+ item.m_signalStrength = (*(it.first)).m_signalStrength;
++ item.m_networkID = (*(it.first)).m_networkID;
++ item.m_transportID = (*(it.first)).m_transportID;
+
+ QMap<uint,ChannelInsertInfo>::iterator dbchan_it;
+ for (dbchan_it = pnum_to_dbchan.begin();
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h b/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h
+index f1bd65b3a38..67988838696 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h
++++ b/mythtv/libs/libmythtv/channelscan/channelscanmiscsettings.h
+@@ -176,14 +176,14 @@ class RemoveDuplicates : public TransMythUICheckBoxSetting
+ public:
+ RemoveDuplicates()
+ {
+- setLabel(QObject::tr("Remove duplicate channels"));
++ setLabel(QObject::tr("Remove duplicates"));
+ setHelpText(
+ QObject::tr(
+- "If set, select the channel with the strongest signal when "
+- "there are duplicate transports and channels. "
+- "This option is useful for DVB-T2 and ATSC/OTA when the same channel "
++ "If set, select the transport stream multiplex with the best signal "
++ "when identical transports are received on different frequencies. "
++ "This option is useful for DVB-T2 and ATSC/OTA when a transport "
+ "can sometimes be received from different transmitters."));
+- setValue(false);
++ setValue(true);
+ };
+ };
+
+diff --git a/mythtv/libs/libmythtv/dtvmultiplex.h b/mythtv/libs/libmythtv/dtvmultiplex.h
+index 0e018d92776..d7bc90f8ae9 100644
+--- a/mythtv/libs/libmythtv/dtvmultiplex.h
++++ b/mythtv/libs/libmythtv/dtvmultiplex.h
+@@ -133,9 +133,11 @@ class MTV_PUBLIC ScanDTVTransport : public DTVMultiplex
+ const QString& mod_sys, const QString& rolloff);
+
+ public:
+- DTVTunerType m_tuner_type {DTVTunerType::kTunerTypeUnknown};
+- uint m_cardid {0};
++ DTVTunerType m_tuner_type {DTVTunerType::kTunerTypeUnknown};
++ uint m_cardid {0};
+ ChannelInsertInfoList m_channels;
++ uint m_networkID {0};
++ uint m_transportID {0};
+ int m_signalStrength {0};
+ };
+ using ScanDTVTransportList = vector<ScanDTVTransport>;
+diff --git a/mythtv/libs/libmythtv/frequencytables.h b/mythtv/libs/libmythtv/frequencytables.h
+index e7ba30e6756..a2e2979ec0b 100644
+--- a/mythtv/libs/libmythtv/frequencytables.h
++++ b/mythtv/libs/libmythtv/frequencytables.h
+@@ -179,13 +179,16 @@ class TransportScanItem
+ bool m_scanning {false}; ///< Probably Unnecessary
+ int m_freqOffsets[3] {0,0,0}; ///< Frequency offsets
+ unsigned m_timeoutTune {1000}; ///< Timeout to tune to a frequency
+- int m_signalStrength {0};
+
+ DTVMultiplex m_tuning; ///< Tuning info
+ IPTVTuningData m_iptvTuning; ///< IPTV Tuning info
+ QString m_iptvChannel; ///< IPTV base channel
+
+ DTVChannelInfoList m_expectedChannels;
++
++ int m_signalStrength {0};
++ uint m_networkID {0};
++ uint m_transportID {0};
+ };
+
+ class transport_scan_items_it_t
+
+From aa63cae341f4001b5a447dc871f2d1962b883845 Mon Sep 17 00:00:00 2001
+From: Philipp Matthias Hahn <pmhahn+mythtv(a)pmhahn.de>
+Date: Mon, 30 Mar 2020 09:53:27 +0200
+Subject: [PATCH 010/165] Python: Update JOBTYPEs
+
+89b6416b50c 78 (Robert McNamara 2011-07-03 16:49:38 -0700 79) JOB_METADATA = 0x0004,
+ab33dd919ef 80 (John Poet 2018-03-08 16:02:25 -0700 80) JOB_PREVIEW = 0x0008,
+
+(cherry picked from commit f9bb4f76c864c65969ccc64541c30548c6f65344)
+---
+ mythtv/bindings/python/MythTV/static.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/bindings/python/MythTV/static.py b/mythtv/bindings/python/MythTV/static.py
+index 167d71377ac..1f9e1203f63 100644
+--- a/mythtv/bindings/python/MythTV/static.py
++++ b/mythtv/bindings/python/MythTV/static.py
+@@ -119,6 +119,8 @@ class JOBTYPE( object ):
+ SYSTEMJOB = 0x00ff
+ TRANSCODE = 0x0001
+ COMMFLAG = 0x0002
++ METADATA = 0x0004
++ PREVIEW = 0x0008
+ USERJOB = 0xff00
+ USERJOB1 = 0x0100
+ USERJOB2 = 0x0200
+
+From 57f25431f5a6935f6d6473323f7f66a7f6a80cc4 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Fri, 3 Apr 2020 11:09:51 +0100
+Subject: [PATCH 011/165] VAAPI: Fix direct rendering for Intel iHD series
+ drivers
+
+---
+ .../libmythtv/decoders/mythvaapicontext.cpp | 43 ++++++++++++-------
+ 1 file changed, 28 insertions(+), 15 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythvaapicontext.cpp b/mythtv/libs/libmythtv/decoders/mythvaapicontext.cpp
+index 9819e981409..65bf676e3be 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvaapicontext.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvaapicontext.cpp
+@@ -333,25 +333,38 @@ int MythVAAPIContext::InitialiseContext(AVCodecContext *Context)
+ // MPEG2 on Ironlake where it seems to return I420 labelled as NV12. I420 is
+ // buggy on Sandybridge (stride?) and produces a mixture of I420/NV12 frames
+ // for H.264 on Ironlake.
+- int format = VA_FOURCC_NV12;
+- QString vendor = interop->GetVendor();
+- if (vendor.contains("ironlake", Qt::CaseInsensitive))
+- if (CODEC_IS_MPEG(Context->codec_id))
+- format = VA_FOURCC_I420;
++ // This may need extending for AMD etc
+
+- if (format != VA_FOURCC_NV12)
++ QString vendor = interop->GetVendor();
++ // Intel NUC
++ if (vendor.contains("iHD", Qt::CaseInsensitive) && vendor.contains("Intel", Qt::CaseInsensitive))
++ {
++ vaapi_frames_ctx->attributes = nullptr;
++ vaapi_frames_ctx->nb_attributes = 0;
++ }
++ // i965 series
++ else
+ {
+- auto vaapiid = static_cast<MythCodecID>(kCodec_MPEG1_VAAPI + (mpeg_version(Context->codec_id) - 1));
+- LOG(VB_GENERAL, LOG_INFO, LOC + QString("Forcing surface format for %1 and %2 with driver '%3'")
+- .arg(toString(vaapiid)).arg(MythOpenGLInterop::TypeToString(type)).arg(vendor));
++ int format = VA_FOURCC_NV12;
++ if (vendor.contains("ironlake", Qt::CaseInsensitive))
++ if (CODEC_IS_MPEG(Context->codec_id))
++ format = VA_FOURCC_I420;
++
++ if (format != VA_FOURCC_NV12)
++ {
++ auto vaapiid = static_cast<MythCodecID>(kCodec_MPEG1_VAAPI + (mpeg_version(Context->codec_id) - 1));
++ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Forcing surface format for %1 and %2 with driver '%3'")
++ .arg(toString(vaapiid)).arg(MythOpenGLInterop::TypeToString(type)).arg(vendor));
++ }
++
++ VASurfaceAttrib prefs[3] = {
++ { VASurfaceAttribPixelFormat, VA_SURFACE_ATTRIB_SETTABLE, { VAGenericValueTypeInteger, { format } } },
++ { VASurfaceAttribUsageHint, VA_SURFACE_ATTRIB_SETTABLE, { VAGenericValueTypeInteger, { VA_SURFACE_ATTRIB_USAGE_HINT_DISPLAY } } },
++ { VASurfaceAttribMemoryType, VA_SURFACE_ATTRIB_SETTABLE, { VAGenericValueTypeInteger, { VA_SURFACE_ATTRIB_MEM_TYPE_VA} } } };
++ vaapi_frames_ctx->attributes = prefs;
++ vaapi_frames_ctx->nb_attributes = 3;
+ }
+
+- VASurfaceAttrib prefs[3] = {
+- { VASurfaceAttribPixelFormat, VA_SURFACE_ATTRIB_SETTABLE, { VAGenericValueTypeInteger, { format } } },
+- { VASurfaceAttribUsageHint, VA_SURFACE_ATTRIB_SETTABLE, { VAGenericValueTypeInteger, { VA_SURFACE_ATTRIB_USAGE_HINT_DISPLAY } } },
+- { VASurfaceAttribMemoryType, VA_SURFACE_ATTRIB_SETTABLE, { VAGenericValueTypeInteger, { VA_SURFACE_ATTRIB_MEM_TYPE_VA} } } };
+- vaapi_frames_ctx->attributes = prefs;
+- vaapi_frames_ctx->nb_attributes = 3;
+ hw_frames_ctx->sw_format = FramesFormat(Context->sw_pix_fmt);
+ int referenceframes = AvFormatDecoder::GetMaxReferenceFrames(Context);
+ hw_frames_ctx->initial_pool_size = static_cast<int>(VideoBuffers::GetNumBuffers(FMT_VAAPI, referenceframes, true));
+
+From 809ea6028d6b30315f88d6fbd3374317cedf4361 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Fri, 3 Apr 2020 11:46:43 +0100
+Subject: [PATCH 012/165] MythNVDECContext: Additional logging for decoder
+ check
+
+---
+ .../libmythtv/decoders/mythnvdeccontext.cpp | 90 +++++++++++--------
+ 1 file changed, 55 insertions(+), 35 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp b/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp
+index a54a340acc6..56f8187647c 100644
+--- a/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp
+@@ -65,6 +65,11 @@ MythCodecID MythNVDECContext::GetSupportedCodec(AVCodecContext **Context,
+
+ cudaVideoChromaFormat cudaformat = cudaVideoChromaFormat_Monochrome;
+ VideoFrameType type = PixelFormatToFrameType((*Context)->pix_fmt);
++ uint depth = static_cast<uint>(ColorDepth(type) - 8);
++ QString desc = QString("'%1 %2 %3 Depth:%4 %5x%6'")
++ .arg(codecstr).arg(profile).arg(pixfmt).arg(depth + 8)
++ .arg((*Context)->width).arg((*Context)->height);
++
+ // N.B. on stream changes format is set to CUDA/NVDEC. This may break if the new
+ // stream has an unsupported chroma but the decoder should fail gracefully - just later.
+ if ((FMT_NVDEC == type) || (format_is_420(type)))
+@@ -74,13 +79,15 @@ MythCodecID MythNVDECContext::GetSupportedCodec(AVCodecContext **Context,
+ else if (format_is_444(type))
+ cudaformat = cudaVideoChromaFormat_444;
+
+- uint depth = static_cast<uint>(ColorDepth(type) - 8);
+- bool supported = false;
+-
+ if ((cudacodec == cudaVideoCodec_NumCodecs) || (cudaformat == cudaVideoChromaFormat_Monochrome))
++ {
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Unknown codec or format");
++ LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("NVDEC does NOT support %1").arg(desc));
+ return failure;
++ }
+
+ // iterate over known decoder capabilities
++ bool supported = false;
+ const std::vector<MythNVDECCaps>& profiles = MythNVDECContext::GetProfiles();
+ for (auto cap : profiles)
+ {
+@@ -91,40 +98,40 @@ MythCodecID MythNVDECContext::GetSupportedCodec(AVCodecContext **Context,
+ }
+ }
+
+- QString desc = QString("'%1 %2 %3 Depth:%4 %5x%6'")
+- .arg(codecstr).arg(profile).arg(pixfmt).arg(depth + 8)
+- .arg((*Context)->width).arg((*Context)->height);
++ if (!supported)
++ {
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "No matching profile support");
++ LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("NVDEC does NOT support %1").arg(desc));
++ return failure;
++ }
+
+ // and finally try and retrieve the actual FFmpeg decoder
+- if (supported)
++ QString name = QString((*Codec)->name) + "_cuvid";
++ if (name == "mpeg2video_cuvid")
++ name = "mpeg2_cuvid";
++ for (int i = 0; ; i++)
+ {
+- for (int i = 0; ; i++)
+- {
+- const AVCodecHWConfig *config = avcodec_get_hw_config(*Codec, i);
+- if (!config)
+- break;
++ const AVCodecHWConfig *config = avcodec_get_hw_config(*Codec, i);
++ if (!config)
++ break;
+
+- if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) &&
+- (config->device_type == AV_HWDEVICE_TYPE_CUDA))
++ if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) &&
++ (config->device_type == AV_HWDEVICE_TYPE_CUDA))
++ {
++ AVCodec *codec = avcodec_find_decoder_by_name(name.toLocal8Bit());
++ if (codec)
+ {
+- QString name = QString((*Codec)->name) + "_cuvid";
+- if (name == "mpeg2video_cuvid")
+- name = "mpeg2_cuvid";
+- AVCodec *codec = avcodec_find_decoder_by_name(name.toLocal8Bit());
+- if (codec)
+- {
+- LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("NVDEC supports decoding %1").arg(desc));
+- *Codec = codec;
+- gCodecMap->freeCodecContext(Stream);
+- *Context = gCodecMap->getCodecContext(Stream, *Codec);
+- return success;
+- }
+- break;
++ LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("NVDEC supports decoding %1").arg(desc));
++ *Codec = codec;
++ gCodecMap->freeCodecContext(Stream);
++ *Context = gCodecMap->getCodecContext(Stream, *Codec);
++ return success;
+ }
++ break;
+ }
+ }
+
+- LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("NVDEC does NOT support %1").arg(desc));
++ LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to find decoder '%1'").arg(name));
+ return failure;
+ }
+
+@@ -497,10 +504,23 @@ bool MythNVDECContext::MythNVDECCaps::Supports(cudaVideoCodec Codec, cudaVideoCh
+ uint Depth, int Width, int Height)
+ {
+ uint mblocks = static_cast<uint>((Width * Height) / 256);
+- return (Codec == m_codec) && (Format == m_format) && (Depth == m_depth) &&
+- (m_maximum.width() >= Width) && (m_maximum.height() >= Height) &&
+- (m_minimum.width() <= Width) && (m_minimum.height() <= Height) &&
+- (m_macroBlocks >= mblocks);
++
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
++ QString("Trying to match: Codec %1 Format %2 Depth %3 Width %4 Height %5 MBs %6")
++ .arg(Codec).arg(Format).arg(Depth).arg(Width).arg(Height).arg(mblocks));
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
++ QString("to this profile: Codec %1 Format %2 Depth %3 Width %4<->%5 Height %6<->%7 MBs %8")
++ .arg(m_codec).arg(m_format).arg(m_depth)
++ .arg(m_minimum.width()).arg(m_maximum.width())
++ .arg(m_minimum.height()).arg(m_maximum.height()).arg(m_macroBlocks));
++
++ bool result = (Codec == m_codec) && (Format == m_format) && (Depth == m_depth) &&
++ (m_maximum.width() >= Width) && (m_maximum.height() >= Height) &&
++ (m_minimum.width() <= Width) && (m_minimum.height() <= Height) &&
++ (m_macroBlocks >= mblocks);
++
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("%1 Match").arg(result ? "" : "NO"));
++ return result;
+ }
+
+ bool MythNVDECContext::HaveNVDEC(void)
+@@ -524,9 +544,9 @@ bool MythNVDECContext::HaveNVDEC(void)
+ LOG(VB_GENERAL, LOG_INFO, LOC + "Supported/available NVDEC decoders:");
+ for (auto profile : profiles)
+ {
+- LOG(VB_GENERAL, LOG_INFO, LOC +
+- MythCodecContext::GetProfileDescription(profile.m_profile,profile.m_maximum,
+- profile.m_type, profile.m_depth + 8));
++ QString desc = MythCodecContext::GetProfileDescription(profile.m_profile,profile.m_maximum,
++ profile.m_type, profile.m_depth + 8);
++ LOG(VB_GENERAL, LOG_INFO, LOC + desc + QString(" MBs: %1").arg(profile.m_macroBlocks));
+ }
+ }
+ }
+
+From 2cd6ccb419cbab542c782f9b8df2cdfb7f406ee5 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Fri, 3 Apr 2020 17:02:09 +0100
+Subject: [PATCH 013/165] NVDEC: Fix decoder support check
+
+(cherry picked from commit a2f19766c768c5ef40f596e1edf30dc3afb6889c)
+---
+ mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp b/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp
+index 56f8187647c..04cb4601971 100644
+--- a/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythnvdeccontext.cpp
+@@ -91,7 +91,7 @@ MythCodecID MythNVDECContext::GetSupportedCodec(AVCodecContext **Context,
+ const std::vector<MythNVDECCaps>& profiles = MythNVDECContext::GetProfiles();
+ for (auto cap : profiles)
+ {
+- if (cap.Supports(cudacodec, cudaformat, depth, (*Context)->width, (*Context)->width))
++ if (cap.Supports(cudacodec, cudaformat, depth, (*Context)->width, (*Context)->height))
+ {
+ supported = true;
+ break;
+
+From 9258fb56250392eb49abeb71785e4166b78c48fd Mon Sep 17 00:00:00 2001
+From: Paul Harrison <paul(a)mythqml.net>
+Date: Tue, 31 Mar 2020 18:25:58 +0100
+Subject: [PATCH 014/165] configure: enable by default gnutls support in our
+ copy of ffmpeg
+
+This is required to support playback from protocols using these schema :-
+https, rtmps, rtmpts and tls.
+
+Support is enabled by default if gnutls is found but can be explicitly
+disabled by passing --disable-gnutls to ./configure
+
+(cherry picked from commit 427d87b0c62d9c51a0ce8543b770abda73dfc6fc)
+---
+ mythtv/configure | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+diff --git a/mythtv/configure b/mythtv/configure
+index 77aee2d0768..3a7ec61fd74 100755
+--- a/mythtv/configure
++++ b/mythtv/configure
+@@ -154,6 +154,7 @@ Advanced options (experts only):
+ directory with parser.h [$libxml2_path_default]
+ --disable-libdns-sd disable DNS Service Discovery (Bonjour/Zeroconf/Avahi)
+ --disable-libcrypto disable use of the OpenSSL cryptographic library
++ --disable-gnutls disable use of GnuTLS for SSL/TLS protocol support in ffmpeg
+
+ --with-bindings=LIST install the bindings specified in the
+ comma-separated list
+@@ -1967,6 +1968,7 @@ MYTHTV_CONFIG_LIST='
+ joystick_menu
+ libcec
+ libcrypto
++ gnutls
+ libdns_sd
+ libfftw3
+ libmpeg2external
+@@ -2758,6 +2760,7 @@ enable libaom
+ enable libass
+ enable libcec
+ enable libcrypto
++enable gnutls
+ enable libdav1d
+ enable libdns_sd
+ enable libxml2
+@@ -6642,6 +6645,12 @@ fi
+
+ enabled libcrypto && check_lib crypto openssl/rsa.h RSA_new -lcrypto || disable libcrypto
+
++if enabled gnutls ; then
++ if ! $(pkg-config --exists gnutls) ; then
++ disable gnutls
++ fi
++fi
++
+ if test $target_os != darwin ; then
+ enabled libdns_sd && check_lib dns_sd dns_sd.h DNSServiceRegister -ldns_sd || disable libdns_sd
+ fi
+@@ -7225,6 +7234,10 @@ if enabled libdav1d; then
+ ffopts="$ffopts --enable-libdav1d"
+ fi
+
++if enabled gnutls; then
++ ffopts="$ffopts --enable-gnutls"
++fi
++
+ ffmpeg_extra_cflags="$extra_cflags -w"
+
+ ## Call FFmpeg configure here
+@@ -7448,6 +7461,7 @@ if enabled frontend; then
+ fi
+ echo "libdns_sd (Bonjour) ${libdns_sd-no}"
+ echo "libcrypto ${libcrypto-no}"
++echo "gnutls ${gnutls-no}"
+ if enabled libbluray_external; then
+ echo "bluray support yes (system)"
+ else
+
+From ece4ff7ebc1b1383e62d11ad55bd9fa845185d90 Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Tue, 7 Apr 2020 10:07:43 -0400
+Subject: [PATCH 015/165] Fix improper sorting of names that start with "An".
+
+The US English translation accidentally removed the space after this
+word, causing it to always be removed instead of just when it was a
+separate word.
+
+Fixes #13603.
+
+(cherry picked from commit 5d3743c7989813d4297ff97ba8959cf17b659889)
+---
+ mythtv/i18n/mythfrontend_en_us.qm | Bin 209928 -> 209845 bytes
+ mythtv/i18n/mythfrontend_en_us.ts | 2 +-
+ .../test_mythsorthelper.cpp | 5 +++++
+ 3 files changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/i18n/mythfrontend_en_us.qm b/mythtv/i18n/mythfrontend_en_us.qm
+index 22d06341fa6fb59b3c6d2e590b5cbad8910a6a51..1658b5c5ffa525fb0f7f12a1ed682069e684f020 100644
+GIT binary patch
+delta 9235
+zcmXY1c|c6v`+v^4Gxy$E?p&=C(V~*1QcCj5R+f+@B}61iRJJUAsYwahb7jwqis~gx
+zCHspC@!FHMtXZNgm5}B4$nURDGgEWVInU?wd6v_P*XqJo>h*@Y?f~=&(8Oi{c@U5(
+zeOr)T`Ub#m1kknza5w{W-%J3f-}pB`*H(zv5siRm1Ar^O&W=X>{Te@a!`C_bi1<3k
+z3!vNY*D;8{UuPlS0%&al;EwP2$f>{|@Pk|qpgX>iiwo*rj=$g!FnSiyw6=)2z_gyg
+z^qdFqYXLC6$MHcoQm<;ABY?@mU&>7fwu=nlq(=+VJbPf5ZwD~02R^A8=qejf6nq8h
+zyHzqG{TcG>vNVC1>1LmSPL8{Qj<AK!N6LUiC4$@24gl?%p<BW$pd$;Q$NXLZZ%o0H
+zbpv`Q0lb7ppa+h?fO+|NaBE;-Rv|!HXBfP)7|7v|FeD`g$clR~q#+on(QNQ78V|JJ
+z4j7e{2gGn31n@TjHhhM_b8@gEJ10Wm=Te}NXJE|Qp8#`S!jxKTAkV{KTGJ>XRc)nq
+zRI3PwNDDlouYQsz)rRbb7>z5y&ZQ80mjNj64sp%dK-RW{__APtz&z01z|CA)3`uM9
+zfEMS&3P%}`Q!J#cJp*LcL`eC*3P>A&Smikh$OCUkO-%-H{tlaW%YjA(NQKm~Q}Pwq
+zKWP^b<08m&yauqQ3=TGB08KK5BahO6<Zgx&U<cI9MG_b%=fiM=z682S1vi500E#8J
+z@p2Tv+#0x5(HbZn1m&7x0B=vhU2`j-`p!}mqiwwto+;~qnyiLr?{O>57A;8jzn0P&
+zt<OGqn^pqE*9vM+B>`<HhmX^p0sbezr*b!d=}VyDb~%u=Ht?BF!XPh%FYO|LMDHQ2
+zI0eA$7~$z$pfwfJ4}8`<qP%h&=(J_TkQ4%05=ji-^#d}=U2>2)wm*4{Sgp(ea#<i9
+z-{C{Az9M$PVL-p_A&z!<dW9ZRoXlL;k8~|v01!HnxX!EwQc(K`Pptogr#q1D@D<?4
+zRpLFP1nB&Sq<=>w^OS>Rpocv`cro#@HU#qTO)~oW9aKy(8M{~k^lBs-e+G$kV!$7~
+zo7;l)<+wlisuu}0{Q$K6_w6L~#(IEpi^-G{D<H4NkgzQ=_=oRWkRAw^JmuPv<4AO^
+z28Dcw%<+!KWe)g*N3XRYEuT(eR=WZX3?mCB;kTsTBMbe`00bW*3-eQe?ASvVK2Jve
+zwj!}I2FUfRQkLA7bd|2kwXF@w1~nQ%>U$yuECsT_f^2${f?Mb!{g7)t%*c_RBLNJ9
+z{@{Q8N#QClpq*>~;H(64G{FL3b|g7L_5pC|<it(nL-&i4FWcL47I~UC8L0hy@?1jd
+z4*!R|7=RpGb&kB8WeqeSRXV_GJuJx^j~JjEKatwspRN{3?NT$K#*yS>7zRbBBj1`0
+zFbGq~_kr#}Vy}>2UofPFQ>7oQR@a&8@7M@LT|@Q1;$GJkQp54c?+MqaiA@-4a~m~D
+z2nN!t6*YU&0OZ+AYUx-7uqvCj?}SgG$Ea150!XnyZ4Mj)a(5oJ)g>cg_R%gqKLIJP
+zrCoCHJo>m%7qT44-*2c(>RKRM7g6{3$U8~m^n4s>kI)<d*<9*rfJ}WgkoJAhAIRJ*
+z)O%n&&_%<k_w#`OiHUT$MLm#=%XCCUCXiqUI^yvEfJ}CxBYykKV*{y{)B2316H)`w
+zhgZ^&rCOlRlj-CpS0KVo8g}|HkkGv}>{9^{3!X+y!Kenjr%~p^fHuX_nb%R1Yc1*Q
+zDBRihT*;TWC4Witc=NF)G_f!q$XjQcv~(3v$JKNxV}>hkpvg0nfY_zbRaG*K>ruL9
+zUm>!=pROBR4#Yf<ZX9ik=43`UjztGuR87-r&{SrW(@p*LfiCMP)$-;#eVQZSN`9Eq
+z+*uOF*@^x$KL*Ga89iKr`?py~^S9anS^k_JbD9od_n00x#Yl>m>52ArxT>S{MCm+q
+z6I*)H@jMVq9X*>+jeM=A#i>mgD~?`lgXd*}SoQ%(BB7Vw+5lbuFTL!EvQ(}<M6Z;i
+ziCtVEr73JJo$38UOM!ZY(5m?vKpZOR)1Uc3>uc!ivv@G`9!Ql6Tip=)iABOhRnyPS
+z*MPV;)28fPAagFzuV#8cM&%>w1M#1aXn<IOXb2>r8=?{7CPZVz&xl$eqeml}ARa|*
+z4J6Q*Hj8MLM<Qu+vo~hsLG=5>o0uWB48%MIkdl;ox@8R8BLGN67^5W7K<z9URV(xn
+zaXO=lV1Z0aWsJ&E_{Tr|!Mg!1NMDNok2Q>TtPOyol4;!&$yqamX{V2Q(xNA0ZiC-f
+zG@3D=il#WkfwBDQ4e;R-V-r3Z1Chws9Ld5p7^w7o+A{Wc%z$`qU^*|9fRt@y9C|bY
+z@v&zdyR88_{x0LR><z%Qy^M3|aez-LOgG&Upl>@f?s+KNFvmYQRUt*Gw3dUJAu)L7
+zOBrTpn_`p~&-fl~0N7H*1T5T-a_S~!smygFnXplA0MYSGSTU~i??7gT918(xU?O+!
+z!(`NtiQaqz$i1%2Y|b4kmnY1eyO^+N3}9mX<^lOQmx;;M0L|ON%sX3(mN1A}Q2q+(
+ze2ht$jN&|)!YuKci|1&_B#zDpa<&b#Is+xcePlL<?81F}sXa)(6sNW&yQD022VMEU
+zOxYDLAiYKAa>-dtqS?%U#;8coW6X_N9FVq)m>b)@fp{yKTS3T7^ZCqePb?U+otcV&
+zOn@3&=7A|PG9r|DxVt@&VGhiznXy0}jxq17FrdyIm`}T>08OZ6J`ceSPiL6s?K=U+
+zzhk}^_5x|Jpl`>@XjC~+>u8xQ+zP+@o{Yy7OZsQa6mQO93O1LC&A5m0N2O$8pl*_^
+z^Lk|A`#mzp!{I>oZIpHK!%Vd)T;?2W2ym*g1?kOmGS6?PfgG=t_3nTfXY~ZxpwL8&
+z?mpR&ISoLEJdh1LBm%j4Ocr2rAEWw078D)|beo$jDEc?*WDyB<K%YL5&5pf~EEp-9
+z7yS$qet0k0!r(lBWAkOPMHq_yoHRk>-r7Kx@)=3d*G#rXKLDWZE!o=2Dxi~wNCz}d
+zid5M~djd4wSgO`^X#eG=Y-iUnpo{m*cK(HoyT!?NFTmH)m9h*|Jc;^Ul7na)GFx^A
+zh5?oLmz^=g{hPg(6(7MzEL<!r8D|c3!aZ3@<#8;^Udu|}AO;r6F2+~`8B;I&&o~X}
+zX)D=vwl~lPrLuCPXF#^!lvRDhr<L}VJ<B@>r0%#>D4OeymVM4VhzV@Etf`_GeruRi
+zEjrSOqmof8bHxC;#{C@7H*u0@D_h-cxp_A3ICh`h;sKKB-rsVYwzfcbHOg%-GywTb
+z<elarSH|6!yI93zxm_=J`CkB#%{Fq^I72K!2FqQy;^#qK<vrqEfU14vJ&&QmRNs^L
+zb;Y9|G)vy^$a6F#Z@Jeoe2&c>`S932nE!0#0f}+|r45filwbu|EY-F$Q)c~=hkl<3
+zbX|pHr01lYFQ0!V2XhQXJdQY3zHrWWfU`F8#e=Zu9Mex8KgtMTatC?5e}814MxJmg
+z0cAc$zC;h1{lA;?#Q82*(s;>}`g;I5pDJHAfdbW=EMHdr4j|b}o>GpHHA$7Hd|r$7
+z;y3w9{wt6Tf%5eW{ejTiv!zl!C%bL(^lvYKPMssq%*5psFOX+`I)P<pn0z0mU*er8
+z&#txt^7OIPPT!GM9Fu(YwPT~?#m@_X`hmREqzqu2vHao+T=U*odBx#4AV0>*?*`%@
+zHLs9Ae2li|?k|5Np}b{L^2Y+Mk(A1xyu1xSI?G=^K$5RXmDf&8#hPZDbXs4lm(Id|
+z+}X2@tis+3wb@Uq);HJfXWNY{0a%{;2b(^z)=hnYrc|-Ez41J@cVz8l?SXn^vi8m>
+zh|$kjhdh656&|n-hk5}#<ynt@X!p_m*`9qdqt=G7z0lpMV9EAAjFnT`47Sgj^FW5W
+zvwcI5wx(Cu!Pm#4GUl*DJ4XW@mmbOv*IHsazlqo$h-F9Ce+PPX?sYa`l?F(DEjyYo
+z1-kSGJ7!ls(02vwm=~ynqzNrZcO<go{pJF7Dq+Vr^~HW+1RH!T7Co;}sx;_86P`(O
+zLwBN*JPkYOs-Lncl~`W>G-X%*g`4R8oL!k117x?FU45+_U87wK(n51~joW&lce}7_
+zmSB!-`+`k-gGE)pHSDHA&#`mKW;fqPSAQGJZuzttsa(Tu|AE?@V#@AljqkU4$EJV%
+z1k`X7yUXSuAgz0_8Hyz63OBc~dn-0#Gh@qU2i(DiXfm5EjRhJX!DeS;g;2_|IT1EM
+z{e#$?xe*wJR+5pC`B)G3{DL}wTR+%~Cg`@m-m_Qxqbq+p&tB6<e*ERl-rTYjQ@76_
+zJUsIc7JXoEySd`GrLlKbyZ};Z%T{<i2EtroD{5pwy>CcsjI<;D*q8d(fXbTKmo?bU
+zeICia{n3EzdBeWz?1lR4!`99!1fm?s);HtkR(Z1DzGY!`vxWWXQ3tfK565&w)@@n9
+zG5H+OIo?vEk-5h{PN~A3>6ZM#m42L3eH_S{nVd4D49HAFPGi3Wh<p(ua#endYh~(&
+zwQ@Jf*VtSa$XRk4G&Oh5(u4poN4fSduo@kl&Dpx&0$Q5I*^i9{==6Yd*^4ada*1=h
+z{ubb&9@p(0+Lu=3dN?ix*!YO+{cRCesUh6Zx0vLo7jeEDdH_r!T)^rupyEny+{BAm
+zQ7z&o3}rAc{o*E89mG{H*Kr{u4S?1S<w9q;0hxb}n`VYa{?{IE#$#m8$WdI>%|NvB
+zo!l(R0|U1e(Fe!{TW<cWV1TTL+yd`XfRt<8f;|;L*Ush^GWhv5Yi?oYQVhv;F1Fhg
+zfDUcB#gCJL-2TPs?uB8KH<wGY;6R5$;+E~}f~C;~Zuu{C(Up1Jnrj%#9$8%K)J#kR
+zgxhd)GrDFpC#l{5HAv$&?VbZ<y(hOb20h|(8Mn&}6Jy9EZqF(|AOkD8j7&_UJ)d$J
+zUn~JU7joH74}pIA%w;cm14LoL<@Uw+q+4>i2Q>KnKL2w24Ura`^|-vaYOKMAa0jD{
+z(PUO}M|PuDa^tyzZUI0$UE~TL`Jk{9xzqlbBzk<~&J4ra<#{o8&KODAs+cP|TnA)v
+zAy?WB_1uSY7lu><Y3Rva&`CHeSiqGH#S=I({SO|CM3g53ouw~Dnb=0`;a<In$FiV~
+zs|mq<IAw94-i*e<&IPVMLk{%zEv})uKPJ!d-1qTwfDQ}bek|C8o%<>t*epz@OL@`}
+zEBFV8dAbgVLc>yd)*m$!W5_EXW&p`i^6EanORaqAhl$os<c(_6aBz{zw^?)s=+2#d
+zTfHh&UM6qeX#+r`jJMox1mwH9<k?!Qo6UFZQG|Ybi0^m~{do2dzSG;a0A8bc`*!un
+z^^3g2n;}3<NApe%#aI=+;=7jPGh=%5u3x7C%&+3R33$Y}5_pddF92Tl<2`vtAYN&F
+zFa1&=yDsqk2wGW5SAIa=T_A7H@<Sxdl$??uYJqCd<>c|hTcPRZ&EQAgnuq4>#E<F}
+z4diHS3(|5Ee$*qB?B5IcF&?;$L-+VGqgtcw?BK`!eIE~TIv-T97N|=OKWVER;86k}
+zb~poQ?P-3Rq8h7J9UtvdgB3~xKikh5NRu8vx8?n#{QuUEg&`zu2fz5kPXL1mKBcG$
+z$Z*>hq??cP>ozz7EI!Du`_&a?^omanio_0nIWGY^kx2-@)ffA*raFGxecW-BtCZfx
+z(QjKBpZzom+c87_XveMCUPbYzd&zJXG=e`<UV=^Ta=!TKH6U(5d~wqq^x;Onv<YQR
+z%=psp`WXCN{?d2<mJ;Er0&RA?xzx^7+d7oLRd^fd4J-c6MchSdcmA#s2G_?=@-^+y
+zY4&x#x?>hf^%wurrVLH{HvcvWv*w5W{JTvV0Db{{?JJb<j_y*rskvn<{)aKf@Or$0
+zbhbbb4OB3DvVes3Q*f*C=;y~tmH0>>g_#bGzYVLfY1RPTu~)cU`V1uEtR%G6>gFi?
+zj5D#tGE|IqTZ-{bRs_dlHVDs9Oq>{i=^|ATcHRu<c<G9;D&*Dj_KK<Znt)EORZQy^
+z40KPCBEksG=U1j8Qbdone65&eu?$Ghhl;qP!*DFKOp)l0Pc3b$NP4gbXF?^4<RN!4
+zJ$?9tgKsMp$qi_a10E<+=7a)mwN|leIcoa*Uy9WV#MrJaNE$aQ)>WhCZ+k0J>t_S`
+zGDea5LyelpW=H3aW^qKSZHr}#V$b($0Or1m3~O8-H%^g-!yNiPR*}2d4V#Ei#eSay
+zKs+22d50LRu{SF6?^~m#1u0HUWq_O$6eo1UfS%+P=Z{ALd3;S#(z^|g1rA6P+L_VU
+zRZ?6#bDgQ8vK1Qf)DDU&z+kp|r+B=p6z#)7@#JVR5dFc5XYc%hEYvD$)wuVs@rv5(
+zp#bxCE9&eT(b-2UKEA?5RLc|%#>l+T8H$F#kWzd4C>qKKBh{WMnlq3xDN#x~4UgdJ
+zPo?rpI?g-vl^QSf5#8ocrSX3J!$&Sk)121Wb6P1)&zu30JV9xGX&2U9kxGm9m_Dce
+zt?ckSeXeMywEd1jY&%(L*SiSl>@a1|$0+up|0+H6EP?jguk2NxfSQe0`gn{5vN1*J
+zj{_EJ*h}f(2U{f<wQ@3t-xFA(oU*?Tg5X-9a_TNz@%A`nct0%;?M^Dgub}qIwkjho
+z#b9gmK{=y38FOKfGV&dU<ICk1q@m%;S)1_hZcb9pX}}$NKT^)~z~=CSmooOyPn1QO
+z)M#d_=c-JZiI2+}p-ef54j1n#IhdR2LdukqEk^!HrZR0DIzoa*nHIes$o(|smYO6?
+zxT}=gI%0wzAt<+vXafxMMVUS=90x4Fl)EjQ09MRY?sh|`Iq#+1_gnBOzm(aoMOeBx
+zD|6f)0XhAzGIyOF4znKr!J-a-@Z2}${xeH~AwN4xSIxD$IAxJrJW$02<?(bhI<dR5
+zZ1w<5r?Zqd4>SUdyr#VU>?_bOA<7CrF95wY$_KUmG0Gj4RYL_JU*{;FjVTA(@vO4i
+zxEGMj^UCUeXMtvnSH7de(3y8B-*0|^S)@!^|8^9B?^NZNL)Lg^a^;s}NaY?${gq$;
+z!9!k>rfd$*!Gi6ZilAXq`*$i@g-y_y`zrYfOrz_|Rh%mx>b<TiE)Q+uqPvuCF@T1Z
+zNYxhI^iNc%It<3be)kQP^MDONUTl*bEVXvoD(Bh5fxb#pxfJXGc>cG_JsjQbO<Pq@
+zzs=}oS5-aF*#g;WAw^kgb-b!i1%A4~SLNMyCy=bss{RHzSMg}C>hFUAtp7_j><Zp;
+zSQM#->-R!FzEzDl^*edYP>m?x0<bh%HR>apO2ul`xZ<@qg!`o$zZmH<%2_qR2#?~x
+zF4crfX92F;s6q_R0$DvrHKiO?`uePD1}n!B>c&x3<Wd=sk@r=zLr^If1l1e^Od?}t
+zs<|_!AaiO|F~)V+4~|g9WPHWit6Vj2SRO#pcGbcFRP0)PRqTj5Ad#C@|NHg3v#L`q
+z-u)3^VJ}tOf4JZ)v8wn=TyV@qDXP6zXQxVj?u;Xgp{flhs)2scQ*9cV0&vMhwP&>q
+zz-Xu{^V57xd7V{z%Z-7q+@;Fy-U#I3CDr~xD61QesspPLpRQCL$i{6g>aEI8y#u7n
+zCDn;2G@X?%R40ce<B+Va>TDI3{Fd>ml7H&3Uwo~)aL5s#%UD_Z5Y@8}F97PZRWD+v
+zV4IN8g7oGv)r&`W@ZxBRsygN|-s_~QUhhV4@~u<7Z-@2ukRVl4bS6Mfxa#XaD1Fz{
+zQk0cB$&}VuX?3}3%>eYRp$F9Z-dzBKcBl=y*`Od|)CN7~;fy=;51#N<n{HTxm2Y=-
+zTO-`Z%h&3*_v=s?h3a;F_TcHiQ(KZ3n9`fnmj3s#YI>x$yto1A#R#?4!2l$tq_!Sr
+zgZEA2)g63Ofm-}h+jT+(>88Z1JGr2OH{Vuwx`TZ1H&r{w!~p40sdnB~g`I1ay2toL
+z9KQ}x_xO>59qd!JXF>!vr{C3mBI}WrI(479*wMQ9tGy#JtkaLFy`#DUaW+u<jC_Q%
+zs+DTrZriYL^HKYa#_(&Osz(_kZ6;n(kBTh-X^(X$!b?}JwTfW%^xs|V%WhJwwYjCW
+zda(tr^O>ZMV=&G;($$HFkmk9~>Lhy<$qKdPVB=)jR=uGSqvIW}-rBVX(740u?b-Mp
+z4IkCp-=fYJ8Ax$99R}B&R2M9efNm{T7w(P4TK1#5C<5K)XRf*^6ZfK*r!K152=wd+
+z^{L;VZ+W0T-F_?H^j%k<j&uXK*{m+vVT410fl{rFxz1f(KFJ?oV2S$fn0lbQZ>aAF
+zTmfS3rG7MKCqUr}b(PE-X!tL6RWE$s>ZSULV-{Y~)T*Bh(f}E8R{i8(H=q?g)i0|f
+zfV7KNzx|!o7iFkxXY>O)YK^+ySOYLINZpt>9q8N;bz=c$U!h9<eJ_5GF7lT^Pn2N0
+znj<JoaG96x35tWLxQHl0v;00levQy-9AXX;48GyAy9Nt}L+jBc>jiCnBO1&Tq4fnH
+z%z0^oX{I-(|1iNO<P5-TQLt&!06ml|*m^er{Ciih4VCaJPAAwN(W2%~3tjVHf{L#F
+zA-KA3#!<^U$;j4Lw?^=BPXp5ZzA&t2F7`f!!s!33@!D&73z7rALf}+0fUv&8nAr-P
+zL~Ry=d{^Q~;IJ@0!5TB$d?BRgD4g9562kj00vI0Nf@IzSA$;^Gq-?4%y)#NQ)LNK+
+z5hXiyqcB4Zz-vcSAvzzc()P=H2(wmy!FfnGVQxAer1K^r#sVMMixn0=G{g3OtyF1i
+z=C}QuuyI2;e!r>($vl5y(*cymh2_HL6OBMRc?w(lBjeK_3tQhR016U>ZJ)~U+_wnZ
+zBWD27rU*MMF|L6&!p>=Zv1nZ>`F1qd84G*6`vBFP5^`i1&0XV#yku7(=0k-1(0O>h
+zH(4lnh~Hv%SSV~{0HRI_C#&tSb2JyuDh+@bqzWZnuw8bVBwXIy2M38Ch5H{3Q6D3O
+zhad6%xpLt#3Khbdgr}akxjDOpr>S1puuT`9wY!F8X-PNX*&AGLQ(Ozu8W5iEd;##n
+zR(RgD4~za%p?XUK7HN&btKrBJwUO{DU?Wbbz6$SSZ~?Cwp<!TaRG26<TFwNr$wczB
+z)7nwtTM7EoU$=#3hibe&Ef9Wsp+wYK!Y@1IUVX7fHn%knK;KJoc22rO8iOe)l+$lp
+zklwP_v<Y;?P`%Q$NzTM#sYqjb9*dp!=9;#iVF1w@jpbDtGPFcvh1D5dsHd^9cSO9V
+zaaf5VHyNOD3C5##|D<t={ukZ)q{d}=0bYb>w;(Opujw9#W>VQs^Vdgo)b=s4nm$n&
+z!y^y=;IRsgPgoVe(H)w>#tfFqgCyTh+L3XZpw<3(^_ry_KOqpvgd3U(1JNw^R%@ob
+z%Ry5)q6xdv0Hoh7O?cWjfDkWD#9;ir?BSZoS|rlPvzn;o$QbM0nz^enMq`#sg`I5m
+zMrabRlmTpy*CgKGgDq--RNG1G0h-hp49oNe&4$Ss$K64ijd(XrlFC|;?x@zJ#p5K}
+zeS&7o0!(wx7d2bg9|n4+i{xpqCHtiSdt1wV&4CzHhW-J~p&$&#hLf5@a|zbn?KA~r
+zpJDm)OiH&m51FaC>~$0~vbpAR2BwBnk(&P~>hFn6^It1;v*Ukj{=3o@VC!1VoiXu1
+ze`IQ^t|C5k(L9awMwt)LJiUw|9T%>7z6E!A?SGnA>6n6UJ8Rw^#Kb<*TC(YEt{bEI
+zs>VS=@JdbdZp8i`BI8<(J=qD7^TCitg^RqIFA&yFRJaxbLobSY*(kzrSJB`s))O<Y
+zh=$)Au_W^nwFfh>DcK}yPhG=8{iNv7-38!spy-@Zha>Vz(WT#HytR5Ix^}`<oe@O$
+zu}6@u50j<R&K=rZtrUl{Qvi0g7Kgcg#Y^4J;&2^Wzpg@(JJ|MBh-24dEX?)A@i}LJ
+z+!-WJ_=-f?Z7GI6z-hy*W-(msh2!50F=7ZtBl3ed{Vb|Uds2-22iyP3{Zf>JR<}Wn
+zUgv=wNpK6&_=Dn1u^xvh3&fcp@Y}9-6=ya)$40G2ocG^XEDkP+^Pgz&{-#)5*mWoN
+zPfx_yLwH7+!Q!ILfdKp<F@9$rkex;1vI%(di><}wgK@99H^mjzjzIGnas4ZAj6f?f
+z74OZV&5~te>eESh*=8V0tMQE`cg2lKDOge1N$ni5Zxj!GvI1ytBo>%p%kj`wENH+K
+z9+4&<i}u0X9*2m5H*Xe?9YmV?Efr5($11naO7SGA0$P|Lp1Ls$$jSimw2}fD<S(B0
+zNW*KlDDk`>sxLfVJRgSs|GrMV_&gY3f2w%NBN^)!U-8mroW1BC>%_807(4qY@p4~`
+zk^MRG@}NQ>T~*?(JXC<+De?B0Sb%>zh~<0|(7qw!o!|eQ;HoD+IFDSI&xnsqaKE!d
+z#Agdz1Nra0_;wg(O08D>;1vTTKkN@44-!9p$j5GCu2}Dl95ZPxHcU$c$;C<E*hu{0
+pn}(wbnfOz^6KIDn_&+nU@k!&HipDvS%8|<xmyn{wC3M-5{{sZSTQ~p!
+
+delta 9294
+zcmXY130w{B_dm}w_s-0nd*_bUi)c|vQAnZ4R#~FZVwW|E3Ki-_7a<{JWO>Oei9!h_
+zLO)CP*IvB#HCwW@NV5Eo`upH(=5}YE^PKbjp5;;SMziv*X1%e#4*-1zG@(C$ax9Rk
+zYZ{SW_6A@#0_fTRI2HrlTLjSY555QJ(hO);OGFdI!2m9Qu8$-BxyH|3@j81vB3@^&
+z1?c+c`hSRju0JE*258Y7pc~$I-)DvYzz_EM19Zn5`w|dK@dp9{M$8798j6SuOicu)
+z=Ujl_^MUa_jt??Rxu)x!4@@Th(7w08c2WSGaBD=GI}g~zZ2+d#z$Y{ST|FCA`Sn13
+zU&$uKo1tJLk0<8(C>1z3r2`!n1sx7w1`=@%T%WfEXx#u^7ry~I{5QDI>jm)63_Ms@
+zpk-IVQ>+D=^Aq~dJ%SsT3<EL?04{d`pH=689PSB&lVX4*S;OF(V4x-^!MAW6(0*?q
+zATt+;@fZl=Zvkxh3M0=b!J6#Y2P40p2O6P*QEPtzM7@Tol{P?L=E3y303c67<<?ZE
+zIt<~KxJC78vIo^o`T#Ln7l0khAnqOmP}&_9HDm!<J00RL2Lp`E1^rDl=ITXANX`X%
+z?iZ|dQUEzQ9Fo=+1DU-Kl76fP(lQWMdrSoKU>&5SEC=ZP12(5CaV<OL0_x<j!W0fn
+z+zG_A5OSTa10-LDgLN4gDAVBZqf{XKUO*Ap12tbHi%duS@=f|0=%x{HGx#IGIT>!g
+z4giRL54Z2O07_*j)%pQcoP>K8)<6vx$`OpN#XEQ*d<5G39=!O3rZiYKBFX6@r!l%g
+zRZx*y0>sxEDo-W=t+jwJGdctOB|=rHE5M8;P;;jgNNNatr4!M~=ipoGFd&hYgq5ZO
+zm>(lNjRsm_CI7^CohHK7J3y!3BF3Zu$dW>0{GlI^iA!Zig;Sf846$C70p!Xk((VJk
+z^tv;#4-NtPy^=WD<L(tKlNTv0^vR^l`S}1-CJ>id<#@My6Ba#e!c%idcc=&Wd5w6@
+zECD*dE%9!L!94XK8Q|6)AoLs=WMd5EbSpCAMj0|Dn2cVi0(z~Gj4Q@KD(c^a_x3d+
+zeVy5aZ<5Fqv(G@={CG{K+*}VZW+9ncVh!X?1_{{`gJ1m6h&1Q0?4i^R&%{m8B9Y5T
+zlvgB>{FEjvY|)6cw1C8{aRE9qkIbKlPuXBiV*QH&g7Zl1kt877D@p9j<ru$XNt}WK
+za<jRdsk9^U@-?Nd#RRfJgF>*uoybAUfXtssHa$y16Bfunl{&XDB*Ab1<FQTnuQw@J
+z?FrPedlSySN{%kJ1eg;}ipX97E{zo3!gz2qlzrLWRwu~w+{r-OpCK=04Beq$$*cYt
+zV{0_z^=uoUL677dR_7K@-nqq~B6yI>#!ttP%4Oz2O$*7F5Oj+E3i;k(gie@Behlaa
+z<S$e5`y0Bn;E?>2)#>L`!|fY^Xx>x9dbD*tP~&kJ-{Y^-=C&co&A(~$#lb*&HKXRQ
+zYLJSZsg=_cfYn*FjRU@gVW>6Im7E(zZFBN~+&fL}^vi)<tD>EHRskvNPCI4ee)L&P
+zok=2)oi5ZlWi61c7ihOn7<aPF84QZ0?o+Y>6w%bf2qX2)0NVF~H;@=p>NOx9=z_ng
+z*UJF_OPA82meoLZo6uolyMYA9(qV`G0y6n8I_!_WJPnpBIo+V$bbQK409ZvQEz<#g
+zd6Q1Aa{(gWq9Laa0h#iJhE(MPvE*skRP^cycN$^g2eht)&bon|Tx&(=M4)BcYGq&E
+zj;xU9@)o0~(4__OKq?l}gk`INI^Cno7;{{44P8Df0f>DPUHwFXekC+{ZvoJhf9N`&
+zQXm$&bmIs+6en}KaWpFE!cH{xJ&H=W1>NLr2y{iXT*+JL$J1;PSMqZj-8WlCKmSGd
+z&x--FWhgyVg7(`!phvda0$J%qk9C{@VE>dJH$zWKS7=e2k3c>UT6BIcs)-#v;dB;=
+zm7bnnT#gaYi=IoVLtk<9VoThw=7^U+16ewRUaGLgRSNWq2hvhl(?GA5qKI8QE2paL
+ztQOMydCP!$9-vR=WdLz}OrQTc0<^}JzCDc_GtX9jtg_Q@qE##gOk^kewc$FDZVj|9
+zYaftk16prx03_fSq9KsLGl)h&f~*jY5tksEAU;Pl1u~*9q7E?~u{n^D1hEC;B-$XM
+zR30v*4GmsEm&)|V!&{gkCo>T996(MG4D`1c);$Qw-8@DhkwERk8Fe$%5orda4r75#
+zf5e!SBJq!ZZo+#(jYwaQX~H+IjBd0ofa)>Rq9+FD`%O%1L(G$wJsAsId|qKXV=)ay
+zaj+v}^~($3^CiYMbTW{{YmDvTOk9JJ+F(#9)4t3ch{pz|L#zzs@>9mqy%xxz_KZ{4
+zWT4~QFdbLC1DL*t>3klmf~q8@t9}W}@O-9QF48t6t_i0F$`NXvRVp($2KRiKA2X!o
+zIZPA*jPKDJfGvegQ0xI9rAy>YwS|5=6B6JG5E;*eoWphgvx}Lj#6p1dX2N&u#blJs
+zL~gzb<iB`k4%ZDUmuF1WJxtg$Q<xb4xj?=YGco(LKyzO*b5EZK5+^hBOW$BPA7d6z
+zMsgl3WtMnG<31WQOGg|*eGXyPWFTd@FU-bCJJD`WjT`wTFVfhN3OQ5LR$nGCm#=yP
+z=`Ar=N={=Et!Dl;MMm~wn47aXAgvZKH@A5K@e-KZV=*!<&M<d8uwckq$lMLu4e;KM
+zd0>VS8J5dDOm73k&yjgED-L<aFdwYZp`F_@Rq0cKF6qvE9gGIg@M9Xb?Eo0}f%#F;
+z3uGVB(0;grMw9}zIj&HITH~|-Q}CE#iFcMl_3jL&U<-xRfHsUL@^W#2e!rr_dW^zP
+zl?tarp+NRNRdn*lOtmRg(K*f-;ACwh(%Tw^$M;h}irOi9x5bRJCQC7J%2M?1Ud7<3
+z8lXdL6@GaVklTzRsQG=MA37?=hE4(c_hQA^$Uk^R5w`dv&=>6$bK>q}6bx6)jeLO#
+zKQu`Z8=MPpY@Q;n5MANzFOS!DYZ0tS`ieo(H%yUi7zEJjwqot$CqO4|l5?~jRgV-K
+z+Y_K^ljL%3+cx#B6g#?vps3U-cJ#oAyX~(?pO4qG+9@*3a3`uOWJk$v(n&=z_yJX}
+zR}`D0{pN2K=MLj5VlOI6##jIiwpNrpK8|efq9}QXIEpGR#@GNE-AnPWX)4fDk%}8^
+zZ~XaYic*som<(Gfo_xo*onNDPk$VPrP$3sc76$2xue%Ro0-K?zyW0z+K2I)}oahW9
+zn>4dfr6{%C&H#OPN%m-Fr$4E*$U=+bs+5)wFqr=PM`_#24rqEGrQL-ZAYX?l9p+-J
+zjI~iZTgL-9Rx6$V3Ieiuw$f#hF_s{yN|&vFK95(r$2$YnY*F?+h63~2TG`hHw|d|S
+zWxvBOQINcpp2zS#Z7r2U<N9F!vsDHyRRRdMJpQ8u$SJK{+00zn<D;DNV*=3iR<emf
+zNBtS)yy9#i8NP_e5f3S2qkaIKwpA`1h(+h9WMzDS3BcsG%J@KUj6kh&@yW$N$7Lv&
+z7+_>CXr){_&l$-7)+!Uc-GH2Zq+Btc0yW4{t|<Qiu-sFbREnN$o}x_px)zz>tz5;|
+zW6iNkxjr@!2)%PsK5x*`zFe91{T0w@r<A*Q<8scORqn3Bo+CL#xfjzf@mi|PDz^sm
+z!a;6r=tS=^vag|T^fBeRm-#^b2P@AvzYOrVsq*4VT=Sk1<=sPzfc(r<-W!QuYPh3(
+z_!MQYTcGlhjPzDSD4&YBM%qmI?DZW0(n0z90S0;UBW2};6s&2=<x_?_gSRXkK+9e{
+zWmWC1G1n!_<%SmeI=1zg5`e^%CakMsZR+{}U1iVO^~U|!7R|O-w84tz6WhKs5@Mty
+z>zEq|bgeDxnAZ#7InTQFL%EM!&-Uz#8MSf}+Y8m5is5YULs&Vb&Sd)}p9M0c8{2mh
+zhHWcT*5}4(WJVM_q(daoF=@H%P@NT~^IM2*F!{A(1Gl4A@4LYUt=3|(*PR`~p9i|!
+zj2*SJ8X5YV9rX%%kdW1gbo(`SoPRXXP7*t=t}pfr!`R?saX`+1{Me{1UE(M!jk}Q%
+zvWIb7{p${F(qk+yf0?nXdY}<~oY+-MV}PWOWY=6TMb&8Ch_qk^o9v2})V&34@)FFE
+ztzNOI@35%qm&|S&_!2vpYIgG-RP~BDc1zV7Amd!wZ9kEFQ>U@pTj2ecAK0||Dy+nw
+zvpa3~18L#TW~dUN3*6em?zy`Wo0%v!E2s<`q8v6$9t|`;pUuj`3gNszn;m8gG;j}_
+z9UTTVX{>BwVljFddv^XufZIRWi_KAOe}7`Hd7~;<i|lnnjE^4c*jrnc0g2k!golcn
+z@K_J_j;jkk?HOCP@)eNBQS4o}r$CrX?A`YY)GRYO*+e%yjeTu+9R;Qz`}#e0b6<zE
+z6+dgR0drwLbnwLLWi?wly8wtVfURyo<5qjH-@j*Kb@P(_<@OQ#nAIH94x?_%e2zK7
+z0gYNG*P2+kRdIqEEz|$ogsajxL312PaS<m>x(sC21Wwz22@vH1M2uDCZLXP_E7r<O
+zWM5MY{VvXm(*o_cl(TA10GOj(n^#zk`ebo--EIRt|Cwt)Iu5|$0q492qo}hH=X#?8
+z;GqH6^$g0FZVcz{v<zV5Bd+)N1z4pX;D%ISlAlRA-wo~n6A2fzCIqNd#*LY95i6<-
+z-1s33CRiVC!jprz>clJDq~S(DKW^rx%ydNxX}Ia;DC9jVxtUKfa)$5VB5sXDDSyY!
+zmfg^CuMh_Txe&$8n;i_0`H-9Mbsiw;IyZmUU8KuNE|$U1ug~IQcQ3=zza<yfbt*vH
+zR@}m;%dsx-;q?E7V3QZkC0KHxM<Q`6_IAR0+<;5`jVih-mrK5mzI6Z0rA*t6X@GDW
+zZfyoqew>rl?|>RT<2I#7Vg0|7+Yy5ran+dHX^x3;(td8&YJVUD9&;JHF^%?o&SiYF
+z0`Q3CvN}G*B-5A6TJjEv%97jH7yXkK&h5+5;`{sjk2_$DVX=7}m%FGOYp}uG!N_w!
+z$zASnI&x)SJeS`!2pwj~<v$t()a)8}DiD)I4{xs64{MiKTJDS~24%BzT*;x2Ko$Xa
+zzAN&%59KZlehj20k-MOmv6Mc`T^@ovaJZlej};;+mjj(WUXEyP7gou=c@>XQ=*GRD
+zgm!ey<f`6{01{%rRc9!HmbT_<%Dpjp?&W@rivsGmllwV;6L#)*d0;bvM&95_JFMUz
+z9OCJ^1^~Ylo()9K#7y9YhZ#V!gLqA!Kc&_$`Db&T{TSY)G8G3GDSXQX#Xxtw<69X#
+zLFVn|EgUug)GBzZ113Oz%#b}===3M~cJ76!w|RWKGpNUNe)0|#YXLk*@a<byV-7Lo
+z9p4QGVm5;BSaS}mqBne(QhaC3D&D1jI>5Xqd{+^-`1V!aZNn>o*Zp`8-U*0jD&NcS
+zJj#**-;bb_mBjP?bMIk!tL6vGm?=4dA7Y7Y&}Y~4Lz|)K=Fa4Y-=2%D&|iFjLnM%+
+zC5=c+r|<!fkh1^G=SR7r8F~NlqXJr>?7ZQ}{Bs|tV+H)!{Ix)xYxs#<l>m<x^C5>a
+zFzR`Jx~d$b@(Lg6{2u6{-uxVY8z6P#_~<|P%LxD1`q40$q`u)7e*Ohu6vihN)&Uu6
+z*NAkBf?v1631Hzte%<dbKtDP2DPzO2gHPmTKqYEEiQnpr{n$4*{_p!}afDt@Yw6^_
+z&6v-6o`CI`F@Ln(R&1}1@uzwzP>Qzk#ib?K<R<dxo?i#zI+j0ISB9lTAO3tD(wdm_
+z=YJTY^K1D_KLQ&|glj64*=;lA)@HgEx%};dJ3wzn@?{s%ij)NYo(Vd4P@3#(*481a
+zC12hy6RG-}e{Fjirv&DFMFM8c&vpEVO&I|GL44&Kr116xInB(%YApZL6n%K(vWj%D
+z1nRX*#q7$&cuZDtYjEr5U6LQ;D>tgl^(g!;S(R;r7ND%X%K6e)AT!jm*h;5ArSdo3
+zjV;y$)d<&R=-->F;5f_%p&wNfCIn%+NKu8HHOD#LTUE#tjH|>ps%ih#0iDuaHN9&v
+z&<v^yGePnB{Ye!rp~kl9qMB{F0!Yt?szpcra4fSzwX_?)^?WN;!h>C?0g`I@;CtAr
+z^k_n#J3*@DH7Jk$AE=U|rT}erU$r_BIsK!DYK;mpu1h15+83&I<;eLv>r^S#bI@HG
+zs+6A^pbm#rvc4ON#ZS4i6_zckT|ce^Soo?kY;bv8rYaMMIrLMBYTrUvpdPuZ1A}sa
+zxH+nF^BC-$o~n-Aw?Ro8t16ntV5T0WD$@G_JrSTfdprWjvlgn7-Ysz~@J$}y+MK?#
+zmlw6R&`(o6ZiYfUZI0>*pfe4+sh;jUkMa?#dUo_25JMl;iw}W7VkfIAHE4T%ysGlX
+z6o9$us*m=y*m0z*zP!Ojln+(am}2BjIjXAZfg!bPwW_An2WX<Bsv!eICh3?!r{fk}
+z`y~k9(t!LkUeJ1?j_5b%3Z@6}i;orwX4x&U=d>2gii?3P&k`&y?ZlcZT(E3|>2umY
+zLfb#-bLDiw?gu)tRgPfayAbG{JfY`PB)hL5c;s4PYgH%oDqW16y(|oJ8x3S*sSt<*
+z7HXU%1ooMVoE#}k=I}YABw^}-k1!Ulj})fu#1(J5B!u?U;n41c5PB84cX_K2b}0sj
+zw>^ZJ<;$@U+9QO2KzDq*(uj1*VPW<r{NAnoLR1Y}==DgL>xRwYXHOw6?-$Tp#&WH>
+zoq=9RnuV{+-X<g+M1_mj%Z?W2`pL$EY=@qI_DM(`gUnw%T1bst59IzcVaxjjOt`Cs
+zzuRGg9wrKZ4{HgG!c$0_9tyOhkC1NJ5n$ykA>9>~=ImNw?;pai@)5FJ3V~ED6tZ0(
+zVL2cO`_|c`VmUNn;hZKs>n$88UWOg<uW0$2g-(A-D0Gd-Nr!=OJPn0TN)Rs3>5u93
+zgm5dT7GU^w;m(VCpx+J%cl|v943dQhmEP#(XyM5a5lH<h;l-#@95ASba?@TwGDV@h
+z-)UshUf~1vLuKA6eA@f~$Q@&$x*`C;cbf1m&j$BwnDFfwhO&FYdZB(lZt{|6LPKyi
+zhJ&}7pkUGt-PH66HbJBAtCdBVMpG=*oC|L1fAMNA7iHq&QaR1CKb<DY<(6FyPgtqj
+z`e0$7epB7K{|3yF<+7ud&c0gRdCpLvZ=R{0^S1-M{72m_6xHorsJf^BW>m9l>Yiup
+zaO5^qj<C|{1Jr%);-~YssJ&Y4KvUAy-bOfAaf?uU4?+i4uTcA4#ZwN;LiJF?UKo!R
+z>R~4_dAvKS9#*;qU|FO(;0ub%-8Jel=hgz5;G-V55W^*)vwFM<ZbeRodi<r+05@#a
+zlZ;LSS#wG~wG>(UPOYBFDpA625q0=71rUE5^_)q_lnbId$_SIl=%MQ9nNu+<xvFDK
+zKLXvkO&ya_kAZEWp6izjP`FJU8-$EqJ6;_(>?4ry7wW%$W7lHnre2u-1t7MUdeOhQ
+z;Hz=!_{X^5xrTB?8=XEzz5Hco99axeZzw7U`gxps)9@sKOU>20)+hi>hNyQ}&BFki
+zuijH?ihWCkI;(pv($q+OU?9@!W}G@_4dU}MbxszVwV=29NJ<%yPM6e05hyyVJE~9k
+zEyp2QEA{CoSn^w4R+sGmi2dSQ^@Thqd@o~dHE4+X#phQ5)miFSaZ|BPSlo#8)>iea
+zM`d_$v_xGV^Ayi@Qq*tLQJehS)Sp^oeLZ-Ox-N1zKz69Qem_#*m6s!|EyyQ1*;=Qs
+z)oA;pZVk!N7<zRA7`t6#)YTT7+W%>c+~;E3^Qj4owrI>YBxB{<UDL`0?Re8g)9U_5
+zz%w6B>pr`1_djT?$SWXE`)RBK@1xAvX{;`80D3WBV|_3P15?)6_}Sugd9S9eZwlJy
+zqp^2D2I;3>);KsLgE!yNIFw;L1e$3&$HV|}f2`@e^9j&UdyV_Jr8s`wq;db5gh{G{
+z#$$09(0~D&KH=3Em3mE|XzXa612taZ=+>Ex#w(%=kj_S$LBk*6b(zMu>)$|^ZPfUW
+zK=<pOYXVF$Y$lp&0^;&P-eu!J0_1BpI#sr2#-A?s^%A+##=>frW}zjn^TiX*A_o1u
+zJx#MT55s(ae@#MrB+1H=vZHNBt5D5`TJ(-rsAg*ycPvnTXtrhHGirNkwpAd{7X-_T
+zY}@)&u$uh&GEQ-{nu0xXK)3YN6o#SN{HoOy?nYY->NSP$Hv&D=Lv!-a=UZ(xr`l}A
+zlfE08Q{k=vw;D7h+f9J}wLz}5wa_osluir;7*L|QH>w)wE;G&jpsPS^JT;F-?EolP
+zsd=KX!6O+T&68eu-}<%YnNubX#ky;r4b%b|c3Si7f3869CTd=nhXH9Fsj2vr))#!#
+zRL<;&JNuue+EfcLVXUS$cLvbt1De`=%)VNC&5u3!9DRh3NQ+9aF02t%&2gER{u5OP
+zk#S+iL~Y`IfFtk4W@8YuiD>j4m)#{>G#*lowMs8hS6vJAZF{lBg+Z9}o{46=y)gZU
+zh_;i80p3cYZJicqUae^7RRi$9d!pSG8IR)hqTOK~&@a5$<;ZJL({%$y7njXI*FBI;
+z?CkXaiG#YO0_lEV^m`wTS_k5Ye{JyCE3pwt&K7axG;@HEzT&7kDx5^U5Xbti!jZrs
+zaol1XoDiQ8C-n>fvR4*Ey%zus4Q)g+_nR0xA^=17kvO9RQgq5JamGcY?6jxiOeqMD
+z9nHkZBUqKTNn9?@Uh@s-Axp&QG~CF}&&3!^d|@wEjD2VhwD*1av7Nd9c5iXxhEROI
+zx)I6Tf5c5WNR11L;^v}SAPyel7H^FB9S-8w3Kc;9V)5^)%Q(V#DQ*j&2}GA9Znr|e
+zj+`y-nBEtw;4;~_orQjqxTpIdOhufStw3+?+$-iTcL8ECSUfUiE>7EX#QcZ&6!RZq
+zK`jFiaZ)@{ZjYVg4DqyJ1jHysEa`;pa>xDRmCb!{koZNs|HT;fc$@g}3*L_&CO$=?
+zLP(wX+yjk^st}*2cmk~|5MQ*uj%8^{SMkL=TyEW>Mx^fti!XP)0(fO7zO37eMgMuR
+ze9K}WJNk%khGLXxOvE=q8*xHaFMf)_1-$hWYX-DHhDl<r)hr;JrpO-lI(uL7dkN}N
+zk2_+6V>uq5{uY0EB1JTx#ozWAdo^0EBDw|ko9^-=`;Phst<h8@%BhM*q_^j3TaI+X
+zO4eE1a`|qYI8d$GSuA$i%+R*-2my%HYOSs*FhWbT)>xg<*l}9h_D(oUY@u~rg)VQN
+zqIC|&t?urjb&mWWs`Ux2b7DRogl9D(&9BpTUxZ@vxV5&&7gW?XF(ukQ5$MAswoQ24
+zN;@dz3Bb|qS|3veIV#J(4!Yr&v}4x<0`bk%jvGG`51!1l;|HKv?(L+V`XL)d<)=2}
+zW(|;jx3!_E-vK6hYQuc+=d%9RhF4-BRjIWRi5M|9>DuTu=%Z1$<pKvggKgTSS1$u>
+zi`Oo_zY8bVzvW5?o!ek-N({PXMvZpEWb|YD9_>avn<mSQ8<D1U(x%4aB)VIccFTNB
+zbDb}0x2`_~w0ME+(OyUD<e>I;R=>14F~|(V9BtlMbcW1o^P&mX-P5)CqhDaX?kK0V
+zw+JiJUhzDN8F_~GN(QEelZD!UDe~``Li=wsRI}snwf|o20<d+hwro^99#4JJKDmbY
+zaFO=;A}^$Qf9>-t=+ZHVwJ*1zmDkT}-=tv*y0cJQaS#*xa2xGMLsZM*_vO|dEc6-L
+zdJPT~g3GiG>4@HL65~=1WSvst2BB*sLM7hZ7YJ)Fsay(xVGJdMEF@#7i)3^f>x$W?
+zlJSpPEX_P6-N6iOPBuxplh^U=f|VS*I|E!9DRoZz2y{|A$+_QTY^<Cm7YAHj@hGX=
+z=))Mc4{yq+I<#$hy`40KoeHqCh2-a2kMr#L(oj97fW>$$-oZ{ED2-l^KCv*A#$^{{
+zrz%V1>oH)`t)wXraOyCpzZ5F<!ZGkiDQqx$C8CEk<216WIV*+l$1%X;Iw|6JF`i^U
+zlOor-0W=dEkrX^`!ZX>8NaMdtv!rS~Uq36&`i#%N)<v3C^Ad}n>8{e;f9tVsxFF4Y
+zro}2qE5&x%fvwduDJ~DUZFjb`VDm@-exMY;BNzJ%Dy<lgPFXlhO7ubV_qUQ(mOBAG
+z;wP<t<Au%{E2ZF(IxJZsr97XA$8o`uyynmKJ!xY?61EdDQtEAVrYchUCrt@6iRBzA
+z{m>8~r%y?FRn`D)Or-qg*q=O%lJaXX--o42$07#-wY-FgQDWI&I(86a+W&@BbOTG`
+zK4sDg@&stXRq5o-*;ujfluij0$iP79tXnFU(8r{+{z%o(%W|@lPPIk4v>C@TPp`;1
+zPUijroOEYY9KimzQYoJRwC@3_?9cyBa50b`oW&rR=O;aCj#kY%Aiaoffv3#wQiUI;
+zN}W#n>=}bJ&TGQsd!(w*M=*$r<<=c7Oeab|eN%Bfp^$!QcHoJ?f-vkf_9pmuEcEY4
+z9uM!|5`v)}1j2L(0ZUj2egFUONU-Mtf3f&S8{95%dWhx1zW6)R(w_Ts%Qr4&dcdrx
+Wn1LbTvqPd19hQ(nhb46N;r{~<++XMb
+
+diff --git a/mythtv/i18n/mythfrontend_en_us.ts b/mythtv/i18n/mythfrontend_en_us.ts
+index a1ff6bc1725..2c02d7ffc81 100644
+--- a/mythtv/i18n/mythfrontend_en_us.ts
++++ b/mythtv/i18n/mythfrontend_en_us.ts
+@@ -12365,7 +12365,7 @@ Error: %1</translation>
+ <location filename="../libs/libmythbase/mythsorthelper.cpp" line="16"/>
+ <source>^(The |A |An )</source>
+ <comment>Regular Expression for what to ignore when sorting</comment>
+- <translation>^(The |A |An)</translation>
++ <translation>^(The |A |An )</translation>
+ </message>
+ </context>
+ <context>
+diff --git a/mythtv/libs/libmythbase/test/test_mythsorthelper/test_mythsorthelper.cpp b/mythtv/libs/libmythbase/test/test_mythsorthelper/test_mythsorthelper.cpp
+index 478c77059ab..46fa7864631 100644
+--- a/mythtv/libs/libmythbase/test/test_mythsorthelper/test_mythsorthelper.cpp
++++ b/mythtv/libs/libmythbase/test/test_mythsorthelper/test_mythsorthelper.cpp
+@@ -92,6 +92,7 @@ void TestSortHelper::Variations_test(void)
+ QVERIFY(sh->doTitle("The Blob") != "blob");
+ QVERIFY(sh->doTitle("The Blob") != "Blob, The");
+ QVERIFY(sh->doTitle("The Blob") != "blob, the");
++ QVERIFY(sh->doTitle("Any Given Sunday") == "Any Given Sunday");
+ QVERIFY(sh->doPathname("/video/recordings/The Flash/Season 1/The Flash - S01E01.ts")
+ == "/video/recordings/The Flash/Season 1/The Flash - S01E01.ts");
+ delete sh;
+@@ -104,6 +105,7 @@ void TestSortHelper::Variations_test(void)
+ QVERIFY(sh->doTitle("The Blob") != "blob");
+ QVERIFY(sh->doTitle("The Blob") != "Blob, The");
+ QVERIFY(sh->doTitle("The Blob") != "blob, the");
++ QVERIFY(sh->doTitle("Any Given Sunday") == "any given sunday");
+ QVERIFY(sh->doPathname("/video/recordings/The Flash/Season 1/The Flash - S01E01.ts")
+ == "/video/recordings/the flash/season 1/the flash - s01e01.ts");
+ delete sh;
+@@ -116,6 +118,7 @@ void TestSortHelper::Variations_test(void)
+ QVERIFY(sh->doTitle("The Sting") != "sting");
+ QVERIFY(sh->doTitle("The Sting") != "Sting, The");
+ QVERIFY(sh->doTitle("The Sting") != "sting, the");
++ QVERIFY(sh->doTitle("Any Given Sunday") == "Any Given Sunday");
+ QVERIFY(sh->doPathname("/video/recordings/The Flash/Season 1/The Flash - S01E01.ts")
+ == "/video/recordings/Flash/Season 1/Flash - S01E01.ts");
+ delete sh;
+@@ -128,6 +131,7 @@ void TestSortHelper::Variations_test(void)
+ QVERIFY(sh->doTitle("The Thing") == "thing");
+ QVERIFY(sh->doTitle("The Thing") != "Thing, The");
+ QVERIFY(sh->doTitle("The Thing") != "thing, the");
++ QVERIFY(sh->doTitle("Any Given Sunday") == "any given sunday");
+ QVERIFY(sh->doPathname("/video/recordings/The Flash/Season 1/The Flash - S01E01.ts")
+ == "/video/recordings/flash/season 1/flash - s01e01.ts");
+ delete sh;
+@@ -138,6 +142,7 @@ void TestSortHelper::Variations_test(void)
+ QVERIFY(sh->doTitle("The Flash") == "flash, the");
+ QVERIFY(sh->doTitle("The Flash") != "Flash");
+ QVERIFY(sh->doTitle("The Flash") != "flash");
++ QVERIFY(sh->doTitle("Any Given Sunday") == "any given sunday");
+ QVERIFY(sh->doPathname("/video/recordings/The Flash/Season 1/The Flash - S01E01.ts")
+ == "/video/recordings/flash, the/season 1/flash - s01e01.ts, the");
+ delete sh;
+
+From a465f1b03d505b0038ba5d40b11bb10039454733 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Tue, 7 Apr 2020 22:33:36 +0200
+Subject: [PATCH 016/165] Use const_iterator for QMap m_encoderList
+
+Use const_iterator, constBegin, constEnd and constFind while accessing
+the tvList and pointers to the tvList such as m_encoderList and m_tvList.
+This fixes the problem of crashing while deleting a recording.
+When deleting, the m_encoderList is accessed by more than one thread.
+The QMap is documented to be re-entrant but this appears to be only the case
+when the QMap is accessed with the const variants of the member functions.
+
+Fixes #13571
+
+(cherry picked from commit 4192aab4d31301596b506c47507e8c3d872cc18f)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/programs/mythbackend/autoexpire.cpp | 9 ++++----
+ mythtv/programs/mythbackend/mainserver.cpp | 26 +++++++++++-----------
+ mythtv/programs/mythbackend/scheduler.cpp | 10 ++++-----
+ 3 files changed, 22 insertions(+), 23 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/autoexpire.cpp b/mythtv/programs/mythbackend/autoexpire.cpp
+index 6f2f309b754..efadbe08d4e 100644
+--- a/mythtv/programs/mythbackend/autoexpire.cpp
++++ b/mythtv/programs/mythbackend/autoexpire.cpp
+@@ -190,7 +190,7 @@ void AutoExpire::CalcParams()
+
+ foreach (auto cardid, fsEncoderMap[fsit->getFSysID()])
+ {
+- EncoderLink *enc = *(m_encoderList->find(cardid));
++ EncoderLink *enc = *(m_encoderList->constFind(cardid));
+
+ if (!enc->IsConnected() || !enc->IsBusy())
+ {
+@@ -541,9 +541,8 @@ void AutoExpire::ExpireRecordings(void)
+ if (!p->IsLocal())
+ {
+ bool foundFile = false;
+- QMap<int, EncoderLink *>::Iterator eit =
+- m_encoderList->begin();
+- while (eit != m_encoderList->end())
++ auto eit = m_encoderList->constBegin();
++ while (eit != m_encoderList->constEnd())
+ {
+ EncoderLink *el = *eit;
+ eit++;
+@@ -555,7 +554,7 @@ void AutoExpire::ExpireRecordings(void)
+ if (el->IsConnected())
+ foundFile = el->CheckFile(p);
+
+- eit = m_encoderList->end();
++ eit = m_encoderList->constEnd();
+ }
+ }
+
+diff --git a/mythtv/programs/mythbackend/mainserver.cpp b/mythtv/programs/mythbackend/mainserver.cpp
+index 76e3702a445..9d627c663b5 100644
+--- a/mythtv/programs/mythbackend/mainserver.cpp
++++ b/mythtv/programs/mythbackend/mainserver.cpp
+@@ -2810,7 +2810,7 @@ void MainServer::HandleCheckRecordingActive(QStringList &slist,
+ else
+ {
+ TVRec::s_inputsLock.lockForRead();
+- for (auto iter = m_encoderList->begin(); iter != m_encoderList->end(); ++iter)
++ for (auto iter = m_encoderList->constBegin(); iter != m_encoderList->constEnd(); ++iter)
+ {
+ EncoderLink *elink = *iter;
+
+@@ -2909,7 +2909,7 @@ void MainServer::DoHandleStopRecording(
+ int recnum = -1;
+
+ TVRec::s_inputsLock.lockForRead();
+- for (auto iter = m_encoderList->begin(); iter != m_encoderList->end(); ++iter)
++ for (auto iter = m_encoderList->constBegin(); iter != m_encoderList->constEnd(); ++iter)
+ {
+ EncoderLink *elink = *iter;
+
+@@ -4315,8 +4315,8 @@ void MainServer::HandleFreeTuner(int cardid, PlaybackSock *pbs)
+ EncoderLink *encoder = nullptr;
+
+ TVRec::s_inputsLock.lockForRead();
+- auto iter = m_encoderList->find(cardid);
+- if (iter == m_encoderList->end())
++ auto iter = m_encoderList->constFind(cardid);
++ if (iter == m_encoderList->constEnd())
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC + "MainServer::HandleFreeTuner() " +
+ QString("Unknown encoder: %1").arg(cardid));
+@@ -4479,8 +4479,8 @@ void MainServer::HandleRecorderQuery(QStringList &slist, QStringList &commands,
+ int recnum = commands[1].toInt();
+
+ TVRec::s_inputsLock.lockForRead();
+- auto iter = m_encoderList->find(recnum);
+- if (iter == m_encoderList->end())
++ auto iter = m_encoderList->constFind(recnum);
++ if (iter == m_encoderList->constEnd())
+ {
+ TVRec::s_inputsLock.unlock();
+ LOG(VB_GENERAL, LOG_ERR, LOC + "MainServer::HandleRecorderQuery() " +
+@@ -4855,8 +4855,8 @@ void MainServer::HandleSetNextLiveTVDir(QStringList &commands,
+ int recnum = commands[1].toInt();
+
+ TVRec::s_inputsLock.lockForRead();
+- auto iter = m_encoderList->find(recnum);
+- if (iter == m_encoderList->end())
++ auto iter = m_encoderList->constFind(recnum);
++ if (iter == m_encoderList->constEnd())
+ {
+ TVRec::s_inputsLock.unlock();
+ LOG(VB_GENERAL, LOG_ERR, LOC + "MainServer::HandleSetNextLiveTVDir() " +
+@@ -4918,8 +4918,8 @@ void MainServer::HandleRemoteEncoder(QStringList &slist, QStringList &commands,
+ QStringList retlist;
+
+ TVRec::s_inputsLock.lockForRead();
+- auto iter = m_encoderList->find(recnum);
+- if (iter == m_encoderList->end())
++ auto iter = m_encoderList->constFind(recnum);
++ if (iter == m_encoderList->constEnd())
+ {
+ TVRec::s_inputsLock.unlock();
+ LOG(VB_GENERAL, LOG_ERR, LOC +
+@@ -7157,7 +7157,7 @@ void MainServer::HandleGetRecorderNum(QStringList &slist, PlaybackSock *pbs)
+ EncoderLink *encoder = nullptr;
+
+ TVRec::s_inputsLock.lockForRead();
+- for (auto iter = m_encoderList->begin(); iter != m_encoderList->end(); ++iter)
++ for (auto iter = m_encoderList->constBegin(); iter != m_encoderList->constEnd(); ++iter)
+ {
+ EncoderLink *elink = *iter;
+
+@@ -7203,8 +7203,8 @@ void MainServer::HandleGetRecorderFromNum(QStringList &slist,
+ QStringList strlist;
+
+ TVRec::s_inputsLock.lockForRead();
+- auto iter = m_encoderList->find(recordernum);
+- if (iter != m_encoderList->end())
++ auto iter = m_encoderList->constFind(recordernum);
++ if (iter != m_encoderList->constEnd())
+ encoder = (*iter);
+ TVRec::s_inputsLock.unlock();
+
+diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
+index 7a9762ea46a..4ca86721472 100644
+--- a/mythtv/programs/mythbackend/scheduler.cpp
++++ b/mythtv/programs/mythbackend/scheduler.cpp
+@@ -2513,7 +2513,7 @@ void Scheduler::HandleWakeSlave(RecordingInfo &ri, int prerollseconds)
+
+ QReadLocker tvlocker(&TVRec::s_inputsLock);
+
+- QMap<int, EncoderLink*>::iterator tvit = m_tvList->find(ri.GetInputID());
++ QMap<int, EncoderLink*>::const_iterator tvit = m_tvList->constFind(ri.GetInputID());
+ if (tvit == m_tvList->end())
+ return;
+
+@@ -2671,7 +2671,7 @@ bool Scheduler::HandleRecording(
+
+ QReadLocker tvlocker(&TVRec::s_inputsLock);
+
+- QMap<int, EncoderLink*>::iterator tvit = m_tvList->find(ri.GetInputID());
++ QMap<int, EncoderLink*>::const_iterator tvit = m_tvList->constFind(ri.GetInputID());
+ if (tvit == m_tvList->end())
+ {
+ QString msg = QString("Invalid cardid [%1] for %2")
+@@ -3078,8 +3078,8 @@ void Scheduler::HandleIdleShutdown(
+ bool recording = false;
+ m_schedLock.unlock();
+ TVRec::s_inputsLock.lockForRead();
+- QMap<int, EncoderLink *>::Iterator it;
+- for (it = m_tvList->begin(); (it != m_tvList->end()) &&
++ QMap<int, EncoderLink *>::const_iterator it;
++ for (it = m_tvList->constBegin(); (it != m_tvList->constEnd()) &&
+ !recording; ++it)
+ {
+ if ((*it)->IsBusy())
+@@ -3478,7 +3478,7 @@ void Scheduler::PutInactiveSlavesToSleep(void)
+ if (secsleft > sleepThreshold)
+ continue;
+
+- if (m_tvList->find(pginfo->GetInputID()) != m_tvList->end())
++ if (m_tvList->constFind(pginfo->GetInputID()) != m_tvList->constEnd())
+ {
+ EncoderLink *enc = (*m_tvList)[pginfo->GetInputID()];
+ if ((!enc->IsLocal()) &&
+
+From 3867297afe19fb6853143e9542688e66c7bc1f39 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 3 Apr 2020 10:09:18 -0600
+Subject: [PATCH 017/165] mythexternrec: Add a cleanup system command option to
+ the config file.
+
+If [RECORDER][cleanup] is defined, it will be run whenever this external
+recorder is shut down.
+
+(cherry picked from commit d5dacff66275b7e45a7c2cf3dadfadcc7fc2a87f)
+---
+ .../mythexternrecorder/MythExternControl.cpp | 6 ++++
+ .../mythexternrecorder/MythExternControl.h | 2 ++
+ .../mythexternrecorder/MythExternRecApp.cpp | 33 +++++++++++++++++++
+ .../mythexternrecorder/MythExternRecApp.h | 2 ++
+ mythtv/programs/mythexternrecorder/main.cpp | 2 ++
+ 5 files changed, 45 insertions(+)
+
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.cpp b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+index 3038a01dc25..a0de57140fc 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+@@ -188,6 +188,11 @@ void Commands::NextChannel(const QString & serial)
+ emit m_parent->NextChannel(serial);
+ }
+
++void Commands::Cleanup(void)
++{
++ emit m_parent->Cleanup();
++}
++
+ bool Commands::SendStatus(const QString & command, const QString & status)
+ {
+ int len = write(2, status.toUtf8().constData(), status.size());
+@@ -385,6 +390,7 @@ bool Commands::ProcessCommand(const QString & cmd)
+ StopStreaming(tokens[0], true);
+ m_parent->Terminate();
+ SendStatus(cmd, tokens[0], "OK:Terminating");
++ Cleanup();
+ }
+ else if (tokens[1].startsWith("FlowControl?"))
+ {
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.h b/mythtv/programs/mythexternrecorder/MythExternControl.h
+index c510ea1dfd6..308a9dd137b 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.h
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.h
+@@ -106,6 +106,7 @@ class Commands : public QObject
+ void LoadChannels(const QString & serial);
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
++ void Cleanup(void);
+
+ private:
+ std::thread m_thread;
+@@ -148,6 +149,7 @@ class MythExternControl : public QObject
+ void LoadChannels(const QString & serial);
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
++ void Cleanup(void);
+
+ public slots:
+ void SetDescription(const QString & desc) { m_desc = desc; }
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index a2597d2ea57..5d866922446 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -85,6 +85,7 @@ bool MythExternRecApp::config(void)
+
+ m_recCommand = settings.value("RECORDER/command").toString();
+ m_recDesc = settings.value("RECORDER/desc").toString();
++ m_cleanup = settings.value("RECORDER/cleanup").toString();
+ m_tuneCommand = settings.value("TUNER/command", "").toString();
+ m_channelsIni = settings.value("TUNER/channels", "").toString();
+ m_lockTimeout = settings.value("TUNER/timeout", "").toInt();
+@@ -200,6 +201,8 @@ void MythExternRecApp::TerminateProcess(void)
+ m_proc.kill();
+ m_proc.waitForFinished();
+ }
++
++ return;
+ }
+
+ Q_SLOT void MythExternRecApp::Close(void)
+@@ -255,6 +258,36 @@ void MythExternRecApp::Run(void)
+ emit Done();
+ }
+
++Q_SLOT void MythExternRecApp::Cleanup(void)
++{
++ if (m_cleanup.isEmpty())
++ return;
++
++ QString cmd = m_cleanup;
++
++ LOG(VB_RECORD, LOG_WARNING, LOC +
++ QString(" Beginning cleanup: '%1'").arg(cmd));
++
++ QProcess cleanup;
++ cleanup.start(cmd);
++ if (!cleanup.waitForStarted())
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to start cleanup process: " + ENO);
++ return;
++ }
++ cleanup.waitForFinished(5000);
++ if (cleanup.state() == QProcess::NotRunning)
++ {
++ if (cleanup.exitStatus() != QProcess::NormalExit)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": Cleanup process failed: " + ENO);
++ return;
++ }
++ }
++
++ LOG(VB_RECORD, LOG_INFO, LOC + ": Cleanup finished.");
++}
++
+ Q_SLOT void MythExternRecApp::LoadChannels(const QString & serial)
+ {
+ if (m_channelsIni.isEmpty())
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.h b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+index d09cffb4ce4..fbaa2b7596c 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+@@ -69,6 +69,7 @@ class MythExternRecApp : public QObject
+ void StopStreaming(const QString & serial, bool silent);
+ void LockTimeout(const QString & serial);
+ void HasTuner(const QString & serial);
++ void Cleanup(void);
+ void LoadChannels(const QString & serial);
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
+@@ -97,6 +98,7 @@ class MythExternRecApp : public QObject
+
+ QProcess m_proc;
+ QString m_command;
++ QString m_cleanup;
+
+ QString m_recCommand;
+ QString m_recDesc;
+diff --git a/mythtv/programs/mythexternrecorder/main.cpp b/mythtv/programs/mythexternrecorder/main.cpp
+index 71ca26079f1..e05e047d7cc 100644
+--- a/mythtv/programs/mythexternrecorder/main.cpp
++++ b/mythtv/programs/mythexternrecorder/main.cpp
+@@ -112,6 +112,8 @@ int main(int argc, char *argv[])
+ process, &MythExternRecApp::LockTimeout);
+ QObject::connect(control, &MythExternControl::HasTuner,
+ process, &MythExternRecApp::HasTuner);
++ QObject::connect(control, &MythExternControl::Cleanup,
++ process, &MythExternRecApp::Cleanup);
+ QObject::connect(control, &MythExternControl::LoadChannels,
+ process, &MythExternRecApp::LoadChannels);
+ QObject::connect(control, &MythExternControl::FirstChannel,
+
+From 4f79764adab0faea6f0e76074358bef902b13f14 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 3 Apr 2020 10:09:18 -0600
+Subject: [PATCH 018/165] mythexternrec: Track channum so an unnecessary tune
+ is not issued on back-to-back recordings.
+
+(cherry picked from commit d8d3b7422b220cbecaec518b350373075797026e)
+---
+ .../mythexternrecorder/MythExternRecApp.cpp | 18 +++++++++++++++---
+ .../mythexternrecorder/MythExternRecApp.h | 2 +-
+ 2 files changed, 16 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 5d866922446..967e5e89c2a 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -260,6 +260,8 @@ void MythExternRecApp::Run(void)
+
+ Q_SLOT void MythExternRecApp::Cleanup(void)
+ {
++ m_tunedChannel.clear();
++
+ if (m_cleanup.isEmpty())
+ return;
+
+@@ -272,7 +274,8 @@ Q_SLOT void MythExternRecApp::Cleanup(void)
+ cleanup.start(cmd);
+ if (!cleanup.waitForStarted())
+ {
+- LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to start cleanup process: " + ENO);
++ LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to start cleanup process: "
++ + ENO);
+ return;
+ }
+ cleanup.waitForFinished(5000);
+@@ -412,6 +415,15 @@ Q_SLOT void MythExternRecApp::NextChannel(const QString & serial)
+ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ const QString & channum)
+ {
++ if (m_tunedChannel == channum)
++ {
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ QString("TuneChanne: Already on %1").arg(channum));
++ emit SendMessage("TuneChannel", serial,
++ QString("OK:Tunned to %1").arg(channum));
++ return;
++ }
++
+ if (m_channelsIni.isEmpty())
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
+@@ -489,7 +501,7 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+ QString(": TuneChannel %1: URL '%2'").arg(channum).arg(url));
+- m_tuned = true;
++ m_tunedChannel = channum;
+
+ emit SetDescription(Desc());
+ emit SendMessage("TuneChannel", serial,
+@@ -528,7 +540,7 @@ Q_SLOT void MythExternRecApp::SetBlockSize(const QString & serial, int blksz)
+ Q_SLOT void MythExternRecApp::StartStreaming(const QString & serial)
+ {
+ m_streaming = true;
+- if (!m_tuned && !m_channelsIni.isEmpty())
++ if (m_tunedChannel.isEmpty() && !m_channelsIni.isEmpty())
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC + ": No channel has been tuned");
+ emit SendMessage("StartStreaming", serial,
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.h b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+index fbaa2b7596c..b94e01d0c86 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+@@ -117,7 +117,7 @@ class MythExternRecApp : public QObject
+ QString m_configIni;
+ QString m_desc;
+
+- bool m_tuned { false };
++ QString m_tunedChannel;
+
+ // Channel scanning
+ QSettings *m_chanSettings { nullptr };
+
+From 1244eddac0d8d65533b997eea67019aa1866acdd Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 3 Apr 2020 10:09:18 -0600
+Subject: [PATCH 019/165] Dvr::AddRecordSchedule: Allow last_record to be
+ specified.
+
+Scheduler::UpdateManuals: When creating the mythconverg.program entry,
+populate program.originalairdate from record.last_record.
+
+
+A little bit of a hack, but it is the cleanest solution without adding
+another variable to mythconverg.record.
+
+(cherry picked from commit 56277c79b7474d87c31bc67785972df16620115e)
+---
+ .../libmythservicecontracts/services/dvrServices.h | 1 +
+ mythtv/programs/mythbackend/scheduler.cpp | 11 ++++++++---
+ mythtv/programs/mythbackend/services/dvr.cpp | 4 ++++
+ mythtv/programs/mythbackend/services/dvr.h | 4 +++-
+ 4 files changed, 16 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/libs/libmythservicecontracts/services/dvrServices.h b/mythtv/libs/libmythservicecontracts/services/dvrServices.h
+index 30de373f00c..47ae6c71c98 100644
+--- a/mythtv/libs/libmythservicecontracts/services/dvrServices.h
++++ b/mythtv/libs/libmythservicecontracts/services/dvrServices.h
+@@ -210,6 +210,7 @@ class SERVICE_PUBLIC DvrServices : public Service //, public QScriptable ???
+ uint PreferredInput,
+ int StartOffset,
+ int EndOffset,
++ QDateTime LastRecorded,
+ QString DupMethod,
+ QString DupIn,
+ uint Filter,
+diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
+index 4ca86721472..42d8e1c8494 100644
+--- a/mythtv/programs/mythbackend/scheduler.cpp
++++ b/mythtv/programs/mythbackend/scheduler.cpp
+@@ -3660,7 +3660,7 @@ void Scheduler::UpdateManuals(uint recordid)
+
+ query.prepare(QString("SELECT type,title,subtitle,description,"
+ "station,startdate,starttime,"
+- "enddate,endtime,season,episode,inetref "
++ "enddate,endtime,season,episode,inetref,last_record "
+ "FROM %1 WHERE recordid = :RECORDID").arg(m_recordTable));
+ query.bindValue(":RECORDID", recordid);
+ if (!query.exec() || query.size() != 1)
+@@ -3687,6 +3687,10 @@ void Scheduler::UpdateManuals(uint recordid)
+ int episode = query.value(10).toInt();
+ QString inetref = query.value(11).toString();
+
++ // A bit of a hack: mythconverg.record.last_record can be used by
++ // the services API to propegate originalairdate information.
++ QDate originalairdate = QDate(query.value(12).toDate());
++
+ if (description.isEmpty())
+ description = startdt.toLocalTime().toString();
+
+@@ -3753,10 +3757,10 @@ void Scheduler::UpdateManuals(uint recordid)
+
+ query.prepare("REPLACE INTO program (chanid, starttime, endtime,"
+ " title, subtitle, description, manualid,"
+- " season, episode, inetref, generic) "
++ " season, episode, inetref, originalairdate, generic) "
+ "VALUES (:CHANID, :STARTTIME, :ENDTIME, :TITLE,"
+ " :SUBTITLE, :DESCRIPTION, :RECORDID, "
+- " :SEASON, :EPISODE, :INETREF, 1)");
++ " :SEASON, :EPISODE, :INETREF, :ORIGINALAIRDATE, 1)");
+ query.bindValue(":CHANID", id);
+ query.bindValue(":STARTTIME", startdt);
+ query.bindValue(":ENDTIME", startdt.addSecs(duration));
+@@ -3766,6 +3770,7 @@ void Scheduler::UpdateManuals(uint recordid)
+ query.bindValue(":SEASON", season);
+ query.bindValue(":EPISODE", episode);
+ query.bindValue(":INETREF", inetref);
++ query.bindValue(":ORIGINALAIRDATE", originalairdate);
+ query.bindValue(":RECORDID", recordid);
+ if (!query.exec())
+ {
+diff --git a/mythtv/programs/mythbackend/services/dvr.cpp b/mythtv/programs/mythbackend/services/dvr.cpp
+index ca9bf9759aa..07eb8e74cc5 100644
+--- a/mythtv/programs/mythbackend/services/dvr.cpp
++++ b/mythtv/programs/mythbackend/services/dvr.cpp
+@@ -1092,6 +1092,7 @@ uint Dvr::AddRecordSchedule (
+ uint nPreferredInput,
+ int nStartOffset,
+ int nEndOffset,
++ QDateTime lastrectsRaw,
+ QString sDupMethod,
+ QString sDupIn,
+ uint nFilter,
+@@ -1113,6 +1114,7 @@ uint Dvr::AddRecordSchedule (
+ {
+ QDateTime recstartts = recstarttsRaw.toUTC();
+ QDateTime recendts = recendtsRaw.toUTC();
++ QDateTime lastrects = lastrectsRaw.toUTC();
+ RecordingRule rule;
+ rule.LoadTemplate("Default");
+
+@@ -1199,6 +1201,8 @@ uint Dvr::AddRecordSchedule (
+
+ rule.m_transcoder = nTranscoder;
+
++ rule.m_lastRecorded = lastrects;
++
+ QString msg;
+ if (!rule.IsValid(msg))
+ throw msg;
+diff --git a/mythtv/programs/mythbackend/services/dvr.h b/mythtv/programs/mythbackend/services/dvr.h
+index 7a6b1be80bc..5bf193c05ab 100644
+--- a/mythtv/programs/mythbackend/services/dvr.h
++++ b/mythtv/programs/mythbackend/services/dvr.h
+@@ -173,6 +173,7 @@ class Dvr : public DvrServices
+ uint PreferredInput,
+ int StartOffset,
+ int EndOffset,
++ QDateTime lastrectsRaw,
+ QString DupMethod,
+ QString DupIn,
+ uint Filter,
+@@ -491,7 +492,8 @@ class ScriptableDvr : public QObject
+ rule->Inetref(), rule->Type(),
+ rule->SearchType(), rule->RecPriority(),
+ rule->PreferredInput(), rule->StartOffset(),
+- rule->EndOffset(), rule->DupMethod(),
++ rule->EndOffset(), rule->LastRecorded(),
++ rule->DupMethod(),
+ rule->DupIn(), rule->Filter(),
+ rule->RecProfile(), rule->RecGroup(),
+ rule->StorageGroup(), rule->PlayGroup(),
+
+From 49e545531a8ef6383abeb76a03220ae3ee880f7b Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 3 Apr 2020 10:09:18 -0600
+Subject: [PATCH 020/165] mythexternrecorder: Allow use of channum with tuning
+ command, even without a channel configuration file.
+
+(cherry picked from commit 356dd5e39a61e3a5e433508bd6afc937ca7c9e30)
+---
+ .../mythexternrecorder/MythExternControl.cpp | 4 +-
+ .../mythexternrecorder/MythExternRecApp.cpp | 95 +++++++++++--------
+ 2 files changed, 56 insertions(+), 43 deletions(-)
+
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.cpp b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+index a0de57140fc..0a0b933e8df 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+@@ -314,7 +314,7 @@ bool Commands::ProcessCommand(const QString & cmd)
+ else
+ SendStatus(cmd, tokens[0], "OK:20");
+ }
+- else if (tokens[1].startsWith("LockTimeout"))
++ else if (tokens[1].startsWith("LockTimeout?"))
+ {
+ LockTimeout(tokens[0]);
+ }
+@@ -357,7 +357,7 @@ bool Commands::ProcessCommand(const QString & cmd)
+ }
+ else if (tokens[1].startsWith("TuneChannel"))
+ {
+- if (tokens.size() > 1)
++ if (tokens.size() > 2)
+ TuneChannel(tokens[0], tokens[2]);
+ else
+ SendStatus(cmd, tokens[0], "ERR:Missing channum");
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 967e5e89c2a..494d207242a 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -27,6 +27,7 @@
+ #include <QFileInfo>
+ #include <QProcess>
+ #include <QtCore/QtCore>
++#include <unistd.h>
+
+ #define LOC Desc()
+
+@@ -42,8 +43,7 @@ MythExternRecApp::MythExternRecApp(QString command,
+ if (m_configIni.isEmpty() || !config())
+ m_recDesc = m_recCommand;
+
+- if (m_tuneCommand.isEmpty())
+- m_command = m_recCommand;
++ m_command = m_recCommand;
+
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+ QString("Channels in '%1', Tuner: '%2', Scanner: '%3'")
+@@ -424,56 +424,62 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ return;
+ }
+
+- if (m_channelsIni.isEmpty())
++ if (m_tuneCommand.isEmpty())
+ {
+- LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
+- emit SendMessage("TuneChannel", serial, "ERR:No channels configured.");
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No 'tuner' configured.");
++ emit SendMessage("TuneChannel", serial, "ERR:No 'tuner' configured.");
+ return;
+ }
+
+- QSettings settings(m_channelsIni, QSettings::IniFormat);
+- settings.beginGroup(channum);
++ m_desc = m_recDesc;
++ m_command = m_recCommand;
+
+- QString url(settings.value("URL").toString());
++ QString tune = m_tuneCommand;
++ QString url;
+
+- if (url.isEmpty())
++ if (!m_channelsIni.isEmpty())
+ {
+- QString msg = QString("Channel number [%1] is missing a URL.")
+- .arg(channum);
++ QSettings settings(m_channelsIni, QSettings::IniFormat);
++ settings.beginGroup(channum);
+
+- LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + msg);
++ url = settings.value("URL").toString();
+
+- emit SendMessage("TuneChannel", serial, QString("ERR:%1").arg(msg));
+- return;
+- }
++ if (url.isEmpty())
++ {
++ QString msg = QString("Channel number [%1] is missing a URL.")
++ .arg(channum);
+
+- if (!m_tuneCommand.isEmpty())
+- {
+- // Repalce URL in command and execute it
+- QString tune = m_tuneCommand;
+- tune.replace("%URL%", url);
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + msg);
++ }
++ else
++ tune.replace("%URL%", url);
+
+- if (system(tune.toUtf8().constData()) != 0)
++ if (!url.isEmpty() && m_command.indexOf("%URL%") >= 0)
+ {
+- QString errmsg = QString("'%1' failed: ").arg(tune) + ENO;
+- LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
+- emit SendMessage("TuneChannel", serial, QString("ERR:%1").arg(errmsg));
+- return;
++ m_command.replace("%URL%", url);
++ LOG(VB_CHANNEL, LOG_DEBUG, LOC +
++ QString(": '%URL%' replaced with '%1' in cmd: '%2'")
++ .arg(url).arg(m_command));
+ }
+- LOG(VB_CHANNEL, LOG_INFO, LOC +
+- QString(": TuneChannel, ran '%1'").arg(tune));
++
++ m_desc.replace("%CHANNAME%", settings.value("NAME").toString());
++ m_desc.replace("%CALLSIGN%", settings.value("CALLSIGN").toString());
++
++ settings.endGroup();
+ }
+
+- // Replace URL in recorder command
+- m_command = m_recCommand;
++ tune.replace("%CHANNUM%", channum);
++ m_command.replace("%CHANNUM%", channum);
+
+- if (!url.isEmpty() && m_command.indexOf("%URL%") >= 0)
++ if (system(tune.toUtf8().constData()) != 0)
+ {
+- m_command.replace("%URL%", url);
+- LOG(VB_CHANNEL, LOG_DEBUG, LOC +
+- QString(": '%URL%' replaced with '%1' in cmd: '%2'")
+- .arg(url).arg(m_command));
++ QString errmsg = QString("'%1' failed: ").arg(tune) + ENO;
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
++ emit SendMessage("TuneChannel", serial, QString("ERR:%1").arg(errmsg));
++ return;
+ }
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ QString(": TuneChannel, ran '%1'").arg(tune));
+
+ if (!m_logFile.isEmpty() && m_command.indexOf("%LOGFILE%") >= 0)
+ {
+@@ -491,13 +497,8 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ .arg(m_logging).arg(m_command));
+ }
+
+- m_desc = m_recDesc;
+ m_desc.replace("%URL%", url);
+ m_desc.replace("%CHANNUM%", channum);
+- m_desc.replace("%CHANNAME%", settings.value("NAME").toString());
+- m_desc.replace("%CALLSIGN%", settings.value("CALLSIGN").toString());
+-
+- settings.endGroup();
+
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+ QString(": TuneChannel %1: URL '%2'").arg(channum).arg(url));
+@@ -505,17 +506,29 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+
+ emit SetDescription(Desc());
+ emit SendMessage("TuneChannel", serial,
+- QString("OK:Tunned to %1").arg(channum));
++ QString("OK:Tuned to %1").arg(channum));
+ }
+
+ Q_SLOT void MythExternRecApp::LockTimeout(const QString & serial)
+ {
+ if (!Open())
++ {
++ LOG(VB_CHANNEL, LOG_WARNING, LOC +
++ "Cannot read LockTimeout from config file.");
++ emit SendMessage("LockTimeout", serial, "ERR: Not open");
+ return;
++ }
+
+ if (m_lockTimeout > 0)
++ {
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ QString("Using configured LockTimeout of %1").arg(m_lockTimeout));
+ emit SendMessage("LockTimeout", serial,
+ QString("OK:%1").arg(m_lockTimeout));
++ return;
++ }
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ "No LockTimeout defined in config, defaulting to 12000ms");
+ emit SendMessage("LockTimeout", serial, QString("OK:%1")
+ .arg(m_scanCommand.isEmpty() ? 12000 : 120000));
+ }
+@@ -523,7 +536,7 @@ Q_SLOT void MythExternRecApp::LockTimeout(const QString & serial)
+ Q_SLOT void MythExternRecApp::HasTuner(const QString & serial)
+ {
+ emit SendMessage("HasTuner", serial, QString("OK:%1")
+- .arg(m_channelsIni.isEmpty() ? "No" : "Yes"));
++ .arg(m_tuneCommand.isEmpty() ? "No" : "Yes"));
+ }
+
+ Q_SLOT void MythExternRecApp::HasPictureAttributes(const QString & serial)
+
+From a58ef59549d9accb5b874140a4a16823732bc4cf Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 3 Apr 2020 10:09:18 -0600
+Subject: [PATCH 021/165] ExternalChannel: When mythbackend is startting up,
+ don't /actually/ tune a channel.
+
+Tinning with an External Recorder can take a long time. As long as the
+External Recorder can be executed, assume tinning a channel will succeed.
+
+(cherry picked from commit 7c0b1421c49173a8955f6e090fbac9fc3d280ea4)
+---
+ .../libmythtv/recorders/ExternalChannel.cpp | 37 ++++++++++++++-----
+ .../libmythtv/recorders/ExternalChannel.h | 1 +
+ 2 files changed, 29 insertions(+), 9 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+index a8946108710..c1620a1dccd 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+@@ -98,18 +98,37 @@ bool ExternalChannel::Tune(const QString &channum)
+ return true;
+
+ QString result;
+-
+- LOG(VB_CHANNEL, LOG_INFO, LOC + "Tuning to " + channum);
+-
+- if (!m_streamHandler->ProcessCommand("TuneChannel:" + channum, result,
+- 20000))
++ if (m_tuneTimeout < 0)
+ {
+- LOG(VB_CHANNEL, LOG_ERR, LOC + QString
+- ("Failed to Tune %1: %2").arg(channum).arg(result));
+- return false;
++ // When mythbackend first starts up, just retrive the
++ // tuneTimeout for subsequent tune requests.
++
++ if (!m_streamHandler->ProcessCommand("LockTimeout?", result))
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + QString
++ ("Failed to retrieve LockTimeout: %1").arg(result));
++ m_tuneTimeout = 60000;
++ }
++ else
++ m_tuneTimeout = result.split(":")[1].toInt();
++
++ LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Using Tune timeout of %1ms")
++ .arg(m_tuneTimeout));
+ }
++ else
++ {
++ LOG(VB_CHANNEL, LOG_INFO, LOC + "Tuning to " + channum);
++
++ if (!m_streamHandler->ProcessCommand("TuneChannel:" + channum, result,
++ m_tuneTimeout))
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + QString
++ ("Failed to Tune %1: %2").arg(channum).arg(result));
++ return false;
++ }
+
+- UpdateDescription();
++ UpdateDescription();
++ }
+
+ return true;
+ }
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalChannel.h b/mythtv/libs/libmythtv/recorders/ExternalChannel.h
+index 243934301ee..1a7fc75a7aa 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalChannel.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalChannel.h
+@@ -52,6 +52,7 @@ class ExternalChannel : public DTVChannel
+ { return true; }
+
+ private:
++ int m_tuneTimeout { -1 };
+ QString m_device;
+ QStringList m_args;
+ ExternalStreamHandler *m_streamHandler {nullptr};
+
+From 74544819067773869c87c4b1974e46dc9ad9e41f Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 3 Apr 2020 10:09:18 -0600
+Subject: [PATCH 022/165] MythExternRecorder: Add support for long channel
+ change times.
+
+Add support for the external application to respond with "OK:Running" in
+response to a tuning request. The external application must then respond to
+"TuningStatus" message.
+
+This keeps LiveTV from timing out after 7 seconds when the tuning command
+takes a long time. It also lets mythbackend start up much faster, since it
+doesn't have to wait for a tuning command to complete before initializing
+the next tuner.
+
+(cherry picked from commit 9a973f5b560c51b83f4af87c2e1a3bf82a5dbd77)
+---
+ .../libmythtv/recorders/ExternalChannel.cpp | 42 +++++++-
+ .../libmythtv/recorders/ExternalChannel.h | 3 +
+ .../recorders/ExternalSignalMonitor.cpp | 13 +++
+ .../mythexternrecorder/MythExternControl.cpp | 9 ++
+ .../mythexternrecorder/MythExternControl.h | 2 +
+ .../mythexternrecorder/MythExternRecApp.cpp | 97 +++++++++++++------
+ .../mythexternrecorder/MythExternRecApp.h | 5 +-
+ .../mythexternrecorder/commandlineparser.cpp | 2 +-
+ mythtv/programs/mythexternrecorder/main.cpp | 2 +
+ 9 files changed, 140 insertions(+), 35 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+index c1620a1dccd..f9c295eda9c 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+@@ -119,8 +119,8 @@ bool ExternalChannel::Tune(const QString &channum)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "Tuning to " + channum);
+
+- if (!m_streamHandler->ProcessCommand("TuneChannel:" + channum, result,
+- m_tuneTimeout))
++ if (!m_streamHandler->ProcessCommand("TuneChannel:" + channum,
++ result, m_tuneTimeout))
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC + QString
+ ("Failed to Tune %1: %2").arg(channum).arg(result));
+@@ -128,6 +128,7 @@ bool ExternalChannel::Tune(const QString &channum)
+ }
+
+ UpdateDescription();
++ m_backgroundTuning = result.startsWith("OK:Start");
+ }
+
+ return true;
+@@ -143,3 +144,40 @@ bool ExternalChannel::EnterPowerSavingMode(void)
+ Close();
+ return true;
+ }
++
++uint ExternalChannel::GetTuneStatus(void)
++{
++
++ if (!m_backgroundTuning)
++ return 3;
++
++ LOG(VB_CHANNEL, LOG_DEBUG, LOC + QString("GetScriptStatus() %1")
++ .arg(m_systemStatus));
++
++ QString result;
++ int ret;
++
++ if (!m_streamHandler->ProcessCommand("TuneStatus?", result))
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + QString
++ ("Failed to Tune: %1").arg(result));
++ ret = 2;
++ m_backgroundTuning = false;
++ }
++ else
++ {
++ if (result.startsWith("OK:Running"))
++ ret = 1;
++ else
++ {
++ ret = 3;
++ m_backgroundTuning = false;
++ }
++ UpdateDescription();
++ }
++
++ LOG(VB_CHANNEL, LOG_DEBUG, LOC + QString("GetScriptStatus() %1 -> %2")
++ .arg(m_systemStatus). arg(ret));
++
++ return ret;
++}
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalChannel.h b/mythtv/libs/libmythtv/recorders/ExternalChannel.h
+index 1a7fc75a7aa..0b09953493b 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalChannel.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalChannel.h
+@@ -46,6 +46,8 @@ class ExternalChannel : public DTVChannel
+
+ QString UpdateDescription(void);
+ QString GetDescription(void);
++ bool IsBackgroundTuning(void) const { return m_backgroundTuning; }
++ uint GetTuneStatus(void);
+
+ protected:
+ bool IsExternalChannelChangeSupported(void) override // ChannelBase
+@@ -53,6 +55,7 @@ class ExternalChannel : public DTVChannel
+
+ private:
+ int m_tuneTimeout { -1 };
++ bool m_backgroundTuning {false};
+ QString m_device;
+ QStringList m_args;
+ ExternalStreamHandler *m_streamHandler {nullptr};
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+index 6fb4cd592a9..2db72f50af8 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+@@ -53,6 +53,9 @@ ExternalSignalMonitor::ExternalSignalMonitor(int db_cardnum,
+ LOG(VB_GENERAL, LOG_ERR, LOC + "Open failed");
+ else
+ m_lock_timeout = GetLockTimeout() * 1000;
++
++ if (GetExternalChannel()->IsBackgroundTuning())
++ m_scriptStatus.SetValue(1);
+ }
+
+ /** \fn ExternalSignalMonitor::~ExternalSignalMonitor()
+@@ -105,6 +108,16 @@ void ExternalSignalMonitor::UpdateValues(void)
+ return;
+ }
+
++ if (GetExternalChannel()->IsBackgroundTuning())
++ {
++ QMutexLocker locker(&m_statusLock);
++ if (m_scriptStatus.GetValue() < 2)
++ m_scriptStatus.SetValue(GetExternalChannel()->GetTuneStatus());
++
++ if (!m_scriptStatus.IsGood())
++ return;
++ }
++
+ if (m_stream_handler_started)
+ {
+ if (!m_stream_handler->IsRunning())
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.cpp b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+index 0a0b933e8df..258dc64dc57 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+@@ -173,6 +173,11 @@ void Commands::TuneChannel(const QString & serial, const QString & channum)
+ emit m_parent->TuneChannel(serial, channum);
+ }
+
++void Commands::TuneStatus(const QString & serial)
++{
++ emit m_parent->TuneStatus(serial);
++}
++
+ void Commands::LoadChannels(const QString & serial)
+ {
+ emit m_parent->LoadChannels(serial);
+@@ -362,6 +367,10 @@ bool Commands::ProcessCommand(const QString & cmd)
+ else
+ SendStatus(cmd, tokens[0], "ERR:Missing channum");
+ }
++ else if (tokens[1].startsWith("TuneStatus?"))
++ {
++ TuneStatus(tokens[0]);
++ }
+ else if (tokens[1].startsWith("LoadChannels"))
+ {
+ LoadChannels(tokens[0]);
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.h b/mythtv/programs/mythexternrecorder/MythExternControl.h
+index 308a9dd137b..172bf5bc1f7 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.h
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.h
+@@ -103,6 +103,7 @@ class Commands : public QObject
+ void HasPictureAttributes(const QString & serial) const;
+ void SetBlockSize(const QString & serial, int blksz);
+ void TuneChannel(const QString & serial, const QString & channum);
++ void TuneStatus(const QString & serial);
+ void LoadChannels(const QString & serial);
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
+@@ -146,6 +147,7 @@ class MythExternControl : public QObject
+ void HasPictureAttributes(const QString & serial) const;
+ void SetBlockSize(const QString & serial, int blksz);
+ void TuneChannel(const QString & serial, const QString & channum);
++ void TuneStatus(const QString & serial);
+ void LoadChannels(const QString & serial);
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 494d207242a..b9e02f6f2ea 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -178,28 +178,28 @@ bool MythExternRecApp::Open(void)
+ return true;
+ }
+
+-void MythExternRecApp::TerminateProcess(void)
++void MythExternRecApp::TerminateProcess(QProcess & proc, const QString & desc)
+ {
+- if (m_proc.state() == QProcess::Running)
++ if (proc.state() == QProcess::Running)
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC +
+- QString("Sending SIGINT to %1").arg(m_proc.pid()));
+- kill(m_proc.pid(), SIGINT);
+- m_proc.waitForFinished(5000);
++ QString("Sending SIGINT to %1(%2)").arg(desc).arg(proc.pid()));
++ kill(proc.pid(), SIGINT);
++ proc.waitForFinished(5000);
+ }
+- if (m_proc.state() == QProcess::Running)
++ if (proc.state() == QProcess::Running)
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC +
+- QString("Sending SIGTERM to %1").arg(m_proc.pid()));
+- m_proc.terminate();
+- m_proc.waitForFinished();
++ QString("Sending SIGTERM to %1(%2)").arg(desc).arg(proc.pid()));
++ proc.terminate();
++ proc.waitForFinished();
+ }
+- if (m_proc.state() == QProcess::Running)
++ if (proc.state() == QProcess::Running)
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC +
+- QString("Sending SIGKILL to %1").arg(m_proc.pid()));
+- m_proc.kill();
+- m_proc.waitForFinished();
++ QString("Sending SIGKILL to %1(%2)").arg(desc).arg(proc.pid()));
++ proc.kill();
++ proc.waitForFinished();
+ }
+
+ return;
+@@ -215,10 +215,16 @@ Q_SLOT void MythExternRecApp::Close(void)
+ std::this_thread::sleep_for(std::chrono::microseconds(50));
+ }
+
++ if (m_tuneProc.state() == QProcess::Running)
++ {
++ m_tuneProc.closeReadChannel(QProcess::StandardOutput);
++ TerminateProcess(m_tuneProc, "App");
++ }
++
+ if (m_proc.state() == QProcess::Running)
+ {
+ m_proc.closeReadChannel(QProcess::StandardOutput);
+- TerminateProcess();
++ TerminateProcess(m_proc, "App");
+ std::this_thread::sleep_for(std::chrono::microseconds(50));
+ }
+
+@@ -252,7 +258,7 @@ void MythExternRecApp::Run(void)
+ if (m_proc.state() == QProcess::Running)
+ {
+ m_proc.closeReadChannel(QProcess::StandardOutput);
+- TerminateProcess();
++ TerminateProcess(m_proc, "App");
+ }
+
+ emit Done();
+@@ -415,6 +421,13 @@ Q_SLOT void MythExternRecApp::NextChannel(const QString & serial)
+ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ const QString & channum)
+ {
++ if (m_tuneCommand.isEmpty())
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No 'tuner' configured.");
++ emit SendMessage("TuneChannel", serial, "ERR:No 'tuner' configured.");
++ return;
++ }
++
+ if (m_tunedChannel == channum)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+@@ -424,13 +437,6 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ return;
+ }
+
+- if (m_tuneCommand.isEmpty())
+- {
+- LOG(VB_CHANNEL, LOG_ERR, LOC + ": No 'tuner' configured.");
+- emit SendMessage("TuneChannel", serial, "ERR:No 'tuner' configured.");
+- return;
+- }
+-
+ m_desc = m_recDesc;
+ m_command = m_recCommand;
+
+@@ -468,18 +474,20 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ settings.endGroup();
+ }
+
++ if (m_tuneProc.state() == QProcess::Running)
++ TerminateProcess(m_tuneProc, "Tune");
++
+ tune.replace("%CHANNUM%", channum);
+ m_command.replace("%CHANNUM%", channum);
+
+- if (system(tune.toUtf8().constData()) != 0)
++ m_tuneProc.start(tune);
++ if (!m_tuneProc.waitForStarted())
+ {
+- QString errmsg = QString("'%1' failed: ").arg(tune) + ENO;
++ QString errmsg = QString("Tune `%1` failed: ").arg(tune) + ENO;
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
+ emit SendMessage("TuneChannel", serial, QString("ERR:%1").arg(errmsg));
+ return;
+ }
+- LOG(VB_CHANNEL, LOG_INFO, LOC +
+- QString(": TuneChannel, ran '%1'").arg(tune));
+
+ if (!m_logFile.isEmpty() && m_command.indexOf("%LOGFILE%") >= 0)
+ {
+@@ -499,14 +507,41 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+
+ m_desc.replace("%URL%", url);
+ m_desc.replace("%CHANNUM%", channum);
++ m_tuningChannel = channum;
+
+- LOG(VB_CHANNEL, LOG_INFO, LOC +
+- QString(": TuneChannel %1: URL '%2'").arg(channum).arg(url));
+- m_tunedChannel = channum;
++ LOG(VB_CHANNEL, LOG_INFO, LOC + QString(": Started `%1` URL '%2'")
++ .arg(tune).arg(url));
++ emit SendMessage("TuneChannel", serial,
++ QString("OK:Started `%1`").arg(tune));
++}
++
++Q_SLOT void MythExternRecApp::TuneStatus(const QString & serial)
++{
++ if (m_tuneProc.state() == QProcess::Running)
++ {
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ QString(": Tune process(%1) still running").arg(m_tuneProc.pid()));
++ emit SendMessage("TuneStatus", serial, "OK:Running");
++ return;
++ }
++
++ if (m_tuneProc.exitStatus() != QProcess::NormalExit)
++ {
++ QString errmsg = QString("'%1' failed: ")
++ .arg(m_tuneProc.program()) + ENO;
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
++ emit SendMessage("TuneStatus", serial,
++ QString("ERR:%1").arg(errmsg));
++ return;
++ }
++
++ m_tunedChannel = m_tuningChannel;
++ m_tuningChannel.clear();
+
++ LOG(VB_CHANNEL, LOG_INFO, LOC + QString(": Tuned %1").arg(m_tunedChannel));
+ emit SetDescription(Desc());
+ emit SendMessage("TuneChannel", serial,
+- QString("OK:Tuned to %1").arg(channum));
++ QString("OK:Tuned to %1").arg(m_tunedChannel));
+ }
+
+ Q_SLOT void MythExternRecApp::LockTimeout(const QString & serial)
+@@ -607,7 +642,7 @@ Q_SLOT void MythExternRecApp::StopStreaming(const QString & serial, bool silent)
+ m_streaming = false;
+ if (m_proc.state() == QProcess::Running)
+ {
+- TerminateProcess();
++ TerminateProcess(m_proc, "App");
+
+ LOG(VB_RECORD, LOG_INFO, LOC + ": External application terminated.");
+ if (silent)
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.h b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+index b94e01d0c86..af87e21e272 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+@@ -75,12 +75,13 @@ class MythExternRecApp : public QObject
+ void NextChannel(const QString & serial);
+
+ void TuneChannel(const QString & serial, const QString & channum);
++ void TuneStatus(const QString & serial);
+ void HasPictureAttributes(const QString & serial);
+ void SetBlockSize(const QString & serial, int blksz);
+
+ protected:
+ void GetChannel(const QString & serial, const QString & func);
+- void TerminateProcess(void);
++ void TerminateProcess(QProcess & proc, const QString & desc);
+
+ private:
+ bool config(void);
+@@ -105,6 +106,7 @@ class MythExternRecApp : public QObject
+
+ QMap<QString, QString> m_appEnv;
+
++ QProcess m_tuneProc;
+ QString m_tuneCommand;
+ QString m_channelsIni;
+ uint m_lockTimeout { 0 };
+@@ -117,6 +119,7 @@ class MythExternRecApp : public QObject
+ QString m_configIni;
+ QString m_desc;
+
++ QString m_tuningChannel;
+ QString m_tunedChannel;
+
+ // Channel scanning
+diff --git a/mythtv/programs/mythexternrecorder/commandlineparser.cpp b/mythtv/programs/mythexternrecorder/commandlineparser.cpp
+index 31c118385e3..f7ca200703a 100644
+--- a/mythtv/programs/mythexternrecorder/commandlineparser.cpp
++++ b/mythtv/programs/mythexternrecorder/commandlineparser.cpp
+@@ -9,7 +9,7 @@ MythExternRecorderCommandLineParser::MythExternRecorderCommandLineParser() :
+
+ QString MythExternRecorderCommandLineParser::GetHelpHeader(void) const
+ {
+- return "MythFileRecorder is a go-between app which interfaces "
++ return "mythexternrecorder is a go-between app which interfaces "
+ "between a recording device and mythbackend.";
+ }
+
+diff --git a/mythtv/programs/mythexternrecorder/main.cpp b/mythtv/programs/mythexternrecorder/main.cpp
+index e05e047d7cc..833ecb8a8c6 100644
+--- a/mythtv/programs/mythexternrecorder/main.cpp
++++ b/mythtv/programs/mythexternrecorder/main.cpp
+@@ -122,6 +122,8 @@ int main(int argc, char *argv[])
+ process, &MythExternRecApp::NextChannel);
+ QObject::connect(control, &MythExternControl::TuneChannel,
+ process, &MythExternRecApp::TuneChannel);
++ QObject::connect(control, &MythExternControl::TuneStatus,
++ process, &MythExternRecApp::TuneStatus);
+ QObject::connect(control, &MythExternControl::HasPictureAttributes,
+ process, &MythExternRecApp::HasPictureAttributes);
+ QObject::connect(control, &MythExternControl::SetBlockSize,
+
+From ecb0c15b4cc3bd86905602f93a453bad6459c08a Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sat, 4 Apr 2020 13:49:46 -0600
+Subject: [PATCH 023/165] ExtneralChannel: Use InProgress instead of running or
+ starting to indicate a long running tunning operation.
+
+Thanks to Gary Buhrmaster for the suggestion.
+
+(cherry picked from commit 1dd0408e236e354f72eaed02c1119bb2b3f4a157)
+---
+ mythtv/libs/libmythtv/recorders/ExternalChannel.cpp | 6 +++---
+ mythtv/programs/mythexternrecorder/MythExternRecApp.cpp | 4 ++--
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+index f9c295eda9c..5405c6f55fa 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+@@ -128,7 +128,7 @@ bool ExternalChannel::Tune(const QString &channum)
+ }
+
+ UpdateDescription();
+- m_backgroundTuning = result.startsWith("OK:Start");
++ m_backgroundTuning = result.startsWith("OK:InProgress");
+ }
+
+ return true;
+@@ -166,14 +166,14 @@ uint ExternalChannel::GetTuneStatus(void)
+ }
+ else
+ {
+- if (result.startsWith("OK:Running"))
++ if (result.startsWith("OK:InProgress"))
+ ret = 1;
+ else
+ {
+ ret = 3;
+ m_backgroundTuning = false;
++ UpdateDescription();
+ }
+- UpdateDescription();
+ }
+
+ LOG(VB_CHANNEL, LOG_DEBUG, LOC + QString("GetScriptStatus() %1 -> %2")
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index b9e02f6f2ea..1758d7e5395 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -512,7 +512,7 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ LOG(VB_CHANNEL, LOG_INFO, LOC + QString(": Started `%1` URL '%2'")
+ .arg(tune).arg(url));
+ emit SendMessage("TuneChannel", serial,
+- QString("OK:Started `%1`").arg(tune));
++ QString("OK:InProgress `%1`").arg(tune));
+ }
+
+ Q_SLOT void MythExternRecApp::TuneStatus(const QString & serial)
+@@ -521,7 +521,7 @@ Q_SLOT void MythExternRecApp::TuneStatus(const QString & serial)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+ QString(": Tune process(%1) still running").arg(m_tuneProc.pid()));
+- emit SendMessage("TuneStatus", serial, "OK:Running");
++ emit SendMessage("TuneStatus", serial, "OK:InProgress");
+ return;
+ }
+
+
+From f8495fd1564df151436ebe941a490ce5f7d871c8 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sun, 5 Apr 2020 18:04:33 -0600
+Subject: [PATCH 024/165] ExternRecorder: Fix live tv channel changes.
+
+(cherry picked from commit 18fa5fff1b9ac862cea0c5e8a3fb8981052bd6a3)
+---
+ mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp b/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
+index 445325bc835..a2acc946557 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
+@@ -177,6 +177,7 @@ bool ExternalRecorder::PauseAndWait(int timeout)
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC + "PauseAndWait pause");
+
++ m_streamHandler->RemoveListener(m_streamData);
+ StopStreaming();
+
+ m_paused = true;
+@@ -196,6 +197,8 @@ bool ExternalRecorder::PauseAndWait(int timeout)
+ m_streamData->Reset(m_streamData->DesiredProgram());
+
+ m_paused = false;
++ m_streamHandler->AddListener(m_streamData);
++ StartStreaming();
+ }
+
+ // Always wait a little bit, unless woken up
+
+From daa1d5d8e2a617c2fadb01e22558e30f53aa86b4 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Wed, 8 Apr 2020 14:41:45 -0600
+Subject: [PATCH 025/165] ExternalRecorder: Allow for optional ICON field is
+ channels.
+
+Any ExternalRecorder which supports fetching channel information can
+now supply the icon image filename. That information will be
+populated into the mythconverg.channel table. The file is NOT
+/installed/ anywhere and must exist where mythbackend looks for such
+files.
+
+(cherry picked from commit be7417fa483f650980767d053fc0cf4a20eac8f2)
+---
+ .../libmythtv/channelscan/externrecscanner.cpp | 9 +++++----
+ .../recorders/ExternalRecChannelFetcher.cpp | 12 ++++++++----
+ .../recorders/ExternalRecChannelFetcher.h | 17 +++++++++++------
+ .../mythexternrecorder/MythExternRecApp.cpp | 10 ++++++----
+ 4 files changed, 30 insertions(+), 18 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp b/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
+index be13e4f968f..11964840c62 100644
+--- a/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
++++ b/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
+@@ -120,9 +120,10 @@ void ExternRecChannelScanner::run(void)
+ QString name;
+ QString callsign;
+ QString xmltvid;
++ QString icon;
+ int cnt = 0;
+
+- if (!fetch.FirstChannel(channum, name, callsign, xmltvid))
++ if (!fetch.FirstChannel(channum, name, callsign, xmltvid, icon))
+ {
+ LOG(VB_CHANNEL, LOG_WARNING, LOC + "No channels found.");
+ QMutexLocker locker(&m_lock);
+@@ -156,7 +157,7 @@ void ExternRecChannelScanner::run(void)
+ ChannelUtil::CreateChannel(0, m_sourceId, chanid, callsign, name,
+ channum, 1, 0, 0,
+ false, kChannelVisible, QString(),
+- QString(), "Default", xmltvid);
++ icon, "Default", xmltvid);
+ }
+ else
+ {
+@@ -166,7 +167,7 @@ void ExternRecChannelScanner::run(void)
+ ChannelUtil::UpdateChannel(0, m_sourceId, chanid, callsign, name,
+ channum, 1, 0, 0,
+ false, kChannelVisible, QString(),
+- QString(), "Default", xmltvid);
++ icon, "Default", xmltvid);
+ }
+
+ SetNumChannelsInserted(cnt);
+@@ -178,7 +179,7 @@ void ExternRecChannelScanner::run(void)
+ }
+
+ if (++idx < m_channelTotal)
+- fetch.NextChannel(channum, name, callsign, xmltvid);
++ fetch.NextChannel(channum, name, callsign, xmltvid, icon);
+ else
+ break;
+ }
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
+index 28e11f2c98b..3a2385989ae 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
+@@ -70,7 +70,8 @@ bool ExternalRecChannelFetcher::FetchChannel(const QString & cmd,
+ QString & channum,
+ QString & name,
+ QString & callsign,
+- QString & xmltvid)
++ QString & xmltvid,
++ QString & icon)
+ {
+ if (!Valid())
+ return false;
+@@ -95,13 +96,14 @@ bool ExternalRecChannelFetcher::FetchChannel(const QString & cmd,
+ return false;
+ }
+
+- // Expect csv: channum, name, callsign, xmltvid
++ // Expect csv: channum, name, callsign, xmltvid, icon
+ QStringList fields = result.mid(3).split(",");
+
+- if (fields.size() != 4)
++ if (fields.size() != 4 && fields.size() != 5)
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC +
+- QString("Expecting channum, name, callsign, xmltvid; "
++ QString("Expecting channum, name, callsign, xmltvid and "
++ "optionally icon; "
+ "Received '%1").arg(result));
+ return false;
+ }
+@@ -110,6 +112,8 @@ bool ExternalRecChannelFetcher::FetchChannel(const QString & cmd,
+ name = fields[1];
+ callsign = fields[2];
+ xmltvid = fields[3];
++ if (fields.size() == 5)
++ icon = fields[4];
+
+ return true;
+ }
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
+index 84f354839d2..638b81becf1 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
+@@ -35,18 +35,22 @@ class ExternalRecChannelFetcher
+ bool FirstChannel(QString & channum,
+ QString & name,
+ QString & callsign,
+- QString & xmltvid)
++ QString & xmltvid,
++ QString & icon)
+ {
+- return FetchChannel("FirstChannel", channum, name, callsign, xmltvid);
++ return FetchChannel("FirstChannel", channum, name, callsign,
++ xmltvid, icon);
+ }
+ bool NextChannel(QString & channum,
+ QString & name,
+ QString & callsign,
+- QString & xmltvid)
++ QString & xmltvid,
++ QString & icon)
+ {
+- return FetchChannel("NextChannel", channum, name, callsign, xmltvid);
+- }
++ return FetchChannel("NextChannel", channum, name, callsign,
++ xmltvid, icon);
+
++ }
+
+ protected:
+ void Close(void);
+@@ -54,7 +58,8 @@ class ExternalRecChannelFetcher
+ QString & channum,
+ QString & name,
+ QString & callsign,
+- QString & xmltvid);
++ QString & xmltvid,
++ QString & icon);
+
+
+ private:
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 1758d7e5395..5a8acb28619 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -396,15 +396,17 @@ void MythExternRecApp::GetChannel(const QString & serial, const QString & func)
+ QString name = m_chanSettings->value("NAME").toString();
+ QString callsign = m_chanSettings->value("CALLSIGN").toString();
+ QString xmltvid = m_chanSettings->value("XMLTVID").toString();
++ QString icon = m_chanSettings->value("ICON").toString();
+
+ m_chanSettings->endGroup();
+
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+- QString(": NextChannel Name:'%1',Callsign:'%2',xmltvid:%3")
+- .arg(name).arg(callsign).arg(xmltvid));
++ QString(": NextChannel Name:'%1',Callsign:'%2',xmltvid:%3,Icon:%4")
++ .arg(name).arg(callsign).arg(xmltvid).arg(icon));
+
+- emit SendMessage(func, serial, QString("OK:%1,%2,%3,%4")
+- .arg(channum).arg(name).arg(callsign).arg(xmltvid));
++ emit SendMessage(func, serial, QString("OK:%1,%2,%3,%4,%5")
++ .arg(channum).arg(name).arg(callsign)
++ .arg(xmltvid).arg(icon));
+ }
+
+ Q_SLOT void MythExternRecApp::FirstChannel(const QString & serial)
+
+From 2921af5591d127e4ff337d7c6c57e1015d28cfd8 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <paul(a)mythqml.net>
+Date: Sat, 11 Apr 2020 15:45:10 +0100
+Subject: [PATCH 026/165] version.sh: if found use DESCRIBE to get branch and
+ version information
+
+The Ubuntu packaging scripts create a DESCRIBE file that has the branch and
+version information we need so use that if it's found.
+
+(cherry picked from commit 0851b35e3ded43ea738473bc60b8e5d13595b922)
+---
+ mythtv/version.sh | 90 +++++++++++++++++++++++++++++------------------
+ 1 file changed, 55 insertions(+), 35 deletions(-)
+
+diff --git a/mythtv/version.sh b/mythtv/version.sh
+index fd2c0be875f..56c04457622 100755
+--- a/mythtv/version.sh
++++ b/mythtv/version.sh
+@@ -21,44 +21,64 @@ GITREPOPATH="exported"
+
+ cd ${GITTREEDIR}
+
+-git status > /dev/null 2>&1
+-SOURCE_VERSION=$(git describe --dirty || git describe || echo Unknown)
++# if we have a mythtv/DECRIBE file use that to get the branch and version
++if test -e $GITTREEDIR/DESCRIBE ; then
++ echo "Using $GITTREEDIR/DESCRIBE"
++ . $GITTREEDIR/DESCRIBE
++ echo "BRANCH: $BRANCH"
++ echo "SOURCE_VERSION: $SOURCE_VERSION"
++else
++ # get the branch and version from git or fall back to EXPORTED_VERSION then VERSION as last resort
++ git status > /dev/null 2>&1
++ SOURCE_VERSION=$(git describe --dirty || git describe || echo Unknown)
++ echo "SOURCE_VERSION: $SOURCE_VERSION"
+
+-case "${SOURCE_VERSION}" in
+- exported|Unknown)
+- if ! grep -q Format $GITTREEDIR/EXPORTED_VERSION; then
+- . $GITTREEDIR/EXPORTED_VERSION
+- # This file has SOURCE_VERSION and BRANCH
+- # example SOURCE_VERSION="30d8a96"
+- # BRANCH examples from github
+- # BRANCH=" (HEAD -> master)"
+- # BRANCH=" (fixes/0.28)"
+- # BRANCH=" (tag: v0.28.1)"
+- # From a checkout they can be as follows:
+- # " (origin/fixes/0.28, fixes/0.28)"
+- # " (HEAD -> master, origin/master, origin/HEAD)"
+- # " (tag: v0.28.1)"
+- hash="$SOURCE_VERSION"
+- # This extracts after the last comma inside the parens:
+- BRANCH=$(echo "${BRANCH}" | sed -e 's/ (\(.*, \)\{0,1\}\(.*\))/\2/' -e 's,origin/,,')
+- # Create a suitable version (hash is no good)
+- SOURCE_VERSION="$BRANCH"
+- SOURCE_VERSION=`echo "$SOURCE_VERSION" | sed "s/tag: *//"`
+- if ! echo "$SOURCE_VERSION" | grep "^v[0-9]" ; then
++ case "${SOURCE_VERSION}" in
++ exported|Unknown)
++ if ! grep -q Format $GITTREEDIR/EXPORTED_VERSION; then
++ . $GITTREEDIR/EXPORTED_VERSION
++ echo "Using $GITTREEDIR/EXPORTED_VERSION"
++ echo "BRANCH: $BRANCH"
++ echo "SOURCE_VERSION: $SOURCE_VERSION"
++ # This file has SOURCE_VERSION and BRANCH
++ # example SOURCE_VERSION="30d8a96"
++ # BRANCH examples from github
++ # BRANCH=" (HEAD -> master)"
++ # BRANCH=" (fixes/0.28)"
++ # BRANCH=" (tag: v0.28.1)"
++ # From a checkout they can be as follows:
++ # " (origin/fixes/0.28, fixes/0.28)"
++ # " (HEAD -> master, origin/master, origin/HEAD)"
++ # " (tag: v0.28.1)"
++ hash="$SOURCE_VERSION"
++ # This extracts after the last comma inside the parens:
++ BRANCH=$(echo "${BRANCH}" | sed -e 's/ (\(.*, \)\{0,1\}\(.*\))/\2/' -e 's,origin/,,')
++ # Create a suitable version (hash is no good)
++ SOURCE_VERSION="$BRANCH"
++ SOURCE_VERSION=`echo "$SOURCE_VERSION" | sed "s/tag: *//"`
++ if ! echo "$SOURCE_VERSION" | grep "^v[0-9]" ; then
++ . $GITTREEDIR/VERSION
++ fi
++ SOURCE_VERSION="${SOURCE_VERSION}-${hash}"
++ echo "Source Version created as $SOURCE_VERSION"
++ echo "Branch created as $BRANCH"
++ elif test -e $GITTREEDIR/VERSION ; then
++ echo "Using $GITTREEDIR/VERSION"
+ . $GITTREEDIR/VERSION
++ echo "BRANCH: $BRANCH"
++ echo "SOURCE_VERSION: $SOURCE_VERSION"
++ fi
++ ;;
++ *)
++ if [ -z "${BRANCH}" ]; then
++ BRANCH=$(git branch --no-color | sed -e '/^[^\*]/d' -e 's/^\* //' -e 's/(no branch)/exported/')
++ echo "Using git to get branch and version"
++ echo "BRANCH: $BRANCH"
++ echo "SOURCE_VERSION: $SOURCE_VERSION"
+ fi
+- SOURCE_VERSION="${SOURCE_VERSION}-${hash}"
+- echo "Source Version created as $SOURCE_VERSION"
+- elif test -e $GITTREEDIR/VERSION ; then
+- . $GITTREEDIR/VERSION
+- fi
+- ;;
+- *)
+- if [ -z "${BRANCH}" ]; then
+- BRANCH=$(git branch --no-color | sed -e '/^[^\*]/d' -e 's/^\* //' -e 's/(no branch)/exported/')
+- fi
+- ;;
+-esac
++ ;;
++ esac
++fi
+
+ if ! echo "${SOURCE_VERSION}" | egrep -i "v[0-9]+.*" ; then
+ # Invalid version - use VERSION file
+
+From 3b54678feba714c514f32b38b0f5c1c6a74eaf3f Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 13 Apr 2020 17:12:12 +0100
+Subject: [PATCH 027/165] VAAPI: Fix compilation for older drivers
+
+Fixes #13606
+
+(cherry picked from commit 394245f0dbab1f2680b5a6edc67764debe3d9dfd)
+---
+ mythtv/libs/libmythtv/opengl/mythvaapidrminterop.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/opengl/mythvaapidrminterop.cpp b/mythtv/libs/libmythtv/opengl/mythvaapidrminterop.cpp
+index 48be0267f11..c03766c6fad 100644
+--- a/mythtv/libs/libmythtv/opengl/mythvaapidrminterop.cpp
++++ b/mythtv/libs/libmythtv/opengl/mythvaapidrminterop.cpp
+@@ -350,8 +350,12 @@ VideoFrameType MythVAAPIInteropDRM::VATypeToMythType(uint32_t Fourcc)
+ case VA_FOURCC_NV12: return FMT_NV12;
+ case VA_FOURCC_YUY2:
+ case VA_FOURCC_UYVY: return FMT_YUY2;
++#if defined (VA_FOURCC_P010)
+ case VA_FOURCC_P010: return FMT_P010;
++#endif
++#if defined (VA_FOURCC_P016)
+ case VA_FOURCC_P016: return FMT_P016;
++#endif
+ case VA_FOURCC_ARGB: return FMT_ARGB32;
+ case VA_FOURCC_RGBA: return FMT_RGBA32;
+ }
+
+From 4d0924203a33203eedc1d4cf0b3f242609d729ef Mon Sep 17 00:00:00 2001
+From: Nigel Jewell <nige(a)grufty.co.uk>
+Date: Sun, 12 Apr 2020 21:31:49 +0100
+Subject: [PATCH 028/165] Fix typo in 0851b35e3ded43ea738473bc60b8e5d13595b922
+ comment
+
+(cherry picked from commit dca115895bcc7631fd4eb9dcfa0b4d838ed1e786)
+---
+ mythtv/version.sh | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/version.sh b/mythtv/version.sh
+index 56c04457622..d412cc0505d 100755
+--- a/mythtv/version.sh
++++ b/mythtv/version.sh
+@@ -21,7 +21,7 @@ GITREPOPATH="exported"
+
+ cd ${GITTREEDIR}
+
+-# if we have a mythtv/DECRIBE file use that to get the branch and version
++# if we have a mythtv/DESCRIBE file use that to get the branch and version
+ if test -e $GITTREEDIR/DESCRIBE ; then
+ echo "Using $GITTREEDIR/DESCRIBE"
+ . $GITTREEDIR/DESCRIBE
+
+From c8f62c1688bb2ceaedabb2563f434dac1d7d5694 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <paul(a)mythqml.net>
+Date: Wed, 15 Apr 2020 20:12:47 +0100
+Subject: [PATCH 029/165] FAQ: trivial change to force an update
+
+---
+ mythtv/FAQ | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/FAQ b/mythtv/FAQ
+index 593d3b9fea6..e4f6c451095 100644
+--- a/mythtv/FAQ
++++ b/mythtv/FAQ
+@@ -1,5 +1,5 @@
+ MythTV FAQ
+
+ The FAQ is available on the MythTV wiki at
+-http://www.mythtv.org/wiki/Frequently_Asked_Questions
++https://www.mythtv.org/wiki/Frequently_Asked_Questions
+
+
+From 4e3935420ac0da8997d01ce77672f90d6dcbd78e Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Thu, 16 Apr 2020 22:22:20 +0200
+Subject: [PATCH 030/165] Fix "Full Scan" for DVB-T only tuners
+
+The "Full Scan" for tuners that can do only DVB-T and not DVB-T2
+was done correct but the modulation system was not entered in
+the transport in the database. This is now fixed.
+This gave problems in mythbackend when used with DVB-T/T2 tuners
+because these need the modulation system information.
+
+(cherry picked from commit a618b675fdd3b388221159e4d72cf15e56b2dfe4)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+index 543c7e1a9cd..98e9f9f7d4b 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+@@ -1032,6 +1032,10 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ item.m_networkID = dtv_sm->GetNetworkID();
+ item.m_transportID = dtv_sm->GetTransportID();
+
++ if (m_scanDTVTunerType == DTVTunerType::kTunerTypeDVBT)
++ {
++ item.m_tuning.m_modSys = DTVModulationSystem::kModulationSystem_DVBT;
++ }
+ if (m_scanDTVTunerType == DTVTunerType::kTunerTypeDVBT2)
+ {
+ if (m_dvbt2Tried)
+
+From 8bfc909dc70e8e9f156e66f7346f63e3e13660d5 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <paul(a)mythqml.net>
+Date: Sat, 18 Apr 2020 15:38:32 +0100
+Subject: [PATCH 031/165] HLSStreamHandler: fix the formatting of a debug
+ statement
+
+Refs #13608
+
+(cherry picked from commit 2b31dbf2ff30ea73b5865918719d14076c39f0cf)
+---
+ mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp b/mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp
+index 964f5396e46..22e0abae062 100644
+--- a/mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp
+@@ -181,7 +181,7 @@ void HLSStreamHandler::run(void)
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC +
+ QString("Packet not starting with SYNC Byte (got 0x%1)")
+- .arg((char)m_readbuffer[0], 2, QLatin1Char('0')));
++ .arg((char)m_readbuffer[0], 2, 16, QLatin1Char('0')));
+ continue;
+ }
+
+
+From 917a2087ef032b2c36a102cf4a2b220e10bf7bfe Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Tue, 21 Apr 2020 14:58:59 -0400
+Subject: [PATCH 032/165] Fix segfault in code called from
+ MythMainWindow::Draw.
+
+This reverts three of the changes in 380102ce34. In
+mythmainwindow.cpp while running the m_stackList, the call to
+MythScreenStack::GetDrawOrder can apparently modify m_stackList or
+something that it points to. Reverting the range-based for loops and
+restoring the original for loops prevents the crash.
+
+Fixes #13613.
+---
+ mythtv/libs/libmythui/mythmainwindow.cpp | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/mythtv/libs/libmythui/mythmainwindow.cpp b/mythtv/libs/libmythui/mythmainwindow.cpp
+index 7bdfe41bb3b..619ba7f7f78 100644
+--- a/mythtv/libs/libmythui/mythmainwindow.cpp
++++ b/mythtv/libs/libmythui/mythmainwindow.cpp
+@@ -690,10 +690,12 @@ void MythMainWindow::animate(void)
+ if (!d->m_repaintRegion.isEmpty())
+ redraw = true;
+
+- foreach (auto & widget, d->m_stackList)
++ // The call to GetDrawOrder can apparently alter m_stackList.
++ // NOLINTNEXTLINE(modernize-loop-convert)
++ for (auto it = d->m_stackList.begin(); it != d->m_stackList.end(); ++it)
+ {
+ QVector<MythScreenType *> drawList;
+- widget->GetDrawOrder(drawList);
++ (*it)->GetDrawOrder(drawList);
+
+ foreach (auto & screen, drawList)
+ {
+@@ -733,10 +735,12 @@ void MythMainWindow::drawScreen(void)
+
+ // Check for any widgets that have been updated since we built
+ // the dirty region list in ::animate()
+- foreach (auto & widget, d->m_stackList)
++ // The call to GetDrawOrder can apparently alter m_stackList.
++ // NOLINTNEXTLINE(modernize-loop-convert)
++ for (auto it = d->m_stackList.begin(); it != d->m_stackList.end(); ++it)
+ {
+ QVector<MythScreenType *> redrawList;
+- widget->GetDrawOrder(redrawList);
++ (*it)->GetDrawOrder(redrawList);
+
+ foreach (auto & screen, redrawList)
+ {
+@@ -823,11 +827,12 @@ void MythMainWindow::draw(MythPainter *painter /* = 0 */)
+ if (r != d->m_uiScreenRect)
+ painter->SetClipRect(r);
+
+- foreach (auto & widget, d->m_stackList)
++ // The call to GetDrawOrder can apparently alter m_stackList.
++ // NOLINTNEXTLINE(modernize-loop-convert)
++ for (auto it = d->m_stackList.begin(); it != d->m_stackList.end(); ++it)
+ {
+ QVector<MythScreenType *> redrawList;
+- widget->GetDrawOrder(redrawList);
+-
++ (*it)->GetDrawOrder(redrawList);
+ foreach (auto & screen, redrawList)
+ {
+ screen->Draw(painter, 0, 0, 255, r);
+
+From 5f1993304e35042192aed059dd7cf8717b76c6a7 Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Tue, 21 Apr 2020 15:24:15 -0400
+Subject: [PATCH 033/165] Fix incorrect data provided to UPnP client.
+
+This bug was introduced in 77b560f3cc when converting from the
+obsolete QString::sprintf function to typical QString formatting using
+QString::arg. In one case the arguments were all converted, but the
+format string wasn't . Fix that format string.
+
+Fixes #13612.
+---
+ mythtv/libs/libmythupnp/upnphelpers.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythupnp/upnphelpers.cpp b/mythtv/libs/libmythupnp/upnphelpers.cpp
+index cda4f86a778..3a2bfbba3ce 100644
+--- a/mythtv/libs/libmythupnp/upnphelpers.cpp
++++ b/mythtv/libs/libmythupnp/upnphelpers.cpp
+@@ -90,7 +90,7 @@ QString resDurationFormat(uint32_t msec)
+ // M = Minutes (2 digits, 0 prefix)
+ // S = Seconds (2 digits, 0 prefix)
+ // FS = Fractional Seconds (milliseconds)
+- return QString("%01u:%02u:%02u.%01u")
++ return QString("%1:%2:%3.%4")
+ .arg((msec / (1000 * 60 * 60)) % 24, 1,10,QChar('0')) // Hours
+ .arg((msec / (1000 * 60)) % 60, 2,10,QChar('0')) // Minutes
+ .arg((msec / 1000) % 60, 2,10,QChar('0')) // Seconds
+
+From c0b8b6e036bdb531fe09be8005dcf1181d9333ec Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= <janisozaur+signed(a)gmail.com>
+Date: Tue, 30 Oct 2018 21:56:52 +0100
+Subject: [PATCH 034/165] Compare to `None` using identity `is` operator
+
+This is a trivial change that replaces `==` operator with `is` operator, following PEP 8 guideline:
+
+> Comparisons to singletons like None should always be done with is or is not, never the equality operators.
+
+https://legacy.python.org/dev/peps/pep-0008/#programming-recommendations
+
+Closes #13343
+
+(cherry picked from commit 2e7e9e82ae71e958a7844e768d3d2758cf6df3ad)
+
+Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+---
+ .../mytharchive/mythburn/scripts/mythburn.py | 18 +++++------
+ .../scripts/giantbomb/giantbomb_api.py | 12 +++----
+ .../bindings/python/MythTV/ttvdb/tvdbXslt.py | 4 +--
+ .../bindings/python/MythTV/ttvdb/tvdb_api.py | 2 +-
+ .../bindings/python/tmdb3/tmdb3/cache_file.py | 2 +-
+ .../contrib/imports/mirobridge/mirobridge.py | 32 +++++++++----------
+ .../imports/mirobridge/mirobridge/metadata.py | 2 +-
+ .../mirobridge_interpreter_4_0_2.py | 4 +--
+ .../mirobridge_interpreter_6_0_0.py | 4 +--
+ .../distros/mythtv_data/uuiddb.py | 2 +-
+ .../scripts/hardwareprofile/sendProfile.py | 2 +-
+ .../programs/scripts/hardwareprofile/smolt.py | 4 +--
+ .../bbciplayer/bbciplayer_api.py | 16 +++++-----
+ .../nv_python_libs/bliptv/bliptv_api.py | 6 ++--
+ .../nv_python_libs/common/common_api.py | 10 +++---
+ .../dailymotion/dailymotion_api.py | 6 ++--
+ .../nv_python_libs/hulu/hulu_api.py | 14 ++++----
+ .../nv_python_libs/mainProcess.py | 4 +--
+ .../nv_python_libs/mashups/mashups_api.py | 10 +++---
+ .../nv_python_libs/mtv/mtv_api.py | 18 +++++------
+ .../nv_python_libs/rev3/rev3_api.py | 16 +++++-----
+ .../nv_python_libs/thewb/thewb_api.py | 10 +++---
+ .../nv_python_libs/vimeo/vimeo_api.py | 32 +++++++++----------
+ .../xsltfunctions/cinemarv_api.py | 6 ++--
+ .../nv_python_libs/xsltfunctions/nasa_api.py | 2 +-
+ .../xsltfunctions/skyAtNight_api.py | 2 +-
+ .../xsltfunctions/tributeca_api.py | 4 +--
+ .../nv_python_libs/youtube/youtube_api.py | 10 +++---
+ .../scripts/metadata/Music/mbutils.py | 16 +++++-----
+ .../metadata/Music/musicbrainzngs/util.py | 2 +-
+ .../scripts/metadata/Television/ttvdb.py | 20 ++++++------
+ 31 files changed, 146 insertions(+), 146 deletions(-)
+
+diff --git a/mythplugins/mytharchive/mythburn/scripts/mythburn.py b/mythplugins/mytharchive/mythburn/scripts/mythburn.py
+index 371e32bd370..87e09c3920f 100755
+--- a/mythplugins/mytharchive/mythburn/scripts/mythburn.py
++++ b/mythplugins/mytharchive/mythburn/scripts/mythburn.py
+@@ -269,16 +269,16 @@ def __init__(self, name=None, fontFile=None, size=19, color="white", effect="nor
+ self.font = None
+
+ def getFont(self):
+- if self.font == None:
++ if self.font is None:
+ self.font = ImageFont.truetype(self.fontFile, int(self.size))
+
+ return self.font
+
+ def drawText(self, text, color=None):
+- if self.font == None:
++ if self.font is None:
+ self.font = ImageFont.truetype(self.fontFile, int(self.size))
+
+- if color == None:
++ if color is None:
+ color = self.color
+
+ textwidth, textheight = self.font.getsize(text)
+@@ -1170,7 +1170,7 @@ def paintText(draw, image, text, node, color = None,
+ """Takes a piece of text and draws it onto an image inside a bounding box."""
+ #The text is wider than the width of the bounding box
+
+- if x == None:
++ if x is None:
+ x = getScaledAttribute(node, "x")
+ y = getScaledAttribute(node, "y")
+ width = getScaledAttribute(node, "w")
+@@ -1178,7 +1178,7 @@ def paintText(draw, image, text, node, color = None,
+
+ font = themeFonts[node.attributes["font"].value]
+
+- if color == None:
++ if color is None:
+ if node.hasAttribute("colour"):
+ color = node.attributes["colour"].value
+ elif node.hasAttribute("color"):
+@@ -3498,7 +3498,7 @@ def drawThemeItem(page, itemsonthispage, itemnum, menuitem, bgimage, draw,
+ else:
+ write( "Dont know how to process %s" % node.nodeName)
+
+- if drawmask == None:
++ if drawmask is None:
+ return
+
+ #Draw the selection mask for this item
+@@ -3685,7 +3685,7 @@ def createMenu(screensize, screendpi, numberofitems):
+ picture = Image.open(imagefile, "r").resize((previeww[itemsonthispage-1], previewh[itemsonthispage-1]))
+ picture = picture.convert("RGBA")
+ imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % itemsonthispage)
+- if previewmask[itemsonthispage-1] != None:
++ if previewmask[itemsonthispage-1] is not None:
+ bgimage.paste(picture, (previewx[itemsonthispage-1], previewy[itemsonthispage-1]), previewmask[itemsonthispage-1])
+ else:
+ bgimage.paste(picture, (previewx[itemsonthispage-1], previewy[itemsonthispage-1]))
+@@ -3886,7 +3886,7 @@ def createChapterMenu(screensize, screendpi, numberofitems):
+ picture = Image.open(imagefile, "r").resize((previeww[previewchapter], previewh[previewchapter]))
+ picture = picture.convert("RGBA")
+ imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % previewchapter)
+- if previewmask[previewchapter] != None:
++ if previewmask[previewchapter] is not None:
+ bgimage.paste(picture, (previewx[previewchapter], previewy[previewchapter]), previewmask[previewchapter])
+ else:
+ bgimage.paste(picture, (previewx[previewchapter], previewy[previewchapter]))
+@@ -4035,7 +4035,7 @@ def createDetailsPage(screensize, screendpi, numberofitems):
+ picture = Image.open(imagefile, "r").resize((previeww, previewh))
+ picture = picture.convert("RGBA")
+ imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % 1)
+- if previewmask != None:
++ if previewmask is not None:
+ bgimage.paste(picture, (previewx, previewy), previewmask)
+ else:
+ bgimage.paste(picture, (previewx, previewy))
+diff --git a/mythplugins/mythgame/mythgame/scripts/giantbomb/giantbomb_api.py b/mythplugins/mythgame/mythgame/scripts/giantbomb/giantbomb_api.py
+index d0d8d002545..2a03a78c624 100644
+--- a/mythplugins/mythgame/mythgame/scripts/giantbomb/giantbomb_api.py
++++ b/mythplugins/mythgame/mythgame/scripts/giantbomb/giantbomb_api.py
+@@ -167,7 +167,7 @@ def fixup(m):
+
+
+ def textUtf8(self, text):
+- if text == None:
++ if text is None:
+ return text
+ try:
+ return unicode(text, 'utf8')
+@@ -268,15 +268,15 @@ def futureReleaseDate(self, context, gameElement):
+ return If there is not enough information to make a date then return an empty string
+ '''
+ try:
+- if gameElement.find('expected_release_year').text != None:
++ if gameElement.find('expected_release_year').text is not None:
+ year = gameElement.find('expected_release_year').text
+ else:
+ year = None
+- if gameElement.find('expected_release_quarter').text != None:
++ if gameElement.find('expected_release_quarter').text is not None:
+ quarter = gameElement.find('expected_release_quarter').text
+ else:
+ quarter = None
+- if gameElement.find('expected_release_month').text != None:
++ if gameElement.find('expected_release_month').text is not None:
+ month = gameElement.find('expected_release_month').text
+ else:
+ month = None
+@@ -416,7 +416,7 @@ def gameSearch(self, gameTitle):
+
+ items = queryXslt(queryResult)
+
+- if items.getroot() != None:
++ if items.getroot() is not None:
+ if len(items.xpath('//item')):
+ sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
+ sys.exit(0)
+@@ -446,7 +446,7 @@ def gameData(self, gameId):
+ gamebombXpath[key] = self.FuncDict[key]
+ items = gameXslt(videoResult)
+
+- if items.getroot() != None:
++ if items.getroot() is not None:
+ if len(items.xpath('//item')):
+ sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
+ sys.exit(0)
+diff --git a/mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py b/mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py
+index f4dc9ca1074..8af7851fd78 100644
+--- a/mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py
++++ b/mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py
+@@ -218,7 +218,7 @@ def imageElements(self, context, *args):
+ for image in xpathFilter(args[0][0]):
+ # print("im %r" % image)
+ # print(etree.tostring(image, method="xml", xml_declaration=False, pretty_print=True, ))
+- if image.find('fileName') == None:
++ if image.find('fileName') is None:
+ continue
+ # print("im2 %r" % image)
+ tmpElement = etree.XML(u'<image></image>')
+@@ -242,7 +242,7 @@ def imageElements(self, context, *args):
+ # end imageElements()
+
+ def textUtf8(self, text):
+- if text == None:
++ if text is None:
+ return text
+ try:
+ return unicode(text, 'utf8')
+diff --git a/mythtv/bindings/python/MythTV/ttvdb/tvdb_api.py b/mythtv/bindings/python/MythTV/ttvdb/tvdb_api.py
+index 01b13c7ebc1..6ce90adc16e 100644
+--- a/mythtv/bindings/python/MythTV/ttvdb/tvdb_api.py
++++ b/mythtv/bindings/python/MythTV/ttvdb/tvdb_api.py
+@@ -1081,7 +1081,7 @@ def _getShowData(self, sid, language):
+
+ self._setShowData(sid, tag, value)
+ # set language
+- if language == None:
++ if language is None:
+ language = self.config['language']
+ self._setShowData(sid, u'language', language)
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py b/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py
+index e2f6165ac82..f847005146c 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/cache_file.py
+@@ -384,7 +384,7 @@ def _write(self, data):
+ # write storage slot definitions
+ prev = None
+ for d in data:
+- if prev == None:
++ if prev is None:
+ d.position = 4 + 16*size
+ else:
+ d.position = prev.position + prev.size
+diff --git a/mythtv/contrib/imports/mirobridge/mirobridge.py b/mythtv/contrib/imports/mirobridge/mirobridge.py
+index f9417f4ff49..a7f87ffec00 100755
+--- a/mythtv/contrib/imports/mirobridge/mirobridge.py
++++ b/mythtv/contrib/imports/mirobridge/mirobridge.py
+@@ -537,7 +537,7 @@ def _can_int(x):
+ >>> _can_int("A test")
+ False
+ """
+- if x == None:
++ if x is None:
+ return False
+ try:
+ int(x)
+@@ -571,7 +571,7 @@ def sanitiseFileName(name):
+ return a sanitised valid file name
+ '''
+ global filename_char_filter
+- if name == None or name == u'':
++ if name is None or name == u'':
+ return u'_'
+ for char in filename_char_filter:
+ name = name.replace(char, u'_')
+@@ -793,7 +793,7 @@ def rtnAbsolutePath(relpath, filetype=u'mythvideo'):
+ return an absolute path and file name
+ return the relpath sting if the file does not actually exist in the absolute path location
+ '''
+- if relpath == None or relpath == u'':
++ if relpath is None or relpath == u'':
+ return relpath
+
+ # There is a chance that this is already an absolute path
+@@ -1264,7 +1264,7 @@ def getStartEndTimes(duration, downloadedTime):
+ starttime.strftime('%Y-%m-%d %H:%M:%S'),
+ starttime.strftime('%Y%m%d%H%M%S')]
+
+- if downloadedTime != None:
++ if downloadedTime is not None:
+ try:
+ dummy = downloadedTime.strftime('%Y-%m-%d')
+ except ValueError:
+@@ -1416,7 +1416,7 @@ def createRecordedRecords(item):
+ ffmpeg_details = metadata.getVideoDetails(item[u'videoFilename'])
+ start_end = getStartEndTimes(ffmpeg_details[u'duration'], item[u'downloadedTime'])
+
+- if item[u'releasedate'] == None:
++ if item[u'releasedate'] is None:
+ item[u'releasedate'] = item[u'downloadedTime']
+ try:
+ dummy = item[u'releasedate'].strftime('%Y-%m-%d')
+@@ -1444,12 +1444,12 @@ def createRecordedRecords(item):
+ tmp_recorded[u'hostname'] = localhostname
+ tmp_recorded[u'lastmodified'] = tmp_recorded[u'endtime']
+ tmp_recorded[u'filesize'] = item[u'size']
+- if item[u'releasedate'] != None:
++ if item[u'releasedate'] is not None:
+ tmp_recorded[u'originalairdate'] = item[u'releasedate'].strftime('%Y-%m-%d')
+
+ basename = setSymbolic(item[u'videoFilename'], u'default', u"%s_%s" % \
+ (channel_id, start_end[2]), allow_symlink=True)
+- if basename != None:
++ if basename is not None:
+ tmp_recorded[u'basename'] = basename
+ else:
+ logger.critical(u"The file (%s) must exist to create a recorded record" % \
+@@ -1472,7 +1472,7 @@ def createRecordedRecords(item):
+
+ tmp_recordedprogram[u'category'] = u"Miro"
+ tmp_recordedprogram[u'category_type'] = u"series"
+- if item[u'releasedate'] != None:
++ if item[u'releasedate'] is not None:
+ tmp_recordedprogram[u'airdate'] = item[u'releasedate'].strftime('%Y')
+ tmp_recordedprogram[u'originalairdate'] = item[u'releasedate'].strftime('%Y-%m-%d')
+ tmp_recordedprogram[u'stereo'] = ffmpeg_details[u'stereo']
+@@ -1524,14 +1524,14 @@ def createVideometadataRecord(item):
+ for key in details.keys():
+ videometadata[key] = details[key]
+
+- if item[u'releasedate'] == None:
++ if item[u'releasedate'] is None:
+ item[u'releasedate'] = item[u'downloadedTime']
+ try:
+ dummy = item[u'releasedate'].strftime('%Y-%m-%d')
+ except ValueError:
+ item[u'releasedate'] = item[u'downloadedTime']
+
+- if item[u'releasedate'] != None:
++ if item[u'releasedate'] is not None:
+ videometadata[u'year'] = item[u'releasedate'].strftime('%Y')
+ videometadata[u'releasedate'] = item[u'releasedate'].strftime('%Y-%m-%d')
+ videometadata[u'length'] = ffmpeg_details[u'duration']/60
+@@ -1544,7 +1544,7 @@ def createVideometadataRecord(item):
+ videofile = setSymbolic(item[u'videoFilename'], u'mythvideo', "%s/%s - %s" % \
+ (sympath, sanitiseFileName(item[u'channelTitle']),
+ sanitiseFileName(item[u'title'])), allow_symlink=True)
+- if videofile != None:
++ if videofile is not None:
+ videometadata[u'filename'] = videofile
+ if not local_only and videometadata[u'filename'][0] != u'/':
+ videometadata[u'host'] = localhostname.lower()
+@@ -1565,14 +1565,14 @@ def createVideometadataRecord(item):
+ elif item[u'channel_icon'] and not item[u'channelTitle'].lower() in channel_icon_override:
+ filename = setSymbolic(item[u'channel_icon'], u'posterdir', u"%s" % \
+ (sanitiseFileName(item[u'channelTitle'])))
+- if filename != None:
++ if filename is not None:
+ videometadata[u'coverfile'] = filename
+ else:
+ if item[u'item_icon']:
+ filename = setSymbolic(item[u'item_icon'], u'posterdir', u"%s - %s" % \
+ (sanitiseFileName(item[u'channelTitle']),
+ sanitiseFileName(item[u'title'])))
+- if filename != None:
++ if filename is not None:
+ videometadata[u'coverfile'] = filename
+ else:
+ videometadata[u'coverfile'] = item[u'channel_icon']
+@@ -1582,7 +1582,7 @@ def createVideometadataRecord(item):
+ filename = setSymbolic(item[u'screenshot'], u'episodeimagedir', u"%s - %s" % \
+ (sanitiseFileName(item[u'channelTitle']),
+ sanitiseFileName(item[u'title'])))
+- if filename != None:
++ if filename is not None:
+ videometadata[u'screenshot'] = filename
+ else:
+ if item[u'screenshot']:
+@@ -1818,7 +1818,7 @@ def updateMythRecorded(items):
+ # Add new Miro unwatched videos to MythTV'd data base
+ for item in items_copy:
+ # Do not create records for Miro video files when Miro has a corrupt or missing file name
+- if item[u'videoFilename'] == None:
++ if item[u'videoFilename'] is None:
+ continue
+ # Do not create records for Miro video files that do not exist
+ if not os.path.isfile(os.path.realpath(item[u'videoFilename'])):
+@@ -2021,7 +2021,7 @@ def updateMythVideo(items):
+ result = takeScreenShot(item[u'videoFilename'], screenshot_mythvideo, size_limit=False)
+ except:
+ result = None
+- if result != None:
++ if result is not None:
+ item[u'screenshot'] = screenshot_mythvideo
+ tmp_array = createVideometadataRecord(item)
+ videometadata = tmp_array[0]
+diff --git a/mythtv/contrib/imports/mirobridge/mirobridge/metadata.py b/mythtv/contrib/imports/mirobridge/mirobridge/metadata.py
+index 4e0c882d2ce..76cbe025291 100644
+--- a/mythtv/contrib/imports/mirobridge/mirobridge/metadata.py
++++ b/mythtv/contrib/imports/mirobridge/mirobridge/metadata.py
+@@ -169,7 +169,7 @@ def getMetadata(self, title):
+ # If there is no Record rule then check ttvdb.com
+ if not len(recordedRules_array):
+ inetref = self.searchTvdb(title)
+- if inetref != None: # Create a new rule for this Miro Channel title
++ if inetref is not None: # Create a new rule for this Miro Channel title
+ ttvdbGraphics['inetref'] = inetref
+ self.makeRecordRule['title'] = title
+ self.makeRecordRule['inetref'] = inetref
+diff --git a/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_4_0_2.py b/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_4_0_2.py
+index 3061903e4f7..966d184677d 100644
+--- a/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_4_0_2.py
++++ b/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_4_0_2.py
+@@ -280,7 +280,7 @@ def do_mythtv_getunwatched(self, line):
+ continue
+
+ # Any item without a proper file name needs to be removed as Miro metadata is corrupt
+- if it.get_filename() == None:
++ if it.get_filename() is None:
+ it.expire()
+ self.statistics[u'Miro_videos_deleted']+=1
+ logging.info(u'Unwatched video (%s) has been removed from Miro as item had no valid file name' % it.get_title())
+@@ -314,7 +314,7 @@ def do_mythtv_getwatched(self, line):
+ continue
+
+ # Any item without a proper file name needs to be removed as Miro metadata is corrupt
+- if it.get_filename() == None:
++ if it.get_filename() is None:
+ it.expire()
+ self.statistics[u'Miro_videos_deleted']+=1
+ logging.info(u'Watched video (%s) has been removed from Miro as item had no valid file name' % it.get_title())
+diff --git a/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_6_0_0.py b/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_6_0_0.py
+index 1a5a6d9e78e..2282722e972 100644
+--- a/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_6_0_0.py
++++ b/mythtv/contrib/imports/mirobridge/mirobridge/mirobridge_interpreter_6_0_0.py
+@@ -292,7 +292,7 @@ def do_mythtv_getunwatched(self, line):
+
+ # Any item without a proper file name needs to be removed
+ # as Miro metadata is corrupt
+- if it.get_filename() == None:
++ if it.get_filename() is None:
+ it.expire()
+ self.statistics[u'Miro_videos_deleted']+=1
+ logging.info(
+@@ -327,7 +327,7 @@ def do_mythtv_getwatched(self, line):
+ continue
+
+ # Any item without a proper file name needs to be removed as Miro metadata is corrupt
+- if it.get_filename() == None:
++ if it.get_filename() is None:
+ it.expire()
+ self.statistics[u'Miro_videos_deleted']+=1
+ logging.info(
+diff --git a/mythtv/programs/scripts/hardwareprofile/distros/mythtv_data/uuiddb.py b/mythtv/programs/scripts/hardwareprofile/distros/mythtv_data/uuiddb.py
+index febd16e9f35..6960f8705ae 100644
+--- a/mythtv/programs/scripts/hardwareprofile/distros/mythtv_data/uuiddb.py
++++ b/mythtv/programs/scripts/hardwareprofile/distros/mythtv_data/uuiddb.py
+@@ -132,7 +132,7 @@ def get_priv_uuid(self):
+ def UuidDb():
+ """Simple singleton wrapper with lazy initialization"""
+ global _uuid_db_instance
+- if _uuid_db_instance == None:
++ if _uuid_db_instance is None:
+ import config
+ from smolt import get_config_attr
+ _uuid_db_instance = _UuidDb(get_config_attr("UUID_DB", os.path.expanduser('~/.smolt/uuiddb.cfg')))
+diff --git a/mythtv/programs/scripts/hardwareprofile/sendProfile.py b/mythtv/programs/scripts/hardwareprofile/sendProfile.py
+index ca929654dbf..ccbfdeac6d4 100755
+--- a/mythtv/programs/scripts/hardwareprofile/sendProfile.py
++++ b/mythtv/programs/scripts/hardwareprofile/sendProfile.py
+@@ -286,7 +286,7 @@ def mention_profile_web_view(opts, pub_uuid, admin):
+
+
+ def get_proxies(opts):
+- if opts.httpproxy == None:
++ if opts.httpproxy is None:
+ proxies = dict()
+ else:
+ proxies = {'http':opts.httpproxy}
+diff --git a/mythtv/programs/scripts/hardwareprofile/smolt.py b/mythtv/programs/scripts/hardwareprofile/smolt.py
+index 5cf234e7c05..1bcc8060d07 100644
+--- a/mythtv/programs/scripts/hardwareprofile/smolt.py
++++ b/mythtv/programs/scripts/hardwareprofile/smolt.py
+@@ -376,7 +376,7 @@ def ignoreDevice(device):
+ ignore = 1
+ if device.bus == 'Unknown' or device.bus == 'unknown':
+ return 1
+- if device.vendorid in (0, None) and device.type == None:
++ if device.vendorid in (0, None) and device.type is None:
+ return 1
+ if device.bus == 'usb' and device.driver == 'hub':
+ return 1
+@@ -388,7 +388,7 @@ def ignoreDevice(device):
+ return 1
+ if device.bus == 'block' and device.type == 'DISK':
+ return 1
+- if device.bus == 'usb_device' and device.type == None:
++ if device.bus == 'usb_device' and device.type is None:
+ return 1
+ return 0
+
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/bbciplayer/bbciplayer_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/bbciplayer/bbciplayer_api.py
+index 01ee6052561..02cefe586b1 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/bbciplayer/bbciplayer_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/bbciplayer/bbciplayer_api.py
+@@ -349,7 +349,7 @@ def searchTitle(self, title, pagenumber, pagelen):
+ pubDate = datetime.datetime.now().strftime(self.common.pubDateFormat)
+
+ # Set the display type for the link (Fullscreen, Web page, Game Console)
+- if self.userPrefs.find('displayURL') != None:
++ if self.userPrefs.find('displayURL') is not None:
+ urlType = self.userPrefs.find('displayURL').text
+ else:
+ urlType = u'fullscreen'
+@@ -519,7 +519,7 @@ def displayTreeView(self):
+ searchResultTree = []
+ searchFilter = etree.XPath(u"//item")
+ userSearchStrings = u'userSearchStrings'
+- if self.userPrefs.find(userSearchStrings) != None:
++ if self.userPrefs.find(userSearchStrings) is not None:
+ userSearch = self.userPrefs.find(userSearchStrings).xpath('./userSearch')
+ if len(userSearch):
+ for searchDetails in userSearch:
+@@ -554,7 +554,7 @@ def displayTreeView(self):
+ # Create a structure of feeds that can be concurrently downloaded
+ rssData = etree.XML(u'<xml></xml>')
+ for feedType in [u'treeviewURLS', u'userFeeds']:
+- if self.userPrefs.find(feedType) == None:
++ if self.userPrefs.find(feedType) is None:
+ continue
+ if not len(self.userPrefs.find(feedType).xpath('./url')):
+ continue
+@@ -581,7 +581,7 @@ def displayTreeView(self):
+ print
+
+ # Get the RSS Feed data
+- if rssData.find('url') != None:
++ if rssData.find('url') is not None:
+ try:
+ resultTree = self.common.getUrlData(rssData)
+ except Exception, errormsg:
+@@ -592,7 +592,7 @@ def displayTreeView(self):
+ print
+
+ # Set the display type for the link (Fullscreen, Web page, Game Console)
+- if self.userPrefs.find('displayURL') != None:
++ if self.userPrefs.find('displayURL') is not None:
+ urlType = self.userPrefs.find('displayURL').text
+ else:
+ urlType = u'fullscreen'
+@@ -638,7 +638,7 @@ def displayTreeView(self):
+ channelLanguage = u'en'
+ # Create a new directory and/or subdirectory if required
+ if names[0] != categoryDir:
+- if categoryDir != None:
++ if categoryDir is not None:
+ channelTree.append(categoryElement)
+ categoryElement = etree.XML(u'<directory></directory>')
+ categoryElement.attrib['name'] = names[0]
+@@ -714,8 +714,8 @@ def displayTreeView(self):
+ break
+
+ # Add the last directory processed
+- if categoryElement != None:
+- if categoryElement.xpath('.//item') != None:
++ if categoryElement is not None:
++ if categoryElement.xpath('.//item') is not None:
+ channelTree.append(categoryElement)
+
+ # Check that there was at least some items
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/bliptv/bliptv_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/bliptv/bliptv_api.py
+index 4abbf697dab..a59f99147d2 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/bliptv/bliptv_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/bliptv/bliptv_api.py
+@@ -279,7 +279,7 @@ def getExternalIP():
+
+ ip = getExternalIP()
+
+- if ip == None:
++ if ip is None:
+ return {}
+
+ try:
+@@ -371,7 +371,7 @@ def _initLogger(self):
+
+
+ def textUtf8(self, text):
+- if text == None:
++ if text is None:
+ return text
+ try:
+ return unicode(text, 'utf8')
+@@ -541,7 +541,7 @@ def searchForVideos(self, title, pagenumber):
+ sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
+ sys.exit(1)
+
+- if data == None:
++ if data is None:
+ return None
+ if not len(data):
+ return None
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/common/common_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/common/common_api.py
+index 03341242ba5..2f58267c300 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/common/common_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/common/common_api.py
+@@ -276,7 +276,7 @@ def initLogger(self, path=sys.stderr, log_name=u'MNV_Grabber'):
+
+
+ def textUtf8(self, text):
+- if text == None:
++ if text is None:
+ return text
+ try:
+ return unicode(text, 'utf8')
+@@ -369,7 +369,7 @@ def getExternalIP():
+
+ ip = getExternalIP()
+
+- if ip == None:
++ if ip is None:
+ return {}
+
+ try:
+@@ -508,7 +508,7 @@ def getUrlData(self, inputUrls, pageFilter=None):
+ urlDictionary[key]['morePages'] = u'false'
+ urlDictionary[key]['tmp'] = None
+ urlDictionary[key]['tree'] = None
+- if element.find('parameter') != None:
++ if element.find('parameter') is not None:
+ urlDictionary[key]['parameter'] = element.find('parameter').text
+
+ if self.debug:
+@@ -747,7 +747,7 @@ def linkWebPage(self, context, sourceLink):
+ # Currently there are no link specific Web pages
+ if not self.linksWebPage:
+ self.linksWebPage = etree.parse(u'%s/nv_python_libs/configs/XML/customeHtmlPageList.xml' % (self.baseProcessingDir, ))
+- if self.linksWebPage.find(sourceLink) != None:
++ if self.linksWebPage.find(sourceLink) is not None:
+ return u'file://%s/nv_python_libs/configs/HTML/%s' % (self.baseProcessingDir, self.linksWebPage.find(sourceLink).text)
+ return u'file://%s/nv_python_libs/configs/HTML/%s' % (self.baseProcessingDir, 'nodownloads.html')
+ # end linkWebPage()
+@@ -1015,7 +1015,7 @@ def run(self):
+ else:
+ continue
+ # Was any data found?
+- if self.urlDictionary[self.urlKey]['tmp'].getroot() == None:
++ if self.urlDictionary[self.urlKey]['tmp'].getroot() is None:
+ sys.stderr.write(u"No Xslt results for Name(%s)\n" % self.urlKey)
+ sys.stderr.write(u"No Xslt results for url(%s)\n" % self.urlDictionary[self.urlKey]['href'])
+ if len(self.urlDictionary[self.urlKey]['filter']) == index-1:
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/dailymotion/dailymotion_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/dailymotion/dailymotion_api.py
+index d4f502fc221..6cee18b2458 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/dailymotion/dailymotion_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/dailymotion/dailymotion_api.py
+@@ -566,7 +566,7 @@ def getExternalIP():
+
+ ip = getExternalIP()
+
+- if ip == None:
++ if ip is None:
+ return {}
+
+ try:
+@@ -658,7 +658,7 @@ def _initLogger(self):
+
+
+ def textUtf8(self, text):
+- if text == None:
++ if text is None:
+ return text
+ try:
+ return unicode(text, 'utf8')
+@@ -747,7 +747,7 @@ def searchForVideos(self, title, pagenumber):
+ sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
+ sys.exit(1)
+
+- if data == None:
++ if data is None:
+ return None
+ if not len(data):
+ return None
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/hulu/hulu_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/hulu/hulu_api.py
+index 1152735289b..153a7b6e852 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/hulu/hulu_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/hulu/hulu_api.py
+@@ -478,7 +478,7 @@ def displayTreeView(self):
+ searchResultTree = []
+ searchFilter = etree.XPath(u"//item")
+ userSearchStrings = u'userSearchStrings'
+- if self.userPrefs.find(userSearchStrings) != None:
++ if self.userPrefs.find(userSearchStrings) is not None:
+ userSearch = self.userPrefs.find(userSearchStrings).xpath('./userSearch')
+ if len(userSearch):
+ for searchDetails in userSearch:
+@@ -513,7 +513,7 @@ def displayTreeView(self):
+ # Create a structure of feeds that can be concurrently downloaded
+ rssData = etree.XML(u'<xml></xml>')
+ for feedType in [u'treeviewURLS', ]:
+- if self.userPrefs.find(feedType) == None:
++ if self.userPrefs.find(feedType) is None:
+ continue
+ if not len(self.userPrefs.find(feedType).xpath('./url')):
+ continue
+@@ -540,7 +540,7 @@ def displayTreeView(self):
+ print
+
+ # Get the RSS Feed data
+- if rssData.find('url') != None:
++ if rssData.find('url') is not None:
+ try:
+ resultTree = self.common.getUrlData(rssData)
+ except Exception, errormsg:
+@@ -591,7 +591,7 @@ def displayTreeView(self):
+ channelLanguage = u'en'
+ # Create a new directory and/or subdirectory if required
+ if names[0] != categoryDir:
+- if categoryDir != None:
++ if categoryDir is not None:
+ channelTree.append(categoryElement)
+ categoryElement = etree.XML(u'<directory></directory>')
+ categoryElement.attrib['name'] = names[0]
+@@ -617,7 +617,7 @@ def displayTreeView(self):
+ huluItem.find('author').text = u'Hulu'
+ huluItem.find('pubDate').text = pubdate
+ description = etree.HTML(etree.tostring(descriptionFilter(itemData)[0], method="text", encoding=unicode).strip())
+- if descFilter2(description)[0].text != None:
++ if descFilter2(description)[0].text is not None:
+ huluItem.find('description').text = self.common.massageText(descFilter2(description)[0].text.strip())
+ else:
+ huluItem.find('description').text = u''
+@@ -667,8 +667,8 @@ def displayTreeView(self):
+ break
+
+ # Add the last directory processed
+- if categoryElement != None:
+- if categoryElement.xpath('.//item') != None:
++ if categoryElement is not None:
++ if categoryElement.xpath('.//item') is not None:
+ channelTree.append(categoryElement)
+
+ # Check that there was at least some items
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/mainProcess.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/mainProcess.py
+index 0bc719f3cde..3a96c14c0d8 100755
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/mainProcess.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/mainProcess.py
+@@ -241,7 +241,7 @@ def searchForVideos(self, search_text, pagenumber):
+ self.config['target'].mashup_title = self.mashup_title
+
+ data_sets = self.config['target'].searchForVideos(search_text, pagenumber)
+- if data_sets == None:
++ if data_sets is None:
+ return
+ if not len(data_sets):
+ return
+@@ -272,7 +272,7 @@ def displayTreeView(self):
+ self.config['target'].mashup_title = self.mashup_title
+
+ data_sets = self.config['target'].displayTreeView()
+- if data_sets == None:
++ if data_sets is None:
+ return
+ if not len(data_sets):
+ return
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/mashups/mashups_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/mashups/mashups_api.py
+index 6fc4460f36d..962323384ce 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/mashups/mashups_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/mashups/mashups_api.py
+@@ -395,7 +395,7 @@ def searchForVideos(self, title, pagenumber):
+ print
+
+ # Get the source data
+- if sourceData.find('url') != None:
++ if sourceData.find('url') is not None:
+ # Process each directory of the user preferences that have an enabled rss feed
+ try:
+ resultTree = self.common.getUrlData(sourceData)
+@@ -488,7 +488,7 @@ def displayTreeView(self):
+ url = etree.XML(u'<url></url>')
+ etree.SubElement(url, "name").text = uniqueName
+ etree.SubElement(url, "href").text = source.attrib.get('url')
+- if source.attrib.get('parameter') != None:
++ if source.attrib.get('parameter') is not None:
+ etree.SubElement(url, "parameter").text = source.attrib.get('parameter')
+ if len(xsltFilename(source)):
+ for xsltName in xsltFilename(source):
+@@ -502,7 +502,7 @@ def displayTreeView(self):
+ print
+
+ # Get the source data
+- if sourceData.find('url') != None:
++ if sourceData.find('url') is not None:
+ # Process each directory of the user preferences that have an enabled rss feed
+ try:
+ resultTree = self.common.getUrlData(sourceData)
+@@ -566,7 +566,7 @@ def displayTreeView(self):
+
+ # Create a new directory and/or subdirectory if required
+ if names[0] != categoryDir:
+- if categoryDir != None:
++ if categoryDir is not None:
+ channelTree.append(categoryElement)
+ categoryElement = etree.XML(u'<directory></directory>')
+ categoryElement.attrib['name'] = names[0]
+@@ -627,7 +627,7 @@ def displayTreeView(self):
+ break
+
+ # Add the last directory processed and the "Special" directories
+- if categoryElement != None:
++ if categoryElement is not None:
+ if len(itemFilter(categoryElement)):
+ channelTree.append(categoryElement)
+ # Add the special directories videos
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/mtv/mtv_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/mtv/mtv_api.py
+index 47612f2a267..c047ca95291 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/mtv/mtv_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/mtv/mtv_api.py
+@@ -307,7 +307,7 @@ def _initLogger(self):
+
+
+ def textUtf8(self, text):
+- if text == None:
++ if text is None:
+ return text
+ try:
+ return unicode(text, 'utf8')
+@@ -392,7 +392,7 @@ def searchTitle(self, title, pagenumber, pagelen):
+ # Make sure there are no item elements that are None
+ for item in data:
+ for key in item.keys():
+- if item[key] == None:
++ if item[key] is None:
+ item[key] = u''
+
+ # Massage each field and eliminate any item without a URL
+@@ -420,7 +420,7 @@ def searchTitle(self, title, pagenumber, pagelen):
+ if key == 'content':
+ if len(item[key]):
+ if item[key][0].has_key('language'):
+- if item[key][0]['language'] != None:
++ if item[key][0]['language'] is not None:
+ item['language'] = item[key][0]['language']
+ if key == 'published_parsed': # '2009-12-21T00:00:00Z'
+ if item[key]:
+@@ -465,7 +465,7 @@ def videoDetails(self, url, title=u''):
+ metadata = {}
+ cur_size = True
+ for e in etree:
+- if e.tag.endswith(u'content') and e.text == None:
++ if e.tag.endswith(u'content') and e.text is None:
+ index = e.get('url').rindex(u':')
+ metadata['video'] = self.mtvHtmlPath % (title, e.get('url')[index+1:])
+ # !! This tag will need to be added at a later date
+@@ -536,7 +536,7 @@ def searchForVideos(self, title, pagenumber):
+ sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
+ sys.exit(1)
+
+- if data == None:
++ if data is None:
+ return None
+ if not len(data):
+ return None
+@@ -696,25 +696,25 @@ def getVideosForURL(self, url, dictionaries):
+ metadata['language'] = self.config['language']
+ for e in elements:
+ if e.tag.endswith(u'title'):
+- if e.text != None:
++ if e.text is not None:
+ metadata['title'] = self.massageDescription(e.text.strip())
+ else:
+ metadata['title'] = u''
+ continue
+ if e.tag == u'content':
+- if e.text != None:
++ if e.text is not None:
+ metadata['media_description'] = self.massageDescription(e.text.strip())
+ else:
+ metadata['media_description'] = u''
+ continue
+ if e.tag.endswith(u'published'): # '2007-03-06T00:00:00Z'
+- if e.text != None:
++ if e.text is not None:
+ pub_time = time.strptime(e.text.strip(), "%Y-%m-%dT%H:%M:%SZ")
+ metadata['published_parsed'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', pub_time)
+ else:
+ metadata['published_parsed'] = u''
+ continue
+- if e.tag.endswith(u'content') and e.text == None:
++ if e.tag.endswith(u'content') and e.text is None:
+ metadata['video'] = self.ampReplace(e.get('url'))
+ metadata['duration'] = e.get('duration')
+ continue
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/rev3/rev3_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/rev3/rev3_api.py
+index 08537df1c8e..b3cc354b09c 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/rev3/rev3_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/rev3/rev3_api.py
+@@ -332,13 +332,13 @@ def updateRev3(self, create=False):
+ tmpName = anchor.text
+ if tmpName == u'Revision3 Beta':
+ continue
+- if showURL != None:
++ if showURL is not None:
+ url = etree.SubElement(tmpDirectory, "url")
+ etree.SubElement(url, "name").text = tmpName
+ etree.SubElement(url, "href").text = showURL
+ etree.SubElement(url, "filter").text = showFilter
+ etree.SubElement(url, "parserType").text = u'html'
+- if tmpDirectory.find('url') != None:
++ if tmpDirectory.find('url') is not None:
+ showData.append(tmpDirectory)
+
+ if self.config['debug_enabled']:
+@@ -391,11 +391,11 @@ def updateRev3(self, create=False):
+ mp4Format.attrib['enabled'] = u'false'
+ mp4Format.attrib['name'] = format.text
+ mp4Format.attrib['rss'] = link
+- if tmpShow.find('mp4Format') != None:
++ if tmpShow.find('mp4Format') is not None:
+ tmpDirectory.append(tmpShow)
+
+ # If there is any data then add to new rev3.xml element tree
+- if tmpDirectory.find('show') != None:
++ if tmpDirectory.find('show') is not None:
+ userRev3.append(tmpDirectory)
+
+ if self.config['debug_enabled']:
+@@ -731,16 +731,16 @@ def displayTreeView(self):
+ for index in range(len(names)):
+ names[index] = self.common.massageText(names[index])
+ channel = channelFilter(result)[0]
+- if channel.find('image') != None:
++ if channel.find('image') is not None:
+ channelThumbnail = self.common.ampReplace(imageFilter(channel)[0].text)
+ else:
+ channelThumbnail = self.common.ampReplace(channel.find('link').text.replace(u'/watch/', u'/images/')+u'100.jpg')
+ channelLanguage = u'en'
+- if channel.find('language') != None:
++ if channel.find('language') is not None:
+ channelLanguage = channel.find('language').text[:2]
+ # Create a new directory and/or subdirectory if required
+ if names[0] != categoryDir:
+- if categoryDir != None:
++ if categoryDir is not None:
+ channelTree.append(categoryElement)
+ categoryElement = etree.XML(u'<directory></directory>')
+ if names[0] == personalFeed:
+@@ -813,7 +813,7 @@ def displayTreeView(self):
+ showElement.append(rev3Item)
+
+ # Add the last directory processed
+- if categoryElement.xpath('.//item') != None:
++ if categoryElement.xpath('.//item') is not None:
+ channelTree.append(categoryElement)
+
+ # Check that there was at least some items
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/thewb/thewb_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/thewb/thewb_api.py
+index 67ca0a123b8..4bfc32fc3ca 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/thewb/thewb_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/thewb/thewb_api.py
+@@ -92,7 +92,7 @@ def can_int(x):
+ >>> _can_int("A test")
+ False
+ """
+- if x == None:
++ if x is None:
+ return False
+ try:
+ int(x)
+@@ -433,7 +433,7 @@ def searchTitle(self, title, pagenumber, pagelen, ignoreError=False):
+ itemDwnLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
+ itemDict = {}
+ for result in searchResults:
+- if linkFilter(result) != None: # Make sure that this result actually has a video
++ if linkFilter(result) is not None: # Make sure that this result actually has a video
+ thewbItem = etree.XML(self.common.mnvItem)
+ # These videos are only viewable in the US so add a country indicator
+ etree.SubElement(thewbItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country").text = u'us'
+@@ -600,11 +600,11 @@ def displayTreeView(self):
+
+ # Process any user specified searches
+ showItems = {}
+- if len(showFeeds) != None:
++ if len(showFeeds) is not None:
+ for searchDetails in showFeeds:
+ try:
+ data = self.searchTitle(searchDetails.text.strip(), 1, self.page_limit, ignoreError=True)
+- if data[0] == None:
++ if data[0] is None:
+ continue
+ except TheWBVideoNotFound, msg:
+ sys.stderr.write(u"%s\n" % msg)
+@@ -685,7 +685,7 @@ def displayTreeView(self):
+ self.rssName = etree.XPath('title', namespaces=self.common.namespaces)
+ self.feedFilter = etree.XPath('//url[text()=$url]')
+ self.HTMLparser = etree.HTMLParser()
+- if rssData.find('url') != None:
++ if rssData.find('url') is not None:
+ try:
+ resultTree = self.common.getUrlData(rssData)
+ except Exception, errormsg:
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/vimeo/vimeo_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/vimeo/vimeo_api.py
+index 37a7df966f7..e87655296b5 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/vimeo/vimeo_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/vimeo/vimeo_api.py
+@@ -231,7 +231,7 @@ def __init__(self, key, secret,
+ self.authorization_url = authorization_url
+ self.consumer = oauth.OAuthConsumer(self.key, self.secret)
+
+- if token != None and token_secret != None:
++ if token is not None and token_secret is not None:
+ self.token = oauth.OAuthToken(token, token_secret)
+ else:
+ self.token = None
+@@ -325,9 +325,9 @@ def vimeo_albums_getAll(self, user_id, sort=None,
+ params = {'user_id': user_id}
+ if sort in ('newest', 'oldest', 'alphabetical'):
+ params['sort'] = sort
+- if per_page != None:
++ if per_page is not None:
+ params['per_page'] = per_page
+- if page != None:
++ if page is not None:
+ params['page'] = page
+ return self._do_vimeo_unauthenticated_call(inspect.stack()[0][3].replace('_', '.'),
+ parameters=params)
+@@ -347,9 +347,9 @@ def vimeo_videos_search(self, query, sort=None,
+ params['sort'] = sort
+ else:
+ params['sort'] = 'most_liked'
+- if per_page != None:
++ if per_page is not None:
+ params['per_page'] = per_page
+- if page != None:
++ if page is not None:
+ params['page'] = page
+ params['full_response'] = '1'
+ #params['query'] = query.replace(u' ', u'_')
+@@ -371,9 +371,9 @@ def vimeo_channels_getAll(self, sort=None,
+ if sort in ('newest', 'oldest', 'alphabetical',
+ 'most_videos', 'most_subscribed', 'most_recently_updated'):
+ params['sort'] = sort
+- if per_page != None:
++ if per_page is not None:
+ params['per_page'] = per_page
+- if page != None:
++ if page is not None:
+ params['page'] = page
+
+ return self._do_vimeo_unauthenticated_call(inspect.stack()[0][3].replace('_', '.'),
+@@ -388,13 +388,13 @@ def vimeo_channels_getVideos(self, channel_id=None, full_response=None,
+ """
+ # full_response channel_id
+ params = {}
+- if channel_id != None:
++ if channel_id is not None:
+ params['channel_id'] = channel_id
+- if full_response != None:
++ if full_response is not None:
+ params['full_response'] = 1
+- if per_page != None:
++ if per_page is not None:
+ params['per_page'] = per_page
+- if page != None:
++ if page is not None:
+ params['page'] = page
+
+ return self._do_vimeo_unauthenticated_call(inspect.stack()[0][3].replace('_', '.'),
+@@ -824,7 +824,7 @@ def _initLogger(self):
+
+
+ def textUtf8(self, text):
+- if text == None:
++ if text is None:
+ return text
+ try:
+ return unicode(text, 'utf8')
+@@ -915,7 +915,7 @@ def searchTitle(self, title, pagenumber, pagelen):
+ except Exception, msg:
+ raise VimeoVideosSearchError(u'%s' % msg)
+
+- if xml_data == None:
++ if xml_data is None:
+ raise VimeoVideoNotFound(self.error_messages['VimeoVideoNotFound'] % title)
+
+ if not len(xml_data.keys()):
+@@ -1063,7 +1063,7 @@ def searchForVideos(self, title, pagenumber):
+ sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
+ sys.exit(1)
+
+- if data == None:
++ if data is None:
+ return None
+ if not len(data):
+ return None
+@@ -1123,7 +1123,7 @@ def getChannels(self):
+ except Exception, msg:
+ raise VimeoAllChannelError(u'%s' % msg)
+
+- if xml_data == None:
++ if xml_data is None:
+ raise VimeoAllChannelError(self.error_messages['1-VimeoAllChannelError'] % sort)
+
+ if not len(xml_data.keys()):
+@@ -1311,7 +1311,7 @@ def getTreeVideos(self, method, dictionaries):
+ except Exception, msg:
+ raise VimeoVideosSearchError(u'%s' % msg)
+
+- if xml_data == None:
++ if xml_data is None:
+ raise VimeoVideoNotFound(self.error_messages['VimeoVideoNotFound'] % self.dir_name)
+
+ if not len(xml_data.keys()):
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/cinemarv_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/cinemarv_api.py
+index a29076f5850..1eedfa7f268 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/cinemarv_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/cinemarv_api.py
+@@ -111,7 +111,7 @@ def cinemarvLinkGeneration(self, context, *args):
+ webURL = args[0]
+ # If this is for the download then just return what was found for the "link" element
+ if self.persistence.has_key('cinemarvLinkGeneration'):
+- if self.persistence['cinemarvLinkGeneration'] != None:
++ if self.persistence['cinemarvLinkGeneration'] is not None:
+ returnValue = self.persistence['cinemarvLinkGeneration']
+ self.persistence['cinemarvLinkGeneration'] = None
+ return returnValue
+@@ -124,7 +124,7 @@ def cinemarvLinkGeneration(self, context, *args):
+ except Exception, errmsg:
+ sys.stderr.write(u'!Warning: The web page URL(%s) could not be read, error(%s)\n' % (webURL, errmsg))
+ return webURL
+- if webPageElement == None:
++ if webPageElement is None:
+ self.persistence['cinemarvLinkGeneration'] = webURL
+ return webURL
+
+@@ -147,7 +147,7 @@ def cinemarvIsCustomHTML(self, context, *args):
+ return True if the link does not starts with "http://"
+ return False if the link starts with "http://"
+ '''
+- if self.persistence['cinemarvLinkGeneration'] == None:
++ if self.persistence['cinemarvLinkGeneration'] is None:
+ return False
+
+ if self.persistence['cinemarvLinkGeneration'].startswith(u'http://'):
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/nasa_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/nasa_api.py
+index 057da71e43e..72208639be7 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/nasa_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/nasa_api.py
+@@ -124,7 +124,7 @@ def nasaTitleEp(self, context, *arg):
+ mythtv = "{%s}" % mythtvNamespace
+ NSMAP = {'mythtv' : mythtvNamespace}
+ elementTmp = etree.Element(mythtv + "mythtv", nsmap=NSMAP)
+- if not episodeNumber == None:
++ if not episodeNumber is None:
+ etree.SubElement(elementTmp, "title").text = u"EP%02d: %s" % (episodeNumber, title)
+ etree.SubElement(elementTmp, mythtv + "episode").text = u"%s" % episodeNumber
+ else:
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/skyAtNight_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/skyAtNight_api.py
+index dde9f1b10c0..a3cb81f6938 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/skyAtNight_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/skyAtNight_api.py
+@@ -117,7 +117,7 @@ def skyAtNightTitleEp(self, context, *arg):
+ mythtv = "{%s}" % mythtvNamespace
+ NSMAP = {'mythtv' : mythtvNamespace}
+ elementTmp = etree.Element(mythtv + "mythtv", nsmap=NSMAP)
+- if not episodeNumber == None:
++ if not episodeNumber is None:
+ etree.SubElement(elementTmp, "title").text = u"EP%02d" % episodeNumber
+ etree.SubElement(elementTmp, mythtv + "episode").text = u"%s" % episodeNumber
+ else:
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/tributeca_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/tributeca_api.py
+index a7376e0e8d4..f0f80756a9a 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/tributeca_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/xsltfunctions/tributeca_api.py
+@@ -115,7 +115,7 @@ def tributecaLinkGeneration(self, context, *args):
+
+ # If this is for the download then just return what was found for the "link" element
+ if self.persistence.has_key('tributecaLinkGeneration'):
+- if self.persistence['tributecaLinkGeneration'] != None:
++ if self.persistence['tributecaLinkGeneration'] is not None:
+ returnValue = self.persistence['tributecaLinkGeneration']
+ self.persistence['tributecaLinkGeneration'] = None
+ if returnValue != webURL:
+@@ -233,7 +233,7 @@ def tributecaIsCustomHTML(self, context, *args):
+ return True if the link does not starts with "http://"
+ return False if the link starts with "http://"
+ '''
+- if self.persistence['tributecaLinkGeneration'] == None:
++ if self.persistence['tributecaLinkGeneration'] is None:
+ return False
+
+ if self.persistence['tributecaLinkGeneration'].startswith(u'http://'):
+diff --git a/mythtv/programs/scripts/internetcontent/nv_python_libs/youtube/youtube_api.py b/mythtv/programs/scripts/internetcontent/nv_python_libs/youtube/youtube_api.py
+index 62fde93b2e0..ff502cc9f9d 100644
+--- a/mythtv/programs/scripts/internetcontent/nv_python_libs/youtube/youtube_api.py
++++ b/mythtv/programs/scripts/internetcontent/nv_python_libs/youtube/youtube_api.py
+@@ -171,7 +171,7 @@ def __init__(self,
+
+ # Read region code from user preferences, used by tree view
+ region = self.userPrefs.find("region")
+- if region != None and region.text:
++ if region is not None and region.text:
+ self.config['region'] = region.text
+ else:
+ self.config['region'] = u'us'
+@@ -179,7 +179,7 @@ def __init__(self,
+ self.apikey = getData().update(getData().a)
+
+ apikey = self.userPrefs.find("apikey")
+- if apikey != None and apikey.text:
++ if apikey is not None and apikey.text:
+ self.apikey = apikey.text
+
+ self.feed_icons = {
+@@ -256,7 +256,7 @@ def getExternalIP():
+
+ ip = getExternalIP()
+
+- if ip == None:
++ if ip is None:
+ return {}
+
+ try:
+@@ -445,7 +445,7 @@ def parseDetails(self, entry):
+
+ for key in item.keys():
+ # Make sure there are no item elements that are None
+- if item[key] == None:
++ if item[key] is None:
+ item[key] = u''
+ elif key == 'published_parsed': # 2010-01-23T08:38:39.000Z
+ if item[key]:
+@@ -499,7 +499,7 @@ def searchForVideos(self, title, pagenumber):
+ sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
+ sys.exit(1)
+
+- if data == None:
++ if data is None:
+ return None
+ if not len(data):
+ return None
+diff --git a/mythtv/programs/scripts/metadata/Music/mbutils.py b/mythtv/programs/scripts/metadata/Music/mbutils.py
+index 0a133fcbc67..bfe523c8353 100755
+--- a/mythtv/programs/scripts/metadata/Music/mbutils.py
++++ b/mythtv/programs/scripts/metadata/Music/mbutils.py
+@@ -246,7 +246,7 @@ def find_disc(cddrive):
+ if "offset-list" in result['disc']:
+ offsets = None
+ for offset in result['disc']['offset-list']:
+- if offsets == None:
++ if offsets is None:
+ offsets = str(offset)
+ else:
+ offsets += " " + str(offset)
+@@ -358,11 +358,11 @@ def main():
+ performSelfTest()
+
+ if opts.searchreleases:
+- if opts.artist == None:
++ if opts.artist is None:
+ print("Missing --artist argument")
+ sys.exit(1)
+
+- if opts.album == None:
++ if opts.album is None:
+ print("Missing --album argument")
+ sys.exit(1)
+
+@@ -373,7 +373,7 @@ def main():
+ search_releases(opts.artist, opts.album, limit)
+
+ if opts.searchartists:
+- if opts.artist == None:
++ if opts.artist is None:
+ print("Missing --artist argument")
+ sys.exit(1)
+
+@@ -384,25 +384,25 @@ def main():
+ search_artists(opts.artist, limit)
+
+ if opts.getartist:
+- if opts.id == None:
++ if opts.id is None:
+ print("Missing --id argument")
+ sys.exit(1)
+
+ get_artist(opts.id)
+
+ if opts.finddisc:
+- if opts.cddevice == None:
++ if opts.cddevice is None:
+ print("Missing --cddevice argument")
+ sys.exit(1)
+
+ find_disc(opts.cddevice)
+
+ if opts.findcoverart:
+- if opts.id == None and opts.relgroupid == None:
++ if opts.id is None and opts.relgroupid is None:
+ print("Missing --id or --relgroupid argument")
+ sys.exit(1)
+
+- if opts.id != None:
++ if opts.id is not None:
+ find_coverart(opts.id)
+ else:
+ find_coverart_releasegroup(opts.relgroupid)
+diff --git a/mythtv/programs/scripts/metadata/Music/musicbrainzngs/util.py b/mythtv/programs/scripts/metadata/Music/musicbrainzngs/util.py
+index 37316f53b1e..5f48e6b0d09 100644
+--- a/mythtv/programs/scripts/metadata/Music/musicbrainzngs/util.py
++++ b/mythtv/programs/scripts/metadata/Music/musicbrainzngs/util.py
+@@ -17,7 +17,7 @@ def _unicode(string, encoding=None):
+ if isinstance(string, compat.unicode):
+ unicode_string = string
+ elif isinstance(string, compat.bytes):
+- # use given encoding, stdin, preferred until something != None is found
++ # use given encoding, stdin, preferred until something is not None is found
+ if encoding is None:
+ encoding = sys.stdin.encoding
+ if encoding is None:
+diff --git a/mythtv/programs/scripts/metadata/Television/ttvdb.py b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+index 64eab727f03..76b98618920 100755
+--- a/mythtv/programs/scripts/metadata/Television/ttvdb.py
++++ b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+@@ -1451,7 +1451,7 @@ def fuzzysearch(self, term = None, key = None):
+ class Episode( tvdb_api.Episode ):
+ _re_strippart = re.compile('(.*) \([0-9]+\)')
+ def fuzzysearch(self, term = None, key = None):
+- if term == None:
++ if term is None:
+ raise TypeError("must supply string to search for (contents)")
+
+ term = unicode(term).lower()
+@@ -1643,7 +1643,7 @@ def get_graphics(t, opts, series_season_ep, graphics_type, single_option, langua
+ graphics = sorted(graphics, key=lambda k: k['rating'], reverse=True)
+ for URL in graphics:
+ if graphics_type == 'filename':
+- if URL[graphics_type] == None:
++ if URL[graphics_type] is None:
+ continue
+ if language and 'language' in URL: # Is there a language to filter URLs on?
+ if language == URL['language']:
+@@ -1753,7 +1753,7 @@ def Getseries_episode_data(t, opts, series_season_ep, language = None):
+ genres_string = series_data[u'genre'].encode('utf-8')
+ except:
+ genres_string=''
+- if genres_string != None and genres_string != '':
++ if genres_string is not None and genres_string != '':
+ genres = change_amp(genres_string)
+ genres = change_to_commas(genres)
+
+@@ -1791,7 +1791,7 @@ def Getseries_episode_data(t, opts, series_season_ep, language = None):
+ continue
+ i = data_keys.index(key) # Include only specific episode data
+ except ValueError:
+- if series_data[season][episode][key] != None:
++ if series_data[season][episode][key] is not None:
+ text = series_data[season][episode][key]
+ if isinstance(text, dict):
+ # handle language tuple
+@@ -1810,11 +1810,11 @@ def Getseries_episode_data(t, opts, series_season_ep, language = None):
+ continue
+ text = series_data[season][episode][key]
+
+- if text == None and key.title() == 'Director':
++ if text is None and key.title() == 'Director':
+ text = u"Unknown"
+ if isinstance(text, list):
+ text = ', '.join(text)
+- if text == None or text == 'None':
++ if text is None or text == 'None':
+ continue
+ else:
+ # handle language tuple
+@@ -1832,7 +1832,7 @@ def Getseries_episode_data(t, opts, series_season_ep, language = None):
+ print(u"Title:%s" % series_data[u'seriesname'])
+
+ for key in data_titles:
+- if key_values[index] != None:
++ if key_values[index] is not None:
+ if data_titles[index] == u'ReleaseDate:' and len(key_values[index]) > 4:
+ print(u'%s%s'% (u'Year:', key_values[index][:4]))
+ if key_values[index] != 'None':
+@@ -2119,7 +2119,7 @@ def displaySearchXML(tvdb_api):
+
+ tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbQuery.xsl')))
+ items = tvdbQueryXslt(tvdb_api.searchTree)
+- if items.getroot() != None:
++ if items.getroot() is not None:
+ if len(items.xpath('//item')):
+ sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
+ return 0
+@@ -2144,7 +2144,7 @@ def displaySeriesXML(tvdb_api, series_season_ep):
+
+ tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbVideo.xsl')))
+ items = tvdbQueryXslt(allDataElement)
+- if items.getroot() != None:
++ if items.getroot() is not None:
+ if len(items.xpath('//item')):
+ sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
+ return 0
+@@ -2169,7 +2169,7 @@ def displayCollectionXML(tvdb_api):
+
+ tvdbCollectionXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbCollection.xsl')))
+ items = tvdbCollectionXslt(tvdb_api.seriesInfoTree)
+- if items.getroot() != None:
++ if items.getroot() is not None:
+ if len(items.xpath('//item')):
+ sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
+ return 0
+
+From 3ccdb8c6d24e86a7282d32c14a1f4f09c87e9756 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Sun, 9 Feb 2020 19:03:03 -0600
+Subject: [PATCH 035/165] Python Bindings: fix warnings seen in *buntu
+ packaging
+
+(cherry picked from commit 12f44c74ed60e3b0909040f30665c9d3fc58c17c)
+---
+ mythtv/bindings/python/MythTV/database.py | 2 +-
+ mythtv/bindings/python/MythTV/system.py | 6 +++---
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/database.py b/mythtv/bindings/python/MythTV/database.py
+index 86294414320..7e3c4fe42be 100644
+--- a/mythtv/bindings/python/MythTV/database.py
++++ b/mythtv/bindings/python/MythTV/database.py
+@@ -1167,7 +1167,7 @@ def __init__(self, db, log, host):
+ self._db = db
+ self._host = host
+ self._log = log
+- if host is 'NULL':
++ if host == 'NULL':
+ self._insert = """INSERT INTO settings
+ (value, data, hostname)
+ VALUES (?, ?, NULL)"""
+diff --git a/mythtv/bindings/python/MythTV/system.py b/mythtv/bindings/python/MythTV/system.py
+index d1d7546c0eb..f7966fb6d69 100644
+--- a/mythtv/bindings/python/MythTV/system.py
++++ b/mythtv/bindings/python/MythTV/system.py
+@@ -131,7 +131,7 @@ def command(self, *args):
+ stderr will be available in the exception and this object
+ as attributes 'returncode' and 'stderr'.
+ """
+- if self.path is '':
++ if self.path == '':
+ return ''
+ cmd = '%s %s' % (self.path, ' '.join(['%s' % a for a in args]))
+ return self._runcmd(cmd)
+@@ -165,7 +165,7 @@ def append(self, *args):
+ self.path += ' '+' '.join(['%s' % a for a in args])
+
+ def _runasync(self, *args):
+- if self.path is '':
++ if self.path == '':
+ return ''
+ cmd = '%s %s' % (self.path, ' '.join(['%s' % a for a in args]))
+ return self.Process(cmd, self.useshell, self.log)
+@@ -441,7 +441,7 @@ def command(self, eventdata):
+ stderr will be available in the exception and this object
+ as attributes 'returncode' and 'stderr'.
+ """
+- if self.path is '':
++ if self.path == '':
+ return
+ cmd = self.path
+ if 'program' in eventdata:
+
+From 723d46eaaa7be6bd760f2c5dfcb50b5410f315ec Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 27 Apr 2020 21:28:44 +0100
+Subject: [PATCH 036/165] libmythtv: Fix VideoToolbox framework name
+
+Closes #13609
+
+(cherry picked from commit 00b8defa6d27bb5688b3217f597adb1faac4773f)
+---
+ mythtv/libs/libmythtv/libmythtv.pro | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro
+index f1b0248d220..646577cd5b5 100644
+--- a/mythtv/libs/libmythtv/libmythtv.pro
++++ b/mythtv/libs/libmythtv/libmythtv.pro
+@@ -66,7 +66,7 @@ macx {
+ LIBS += -framework OpenGL
+ LIBS += -framework IOKit
+ LIBS += -framework CoreVideo
+- LIBS += -framework VideoToolBox
++ LIBS += -framework VideoToolbox
+ LIBS += -framework IOSurface
+ DEFINES += USING_VTB
+ HEADERS += decoders/mythvtbcontext.h
+
+From f5d75a6de7c4ac668e1a64cdf31c7100bc81b65b Mon Sep 17 00:00:00 2001
+From: Hans Dingemans <jpldingemans(a)gmail.com>
+Date: Fri, 21 Feb 2020 12:13:00 -0500
+Subject: [PATCH 037/165] mythfilldatabase: reduce memory usage.
+
+Mythfilldatabase uses QDomDocument to parse and store the XML data that is read; according to QDomDocument documentation this object is not meant to handle large XML files; QXmlStreamReader should be used in these situations.
+
+This commit replaces QDomDocument by QXmlStreamreader.
+
+A test showed that memory usage dropped from 5.6 GB to 698 MB.
+
+Fixes #13517
+
+Signed-off-by: Peter Bennett <pbennett(a)mythtv.org>
+(cherry picked from commit a9aa006139da24cbad861a2c25d57fa3f71aba2a)
+---
+ mythtv/programs/mythfilldatabase/filldata.cpp | 1 -
+ .../programs/mythfilldatabase/xmltvparser.cpp | 997 +++++++++---------
+ .../programs/mythfilldatabase/xmltvparser.h | 4 -
+ 3 files changed, 496 insertions(+), 506 deletions(-)
+
+diff --git a/mythtv/programs/mythfilldatabase/filldata.cpp b/mythtv/programs/mythfilldatabase/filldata.cpp
+index 59137f26427..f320857c186 100644
+--- a/mythtv/programs/mythfilldatabase/filldata.cpp
++++ b/mythtv/programs/mythfilldatabase/filldata.cpp
+@@ -92,7 +92,6 @@ bool FillData::GrabDataFromFile(int id, QString &filename)
+ ChannelInfoList chanlist;
+ QMap<QString, QList<ProgInfo> > proglist;
+
+- m_xmltvParser.lateInit();
+ if (!m_xmltvParser.parseFile(filename, &chanlist, &proglist))
+ return false;
+
+diff --git a/mythtv/programs/mythfilldatabase/xmltvparser.cpp b/mythtv/programs/mythfilldatabase/xmltvparser.cpp
+index b8cdff01310..66157ffc648 100644
+--- a/mythtv/programs/mythfilldatabase/xmltvparser.cpp
++++ b/mythtv/programs/mythfilldatabase/xmltvparser.cpp
+@@ -34,12 +34,6 @@ XMLTVParser::XMLTVParser()
+ m_currentYear = MythDate::current().date().toString("yyyy").toUInt();
+ }
+
+-void XMLTVParser::lateInit()
+-{
+- m_movieGrabberPath = MetadataDownload::GetMovieGrabber();
+- m_tvGrabberPath = MetadataDownload::GetTelevisionGrabber();
+-}
+-
+ static uint ELFHash(const QByteArray &ba)
+ {
+ const auto *k = (const uchar *)ba.data();
+@@ -60,74 +54,6 @@ static uint ELFHash(const QByteArray &ba)
+ return h;
+ }
+
+-static QString getFirstText(const QDomElement& element)
+-{
+- for (QDomNode dname = element.firstChild(); !dname.isNull();
+- dname = dname.nextSibling())
+- {
+- QDomText t = dname.toText();
+- if (!t.isNull())
+- return t.data();
+- }
+- return QString();
+-}
+-
+-ChannelInfo *XMLTVParser::parseChannel(QDomElement &element, QUrl &baseUrl)
+-{
+- auto *chaninfo = new ChannelInfo;
+-
+- QString xmltvid = element.attribute("id", "");
+-
+- chaninfo->m_xmltvId = xmltvid;
+- chaninfo->m_tvFormat = "Default";
+-
+- for (QDomNode child = element.firstChild(); !child.isNull();
+- child = child.nextSibling())
+- {
+- QDomElement info = child.toElement();
+- if (!info.isNull())
+- {
+- if (info.tagName() == "icon")
+- {
+- if (chaninfo->m_icon.isEmpty())
+- {
+- QString path = info.attribute("src", "");
+- if (!path.isEmpty() && !path.contains("://"))
+- {
+- QString base = baseUrl.toString(QUrl::StripTrailingSlash);
+- chaninfo->m_icon = base +
+- ((path.startsWith("/")) ? path : QString("/") + path);
+- }
+- else if (!path.isEmpty())
+- {
+- QUrl url(path);
+- if (url.isValid())
+- chaninfo->m_icon = url.toString();
+- }
+- }
+- }
+- else if (info.tagName() == "display-name")
+- {
+- if (chaninfo->m_name.isEmpty())
+- {
+- chaninfo->m_name = info.text();
+- }
+- else if (chaninfo->m_callSign.isEmpty())
+- {
+- chaninfo->m_callSign = info.text();
+- }
+- else if (chaninfo->m_chanNum.isEmpty())
+- {
+- chaninfo->m_chanNum = info.text();
+- }
+- }
+- }
+- }
+-
+- chaninfo->m_freqId = chaninfo->m_chanNum;
+- return chaninfo;
+-}
+-
+ static void fromXMLTVDate(QString ×tr, QDateTime &dt)
+ {
+ // The XMLTV spec requires dates to either be in UTC/GMT or to specify a
+@@ -223,12 +149,12 @@ static void fromXMLTVDate(QString ×tr, QDateTime &dt)
+
+ QDateTime tmpDT = QDateTime(tmpDate, tmpTime, Qt::UTC);
+ if (!tmpDT.isValid())
+- {
+- LOG(VB_XMLTV, LOG_ERR,
+- QString("Invalid datetime (combination of date/time) "
++ {
++ LOG(VB_XMLTV, LOG_ERR,
++ QString("Invalid datetime (combination of date/time) "
+ "in XMLTV data, ignoring: %1").arg(timestr));
+- return;
+- }
++ return;
++ }
+
+ // While this seems like a hack, it's better than what was done before
+ QString isoDateString = tmpDT.toString(Qt::ISODate);
+@@ -248,485 +174,551 @@ static void fromXMLTVDate(QString ×tr, QDateTime &dt)
+ timestr = MythDate::toString(dt, MythDate::kFilename);
+ }
+
+-static void parseCredits(QDomElement &element, ProgInfo *pginfo)
++static int readNextWithErrorCheck(QXmlStreamReader &xml)
+ {
+- for (QDomNode child = element.firstChild(); !child.isNull();
+- child = child.nextSibling())
++ xml.readNext();
++ if (xml.hasError())
+ {
+- QDomElement info = child.toElement();
+- if (!info.isNull())
+- pginfo->AddPerson(info.tagName(), getFirstText(info));
++ LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
++ return false;
+ }
++ return true;
+ }
+
+-static void parseVideo(QDomElement &element, ProgInfo *pginfo)
++bool XMLTVParser::parseFile(
++ const QString& filename, ChannelInfoList *chanlist,
++ QMap<QString, QList<ProgInfo> > *proglist)
+ {
+- for (QDomNode child = element.firstChild(); !child.isNull();
+- child = child.nextSibling())
++ m_movieGrabberPath = MetadataDownload::GetMovieGrabber();
++ m_tvGrabberPath = MetadataDownload::GetTelevisionGrabber();
++ QFile f;
++ if (!dash_open(f, filename, QIODevice::ReadOnly))
+ {
+- QDomElement info = child.toElement();
+- if (!info.isNull())
+- {
+- if (info.tagName() == "quality")
+- {
+- if (getFirstText(info) == "HDTV")
+- pginfo->m_videoProps |= VID_HDTV;
+- }
+- else if (info.tagName() == "aspect")
+- {
+- if (getFirstText(info) == "16:9")
+- pginfo->m_videoProps |= VID_WIDESCREEN;
+- }
+- }
++ LOG(VB_GENERAL, LOG_ERR,
++ QString("Error unable to open '%1' for reading.") .arg(filename));
++ return false;
+ }
+-}
+
+-static void parseAudio(QDomElement &element, ProgInfo *pginfo)
+-{
+- for (QDomNode child = element.firstChild(); !child.isNull();
+- child = child.nextSibling())
++ QXmlStreamReader xml(&f);
++ QUrl baseUrl;
++ QUrl sourceUrl;
++ QString aggregatedTitle;
++ QString aggregatedDesc;
++ bool haveReadTV = false;
++ QString last_channel = ""; //xmltvId of the last program element we read
++ QDateTime last_starttime; //starttime of the last program element we read
++ while (!xml.atEnd() && !xml.hasError() && (! (xml.isEndElement() && xml.name() == "tv")))
+ {
+- QDomElement info = child.toElement();
+- if (!info.isNull())
++ if (xml.readNextStartElement())
+ {
+- if (info.tagName() == "stereo")
++ if (xml.name() == "tv")
+ {
+- if (getFirstText(info) == "mono")
+- {
+- pginfo->m_audioProps |= AUD_MONO;
+- }
+- else if (getFirstText(info) == "stereo")
+- {
+- pginfo->m_audioProps |= AUD_STEREO;
+- }
+- else if (getFirstText(info) == "dolby" ||
+- getFirstText(info) == "dolby digital")
+- {
+- pginfo->m_audioProps |= AUD_DOLBY;
+- }
+- else if (getFirstText(info) == "surround")
+- {
+- pginfo->m_audioProps |= AUD_SURROUND;
+- }
++ sourceUrl = QUrl(xml.attributes().value("source-info-url").toString());
++ baseUrl = QUrl(xml.attributes().value("source-data-url").toString());
++ haveReadTV = true;
+ }
+- }
+- }
+-}
+-
+-ProgInfo *XMLTVParser::parseProgram(QDomElement &element)
+-{
+- QString programid;
+- QString season;
+- QString episode;
+- QString totalepisodes;
+- auto *pginfo = new ProgInfo();
+-
+- QString text = element.attribute("start", "");
+- fromXMLTVDate(text, pginfo->m_starttime);
+- pginfo->m_startts = text;
+-
+- text = element.attribute("stop", "");
+- fromXMLTVDate(text, pginfo->m_endtime);
+- pginfo->m_endts = text;
+-
+- text = element.attribute("channel", "");
+- QStringList split = text.split(" ");
+-
+- pginfo->m_channel = split[0];
+-
+- text = element.attribute("clumpidx", "");
+- if (!text.isEmpty())
+- {
+- split = text.split('/');
+- pginfo->m_clumpidx = split[0];
+- pginfo->m_clumpmax = split[1];
+- }
+-
+- for (QDomNode child = element.firstChild(); !child.isNull();
+- child = child.nextSibling())
+- {
+- QDomElement info = child.toElement();
+- if (!info.isNull())
+- {
+- if (info.tagName() == "title")
++ if (xml.name() == "channel")
+ {
+- if (info.attribute("lang") == "ja_JP")
+- { // NOLINT(bugprone-branch-clone)
+- pginfo->m_title = getFirstText(info);
+- }
+- else if (info.attribute("lang") == "ja_JP@kana")
+- {
+- pginfo->m_title_pronounce = getFirstText(info);
+- }
+- else if (pginfo->m_title.isEmpty())
++ if (!haveReadTV)
+ {
+- pginfo->m_title = getFirstText(info);
++ LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, no <tv> element found, at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
++ return false;
+ }
+- }
+- else if (info.tagName() == "sub-title" &&
+- pginfo->m_subtitle.isEmpty())
+- {
+- pginfo->m_subtitle = getFirstText(info);
+- }
+- else if (info.tagName() == "desc" && pginfo->m_description.isEmpty())
+- {
+- pginfo->m_description = getFirstText(info);
+- }
+- else if (info.tagName() == "category")
+- {
+- const QString cat = getFirstText(info);
+
+- if (ProgramInfo::kCategoryNone == pginfo->m_categoryType &&
+- string_to_myth_category_type(cat) != ProgramInfo::kCategoryNone)
+- {
+- pginfo->m_categoryType = string_to_myth_category_type(cat);
+- }
+- else if (pginfo->m_category.isEmpty())
+- {
+- pginfo->m_category = cat;
+- }
++ //get id attribute
++ QString xmltvid;
++ xmltvid = xml.attributes().value( "id").toString();
++ auto *chaninfo = new ChannelInfo;
++ chaninfo->m_xmltvId = xmltvid;
++ chaninfo->m_tvFormat = "Default";
+
+- if ((cat.compare(QObject::tr("movie"),Qt::CaseInsensitive) == 0) ||
+- (cat.compare(QObject::tr("film"),Qt::CaseInsensitive) == 0))
++ //readNextStartElement says it reads for the next start element WITHIN the current element; but it doesnt; so we use readNext()
++ do
+ {
+- // Hack for tv_grab_uk_rt
+- pginfo->m_categoryType = ProgramInfo::kCategoryMovie;
++ if (!readNextWithErrorCheck(xml))
++ return false;
++ if (xml.name() == "icon")
++ {
++ if (chaninfo->m_icon.isEmpty())
++ {
++ QString path = xml.attributes().value("src").toString();
++ if (!path.isEmpty() && !path.contains("://"))
++ {
++ QString base = baseUrl.toString(QUrl::StripTrailingSlash);
++ chaninfo->m_icon = base +
++ ((path.startsWith("/")) ? path : QString("/") + path);
++ }
++ else if (!path.isEmpty())
++ {
++ QUrl url(path);
++ if (url.isValid())
++ chaninfo->m_icon = url.toString();
++ }
++ }
++ }
++ else if (xml.name() == "display-name")
++ {
++ //now get text
++ QString text;
++ text = xml.readElementText(QXmlStreamReader::SkipChildElements);
++ if (!text.isEmpty())
++ {
++ if (chaninfo->m_name.isEmpty())
++ {
++ chaninfo->m_name = text;
++ }
++ else if (chaninfo->m_callSign.isEmpty())
++ {
++ chaninfo->m_callSign = text;
++ }
++ else if (chaninfo->m_chanNum.isEmpty())
++ {
++ chaninfo->m_chanNum = text;
++ }
++ }
++ }
+ }
+-
+- pginfo->m_genres.append(cat);
+- }
+- else if (info.tagName() == "date" && (pginfo->m_airdate == 0U))
++ while (! (xml.isEndElement() && xml.name() == "channel"));
++ chaninfo->m_freqId = chaninfo->m_chanNum;
++ //TODO optimize this, no use to do al this parsing if xmltvid is empty; but make sure you will read until the next channel!!
++ if (!chaninfo->m_xmltvId.isEmpty())
++ chanlist->push_back(*chaninfo);
++ delete chaninfo;
++ }//channel
++ else if (xml.name() == "programme")
+ {
+- // Movie production year
+- QString date = getFirstText(info);
+- pginfo->m_airdate = date.left(4).toUInt();
+- }
+- else if (info.tagName() == "star-rating" && pginfo->m_stars == 0.0F)
+- {
+- QDomNodeList values = info.elementsByTagName("value");
+- QDomElement item;
+- QString stars;
+- float rating = 0.0;
+-
+- // Use the first rating to appear in the xml, this should be
+- // the most important one.
+- //
+- // Averaging is not a good idea here, any subsequent ratings
+- // are likely to represent that days recommended programmes
+- // which on a bad night could given to an average programme.
+- // In the case of uk_rt it's not unknown for a recommendation
+- // to be given to programmes which are 'so bad, you have to
+- // watch!'
+- //
+- // XMLTV uses zero based ratings and signals no rating by absence.
+- // A rating from 1 to 5 is encoded as 0/4 to 4/4.
+- // MythTV uses zero to signal no rating!
+- // The same rating is encoded as 0.2 to 1.0 with steps of 0.2, it
+- // is not encoded as 0.0 to 1.0 with steps of 0.25 because
+- // 0 signals no rating!
+- // See http://xmltv.cvs.sourceforge.net/viewvc/xmltv/xmltv/xmltv.dtd?revision=1....
+- item = values.item(0).toElement();
+- if (!item.isNull())
++ if (!haveReadTV)
+ {
+- stars = getFirstText(item);
+- float num = stars.section('/', 0, 0).toFloat() + 1;
+- float den = stars.section('/', 1, 1).toFloat() + 1;
+- if (0.0F < den)
+- rating = num/den;
++ LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, no <tv> element found, at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
++ return false;
+ }
+
+- pginfo->m_stars = rating;
+- }
+- else if (info.tagName() == "rating")
+- {
+- // again, the structure of ratings seems poorly represented
+- // in the XML. no idea what we'd do with multiple values.
+- QDomNodeList values = info.elementsByTagName("value");
+- QDomElement item = values.item(0).toElement();
+- if (item.isNull())
+- continue;
+- EventRating rating;
+- rating.m_system = info.attribute("system", "");
+- rating.m_rating = getFirstText(item);
+- pginfo->m_ratings.append(rating);
+- }
+- else if (info.tagName() == "previously-shown")
+- {
+- pginfo->m_previouslyshown = true;
++ QString programid, season, episode, totalepisodes;
++ auto *pginfo = new ProgInfo();
+
+- QString prevdate = info.attribute("start");
+- if (!prevdate.isEmpty())
+- {
+- QDateTime date;
+- fromXMLTVDate(prevdate, date);
+- pginfo->m_originalairdate = date.date();
+- }
+- }
+- else if (info.tagName() == "credits")
+- {
+- parseCredits(info, pginfo);
+- }
+- else if (info.tagName() == "subtitles")
+- {
+- if (info.attribute("type") == "teletext")
+- pginfo->m_subtitleType |= SUB_NORMAL;
+- else if (info.attribute("type") == "onscreen")
+- pginfo->m_subtitleType |= SUB_ONSCREEN;
+- else if (info.attribute("type") == "deaf-signed")
+- pginfo->m_subtitleType |= SUB_SIGNED;
+- }
+- else if (info.tagName() == "audio")
+- {
+- parseAudio(info, pginfo);
+- }
+- else if (info.tagName() == "video")
+- {
+- parseVideo(info, pginfo);
+- }
+- else if (info.tagName() == "episode-num")
+- {
+- if (info.attribute("system") == "dd_progid")
++ QString text = xml.attributes().value("start").toString();
++ fromXMLTVDate(text, pginfo->m_starttime);
++ pginfo->m_startts = text;
++
++ text = xml.attributes().value("stop").toString();
++ //not a mandatory attribute according to XMLTV DTD https://github.com/XMLTV/xmltv/blob/master/xmltv.dtd
++ fromXMLTVDate(text, pginfo->m_endtime);
++ pginfo->m_endts = text;
++
++ text = xml.attributes().value("channel").toString();
++ QStringList split = text.split(" ");
++ pginfo->m_channel = split[0];
++
++ text = xml.attributes().value("clumpidx").toString();
++ if (!text.isEmpty())
+ {
+- QString episodenum(getFirstText(info));
+- // if this field includes a dot, strip it out
+- int idx = episodenum.indexOf('.');
+- if (idx != -1)
+- episodenum.remove(idx, 1);
+- programid = episodenum;
+- /* Only EPisodes and SHows are part of a series for SD */
+- if (programid.startsWith(QString("EP")) ||
+- programid.startsWith(QString("SH")))
+- pginfo->m_seriesId = QString("EP") + programid.mid(2,8);
++ split = text.split('/');
++ pginfo->m_clumpidx = split[0];
++ pginfo->m_clumpmax = split[1];
+ }
+- else if (info.attribute("system") == "xmltv_ns")
++
++ do
+ {
+- QString episodenum(getFirstText(info));
+- episode = episodenum.section('.',1,1);
+- totalepisodes = episode.section('/',1,1).trimmed();
+- episode = episode.section('/',0,0).trimmed();
+- season = episodenum.section('.',0,0).trimmed();
+- season = season.section('/',0,0).trimmed();
+- QString part(episodenum.section('.',2,2));
+- QString partnumber(part.section('/',0,0).trimmed());
+- QString parttotal(part.section('/',1,1).trimmed());
+-
+- pginfo->m_categoryType = ProgramInfo::kCategorySeries;
+-
+- if (!season.isEmpty())
++ if (!readNextWithErrorCheck(xml))
++ return false;
++ if (xml.name() == "title")
+ {
+- int tmp = season.toUInt() + 1;
+- pginfo->m_season = tmp;
+- season = QString::number(tmp);
+- pginfo->m_syndicatedepisodenumber = 'S' + season;
++ QString text2=xml.readElementText(QXmlStreamReader::SkipChildElements);
++ if (xml.attributes().value("lang").toString() == "ja_JP")
++ {
++ pginfo->m_title = text2;
++ }
++ else if (xml.attributes().value("lang").toString() == "ja_JP@kana")
++ {
++ pginfo->m_title_pronounce = text2;
++ }
++ else if (pginfo->m_title.isEmpty())
++ {
++ pginfo->m_title = text2;
++ }
+ }
+-
+- if (!episode.isEmpty())
++ else if (xml.name() == "sub-title" && pginfo->m_subtitle.isEmpty())
+ {
+- int tmp = episode.toUInt() + 1;
+- pginfo->m_episode = tmp;
+- episode = QString::number(tmp);
+- pginfo->m_syndicatedepisodenumber.append('E' + episode);
++ pginfo->m_subtitle = xml.readElementText(QXmlStreamReader::SkipChildElements);
+ }
+-
+- if (!totalepisodes.isEmpty())
++ else if (xml.name() == "subtitles")
++ {
++ if (xml.attributes().value("type").toString() == "teletext")
++ pginfo->m_subtitleType |= SUB_NORMAL;
++ else if (xml.attributes().value("type").toString() == "onscreen")
++ pginfo->m_subtitleType |= SUB_ONSCREEN;
++ else if (xml.attributes().value("type").toString() == "deaf-signed")
++ pginfo->m_subtitleType |= SUB_SIGNED;
++ }
++ else if (xml.name() == "desc" && pginfo->m_description.isEmpty())
+ {
+- pginfo->m_totalepisodes = totalepisodes.toUInt();
++ pginfo->m_description = xml.readElementText(QXmlStreamReader::SkipChildElements);
+ }
++ else if (xml.name() == "category")
++ {
++ const QString cat = xml.readElementText(QXmlStreamReader::SkipChildElements);
+
+- uint partno = 0;
+- if (!partnumber.isEmpty())
++ if (ProgramInfo::kCategoryNone == pginfo->m_categoryType && string_to_myth_category_type(cat) != ProgramInfo::kCategoryNone)
++ {
++ pginfo->m_categoryType = string_to_myth_category_type(cat);
++ }
++ else if (pginfo->m_category.isEmpty())
++ {
++ pginfo->m_category = cat;
++ }
++ if ((cat.compare(QObject::tr("movie"),Qt::CaseInsensitive) == 0) || (cat.compare(QObject::tr("film"),Qt::CaseInsensitive) == 0))
++ {
++ // Hack for tv_grab_uk_rt
++ pginfo->m_categoryType = ProgramInfo::kCategoryMovie;
++ }
++ pginfo->m_genres.append(cat);
++ }
++ else if (xml.name() == "date" && (pginfo->m_airdate == 0U))
++ {
++ // Movie production year
++ QString date = xml.readElementText(QXmlStreamReader::SkipChildElements);
++ pginfo->m_airdate = date.left(4).toUInt();
++ }
++ else if (xml.name() == "star-rating")
+ {
+- bool ok = false;
+- partno = partnumber.toUInt(&ok) + 1;
+- partno = (ok) ? partno : 0;
++ QString stars;
++ float rating = 0.0;
++
++ // Use the first rating to appear in the xml, this should be
++ // the most important one.
++ //
++ // Averaging is not a good idea here, any subsequent ratings
++ // are likely to represent that days recommended programmes
++ // which on a bad night could given to an average programme.
++ // In the case of uk_rt it's not unknown for a recommendation
++ // to be given to programmes which are 'so bad, you have to
++ // watch!'
++ //
++ // XMLTV uses zero based ratings and signals no rating by absence.
++ // A rating from 1 to 5 is encoded as 0/4 to 4/4.
++ // MythTV uses zero to signal no rating!
++ // The same rating is encoded as 0.2 to 1.0 with steps of 0.2, it
++ // is not encoded as 0.0 to 1.0 with steps of 0.25 because
++ // 0 signals no rating!
++ // See http://xmltv.cvs.sourceforge.net/viewvc/xmltv/xmltv/xmltv.dtd?revision=1....
++ stars = "0"; //no rating
++ do
++ {
++ if (!readNextWithErrorCheck(xml))
++ return false;
++ if (xml.isStartElement())
++ {
++ if (xml.name() == "value")
++ {
++ stars=xml.readElementText(QXmlStreamReader::SkipChildElements);
++ }
++ }
++ }
++ while (! (xml.isEndElement() && xml.name() == "star-rating"));
++ if (pginfo->m_stars == 0.0F)
++ {
++ float num = stars.section('/', 0, 0).toFloat() + 1;
++ float den = stars.section('/', 1, 1).toFloat() + 1;
++ if (0.0F < den)
++ rating = num/den;
++ }
++ pginfo->m_stars = rating;
+ }
++ else if (xml.name() == "rating")
++ {
++ // again, the structure of ratings seems poorly represented
++ // in the XML. no idea what we'd do with multiple values.
++ QString rat;
++ QString rating_system = xml.attributes().value("system").toString();
++ if (rating_system == NULL)
++ rating_system = "";
++
++ do
++ {
++ if (!readNextWithErrorCheck(xml))
++ return false;
++ if (xml.isStartElement())
++ {
++ if (xml.name() == "value")
++ {
++ rat=xml.readElementText(QXmlStreamReader::SkipChildElements);
++ }
++ }
++ }
++ while (! (xml.isEndElement() && xml.name() == "rating"));
+
+- if (!parttotal.isEmpty() && partno > 0)
++ if (!rat.isEmpty())
++ {
++ EventRating rating;
++ rating.m_system = rating_system;
++ rating.m_rating = rat;
++ pginfo->m_ratings.append(rating);
++ }
++ }
++ else if (xml.name() == "previously-shown")
+ {
+- bool ok = false;
+- uint partto = parttotal.toUInt(&ok);
+- if (ok && partnumber <= parttotal)
++ pginfo->m_previouslyshown = true;
++ QString prevdate = xml.attributes().value( "start").toString();
++ if (!prevdate.isEmpty())
+ {
+- pginfo->m_parttotal = partto;
+- pginfo->m_partnumber = partno;
++ QDateTime date;
++ fromXMLTVDate(prevdate, date);
++ pginfo->m_originalairdate = date.date();
+ }
+ }
+- }
+- else if (info.attribute("system") == "onscreen")
+- {
+- pginfo->m_categoryType = ProgramInfo::kCategorySeries;
+- if (pginfo->m_subtitle.isEmpty())
++ else if (xml.name() == "credits")
+ {
+- pginfo->m_subtitle = getFirstText(info);
++ do
++ {
++ if (!readNextWithErrorCheck(xml))
++ return false;
++ if (xml.isStartElement())
++ {
++ QString tagname=xml.name().toString();
++ QString text2=xml.readElementText(QXmlStreamReader::SkipChildElements);
++ pginfo->AddPerson(tagname, text2);
++ }
++ }
++ while (! (xml.isEndElement() && xml.name() == "credits"));
+ }
+- }
+- else if ((info.attribute("system") == "themoviedb.org") &&
+- (m_movieGrabberPath.endsWith(QString("/tmdb3.py"))))
+- {
+- /* text is movie/<inetref> */
+- QString inetrefRaw(getFirstText(info));
+- if (inetrefRaw.startsWith(QString("movie/"))) {
+- QString inetref(QString ("tmdb3.py_") + inetrefRaw.section('/',1,1).trimmed());
+- pginfo->m_inetref = inetref;
++ else if (xml.name() == "audio")
++ {
++ do
++ {
++ if (!readNextWithErrorCheck(xml))
++ return false;
++ if (xml.isStartElement())
++ {
++ if (xml.name() == "stereo")
++ {
++ QString text2=xml.readElementText(QXmlStreamReader::SkipChildElements);
++ if (text2 == "mono")
++ {
++ pginfo->m_audioProps |= AUD_MONO;
++ }
++ else if (text2 == "stereo")
++ {
++ pginfo->m_audioProps |= AUD_STEREO;
++ }
++ else if (text2 == "dolby" || text2 == "dolby digital")
++ {
++ pginfo->m_audioProps |= AUD_DOLBY;
++ }
++ else if (text2 == "surround")
++ {
++ pginfo->m_audioProps |= AUD_SURROUND;
++ }
++ }
++ }
++ }
++ while (! (xml.isEndElement() && xml.name() == "audio"));
+ }
+- }
+- else if ((info.attribute("system") == "thetvdb.com") &&
+- (m_tvGrabberPath.endsWith(QString("/ttvdb.py"))))
+- {
+- /* text is series/<inetref> */
+- QString inetrefRaw(getFirstText(info));
+- if (inetrefRaw.startsWith(QString("series/"))) {
+- QString inetref(QString ("ttvdb.py_") + inetrefRaw.section('/',1,1).trimmed());
+- pginfo->m_inetref = inetref;
+- /* ProgInfo does not have a collectionref, so we don't set any */
++ else if (xml.name() == "video")
++ {
++ do
++ {
++ if (!readNextWithErrorCheck(xml))
++ return false;
++ if (xml.isStartElement())
++ {
++ if (xml.name() == "quality")
++ {
++ if (xml.readElementText(QXmlStreamReader::SkipChildElements) == "HDTV")
++ pginfo->m_videoProps |= VID_HDTV;
++ }
++ else if (xml.name() == "aspect")
++ {
++ if (xml.readElementText(QXmlStreamReader::SkipChildElements) == "16:9")
++ pginfo->m_videoProps |= VID_WIDESCREEN;
++ }
++ }
++ }
++ while (! (xml.isEndElement() && xml.name() == "video"));
+ }
++ else if (xml.name() == "episode-num")
++ {
++ QString system = xml.attributes().value( "system").toString();
++ if (system == "dd_progid")
++ {
++ QString episodenum(xml.readElementText(QXmlStreamReader::SkipChildElements));
++ // if this field includes a dot, strip it out
++ int idx = episodenum.indexOf('.');
++ if (idx != -1)
++ episodenum.remove(idx, 1);
++ programid = episodenum;
++ // Only EPisodes and SHows are part of a series for SD
++ if (programid.startsWith(QString("EP")) ||
++ programid.startsWith(QString("SH")))
++ pginfo->m_seriesId = QString("EP") + programid.mid(2,8);
++ }
++ else if (system == "xmltv_ns")
++ {
++ QString episodenum(xml.readElementText(QXmlStreamReader::SkipChildElements));
++ episode = episodenum.section('.',1,1);
++ totalepisodes = episode.section('/',1,1).trimmed();
++ episode = episode.section('/',0,0).trimmed();
++ season = episodenum.section('.',0,0).trimmed();
++ season = season.section('/',0,0).trimmed();
++ QString part(episodenum.section('.',2,2));
++ QString partnumber(part.section('/',0,0).trimmed());
++ QString parttotal(part.section('/',1,1).trimmed());
++ pginfo->m_categoryType = ProgramInfo::kCategorySeries;
++ if (!season.isEmpty())
++ {
++ int tmp = season.toUInt() + 1;
++ pginfo->m_season = tmp;
++ season = QString::number(tmp);
++ pginfo->m_syndicatedepisodenumber = 'S' + season;
++ }
++ if (!episode.isEmpty())
++ {
++ int tmp = episode.toUInt() + 1;
++ pginfo->m_episode = tmp;
++ episode = QString::number(tmp);
++ pginfo->m_syndicatedepisodenumber.append('E' + episode);
++ }
++ if (!totalepisodes.isEmpty())
++ {
++ pginfo->m_totalepisodes = totalepisodes.toUInt();
++ }
++ uint partno = 0;
++ if (!partnumber.isEmpty())
++ {
++ bool ok = false;
++ partno = partnumber.toUInt(&ok) + 1;
++ partno = (ok) ? partno : 0;
++ }
++ if (!parttotal.isEmpty() && partno > 0)
++ {
++ bool ok = false;
++ uint partto = parttotal.toUInt(&ok);
++ if (ok && partnumber <= parttotal)
++ {
++ pginfo->m_parttotal = partto;
++ pginfo->m_partnumber = partno;
++ }
++ }
++ }
++ else if (system == "onscreen")
++ {
++ pginfo->m_categoryType = ProgramInfo::kCategorySeries;
++ if (pginfo->m_subtitle.isEmpty())
++ {
++ pginfo->m_subtitle = xml.readElementText(QXmlStreamReader::SkipChildElements);
++ }
++ }
++ else if ((system == "themoviedb.org") && (m_movieGrabberPath.endsWith(QString("/tmdb3.py"))))
++ {
++ // text is movie/<inetref>
++ QString inetrefRaw(xml.readElementText(QXmlStreamReader::SkipChildElements));
++ if (inetrefRaw.startsWith(QString("movie/")))
++ {
++ QString inetref(QString ("tmdb3.py_") + inetrefRaw.section('/',1,1).trimmed());
++ pginfo->m_inetref = inetref;
++ }
++ }
++ else if ((system == "thetvdb.com") && (m_tvGrabberPath.endsWith(QString("/ttvdb.py"))))
++ {
++ // text is series/<inetref>
++ QString inetrefRaw(xml.readElementText(QXmlStreamReader::SkipChildElements));
++ if (inetrefRaw.startsWith(QString("series/")))
++ {
++ QString inetref(QString ("ttvdb.py_") + inetrefRaw.section('/',1,1).trimmed());
++ pginfo->m_inetref = inetref;
++ // ProgInfo does not have a collectionref, so we don't set any
++ }
++ }
++ }//episode-num
+ }
+- }
+- }
+- }
+-
+- if (pginfo->m_category.isEmpty() &&
+- pginfo->m_categoryType != ProgramInfo::kCategoryNone)
+- pginfo->m_category = myth_category_type_to_string(pginfo->m_categoryType);
+-
+- if (!pginfo->m_airdate
+- && ProgramInfo::kCategorySeries != pginfo->m_categoryType)
+- pginfo->m_airdate = m_currentYear;
+-
+- if (programid.isEmpty())
+- {
+-
+- /* Let's build ourself a programid */
++ while (! (xml.isEndElement() && xml.name() == "programme"));
+
+- if (ProgramInfo::kCategoryMovie == pginfo->m_categoryType)
+- programid = "MV";
+- else if (ProgramInfo::kCategorySeries == pginfo->m_categoryType)
+- programid = "EP";
+- else if (ProgramInfo::kCategorySports == pginfo->m_categoryType)
+- programid = "SP";
+- else
+- programid = "SH";
++ if (pginfo->m_category.isEmpty() && pginfo->m_categoryType != ProgramInfo::kCategoryNone)
++ pginfo->m_category = myth_category_type_to_string(pginfo->m_categoryType);
+
+- QString seriesid = QString::number(ELFHash(pginfo->m_title.toUtf8()));
+- pginfo->m_seriesId = seriesid;
+- programid.append(seriesid);
++ if (!pginfo->m_airdate && ProgramInfo::kCategorySeries != pginfo->m_categoryType)
++ pginfo->m_airdate = m_currentYear;
+
+- if (!episode.isEmpty() && !season.isEmpty())
+- {
+- /* Append unpadded episode and season number to the seriesid (to
+- maintain consistency with historical encoding), but limit the
+- season number representation to a single base-36 character to
+- ensure unique programid generation. */
+- int season_int = season.toInt();
+- if (season_int > 35)
+- {
+- // Cannot represent season as a single base-36 character, so
+- // remove the programid and fall back to normal dup matching.
+- if (ProgramInfo::kCategoryMovie != pginfo->m_categoryType)
+- programid.clear();
+- }
+- else
+- {
+- programid.append(episode);
+- programid.append(QString::number(season_int, 36));
+- if (pginfo->m_partnumber && pginfo->m_parttotal)
++ if (programid.isEmpty())
+ {
+- programid += QString::number(pginfo->m_partnumber);
+- programid += QString::number(pginfo->m_parttotal);
+- }
+- }
+- }
+- else
+- {
+- /* No ep/season info? Well then remove the programid and rely on
+- normal dupchecking methods instead. */
+- if (ProgramInfo::kCategoryMovie != pginfo->m_categoryType)
+- programid.clear();
+- }
+- }
+-
+- pginfo->m_programId = programid;
+-
+- return pginfo;
+-}
+-
+-bool XMLTVParser::parseFile(
+- const QString& filename, ChannelInfoList *chanlist,
+- QMap<QString, QList<ProgInfo> > *proglist)
+-{
+- QDomDocument doc;
+- QFile f;
+-
+- if (!dash_open(f, filename, QIODevice::ReadOnly))
+- {
+- LOG(VB_GENERAL, LOG_ERR,
+- QString("Error unable to open '%1' for reading.") .arg(filename));
+- return false;
+- }
+-
+- QString errorMsg = "unknown";
+- int errorLine = 0;
+- int errorColumn = 0;
+-
+- if (!doc.setContent(&f, &errorMsg, &errorLine, &errorColumn))
+- {
+- LOG(VB_GENERAL, LOG_ERR, QString("Error in %1:%2: %3")
+- .arg(errorLine).arg(errorColumn).arg(errorMsg));
+-
+- f.close();
+- return true;
+- }
+-
+- f.close();
+-
+- QDomElement docElem = doc.documentElement();
+-
+- QUrl baseUrl(docElem.attribute("source-data-url", ""));
+- //QUrl sourceUrl(docElem.attribute("source-info-url", ""));
++ //Let's build ourself a programid
++ if (ProgramInfo::kCategoryMovie == pginfo->m_categoryType)
++ programid = "MV";
++ else if (ProgramInfo::kCategorySeries == pginfo->m_categoryType)
++ programid = "EP";
++ else if (ProgramInfo::kCategorySports == pginfo->m_categoryType)
++ programid = "SP";
++ else
++ programid = "SH";
+
+- QString aggregatedTitle;
+- QString aggregatedDesc;
+-
+- QDomNode n = docElem.firstChild();
+- while (!n.isNull())
+- {
+- QDomElement e = n.toElement();
+- if (!e.isNull())
+- {
+- if (e.tagName() == "channel")
+- {
+- ChannelInfo *chinfo = parseChannel(e, baseUrl);
+- if (!chinfo->m_xmltvId.isEmpty())
+- chanlist->push_back(*chinfo);
+- delete chinfo;
+- }
+- else if (e.tagName() == "programme")
+- {
+- ProgInfo *pginfo = parseProgram(e);
++ QString seriesid = QString::number(ELFHash(pginfo->m_title.toUtf8()));
++ pginfo->m_seriesId = seriesid;
++ programid.append(seriesid);
+
++ if (!episode.isEmpty() && !season.isEmpty())
++ {
++ /* Append unpadded episode and season number to the seriesid (to
++ maintain consistency with historical encoding), but limit the
++ season number representation to a single base-36 character to
++ ensure unique programid generation. */
++ int season_int = season.toInt();
++ if (season_int > 35)
++ {
++ // Cannot represent season as a single base-36 character, so
++ // remove the programid and fall back to normal dup matching.
++ if (ProgramInfo::kCategoryMovie != pginfo->m_categoryType)
++ programid.clear();
++ }
++ else
++ {
++ programid.append(episode);
++ programid.append(QString::number(season_int, 36));
++ if (pginfo->m_partnumber && pginfo->m_parttotal)
++ {
++ programid += QString::number(pginfo->m_partnumber);
++ programid += QString::number(pginfo->m_parttotal);
++ }
++ }
++ }
++ else
++ {
++ /* No ep/season info? Well then remove the programid and rely on
++ normal dupchecking methods instead. */
++ if (ProgramInfo::kCategoryMovie != pginfo->m_categoryType)
++ programid.clear();
++ }
++ }
++ pginfo->m_programId = programid;
+ if (!(pginfo->m_starttime.isValid()))
+ {
+- LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
+- "invalid start time, "
+- "skipping")
+- .arg(pginfo->m_title));
++ LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), " "invalid start time, " "skipping").arg(pginfo->m_title));
+ }
+ else if (pginfo->m_channel.isEmpty())
+ {
+- LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
+- "missing channel, "
+- "skipping")
+- .arg(pginfo->m_title));
++ LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), " "missing channel, " "skipping").arg(pginfo->m_title));
+ }
+ else if (pginfo->m_startts == pginfo->m_endts)
+ {
+- LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), "
+- "identical start and end "
+- "times, skipping")
+- .arg(pginfo->m_title));
++ LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), " "identical start and end " "times, skipping").arg(pginfo->m_title));
+ }
+ else
+ {
++ // so we have a (relatively) clean program element now, which is good enough to process or to store
++ if (pginfo->m_channel != last_channel) {
++ //we have a channel change here
++ last_channel = pginfo->m_channel;
++ last_starttime = QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0)); //initialize it to a time far, far away ...
++ }
++ else {
++ //we are still on the same channel
++ if (pginfo->m_starttime >= last_starttime) {
++ last_starttime = pginfo->m_starttime;
++ }
++ else {
++ LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, program out of order at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
++ return false;
++ }
++ }
++
+ if (pginfo->m_clumpidx.isEmpty())
+ (*proglist)[pginfo->m_channel].push_back(*pginfo);
+ else
+@@ -737,22 +729,19 @@ bool XMLTVParser::parseFile(
+ aggregatedTitle.clear();
+ aggregatedDesc.clear();
+ }
+-
+ if (!pginfo->m_title.isEmpty())
+ {
+ if (!aggregatedTitle.isEmpty())
+ aggregatedTitle.append(" | ");
+ aggregatedTitle.append(pginfo->m_title);
+ }
+-
+ if (!pginfo->m_description.isEmpty())
+ {
+ if (!aggregatedDesc.isEmpty())
+ aggregatedDesc.append(" | ");
+ aggregatedDesc.append(pginfo->m_description);
+ }
+- if (pginfo->m_clumpidx.toInt() ==
+- pginfo->m_clumpmax.toInt() - 1)
++ if (pginfo->m_clumpidx.toInt() == pginfo->m_clumpmax.toInt() - 1)
+ {
+ pginfo->m_title = aggregatedTitle;
+ pginfo->m_description = aggregatedDesc;
+@@ -761,10 +750,16 @@ bool XMLTVParser::parseFile(
+ }
+ }
+ delete pginfo;
+- }
+- }
+- n = n.nextSibling();
++ }//if programme
++ }//if readNextStartElement
++ }//while loop
++ if (! (xml.isEndElement() && xml.name() == "tv"))
++ {
++ LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, missing </tv> element, at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
++ return false;
+ }
++ //TODO add code for adding data on the run
++ f.close();
+
+ return true;
+ }
+diff --git a/mythtv/programs/mythfilldatabase/xmltvparser.h b/mythtv/programs/mythfilldatabase/xmltvparser.h
+index 6d06aefe405..9fad22dc1e4 100644
+--- a/mythtv/programs/mythfilldatabase/xmltvparser.h
++++ b/mythtv/programs/mythfilldatabase/xmltvparser.h
+@@ -17,10 +17,6 @@ class XMLTVParser
+ {
+ public:
+ XMLTVParser();
+- void lateInit();
+-
+- static ChannelInfo *parseChannel(QDomElement &element, QUrl &baseUrl);
+- ProgInfo *parseProgram(QDomElement &element);
+ bool parseFile(const QString& filename, ChannelInfoList *chanlist,
+ QMap<QString, QList<ProgInfo> > *proglist);
+
+
+From a2b8c262dc96274ef55be25c510a4bbe9b6b52b2 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Tue, 3 Mar 2020 11:31:18 +0000
+Subject: [PATCH 038/165] mythfilldatabase: Fix 2 potential leaks
+
+- introduced in a9aa006139da24cb and picked up by coverity.
+
+(cherry picked from commit cf282591a249864215c82cb7248153b3033d6ea1)
+---
+ mythtv/programs/mythfilldatabase/xmltvparser.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/mythtv/programs/mythfilldatabase/xmltvparser.cpp b/mythtv/programs/mythfilldatabase/xmltvparser.cpp
+index 66157ffc648..a875020462b 100644
+--- a/mythtv/programs/mythfilldatabase/xmltvparser.cpp
++++ b/mythtv/programs/mythfilldatabase/xmltvparser.cpp
+@@ -236,7 +236,10 @@ bool XMLTVParser::parseFile(
+ do
+ {
+ if (!readNextWithErrorCheck(xml))
++ {
++ delete chaninfo;
+ return false;
++ }
+ if (xml.name() == "icon")
+ {
+ if (chaninfo->m_icon.isEmpty())
+@@ -715,6 +718,7 @@ bool XMLTVParser::parseFile(
+ }
+ else {
+ LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, program out of order at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
++ delete pginfo;
+ return false;
+ }
+ }
+
+From c8e779649384e9810bf8b2262a8f928b1986994d Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Fri, 6 Mar 2020 18:19:45 +0100
+Subject: [PATCH 039/165] BackendServerAddr and MasterServerName replaced
+ MasterServerIP
+
+in V30, according ticket #13024.
+
+Replace any occurrences of the deprecated settings and allow
+'BackendServerAddr(MasterServerName)' to be an alias for the
+host-ip as well.
+
+Fixes #13593
+
+(cherry picked from commit 105faee393b682e79a336d7ee893f82f8c10a896)
+---
+ mythtv/bindings/python/MythTV/database.py | 12 +---
+ mythtv/bindings/python/MythTV/methodheap.py | 42 ++++++++------
+ mythtv/bindings/python/MythTV/mythproto.py | 55 ++++++++++---------
+ .../python/MythTV/utility/__init__.py | 3 +-
+ .../bindings/python/MythTV/utility/other.py | 9 +++
+ 5 files changed, 66 insertions(+), 55 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/database.py b/mythtv/bindings/python/MythTV/database.py
+index 7e3c4fe42be..e8b8bf5d52d 100644
+--- a/mythtv/bindings/python/MythTV/database.py
++++ b/mythtv/bindings/python/MythTV/database.py
+@@ -1338,15 +1338,7 @@ def _check_schema(self, value, local, name='Database', update=None):
+
+ def _gethostfromaddr(self, addr, value=None):
+ if value is None:
+- for value in ['BackendServerAddr']:
+- try:
+- return self._gethostfromaddr(addr, value)
+- except MythDBError:
+- pass
+- else:
+- raise MythDBError(MythError.DB_SETTING,
+- 'BackendServerAddr', addr)
+-
++ value = 'BackendServerAddr'
+ with self as cursor:
+ if cursor.execute("""SELECT hostname FROM settings
+ WHERE value=? AND data=?""", [value, addr]) == 0:
+@@ -1360,7 +1352,7 @@ def gethostname(self):
+ return self.dbconfig.profile
+
+ def getMasterBackend(self):
+- return self._gethostfromaddr(self.settings.NULL.MasterServerIP)
++ return self.settings.NULL.MasterServerName
+
+ def getStorageGroup(self, groupname=None, hostname=None):
+ """
+diff --git a/mythtv/bindings/python/MythTV/methodheap.py b/mythtv/bindings/python/MythTV/methodheap.py
+index 04c7e98bb48..404864618c9 100644
+--- a/mythtv/bindings/python/MythTV/methodheap.py
++++ b/mythtv/bindings/python/MythTV/methodheap.py
+@@ -8,7 +8,7 @@
+ from MythTV.exceptions import *
+ from MythTV.logging import MythLog
+ from MythTV.connections import FEConnection, XMLConnection, BEEventConnection
+-from MythTV.utility import databaseSearch, datetime, check_ipv6, _donothing
++from MythTV.utility import databaseSearch, datetime, check_ipv6, _donothing, resolve_ip
+ from MythTV.database import DBCache, DBData
+ from MythTV.system import SystemEvent
+ from MythTV.mythproto import BECache, FileOps, Program, FreeSpace, EventLock
+@@ -1131,6 +1131,8 @@ def scanStorageGroups(self, delete=True):
+ class MythXML( XMLConnection ):
+ """
+ Provides convenient methods to access the backend XML server.
++ Parameter 'backend' is either a hostname from 'settings',
++ an ip address or a hostname in ip-notation.
+ """
+ def __init__(self, backend=None, port=None, db=None):
+ if backend and port:
+@@ -1142,24 +1144,28 @@ def __init__(self, backend=None, port=None, db=None):
+ self.log = MythLog('Python XML Connection')
+ if backend is None:
+ # use master backend
+- backend = self.db.settings.NULL.MasterServerIP
+- if re.match(r'(?:\d{1,3}\.){3}\d{1,3}',backend) or \
+- check_ipv6(backend):
+- # process ip address
+- host = self.db._gethostfromaddr(backend)
+- self.host = backend
+- self.port = int(self.db.settings[host].BackendStatusPort)
++ backend = self.db.getMasterBackend()
++
++ # assume hostname from settings
++ host = self.db._getpreferredaddr(backend)
++ if host:
++ port = int(self.db.settings[backend].BackendStatusPort)
+ else:
+- # assume given a hostname
+- self.host = backend
+- self.port = int(self.db.settings[self.host].BackendStatusPort)
+- if not self.port:
+- # try a truncated hostname
+- self.host = backend.split('.')[0]
+- self.port = int(self.db.setting[self.host].BackendStatusPort)
+- if not self.port:
+- raise MythDBError(MythError.DB_SETTING,
+- backend+': BackendStatusPort')
++ # assume ip address
++ hostname = self.db._gethostfromaddr(backend)
++ host = backend
++ port = int(self.db.settings[hostname].BackendStatusPort)
++
++ # resolve ip address from name
++ reshost, resport = resolve_ip(host,port)
++ if not reshost:
++ raise MythDBError(MythError.DB_SETTING,
++ backend+': BackendServerAddr')
++ if not resport:
++ raise MythDBError(MythError.DB_SETTING,
++ backend+': BackendStatusPort')
++ self.host = host
++ self.port = port
+
+ def getHosts(self):
+ """Returns a list of unique hostnames found in the settings table."""
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py b/mythtv/bindings/python/MythTV/mythproto.py
+index b388e9619ff..6b00ba222cb 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -12,7 +12,7 @@
+ from MythTV.connections import BEConnection, BEEventConnection
+ from MythTV.database import DBCache
+ from MythTV.utility import CMPRecord, datetime, ParseEnum, \
+- CopyData, CopyData2, check_ipv6, py23_repr
++ CopyData, CopyData2, check_ipv6, py23_repr, resolve_ip
+
+ from datetime import date
+ from time import sleep
+@@ -75,32 +75,33 @@ def __init__(self, backend=None, blockshutdown=False, events=False, db=None):
+ self.receiveevents = events
+
+ if backend is None:
+- # no backend given, use master
+- self.host = self.db.settings.NULL.MasterServerIP
+- self.hostname = self.db._gethostfromaddr(self.host)
+-
++ # use master backend
++ backend = self.db.getMasterBackend()
+ else:
+ backend = backend.strip('[]')
+- if self._reip.match(backend):
+- # given backend is IP address
+- self.host = backend
+- self.hostname = self.db._gethostfromaddr(
+- backend, 'BackendServerAddr')
+- elif check_ipv6(backend):
+- # given backend is IPv6 address
+- self.host = backend
+- self.hostname = self.db._gethostfromaddr(
+- backend, 'BackendServerAddr')
+- else:
+- # given backend is hostname, pull address from database
+- self.hostname = backend
+- self.host = self.db._getpreferredaddr(backend)
+
+- # lookup port from database
+- self.port = int(self.db.settings[self.hostname].BackendServerPort)
+- if not self.port:
+- raise MythDBError(MythError.DB_SETTING, 'BackendServerPort',
+- self.port)
++ # assume backend is hostname from settings
++ host = self.db._getpreferredaddr(backend)
++ if host:
++ port = int(self.db.settings[backend].BackendServerPort)
++ self.hostname = backend
++ else:
++ # assume ip address
++ self.hostname = self.db._gethostfromaddr(backend)
++ host = backend
++ port = int(self.db.settings[self.hostname].BackendServerPort)
++
++ # resolve ip address from name
++ reshost, resport = resolve_ip(host,port)
++ if not reshost:
++ raise MythDBError(MythError.DB_SETTING,
++ backend+': BackendServerAddr')
++ if not resport:
++ raise MythDBError(MythError.DB_SETTING,
++ backend+': BackendServerPort')
++
++ self.host = host
++ self.port = port
+
+ self._ident = '%s:%d' % (self.host, self.port)
+ if self._ident in self._shared:
+@@ -241,9 +242,11 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+ else:
+ raise MythError('Invalid FileTransfer input string: '+file)
+
+- # get full system name
++ # prefer hostname from settings over IP address
+ host = host.strip('[]')
+- if reip.match(host) or check_ipv6(host):
++ if ( not db._getpreferredaddr(host) and \
++ resolve_ip(host, None)[0] ):
++ # host is either IPv4, IPv6 or an (aliased) name
+ host = db._gethostfromaddr(host)
+
+ # select the correct transfer function:
+diff --git a/mythtv/bindings/python/MythTV/utility/__init__.py b/mythtv/bindings/python/MythTV/utility/__init__.py
+index 4f8d060a23f..1ca7087d7b2 100644
+--- a/mythtv/bindings/python/MythTV/utility/__init__.py
++++ b/mythtv/bindings/python/MythTV/utility/__init__.py
+@@ -7,5 +7,6 @@
+
+ from .other import _donothing, SchemaUpdate, databaseSearch, deadlinesocket, \
+ MARKUPLIST, levenshtein, ParseEnum, ParseSet, CopyData, \
+- CopyData2, check_ipv6, QuickProperty, py23_str, py23_repr
++ CopyData2, check_ipv6, QuickProperty, py23_str, py23_repr, \
++ resolve_ip
+
+diff --git a/mythtv/bindings/python/MythTV/utility/other.py b/mythtv/bindings/python/MythTV/utility/other.py
+index bb8f29630dc..7f5b0c759c8 100644
+--- a/mythtv/bindings/python/MythTV/utility/other.py
++++ b/mythtv/bindings/python/MythTV/utility/other.py
+@@ -576,6 +576,15 @@ def check_ipv6(n):
+ except socket.error:
+ return False
+
++def resolve_ip(host, port):
++ try:
++ res = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)[0]
++ # (family, socktype, proto, canonname, sockaddr)
++ af, socktype, proto, canonname, sa = res
++ return(sa[0], sa[1])
++ except:
++ return (None, None)
++
+ def py23_str(value, ignore_errors=False):
+ error_methods = ('strict', 'ignore')
+ error_method = error_methods[ignore_errors]
+
+From ce23a0225fcec2afbdfe5a7e82170e28f406c830 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sun, 10 May 2020 12:22:25 +0200
+Subject: [PATCH 040/165] Fix mysql cursor class to handle bytearrays
+
+Newer Python MySQLdb modules call 'cursor.execute()' multiple times
+from 'cursor.executemany()'.
+With python3 and python3-MySQLdb > 1.4.0 these call-backs containing
+a query are bytearrays, resulting in a traceback in the '_sanitize' method.
+
+Note: This '_sanitize' method is only needed when creating a query within
+the Python Bindings, but not necessary when python-mysqldb itself calls the
+cursor.execute() method.
+
+Fixes #13614
+
+(cherry picked from commit b2e9c6a44233570704554894bf45e01bfa8e26a7)
+---
+ mythtv/bindings/python/MythTV/_conn_mysqldb.py | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/_conn_mysqldb.py b/mythtv/bindings/python/MythTV/_conn_mysqldb.py
+index 177a880a121..3f798198219 100644
+--- a/mythtv/bindings/python/MythTV/_conn_mysqldb.py
++++ b/mythtv/bindings/python/MythTV/_conn_mysqldb.py
+@@ -41,7 +41,11 @@ def __init__(self, connection):
+ def _ping121(self): self._get_db().ping()
+ def _ping122(self): self._get_db().ping(True)
+
+- def _sanitize(self, query): return query.replace('?', '%s')
++ def _sanitize(self, query):
++ if isinstance(query, bytearray):
++ # MySQLdb calls execute() as bytearrays, already sanitized
++ return query
++ return query.replace('?', '%s')
+
+ def log_query(self, query, args):
+ if isinstance(query, bytearray):
+
+From 6a5afb4dba08fcd790279af97348f3e69ebec8c3 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Tue, 19 May 2020 18:08:50 +0100
+Subject: [PATCH 041/165] VDPAU: Try and fall 'back' to H264 Main support
+
+- if H264 constrained baseline is reported as not supported
+- this mimics the additional test carried out in FFmpeg
+- N.B. untested but as far as I can tell this should get this profile
+working again on older chipsets.
+
+(cherry picked from commit 7eb2231803ec9851d91bc7461c184079c65385d5)
+---
+ .../libs/libmythtv/decoders/mythvdpauhelper.cpp | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+index b8d3b2d1ef2..47b4a927bed 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+@@ -84,6 +84,23 @@ bool MythVDPAUHelper::ProfileCheck(VdpDecoderProfile Profile, uint32_t &Level,
+ status = m_vdpDecoderQueryCapabilities(m_device, Profile, &supported,
+ &Level, &Macros, &Width, &Height);
+ CHECK_ST
++
++ if (((supported != VDP_TRUE) || (status != VDP_STATUS_OK)) &&
++ (Profile == VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE))
++ {
++ // H264 Constrained baseline is reported as not supported on older chipsets but
++ // works due to support for H264 Main. Test for H264 main if constrained baseline
++ // fails - which mimics the fallback in FFmpeg.
++ status = m_vdpDecoderQueryCapabilities(m_device, VDP_DECODER_PROFILE_H264_MAIN, &supported,
++ &Level, &Macros, &Width, &Height);
++ if (supported > 0)
++ {
++ LOG(VB_GENERAL, LOG_INFO, LOC + "Driver does not report support for H264 Constrained Baseline");
++ LOG(VB_GENERAL, LOG_INFO, LOC + " - but assuming available as H264 Main is supported");
++ }
++ CHECK_ST
++ }
++
+ return supported > 0;
+ }
+
+
+From 7a31a2e35ccf338952f377d2885eb3af81defb54 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Tue, 12 May 2020 18:51:52 -0600
+Subject: [PATCH 042/165] mythexternrecorder: Add ondatastart command option
+
+The config file can now specify a command to run as soon as data is detected
+from the 'external' application.
+
+(cherry picked from commit e4d9172d6e1e636fd61e1484f858cb4010380b50)
+---
+ .../mythexternrecorder/MythExternControl.cpp | 7 ++++
+ .../mythexternrecorder/MythExternControl.h | 2 ++
+ .../mythexternrecorder/MythExternRecApp.cpp | 32 +++++++++++++++++++
+ .../mythexternrecorder/MythExternRecApp.h | 2 ++
+ mythtv/programs/mythexternrecorder/main.cpp | 2 ++
+ 5 files changed, 45 insertions(+)
+
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.cpp b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+index 258dc64dc57..a29efa17a99 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+@@ -513,6 +513,13 @@ bool Buffer::Fill(const QByteArray & buffer)
+ static int s_droppedBytes = 0;
+
+ m_parent->m_flow_mutex.lock();
++
++ if (!m_dataSeen)
++ {
++ m_dataSeen = true;
++ emit m_parent->DataStarted();
++ }
++
+ if (m_data.size() < MAX_QUEUE)
+ {
+ block_t blk(reinterpret_cast<const uint8_t *>(buffer.constData()),
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.h b/mythtv/programs/mythexternrecorder/MythExternControl.h
+index 172bf5bc1f7..57d26465fcd 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.h
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.h
+@@ -66,6 +66,7 @@ class Buffer : QObject
+ std::thread m_thread;
+
+ stack_t m_data;
++ bool m_dataSeen {false};
+
+ std::chrono::time_point<std::chrono::system_clock> m_heartbeat;
+ };
+@@ -152,6 +153,7 @@ class MythExternControl : public QObject
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
+ void Cleanup(void);
++ void DataStarted(void);
+
+ public slots:
+ void SetDescription(const QString & desc) { m_desc = desc; }
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 5a8acb28619..29bc0fd2bd9 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -87,6 +87,7 @@ bool MythExternRecApp::config(void)
+ m_recDesc = settings.value("RECORDER/desc").toString();
+ m_cleanup = settings.value("RECORDER/cleanup").toString();
+ m_tuneCommand = settings.value("TUNER/command", "").toString();
++ m_onDataStart = settings.value("TUNER/ondatastart", "").toString();
+ m_channelsIni = settings.value("TUNER/channels", "").toString();
+ m_lockTimeout = settings.value("TUNER/timeout", "").toInt();
+ m_scanCommand = settings.value("SCANNER/command", "").toString();
+@@ -297,6 +298,37 @@ Q_SLOT void MythExternRecApp::Cleanup(void)
+ LOG(VB_RECORD, LOG_INFO, LOC + ": Cleanup finished.");
+ }
+
++Q_SLOT void MythExternRecApp::DataStarted(void)
++{
++ if (m_onDataStart.isEmpty())
++ return;
++
++ QString cmd = m_onDataStart;
++
++ LOG(VB_RECORD, LOG_INFO, LOC +
++ QString(" Data started, finishing tune: '%1'").arg(cmd));
++
++ QProcess finish;
++ finish.start(cmd);
++ if (!finish.waitForStarted())
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to finish tune process: "
++ + ENO);
++ return;
++ }
++ finish.waitForFinished(5000);
++ if (finish.state() == QProcess::NotRunning)
++ {
++ if (finish.exitStatus() != QProcess::NormalExit)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": Finish tune failed: " + ENO);
++ return;
++ }
++ }
++
++ LOG(VB_RECORD, LOG_INFO, LOC + ": tunning finished.");
++}
++
+ Q_SLOT void MythExternRecApp::LoadChannels(const QString & serial)
+ {
+ if (m_channelsIni.isEmpty())
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.h b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+index af87e21e272..63f549e8836 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+@@ -70,6 +70,7 @@ class MythExternRecApp : public QObject
+ void LockTimeout(const QString & serial);
+ void HasTuner(const QString & serial);
+ void Cleanup(void);
++ void DataStarted(void);
+ void LoadChannels(const QString & serial);
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
+@@ -108,6 +109,7 @@ class MythExternRecApp : public QObject
+
+ QProcess m_tuneProc;
+ QString m_tuneCommand;
++ QString m_onDataStart;
+ QString m_channelsIni;
+ uint m_lockTimeout { 0 };
+
+diff --git a/mythtv/programs/mythexternrecorder/main.cpp b/mythtv/programs/mythexternrecorder/main.cpp
+index 833ecb8a8c6..7bbff574f27 100644
+--- a/mythtv/programs/mythexternrecorder/main.cpp
++++ b/mythtv/programs/mythexternrecorder/main.cpp
+@@ -114,6 +114,8 @@ int main(int argc, char *argv[])
+ process, &MythExternRecApp::HasTuner);
+ QObject::connect(control, &MythExternControl::Cleanup,
+ process, &MythExternRecApp::Cleanup);
++ QObject::connect(control, &MythExternControl::DataStarted,
++ process, &MythExternRecApp::DataStarted);
+ QObject::connect(control, &MythExternControl::LoadChannels,
+ process, &MythExternRecApp::LoadChannels);
+ QObject::connect(control, &MythExternControl::FirstChannel,
+
+From fa2165511fe0735c612be69445b6acafc05e4caa Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sun, 17 May 2020 20:58:41 -0600
+Subject: [PATCH 043/165] mythexternrecorder: Add TUNER/newepisodecommand
+ option.
+
+Some streaming sources have a "bandwidth saver" option and therefore need
+touched occationally to keep them going. If provided, this command will be
+executed whenever a new 'episode' starts up if the recording is already
+running on the desired "channel".
+
+(cherry picked from commit 07b49fc2546bbc4b4d7a85ffb4943d65a1986fbb)
+---
+ .../mythexternrecorder/MythExternRecApp.cpp | 34 +++++++++++++++++++
+ .../mythexternrecorder/MythExternRecApp.h | 2 ++
+ 2 files changed, 36 insertions(+)
+
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 29bc0fd2bd9..6ce2d9919cf 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -87,6 +87,7 @@ bool MythExternRecApp::config(void)
+ m_recDesc = settings.value("RECORDER/desc").toString();
+ m_cleanup = settings.value("RECORDER/cleanup").toString();
+ m_tuneCommand = settings.value("TUNER/command", "").toString();
++ m_newEpisodeCommand = settings.value("TUNER/newepisodecommand", "").toString();
+ m_onDataStart = settings.value("TUNER/ondatastart", "").toString();
+ m_channelsIni = settings.value("TUNER/channels", "").toString();
+ m_lockTimeout = settings.value("TUNER/timeout", "").toInt();
+@@ -452,6 +453,36 @@ Q_SLOT void MythExternRecApp::NextChannel(const QString & serial)
+ GetChannel(serial, "NextChannel");
+ }
+
++void MythExternRecApp::NewEpisodeStarting(const QString & channum)
++{
++ QString cmd = m_newEpisodeCommand;
++ cmd.replace("%CHANNUM%", channum);
++
++ LOG(VB_RECORD, LOG_WARNING, LOC +
++ QString(" New episode starting on current channel: '%1'").arg(cmd));
++
++ QProcess proc;
++ proc.start(cmd);
++ if (!proc.waitForStarted())
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ " NewEpisodeStarting: Failed to start process: " + ENO);
++ return;
++ }
++ proc.waitForFinished(5000);
++ if (proc.state() == QProcess::NotRunning)
++ {
++ if (proc.exitStatus() != QProcess::NormalExit)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ " NewEpisodeStarting: process failed: " + ENO);
++ return;
++ }
++ }
++
++ LOG(VB_RECORD, LOG_INFO, LOC + "NewEpisodeStarting: finished.");
++}
++
+ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ const QString & channum)
+ {
+@@ -464,6 +495,9 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+
+ if (m_tunedChannel == channum)
+ {
++ if (!m_newEpisodeCommand.isEmpty())
++ NewEpisodeStarting(channum);
++
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+ QString("TuneChanne: Already on %1").arg(channum));
+ emit SendMessage("TuneChannel", serial,
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.h b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+index 63f549e8836..5d105691e26 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+@@ -75,6 +75,7 @@ class MythExternRecApp : public QObject
+ void FirstChannel(const QString & serial);
+ void NextChannel(const QString & serial);
+
++ void NewEpisodeStarting(const QString & channum);
+ void TuneChannel(const QString & serial, const QString & channum);
+ void TuneStatus(const QString & serial);
+ void HasPictureAttributes(const QString & serial);
+@@ -110,6 +111,7 @@ class MythExternRecApp : public QObject
+ QProcess m_tuneProc;
+ QString m_tuneCommand;
+ QString m_onDataStart;
++ QString m_newEpisodeCommand;
+ QString m_channelsIni;
+ uint m_lockTimeout { 0 };
+
+
+From f9baf09e4397032f1a00d98b5085f912547380b6 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sun, 17 May 2020 19:57:18 +0200
+Subject: [PATCH 044/165] Crash of backend on delete of program being recorded
+
+Fix this crash and similar backend crashes in the scheduler
+by replacing all iterations over m_tvList/m_encoderLink/m_pEncoders
+from using the Qt extension foreach to the C++11 range-based for loop.
+The foreach apparently makes a deep copy of the container thereby
+invalidating the iterators that may be active on the same container
+simultaneously in a different thread.
+As an additional safeguard the qAsConst, a Qt-specific variant
+of std:as_const, is added to all loops.
+
+Fixes #13571
+
+(cherry picked from commit 8e2872679315547abc3c2f1a91e0f7b8baac79dc)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/programs/mythbackend/httpstatus.cpp | 2 +-
+ mythtv/programs/mythbackend/mainserver.cpp | 22 ++++++++-------
+ mythtv/programs/mythbackend/scheduler.cpp | 28 ++++++++++----------
+ mythtv/programs/mythbackend/services/dvr.cpp | 2 +-
+ 4 files changed, 28 insertions(+), 26 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/httpstatus.cpp b/mythtv/programs/mythbackend/httpstatus.cpp
+index 011c7f6ee1f..1dc6e6f038b 100644
+--- a/mythtv/programs/mythbackend/httpstatus.cpp
++++ b/mythtv/programs/mythbackend/httpstatus.cpp
+@@ -197,7 +197,7 @@ void HttpStatus::FillStatusXML( QDomDocument *pDoc )
+
+ TVRec::s_inputsLock.lockForRead();
+
+- foreach (auto elink, *m_pEncoders)
++ for (auto * elink : qAsConst(*m_pEncoders))
+ {
+ if (elink != nullptr)
+ {
+diff --git a/mythtv/programs/mythbackend/mainserver.cpp b/mythtv/programs/mythbackend/mainserver.cpp
+index 9d627c663b5..072b1fb8887 100644
+--- a/mythtv/programs/mythbackend/mainserver.cpp
++++ b/mythtv/programs/mythbackend/mainserver.cpp
+@@ -1863,7 +1863,7 @@ void MainServer::HandleAnnounce(QStringList &slist, QStringList commands,
+
+ bool wasAsleep = true;
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto elink, *m_encoderList)
++ for (auto * elink : qAsConst(*m_encoderList))
+ {
+ if (elink->GetHostName() == commands[2])
+ {
+@@ -2930,6 +2930,8 @@ void MainServer::DoHandleStopRecording(
+ if (m_sched)
+ m_sched->UpdateRecStatus(&recinfo);
+ }
++
++ break;
+ }
+ }
+ TVRec::s_inputsLock.unlock();
+@@ -4238,7 +4240,7 @@ void MainServer::HandleLockTuner(PlaybackSock *pbs, int cardid)
+ QString enchost;
+
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto elink, *m_encoderList)
++ for (auto * elink : qAsConst(*m_encoderList))
+ {
+ // we're looking for a specific card but this isn't the one we want
+ if ((cardid != -1) && (cardid != elink->GetInputID()))
+@@ -4363,7 +4365,7 @@ void MainServer::HandleGetFreeInputInfo(PlaybackSock *pbs,
+ // Lopp over each encoder and divide the inputs into busy and free
+ // lists.
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto elink, *m_encoderList)
++ for (auto * elink : qAsConst(*m_encoderList))
+ {
+ InputInfo info;
+ info.m_inputId = elink->GetInputID();
+@@ -4895,7 +4897,7 @@ void MainServer::HandleSetChannelInfo(QStringList &slist, PlaybackSock *pbs)
+ }
+
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto & encoder, *m_encoderList)
++ for (auto * encoder : qAsConst(*m_encoderList))
+ {
+ if (encoder)
+ {
+@@ -5091,7 +5093,7 @@ size_t MainServer::GetCurrentMaxBitrate(void)
+ size_t totalKBperMin = 0;
+
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto enc, *m_encoderList)
++ for (auto * enc : qAsConst(*m_encoderList))
+ {
+ if (!enc->IsConnected() || !enc->IsBusy())
+ continue;
+@@ -7328,7 +7330,7 @@ void MainServer::HandleIsRecording(QStringList &slist, PlaybackSock *pbs)
+ QStringList retlist;
+
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto elink, *m_encoderList)
++ for (auto * elink : qAsConst(*m_encoderList))
+ {
+ if (elink->IsBusyRecording()) {
+ RecordingsInProgress++;
+@@ -7793,7 +7795,7 @@ void MainServer::connectionClosed(MythSocket *socket)
+
+ bool isFallingAsleep = true;
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto elink, *m_encoderList)
++ for (auto * elink : qAsConst(*m_encoderList))
+ {
+ if (elink->GetSocket() == pbs)
+ {
+@@ -7834,7 +7836,7 @@ void MainServer::connectionClosed(MythSocket *socket)
+ if (chain->HostSocketCount() == 0)
+ {
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto enc, *m_encoderList)
++ for (auto * enc : qAsConst(*m_encoderList))
+ {
+ if (enc->IsLocal())
+ {
+@@ -8181,7 +8183,7 @@ void MainServer::reconnectTimeout(void)
+ QStringList strlist( str );
+
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto elink, *m_encoderList)
++ for (auto * elink : qAsConst(*m_encoderList))
+ {
+ elink->CancelNextRecording(true);
+ ProgramInfo *pinfo = elink->GetRecording();
+@@ -8354,7 +8356,7 @@ void MainServer::UpdateSystemdStatus (void)
+ {
+ int active = 0;
+ TVRec::s_inputsLock.lockForRead();
+- foreach (auto elink, *m_encoderList)
++ for (auto * elink : qAsConst(*m_encoderList))
+ {
+ if (not elink->IsLocal())
+ continue;
+diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
+index 42d8e1c8494..b787be27ce3 100644
+--- a/mythtv/programs/mythbackend/scheduler.cpp
++++ b/mythtv/programs/mythbackend/scheduler.cpp
+@@ -2514,7 +2514,7 @@ void Scheduler::HandleWakeSlave(RecordingInfo &ri, int prerollseconds)
+ QReadLocker tvlocker(&TVRec::s_inputsLock);
+
+ QMap<int, EncoderLink*>::const_iterator tvit = m_tvList->constFind(ri.GetInputID());
+- if (tvit == m_tvList->end())
++ if (tvit == m_tvList->constEnd())
+ return;
+
+ QString sysEventKey = ri.MakeUniqueKey();
+@@ -2597,7 +2597,7 @@ void Scheduler::HandleWakeSlave(RecordingInfo &ri, int prerollseconds)
+ "to reschedule around its tuners.")
+ .arg(nexttv->GetHostName()));
+
+- foreach (auto & enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (enc->GetHostName() == nexttv->GetHostName())
+ enc->SetSleepStatus(sStatus_Undefined);
+@@ -2672,7 +2672,7 @@ bool Scheduler::HandleRecording(
+ QReadLocker tvlocker(&TVRec::s_inputsLock);
+
+ QMap<int, EncoderLink*>::const_iterator tvit = m_tvList->constFind(ri.GetInputID());
+- if (tvit == m_tvList->end())
++ if (tvit == m_tvList->constEnd())
+ {
+ QString msg = QString("Invalid cardid [%1] for %2")
+ .arg(ri.GetInputID()).arg(ri.GetTitle());
+@@ -2754,7 +2754,7 @@ bool Scheduler::HandleRecording(
+ "to reschedule around its tuners.")
+ .arg(nexttv->GetHostName()));
+
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (enc->GetHostName() == nexttv->GetHostName())
+ enc->SetSleepStatus(sStatus_Undefined);
+@@ -3442,7 +3442,7 @@ void Scheduler::PutInactiveSlavesToSleep(void)
+ QReadLocker tvlocker(&TVRec::s_inputsLock);
+
+ bool someSlavesCanSleep = false;
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (enc->CanSleep())
+ someSlavesCanSleep = true;
+@@ -3524,7 +3524,7 @@ void Scheduler::PutInactiveSlavesToSleep(void)
+ "be inactive for the next %1 minutes and can be put to sleep.")
+ .arg(sleepThreshold / 60));
+
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if ((!enc->IsLocal()) &&
+ (enc->IsAwake()) &&
+@@ -3548,7 +3548,7 @@ void Scheduler::PutInactiveSlavesToSleep(void)
+
+ if (enc->GoToSleep())
+ {
+- foreach (auto slv, *m_tvList)
++ for (auto * slv : qAsConst(*m_tvList))
+ {
+ if (slv->GetHostName() == thisHost)
+ {
+@@ -3566,7 +3566,7 @@ void Scheduler::PutInactiveSlavesToSleep(void)
+ LOG(VB_GENERAL, LOG_ERR, LOC +
+ QString("Unable to shutdown %1 slave backend, setting "
+ "sleep status to undefined.").arg(thisHost));
+- foreach (auto slv, *m_tvList)
++ for (auto * slv : qAsConst(*m_tvList))
+ {
+ if (slv->GetHostName() == thisHost)
+ slv->SetSleepStatus(sStatus_Undefined);
+@@ -3596,7 +3596,7 @@ bool Scheduler::WakeUpSlave(const QString& slaveHostname, bool setWakingStatus)
+ QString("Trying to Wake Up %1, but this slave "
+ "does not have a WakeUpCommand set.").arg(slaveHostname));
+
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (enc->GetHostName() == slaveHostname)
+ enc->SetSleepStatus(sStatus_Undefined);
+@@ -3606,7 +3606,7 @@ bool Scheduler::WakeUpSlave(const QString& slaveHostname, bool setWakingStatus)
+ }
+
+ QDateTime curtime = MythDate::current();
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (setWakingStatus && (enc->GetHostName() == slaveHostname))
+ enc->SetSleepStatus(sStatus_Waking);
+@@ -3630,7 +3630,7 @@ void Scheduler::WakeUpSlaves(void)
+
+ QStringList SlavesThatCanWake;
+ QString thisSlave;
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (enc->IsLocal())
+ continue;
+@@ -4304,7 +4304,7 @@ void Scheduler::AddNewRecords(void)
+ RecList tmpList;
+
+ QMap<int, bool> cardMap;
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (enc->IsConnected() || enc->IsAsleep())
+ cardMap[enc->GetInputID()] = true;
+@@ -5438,7 +5438,7 @@ int Scheduler::FillRecordingDir(
+ ProgramInfo *programinfo = expire;
+ bool foundSlave = false;
+
+- foreach (auto & enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (enc->GetHostName() ==
+ programinfo->GetHostname())
+@@ -5596,7 +5596,7 @@ void Scheduler::SchedLiveTV(void)
+ return;
+
+ // Build a list of active livetv programs
+- foreach (auto enc, *m_tvList)
++ for (auto * enc : qAsConst(*m_tvList))
+ {
+ if (kState_WatchingLiveTV != enc->GetState())
+ continue;
+diff --git a/mythtv/programs/mythbackend/services/dvr.cpp b/mythtv/programs/mythbackend/services/dvr.cpp
+index 07eb8e74cc5..d5b2702877f 100644
+--- a/mythtv/programs/mythbackend/services/dvr.cpp
++++ b/mythtv/programs/mythbackend/services/dvr.cpp
+@@ -680,7 +680,7 @@ DTC::EncoderList* Dvr::GetEncoderList()
+
+ QReadLocker tvlocker(&TVRec::s_inputsLock);
+ QList<InputInfo> inputInfoList = CardUtil::GetAllInputInfo();
+- foreach (auto elink, tvList)
++ for (auto * elink : qAsConst(tvList))
+ {
+ if (elink != nullptr)
+ {
+
+From 1ca7a4b09ef38cd6e108a26bdc358f280d6ae3d3 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Thu, 21 May 2020 16:27:21 -0500
+Subject: [PATCH 045/165] Python Bindings: Services API, logging & XML
+ enhancements
+
+- Improve logging dump of 'postdata'
+- Add an option to return raw XML data, {'rawxml': True}
+
+Some lxml.etree functions, e.g. fromstring()/tostring() cause pylint 'I'
+messages. users may want to add the following to their .pylintrc (or just
+add # pylint: disable=c-extension-no-member inline):
+
+ [MESSAGE-CONTROL]
+ disable=c-extension-no-member
+
+Closes #13619
+
+Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+(cherry picked from commit 1a1b69836515a5ec77b09c4559a3bb729af9cd7b)
+---
+ .../bindings/python/MythTV/services_api/send.py | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/services_api/send.py b/mythtv/bindings/python/MythTV/services_api/send.py
+index 1f49389508f..fa817066f27 100644
+--- a/mythtv/bindings/python/MythTV/services_api/send.py
++++ b/mythtv/bindings/python/MythTV/services_api/send.py
+@@ -141,6 +141,10 @@ def send(self, endpoint='', postdata=None, rest='', opts=None):
+ its response in XML rather than JSON. Defaults to
+ False.
+
++ opts['rawxml']: If True, causes the backend to send it's response in
++ XML as bytes. This can be easily parsed by Python's
++ 'lxml.etree.fromstring()'. Defaults to False.
++
+ opts['wrmi']: If True and there is postdata, the URL is then sent to
+ the server.
+
+@@ -296,6 +300,9 @@ def send(self, endpoint='', postdata=None, rest='', opts=None):
+ if self.opts['usexml']:
+ return response.text
+
++ if self.opts['rawxml']:
++ return response.content
++
+ try:
+ return response.json()
+ except ValueError as err:
+@@ -320,7 +327,7 @@ def _set_missing_opts(self):
+ if not isinstance(self.opts, dict):
+ self.opts = {}
+
+- for option in ('noetag', 'nogzip', 'usexml', 'wrmi', 'wsdl'):
++ for option in ('noetag', 'nogzip', 'usexml', 'rawxml', 'wrmi', 'wsdl'):
+ try:
+ self.opts[option]
+ except (KeyError, TypeError):
+@@ -368,8 +375,8 @@ def _validate_postdata(self):
+ raise RuntimeError('usage: postdata must be passed as a dict')
+
+ self.logger.debug('The following postdata was included:')
+- for key in self.postdata:
+- self.logger.debug('%15s: %s', key, self.postdata[key])
++ for k, v in self.postdata.items():
++ self.logger.debug('%15s: %s', k, v)
+
+ if not self.opts['wrmi']:
+ raise RuntimeWarning('wrmi=False')
+@@ -396,7 +403,7 @@ def _create_session(self):
+ else:
+ self.session.headers.update({'Accept-Encoding': 'gzip,deflate'})
+
+- if self.opts['usexml']:
++ if self.opts['usexml'] or self.opts['rawxml']:
+ self.session.headers.update({'Accept': ''})
+ else:
+ self.session.headers.update({'Accept': 'application/json'})
+
+From 9380616198f4149d20bb5ef41c3ca0f14944290b Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Mon, 25 May 2020 11:52:11 -0500
+Subject: [PATCH 046/165] Always request a reschedule when running
+ mythfilldatabase
+
+Previous edits incorrectly left it where the request was only done
+when repeats were marked.
+
+Fixes #13625
+
+Signed-off-by: David Engel <dengel(a)mythtv.org>
+(cherry picked from commit 1ba15e5cdb91f121b60ae96358021d668cf73a71)
+---
+ mythtv/programs/mythfilldatabase/main.cpp | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/programs/mythfilldatabase/main.cpp b/mythtv/programs/mythfilldatabase/main.cpp
+index 3f764b16fb2..ef20217f1ce 100644
+--- a/mythtv/programs/mythfilldatabase/main.cpp
++++ b/mythtv/programs/mythfilldatabase/main.cpp
+@@ -660,9 +660,8 @@ int main(int argc, char *argv[])
+ "| the master backend is restarted. |\n"
+ "===============================================================");
+
+- if (mark_repeats)
+- ScheduledRecording::RescheduleMatch(0, 0, 0, QDateTime(),
+- "MythFillDatabase");
++ ScheduledRecording::RescheduleMatch(0, 0, 0, QDateTime(),
++ "MythFillDatabase");
+
+ gCoreContext->SendMessage("CLEAR_SETTINGS_CACHE");
+
+
+From fc9048228105e0bf416990f97c3ce3c2eceb3201 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Mon, 25 May 2020 12:02:17 -0500
+Subject: [PATCH 047/165] mythfilldatabase: remove program starttime order
+ check
+
+Closes #13623
+
+Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+(cherry picked from commit 1f8b759dd7b047bde1b5c52f2471c79cd1619e30)
+---
+ .../programs/mythfilldatabase/xmltvparser.cpp | 19 -------------------
+ 1 file changed, 19 deletions(-)
+
+diff --git a/mythtv/programs/mythfilldatabase/xmltvparser.cpp b/mythtv/programs/mythfilldatabase/xmltvparser.cpp
+index a875020462b..0024819bc3d 100644
+--- a/mythtv/programs/mythfilldatabase/xmltvparser.cpp
++++ b/mythtv/programs/mythfilldatabase/xmltvparser.cpp
+@@ -205,8 +205,6 @@ bool XMLTVParser::parseFile(
+ QString aggregatedTitle;
+ QString aggregatedDesc;
+ bool haveReadTV = false;
+- QString last_channel = ""; //xmltvId of the last program element we read
+- QDateTime last_starttime; //starttime of the last program element we read
+ while (!xml.atEnd() && !xml.hasError() && (! (xml.isEndElement() && xml.name() == "tv")))
+ {
+ if (xml.readNextStartElement())
+@@ -706,23 +704,6 @@ bool XMLTVParser::parseFile(
+ else
+ {
+ // so we have a (relatively) clean program element now, which is good enough to process or to store
+- if (pginfo->m_channel != last_channel) {
+- //we have a channel change here
+- last_channel = pginfo->m_channel;
+- last_starttime = QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0)); //initialize it to a time far, far away ...
+- }
+- else {
+- //we are still on the same channel
+- if (pginfo->m_starttime >= last_starttime) {
+- last_starttime = pginfo->m_starttime;
+- }
+- else {
+- LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, program out of order at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
+- delete pginfo;
+- return false;
+- }
+- }
+-
+ if (pginfo->m_clumpidx.isEmpty())
+ (*proglist)[pginfo->m_channel].push_back(*pginfo);
+ else
+
+From c89a7e3771ed094662710fdb18fe972d09a55955 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 1 Jun 2020 18:26:44 +0100
+Subject: [PATCH 048/165] MythDisplay: Track device pixel ratio
+
+(cherry picked from commit 907841a119d94edc4c66c1f1af1114c5ff012258)
+---
+ mythtv/libs/libmythui/mythdisplay.cpp | 14 +++++++++++++-
+ mythtv/libs/libmythui/mythdisplay.h | 2 ++
+ 2 files changed, 15 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythui/mythdisplay.cpp b/mythtv/libs/libmythui/mythdisplay.cpp
+index e3f1483a8c4..cf9b1d795c7 100644
+--- a/mythtv/libs/libmythui/mythdisplay.cpp
++++ b/mythtv/libs/libmythui/mythdisplay.cpp
+@@ -183,7 +183,10 @@ MythDisplay::MythDisplay()
+ m_screen = GetDesiredScreen();
+ DebugScreen(m_screen, "Using");
+ if (m_screen)
++ {
+ connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
++ connect(m_screen, &QScreen::physicalDotsPerInchChanged, this, &MythDisplay::PhysicalDPIChanged);
++ }
+
+ connect(qGuiApp, &QGuiApplication::screenRemoved, this, &MythDisplay::ScreenRemoved);
+ connect(qGuiApp, &QGuiApplication::screenAdded, this, &MythDisplay::ScreenAdded);
+@@ -389,10 +392,18 @@ void MythDisplay::ScreenChanged(QScreen *qScreen)
+ DebugScreen(qScreen, "Changed to");
+ m_screen = qScreen;
+ connect(m_screen, &QScreen::geometryChanged, this, &MythDisplay::GeometryChanged);
++ connect(m_screen, &QScreen::physicalDotsPerInchChanged, this, &MythDisplay::PhysicalDPIChanged);
+ Initialise();
+ emit CurrentScreenChanged(qScreen);
+ }
+
++void MythDisplay::PhysicalDPIChanged(qreal DPI)
++{
++ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio changed to %1")
++ .arg(DPI, 2, 'f', 2, '0'));
++ emit CurrentDPIChanged(DPI);
++}
++
+ void MythDisplay::PrimaryScreenChanged(QScreen* qScreen)
+ {
+ DebugScreen(qScreen, "New primary");
+@@ -475,7 +486,8 @@ void MythDisplay::DebugScreen(QScreen *qScreen, const QString &Message)
+
+ LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 screen '%2' %3")
+ .arg(Message).arg(qScreen->name()).arg(extra));
+-
++ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt screen pixel ratio: %1")
++ .arg(qScreen->devicePixelRatio(), 2, 'f', 2, '0'));
+ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Geometry: %1x%2+%3+%4 Size(Qt): %5mmx%6mm")
+ .arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top())
+ .arg(qScreen->physicalSize().width()).arg(qScreen->physicalSize().height()));
+diff --git a/mythtv/libs/libmythui/mythdisplay.h b/mythtv/libs/libmythui/mythdisplay.h
+index 62600435384..d3b2a97a210 100644
+--- a/mythtv/libs/libmythui/mythdisplay.h
++++ b/mythtv/libs/libmythui/mythdisplay.h
+@@ -57,10 +57,12 @@ class MUI_PUBLIC MythDisplay : public QObject, public ReferenceCounter
+ void ScreenAdded (QScreen *qScreen);
+ void ScreenRemoved (QScreen *qScreen);
+ void GeometryChanged (const QRect &Geometry);
++ void PhysicalDPIChanged (qreal DPI);
+
+ signals:
+ void CurrentScreenChanged (QScreen *qScreen);
+ void ScreenCountChanged (int Screens);
++ void CurrentDPIChanged (qreal DPI);
+
+ protected:
+ MythDisplay();
+
+From 8c1fd6bb95bfc554d66a444cbfb177a2cd58c485 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Sat, 6 Jun 2020 14:30:55 +0100
+Subject: [PATCH 049/165] VDPAU: Extend logging of profile check
+
+(cherry picked from commit 43293708579193ff23459feba159cb9fab5259d1)
+---
+ .../libs/libmythtv/decoders/mythvdpauhelper.cpp | 16 +++++++++-------
+ 1 file changed, 9 insertions(+), 7 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+index 47b4a927bed..bb5ef391406 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+@@ -80,28 +80,30 @@ bool MythVDPAUHelper::ProfileCheck(VdpDecoderProfile Profile, uint32_t &Level,
+ return false;
+
+ INIT_ST
+- VdpBool supported = 0;
++ VdpBool supported = VDP_FALSE;
+ status = m_vdpDecoderQueryCapabilities(m_device, Profile, &supported,
+ &Level, &Macros, &Width, &Height);
+ CHECK_ST
+
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("ProfileCheck: Prof %1 Supp %2 Level %3 Macros %4 Width %5 Height %6 Status %7")
++ .arg(Profile).arg(supported).arg(Level).arg(Macros).arg(Width).arg(Height).arg(status));
++
+ if (((supported != VDP_TRUE) || (status != VDP_STATUS_OK)) &&
+ (Profile == VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE))
+ {
++ LOG(VB_GENERAL, LOG_INFO, LOC + "Driver does not report support for H264 Constrained Baseline...");
++
+ // H264 Constrained baseline is reported as not supported on older chipsets but
+ // works due to support for H264 Main. Test for H264 main if constrained baseline
+ // fails - which mimics the fallback in FFmpeg.
+ status = m_vdpDecoderQueryCapabilities(m_device, VDP_DECODER_PROFILE_H264_MAIN, &supported,
+ &Level, &Macros, &Width, &Height);
+- if (supported > 0)
+- {
+- LOG(VB_GENERAL, LOG_INFO, LOC + "Driver does not report support for H264 Constrained Baseline");
+- LOG(VB_GENERAL, LOG_INFO, LOC + " - but assuming available as H264 Main is supported");
+- }
+ CHECK_ST
++ if (supported == VDP_TRUE)
++ LOG(VB_GENERAL, LOG_INFO, LOC + "... but assuming available as H264 Main is supported");
+ }
+
+- return supported > 0;
++ return supported == VDP_TRUE;
+ }
+
+ const VDPAUProfiles& MythVDPAUHelper::GetProfiles(void)
+
+From 672d45b7bd8f03514e9936a0abdcb5c6d17c3112 Mon Sep 17 00:00:00 2001
+From: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Sat, 6 Jun 2020 09:06:46 -0400
+Subject: [PATCH 050/165] Fix musicmetadata handling of compilations.
+
+ Fixes #13585
+ Closes #192
+
+Signed-off-by: David Hampton <mythtv(a)love2code.net>
+
+(cherry picked from commit 1236aef0ae520294446ed91d91ed0e988976e183)
+
+-----
+
+Squashed commit of the following:
+
+commit ca6ffb883c9f32ec8a7f1461a0b4d71914e3c210
+Author: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Mon Mar 16 20:18:29 2020 +0800
+
+ musicmetadata: check for empty field before dumping to db
+
+ This ensures all the fields are properly filled in. In particular it ensures
+ that non-compilation albums have the compilation artist filled in to match the
+ artist, otherwise they all end up with "Unknown Artist" which in turn means
+ that albums which happen to have the same title (e.g. "Greatest Hits") all get
+ lumped into one.
+
+commit 88418b6c7c400d04440eeeaf232f571a1463b09a
+Author: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Mon Mar 16 20:02:28 2020 +0800
+
+ musicmetadata: clear id fields when main field is set
+
+ ... otherwise they are never recalculated when the actual value changes.
+
+commit a0e93004c18f3a34c2c2d450af72366860a19b4e
+Author: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Sun Mar 15 15:35:32 2020 +0800
+
+ musicmetadata: Fully update music_albums, including name and artist
+
+ Since these may have changed.
+
+commit 30898722aebbcfbd9d28557fcdbf2324379db2b6
+Author: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Sun Mar 15 15:33:43 2020 +0800
+
+ musicmetadata: Do not call `ensureSortFields` after `checkEmptyFields`
+
+ The latter already calls the former right at the end.
+
+commit 2e4a0e93768142c05bcfcd1b58f1b7db7bbde609
+Author: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Fri Feb 28 07:15:41 2020 +0800
+
+ musicmetadata: ensure compilation artist id is always set
+
+ I was observing that I had one `music_albums` entry per track on each
+ compilation album after the album was scanned the second time (first time it
+ went in correctly).
+
+ The issue was that on reloading from the DB the field was not being initialised
+ so remained as `-1` when the entry came to be written back, which because the
+ field in the DB is `unsigned` ended up being stored as `0`, so when subsequent
+ lookups try to find the album it failed every time (since the 0 in the database
+ matches neither -1 nor the >0 correct value) and a fresh one is inserted for
+ every track.
+
+ Fix this by adding and using `{get,set}CompilationArtistId` corresponding to
+ the uses of `{get,set}ArtistId`. I broke out `getCompilationArtistId` from the
+ within exiting `getArtistId` implementation.
+
+commit 56506e477ceb815081ed05aea3ff6656413b592a
+Author: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Wed Feb 19 20:05:22 2020 +0800
+
+ metaioflacvorbis: Handle ALBUMARTIST as a fallback for COMPILATION_ARTIST
+
+ Although there is no real standard this is as described in
+ https://picard.musicbrainz.org/docs/mappings/ and what one gets by default
+ using the picard tool (as I do).
+
+commit 1e303b005e4b613e4b965b1a5cbdc830b64020c2
+Author: Ian Campbell <ijc(a)hellion.org.uk>
+Date: Thu Feb 27 20:00:05 2020 +0800
+
+ Support `mythutil --scanmusic --force` to ignore file timestamps
+
+ Useful after an upgrade (or while hacking) or if something else changed which
+ doesn't affect the timestamp of the file.
+---
+ .../libs/libmythmetadata/metaioflacvorbis.cpp | 10 +++++
+ .../libs/libmythmetadata/musicfilescanner.cpp | 17 +++++++-
+ .../libs/libmythmetadata/musicfilescanner.h | 4 +-
+ mythtv/libs/libmythmetadata/musicmetadata.cpp | 39 +++++++++++++++++--
+ mythtv/libs/libmythmetadata/musicmetadata.h | 12 +++++-
+ .../programs/mythutil/commandlineparser.cpp | 2 +
+ mythtv/programs/mythutil/musicmetautils.cpp | 4 +-
+ 7 files changed, 77 insertions(+), 11 deletions(-)
+
+diff --git a/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp b/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp
+index 142894835f6..b505fb6bb3b 100644
+--- a/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp
++++ b/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp
+@@ -125,6 +125,16 @@ MusicMetadata* MetaIOFLACVorbis::read(const QString &filename)
+ compilation = true;
+ }
+ }
++ else if (tag->contains("ALBUMARTIST"))
++ {
++ QString compilation_artist = TStringToQString(
++ tag->fieldListMap()["ALBUMARTIST"].toString()).trimmed();
++ if (compilation_artist != metadata->Artist())
++ {
++ metadata->setCompilationArtist(compilation_artist);
++ compilation = true;
++ }
++ }
+
+ if (!compilation && tag->contains("MUSICBRAINZ_ALBUMARTISTID"))
+ {
+diff --git a/mythtv/libs/libmythmetadata/musicfilescanner.cpp b/mythtv/libs/libmythmetadata/musicfilescanner.cpp
+index 8a43a3eee09..9b5e3212a48 100644
+--- a/mythtv/libs/libmythmetadata/musicfilescanner.cpp
++++ b/mythtv/libs/libmythmetadata/musicfilescanner.cpp
+@@ -13,7 +13,7 @@
+ #include <metaio.h>
+ #include <musicfilescanner.h>
+
+-MusicFileScanner::MusicFileScanner()
++MusicFileScanner::MusicFileScanner(bool force) : m_forceupdate{force}
+ {
+ MSqlQuery query(MSqlQuery::InitCon());
+
+@@ -318,6 +318,10 @@ void MusicFileScanner::AddFileToDB(const QString &filename, const QString &start
+ data->setAlbumId(m_albumid[album_cache_string]);
+ }
+
++ int caid = m_artistid[data->CompilationArtist().toLower()];
++ if (caid > 0)
++ data->setCompilationArtistId(caid);
++
+ int gid = m_genreid[data->Genre().toLower()];
+ if (gid > 0)
+ data->setGenreId(gid);
+@@ -329,6 +333,9 @@ void MusicFileScanner::AddFileToDB(const QString &filename, const QString &start
+ m_artistid[data->Artist().toLower()] =
+ data->getArtistId();
+
++ m_artistid[data->CompilationArtist().toLower()] =
++ data->getCompilationArtistId();
++
+ m_genreid[data->Genre().toLower()] =
+ data->getGenreId();
+
+@@ -599,6 +606,10 @@ void MusicFileScanner::UpdateFileInDB(const QString &filename, const QString &st
+ disk_meta->setAlbumId(m_albumid[album_cache_string]);
+ }
+
++ int caid = m_artistid[disk_meta->CompilationArtist().toLower()];
++ if (caid > 0)
++ disk_meta->setCompilationArtistId(caid);
++
+ int gid = m_genreid[disk_meta->Genre().toLower()];
+ if (gid > 0)
+ disk_meta->setGenreId(gid);
+@@ -613,6 +624,8 @@ void MusicFileScanner::UpdateFileInDB(const QString &filename, const QString &st
+ // Update the cache
+ m_artistid[disk_meta->Artist().toLower()]
+ = disk_meta->getArtistId();
++ m_artistid[disk_meta->CompilationArtist().toLower()]
++ = disk_meta->getCompilationArtistId();
+ m_genreid[disk_meta->Genre().toLower()]
+ = disk_meta->getGenreId();
+ album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
+@@ -803,7 +816,7 @@ void MusicFileScanner::ScanMusic(MusicLoadedMap &music_files)
+ {
+ if (music_files[name].location == MusicFileScanner::kDatabase)
+ continue;
+- if (HasFileChanged(name, query.value(1).toString()))
++ if (m_forceupdate || HasFileChanged(name, query.value(1).toString()))
+ music_files[name].location = MusicFileScanner::kNeedUpdate;
+ else
+ {
+diff --git a/mythtv/libs/libmythmetadata/musicfilescanner.h b/mythtv/libs/libmythmetadata/musicfilescanner.h
+index 5f2cae2ce13..8ceefdd189d 100644
+--- a/mythtv/libs/libmythmetadata/musicfilescanner.h
++++ b/mythtv/libs/libmythmetadata/musicfilescanner.h
+@@ -29,7 +29,7 @@ class META_PUBLIC MusicFileScanner
+
+ using MusicLoadedMap = QMap <QString, MusicFileData>;
+ public:
+- MusicFileScanner(void);
++ MusicFileScanner(bool force = false);
+ ~MusicFileScanner(void) = default;
+
+ void SearchDirs(const QStringList &dirList);
+@@ -69,6 +69,8 @@ class META_PUBLIC MusicFileScanner
+ uint m_coverartAdded {0};
+ uint m_coverartRemoved {0};
+ uint m_coverartUpdated {0};
++
++ bool m_forceupdate;
+ };
+
+ #endif // _MUSICFILESCANNER_H_
+diff --git a/mythtv/libs/libmythmetadata/musicmetadata.cpp b/mythtv/libs/libmythmetadata/musicmetadata.cpp
+index f5959d33973..bcf9d561f06 100644
+--- a/mythtv/libs/libmythmetadata/musicmetadata.cpp
++++ b/mythtv/libs/libmythmetadata/musicmetadata.cpp
+@@ -537,11 +537,20 @@ int MusicMetadata::getArtistId()
+ }
+ m_artistId = query.lastInsertId().toInt();
+ }
++ }
++
++ return m_artistId;
++}
++
++int MusicMetadata::getCompilationArtistId()
++{
++ if (m_compartistId < 0) {
++ MSqlQuery query(MSqlQuery::InitCon());
+
+ // Compilation Artist
+ if (m_artist == m_compilationArtist)
+ {
+- m_compartistId = m_artistId;
++ m_compartistId = getArtistId();
+ }
+ else
+ {
+@@ -572,7 +581,7 @@ int MusicMetadata::getArtistId()
+ }
+ }
+
+- return m_artistId;
++ return m_compartistId;
+ }
+
+ int MusicMetadata::getAlbumId()
+@@ -667,12 +676,17 @@ QString MusicMetadata::Url(uint index)
+
+ void MusicMetadata::dumpToDatabase()
+ {
++ checkEmptyFields();
++
+ if (m_directoryId < 0)
+ getDirectoryId();
+
+ if (m_artistId < 0)
+ getArtistId();
+
++ if (m_compartistId < 0)
++ getCompilationArtistId();
++
+ if (m_albumId < 0)
+ getAlbumId();
+
+@@ -764,10 +778,14 @@ void MusicMetadata::dumpToDatabase()
+ if (m_albumArt)
+ m_albumArt->dumpToDatabase();
+
+- // make sure the compilation flag is updated
+- query.prepare("UPDATE music_albums SET compilation = :COMPILATION, year = :YEAR "
++ // update the album
++ query.prepare("UPDATE music_albums SET album_name = :ALBUM_NAME, "
++ "artist_id = :COMP_ARTIST_ID, compilation = :COMPILATION, "
++ "year = :YEAR "
+ "WHERE music_albums.album_id = :ALBUMID");
+ query.bindValue(":ALBUMID", m_albumId);
++ query.bindValue(":ALBUM_NAME", m_album);
++ query.bindValue(":COMP_ARTIST_ID", m_compartistId);
+ query.bindValue(":COMPILATION", m_compilation);
+ query.bindValue(":YEAR", m_year);
+
+@@ -849,16 +867,28 @@ inline QString MusicMetadata::formatReplaceSymbols(const QString &format)
+ void MusicMetadata::checkEmptyFields()
+ {
+ if (m_artist.isEmpty())
++ {
+ m_artist = tr("Unknown Artist", "Default artist if no artist");
++ m_artistId = -1;
++ }
+ // This should be the same as Artist if it's a compilation track or blank
+ if (!m_compilation || m_compilationArtist.isEmpty())
++ {
+ m_compilationArtist = m_artist;
++ m_compartistId = -1;
++ }
+ if (m_album.isEmpty())
++ {
+ m_album = tr("Unknown Album", "Default album if no album");
++ m_albumId = -1;
++ }
+ if (m_title.isEmpty())
+ m_title = m_filename;
+ if (m_genre.isEmpty())
++ {
+ m_genre = tr("Unknown Genre", "Default genre if no genre");
++ m_genreId = -1;
++ }
+ ensureSortFields();
+ }
+
+@@ -1537,6 +1567,7 @@ void AllMusic::resync()
+
+ dbMeta->setDirectoryId(query.value(11).toInt());
+ dbMeta->setArtistId(query.value(1).toInt());
++ dbMeta->setCompilationArtistId(query.value(3).toInt());
+ dbMeta->setAlbumId(query.value(4).toInt());
+ dbMeta->setTrackCount(query.value(19).toInt());
+ dbMeta->setFileSize(query.value(20).toULongLong());
+diff --git a/mythtv/libs/libmythmetadata/musicmetadata.h b/mythtv/libs/libmythmetadata/musicmetadata.h
+index 9dec96b1f65..c554a0befec 100644
+--- a/mythtv/libs/libmythmetadata/musicmetadata.h
++++ b/mythtv/libs/libmythmetadata/musicmetadata.h
+@@ -108,7 +108,6 @@ class META_PUBLIC MusicMetadata
+ m_filename(std::move(lfilename))
+ {
+ checkEmptyFields();
+- ensureSortFields();
+ }
+
+ MusicMetadata(int lid, QString lbroadcaster, QString lchannel, QString ldescription, UrlList lurls, QString llogourl,
+@@ -130,6 +129,7 @@ class META_PUBLIC MusicMetadata
+ const QString &lartist_sort = nullptr)
+ {
+ m_artist = lartist;
++ m_artistId = -1;
+ m_artistSort = lartist_sort;
+ m_formattedArtist.clear(); m_formattedTitle.clear();
+ ensureSortFields();
+@@ -141,6 +141,7 @@ class META_PUBLIC MusicMetadata
+ const QString &lcompilation_artist_sort = nullptr)
+ {
+ m_compilationArtist = lcompilation_artist;
++ m_compartistId = -1;
+ m_compilationArtistSort = lcompilation_artist_sort;
+ m_formattedArtist.clear(); m_formattedTitle.clear();
+ ensureSortFields();
+@@ -152,6 +153,7 @@ class META_PUBLIC MusicMetadata
+ const QString &lalbum_sort = nullptr)
+ {
+ m_album = lalbum;
++ m_albumId = -1;
+ m_albumSort = lalbum_sort;
+ m_formattedArtist.clear(); m_formattedTitle.clear();
+ ensureSortFields();
+@@ -171,7 +173,10 @@ class META_PUBLIC MusicMetadata
+ QString FormatTitle();
+
+ QString Genre() const { return m_genre; }
+- void setGenre(const QString &lgenre) { m_genre = lgenre; }
++ void setGenre(const QString &lgenre) {
++ m_genre = lgenre;
++ m_genreId = -1;
++ }
+
+ void setDirectoryId(int ldirectoryid) { m_directoryId = ldirectoryid; }
+ int getDirectoryId();
+@@ -179,6 +184,9 @@ class META_PUBLIC MusicMetadata
+ void setArtistId(int lartistid) { m_artistId = lartistid; }
+ int getArtistId();
+
++ void setCompilationArtistId(int lartistid) { m_compartistId = lartistid; }
++ int getCompilationArtistId();
++
+ void setAlbumId(int lalbumid) { m_albumId = lalbumid; }
+ int getAlbumId();
+
+diff --git a/mythtv/programs/mythutil/commandlineparser.cpp b/mythtv/programs/mythutil/commandlineparser.cpp
+index d6c8e146581..9483adf58b8 100644
+--- a/mythtv/programs/mythutil/commandlineparser.cpp
++++ b/mythtv/programs/mythutil/commandlineparser.cpp
+@@ -232,6 +232,8 @@ void MythUtilCommandLineParser::LoadArguments(void)
+ ->SetChildOf("notification");
+
+ // musicmetautils.cpp
++ add("--force", "musicforce", false, "Ignore file timestamps", "")
++ ->SetChildOf("scanmusic");
+ add("--songid", "songid", "", "ID of track to update", "")
+ ->SetChildOf("updatemeta");
+ add("--title", "title", "", "(optional) Title of track", "")
+diff --git a/mythtv/programs/mythutil/musicmetautils.cpp b/mythtv/programs/mythutil/musicmetautils.cpp
+index 969b3908eb1..0b34b188aa5 100644
+--- a/mythtv/programs/mythutil/musicmetautils.cpp
++++ b/mythtv/programs/mythutil/musicmetautils.cpp
+@@ -178,9 +178,9 @@ static int ExtractImage(const MythUtilCommandLineParser &cmdline)
+ return GENERIC_EXIT_OK;
+ }
+
+-static int ScanMusic(const MythUtilCommandLineParser &/*cmdline*/)
++static int ScanMusic(const MythUtilCommandLineParser &cmdline)
+ {
+- auto *fscan = new MusicFileScanner();
++ auto *fscan = new MusicFileScanner(cmdline.toBool("musicforce"));
+ QStringList dirList;
+
+ if (!StorageGroup::FindDirs("Music", gCoreContext->GetHostName(), &dirList))
+
+From dd35db8df117b5f2d2c6706438dcf2e316c19d95 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Sat, 6 Jun 2020 22:09:21 +0100
+Subject: [PATCH 051/165] MythPlayer: Fix double rate CPU deinterlacing
+
+- the second field was not being processed as after the first pass the
+frame was marked 'already_deinterlaced'
+- was only obvious for yadif which broke av sync quite badly when
+running at double rate
+
+(cherry picked from commit 72c9209e158e3cba33f81515220af36e704bec66)
+---
+ mythtv/libs/libmythtv/mythdeinterlacer.cpp | 5 +++--
+ mythtv/libs/libmythtv/mythplayer.cpp | 4 ++++
+ 2 files changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/mythdeinterlacer.cpp b/mythtv/libs/libmythtv/mythdeinterlacer.cpp
+index 7c22a484d20..0b19b69b12f 100644
+--- a/mythtv/libs/libmythtv/mythdeinterlacer.cpp
++++ b/mythtv/libs/libmythtv/mythdeinterlacer.cpp
+@@ -70,13 +70,14 @@ void MythDeinterlacer::Filter(VideoFrame *Frame, FrameScanType Scan,
+ VideoDisplayProfile *Profile, bool Force)
+ {
+ // nothing to see here
+- if (!Frame || (Scan != kScan_Interlaced && Scan != kScan_Intr2ndField))
++
++ if (!Frame || !is_interlaced(Scan))
+ {
+ Cleanup();
+ return;
+ }
+
+- if (Frame && Frame->already_deinterlaced)
++ if (Frame->already_deinterlaced)
+ return;
+
+ // Sanity check frame format
+diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
+index ec0d58d4c2a..7865bab69af 100644
+--- a/mythtv/libs/libmythtv/mythplayer.cpp
++++ b/mythtv/libs/libmythtv/mythplayer.cpp
+@@ -1868,7 +1868,11 @@ void MythPlayer::AVSync(VideoFrame *buffer)
+ m_osdLock.lock();
+ // Only double rate CPU deinterlacers require an extra call to ProcessFrame
+ if (GetDoubleRateOption(buffer, DEINT_CPU) && !GetDoubleRateOption(buffer, DEINT_SHADER))
++ {
++ // the first deinterlacing pass will have marked the frame as already deinterlaced
++ buffer->already_deinterlaced = false;
+ m_videoOutput->ProcessFrame(buffer, m_osd, m_pipPlayers, ps);
++ }
+ m_videoOutput->PrepareFrame(buffer, ps, m_osd);
+ m_osdLock.unlock();
+ // Display the second field
+
+From aa753a179b3a08d0935f8adacc8d2611190bdac0 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sat, 6 Jun 2020 20:29:45 -0600
+Subject: [PATCH 052/165] mythexternrecorder: ondatastart might need to know
+ the channel number.
+
+(cherry picked from commit 5cf1846f76ff3a18212c2d6693b4701bdf64c03f)
+---
+ mythtv/programs/mythexternrecorder/MythExternRecApp.cpp | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 6ce2d9919cf..514a6ca6b0e 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -305,6 +305,7 @@ Q_SLOT void MythExternRecApp::DataStarted(void)
+ return;
+
+ QString cmd = m_onDataStart;
++ cmd.replace("%CHANNUM%", m_tunedChannel);
+
+ LOG(VB_RECORD, LOG_INFO, LOC +
+ QString(" Data started, finishing tune: '%1'").arg(cmd));
+
+From 6b45963cf8a25a6b0852edf3943db9b6857bb939 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sun, 7 Jun 2020 14:12:37 -0600
+Subject: [PATCH 053/165] mythexternrecorder: Fix "tuning" of channels by
+ external recorder, instead of separate "tuner".
+
+(cherry picked from commit d03307172137afeaa1f70e4f9e5458ff5f46570b)
+---
+ .../mythexternrecorder/MythExternRecApp.cpp | 53 ++++++++++++-------
+ 1 file changed, 33 insertions(+), 20 deletions(-)
+
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 514a6ca6b0e..624b30c98ab 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -487,7 +487,7 @@ void MythExternRecApp::NewEpisodeStarting(const QString & channum)
+ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ const QString & channum)
+ {
+- if (m_tuneCommand.isEmpty())
++ if (m_tuneCommand.isEmpty() && m_channelsIni.isEmpty())
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No 'tuner' configured.");
+ emit SendMessage("TuneChannel", serial, "ERR:No 'tuner' configured.");
+@@ -509,7 +509,7 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ m_desc = m_recDesc;
+ m_command = m_recCommand;
+
+- QString tune = m_tuneCommand;
++ QString tunecmd = m_tuneCommand;
+ QString url;
+
+ if (!m_channelsIni.isEmpty())
+@@ -527,7 +527,7 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + msg);
+ }
+ else
+- tune.replace("%URL%", url);
++ tunecmd.replace("%URL%", url);
+
+ if (!url.isEmpty() && m_command.indexOf("%URL%") >= 0)
+ {
+@@ -546,18 +546,9 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+ if (m_tuneProc.state() == QProcess::Running)
+ TerminateProcess(m_tuneProc, "Tune");
+
+- tune.replace("%CHANNUM%", channum);
++ tunecmd.replace("%CHANNUM%", channum);
+ m_command.replace("%CHANNUM%", channum);
+
+- m_tuneProc.start(tune);
+- if (!m_tuneProc.waitForStarted())
+- {
+- QString errmsg = QString("Tune `%1` failed: ").arg(tune) + ENO;
+- LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
+- emit SendMessage("TuneChannel", serial, QString("ERR:%1").arg(errmsg));
+- return;
+- }
+-
+ if (!m_logFile.isEmpty() && m_command.indexOf("%LOGFILE%") >= 0)
+ {
+ m_command.replace("%LOGFILE%", m_logFile);
+@@ -576,12 +567,32 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
+
+ m_desc.replace("%URL%", url);
+ m_desc.replace("%CHANNUM%", channum);
+- m_tuningChannel = channum;
+
+- LOG(VB_CHANNEL, LOG_INFO, LOC + QString(": Started `%1` URL '%2'")
+- .arg(tune).arg(url));
+- emit SendMessage("TuneChannel", serial,
+- QString("OK:InProgress `%1`").arg(tune));
++ if (!m_tuneCommand.isEmpty())
++ {
++ m_tuningChannel = channum;
++ m_tuneProc.start(tunecmd);
++ if (!m_tuneProc.waitForStarted())
++ {
++ QString errmsg = QString("Tune `%1` failed: ").arg(tunecmd) + ENO;
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
++ emit SendMessage("TuneChannel", serial,
++ QString("ERR:%1").arg(errmsg));
++ return;
++ }
++
++ LOG(VB_CHANNEL, LOG_INFO, LOC + QString(": Started `%1` URL '%2'")
++ .arg(tunecmd).arg(url));
++ emit SendMessage("TuneChannel", serial,
++ QString("OK:InProgress `%1`").arg(tunecmd));
++ }
++ else
++ {
++ m_tunedChannel = channum;
++ emit SetDescription(Desc());
++ emit SendMessage("TuneChannel", serial,
++ QString("OK:Tuned to %1").arg(m_tunedChannel));
++ }
+ }
+
+ Q_SLOT void MythExternRecApp::TuneStatus(const QString & serial)
+@@ -594,7 +605,8 @@ Q_SLOT void MythExternRecApp::TuneStatus(const QString & serial)
+ return;
+ }
+
+- if (m_tuneProc.exitStatus() != QProcess::NormalExit)
++ if (!m_tuneCommand.isEmpty() &&
++ m_tuneProc.exitStatus() != QProcess::NormalExit)
+ {
+ QString errmsg = QString("'%1' failed: ")
+ .arg(m_tuneProc.program()) + ENO;
+@@ -640,7 +652,8 @@ Q_SLOT void MythExternRecApp::LockTimeout(const QString & serial)
+ Q_SLOT void MythExternRecApp::HasTuner(const QString & serial)
+ {
+ emit SendMessage("HasTuner", serial, QString("OK:%1")
+- .arg(m_tuneCommand.isEmpty() ? "No" : "Yes"));
++ .arg(m_tuneCommand.isEmpty() &&
++ m_channelsIni.isEmpty() ? "No" : "Yes"));
+ }
+
+ Q_SLOT void MythExternRecApp::HasPictureAttributes(const QString & serial)
+
+From 445cf1fe6be324245dfc1c548d265bec147f711c Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sun, 7 Jun 2020 14:37:09 -0600
+Subject: [PATCH 054/165] ExternalStreamHandler: Use DEBUG log level for
+ TunerStatus
+
+(cherry picked from commit 118db4df5d0fc70971f7aa1d4f468f41bf3baa81)
+---
+ mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index 87548086a1b..74265cf9bd3 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -1449,7 +1449,9 @@ bool ExternalStreamHandler::ProcessVer2(const QString & command,
+ m_ioErrCnt = 0;
+ if (!okay)
+ level = LOG_WARNING;
+- else if (command.startsWith("SendBytes"))
++ else if (command.startsWith("SendBytes") ||
++ (command.startsWith("TuneStatus") &&
++ result == "OK:InProgress"))
+ level = LOG_DEBUG;
+
+ LOG(VB_RECORD, level,
+
+From 0d8e6f1b14e6d670e54c70fac80cde797e100111 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <mythtv(a)sky.com>
+Date: Thu, 27 Feb 2020 17:52:31 +0000
+Subject: [PATCH 055/165] Merge pull request #191 from
+ ijc/musicmetadata-disc-number
+
+mythmusic fixes for multiple discs
+
+(cherry picked from commit 90a86400ae13e8dcff7d7ddb1b469409a8e3cc03)
+---
+ mythplugins/mythmusic/mythmusic/playlist.cpp | 4 +++-
+ .../libs/libmythmetadata/metaioflacvorbis.cpp | 24 +++++++++++++++++++
+ 2 files changed, 27 insertions(+), 1 deletion(-)
+
+diff --git a/mythplugins/mythmusic/mythmusic/playlist.cpp b/mythplugins/mythmusic/mythmusic/playlist.cpp
+index f0ca201e079..86addc845b4 100644
+--- a/mythplugins/mythmusic/mythmusic/playlist.cpp
++++ b/mythplugins/mythmusic/mythmusic/playlist.cpp
+@@ -386,8 +386,10 @@ void Playlist::shuffleTracks(MusicPlayer::ShuffleMode shuffleMode)
+ }
+ else
+ {
+- album_order = Ialbum->second * 1000;
++ album_order = Ialbum->second * 10000;
+ }
++ if (mdata->DiscNumber() != -1)
++ album_order += mdata->DiscNumber()*100;
+ album_order += mdata->Track();
+
+ songMap.insert(album_order, m_songs.at(x));
+diff --git a/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp b/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp
+index b505fb6bb3b..ae9cdee0473 100644
+--- a/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp
++++ b/mythtv/libs/libmythmetadata/metaioflacvorbis.cpp
+@@ -149,6 +149,30 @@ MusicMetadata* MetaIOFLACVorbis::read(const QString &filename)
+ if (metadata->Length() <= 0)
+ metadata->setLength(getTrackLength(flacfile));
+
++ if (tag->contains("DISCNUMBER"))
++ {
++ bool valid = false;
++ int n = tag->fieldListMap()["DISCNUMBER"].toString().toInt(&valid);
++ if (valid)
++ metadata->setDiscNumber(n);
++ }
++
++ if (tag->contains("TOTALTRACKS"))
++ {
++ bool valid = false;
++ int n = tag->fieldListMap()["TOTALTRACKS"].toString().toInt(&valid);
++ if (valid)
++ metadata->setTrackCount(n);
++ }
++
++ if (tag->contains("TOTALDISCS"))
++ {
++ bool valid = false;
++ int n = tag->fieldListMap()["TOTALDISCS"].toString().toInt(&valid);
++ if (valid)
++ metadata->setDiscCount(n);
++ }
++
+ delete flacfile;
+
+ return metadata;
+
+From d8ecd8fe7c85165fe3c818a6d07e7ca9472e7735 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Wed, 10 Jun 2020 19:24:00 +0200
+Subject: [PATCH 056/165] Python: fix timestamp calculation
+
+Python 3.8 changed the handling of the 'datetime' class:
+According release notes ("What's New In Python 3.8"):
+Arithmetic operations between subclasses of datetime.date or
+datetime.datetime and datetime.timedelta objects now return an
+instance of the subclass, rather than the base class.
+
+This caused an error in the calculation of a 'timestamp'.
+
+Tested with python2.7, python3.6 and python3.8.
+No other occurences of similar arithmetic operations identified.
+
+Fixes #13622
+
+(cherry picked from commit 24db137ee6435ae0f1ecc51c580ef1b3d5936402)
+---
+ mythtv/bindings/python/MythTV/utility/dt.py | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/utility/dt.py b/mythtv/bindings/python/MythTV/utility/dt.py
+index ef61749a56f..97ef75a243a 100644
+--- a/mythtv/bindings/python/MythTV/utility/dt.py
++++ b/mythtv/bindings/python/MythTV/utility/dt.py
+@@ -475,10 +475,11 @@ def mythformat(self):
+ return self.astimezone(self.UTCTZ()).strftime('%Y%m%d%H%M%S')
+
+ def 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())
++ # utc time = local time - utc offset
++ utc_naive = self.replace(tzinfo=None) - self.utcoffset()
++ utc_naive = utc_naive.replace(tzinfo=None)
++ 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 4c990647889687b5a5ee5951d289a5f0777dda90 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Wed, 10 Jun 2020 19:25:02 +0200
+Subject: [PATCH 057/165] Set MySQL Mode explicitely when starting a session
+
+Newer SQL server enable the 'strict' MySQL Modes
+"STRICT_TRANS_TABLES" and/or "STRICT_ALL_TABLES",
+which cause an error on committing to tables with fields,
+having no default value defined.
+
+An example: table "jobqueue", field "args", type "blob".
+
+This change sets explicitely the "SQL Mode" per python session
+like 'libmythbase' does it.
+
+Tested with python2.7, python3.6 and python3.8.
+
+(cherry picked from commit 968712b9280b7220d73a44da709cbed655f1ee0a)
+---
+ mythtv/bindings/python/MythTV/_conn_mysqldb.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mythtv/bindings/python/MythTV/_conn_mysqldb.py b/mythtv/bindings/python/MythTV/_conn_mysqldb.py
+index 3f798198219..6f50433036b 100644
+--- a/mythtv/bindings/python/MythTV/_conn_mysqldb.py
++++ b/mythtv/bindings/python/MythTV/_conn_mysqldb.py
+@@ -24,6 +24,7 @@ def dbconnect(dbconn, log):
+ use_unicode=True,
+ charset='utf8')
+ db.autocommit(True)
++ db.set_sql_mode("") # reset default sql_mode
+ return db
+
+ class LoggedCursor( MySQLdb.cursors.Cursor ):
+
+From 134ebd7b2938dfede4a916932e52146cf66c4a9c Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Wed, 10 Jun 2020 19:28:39 +0200
+Subject: [PATCH 058/165] Python: Resolve deprecation warnings
+
+Python3.8 shows a couple of deprecation warnings when running
+with the "-Wall" switch:
+
+The method locale.format() will be removed in a future version of Python.
+Use 'locale.format_string()' instead.
+
+DeprecationWarning: isAlive() is deprecated, use is_alive() instead.
+
+Tested with python2.7, python3.6 and python3.8.
+
+(cherry picked from commit e3f7f092fde8a81cfa8de2a808ce64cc3fa1d83c)
+---
+ mythtv/bindings/python/MythTV/altdict.py | 2 +-
+ mythtv/bindings/python/MythTV/utility/dequebuffer.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/altdict.py b/mythtv/bindings/python/MythTV/altdict.py
+index a90e9c3baa1..90b4fd606d6 100644
+--- a/mythtv/bindings/python/MythTV/altdict.py
++++ b/mythtv/bindings/python/MythTV/altdict.py
+@@ -107,7 +107,7 @@ class DictData( OrdDict ):
+ lambda x: datetime.fromRfc(x, datetime.UTCTZ())\
+ .astimezone(datetime.localTZ())]
+ _inv_trans = [ str,
+- lambda x: locale.format("%0.6f", x),
++ lambda x: locale.format_string("%0.6f", x),
+ lambda x: str(int(x)),
+ lambda x: x,
+ lambda x: str(int(x.timestamp())),
+diff --git a/mythtv/bindings/python/MythTV/utility/dequebuffer.py b/mythtv/bindings/python/MythTV/utility/dequebuffer.py
+index 650ac609d67..df4b537ced2 100644
+--- a/mythtv/bindings/python/MythTV/utility/dequebuffer.py
++++ b/mythtv/bindings/python/MythTV/utility/dequebuffer.py
+@@ -354,7 +354,7 @@ def _add_pipe(cls, pipe, buffer, mode=None):
+ # get IO mode from pipe
+ mode = pipe.mode
+
+- if (cls._pollingthread is None) or not cls._pollingthread.isAlive():
++ if (cls._pollingthread is None) or not cls._pollingthread.is_alive():
+ # create new thread, and set it to not block shutdown
+ cls._pollingthread = _PollingThread()
+ cls._pollingthread.daemon = True
+
+From 16e06262ba588597496306467dbea845d5f53c47 Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Sun, 31 May 2020 20:20:21 -0400
+Subject: [PATCH 059/165] Fix missing Qt 5.15 include in mythpainter.cpp.
+
+(cherry picked from commit f12096ba57c37f8966b9cc8fa2a775255862df9f)
+---
+ mythtv/libs/libmythui/mythpainter.cpp | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mythtv/libs/libmythui/mythpainter.cpp b/mythtv/libs/libmythui/mythpainter.cpp
+index 4435efb78a2..d70010839d0 100644
+--- a/mythtv/libs/libmythui/mythpainter.cpp
++++ b/mythtv/libs/libmythui/mythpainter.cpp
+@@ -5,6 +5,7 @@
+ // QT headers
+ #include <QRect>
+ #include <QPainter>
++#include <QPainterPath>
+
+ // libmythbase headers
+ #include "mythlogging.h"
+
+From 9e47ae9d385be2b5fdb72007aad998443fbab11d Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Tue, 16 Jun 2020 17:30:20 +0100
+Subject: [PATCH 060/165] MythOpenGLVideo: Fix chroma sampling for multiplanar
+ formats when resizing
+
+- we need GL_NEAREST for YUY2 and when using unsigned integers texture
+formats (GLES3.X for 10bit)
+- but it introduces some sampling errors for the chroma planes as, while
+the textures are not being resized for the first pass, the chroma planes
+are a different size to the main texture.
+- so where possible, use GL_LINEAR to filter the chroma planes and
+improve playback quality in the bulk of cases.
+
+(cherry picked from commit 3a4da22e3b1fa941d3495a6950ef6e5a6bfef7ab)
+---
+ .../libs/libmythtv/opengl/mythopenglvideo.cpp | 20 ++++++++++++-------
+ 1 file changed, 13 insertions(+), 7 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/opengl/mythopenglvideo.cpp b/mythtv/libs/libmythtv/opengl/mythopenglvideo.cpp
+index 6313fdafaa5..0fd86ae50a9 100644
+--- a/mythtv/libs/libmythtv/opengl/mythopenglvideo.cpp
++++ b/mythtv/libs/libmythtv/opengl/mythopenglvideo.cpp
+@@ -303,8 +303,8 @@ bool MythOpenGLVideo::AddDeinterlacer(const VideoFrame *Frame, FrameScanType Sca
+ sizes.emplace_back(QSize(m_videoDim));
+ m_prevTextures = MythVideoTexture::CreateTextures(m_render, m_inputType, m_outputType, sizes);
+ m_nextTextures = MythVideoTexture::CreateTextures(m_render, m_inputType, m_outputType, sizes);
+- // ensure we use GL_NEAREST if resizing is already active
+- if (m_resizing)
++ // ensure we use GL_NEAREST if resizing is already active and needed
++ if (m_resizing & Sampling)
+ {
+ MythVideoTexture::SetTextureFilters(m_render, m_prevTextures, QOpenGLTexture::Nearest);
+ MythVideoTexture::SetTextureFilters(m_render, m_nextTextures, QOpenGLTexture::Nearest);
+@@ -803,7 +803,12 @@ void MythOpenGLVideo::PrepareFrame(VideoFrame *Frame, bool TopFieldFirst, FrameS
+ // N.B. not needed for the basic deinterlacer
+ if (deinterlacing && !basicdeinterlacing && (m_videoDispDim.height() > m_displayVideoRect.height()))
+ resize |= Deinterlacer;
+- // UYVY packed pixels must be sampled exactly
++
++ // NB GL_NEAREST introduces some 'minor' chroma sampling errors
++ // for the following 2 cases. For YUY2 this may be better handled in the
++ // shader. For GLES3.0 10bit textures - Vulkan is probably the better solution.
++
++ // UYVY packed pixels must be sampled exactly with GL_NEAREST
+ if (FMT_YUY2 == m_outputType)
+ resize |= Sampling;
+ // unsigned integer texture formats need GL_NEAREST sampling
+@@ -850,9 +855,10 @@ void MythOpenGLVideo::PrepareFrame(VideoFrame *Frame, bool TopFieldFirst, FrameS
+ else if (!m_resizing && resize)
+ {
+ // framebuffer will be created as needed below
+- MythVideoTexture::SetTextureFilters(m_render, m_inputTextures, QOpenGLTexture::Nearest);
+- MythVideoTexture::SetTextureFilters(m_render, m_prevTextures, QOpenGLTexture::Nearest);
+- MythVideoTexture::SetTextureFilters(m_render, m_nextTextures, QOpenGLTexture::Nearest);
++ QOpenGLTexture::Filter filter = (resize & Sampling) ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear;
++ MythVideoTexture::SetTextureFilters(m_render, m_inputTextures, filter);
++ MythVideoTexture::SetTextureFilters(m_render, m_prevTextures, filter);
++ MythVideoTexture::SetTextureFilters(m_render, m_nextTextures, filter);
+ m_resizing = resize;
+ LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Resizing from %1x%2 to %3x%4 for %5")
+ .arg(m_videoDispDim.width()).arg(m_videoDispDim.height())
+@@ -863,7 +869,7 @@ void MythOpenGLVideo::PrepareFrame(VideoFrame *Frame, bool TopFieldFirst, FrameS
+ // check hardware frames have the correct filtering
+ if (hwframes)
+ {
+- QOpenGLTexture::Filter filter = resize ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear;
++ QOpenGLTexture::Filter filter = (resize & Sampling) ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear;
+ if (inputtextures[0]->m_filter != filter)
+ MythVideoTexture::SetTextureFilters(m_render, inputtextures, filter);
+ }
+
+From e9e48d190e11b51051dfea9485dc4f903af7b8e9 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Thu, 18 Jun 2020 11:22:02 +0100
+Subject: [PATCH 061/165] VDPAU: Further extend debug logging of support tests
+
+(cherry picked from commit 43714e821ba5e53a4e3e9726c658fb4b2aaeccea)
+---
+ .../libmythtv/decoders/mythvdpaucontext.cpp | 17 +++++++++++++----
+ .../libs/libmythtv/decoders/mythvdpauhelper.cpp | 11 +++++++++--
+ 2 files changed, 22 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp b/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
+index 73d31dd9139..b80eeaad22f 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
+@@ -168,18 +168,27 @@ MythCodecID MythVDPAUContext::GetSupportedCodec(AVCodecContext **Context,
+ vdpau = false;
+ for (auto vdpauprofile : profiles)
+ {
+- if (vdpauprofile.first == mythprofile &&
+- vdpauprofile.second.Supported((*Context)->width, (*Context)->height, (*Context)->level))
++ bool match = vdpauprofile.first == mythprofile;
++ if (match)
+ {
+- vdpau = true;
+- break;
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Trying %1")
++ .arg(MythCodecContext::GetProfileDescription(mythprofile, QSize())));
++ if (vdpauprofile.second.Supported((*Context)->width, (*Context)->height, (*Context)->level))
++ {
++ vdpau = true;
++ break;
++ }
+ }
+ }
+ }
+
+ // H264 needs additional checks for old hardware
+ if (vdpau && (success == kCodec_H264_VDPAU || success == kCodec_H264_VDPAU_DEC))
++ {
+ vdpau = MythVDPAUHelper::CheckH264Decode(*Context);
++ if (!vdpau)
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "H264 decode check failed");
++ }
+
+ QString desc = QString("'%1 %2 %3 %4x%5'")
+ .arg(codec).arg(profile).arg(pixfmt).arg((*Context)->width).arg((*Context)->height);
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+index bb5ef391406..9bddec96d65 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+@@ -38,8 +38,15 @@ VDPAUCodec::VDPAUCodec(MythCodecContext::CodecProfile Profile, QSize Size, uint3
+ bool VDPAUCodec::Supported(int Width, int Height, int Level)
+ {
+ uint32_t macros = static_cast<uint32_t>(((Width + 15) & ~15) * ((Height + 15) & ~15)) / 256;
+- return (Width <= m_maxSize.width()) && (Height <= m_maxSize.height()) &&
+- (macros <= m_maxMacroBlocks) && (static_cast<uint32_t>(Level) <= m_maxLevel);
++ bool result = (Width <= m_maxSize.width()) && (Height <= m_maxSize.height()) &&
++ (macros <= m_maxMacroBlocks) && (static_cast<uint32_t>(Level) <= m_maxLevel);
++ if (!result)
++ {
++ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Not supported: Size %1x%2 > %3x%4, MBs %5 > %6, Level %7 > %8")
++ .arg(Width).arg(Height).arg(m_maxSize.width()).arg(m_maxSize.height())
++ .arg(macros).arg(m_maxMacroBlocks).arg(Level).arg(m_maxLevel));
++ }
++ return result;
+ }
+
+ bool MythVDPAUHelper::HaveVDPAU(void)
+
+From 94423151e51e155472531d80a7e24b875a13b13c Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Thu, 18 Jun 2020 11:25:09 +0100
+Subject: [PATCH 062/165] MythPlayer: Use yadif for deinterlacing previews
+ (regression)
+
+- the cpu deinterlacers were re-factored shortly before v31 and yadif is
+now 'high' quality
+
+(cherry picked from commit d3378789e99f00ec9c2b911cc244ffec6b82b6d6)
+---
+ mythtv/libs/libmythtv/mythplayer.cpp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
+index 7865bab69af..1eecc103cfe 100644
+--- a/mythtv/libs/libmythtv/mythplayer.cpp
++++ b/mythtv/libs/libmythtv/mythplayer.cpp
+@@ -4526,9 +4526,9 @@ char *MythPlayer::GetScreenGrabAtFrame(uint64_t FrameNum, bool Absolute,
+
+ if (frame->interlaced_frame)
+ {
+- // Use medium quality - which is currently yadif
++ // Use high quality - which is currently yadif
+ frame->deinterlace_double = DEINT_NONE;
+- frame->deinterlace_allowed = frame->deinterlace_single = DEINT_CPU | DEINT_MEDIUM;
++ frame->deinterlace_allowed = frame->deinterlace_single = DEINT_CPU | DEINT_HIGH;
+ MythDeinterlacer deinterlacer;
+ deinterlacer.Filter(frame, kScan_Interlaced, nullptr, true);
+ }
+
+From bcd9a63ec012adeb4a028d6873c512bb93afa3e2 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 22 Jun 2020 18:36:33 +0100
+Subject: [PATCH 063/165] VDPAU: Fall 'back' to H264 Main profile for
+ H264Baseline
+
+- as well as constrained baseline
+
+(cherry picked from commit d3719e6b110c76b188a3e4217ed5e253d2f6bd7b)
+---
+ mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+index 9bddec96d65..d529f1b5453 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+@@ -96,13 +96,16 @@ bool MythVDPAUHelper::ProfileCheck(VdpDecoderProfile Profile, uint32_t &Level,
+ .arg(Profile).arg(supported).arg(Level).arg(Macros).arg(Width).arg(Height).arg(status));
+
+ if (((supported != VDP_TRUE) || (status != VDP_STATUS_OK)) &&
+- (Profile == VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE))
++ (Profile == VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE ||
++ Profile == VDP_DECODER_PROFILE_H264_BASELINE))
+ {
+- LOG(VB_GENERAL, LOG_INFO, LOC + "Driver does not report support for H264 Constrained Baseline...");
++ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Driver does not report support for H264 %1Baseline")
++ .arg(Profile == VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE ? "Constrained " : ""));
+
+ // H264 Constrained baseline is reported as not supported on older chipsets but
+ // works due to support for H264 Main. Test for H264 main if constrained baseline
+ // fails - which mimics the fallback in FFmpeg.
++ // Updated to included baseline... not so sure about that:)
+ status = m_vdpDecoderQueryCapabilities(m_device, VDP_DECODER_PROFILE_H264_MAIN, &supported,
+ &Level, &Macros, &Width, &Height);
+ CHECK_ST
+@@ -350,9 +353,7 @@ bool MythVDPAUHelper::CheckH264Decode(AVCodecContext *Context)
+ switch (Context->profile & ~FF_PROFILE_H264_INTRA)
+ {
+ case FF_PROFILE_H264_BASELINE: profile = VDP_DECODER_PROFILE_H264_BASELINE; break;
+-#ifdef VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE
+ case FF_PROFILE_H264_CONSTRAINED_BASELINE: profile = VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE; break;
+-#endif
+ case FF_PROFILE_H264_MAIN: profile = VDP_DECODER_PROFILE_H264_MAIN; break;
+ case FF_PROFILE_H264_HIGH: profile = VDP_DECODER_PROFILE_H264_HIGH; break;
+ #ifdef VDP_DECODER_PROFILE_H264_EXTENDED
+
+From 1182a9abea4724d8af0b8690f04e39779855fce9 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Mon, 22 Jun 2020 13:23:25 -0500
+Subject: [PATCH 064/165] Python Bindings: care for python3.3+ use of
+ ElementTree
+
+Fedora 33 is using Python 3.9 and no longer has cElementTree.
+Which was depracated in v3.3, thanks GB.
+
+https://docs.python.org/3/library/xml.etree.elementtree.html
+(cherry picked from commit d7c0c5d263d62312559b1c96f375b5f081cf564b)
+---
+ mythtv/bindings/python/MythTV/dataheap.py | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/bindings/python/MythTV/dataheap.py b/mythtv/bindings/python/MythTV/dataheap.py
+index a50c762734d..bfb84d10dcb 100644
+--- a/mythtv/bindings/python/MythTV/dataheap.py
++++ b/mythtv/bindings/python/MythTV/dataheap.py
+@@ -15,7 +15,13 @@
+
+ import re
+ import locale
+-import xml.etree.cElementTree as etree
++
++# TODO: if Python 3.3+ is in use by all distributions, use ElementTree only.
++try:
++ import xml.etree.cElementTree as etree
++except ImportError:
++ import xml.etree.ElementTree as etree
++
+ from datetime import date, time
+
+ _default_datetime = datetime(1900,1,1, tzinfo=datetime.UTCTZ())
+
+From b89d76fa944a5dfc61e96eaa532eb399d92419ab Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Tue, 5 May 2020 23:36:21 +0200
+Subject: [PATCH 065/165] Transport Editor updates
+
+Show the DVB-C parameter edit page for HDHomeRun tuners when they can do DVB-C.
+Previously the ATSC parameter edit page was shown.
+Show the DVB-T2 parameter edit page for HDHomeRun tuners when they can do other DVB.
+Previously the ATSC parameter edit page was shown.
+Fixed a problem where sometimes the Transport Editor did not show any
+transports when there were only a small number of transports in the video source.
+FIxed a problem where the modulation system was not saved in the transport when a
+DVB-S (not a DVB-S2) tuner and the transports came from the NIT transport loop.
+
+(cherry picked from commit fcf9e8e79fd4971d298cd8c3a53466d256f7f0ff)
+
+Fixes #13640
+
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/cardutil.cpp | 18 +++++++-------
+ mythtv/libs/libmythtv/cardutil.h | 6 +++++
+ mythtv/libs/libmythtv/dtvmultiplex.cpp | 2 +-
+ mythtv/libs/libmythtv/sourceutil.cpp | 23 ++++++++++++++++++
+ mythtv/libs/libmythtv/sourceutil.h | 1 +
+ mythtv/libs/libmythtv/transporteditor.cpp | 29 ++++++++++++++---------
+ 6 files changed, 58 insertions(+), 21 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/cardutil.cpp b/mythtv/libs/libmythtv/cardutil.cpp
+index f9098e7d911..5f2d8940ee2 100644
+--- a/mythtv/libs/libmythtv/cardutil.cpp
++++ b/mythtv/libs/libmythtv/cardutil.cpp
+@@ -265,7 +265,7 @@ bool CardUtil::IsTunerShared(uint inputidA, uint inputidB)
+
+ if (!query.exec())
+ {
+- MythDB::DBError("CardUtil::is_tuner_shared", query);
++ MythDB::DBError("CardUtil::is_tuner_shared()", query);
+ return false;
+ }
+
+@@ -320,7 +320,7 @@ bool CardUtil::IsInputTypePresent(const QString &rawtype, QString hostname)
+
+ if (!query.exec())
+ {
+- MythDB::DBError("CardUtil::IsInputTypePresent", query);
++ MythDB::DBError("CardUtil::IsInputTypePresent()", query);
+ return false;
+ }
+
+@@ -1229,7 +1229,7 @@ QString get_on_input(const QString &to_get, uint inputid)
+ query.bindValue(":INPUTID", inputid);
+
+ if (!query.exec())
+- MythDB::DBError("CardUtil::get_on_source", query);
++ MythDB::DBError("CardUtil::get_on_input", query);
+ else if (query.next())
+ return query.value(0).toString();
+
+@@ -1597,7 +1597,7 @@ vector<uint> CardUtil::GetInputIDs(uint sourceid)
+
+ if (!query.exec())
+ {
+- MythDB::DBError("CardUtil::GetInputIDs()", query);
++ MythDB::DBError("CardUtil::GetInputIDs(sourceid)", query);
+ return list;
+ }
+
+@@ -1618,7 +1618,7 @@ bool CardUtil::SetStartChannel(uint inputid, const QString &channum)
+
+ if (!query.exec())
+ {
+- MythDB::DBError("set_startchan", query);
++ MythDB::DBError("CardUtil::SetStartChannel", query);
+ return false;
+ }
+
+@@ -1836,7 +1836,7 @@ int CardUtil::CreateCardInput(const uint inputid,
+
+ if (!query.exec())
+ {
+- MythDB::DBError("CreateCardInput", query);
++ MythDB::DBError("CardUtil::CreateCardInput()", query);
+ return -1;
+ }
+
+@@ -1853,7 +1853,7 @@ uint CardUtil::CreateInputGroup(const QString &name)
+ query.bindValue(":GROUPNAME", name);
+ if (!query.exec())
+ {
+- MythDB::DBError("CreateNewInputGroup 0", query);
++ MythDB::DBError("CardUtil::CreateNewInputGroup 0", query);
+ return 0;
+ }
+
+@@ -1863,7 +1863,7 @@ uint CardUtil::CreateInputGroup(const QString &name)
+ query.prepare("SELECT MAX(inputgroupid) FROM inputgroup");
+ if (!query.exec())
+ {
+- MythDB::DBError("CreateNewInputGroup 1", query);
++ MythDB::DBError("CardUtil::CreateNewInputGroup 1", query);
+ return 0;
+ }
+
+@@ -1878,7 +1878,7 @@ uint CardUtil::CreateInputGroup(const QString &name)
+ query.bindValue(":GROUPNAME", name);
+ if (!query.exec())
+ {
+- MythDB::DBError("CreateNewInputGroup 2", query);
++ MythDB::DBError("CardUtil::CreateNewInputGroup 2", query);
+ return 0;
+ }
+
+diff --git a/mythtv/libs/libmythtv/cardutil.h b/mythtv/libs/libmythtv/cardutil.h
+index 416c29da3b9..55aae9d9ac0 100644
+--- a/mythtv/libs/libmythtv/cardutil.h
++++ b/mythtv/libs/libmythtv/cardutil.h
+@@ -79,10 +79,16 @@ class MTV_PUBLIC CardUtil
+ return ERROR_PROBE;
+ if ("QPSK" == name)
+ return QPSK;
++ if ("DVBS" == name)
++ return DVBS;
+ if ("QAM" == name)
+ return QAM;
++ if ("DVBC" == name)
++ return DVBC;
+ if ("OFDM" == name)
+ return OFDM;
++ if ("DVBT" == name)
++ return DVBT;
+ if ("ATSC" == name)
+ return ATSC;
+ if ("V4L" == name)
+diff --git a/mythtv/libs/libmythtv/dtvmultiplex.cpp b/mythtv/libs/libmythtv/dtvmultiplex.cpp
+index 6477d526e1e..a55f1251ee2 100644
+--- a/mythtv/libs/libmythtv/dtvmultiplex.cpp
++++ b/mythtv/libs/libmythtv/dtvmultiplex.cpp
+@@ -509,7 +509,7 @@ bool DTVMultiplex::FillFromDeliverySystemDesc(DTVTunerType type,
+ return false;
+ }
+
+- return ParseDVB_S_and_C(
++ return ParseDVB_S(
+ QString::number(cd.FrequencykHz()), "a",
+ QString::number(cd.SymbolRateHz()), cd.FECInnerString(),
+ cd.ModulationString(),
+diff --git a/mythtv/libs/libmythtv/sourceutil.cpp b/mythtv/libs/libmythtv/sourceutil.cpp
+index b9758d9fd48..9a8334c84aa 100644
+--- a/mythtv/libs/libmythtv/sourceutil.cpp
++++ b/mythtv/libs/libmythtv/sourceutil.cpp
+@@ -65,6 +65,29 @@ QString SourceUtil::GetSourceName(uint sourceid)
+ return query.value(0).toString();
+ }
+
++uint SourceUtil::GetSourceID(const QString &name)
++{
++ MSqlQuery query(MSqlQuery::InitCon());
++
++ query.prepare(
++ "SELECT sourceid "
++ "FROM videosource "
++ "WHERE name = :NAME");
++ query.bindValue(":NAME", name);
++
++ if (!query.exec())
++ {
++ MythDB::DBError("SourceUtil::GetSourceID()", query);
++ return 0;
++ }
++ if (!query.next())
++ {
++ return 0;
++ }
++
++ return query.value(0).toUInt();
++}
++
+ QString SourceUtil::GetChannelSeparator(uint sourceid)
+ {
+ MSqlQuery query(MSqlQuery::InitCon());
+diff --git a/mythtv/libs/libmythtv/sourceutil.h b/mythtv/libs/libmythtv/sourceutil.h
+index 496c6959c7e..666e9a3a321 100644
+--- a/mythtv/libs/libmythtv/sourceutil.h
++++ b/mythtv/libs/libmythtv/sourceutil.h
+@@ -17,6 +17,7 @@ class MTV_PUBLIC SourceUtil
+ public:
+ static bool HasDigitalChannel(uint sourceid);
+ static QString GetSourceName(uint sourceid);
++ static uint GetSourceID(const QString &name);
+ static QString GetChannelSeparator(uint sourceid);
+ static QString GetChannelFormat(uint sourceid);
+ static uint GetChannelCount(uint sourceid);
+diff --git a/mythtv/libs/libmythtv/transporteditor.cpp b/mythtv/libs/libmythtv/transporteditor.cpp
+index dae14fece8e..0359370a245 100644
+--- a/mythtv/libs/libmythtv/transporteditor.cpp
++++ b/mythtv/libs/libmythtv/transporteditor.cpp
+@@ -35,6 +35,7 @@ using namespace std;
+
+ #include "transporteditor.h"
+ #include "videosource.h"
++#include "sourceutil.h"
+ #include "mythcorecontext.h"
+ #include "mythdb.h"
+
+@@ -109,6 +110,14 @@ static CardUtil::INPUT_TYPES get_cardtype(uint sourceid)
+ cardtype = CardUtil::ProbeSubTypeName(cardid);
+ nType = CardUtil::toInputType(cardtype);
+
++ if (nType == CardUtil::HDHOMERUN)
++ {
++ if (CardUtil::HDHRdoesDVBC(CardUtil::GetVideoDevice(cardid)))
++ nType = CardUtil::DVBC;
++ else if (CardUtil::HDHRdoesDVB(CardUtil::GetVideoDevice(cardid)))
++ nType = CardUtil::DVBT2;
++ }
++
+ if ((CardUtil::ERROR_OPEN == nType) ||
+ (CardUtil::ERROR_UNKNOWN == nType) ||
+ (CardUtil::ERROR_PROBE == nType))
+@@ -161,27 +170,22 @@ static CardUtil::INPUT_TYPES get_cardtype(uint sourceid)
+ return cardtypes[0];
+ }
+
+-void TransportListEditor::SetSourceID(uint _sourceid)
++void TransportListEditor::SetSourceID(uint sourceid)
+ {
+ for (auto *setting : m_list)
+ removeChild(setting);
+ m_list.clear();
+
+-#if 0
+- LOG(VB_GENERAL, LOG_DEBUG, QString("TransportList::SetSourceID(%1)")
+- .arg(_sourceid));
+-#endif
+-
+- if (!_sourceid)
++ if (!sourceid)
+ {
+ m_sourceid = 0;
+ }
+ else
+ {
+- m_cardtype = get_cardtype(_sourceid);
++ m_cardtype = get_cardtype(sourceid);
+ m_sourceid = ((CardUtil::ERROR_OPEN == m_cardtype) ||
+ (CardUtil::ERROR_UNKNOWN == m_cardtype) ||
+- (CardUtil::ERROR_PROBE == m_cardtype)) ? 0 : _sourceid;
++ (CardUtil::ERROR_PROBE == m_cardtype)) ? 0 : sourceid;
+ }
+ }
+
+@@ -204,11 +208,14 @@ TransportListEditor::TransportListEditor(uint sourceid) :
+ SetSourceID(sourceid);
+ }
+
+-void TransportListEditor::SetSourceID(const QString& sourceid)
++void TransportListEditor::SetSourceID(const QString& name)
+ {
+ if (m_isLoading)
+ return;
+- SetSourceID(sourceid.toUInt());
++
++ uint sourceid = SourceUtil::GetSourceID(name);
++
++ SetSourceID(sourceid);
+ Load();
+ }
+
+
+From 261eeff1bd1a5ec8a3573f5957238961793d9356 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Thu, 25 Jun 2020 11:04:14 +0100
+Subject: [PATCH 066/165] VDPAU: Extend FFmpeg constrained baseline check to
+ include baseline
+
+- to match the changes in our own code.
+
+(cherry picked from commit f142e8535fa61db91454f571b57d479c9130515f)
+---
+ mythtv/external/FFmpeg/libavcodec/vdpau.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/external/FFmpeg/libavcodec/vdpau.c b/mythtv/external/FFmpeg/libavcodec/vdpau.c
+index 167f06d7aeb..2e7e8d757ca 100644
+--- a/mythtv/external/FFmpeg/libavcodec/vdpau.c
++++ b/mythtv/external/FFmpeg/libavcodec/vdpau.c
+@@ -243,7 +243,9 @@ int ff_vdpau_common_init(AVCodecContext *avctx, VdpDecoderProfile profile,
+ status = decoder_query_caps(vdctx->device, profile, &supported, &max_level,
+ &max_mb, &max_width, &max_height);
+ #ifdef VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE
+- if ((status != VDP_STATUS_OK || supported != VDP_TRUE) && profile == VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE) {
++ if ((status != VDP_STATUS_OK || supported != VDP_TRUE) &&
++ (profile == VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE ||
++ profile == VDP_DECODER_PROFILE_H264_BASELINE)) {
+ profile = VDP_DECODER_PROFILE_H264_MAIN;
+ status = decoder_query_caps(vdctx->device, profile, &supported,
+ &max_level, &max_mb,
+
+From a675ee1e110fca3ea61a197729ca42c899db94f9 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Thu, 25 Jun 2020 11:34:54 +0100
+Subject: [PATCH 067/165] VDPAU: Disable level checks in MythTV and FFmpeg
+
+- per the FFmpeg docs, this should be the default and it is causing
+hardware acceleration to fail on older VDPAU devices.
+
+(cherry picked from commit 9995644dac9047e04a8c09ebfe029099aa915ec9)
+---
+ mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp | 3 ++-
+ mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp | 3 ++-
+ 2 files changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp b/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
+index b80eeaad22f..56e57ee2d26 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
+@@ -106,7 +106,8 @@ int MythVDPAUContext::InitialiseContext(AVCodecContext* Context)
+ }
+
+ auto* vdpaudevicectx = static_cast<AVVDPAUDeviceContext*>(hwdevicecontext->hwctx);
+- if (av_vdpau_bind_context(Context, vdpaudevicectx->device, vdpaudevicectx->get_proc_address, 0) != 0)
++ if (av_vdpau_bind_context(Context, vdpaudevicectx->device,
++ vdpaudevicectx->get_proc_address, AV_HWACCEL_FLAG_IGNORE_LEVEL) != 0)
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to bind VDPAU context");
+ av_buffer_unref(&hwdeviceref);
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+index d529f1b5453..918ce6275f7 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpauhelper.cpp
+@@ -37,9 +37,10 @@ VDPAUCodec::VDPAUCodec(MythCodecContext::CodecProfile Profile, QSize Size, uint3
+
+ bool VDPAUCodec::Supported(int Width, int Height, int Level)
+ {
++ // Note - level checks are now ignored here and in FFmpeg
+ uint32_t macros = static_cast<uint32_t>(((Width + 15) & ~15) * ((Height + 15) & ~15)) / 256;
+ bool result = (Width <= m_maxSize.width()) && (Height <= m_maxSize.height()) &&
+- (macros <= m_maxMacroBlocks) && (static_cast<uint32_t>(Level) <= m_maxLevel);
++ (macros <= m_maxMacroBlocks) /*&& (static_cast<uint32_t>(Level) <= m_maxLevel)*/;
+ if (!result)
+ {
+ LOG(VB_PLAYBACK, LOG_DEBUG, LOC + QString("Not supported: Size %1x%2 > %3x%4, MBs %5 > %6, Level %7 > %8")
+
+From ade713f98c5846e9c5f08a76e72cbe24ad7057ef Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Fri, 5 Jun 2020 14:21:59 -0400
+Subject: [PATCH 068/165] Services: Add new GetStreamInfo method
+
+GetStreamInfo gets basic stream information for a video or recording, including
+frame rate, picture dimensons and video codec, as well as audio codec and number
+of channels. The information is obtained from the ffmpeg avformat apis.
+
+(cherry picked from commit a2af89101bd4a4a28e83f85fbcd7b30456a01ca8)
+---
+ .../datacontracts/videoStreamInfo.h | 93 ++++++++++++
+ .../datacontracts/videoStreamInfoList.h | 99 +++++++++++++
+ .../libmythservicecontracts.pro | 2 +
+ .../services/videoServices.h | 6 +
+ mythtv/libs/libmythtv/mythavutil.cpp | 138 ++++++++++++++++++
+ mythtv/libs/libmythtv/mythavutil.h | 36 +++++
+ .../programs/mythbackend/services/video.cpp | 51 +++++++
+ mythtv/programs/mythbackend/services/video.h | 2 +
+ 8 files changed, 427 insertions(+)
+ create mode 100644 mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfo.h
+ create mode 100644 mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfoList.h
+
+diff --git a/mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfo.h b/mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfo.h
+new file mode 100644
+index 00000000000..176d4d998b9
+--- /dev/null
++++ b/mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfo.h
+@@ -0,0 +1,93 @@
++//////////////////////////////////////////////////////////////////////////////
++// Program Name: videoStreamInfo.h
++// Created : May. 30, 2020
++//
++// Copyright (c) 2020 Peter Bennett <pbennett(a)mythtv.org>
++//
++// Licensed under the GPL v2 or later, see COPYING for details
++//
++//////////////////////////////////////////////////////////////////////////////
++
++#ifndef VIDEOSTREAMINFO_H_
++#define VIDEOSTREAMINFO_H_
++
++#include <QString>
++#include <QDateTime>
++
++#include "serviceexp.h"
++#include "datacontracthelper.h"
++
++namespace DTC
++{
++
++/////////////////////////////////////////////////////////////////////////////
++
++class SERVICE_PUBLIC VideoStreamInfo : public QObject
++{
++ Q_OBJECT
++ Q_CLASSINFO( "version" , "1.00" );
++
++ Q_PROPERTY( QString CodecType READ CodecType WRITE setCodecType )
++ Q_PROPERTY( QString CodecName READ CodecName WRITE setCodecName )
++ Q_PROPERTY( int Width READ Width WRITE setWidth )
++ Q_PROPERTY( int Height READ Height WRITE setHeight )
++ Q_PROPERTY( float AspectRatio READ AspectRatio WRITE setAspectRatio )
++ Q_PROPERTY( QString FieldOrder READ FieldOrder WRITE setFieldOrder )
++ Q_PROPERTY( float FrameRate READ FrameRate WRITE setFrameRate )
++ Q_PROPERTY( float AvgFrameRate READ AvgFrameRate WRITE setAvgFrameRate )
++ Q_PROPERTY( int Channels READ Channels WRITE setChannels )
++ Q_PROPERTY( qlonglong Duration READ Duration WRITE setDuration )
++
++ PROPERTYIMP ( QString , CodecType )
++ PROPERTYIMP ( QString , CodecName )
++ PROPERTYIMP ( int , Width )
++ PROPERTYIMP ( int , Height )
++ PROPERTYIMP ( float , AspectRatio )
++ PROPERTYIMP ( QString , FieldOrder )
++ PROPERTYIMP ( float , FrameRate )
++ PROPERTYIMP ( float , AvgFrameRate )
++ PROPERTYIMP ( int , Channels )
++ PROPERTYIMP ( qlonglong , Duration )
++
++ public:
++
++ static inline void InitializeCustomTypes();
++
++ Q_INVOKABLE VideoStreamInfo(QObject *parent = nullptr)
++ : QObject ( parent ),
++ m_Width ( 0 ),
++ m_Height ( 0 ),
++ m_AspectRatio ( 0 ),
++ m_FrameRate ( 0 ),
++ m_AvgFrameRate ( 0 ),
++ m_Channels ( 0 ),
++ m_Duration ( 0 )
++ {
++ }
++
++ void Copy( const VideoStreamInfo *src )
++ {
++ m_CodecType = src->m_CodecType ;
++ m_CodecName = src->m_CodecName ;
++ m_Width = src->m_Width ;
++ m_Height = src->m_Height ;
++ m_AspectRatio = src->m_AspectRatio ;
++ m_FieldOrder = src->m_FieldOrder ;
++ m_FrameRate = src->m_FrameRate ;
++ m_AvgFrameRate = src->m_AvgFrameRate ;
++ m_Channels = src->m_Channels ;
++ m_Duration = src->m_Duration ;
++ }
++
++ private:
++ Q_DISABLE_COPY(VideoStreamInfo);
++};
++
++inline void VideoStreamInfo::InitializeCustomTypes()
++{
++ qRegisterMetaType< VideoStreamInfo* >();
++}
++
++} // namespace DTC
++
++#endif
+diff --git a/mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfoList.h b/mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfoList.h
+new file mode 100644
+index 00000000000..fa9e417fcf0
+--- /dev/null
++++ b/mythtv/libs/libmythservicecontracts/datacontracts/videoStreamInfoList.h
+@@ -0,0 +1,99 @@
++//////////////////////////////////////////////////////////////////////////////
++// Program Name: videoStreamInfoList.h
++// Created : May. 30, 2020
++//
++// Copyright (c) 2011 Peter Bennett <pbennett(a)mythtv.org>
++//
++// Licensed under the GPL v2 or later, see COPYING for details
++//
++//////////////////////////////////////////////////////////////////////////////
++
++#ifndef VIDEOSTREAMINFOLIST_H_
++#define VIDEOSTREAMINFOLIST_H_
++
++#include <QVariantList>
++
++#include "serviceexp.h"
++#include "datacontracthelper.h"
++
++#include "videoStreamInfo.h"
++
++namespace DTC
++{
++
++class SERVICE_PUBLIC VideoStreamInfoList : public QObject
++{
++ Q_OBJECT
++ Q_CLASSINFO( "version", "1.00" );
++
++ // Q_CLASSINFO Used to augment Metadata for properties.
++ // See datacontracthelper.h for details
++
++ Q_CLASSINFO( "VideoStreamInfos", "type=DTC::VideoStreamInfo");
++ Q_CLASSINFO( "AsOf" , "transient=true" );
++
++ Q_PROPERTY( int Count READ Count WRITE setCount )
++ Q_PROPERTY( QDateTime AsOf READ AsOf WRITE setAsOf )
++ Q_PROPERTY( QString Version READ Version WRITE setVersion )
++ Q_PROPERTY( QString ProtoVer READ ProtoVer WRITE setProtoVer )
++ Q_PROPERTY( int ErrorCode READ ErrorCode WRITE setErrorCode )
++ Q_PROPERTY( QString ErrorMsg READ ErrorMsg WRITE setErrorMsg )
++
++ Q_PROPERTY( QVariantList VideoStreamInfos READ VideoStreamInfos DESIGNABLE true )
++
++ PROPERTYIMP ( int , Count )
++ PROPERTYIMP ( QDateTime , AsOf )
++ PROPERTYIMP ( QString , Version )
++ PROPERTYIMP ( QString , ProtoVer )
++ PROPERTYIMP ( int , ErrorCode )
++ PROPERTYIMP ( QString , ErrorMsg )
++
++ PROPERTYIMP_RO_REF( QVariantList, VideoStreamInfos );
++
++ public:
++
++ static inline void InitializeCustomTypes();
++
++ Q_INVOKABLE VideoStreamInfoList(QObject *parent = nullptr)
++ : QObject( parent ),
++ m_Count ( 0 )
++ {
++ }
++
++ void Copy( const VideoStreamInfoList *src )
++ {
++ m_Count = src->m_Count ;
++ m_AsOf = src->m_AsOf ;
++ m_Version = src->m_Version ;
++ m_ProtoVer = src->m_ProtoVer ;
++ m_ErrorCode = src->m_ErrorCode ;
++ m_ErrorMsg = src->m_ErrorMsg ;
++
++ CopyListContents< VideoStreamInfo >( this, m_VideoStreamInfos, src->m_VideoStreamInfos );
++ }
++
++ VideoStreamInfo *AddNewVideoStreamInfo()
++ {
++ // We must make sure the object added to the QVariantList has
++ // a parent of 'this'
++
++ auto *pObject = new VideoStreamInfo( this );
++ m_VideoStreamInfos.append( QVariant::fromValue<QObject *>( pObject ));
++
++ return pObject;
++ }
++
++ private:
++ Q_DISABLE_COPY(VideoStreamInfoList);
++};
++
++inline void VideoStreamInfoList::InitializeCustomTypes()
++{
++ qRegisterMetaType< VideoStreamInfoList* >();
++
++ VideoStreamInfo::InitializeCustomTypes();
++}
++
++} // namespace DTC
++
++#endif
+diff --git a/mythtv/libs/libmythservicecontracts/libmythservicecontracts.pro b/mythtv/libs/libmythservicecontracts/libmythservicecontracts.pro
+index 2c4a0e12d5f..aee335ac7ff 100644
+--- a/mythtv/libs/libmythservicecontracts/libmythservicecontracts.pro
++++ b/mythtv/libs/libmythservicecontracts/libmythservicecontracts.pro
+@@ -39,6 +39,7 @@ HEADERS += datacontracts/channelInfoList.h datacontracts/videoSource.h
+ HEADERS += datacontracts/videoSourceList.h datacontracts/videoMultiplex.h
+ HEADERS += datacontracts/videoMultiplexList.h datacontracts/videoMetadataInfo.h
+ HEADERS += datacontracts/videoMetadataInfoList.h datacontracts/blurayInfo.h
++HEADERS += datacontracts/videoStreamInfoList.h datacontracts/videoStreamInfo.h
+ HEADERS += datacontracts/timeZoneInfo.h datacontracts/videoLookupInfo.h
+ HEADERS += datacontracts/videoLookupInfoList.h datacontracts/versionInfo.h
+ HEADERS += datacontracts/lineup.h datacontracts/captureCard.h
+@@ -103,6 +104,7 @@ incDatacontracts.files += datacontracts/wolInfo.h datacontracts/chan
+ incDatacontracts.files += datacontracts/videoSource.h datacontracts/videoSourceList.h
+ incDatacontracts.files += datacontracts/videoMultiplex.h datacontracts/videoMultiplexList.h
+ incDatacontracts.files += datacontracts/videoMetadataInfo.h datacontracts/videoMetadataInfoList.h
++incDatacontracts.files += datacontracts/videoSTreamInfo.h datacontracts/videoStreamInfoList.h
+ incDatacontracts.files += datacontracts/musicMetadataInfo.h datacontracts/musicMetadataInfoList.h
+ incDatacontracts.files += datacontracts/blurayInfo.h datacontracts/videoLookupInfo.h
+ incDatacontracts.files += datacontracts/timeZoneInfo.h datacontracts/videoLookupInfoList.h
+diff --git a/mythtv/libs/libmythservicecontracts/services/videoServices.h b/mythtv/libs/libmythservicecontracts/services/videoServices.h
+index 894df6632eb..2ec7c6a333c 100644
+--- a/mythtv/libs/libmythservicecontracts/services/videoServices.h
++++ b/mythtv/libs/libmythservicecontracts/services/videoServices.h
+@@ -21,6 +21,7 @@
+ #include "datacontracts/videoMetadataInfoList.h"
+ #include "datacontracts/videoLookupInfoList.h"
+ #include "datacontracts/blurayInfo.h"
++#include "datacontracts/videoStreamInfoList.h"
+
+ /////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+@@ -57,6 +58,7 @@ class SERVICE_PUBLIC VideoServices : public Service //, public QScriptable ???
+ DTC::VideoMetadataInfoList::InitializeCustomTypes();
+ DTC::VideoLookupList::InitializeCustomTypes();
+ DTC::BlurayInfo::InitializeCustomTypes();
++ DTC::VideoStreamInfoList::InitializeCustomTypes();
+ }
+
+ public slots:
+@@ -130,6 +132,10 @@ class SERVICE_PUBLIC VideoServices : public Service //, public QScriptable ???
+ const QString &Genres,
+ const QString &Cast,
+ const QString &Countries) = 0;
++
++ virtual DTC::VideoStreamInfoList* GetStreamInfo ( const QString &StorageGroup,
++ const QString &FileName ) = 0;
++
+ };
+
+ #endif
+diff --git a/mythtv/libs/libmythtv/mythavutil.cpp b/mythtv/libs/libmythtv/mythavutil.cpp
+index 0905bf6ada7..4f18e37cca2 100644
+--- a/mythtv/libs/libmythtv/mythavutil.cpp
++++ b/mythtv/libs/libmythtv/mythavutil.cpp
+@@ -20,6 +20,7 @@ extern "C" {
+ #include "libavformat/avformat.h"
+ }
+ #include <QMutexLocker>
++#include <QFile>
+
+ AVPixelFormat FrameTypeToPixelFormat(VideoFrameType type)
+ {
+@@ -576,3 +577,140 @@ void MythCodecMap::freeAllCodecContexts()
+ freeCodecContext(stream);
+ }
+ }
++
++MythStreamInfoList::MythStreamInfoList(QString filename)
++{
++ const int probeBufferSize = 8 * 1024;
++ AVInputFormat *fmt = nullptr;
++ AVProbeData probe;
++ memset(&probe, 0, sizeof(AVProbeData));
++ probe.filename = "";
++ probe.buf = new unsigned char[probeBufferSize + AVPROBE_PADDING_SIZE];
++ probe.buf_size = probeBufferSize;
++ memset(probe.buf, 0, probeBufferSize + AVPROBE_PADDING_SIZE);
++ av_log_set_level(AV_LOG_FATAL);
++ m_errorCode = 0;
++ if (filename == "")
++ m_errorCode = 97;
++ QFile infile(filename);
++ if (m_errorCode == 0 && !infile.open(QIODevice::ReadOnly))
++ m_errorCode = 99;
++ if (m_errorCode==0) {
++ int64_t leng = infile.read(reinterpret_cast<char*>(probe.buf), probeBufferSize);
++ probe.buf_size = static_cast<int>(leng);
++ infile.close();
++ fmt = av_probe_input_format(&probe, static_cast<int>(true));
++ if (fmt == nullptr)
++ m_errorCode = 98;
++ }
++ AVFormatContext *ctx = nullptr;
++ if (m_errorCode==0)
++ {
++ ctx = avformat_alloc_context();
++ m_errorCode = avformat_open_input(&ctx, filename.toUtf8(), fmt, nullptr);
++ }
++ if (m_errorCode==0)
++ m_errorCode = avformat_find_stream_info(ctx, nullptr);
++
++ if (m_errorCode==0)
++ {
++ for (uint ix = 0; ix < ctx->nb_streams; ix++)
++ {
++ AVStream *stream = ctx->streams[ix];
++ if (stream == nullptr)
++ continue;
++ AVCodecParameters *codecpar = stream->codecpar;
++ const AVCodecDescriptor* desc = nullptr;
++ if (codecpar != nullptr)
++ desc = avcodec_descriptor_get(codecpar->codec_id);
++ MythStreamInfo info;
++ info.m_codecType = ' ';
++ switch (codecpar->codec_type)
++ {
++ case AVMEDIA_TYPE_VIDEO:
++ info.m_codecType = 'V';
++ break;
++ case AVMEDIA_TYPE_AUDIO:
++ info.m_codecType = 'A';
++ break;
++ case AVMEDIA_TYPE_SUBTITLE:
++ info.m_codecType = 'S';
++ break;
++ default:
++ continue;
++ }
++ if (desc != nullptr)
++ info.m_codecName = desc->name;
++ info.m_duration = stream->duration * stream->time_base.num / stream->time_base.den;
++ if (info.m_codecType == 'V')
++ {
++ if (codecpar != nullptr)
++ {
++ info.m_width = codecpar->width;
++ info.m_height = codecpar->height;
++ info.m_SampleAspectRatio = static_cast<float>(codecpar->sample_aspect_ratio.num)
++ / static_cast<float>(codecpar->sample_aspect_ratio.den);
++ switch (codecpar->field_order)
++ {
++ case AV_FIELD_PROGRESSIVE:
++ info.m_fieldOrder = "PR";
++ break;
++ case AV_FIELD_TT:
++ info.m_fieldOrder = "TT";
++ break;
++ case AV_FIELD_BB:
++ info.m_fieldOrder = "BB";
++ break;
++ case AV_FIELD_TB:
++ info.m_fieldOrder = "TB";
++ break;
++ case AV_FIELD_BT:
++ info.m_fieldOrder = "BT";
++ break;
++ default:
++ break;
++ }
++ }
++ info.m_frameRate = static_cast<float>(stream->r_frame_rate.num)
++ / static_cast<float>(stream->r_frame_rate.den);
++ info.m_avgFrameRate = static_cast<float>(stream->avg_frame_rate.num)
++ / static_cast<float>(stream->avg_frame_rate.den);
++ }
++ if (info.m_codecType == 'A')
++ info.m_channels = codecpar->channels;
++ m_streamInfoList.append(info);
++ }
++ }
++ if (m_errorCode != 0)
++ {
++ switch(m_errorCode) {
++ case 97:
++ m_errorMsg = "File Not Found";
++ break;
++ case 98:
++ m_errorMsg = "av_probe_input_format returned no result";
++ break;
++ case 99:
++ m_errorMsg = "File could not be opened";
++ break;
++ default:
++ char errbuf[256];
++ if (av_strerror(m_errorCode, errbuf, sizeof errbuf) == 0)
++ m_errorMsg = QString(errbuf);
++ else
++ m_errorMsg = "UNKNOWN";
++ }
++ LOG(VB_GENERAL, LOG_ERR,
++ QString("MythStreamInfoList failed for %1. Error code:%2 Message:%3")
++ .arg(filename).arg(m_errorCode).arg(m_errorMsg));
++
++ }
++
++ if (ctx != nullptr)
++ {
++ avformat_close_input(&ctx);
++ avformat_free_context(ctx);
++ }
++ if (probe.buf != nullptr)
++ delete probe.buf;
++}
+\ No newline at end of file
+diff --git a/mythtv/libs/libmythtv/mythavutil.h b/mythtv/libs/libmythtv/mythavutil.h
+index c58dcf1c0a9..a3730e54e82 100644
+--- a/mythtv/libs/libmythtv/mythavutil.h
++++ b/mythtv/libs/libmythtv/mythavutil.h
+@@ -16,6 +16,7 @@ extern "C" {
+
+ #include <QMap>
+ #include <QMutex>
++#include <QVector>
+
+ struct AVFilterGraph;
+ struct AVFilterContext;
+@@ -198,4 +199,39 @@ class MTV_PUBLIC MythPictureDeinterlacer
+ float m_ar;
+ bool m_errored {false};
+ };
++
++
++class MTV_PUBLIC MythStreamInfo {
++public:
++ // These are for All types
++ char m_codecType {' '}; // V=video, A=audio, S=subtitle
++ QString m_codecName;
++ int64_t m_duration {0};
++ // These are for Video only
++ int m_width {0};
++ int m_height {0};
++ float m_SampleAspectRatio {0.0};
++ // AV_FIELD_TT, //< Top coded_first, top displayed first
++ // AV_FIELD_BB, //< Bottom coded first, bottom displayed first
++ // AV_FIELD_TB, //< Top coded first, bottom displayed first
++ // AV_FIELD_BT, //< Bottom coded first, top displayed first
++ QString m_fieldOrder {"UN"}; // UNknown, PRogressive, TT, BB, TB, BT
++ float m_frameRate {0.0};
++ float m_avgFrameRate {0.0};
++ // This is for audio only
++ int m_channels {0};
++};
++
++
++/*
++* Class to get stream info, used by service Video/GetStreamInfo
++*/
++class MTV_PUBLIC MythStreamInfoList {
++public:
++ MythStreamInfoList(QString filename);
++ int m_errorCode {0};
++ QString m_errorMsg;
++ QVector<MythStreamInfo> m_streamInfoList;
++};
++
+ #endif
+diff --git a/mythtv/programs/mythbackend/services/video.cpp b/mythtv/programs/mythbackend/services/video.cpp
+index 80ace227497..bc023185665 100644
+--- a/mythtv/programs/mythbackend/services/video.cpp
++++ b/mythtv/programs/mythbackend/services/video.cpp
+@@ -44,6 +44,7 @@
+ #include "mythdate.h"
+ #include "serviceUtil.h"
+ #include "mythmiscutil.h"
++#include "mythavutil.h"
+
+ /////////////////////////////////////////////////////////////////////////////
+ //
+@@ -780,6 +781,56 @@ bool Video::UpdateVideoMetadata ( int nId,
+ return true;
+ }
+
++/////////////////////////////////////////////////////////////////////////////
++// Jun 3, 2020
++// Service to get stream info for all streams in a media file.
++// This gets some basic info. If anything more is needed it can be added,
++// depending on whether it is available from ffmpeg avformat apis.
++// See the MythStreamInfoList class for the code that uses avformat to
++// extract the information.
++/////////////////////////////////////////////////////////////////////////////
++
++DTC::VideoStreamInfoList* Video::GetStreamInfo
++ ( const QString &storageGroup,
++ const QString &FileName )
++{
++
++ // Search for the filename
++
++ StorageGroup storage( storageGroup );
++ QString sFullFileName = storage.FindFile( FileName );
++ MythStreamInfoList infos(sFullFileName);
++
++ // The constructor of this class reads the file and gets the needed
++ // information.
++ auto *pVideoStreamInfos = new DTC::VideoStreamInfoList();
++
++ pVideoStreamInfos->setCount ( infos.m_streamInfoList.size() );
++ pVideoStreamInfos->setAsOf ( MythDate::current() );
++ pVideoStreamInfos->setVersion ( MYTH_BINARY_VERSION );
++ pVideoStreamInfos->setProtoVer ( MYTH_PROTO_VERSION );
++ pVideoStreamInfos->setErrorCode ( infos.m_errorCode );
++ pVideoStreamInfos->setErrorMsg ( infos.m_errorMsg );
++
++ for( int n = 0; n < infos.m_streamInfoList.size() ; n++ )
++ {
++ DTC::VideoStreamInfo *pVideoStreamInfo = pVideoStreamInfos->AddNewVideoStreamInfo();
++ const MythStreamInfo &info = infos.m_streamInfoList.at(n);
++ pVideoStreamInfo->setCodecType ( QString(QChar(info.m_codecType)) );
++ pVideoStreamInfo->setCodecName ( info.m_codecName );
++ pVideoStreamInfo->setWidth ( info.m_width );
++ pVideoStreamInfo->setHeight ( info.m_height );
++ pVideoStreamInfo->setAspectRatio ( info.m_SampleAspectRatio );
++ pVideoStreamInfo->setFieldOrder ( info.m_fieldOrder );
++ pVideoStreamInfo->setFrameRate ( info.m_frameRate );
++ pVideoStreamInfo->setAvgFrameRate ( info.m_avgFrameRate );
++ pVideoStreamInfo->setChannels ( info.m_channels );
++ pVideoStreamInfo->setDuration ( info.m_duration );
++
++ }
++ return pVideoStreamInfos;
++}
++
+ /////////////////////////////////////////////////////////////////////////////
+ //
+ /////////////////////////////////////////////////////////////////////////////
+diff --git a/mythtv/programs/mythbackend/services/video.h b/mythtv/programs/mythbackend/services/video.h
+index 93469c246b9..5f49e65d1df 100644
+--- a/mythtv/programs/mythbackend/services/video.h
++++ b/mythtv/programs/mythbackend/services/video.h
+@@ -114,6 +114,8 @@ class Video : public VideoServices
+
+ DTC::BlurayInfo* GetBluray ( const QString &Path ) override; // VideoServices
+
++ DTC::VideoStreamInfoList* GetStreamInfo ( const QString &StorageGroup,
++ const QString &FileName ) override; // VideoServices
+ };
+
+ // --------------------------------------------------------------------------
+
+From 8a0c4f9bd2d9567ccbdb4151d7c03068388b45a6 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 29 Jun 2020 17:15:57 +0100
+Subject: [PATCH 069/165] macos: Handle high DPI displays
+
+- behaviour should be unchanged for non-macos installations
+- the underlying problem is that when macos is using high DPI, the
+windowing system reports e.g. 1920x1080 as the window size but the
+underlying OpenGL context is using the full resolution e.g. 3840x2160.
+- video playback is actually easiest - as we just scale the video
+window/viewport as needed - and everything else falls into place.
+- the UI painter is slightly more complicated as our UI images are at
+the lower resolution - and the entire UI code is operating on the
+reported window size. So scale incoming rendering data as required,
+which also requires some tweaking of the texture vertices.
+
+Refs #13618
+---
+ mythtv/libs/libmythtv/mythvideoout.cpp | 2 +-
+ mythtv/libs/libmythtv/videooutwindow.cpp | 30 +++++++--
+ mythtv/libs/libmythtv/videooutwindow.h | 5 ++
+ mythtv/libs/libmythui/mythpainter.h | 4 +-
+ .../libmythui/opengl/mythpainteropengl.cpp | 62 ++++++++++++++++---
+ .../libs/libmythui/opengl/mythpainteropengl.h | 9 +++
+ .../libmythui/opengl/mythrenderopengl.cpp | 10 +--
+ .../libs/libmythui/opengl/mythrenderopengl.h | 4 +-
+ 8 files changed, 105 insertions(+), 21 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/mythvideoout.cpp b/mythtv/libs/libmythtv/mythvideoout.cpp
+index ee4b759d814..888b283cfcc 100644
+--- a/mythtv/libs/libmythtv/mythvideoout.cpp
++++ b/mythtv/libs/libmythtv/mythvideoout.cpp
+@@ -1020,7 +1020,7 @@ void MythVideoOutput::InitDisplayMeasurements(void)
+ .arg(displayaspect).arg(source));
+
+ // Get the window and screen resolutions
+- QSize window = m_window.GetWindowRect().size();
++ QSize window = m_window.GetRawWindowRect().size();
+ QSize screen = m_display->GetResolution();
+
+ // If not running fullscreen, adjust for window size and ignore any video
+diff --git a/mythtv/libs/libmythtv/videooutwindow.cpp b/mythtv/libs/libmythtv/videooutwindow.cpp
+index af8af15c312..9c7326f11cb 100644
+--- a/mythtv/libs/libmythtv/videooutwindow.cpp
++++ b/mythtv/libs/libmythtv/videooutwindow.cpp
+@@ -38,6 +38,11 @@
+
+ #define LOC QString("VideoWin: ")
+
++#define SCALED_RECT(SRC, SCALE) QRect{ static_cast<int>(SRC.left() * SCALE), \
++ static_cast<int>(SRC.top() * SCALE), \
++ static_cast<int>(SRC.width() * SCALE), \
++ static_cast<int>(SRC.height() * SCALE) }
++
+ static float fix_aspect(float raw);
+ static float snap(float value, float snapto, float diff);
+
+@@ -63,6 +68,14 @@ void VideoOutWindow::ScreenChanged(QScreen */*screen*/)
+ MoveResize();
+ }
+
++void VideoOutWindow::PhysicalDPIChanged(qreal /*DPI*/)
++{
++ // PopulateGeometry will update m_devicePixelRatio
++ PopulateGeometry();
++ m_windowRect = m_displayVisibleRect = SCALED_RECT(m_rawWindowRect, m_devicePixelRatio);
++ MoveResize();
++}
++
+ void VideoOutWindow::PopulateGeometry(void)
+ {
+ if (!m_display)
+@@ -72,6 +85,10 @@ void VideoOutWindow::PopulateGeometry(void)
+ if (!screen)
+ return;
+
++#ifdef Q_OS_MACOS
++ m_devicePixelRatio = screen->devicePixelRatio();
++#endif
++
+ if (MythDisplay::SpanAllScreens() && MythDisplay::GetScreenCount() > 1)
+ {
+ m_screenGeometry = screen->virtualGeometry();
+@@ -416,6 +433,9 @@ bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim,
+ {
+ m_display = Display;
+ connect(m_display, &MythDisplay::CurrentScreenChanged, this, &VideoOutWindow::ScreenChanged);
++#ifdef Q_OS_MACOS
++ connect(m_display, &MythDisplay::PhysicalDPIChanged, this, &VideoOutWindow::PhysicalDPIChanged);
++#endif
+ }
+
+ if (m_display)
+@@ -429,7 +449,8 @@ bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim,
+
+ // N.B. we are always confined to the window size so use that for the initial
+ // displayVisibleRect
+- m_windowRect = m_displayVisibleRect = WindowRect;
++ m_rawWindowRect = WindowRect;
++ m_windowRect = m_displayVisibleRect = SCALED_RECT(WindowRect, m_devicePixelRatio);
+
+ int pbp_width = m_displayVisibleRect.width() / 2;
+ if (m_pipState == kPBPLeft || m_pipState == kPBPRight)
+@@ -613,12 +634,13 @@ void VideoOutWindow::SetDisplayAspect(float DisplayAspect)
+
+ void VideoOutWindow::SetWindowSize(QSize Size)
+ {
+- if (Size != m_windowRect.size())
++ if (Size != m_rawWindowRect.size())
+ {
+- QRect rect(m_windowRect.topLeft(), Size);
++ QRect rect(m_rawWindowRect.topLeft(), Size);
+ LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New window rect: %1x%2+%3+%4")
+ .arg(rect.width()).arg(rect.height()).arg(rect.left()).arg(rect.top()));
+- m_windowRect = m_displayVisibleRect = rect;
++ m_rawWindowRect = rect;
++ m_windowRect = m_displayVisibleRect = SCALED_RECT(rect, m_devicePixelRatio);
+ MoveResize();
+ }
+ }
+diff --git a/mythtv/libs/libmythtv/videooutwindow.h b/mythtv/libs/libmythtv/videooutwindow.h
+index 9480045c921..cce077b5fde 100644
+--- a/mythtv/libs/libmythtv/videooutwindow.h
++++ b/mythtv/libs/libmythtv/videooutwindow.h
+@@ -45,6 +45,7 @@ class VideoOutWindow : public QObject
+
+ public slots:
+ void ScreenChanged (QScreen *screen);
++ void PhysicalDPIChanged (qreal /*DPI*/);
+
+ // Sets
+ void InputChanged (const QSize &VideoDim, const QSize &VideoDispDim, float Aspect);
+@@ -74,6 +75,7 @@ class VideoOutWindow : public QObject
+ float GetOverridenVideoAspect(void) const { return m_videoAspectOverride;}
+ QRect GetDisplayVisibleRect(void) const { return m_displayVisibleRect; }
+ QRect GetWindowRect(void) const { return m_windowRect; }
++ QRect GetRawWindowRect(void) const { return m_rawWindowRect; }
+ QRect GetScreenGeometry(void) const { return m_screenGeometry; }
+ QRect GetVideoRect(void) const { return m_videoRect; }
+ QRect GetDisplayVideoRect(void) const { return m_displayVideoRect; }
+@@ -115,6 +117,7 @@ class VideoOutWindow : public QObject
+ bool m_dbScalingAllowed {true}; ///< disable this to prevent overscan/underscan
+ bool m_dbUseGUISize {false}; ///< Use the gui size for video window
+ QRect m_screenGeometry {0,0,1024,768}; ///< Full screen geometry
++ qreal m_devicePixelRatio {1.0};
+
+ // Manual Zoom
+ float m_manualVertScale {1.0F}; ///< Manually applied vertical scaling.
+@@ -147,6 +150,8 @@ class VideoOutWindow : public QObject
+ QRect m_displayVisibleRect {0,0,0,0};
+ /// Rectangle describing QWidget bounds.
+ QRect m_windowRect {0,0,0,0};
++ /// Rectangle describing QWidget bounds - not adjusted for high DPI scaling (macos)
++ QRect m_rawWindowRect {0,0,0,0};
+ /// Used to save the display_visible_rect for
+ /// restoration after video embedding ends.
+ QRect m_tmpDisplayVisibleRect {0,0,0,0};
+diff --git a/mythtv/libs/libmythui/mythpainter.h b/mythtv/libs/libmythui/mythpainter.h
+index b6b054a8136..67175a1ed51 100644
+--- a/mythtv/libs/libmythui/mythpainter.h
++++ b/mythtv/libs/libmythui/mythpainter.h
+@@ -29,8 +29,10 @@ class UIEffects;
+ using LayoutVector = QVector<QTextLayout *>;
+ using FormatVector = QVector<QTextLayout::FormatRange>;
+
+-class MUI_PUBLIC MythPainter
++class MUI_PUBLIC MythPainter : public QObject
+ {
++ Q_OBJECT
++
+ public:
+ MythPainter();
+ /** MythPainter destructor.
+diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
+index 8fec14d3c20..abbb7685f0a 100644
+--- a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
++++ b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
+@@ -20,10 +20,20 @@ MythOpenGLPainter::MythOpenGLPainter(MythRenderOpenGL *Render, QWidget *Parent)
+
+ if (!m_render)
+ LOG(VB_GENERAL, LOG_ERR, "OpenGL painter has no render device");
++
++#ifdef Q_OS_MACOS
++ m_display = MythDisplay::AcquireRelease();
++ CurrentDPIChanged(m_parent->devicePixelRatioF());
++ connect(m_display, &MythDisplay::CurrentDPIChanged, this, &MythOpenGLPainter::CurrentDPIChanged);
++#endif
+ }
+
+ MythOpenGLPainter::~MythOpenGLPainter()
+ {
++#ifdef Q_OS_MACOS
++ MythDisplay::AcquireRelease(false);
++#endif
++
+ if (!m_render)
+ return;
+ if (!m_render->IsReady())
+@@ -84,6 +94,13 @@ void MythOpenGLPainter::ClearCache(void)
+ m_imageToTextureMap.clear();
+ }
+
++void MythOpenGLPainter::CurrentDPIChanged(qreal DPI)
++{
++ m_pixelRatio = DPI;
++ m_usingHighDPI = !qFuzzyCompare(m_pixelRatio, 1.0);
++ LOG(VB_GENERAL, LOG_INFO, QString("High DPI scaling %1").arg(m_usingHighDPI ? "enabled" : "disabled"));
++}
++
+ void MythOpenGLPainter::Begin(QPaintDevice *Parent)
+ {
+ MythPainter::Begin(Parent);
+@@ -109,13 +126,17 @@ void MythOpenGLPainter::Begin(QPaintDevice *Parent)
+ buf = m_render->CreateVBO(static_cast<int>(MythRenderOpenGL::kVertexSize));
+ }
+
++ QSize currentsize = m_parent->size();
++
+ // check if we need to adjust cache sizes
+- if (m_lastSize != m_parent->size())
++ // NOTE - don't use the scaled size if using high DPI. Our images are at the lower
++ // resolution
++ if (m_lastSize != currentsize)
+ {
+ // This will scale the cache depending on the resolution in use
+ static const int s_onehd = 1920 * 1080;
+ static const int s_basesize = 64;
+- m_lastSize = m_parent->size();
++ m_lastSize = currentsize;
+ float hdscreens = (static_cast<float>(m_lastSize.width() + 1) * m_lastSize.height()) / s_onehd;
+ int cpu = qMax(static_cast<int>(hdscreens * s_basesize), s_basesize);
+ int gpu = cpu * 3 / 2;
+@@ -130,8 +151,11 @@ void MythOpenGLPainter::Begin(QPaintDevice *Parent)
+
+ if (m_target || m_swapControl)
+ {
++ // If we are master and using high DPI then scale the viewport
++ if (m_swapControl && m_usingHighDPI)
++ currentsize *= m_pixelRatio;
+ m_render->BindFramebuffer(m_target);
+- m_render->SetViewPort(QRect(0, 0, m_parent->width(), m_parent->height()));
++ m_render->SetViewPort(QRect(0, 0, currentsize.width(), currentsize.height()));
+ m_render->SetBackground(0, 0, 0, 0);
+ m_render->ClearFramebuffer();
+ }
+@@ -221,12 +245,25 @@ MythGLTexture* MythOpenGLPainter::GetTextureFromCache(MythImage *Image)
+ return texture;
+ }
+
++#ifdef Q_OS_MACOS
++#define DEST dest
++#else
++#define DEST Dest
++#endif
++
+ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image,
+ const QRect &Source, int Alpha)
+ {
+ if (m_render)
+ {
+- // Drawing an image multiple times with the same VBO will stall most GPUs as
++#ifdef Q_OS_MACOS
++ QRect dest = QRect(static_cast<int>(Dest.left() * m_pixelRatio),
++ static_cast<int>(Dest.top() * m_pixelRatio),
++ static_cast<int>(Dest.width() * m_pixelRatio),
++ static_cast<int>(Dest.height() * m_pixelRatio));
++#endif
++
++ // Drawing an image multiple times with the same VBO will stall most GPUs as
+ // the VBO is re-mapped whilst still in use. Use a pooled VBO instead.
+ MythGLTexture *texture = GetTextureFromCache(Image);
+ if (texture && m_mappedTextures.contains(texture))
+@@ -234,7 +271,7 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image,
+ QOpenGLBuffer *vbo = texture->m_vbo;
+ texture->m_vbo = m_mappedBufferPool[m_mappedBufferPoolIdx];
+ texture->m_destination = QRect();
+- m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha);
++ m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio);
+ texture->m_destination = QRect();
+ texture->m_vbo = vbo;
+ if (++m_mappedBufferPoolIdx >= MAX_BUFFER_POOL)
+@@ -242,17 +279,26 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image,
+ }
+ else
+ {
+- m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha);
++ m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio);
+ m_mappedTextures.append(texture);
+ }
+ }
+ }
+
++/*! \brief Draw a rectangle
++ *
++ * If it is a simple rectangle, then use our own shaders for rendering (which
++ * saves texture memory but may not be as accurate as Qt rendering) otherwise
++ * fallback to Qt painting to a QImage, which is uploaded as a texture.
++ *
++ * \note If high DPI scaling is in use, just use Qt painting rather than
++ * handling all of the adjustments required for pen width etc etc.
++*/
+ void MythOpenGLPainter::DrawRect(const QRect &Area, const QBrush &FillBrush,
+ const QPen &LinePen, int Alpha)
+ {
+ if ((FillBrush.style() == Qt::SolidPattern ||
+- FillBrush.style() == Qt::NoBrush) && m_render)
++ FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI)
+ {
+ m_render->DrawRect(m_target, Area, FillBrush, LinePen, Alpha);
+ return;
+@@ -265,7 +311,7 @@ void MythOpenGLPainter::DrawRoundRect(const QRect &Area, int CornerRadius,
+ const QPen &LinePen, int Alpha)
+ {
+ if ((FillBrush.style() == Qt::SolidPattern ||
+- FillBrush.style() == Qt::NoBrush) && m_render)
++ FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI)
+ {
+ m_render->DrawRoundRect(m_target, Area, CornerRadius, FillBrush,
+ LinePen, Alpha);
+diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.h b/mythtv/libs/libmythui/opengl/mythpainteropengl.h
+index 097577231fa..540f7db79df 100644
+--- a/mythtv/libs/libmythui/opengl/mythpainteropengl.h
++++ b/mythtv/libs/libmythui/opengl/mythpainteropengl.h
+@@ -6,6 +6,7 @@
+ #include <QQueue>
+
+ // MythTV
++#include "mythdisplay.h"
+ #include "mythpainter.h"
+ #include "mythimage.h"
+
+@@ -22,6 +23,8 @@ class QOpenGLFramebufferObject;
+
+ class MUI_PUBLIC MythOpenGLPainter : public MythPainter
+ {
++ Q_OBJECT
++
+ public:
+ explicit MythOpenGLPainter(MythRenderOpenGL *Render = nullptr, QWidget *Parent = nullptr);
+ ~MythOpenGLPainter() override;
+@@ -46,6 +49,9 @@ class MUI_PUBLIC MythOpenGLPainter : public MythPainter
+ void PushTransformation(const UIEffects &Fx, QPointF Center = QPointF()) override;
+ void PopTransformation(void) override;
+
++ public slots:
++ void CurrentDPIChanged(qreal DPI);
++
+ protected:
+ void ClearCache(void);
+ MythGLTexture* GetTextureFromCache(MythImage *Image);
+@@ -60,6 +66,9 @@ class MUI_PUBLIC MythOpenGLPainter : public MythPainter
+ QOpenGLFramebufferObject* m_target { nullptr };
+ bool m_swapControl { true };
+ QSize m_lastSize { };
++ qreal m_pixelRatio { 1.0 };
++ MythDisplay* m_display { nullptr };
++ bool m_usingHighDPI { false };
+
+ QMap<MythImage *, MythGLTexture*> m_imageToTextureMap;
+ std::list<MythImage *> m_ImageExpireList;
+diff --git a/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp b/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp
+index e34320f3dcf..684740c5848 100644
+--- a/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp
++++ b/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp
+@@ -804,7 +804,7 @@ void MythRenderOpenGL::ClearFramebuffer(void)
+
+ void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target,
+ const QRect &Source, const QRect &Destination,
+- QOpenGLShaderProgram *Program, int Alpha)
++ QOpenGLShaderProgram *Program, int Alpha, qreal Scale)
+ {
+ makeCurrent();
+
+@@ -827,7 +827,7 @@ void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObje
+
+ QOpenGLBuffer* buffer = Texture->m_vbo;
+ buffer->bind();
+- if (UpdateTextureVertices(Texture, Source, Destination, 0))
++ if (UpdateTextureVertices(Texture, Source, Destination, 0, Scale))
+ {
+ if (m_extraFeaturesUsed & kGLBufferMap)
+ {
+@@ -1262,7 +1262,7 @@ QStringList MythRenderOpenGL::GetDescription(void)
+ }
+
+ bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect &Source,
+- const QRect &Destination, int Rotation)
++ const QRect &Destination, int Rotation, qreal Scale)
+ {
+ if (!Texture || (Texture && Texture->m_size.isEmpty()))
+ return false;
+@@ -1301,8 +1301,8 @@ bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect
+ data[4 + TEX_OFFSET] = data[6 + TEX_OFFSET];
+ data[5 + TEX_OFFSET] = data[1 + TEX_OFFSET];
+
+- width = Texture->m_crop ? min(width, Destination.width()) : Destination.width();
+- height = Texture->m_crop ? min(height, Destination.height()) : Destination.height();
++ width = Texture->m_crop ? min(static_cast<int>(width * Scale), Destination.width()) : Destination.width();
++ height = Texture->m_crop ? min(static_cast<int>(height * Scale), Destination.height()) : Destination.height();
+
+ data[2] = data[0] = Destination.left();
+ data[5] = data[1] = Destination.top();
+diff --git a/mythtv/libs/libmythui/opengl/mythrenderopengl.h b/mythtv/libs/libmythui/opengl/mythrenderopengl.h
+index 199f0d642be..2ccb9a60d5b 100644
+--- a/mythtv/libs/libmythui/opengl/mythrenderopengl.h
++++ b/mythtv/libs/libmythui/opengl/mythrenderopengl.h
+@@ -143,7 +143,7 @@ class MUI_PUBLIC MythRenderOpenGL : public QOpenGLContext, public QOpenGLFunctio
+
+ void DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target,
+ const QRect &Source, const QRect &Destination,
+- QOpenGLShaderProgram *Program, int Alpha = 255);
++ QOpenGLShaderProgram *Program, int Alpha = 255, qreal Scale = 1.0);
+ void DrawBitmap(MythGLTexture **Textures, uint TextureCount,
+ QOpenGLFramebufferObject *Target,
+ const QRect &Source, const QRect &Destination,
+@@ -171,7 +171,7 @@ class MUI_PUBLIC MythRenderOpenGL : public QOpenGLContext, public QOpenGLFunctio
+ void SetMatrixView(void);
+ void DeleteFramebuffers(void);
+ static bool UpdateTextureVertices(MythGLTexture *Texture, const QRect &Source,
+- const QRect &Destination, int Rotation);
++ const QRect &Destination, int Rotation, qreal Scale = 1.0);
+ GLfloat* GetCachedVertices(GLuint Type, const QRect &Area);
+ void ExpireVertices(int Max = 0);
+ void GetCachedVBO(GLuint Type, const QRect &Area);
+
+From 3ef7db67fec2a2d843cea673d14240d9ee512332 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Fri, 3 Jul 2020 19:45:12 -0500
+Subject: [PATCH 070/165] Games Plugin: change system to `system` for MySQL v8
+
+(cherry picked from commit 94931c00dc5b67d72503fd112846f148a8e942c4)
+---
+ mythplugins/mythgame/mythgame/gameui.cpp | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/mythplugins/mythgame/mythgame/gameui.cpp b/mythplugins/mythgame/mythgame/gameui.cpp
+index 2146fac0009..71e48750fe0 100644
+--- a/mythplugins/mythgame/mythgame/gameui.cpp
++++ b/mythplugins/mythgame/mythgame/gameui.cpp
+@@ -112,7 +112,7 @@ void GameUI::BuildTree()
+ {
+ QString system = GameHandler::getHandler(i)->SystemName();
+ if (i == 0)
+- systemFilter = "system in ('" + system + "'";
++ systemFilter = "`system` in ('" + system + "'";
+ else
+ systemFilter += ",'" + system + "'";
+ }
+@@ -655,7 +655,7 @@ QString GameUI::getFillSql(MythGenericTree *node) const
+ if ((childLevel == "gamename") && (m_gameShowFileName))
+ {
+ columns = childIsLeaf
+- ? "romname,system,year,genre,gamename"
++ ? "romname,`system`,year,genre,gamename"
+ : "romname";
+
+ if (m_showHashed)
+@@ -665,7 +665,7 @@ QString GameUI::getFillSql(MythGenericTree *node) const
+ else if ((childLevel == "gamename") && (layer.length() == 1))
+ {
+ columns = childIsLeaf
+- ? childLevel + ",system,year,genre,gamename"
++ ? childLevel + ",`system`,year,genre,gamename"
+ : childLevel;
+
+ if (m_showHashed)
+@@ -680,7 +680,7 @@ QString GameUI::getFillSql(MythGenericTree *node) const
+ {
+
+ columns = childIsLeaf
+- ? childLevel + ",system,year,genre,gamename"
++ ? childLevel + ",`system`,year,genre,gamename"
+ : childLevel;
+ }
+
+
+From 0add177794446f98e0dfc4b40d628017e864701f Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Mon, 6 Jul 2020 22:07:23 -0500
+Subject: [PATCH 071/165] MacOS: remove hard-coded python2.6 PYTHONPATH code
+
+Based on a patch from John Hoyt, and thanks for testing
+on MacOS.
+
+This could prevent the location of Python packages in
+the future if the OSX packaging sets --python=python
+(for example). Currently the version is included in
+the Python executable's file name:
+
+ --python=/opt/local/bin/python3.8
+
+Fixes #13643
+
+(cherry picked from commit d30fd541c74a37d589548eadb9d225b2a96563ab)
+---
+ mythtv/programs/mythbackend/main.cpp | 3 ++-
+ mythtv/programs/mythfrontend/main.cpp | 3 ++-
+ mythtv/programs/mythmetadatalookup/main.cpp | 3 ++-
+ 3 files changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/main.cpp b/mythtv/programs/mythbackend/main.cpp
+index 7cf7f5b6440..ddbef2fa539 100644
+--- a/mythtv/programs/mythbackend/main.cpp
++++ b/mythtv/programs/mythbackend/main.cpp
+@@ -96,8 +96,9 @@ int main(int argc, char **argv)
+ #ifdef Q_OS_MAC
+ QString path = QCoreApplication::applicationDirPath();
+ setenv("PYTHONPATH",
+- QString("%1/../Resources/lib/python2.6/site-packages:%2")
++ QString("%1/../Resources/lib/%2/site-packages:%3")
+ .arg(path)
++ .arg(QFileInfo(PYTHON_EXE).fileName())
+ .arg(QProcessEnvironment::systemEnvironment().value("PYTHONPATH"))
+ .toUtf8().constData(), 1);
+ #endif
+diff --git a/mythtv/programs/mythfrontend/main.cpp b/mythtv/programs/mythfrontend/main.cpp
+index 16bc8819bea..fa713e5d51e 100644
+--- a/mythtv/programs/mythfrontend/main.cpp
++++ b/mythtv/programs/mythfrontend/main.cpp
+@@ -1868,8 +1868,9 @@ int main(int argc, char **argv)
+ #ifdef Q_OS_MAC
+ QString path = QCoreApplication::applicationDirPath();
+ setenv("PYTHONPATH",
+- QString("%1/../Resources/lib/python2.6/site-packages:%2")
++ QString("%1/../Resources/lib/%2/site-packages:%3")
+ .arg(path)
++ .arg(QFileInfo(PYTHON_EXE).fileName())
+ .arg(QProcessEnvironment::systemEnvironment().value("PYTHONPATH"))
+ .toUtf8().constData(), 1);
+ #endif
+diff --git a/mythtv/programs/mythmetadatalookup/main.cpp b/mythtv/programs/mythmetadatalookup/main.cpp
+index 423b7190419..78393d9cbe5 100644
+--- a/mythtv/programs/mythmetadatalookup/main.cpp
++++ b/mythtv/programs/mythmetadatalookup/main.cpp
+@@ -68,8 +68,9 @@ int main(int argc, char *argv[])
+ #ifdef Q_OS_MAC
+ QString path = QCoreApplication::applicationDirPath();
+ setenv("PYTHONPATH",
+- QString("%1/../Resources/lib/python2.6/site-packages:%2")
++ QString("%1/../Resources/lib/%2/site-packages:%3")
+ .arg(path)
++ .arg(QFileInfo(PYTHON_EXE).fileName())
+ .arg(QProcessEnvironment::systemEnvironment().value("PYTHONPATH"))
+ .toUtf8().constData(), 1);
+ #endif
+
+From 8212a9b7bf0354894013b102f88e4ef9d827c26c Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Sat, 11 Jul 2020 11:47:01 +0100
+Subject: [PATCH 072/165] VDPAU: Fix VDPAU rendering for AMD/Gallium
+
+Fixes #13253
+
+(cherry picked from commit 2fb7e4cb51408343ef40e13d099ca9802a576a8c)
+---
+ mythtv/libs/libmythtv/opengl/mythvdpauinterop.cpp | 8 +++++---
+ mythtv/libs/libmythtv/opengl/mythvdpauinterop.h | 1 +
+ 2 files changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/opengl/mythvdpauinterop.cpp b/mythtv/libs/libmythtv/opengl/mythvdpauinterop.cpp
+index e01dd9b50c1..a20dca8d9bc 100644
+--- a/mythtv/libs/libmythtv/opengl/mythvdpauinterop.cpp
++++ b/mythtv/libs/libmythtv/opengl/mythvdpauinterop.cpp
+@@ -109,7 +109,7 @@ bool MythVDPAUInterop::InitNV(AVVDPAUDeviceContext* DeviceContext)
+ if (!DeviceContext || !m_context)
+ return false;
+
+- if (m_initNV && m_finiNV && m_registerNV && m_accessNV && m_mapNV &&
++ if (m_initNV && m_finiNV && m_registerNV && m_accessNV && m_mapNV && m_unmapNV &&
+ m_helper && m_helper->IsValid())
+ return true;
+
+@@ -119,11 +119,12 @@ bool MythVDPAUInterop::InitNV(AVVDPAUDeviceContext* DeviceContext)
+ m_registerNV = reinterpret_cast<MYTH_VDPAUREGOUTSURFNV>(m_context->GetProcAddress("glVDPAURegisterOutputSurfaceNV"));
+ m_accessNV = reinterpret_cast<MYTH_VDPAUSURFACCESSNV>(m_context->GetProcAddress("glVDPAUSurfaceAccessNV"));
+ m_mapNV = reinterpret_cast<MYTH_VDPAUMAPSURFNV>(m_context->GetProcAddress("glVDPAUMapSurfacesNV"));
++ m_unmapNV = reinterpret_cast<MYTH_VDPAUMAPSURFNV>(m_context->GetProcAddress("glVDPAUUnmapSurfacesNV"));
+
+ delete m_helper;
+ m_helper = nullptr;
+
+- if (m_initNV && m_finiNV && m_registerNV && m_accessNV && m_mapNV)
++ if (m_initNV && m_finiNV && m_registerNV && m_accessNV && m_mapNV && m_unmapNV)
+ {
+ m_helper = new MythVDPAUHelper(DeviceContext);
+ if (m_helper->IsValid())
+@@ -198,7 +199,6 @@ bool MythVDPAUInterop::InitVDPAU(AVVDPAUDeviceContext* DeviceContext, VdpVideoSu
+ else
+ {
+ m_accessNV(m_outputSurfaceReg, QOpenGLBuffer::ReadOnly);
+- m_mapNV(1, &m_outputSurfaceReg);
+ }
+ }
+ return true;
+@@ -347,9 +347,11 @@ vector<MythVideoTexture*> MythVDPAUInterop::Acquire(MythRenderOpenGL *Context,
+ }
+
+ // Render surface
++ m_unmapNV(1, &m_outputSurfaceReg);
+ m_helper->MixerRender(m_mixer, surface, m_outputSurface, Scan,
+ Frame->interlaced_reversed ? !Frame->top_field_first :
+ Frame->top_field_first, m_referenceFrames);
++ m_mapNV(1, &m_outputSurfaceReg);
+ return m_openglTextures[DUMMY_INTEROP_ID];
+ }
+
+diff --git a/mythtv/libs/libmythtv/opengl/mythvdpauinterop.h b/mythtv/libs/libmythtv/opengl/mythvdpauinterop.h
+index 79f7bb40b8d..6a8b3bf6b9d 100644
+--- a/mythtv/libs/libmythtv/opengl/mythvdpauinterop.h
++++ b/mythtv/libs/libmythtv/opengl/mythvdpauinterop.h
+@@ -65,6 +65,7 @@ class MythVDPAUInterop : public MythOpenGLInterop
+ MYTH_VDPAUREGOUTSURFNV m_registerNV { nullptr };
+ MYTH_VDPAUSURFACCESSNV m_accessNV { nullptr };
+ MYTH_VDPAUMAPSURFNV m_mapNV { nullptr };
++ MYTH_VDPAUMAPSURFNV m_unmapNV { nullptr };
+ MythCodecID m_codec { kCodec_NONE };
+ bool m_preempted { false };
+ bool m_preemptedWarning { false };
+
+From 7bf1284867b94509cdbf473cb6e216c0c36145f1 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Fri, 10 Jul 2020 15:00:58 -0400
+Subject: [PATCH 073/165] Services: Add new Video GetSavedBookmark and
+ SetSavedBookmark methods
+
+Previously these methods only existed for recordings. Now adding
+similar methods for Videos.
+
+(cherry picked from commit 48557d32c25903c58042b9b4b2c71e5da8631390)
+---
+ .../services/videoServices.h | 6 ++
+ .../programs/mythbackend/services/video.cpp | 94 +++++++++++++++++++
+ mythtv/programs/mythbackend/services/video.h | 5 +
+ 3 files changed, 105 insertions(+)
+
+diff --git a/mythtv/libs/libmythservicecontracts/services/videoServices.h b/mythtv/libs/libmythservicecontracts/services/videoServices.h
+index 2ec7c6a333c..b1d439e1ed1 100644
+--- a/mythtv/libs/libmythservicecontracts/services/videoServices.h
++++ b/mythtv/libs/libmythservicecontracts/services/videoServices.h
+@@ -47,6 +47,7 @@ class SERVICE_PUBLIC VideoServices : public Service //, public QScriptable ???
+ Q_CLASSINFO( "RemoveVideoFromDB_Method", "POST" )
+ Q_CLASSINFO( "UpdateVideoWatchedStatus_Method", "POST" )
+ Q_CLASSINFO( "UpdateVideoMetadata_Method", "POST" )
++ Q_CLASSINFO( "SetSavedBookmark_Method", "POST" )
+
+ public:
+
+@@ -136,6 +137,11 @@ class SERVICE_PUBLIC VideoServices : public Service //, public QScriptable ???
+ virtual DTC::VideoStreamInfoList* GetStreamInfo ( const QString &StorageGroup,
+ const QString &FileName ) = 0;
+
++ virtual long GetSavedBookmark ( int Id) = 0;
++
++ virtual bool SetSavedBookmark ( int Id,
++ long Offset ) = 0;
++
+ };
+
+ #endif
+diff --git a/mythtv/programs/mythbackend/services/video.cpp b/mythtv/programs/mythbackend/services/video.cpp
+index bc023185665..03eef32d94a 100644
+--- a/mythtv/programs/mythbackend/services/video.cpp
++++ b/mythtv/programs/mythbackend/services/video.cpp
+@@ -831,6 +831,100 @@ DTC::VideoStreamInfoList* Video::GetStreamInfo
+ return pVideoStreamInfos;
+ }
+
++/////////////////////////////////////////////////////////////////////////////
++// Get bookmark of a video as a frame number.
++/////////////////////////////////////////////////////////////////////////////
++
++long Video::GetSavedBookmark( int Id )
++{
++ MSqlQuery query(MSqlQuery::InitCon());
++
++ query.prepare("SELECT filename "
++ "FROM videometadata "
++ "WHERE intid = :ID ");
++ query.bindValue(":ID", Id);
++
++ if (!query.exec())
++ {
++ MythDB::DBError("Video::GetSavedBookmark", query);
++ return 0;
++ }
++
++ QString fileName;
++
++ if (query.next())
++ fileName = query.value(0).toString();
++ else
++ {
++ LOG(VB_GENERAL, LOG_ERR, QString("Video/GetSavedBookmark Video id %1 Not found.").arg(Id));
++ return -1;
++ }
++
++ ProgramInfo pi(fileName,
++ nullptr, // _plot,
++ nullptr, // _title,
++ nullptr, // const QString &_sortTitle,
++ nullptr, // const QString &_subtitle,
++ nullptr, // const QString &_sortSubtitle,
++ nullptr, // const QString &_director,
++ 0, // int _season,
++ 0, // int _episode,
++ nullptr, // const QString &_inetref,
++ 0, // uint _length_in_minutes,
++ 0, // uint _year,
++ nullptr); //const QString &_programid);
++
++ long ret = pi.QueryBookmark();
++ return ret;
++}
++
++/////////////////////////////////////////////////////////////////////////////
++// Set bookmark of a video as a frame number.
++/////////////////////////////////////////////////////////////////////////////
++
++bool Video::SetSavedBookmark( int Id, long Offset )
++{
++ MSqlQuery query(MSqlQuery::InitCon());
++
++ query.prepare("SELECT filename "
++ "FROM videometadata "
++ "WHERE intid = :ID ");
++ query.bindValue(":ID", Id);
++
++ if (!query.exec())
++ {
++ MythDB::DBError("Video::SetSavedBookmark", query);
++ return false;
++ }
++
++ QString fileName;
++
++ if (query.next())
++ fileName = query.value(0).toString();
++ else
++ {
++ LOG(VB_GENERAL, LOG_ERR, QString("Video/SetSavedBookmark Video id %1 Not found.").arg(Id));
++ return false;
++ }
++
++ ProgramInfo pi(fileName,
++ nullptr, // _plot,
++ nullptr, // _title,
++ nullptr, // const QString &_sortTitle,
++ nullptr, // const QString &_subtitle,
++ nullptr, // const QString &_sortSubtitle,
++ nullptr, // const QString &_director,
++ 0, // int _season,
++ 0, // int _episode,
++ nullptr, // const QString &_inetref,
++ 0, // uint _length_in_minutes,
++ 0, // uint _year,
++ nullptr); //const QString &_programid);
++
++ pi.SaveBookmark(Offset);
++ return true;
++}
++
+ /////////////////////////////////////////////////////////////////////////////
+ //
+ /////////////////////////////////////////////////////////////////////////////
+diff --git a/mythtv/programs/mythbackend/services/video.h b/mythtv/programs/mythbackend/services/video.h
+index 5f49e65d1df..fdafadf1781 100644
+--- a/mythtv/programs/mythbackend/services/video.h
++++ b/mythtv/programs/mythbackend/services/video.h
+@@ -110,6 +110,11 @@ class Video : public VideoServices
+ const QString &Countries
+ ) override; // VideoServices
+
++ long GetSavedBookmark ( int Id ) override;
++
++ bool SetSavedBookmark ( int Id,
++ long Offset ) override;
++
+ /* Bluray Methods */
+
+ DTC::BlurayInfo* GetBluray ( const QString &Path ) override; // VideoServices
+
+From 167c8d56e20167a5cc39f1ab8b301313a562b929 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Thu, 16 Jul 2020 07:55:45 -0500
+Subject: [PATCH 074/165] Plugins/dbcheck: Replace CHARACTER SET 'default' with
+ 'utf8'
+
+Fix required for MySQL v8 because using 'default' CHARACTER
+SET results in a "You have an error in your SQL syntax"
+message.
+
+There are two cases to solve.
+
+ 1. New systems that will execute the DBSchemaVer changes
+ for the mytharchive, mythgame, mythmusic & mythweather
+ plugin tables. The origial Trac ticket.
+
+ 2. Existing systems that have up to date DBSchemaVer for
+ the above but need a new version to use the unambiguous
+ character set. Not solved here, just to get the the
+ above fixed.
+
+Refs #13577
+---
+ .../mytharchive/mytharchive/dbcheck.cpp | 10 +++----
+ mythplugins/mythgame/mythgame/dbcheck.cpp | 6 ++--
+ mythplugins/mythmusic/mythmusic/dbcheck.cpp | 30 +++++++++----------
+ .../mythweather/mythweather/dbcheck.cpp | 18 +++++------
+ 4 files changed, 32 insertions(+), 32 deletions(-)
+
+diff --git a/mythplugins/mytharchive/mytharchive/dbcheck.cpp b/mythplugins/mytharchive/mytharchive/dbcheck.cpp
+index 4bc4b4de57e..9f3cc0ae40c 100644
+--- a/mythplugins/mytharchive/mytharchive/dbcheck.cpp
++++ b/mythplugins/mytharchive/mytharchive/dbcheck.cpp
+@@ -143,12 +143,12 @@ bool UpgradeArchiveDatabaseSchema(void)
+ QString("ALTER DATABASE %1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;")
+ .arg(gContext->GetDatabaseParams().m_dbName),
+ "ALTER TABLE archiveitems"
+- " DEFAULT CHARACTER SET default,"
+- " MODIFY title varchar(128) CHARACTER SET utf8 default NULL,"
+- " MODIFY subtitle varchar(128) CHARACTER SET utf8 default NULL,"
++ " DEFAULT CHARACTER SET utf8,"
++ " MODIFY title varchar(128) CHARACTER SET utf8 NULL,"
++ " MODIFY subtitle varchar(128) CHARACTER SET utf8 NULL,"
+ " MODIFY description text CHARACTER SET utf8,"
+- " MODIFY startdate varchar(30) CHARACTER SET utf8 default NULL,"
+- " MODIFY starttime varchar(30) CHARACTER SET utf8 default NULL,"
++ " MODIFY startdate varchar(30) CHARACTER SET utf8 NULL,"
++ " MODIFY starttime varchar(30) CHARACTER SET utf8 NULL,"
+ " MODIFY filename text CHARACTER SET utf8 NOT NULL,"
+ " MODIFY cutlist text CHARACTER SET utf8;",
+ ""
+diff --git a/mythplugins/mythgame/mythgame/dbcheck.cpp b/mythplugins/mythgame/mythgame/dbcheck.cpp
+index dc7de5972cb..47f22fe68eb 100644
+--- a/mythplugins/mythgame/mythgame/dbcheck.cpp
++++ b/mythplugins/mythgame/mythgame/dbcheck.cpp
+@@ -343,7 +343,7 @@ QString("ALTER DATABASE %1 DEFAULT CHARACTER SET latin1;")
+ QString("ALTER DATABASE %1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;")
+ .arg(gContext->GetDatabaseParams().m_dbName),
+ "ALTER TABLE gamemetadata"
+-" DEFAULT CHARACTER SET default,"
++" DEFAULT CHARACTER SET utf8,"
+ " MODIFY `system` varchar(128) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY romname varchar(128) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY gamename varchar(128) CHARACTER SET utf8 NOT NULL default '',"
+@@ -356,7 +356,7 @@ QString("ALTER DATABASE %1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;")
+ " MODIFY crc_value varchar(64) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY version varchar(64) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE gameplayers"
+-" DEFAULT CHARACTER SET default,"
++" DEFAULT CHARACTER SET utf8,"
+ " MODIFY playername varchar(64) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY workingpath varchar(255) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY rompath varchar(255) CHARACTER SET utf8 NOT NULL default '',"
+@@ -365,7 +365,7 @@ QString("ALTER DATABASE %1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;")
+ " MODIFY gametype varchar(64) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY extensions varchar(128) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE romdb"
+-" DEFAULT CHARACTER SET default,"
++" DEFAULT CHARACTER SET utf8,"
+ " MODIFY crc varchar(64) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY name varchar(128) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY description varchar(128) CHARACTER SET utf8 NOT NULL default '',"
+diff --git a/mythplugins/mythmusic/mythmusic/dbcheck.cpp b/mythplugins/mythmusic/mythmusic/dbcheck.cpp
+index eef679d1c02..17c17666359 100644
+--- a/mythplugins/mythmusic/mythmusic/dbcheck.cpp
++++ b/mythplugins/mythmusic/mythmusic/dbcheck.cpp
+@@ -788,49 +788,49 @@ static bool doUpgradeMusicDatabaseSchema(QString &dbver)
+ .arg(gContext->GetDatabaseParams().m_dbName),
+ // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
+ "ALTER TABLE music_albumart"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY filename varchar(255) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE music_albums"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY album_name varchar(255) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE music_artists"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY artist_name varchar(255) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE music_directories"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY path text CHARACTER SET utf8 NOT NULL;",
+ "ALTER TABLE music_genres"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY genre varchar(255) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE music_playlists"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY playlist_name varchar(255) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY playlist_songs text CHARACTER SET utf8 NOT NULL,"
+ " MODIFY hostname varchar(64) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE music_smartplaylist_categories"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY name varchar(128) CHARACTER SET utf8 NOT NULL;",
+ "ALTER TABLE music_smartplaylist_items"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY field varchar(50) CHARACTER SET utf8 NOT NULL,"
+ " MODIFY operator varchar(20) CHARACTER SET utf8 NOT NULL,"
+ " MODIFY value1 varchar(255) CHARACTER SET utf8 NOT NULL,"
+ " MODIFY value2 varchar(255) CHARACTER SET utf8 NOT NULL;",
+ "ALTER TABLE music_smartplaylists"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY name varchar(128) CHARACTER SET utf8 NOT NULL,"
+ " MODIFY orderby varchar(128) CHARACTER SET utf8 NOT NULL default '';",
+ "ALTER TABLE music_songs"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY filename text CHARACTER SET utf8 NOT NULL,"
+ " MODIFY name varchar(255) CHARACTER SET utf8 NOT NULL default '',"
+ " MODIFY format varchar(4) CHARACTER SET utf8 NOT NULL default '0',"
+- " MODIFY mythdigest varchar(255) CHARACTER SET utf8 default NULL,"
+- " MODIFY description varchar(255) CHARACTER SET utf8 default NULL,"
+- " MODIFY comment varchar(255) CHARACTER SET utf8 default NULL,"
+- " MODIFY eq_preset varchar(255) CHARACTER SET utf8 default NULL;",
++ " MODIFY mythdigest varchar(255) CHARACTER SET utf8 NULL,"
++ " MODIFY description varchar(255) CHARACTER SET utf8 NULL,"
++ " MODIFY comment varchar(255) CHARACTER SET utf8 NULL,"
++ " MODIFY eq_preset varchar(255) CHARACTER SET utf8 NULL;",
+ "ALTER TABLE music_stats"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SEt utf8,"
+ " MODIFY total_time varchar(12) CHARACTER SET utf8 NOT NULL default '0',"
+ " MODIFY total_size varchar(10) CHARACTER SET utf8 NOT NULL default '0';",
+ ""
+diff --git a/mythplugins/mythweather/mythweather/dbcheck.cpp b/mythplugins/mythweather/mythweather/dbcheck.cpp
+index 0e5a053acee..50ac049becf 100644
+--- a/mythplugins/mythweather/mythweather/dbcheck.cpp
++++ b/mythplugins/mythweather/mythweather/dbcheck.cpp
+@@ -165,21 +165,21 @@ bool InitializeDatabase()
+ updates << QString("ALTER DATABASE %1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;")
+ .arg(gContext->GetDatabaseParams().m_dbName) <<
+ "ALTER TABLE weatherdatalayout"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SET utf8,"
+ " MODIFY location varchar(64) CHARACTER SET utf8 NOT NULL,"
+ " MODIFY dataitem varchar(64) CHARACTER SET utf8 NOT NULL;" <<
+ "ALTER TABLE weatherscreens"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SET utf8,"
+ " MODIFY container varchar(64) CHARACTER SET utf8 NOT NULL,"
+- " MODIFY hostname varchar(64) CHARACTER SET utf8 default NULL;" <<
++ " MODIFY hostname varchar(64) CHARACTER SET utf8 NULL;" <<
+ "ALTER TABLE weathersourcesettings"
+- " DEFAULT CHARACTER SET default,"
++ " DEFAULT CHARACTER SET utf8,"
+ " MODIFY source_name varchar(64) CHARACTER SET utf8 NOT NULL,"
+- " MODIFY hostname varchar(64) CHARACTER SET utf8 default NULL,"
+- " MODIFY path varchar(255) CHARACTER SET utf8 default NULL,"
+- " MODIFY author varchar(128) CHARACTER SET utf8 default NULL,"
+- " MODIFY version varchar(32) CHARACTER SET utf8 default NULL,"
+- " MODIFY email varchar(255) CHARACTER SET utf8 default NULL,"
++ " MODIFY hostname varchar(64) CHARACTER SET utf8 NULL,"
++ " MODIFY path varchar(255) CHARACTER SET utf8 NULL,"
++ " MODIFY author varchar(128) CHARACTER SET utf8 NULL,"
++ " MODIFY version varchar(32) CHARACTER SET utf8 NULL,"
++ " MODIFY email varchar(255) CHARACTER SET utf8 NULL,"
+ " MODIFY types mediumtext CHARACTER SET utf8;";
+
+ if (!performActualUpdate(updates, "1003", dbver))
+
+From 62af47c971c30d2201705302ab260147b7fffd26 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Sat, 13 Jun 2020 23:50:45 +0000
+Subject: [PATCH 075/165] add missing(?) log message when grabber interrupted
+
+Signed-off-by: David Hampton <mythtv(a)love2code.net>
+(cherry picked from commit da860e00f7ca51e709485cb7358546a551b46828)
+---
+ mythtv/programs/mythfilldatabase/filldata.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/programs/mythfilldatabase/filldata.cpp b/mythtv/programs/mythfilldatabase/filldata.cpp
+index f320857c186..db751bebf39 100644
+--- a/mythtv/programs/mythfilldatabase/filldata.cpp
++++ b/mythtv/programs/mythfilldatabase/filldata.cpp
+@@ -194,6 +194,8 @@ bool FillData::GrabData(const Source& source, int offset)
+ {
+ m_interrupted = true;
+ status = QObject::tr("FAILED: XMLTV grabber ran but was interrupted.");
++ LOG(VB_GENERAL, LOG_ERR,
++ QString("XMLTV grabber ran but was interrupted."));
+ }
+ else
+ {
+
+From 396dd023e60aecf667d2e7affa63bb18b468c660 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Sat, 13 Jun 2020 23:54:59 +0000
+Subject: [PATCH 076/165] eliminate extranous LOC in logging
+
+All the other LOG_ERR logging in mythfilldata do not
+include the LOC field. Eliminate it for consistency
+in the logging output
+
+Signed-off-by: David Hampton <mythtv(a)love2code.net>
+(cherry picked from commit a8eeda8f0ed97cc6aca025d2a517110eb11b5aa9)
+---
+ mythtv/programs/mythfilldatabase/filldata.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/programs/mythfilldatabase/filldata.cpp b/mythtv/programs/mythfilldatabase/filldata.cpp
+index db751bebf39..1e42900832e 100644
+--- a/mythtv/programs/mythfilldatabase/filldata.cpp
++++ b/mythtv/programs/mythfilldatabase/filldata.cpp
+@@ -201,7 +201,7 @@ bool FillData::GrabData(const Source& source, int offset)
+ {
+ status = QObject::tr("FAILED: XMLTV grabber returned error code %1.")
+ .arg(systemcall_status);
+- LOG(VB_GENERAL, LOG_ERR, LOC +
++ LOG(VB_GENERAL, LOG_ERR,
+ QString("XMLTV grabber returned error code %1")
+ .arg(systemcall_status));
+ }
+
+From 85dec4804c286483794bb4d11646ef1ff910c0ee Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Sun, 14 Jun 2020 00:01:39 +0000
+Subject: [PATCH 077/165] Enable output from the grabber to be logged
+
+Invoke the running of the grabber such that the output is
+actually captured and logged if the verbose xmltv option
+is specified (the header/trailer implies that at some time
+in the past the output was made available in the logs, but
+it has not been included for some time).
+
+Signed-off-by: David Hampton <mythtv(a)love2code.net>
+(cherry picked from commit be1c88665a79e654f12f818944732101b0fcbcfc)
+---
+ mythtv/programs/mythfilldatabase/filldata.cpp | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/programs/mythfilldatabase/filldata.cpp b/mythtv/programs/mythfilldatabase/filldata.cpp
+index 1e42900832e..e3c97bd8016 100644
+--- a/mythtv/programs/mythfilldatabase/filldata.cpp
++++ b/mythtv/programs/mythfilldatabase/filldata.cpp
+@@ -178,9 +178,20 @@ bool FillData::GrabData(const Source& source, int offset)
+ LOG(VB_XMLTV, LOG_INFO,
+ "----------------- Start of XMLTV output -----------------");
+
+- uint systemcall_status = myth_system(command, kMSRunShell);
++ MythSystemLegacy run_grabber(command, kMSRunShell | kMSStdErr);
++
++ run_grabber.Run();
++ uint systemcall_status = run_grabber.Wait();
+ bool succeeded = (systemcall_status == GENERIC_EXIT_OK);
+
++ QByteArray result = run_grabber.ReadAllErr();
++ QTextStream ostream(result);
++ while (!ostream.atEnd())
++ {
++ QString line = ostream.readLine().simplified();
++ LOG(VB_XMLTV, LOG_INFO, line);
++ }
++
+ LOG(VB_XMLTV, LOG_INFO,
+ "------------------ End of XMLTV output ------------------");
+
+
+From 8602e978777fc20bea760ff37b620017f77a1fc1 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Sun, 14 Jun 2020 00:19:39 +0000
+Subject: [PATCH 078/165] Update XMLTV loglevel in programdata
+
+Change the loglevel in programdata to be the same as
+EIT updates for the equivalent changes (i.e. debug).
+This allows a run of mythfilldatabase with the options
+of --verbose general,xmltv with the default loglevel
+of info to not blather on about the expected case of
+adding, deleting, updating programs (i.e. making the
+usage easier to review), but just include the XMLTV
+logging that likely matters for the average use case.
+
+Last of four patches. Fixes #13633.
+
+Signed-off-by: David Hampton <mythtv(a)love2code.net>
+(cherry picked from commit 6e61aa988fe1cbc260798d5be53c496f66b04f4f)
+---
+ mythtv/libs/libmythtv/programdata.cpp | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/programdata.cpp b/mythtv/libs/libmythtv/programdata.cpp
+index e32c678e861..8495e941558 100644
+--- a/mythtv/libs/libmythtv/programdata.cpp
++++ b/mythtv/libs/libmythtv/programdata.cpp
+@@ -1205,7 +1205,7 @@ void ProgInfo::Squeeze(void)
+ */
+ uint ProgInfo::InsertDB(MSqlQuery &query, uint chanid) const
+ {
+- LOG(VB_XMLTV, LOG_INFO,
++ LOG(VB_XMLTV, LOG_DEBUG,
+ QString("Inserting new program : %1 - %2 %3 %4")
+ .arg(m_starttime.toString(Qt::ISODate))
+ .arg(m_endtime.toString(Qt::ISODate))
+@@ -1426,14 +1426,14 @@ void ProgramData::FixProgramList(QList<ProgInfo*> &fixlist)
+ tokeep = it, todelete = cur;
+
+
+- LOG(VB_XMLTV, LOG_INFO,
++ LOG(VB_XMLTV, LOG_DEBUG,
+ QString("Removing conflicting program: %1 - %2 %3 %4")
+ .arg((*todelete)->m_starttime.toString(Qt::ISODate))
+ .arg((*todelete)->m_endtime.toString(Qt::ISODate))
+ .arg((*todelete)->m_channel)
+ .arg((*todelete)->m_title));
+
+- LOG(VB_XMLTV, LOG_INFO,
++ LOG(VB_XMLTV, LOG_DEBUG,
+ QString("Conflicted with : %1 - %2 %3 %4")
+ .arg((*tokeep)->m_starttime.toString(Qt::ISODate))
+ .arg((*tokeep)->m_endtime.toString(Qt::ISODate))
+@@ -1684,7 +1684,7 @@ bool ProgramData::IsUnchanged(
+ bool ProgramData::DeleteOverlaps(
+ MSqlQuery &query, uint chanid, const ProgInfo &pi)
+ {
+- if (VERBOSE_LEVEL_CHECK(VB_XMLTV, LOG_INFO))
++ if (VERBOSE_LEVEL_CHECK(VB_XMLTV, LOG_DEBUG))
+ {
+ // Get overlaps..
+ query.prepare(
+@@ -1705,7 +1705,7 @@ bool ProgramData::DeleteOverlaps(
+
+ do
+ {
+- LOG(VB_XMLTV, LOG_INFO,
++ LOG(VB_XMLTV, LOG_DEBUG,
+ QString("Removing existing program: %1 - %2 %3 %4")
+ .arg(MythDate::as_utc(query.value(1).toDateTime()).toString(Qt::ISODate))
+ .arg(MythDate::as_utc(query.value(2).toDateTime()).toString(Qt::ISODate))
+
+From 89d1991ef285567f8b46f287786b2d8ca1fb9236 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Wed, 15 Jul 2020 15:13:26 -0400
+Subject: [PATCH 079/165] Android: Fix support for android 5
+
+For android 5 we need to build with api level 21 and that level does
+not have the ftello and fseeko functions.
+
+(cherry picked from commit b76dbf4214614fa4cd572cc38c62481bb97e0146)
+---
+ mythtv/libs/libmythbase/mythcommandlineparser.cpp | 6 ++++++
+ mythtv/programs/mythcommflag/main.cpp | 7 +++++++
+ 2 files changed, 13 insertions(+)
+
+diff --git a/mythtv/libs/libmythbase/mythcommandlineparser.cpp b/mythtv/libs/libmythbase/mythcommandlineparser.cpp
+index e58f2a55b0d..d20ad9d29e3 100644
+--- a/mythtv/libs/libmythbase/mythcommandlineparser.cpp
++++ b/mythtv/libs/libmythbase/mythcommandlineparser.cpp
+@@ -20,6 +20,12 @@
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
++#if defined ANDROID && __ANDROID_API__ < 24
++// ftello and fseeko do not exist in android before api level 24
++#define ftello ftell
++#define fseeko fseek
++#endif
++
+ // C++ headers
+ #include <algorithm>
+ #include <csignal>
+diff --git a/mythtv/programs/mythcommflag/main.cpp b/mythtv/programs/mythcommflag/main.cpp
+index a9575bf18ff..f6e60ab8419 100644
+--- a/mythtv/programs/mythcommflag/main.cpp
++++ b/mythtv/programs/mythcommflag/main.cpp
+@@ -1,3 +1,10 @@
++
++#if defined ANDROID && __ANDROID_API__ < 24
++// ftello and fseeko do not exist in android before api level 24
++#define ftello ftell
++#define fseeko fseek
++#endif
++
+ // POSIX headers
+ #include <unistd.h>
+ #include <sys/time.h> // for gettimeofday
+
+From f7a1f4b1a0204f624985d872395c7d689ca91da2 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Thu, 16 Jul 2020 21:26:02 +0200
+Subject: [PATCH 080/165] Create key for DVB channel master lock only once
+
+Create master key only once and use that each time the lock is requested instead of creating the key every time again. This solves the problem that the database is accessed 40 times per second for the source ID of a capture card for each capture card while monitoring the signal status.
+
+Fixes #13649
+
+(cherry picked from commit 7277ae9af33093bdf50b532888bd49b5a2d69bb0)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ .../libs/libmythtv/recorders/dvbchannel.cpp | 31 +++++++------------
+ mythtv/libs/libmythtv/recorders/dvbchannel.h | 3 +-
+ 2 files changed, 14 insertions(+), 20 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/dvbchannel.cpp b/mythtv/libs/libmythtv/recorders/dvbchannel.cpp
+index 9158cd75548..47957a16c7e 100644
+--- a/mythtv/libs/libmythtv/recorders/dvbchannel.cpp
++++ b/mythtv/libs/libmythtv/recorders/dvbchannel.cpp
+@@ -77,12 +77,13 @@ DVBChannel::DVBChannel(QString aDevice, TVRec *parent)
+ : DTVChannel(parent), m_device(std::move(aDevice))
+ {
+ s_master_map_lock.lockForWrite();
+- QString key = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, m_device);
++ m_key = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, m_device);
+ if (m_pParent)
+- key += QString(":%1")
++ m_key += QString(":%1")
+ .arg(CardUtil::GetSourceID(m_pParent->GetInputId()));
+- s_master_map[key].push_back(this); // == RegisterForMaster
+- auto *master = static_cast<DVBChannel*>(s_master_map[key].front());
++
++ s_master_map[m_key].push_back(this); // == RegisterForMaster
++ auto *master = dynamic_cast<DVBChannel*>(s_master_map[m_key].front());
+ if (master == this)
+ {
+ m_dvbCam = new DVBCam(m_device);
+@@ -103,17 +104,13 @@ DVBChannel::~DVBChannel()
+ // set a new master if there are other instances and we're the master
+ // whether we are the master or not remove us from the map..
+ s_master_map_lock.lockForWrite();
+- QString key = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, m_device);
+- if (m_pParent)
+- key += QString(":%1")
+- .arg(CardUtil::GetSourceID(m_pParent->GetInputId()));
+- auto *master = static_cast<DVBChannel*>(s_master_map[key].front());
++ auto *master = dynamic_cast<DVBChannel*>(s_master_map[m_key].front());
+ if (master == this)
+ {
+- s_master_map[key].pop_front();
++ s_master_map[m_key].pop_front();
+ DVBChannel *new_master = nullptr;
+- if (!s_master_map[key].empty())
+- new_master = dynamic_cast<DVBChannel*>(s_master_map[key].front());
++ if (!s_master_map[m_key].empty())
++ new_master = dynamic_cast<DVBChannel*>(s_master_map[m_key].front());
+ if (new_master)
+ {
+ QMutexLocker master_locker(&(master->m_hwLock));
+@@ -123,7 +120,7 @@ DVBChannel::~DVBChannel()
+ }
+ else
+ {
+- s_master_map[key].removeAll(this);
++ s_master_map[m_key].removeAll(this);
+ }
+ s_master_map_lock.unlock();
+
+@@ -131,7 +128,7 @@ DVBChannel::~DVBChannel()
+
+ // if we're the last one out delete dvbcam
+ s_master_map_lock.lockForRead();
+- MasterMap::iterator mit = s_master_map.find(key);
++ MasterMap::iterator mit = s_master_map.find(m_key);
+ if ((*mit).empty())
+ delete m_dvbCam;
+ m_dvbCam = nullptr;
+@@ -1358,11 +1355,7 @@ void DVBChannel::ReturnMasterLock(DVBChannel* &dvbm)
+
+ DVBChannel *DVBChannel::GetMasterLock(void) const
+ {
+- QString key = CardUtil::GetDeviceName(DVB_DEV_FRONTEND, m_device);
+- if (m_pParent)
+- key += QString(":%1")
+- .arg(CardUtil::GetSourceID(m_pParent->GetInputId()));
+- DTVChannel *master = DTVChannel::GetMasterLock(key);
++ DTVChannel *master = DTVChannel::GetMasterLock(m_key);
+ auto *dvbm = dynamic_cast<DVBChannel*>(master);
+ if (master && !dvbm)
+ DTVChannel::ReturnMasterLock(master);
+diff --git a/mythtv/libs/libmythtv/recorders/dvbchannel.h b/mythtv/libs/libmythtv/recorders/dvbchannel.h
+index 2ce0898e4dc..99caaabf3a1 100644
+--- a/mythtv/libs/libmythtv/recorders/dvbchannel.h
++++ b/mythtv/libs/libmythtv/recorders/dvbchannel.h
+@@ -162,7 +162,8 @@ class DVBChannel : public DTVChannel
+ // Other State
+ /// File descriptor for tuning hardware
+ int m_fdFrontend {-1};
+- QString m_device; ///< DVB Device
++ QString m_device; // DVB Device
++ QString m_key; // master lock key
+ /// true iff our driver munges PMT
+ bool m_hasCrcBug {false};
+
+
+From 05a613f9faaf9193f86fc29a3e42e730e401a27f Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Thu, 30 Jul 2020 08:58:37 -0500
+Subject: [PATCH 081/165] dbcheck: quote yet another MySQL v8 reserved work
+
+Forum user reports being unable to upgrade a 0.25 DB to v31.
+
+Enclose the function column name in grave accents.
+---
+ mythtv/libs/libmythtv/dbcheck.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/dbcheck.cpp b/mythtv/libs/libmythtv/dbcheck.cpp
+index 329a716a4db..90915e6cc8c 100644
+--- a/mythtv/libs/libmythtv/dbcheck.cpp
++++ b/mythtv/libs/libmythtv/dbcheck.cpp
+@@ -1787,7 +1787,7 @@ nullptr
+ " ADD COLUMN tid INT(11) NOT NULL DEFAULT '0' AFTER pid, "
+ " ADD COLUMN filename VARCHAR(255) NOT NULL DEFAULT '' AFTER thread, "
+ " ADD COLUMN line INT(11) NOT NULL DEFAULT '0' AFTER filename, "
+-" ADD COLUMN function VARCHAR(255) NOT NULL DEFAULT '' AFTER line;",
++" ADD COLUMN `function` VARCHAR(255) NOT NULL DEFAULT '' AFTER line;",
+ nullptr
+ };
+
+
+From e537ea801af3a1d69c6fd0dbf8060ff22ba34cf2 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Wed, 29 Jul 2020 16:08:57 +0100
+Subject: [PATCH 082/165] Wayland: Fix alpha blending
+
+- each window in wayland has its own buffer/texture and these are always
+composited with alpha blending
+- as a result any alpha blended areas of our UI will allow the
+underlying window to be visible if the window/surface buffer has a
+buffer with alpha
+- usually the default surface format does not request a buffer with
+alpha but when wayland decorations are enabled, Qt overrides the alpha
+depth
+- so as a workaround, disable Qt wayland decorations, which we don't
+need anyway
+- note - this may not be the best solution. Using
+wl_surface_set_opaque_region on our surface would allow the compositor
+to optimise rendering as it knows it does not need to show anything
+hidden by the window. In testing this works but requires linking to
+libwayland-client and including Qt private headers (which is far from
+ideal)
+
+- Fixes #13483
+
+(cherry picked from commit b6e7e18a4c209a0dd246c4624db918af0d5152ff)
+---
+ mythtv/libs/libmythui/mythdisplay.cpp | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/mythtv/libs/libmythui/mythdisplay.cpp b/mythtv/libs/libmythui/mythdisplay.cpp
+index cf9b1d795c7..2470ef3ae8c 100644
+--- a/mythtv/libs/libmythui/mythdisplay.cpp
++++ b/mythtv/libs/libmythui/mythdisplay.cpp
+@@ -1059,6 +1059,7 @@ void MythDisplay::ConfigureQtGUI(int SwapInterval)
+ {
+ // Set the default surface format. Explicitly required on some platforms.
+ QSurfaceFormat format;
++ format.setAlphaBufferSize(0);
+ format.setDepthBufferSize(0);
+ format.setStencilBufferSize(0);
+ format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
+@@ -1071,6 +1072,20 @@ void MythDisplay::ConfigureQtGUI(int SwapInterval)
+ // of the MythPushButton widgets, and they don't use the themed background.
+ QApplication::setDesktopSettingsAware(false);
+ #endif
++
++ // If Wayland decorations are enabled, the default framebuffer format is forced
++ // to use alpha. This framebuffer is rendered with alpha blending by the wayland
++ // compositor - so any translucent areas of our UI will allow the underlying
++ // window to bleed through.
++ // N.B. this is probably not the most performant solution as compositors MAY
++ // still render hidden windows. A better solution is probably to call
++ // wl_surface_set_opaque_region on the wayland surface. This is confirmed to work
++ // and should allow the compositor to optimise rendering for opaque areas. It does
++ // however require linking to libwayland-client AND including private Qt headers
++ // to retrieve the surface and compositor structures (the latter being a significant issue).
++ // see also setAlphaBufferSize above
++ setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 0);
++
+ #if defined (Q_OS_LINUX) && defined (USING_EGL)
+ // We want to use EGL for VAAPI/MMAL/DRMPRIME rendering to ensure we
+ // can use zero copy video buffers for the best performance (N.B. not tested
+
+From 3322b374c1850fd2fec170db15a3be349db73b8d Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Tue, 11 Aug 2020 13:12:38 -0500
+Subject: [PATCH 083/165] mythfilldatabase: mark --dd-grab-all as deprecated
+
+Add a log warning, but continue running.
+
+(cherry picked from commit 91a3646e5bffc03638f3a75b15c2bc04c73fa746)
+---
+ mythtv/programs/mythfilldatabase/commandlineparser.cpp | 4 ++++
+ mythtv/programs/mythfilldatabase/main.cpp | 4 ++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/mythtv/programs/mythfilldatabase/commandlineparser.cpp b/mythtv/programs/mythfilldatabase/commandlineparser.cpp
+index 787e46b5f0b..b3b9f7e38d5 100644
+--- a/mythtv/programs/mythfilldatabase/commandlineparser.cpp
++++ b/mythtv/programs/mythfilldatabase/commandlineparser.cpp
+@@ -164,4 +164,8 @@ void MythFillDatabaseCommandLineParser::LoadArguments(void)
+ add("--mark-repeats", "oldmarkrepeats", "", "", "")
+ ->SetRemoved("This is now the default behavior. Use\n"
+ " --no-mark-repeats to disable.", "0.25");
++ add("--dd-grab-all", "ddgraball", false, "", "")
++ ->SetDeprecated("It's no longer valid with Schedules Direct XMLTV.\n"
++ " Remove in mythtv-setup General -> Program Schedule\n"
++ " -> Downloading Options -> Guide Data Arguements");
+ }
+diff --git a/mythtv/programs/mythfilldatabase/main.cpp b/mythtv/programs/mythfilldatabase/main.cpp
+index ef20217f1ce..b197347128e 100644
+--- a/mythtv/programs/mythfilldatabase/main.cpp
++++ b/mythtv/programs/mythfilldatabase/main.cpp
+@@ -82,6 +82,10 @@ int main(int argc, char *argv[])
+ if (retval != GENERIC_EXIT_OK)
+ return retval;
+
++ if (cmdline.toBool("ddgraball"))
++ LOG(VB_GENERAL, LOG_WARNING,
++ "Invalid option, see: mythfilldatabase --help dd-grab-all");
++
+ if (cmdline.toBool("manual"))
+ {
+ cout << "###\n";
+
+From fb389f2100bc5179390de1cbab80cb410b1e2520 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Sun, 16 Aug 2020 14:18:40 -0500
+Subject: [PATCH 084/165] Fix issue with daily and weekly, manual, recording
+ rules.
+
+Commit 5f6697ec removed the setting of subtitle to the recording time.
+That broke duplicate checking in most cases. This change forces all
+future rules to use no duplicate checking.
+
+(cherry picked from commit a28191023de31e36efaf23a2d837b4b1725ec73b)
+---
+ mythtv/programs/mythbackend/services/dvr.cpp | 10 ++++++++--
+ mythtv/programs/mythfrontend/manualschedule.cpp | 1 +
+ mythtv/programs/mythfrontend/scheduleeditor.cpp | 5 ++++-
+ 3 files changed, 13 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/services/dvr.cpp b/mythtv/programs/mythbackend/services/dvr.cpp
+index d5b2702877f..46cf87e29ef 100644
+--- a/mythtv/programs/mythbackend/services/dvr.cpp
++++ b/mythtv/programs/mythbackend/services/dvr.cpp
+@@ -1141,7 +1141,10 @@ uint Dvr::AddRecordSchedule (
+
+ rule.m_type = recTypeFromString(sType);
+ rule.m_searchType = searchTypeFromString(sSearchType);
+- rule.m_dupMethod = dupMethodFromString(sDupMethod);
++ if (rule.m_searchType == kManualSearch)
++ rule.m_dupMethod = kDupCheckNone;
++ else
++ rule.m_dupMethod = dupMethodFromString(sDupMethod);
+ rule.m_dupIn = dupInFromString(sDupIn);
+
+ if (sRecProfile.isEmpty())
+@@ -1284,7 +1287,10 @@ bool Dvr::UpdateRecordSchedule ( uint nRecordId,
+
+ pRule.m_type = recTypeFromString(sType);
+ pRule.m_searchType = searchTypeFromString(sSearchType);
+- pRule.m_dupMethod = dupMethodFromString(sDupMethod);
++ if (pRule.m_searchType == kManualSearch)
++ pRule.m_dupMethod = kDupCheckNone;
++ else
++ pRule.m_dupMethod = dupMethodFromString(sDupMethod);
+ pRule.m_dupIn = dupInFromString(sDupIn);
+
+ if (sRecProfile.isEmpty())
+diff --git a/mythtv/programs/mythfrontend/manualschedule.cpp b/mythtv/programs/mythfrontend/manualschedule.cpp
+index e8e2b349b02..521f8707f68 100644
+--- a/mythtv/programs/mythfrontend/manualschedule.cpp
++++ b/mythtv/programs/mythfrontend/manualschedule.cpp
+@@ -219,6 +219,7 @@ void ManualSchedule::recordClicked(void)
+ auto *record = new RecordingRule();
+ record->LoadByProgram(&p);
+ record->m_searchType = kManualSearch;
++ record->m_dupMethod = kDupCheckNone;
+
+ MythScreenStack *mainStack = GetMythMainWindow()->GetMainStack();
+ auto *schededit = new ScheduleEditor(mainStack, record);
+diff --git a/mythtv/programs/mythfrontend/scheduleeditor.cpp b/mythtv/programs/mythfrontend/scheduleeditor.cpp
+index cdb7f6d68b5..ddd18d4e26c 100644
+--- a/mythtv/programs/mythfrontend/scheduleeditor.cpp
++++ b/mythtv/programs/mythfrontend/scheduleeditor.cpp
+@@ -2138,6 +2138,7 @@ void SchedOptMixin::RuleChanged(void)
+ m_rule->m_type != kDontRecord);
+ bool isSingle = (m_rule->m_type == kSingleRecord ||
+ m_rule->m_type == kOverrideRecord);
++ bool isManual = (m_rule->m_searchType == kManualSearch);
+
+ if (m_prioritySpin)
+ m_prioritySpin->SetEnabled(isScheduled);
+@@ -2146,7 +2147,9 @@ void SchedOptMixin::RuleChanged(void)
+ if (m_endoffsetSpin)
+ m_endoffsetSpin->SetEnabled(isScheduled);
+ if (m_dupmethodList)
+- m_dupmethodList->SetEnabled(isScheduled && !isSingle);
++ m_dupmethodList->SetEnabled(
++ isScheduled && !isSingle &&
++ (!isManual || m_rule->m_dupMethod != kDupCheckNone));
+ if (m_dupscopeList)
+ m_dupscopeList->SetEnabled(isScheduled && !isSingle &&
+ m_rule->m_dupMethod != kDupCheckNone);
+
+From d3088629deadc957eb26ba3f6b16698b6e7f668b Mon Sep 17 00:00:00 2001
+From: Stuart Auchterlonie <stuarta(a)mythtv.org>
+Date: Tue, 18 Aug 2020 22:45:26 +0100
+Subject: [PATCH 085/165] Refs #12307 - Respect the user setting to disable
+ media monitor
+
+Also fix a typo in the message stating it is disabled.
+
+(cherry picked from commit d09f11da0e946a099cfbe04f5b64c32eb3e7ee64)
+---
+ mythtv/libs/libmyth/mediamonitor-unix.cpp | 9 ++++++++-
+ mythtv/libs/libmyth/mythmediamonitor.cpp | 2 +-
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmyth/mediamonitor-unix.cpp b/mythtv/libs/libmyth/mediamonitor-unix.cpp
+index 04df271132e..367b0f8c164 100644
+--- a/mythtv/libs/libmyth/mediamonitor-unix.cpp
++++ b/mythtv/libs/libmyth/mediamonitor-unix.cpp
+@@ -38,6 +38,7 @@ using namespace std;
+ #include "mythmediamonitor.h"
+ #include "mediamonitor-unix.h"
+ #include "mythconfig.h"
++#include "mythcorecontext.h"
+ #include "mythcdrom.h"
+ #include "mythhdd.h"
+ #include "mythlogging.h"
+@@ -119,7 +120,13 @@ MediaMonitorUnix::MediaMonitorUnix(QObject* par,
+ : MediaMonitor(par, interval, allowEject)
+ {
+ CheckFileSystemTable();
+- CheckMountable();
++ if (!gCoreContext->GetBoolSetting("MonitorDrives", false)) {
++ LOG(VB_GENERAL, LOG_NOTICE, "MediaMonitor disabled by user setting.");
++ }
++ else
++ {
++ CheckMountable();
++ }
+
+ LOG(VB_MEDIA, LOG_INFO, "Initial device list...\n" + listDevices());
+ }
+diff --git a/mythtv/libs/libmyth/mythmediamonitor.cpp b/mythtv/libs/libmyth/mythmediamonitor.cpp
+index c93de12c9fe..d9bf7ae2630 100644
+--- a/mythtv/libs/libmyth/mythmediamonitor.cpp
++++ b/mythtv/libs/libmyth/mythmediamonitor.cpp
+@@ -460,7 +460,7 @@ void MediaMonitor::StartMonitoring(void)
+ if (m_Active)
+ return;
+ if (!gCoreContext->GetBoolSetting("MonitorDrives", false)) {
+- LOG(VB_MEDIA, LOG_NOTICE, "MediaMonitor diasabled by user setting.");
++ LOG(VB_MEDIA, LOG_NOTICE, "MediaMonitor disabled by user setting.");
+ return;
+ }
+
+
+From 623192215ae0d08af094a11e148efdac664eb2bc Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sun, 23 Aug 2020 21:09:34 +0200
+Subject: [PATCH 086/165] Accept VBOX version numbers starting with VT
+
+(cherry picked from commit 083367b4907afbb40c5ee0adbb402a1aabc92468)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/recorders/vboxutils.cpp | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/vboxutils.cpp b/mythtv/libs/libmythtv/recorders/vboxutils.cpp
+index bc316e4ebfa..d3fff12317b 100644
+--- a/mythtv/libs/libmythtv/recorders/vboxutils.cpp
++++ b/mythtv/libs/libmythtv/recorders/vboxutils.cpp
+@@ -240,7 +240,8 @@ bool VBox::checkVersion(QString &version)
+ sList = version.split('.');
+
+ // sanity check this looks like a VBox version string
+- if (sList.count() < 3 || !(version.startsWith("VB.") || version.startsWith("VJ.")))
++ if (sList.count() < 3 || !(version.startsWith("VB.") || version.startsWith("VJ.")
++ || version.startsWith("VT.")))
+ {
+ LOG(VB_GENERAL, LOG_INFO, LOC + QString("Failed to parse version from %1").arg(version));
+ delete xmlDoc;
+
+From aac5e7f0f454a95a69de15f40d62306f11033060 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Thu, 27 Aug 2020 16:10:55 -0500
+Subject: [PATCH 087/165] mythfilldatabase: Change one more LOG to debug
+
+For users that don't run MFDB using the --only-update-guide
+switch, only print these with xmltv:debug:
+
+ Match found for xmltvid I30415.json.schedulesdirect.org to channel WTTW-HD (11101)
+
+Eliminates potentially hundreds of message on every run.
+
+(cherry picked from commit bcbcb356dc8ceaf3579474e6772fd451eb8d8fd7)
+---
+ mythtv/programs/mythfilldatabase/channeldata.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/programs/mythfilldatabase/channeldata.cpp b/mythtv/programs/mythfilldatabase/channeldata.cpp
+index 5b2c5811427..caafde56b11 100644
+--- a/mythtv/programs/mythfilldatabase/channeldata.cpp
++++ b/mythtv/programs/mythfilldatabase/channeldata.cpp
+@@ -264,7 +264,7 @@ void ChannelData::handleChannels(int id, ChannelInfoList *chanlist)
+ ChannelInfo dbChan = FindMatchingChannel(*i, existingChannels);
+ if (dbChan.m_chanId > 0) // Channel exists, updating
+ {
+- LOG(VB_XMLTV, LOG_NOTICE,
++ LOG(VB_XMLTV, LOG_DEBUG,
+ QString("Match found for xmltvid %1 to channel %2 (%3)")
+ .arg((*i).m_xmltvId).arg(dbChan.m_name).arg(dbChan.m_chanId));
+ if (m_interactive)
+
+From 5c395c59e26a1989c2cc1aced55506dbc1d9be7a Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Wed, 26 Aug 2020 22:51:23 +0200
+Subject: [PATCH 088/165] Python Bindings: Open video/recoring in binary mode
+
+Since the exported function `ftopen` is meant for videos or
+recordings, open those type of files in binary mode.
+
+Tested with python2.7 and python3.6
+
+Refs #13475
+
+Thanks to Jay Harbeston, who reported this issue
+See
+http://lists.mythtv.org/pipermail/mythtv-users/2020-August/404781.html
+
+(cherry picked from commit c2ff157ca0a983850f372dd90a66352c8f3c8062)
+---
+ mythtv/bindings/python/MythTV/mythproto.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py b/mythtv/bindings/python/MythTV/mythproto.py
+index 6b00ba222cb..5aa730cef87 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -288,7 +288,7 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+ for sg in sgs:
+ if sg.dirname in path:
+ if sg.local:
+- return open(sg.dirname+filename, mode)
++ return open(sg.dirname+filename, mode+'b')
+ else:
+ return protoopen(host, filename, sgroup)
+
+@@ -307,9 +307,9 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+ path = sg.dirname+filename.rsplit('/',1)[0]
+ if not os.access(path, os.F_OK):
+ os.makedirs(path)
+- log(log.FILE, log.INFO, 'Opening local file (w)',
++ log(log.FILE, log.INFO, 'Opening local file (wb)',
+ sg.dirname+filename)
+- return open(sg.dirname+filename, mode)
++ return open(sg.dirname+filename, mode+'b')
+
+ # fallback to remote write
+ else:
+@@ -322,9 +322,9 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+ sg = findfile(filename, sgroup, db)
+ if sg is not None:
+ # file found, open local
+- log(log.FILE, log.INFO, 'Opening local file (r)',
++ log(log.FILE, log.INFO, 'Opening local file (rb)',
+ sg.dirname+filename)
+- return open(sg.dirname+filename, mode)
++ return open(sg.dirname+filename, mode+'b')
+ else:
+ # file not found, open remote
+ return protoopen(host, filename, sgroup)
+
+From ab0c38a4764c29019f1fe10c8a8315bb85d65150 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Wed, 26 Aug 2020 23:50:26 +0200
+Subject: [PATCH 089/165] Python Bindings: Add robustness on using paths to
+ videos or recordings
+
+Storage Group paths may be defined with or without trailing slash ('/').
+Accept both.
+
+Thanks to Jay Harbeston, who reported this issue
+See
+http://lists.mythtv.org/pipermail/mythtv-users/2020-August/404781.html
+
+(cherry picked from commit a9736fc1d24d1eacee80a418249d8e6ae78a6f3f)
+---
+ mythtv/bindings/python/MythTV/mythproto.py | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/mythproto.py b/mythtv/bindings/python/MythTV/mythproto.py
+index 5aa730cef87..a89d50f6ef4 100644
+--- a/mythtv/bindings/python/MythTV/mythproto.py
++++ b/mythtv/bindings/python/MythTV/mythproto.py
+@@ -288,7 +288,7 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+ for sg in sgs:
+ if sg.dirname in path:
+ if sg.local:
+- return open(sg.dirname+filename, mode+'b')
++ return open(os.path.join(sg.dirname, filename), mode+'b')
+ else:
+ return protoopen(host, filename, sgroup)
+
+@@ -304,12 +304,12 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+ sg = sorted(sgs, key=lambda sg: sg.free, reverse=True)[0]
+ # create folder if it does not exist
+ if filename.find('/') != -1:
+- path = sg.dirname+filename.rsplit('/',1)[0]
++ path = os.path.join(sg.dirname, filename.rsplit('/',1)[0])
+ if not os.access(path, os.F_OK):
+ os.makedirs(path)
+ log(log.FILE, log.INFO, 'Opening local file (wb)',
+- sg.dirname+filename)
+- return open(sg.dirname+filename, mode+'b')
++ os.path.join(sg.dirname, filename))
++ return open(os.path.join(sg.dirname, filename), mode+'b')
+
+ # fallback to remote write
+ else:
+@@ -323,8 +323,8 @@ def ftopen(file, mode, forceremote=False, nooverwrite=False, db=None, \
+ if sg is not None:
+ # file found, open local
+ log(log.FILE, log.INFO, 'Opening local file (rb)',
+- sg.dirname+filename)
+- return open(sg.dirname+filename, mode+'b')
++ os.path.join(sg.dirname, filename))
++ return open(os.path.join(sg.dirname, filename), mode+'b')
+ else:
+ # file not found, open remote
+ return protoopen(host, filename, sgroup)
+
+From 40841f5804501478a271369da2bc4ee93418a9dc Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sun, 30 Aug 2020 12:26:33 +0200
+Subject: [PATCH 090/165] No discontinuity for first TS packet of PID
+
+Initialize all elements of m_continuityCounter to 0xff.
+This is an essential part of the existing code that avoids giving continuity
+error messages for the first transport stream packet received on a PID.
+The initialization code is present in mythtv v30 but has been accidentally
+removed moving forward to mythtv v31.
+---
+ mythtv/libs/libmythtv/recorders/dtvrecorder.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/recorders/dtvrecorder.cpp b/mythtv/libs/libmythtv/recorders/dtvrecorder.cpp
+index 3c1248ea14f..a868c6f0489 100644
+--- a/mythtv/libs/libmythtv/recorders/dtvrecorder.cpp
++++ b/mythtv/libs/libmythtv/recorders/dtvrecorder.cpp
+@@ -55,6 +55,8 @@ DTVRecorder::DTVRecorder(TVRec *rec) :
+ gCoreContext->GetNumSetting("MinimumRecordingQuality", 95);
+
+ m_containerFormat = formatMPEG2_TS;
++
++ memset(m_continuityCounter, 0xff, sizeof(m_continuityCounter));
+ }
+
+ DTVRecorder::~DTVRecorder(void)
+
+From b282809197febe7a7619e99c3b3067215a62afc3 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Wed, 26 Aug 2020 15:51:54 -0400
+Subject: [PATCH 091/165] Service API: Fix bug where "New Episodes Only"
+ corrupts value of DupIn
+
+DupIn value carries two meanings, "New Episodes Only" flag as well as
+flag for searching in current, old or both when cheing duplicates.
+Changed the API methods to have one more parameter, for new episodes
+only.
+
+(cherry picked from commit 9d084c2e42ec15aacb71a48792f6ddf200479ca1)
+---
+ mythtv/libs/libmyth/recordingtypes.cpp | 23 ++++++++++++++++---
+ mythtv/libs/libmyth/recordingtypes.h | 2 ++
+ .../datacontracts/recRule.h | 6 ++++-
+ .../services/dvrServices.h | 2 ++
+ mythtv/programs/mythbackend/services/dvr.cpp | 6 +++--
+ mythtv/programs/mythbackend/services/dvr.h | 8 +++++--
+ .../mythbackend/services/serviceUtil.cpp | 1 +
+ 7 files changed, 40 insertions(+), 8 deletions(-)
+
+diff --git a/mythtv/libs/libmyth/recordingtypes.cpp b/mythtv/libs/libmyth/recordingtypes.cpp
+index cc86b2be17c..86658d05e46 100644
+--- a/mythtv/libs/libmyth/recordingtypes.cpp
++++ b/mythtv/libs/libmyth/recordingtypes.cpp
+@@ -161,6 +161,8 @@ QString toString(RecordingDupInType recdupin)
+ return QObject::tr("Previous Recordings");
+ case kDupsInAll:
+ return QObject::tr("All Recordings");
++ // TODO: This is wrong, kDupsNewEpi is returned in conjunction with
++ // one of the other values.
+ case kDupsNewEpi:
+ return QObject::tr("New Episodes Only");
+ default:
+@@ -179,6 +181,8 @@ QString toDescription(RecordingDupInType recdupin)
+ case kDupsInAll:
+ return QObject::tr("Look for duplicates in current and previous "
+ "recordings");
++ // TODO: This is wrong, kDupsNewEpi is returned in conjunction with
++ // one of the other values.
+ case kDupsNewEpi:
+ return QObject::tr("Record new episodes only");
+ default:
+@@ -188,6 +192,8 @@ QString toDescription(RecordingDupInType recdupin)
+
+ QString toRawString(RecordingDupInType recdupin)
+ {
++ // Remove "New Episodes" flag
++ recdupin = (RecordingDupInType) (recdupin & (-1 - kDupsNewEpi));
+ switch (recdupin)
+ {
+ case kDupsInRecorded:
+@@ -196,13 +202,17 @@ QString toRawString(RecordingDupInType recdupin)
+ return QString("Previous Recordings");
+ case kDupsInAll:
+ return QString("All Recordings");
+- case kDupsNewEpi:
+- return QString("New Episodes Only");
+ default:
+ return QString("Unknown");
+ }
+ }
+
++// New Episodes Only is a flag added to DupIn
++bool newEpifromDupIn(RecordingDupInType recdupin)
++{
++ return (recdupin & kDupsNewEpi);
++}
++
+ RecordingDupInType dupInFromString(const QString& type)
+ {
+ if (type.toLower() == "current recordings" || type.toLower() == "current")
+@@ -212,10 +222,17 @@ RecordingDupInType dupInFromString(const QString& type)
+ if (type.toLower() == "all recordings" || type.toLower() == "all")
+ return kDupsInAll;
+ if (type.toLower() == "new episodes only" || type.toLower() == "new")
+- return kDupsNewEpi;
++ return static_cast<RecordingDupInType> (kDupsInAll | kDupsNewEpi);
+ return kDupsInAll;
+ }
+
++RecordingDupInType dupInFromStringAndBool(const QString& type, bool newEpisodesOnly) {
++ RecordingDupInType result = dupInFromString(type);
++ if (newEpisodesOnly)
++ result = static_cast<RecordingDupInType> (result | kDupsNewEpi);
++ return result;
++}
++
+ QString toString(RecordingDupMethodType duptype)
+ {
+ switch (duptype)
+diff --git a/mythtv/libs/libmyth/recordingtypes.h b/mythtv/libs/libmyth/recordingtypes.h
+index ccb02120d56..c5654dd6b16 100644
+--- a/mythtv/libs/libmyth/recordingtypes.h
++++ b/mythtv/libs/libmyth/recordingtypes.h
+@@ -50,7 +50,9 @@ enum RecordingDupInType
+ MPUBLIC QString toString(RecordingDupInType rectype);
+ MPUBLIC QString toDescription(RecordingDupInType rectype);
+ MPUBLIC QString toRawString(RecordingDupInType rectype);
++MPUBLIC bool newEpifromDupIn(RecordingDupInType recdupin);
+ MPUBLIC RecordingDupInType dupInFromString(const QString& type);
++MPUBLIC RecordingDupInType dupInFromStringAndBool(const QString& type, bool newEpisodesOnly);
+
+ enum RecordingDupMethodType
+ {
+diff --git a/mythtv/libs/libmythservicecontracts/datacontracts/recRule.h b/mythtv/libs/libmythservicecontracts/datacontracts/recRule.h
+index 9fe14910d82..518e9417026 100644
+--- a/mythtv/libs/libmythservicecontracts/datacontracts/recRule.h
++++ b/mythtv/libs/libmythservicecontracts/datacontracts/recRule.h
+@@ -15,7 +15,7 @@ namespace DTC
+ class SERVICE_PUBLIC RecRule : public QObject
+ {
+ Q_OBJECT
+- Q_CLASSINFO( "version" , "2.00" );
++ Q_CLASSINFO( "version" , "2.10" );
+
+ Q_PROPERTY( int Id READ Id WRITE setId )
+ Q_PROPERTY( int ParentId READ ParentId WRITE setParentId )
+@@ -46,6 +46,7 @@ class SERVICE_PUBLIC RecRule : public QObject
+ Q_PROPERTY( int EndOffset READ EndOffset WRITE setEndOffset )
+ Q_PROPERTY( QString DupMethod READ DupMethod WRITE setDupMethod )
+ Q_PROPERTY( QString DupIn READ DupIn WRITE setDupIn )
++ Q_PROPERTY( bool NewEpisOnly READ NewEpisOnly WRITE setNewEpisOnly )
+ Q_PROPERTY( uint Filter READ Filter WRITE setFilter )
+
+ Q_PROPERTY( QString RecProfile READ RecProfile WRITE setRecProfile )
+@@ -97,6 +98,7 @@ class SERVICE_PUBLIC RecRule : public QObject
+ PROPERTYIMP ( int , EndOffset )
+ PROPERTYIMP ( QString , DupMethod )
+ PROPERTYIMP ( QString , DupIn )
++ PROPERTYIMP ( bool , NewEpisOnly )
+ PROPERTYIMP ( uint , Filter )
+ PROPERTYIMP ( QString , RecProfile )
+ PROPERTYIMP ( QString , RecGroup )
+@@ -135,6 +137,7 @@ class SERVICE_PUBLIC RecRule : public QObject
+ m_PreferredInput( 0 ),
+ m_StartOffset ( 0 ),
+ m_EndOffset ( 0 ),
++ m_NewEpisOnly ( false ),
+ m_Filter ( 0 ),
+ m_AutoExpire ( false ),
+ m_MaxEpisodes ( 0 ),
+@@ -179,6 +182,7 @@ class SERVICE_PUBLIC RecRule : public QObject
+ m_EndOffset = src->m_EndOffset ;
+ m_DupMethod = src->m_DupMethod ;
+ m_DupIn = src->m_DupIn ;
++ m_NewEpisOnly = src->m_NewEpisOnly ;
+ m_Filter = src->m_Filter ;
+ m_RecProfile = src->m_RecProfile ;
+ m_RecGroup = src->m_RecGroup ;
+diff --git a/mythtv/libs/libmythservicecontracts/services/dvrServices.h b/mythtv/libs/libmythservicecontracts/services/dvrServices.h
+index 47ae6c71c98..d3041751ad6 100644
+--- a/mythtv/libs/libmythservicecontracts/services/dvrServices.h
++++ b/mythtv/libs/libmythservicecontracts/services/dvrServices.h
+@@ -213,6 +213,7 @@ class SERVICE_PUBLIC DvrServices : public Service //, public QScriptable ???
+ QDateTime LastRecorded,
+ QString DupMethod,
+ QString DupIn,
++ bool NewEpisOnly,
+ uint Filter,
+ QString RecProfile,
+ QString RecGroup,
+@@ -255,6 +256,7 @@ class SERVICE_PUBLIC DvrServices : public Service //, public QScriptable ???
+ int EndOffset,
+ QString DupMethod,
+ QString DupIn,
++ bool NewEpisOnly,
+ uint Filter,
+ QString RecProfile,
+ QString RecGroup,
+diff --git a/mythtv/programs/mythbackend/services/dvr.cpp b/mythtv/programs/mythbackend/services/dvr.cpp
+index 46cf87e29ef..60cd91af903 100644
+--- a/mythtv/programs/mythbackend/services/dvr.cpp
++++ b/mythtv/programs/mythbackend/services/dvr.cpp
+@@ -1095,6 +1095,7 @@ uint Dvr::AddRecordSchedule (
+ QDateTime lastrectsRaw,
+ QString sDupMethod,
+ QString sDupIn,
++ bool bNewEpisOnly,
+ uint nFilter,
+ QString sRecProfile,
+ QString sRecGroup,
+@@ -1145,7 +1146,7 @@ uint Dvr::AddRecordSchedule (
+ rule.m_dupMethod = kDupCheckNone;
+ else
+ rule.m_dupMethod = dupMethodFromString(sDupMethod);
+- rule.m_dupIn = dupInFromString(sDupIn);
++ rule.m_dupIn = dupInFromStringAndBool(sDupIn, bNewEpisOnly);
+
+ if (sRecProfile.isEmpty())
+ sRecProfile = "Default";
+@@ -1242,6 +1243,7 @@ bool Dvr::UpdateRecordSchedule ( uint nRecordId,
+ int nEndOffset,
+ QString sDupMethod,
+ QString sDupIn,
++ bool bNewEpisOnly,
+ uint nFilter,
+ QString sRecProfile,
+ QString sRecGroup,
+@@ -1291,7 +1293,7 @@ bool Dvr::UpdateRecordSchedule ( uint nRecordId,
+ pRule.m_dupMethod = kDupCheckNone;
+ else
+ pRule.m_dupMethod = dupMethodFromString(sDupMethod);
+- pRule.m_dupIn = dupInFromString(sDupIn);
++ pRule.m_dupIn = dupInFromStringAndBool(sDupIn, bNewEpisOnly);
+
+ if (sRecProfile.isEmpty())
+ sRecProfile = "Default";
+diff --git a/mythtv/programs/mythbackend/services/dvr.h b/mythtv/programs/mythbackend/services/dvr.h
+index 5bf193c05ab..3799f27488b 100644
+--- a/mythtv/programs/mythbackend/services/dvr.h
++++ b/mythtv/programs/mythbackend/services/dvr.h
+@@ -176,6 +176,7 @@ class Dvr : public DvrServices
+ QDateTime lastrectsRaw,
+ QString DupMethod,
+ QString DupIn,
++ bool NewEpisOnly,
+ uint Filter,
+ QString RecProfile,
+ QString RecGroup,
+@@ -218,6 +219,7 @@ class Dvr : public DvrServices
+ int EndOffset,
+ QString DupMethod,
+ QString DupIn,
++ bool NewEpisOnly,
+ uint Filter,
+ QString RecProfile,
+ QString RecGroup,
+@@ -494,7 +496,8 @@ class ScriptableDvr : public QObject
+ rule->PreferredInput(), rule->StartOffset(),
+ rule->EndOffset(), rule->LastRecorded(),
+ rule->DupMethod(),
+- rule->DupIn(), rule->Filter(),
++ rule->DupIn(), rule->NewEpisOnly(),
++ rule->Filter(),
+ rule->RecProfile(), rule->RecGroup(),
+ rule->StorageGroup(), rule->PlayGroup(),
+ rule->AutoExpire(), rule->MaxEpisodes(),
+@@ -527,7 +530,8 @@ class ScriptableDvr : public QObject
+ rule->SearchType(), rule->RecPriority(),
+ rule->PreferredInput(), rule->StartOffset(),
+ rule->EndOffset(), rule->DupMethod(),
+- rule->DupIn(), rule->Filter(),
++ rule->DupIn(), rule->NewEpisOnly(),
++ rule->Filter(),
+ rule->RecProfile(), rule->RecGroup(),
+ rule->StorageGroup(), rule->PlayGroup(),
+ rule->AutoExpire(), rule->MaxEpisodes(),
+diff --git a/mythtv/programs/mythbackend/services/serviceUtil.cpp b/mythtv/programs/mythbackend/services/serviceUtil.cpp
+index b1d5071d6a4..bfce03776dd 100644
+--- a/mythtv/programs/mythbackend/services/serviceUtil.cpp
++++ b/mythtv/programs/mythbackend/services/serviceUtil.cpp
+@@ -299,6 +299,7 @@ void FillRecRuleInfo( DTC::RecRule *pRecRule,
+ pRecRule->setEndOffset ( pRule->m_endOffset );
+ pRecRule->setDupMethod ( toRawString(pRule->m_dupMethod) );
+ pRecRule->setDupIn ( toRawString(pRule->m_dupIn) );
++ pRecRule->setNewEpisOnly ( newEpifromDupIn(pRule->m_dupIn) );
+ pRecRule->setFilter ( pRule->m_filter );
+ pRecRule->setRecProfile ( pRule->m_recProfile );
+ pRecRule->setRecGroup ( RecordingInfo::GetRecgroupString(pRule->m_recGroupID) );
+
+From 2cff5c78b2227eb69efb22e384e20bad90b804ae Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Tue, 8 Sep 2020 16:30:32 -0500
+Subject: [PATCH 092/165] Backport onc foreach fix from master commit
+ 11df0636c5a.
+
+I believe the use of foreach at this location has been causing some of
+my backend crashes.
+---
+ mythtv/libs/libmythbase/mythdbcon.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythbase/mythdbcon.cpp b/mythtv/libs/libmythbase/mythdbcon.cpp
+index 102395fcaed..1df2f65be3d 100644
+--- a/mythtv/libs/libmythbase/mythdbcon.cpp
++++ b/mythtv/libs/libmythbase/mythdbcon.cpp
+@@ -472,7 +472,7 @@ void MDBManager::CloseDatabases()
+ m_pool[QThread::currentThread()].clear();
+ m_lock.unlock();
+
+- foreach (auto & conn, list)
++ for (auto *conn : qAsConst(list))
+ {
+ LOG(VB_DATABASE, LOG_INFO,
+ "Closing DB connection named '" + conn->m_name + "'");
+
+From 48b2a03b78c74cc4268df91f442a4a5e5c3af363 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Tue, 15 Sep 2020 10:18:08 +0100
+Subject: [PATCH 093/165] Add github workflow for fixes/31
+
+---
+ .github/workflows/buildfixes31.yml | 85 ++++++++++++++++++++++++++++++
+ 1 file changed, 85 insertions(+)
+ create mode 100644 .github/workflows/buildfixes31.yml
+
+diff --git a/.github/workflows/buildfixes31.yml b/.github/workflows/buildfixes31.yml
+new file mode 100644
+index 00000000000..ec42bc4f6b8
+--- /dev/null
++++ b/.github/workflows/buildfixes31.yml
+@@ -0,0 +1,85 @@
++name: fixes/31
++
++on:
++ push:
++ branches: [ fixes/31 ]
++ pull_request:
++ branches: [ fixes/31 ]
++
++jobs:
++ build:
++ name: build
++ strategy:
++ matrix:
++ os: ['ubuntu-18.04', 'macos-10.15']
++ cc: ['gcc', 'clang']
++ include:
++ - cc: 'gcc'
++ cxx: 'g++'
++ - cc: 'clang'
++ cxx: 'clang++'
++ fail-fast: false
++ runs-on: ${{ matrix.os }}
++
++ steps:
++ - name: Checkout fixes/31
++ uses: actions/checkout@v2
++
++ - name: Setup build environment
++ run: echo "::set-env name=MYTHTV_CONFIG::--prefix=${{ github.workspace }}/build/install --cc=${{ matrix.cc }} --cxx=${{ matrix.cxx }}"
++
++ - name: Check ccache
++ uses: actions/cache@v2
++ with:
++ path: ~/.ccache
++ key: ${{ matrix.os }}-${{ matrix.cc }}-ccache-${{ github.sha }}
++ restore-keys: ${{ matrix.os }}-${{ matrix.cc }}-ccache
++
++ # N.B. These dependencies are for the fixes/31 branch. The list is intended to provide as much code coverage as possible (i.e. enable as many options as possible)
++ - name: Install core dependencies (linux)
++ run: |
++ sudo apt update
++ sudo apt install ccache qt5-qmake qtscript5-dev nasm libsystemd-dev libfreetype6-dev libmp3lame-dev libx264-dev libx265-dev libxrandr-dev libxml2-dev libavahi-compat-libdnssd-dev libasound2-dev liblzo2-dev libhdhomerun-dev libsamplerate0-dev libva-dev libdrm-dev libvdpau-dev libass-dev libpulse-dev libcec-dev libfftw3-dev libssl-dev libtag1-dev libbluray-dev libbluray-bdj libgnutls28-dev libqt5webkit5-dev libvpx-dev python3-mysqldb python3-lxml python3-simplejson python3-future libdbi-perl libdbd-mysql-perl libnet-upnp-perl libio-socket-inet6-perl libxml-simple-perl libqt5sql5-mysql libxxf86vm-dev libxinerama-dev libexiv2-dev
++ if: runner.os == 'Linux'
++
++ - name: Install core dependencies (macOS)
++ run: |
++ brew install pkg-config ccache qt5 nasm libsamplerate taglib lzo libcec libbluray fftw libass libhdhomerun dav1d x264 x265 libvpx openssl exiv2
++ brew link qt5 --force
++ echo "::set-env name=MYTHTV_CONFIG::$MYTHTV_CONFIG --extra-cxxflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib"
++ if: runner.os == 'macOS'
++
++ - name: Configure core
++ working-directory: ./mythtv
++ run: ./configure $MYTHTV_CONFIG --enable-libmp3lame --enable-libvpx --enable-libx264 --enable-libx265 --enable-bdjava
++
++ - name: Make core
++ working-directory: ./mythtv
++ run: make all_no_test -j4
++
++ - name: Install core
++ working-directory: ./mythtv
++ run: make install
++
++ # QTest requires a QT SQL plugin - but there are currently none available via brew on macOS
++ - name: Unit test core
++ working-directory: ./mythtv
++ run: make test
++ if: runner.os == 'Linux'
++
++ - name: Install plugin dependencies (linux)
++ run: sudo apt install libvorbis-dev libflac++-dev libminizip-dev libcdio-dev libcdio-paranoia-dev python3-oauth python3-pycurl
++ libxml-xpath-perl libdate-manip-perl libdatetime-format-iso8601-perl libsoap-lite-perl libjson-perl libimage-size-perl
++ if: runner.os == 'Linux'
++
++ - name: Install plugin dependencies (macOS)
++ run: brew install minizip flac libvorbis libcdio
++ if: runner.os == 'macOS'
++
++ - name: Configure plugins
++ working-directory: ./mythplugins
++ run: ./configure $MYTHTV_CONFIG
++
++ - name: Make plugins
++ working-directory: ./mythplugins
++ run: make -j4
+
+From b033cd7b75a9220c46e230e2a62802e923119332 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 14 Sep 2020 18:51:12 +0100
+Subject: [PATCH 094/165] TV: Fix crash when playback exits and guide grid is
+ showing
+
+- not normally a problem in live tv but if playback is allowed to
+continue while embedding, the frontend crashes hard when the GuideGrid
+object loses its player
+- so emit a PlaybackExiting signal and listen for it in the GuideGrid
+- the GuideGrid will continue to show until exited properly - at which
+point the user will drop back to the last UI screen before playback
+started - which I guess makes sense...
+
+Refs #202
+
+(cherry picked from commit 849c2b3243d3de9b857594097d8081325482b568)
+---
+ mythtv/libs/libmythtv/tv_play.cpp | 3 +++
+ mythtv/libs/libmythtv/tv_play.h | 3 +++
+ mythtv/programs/mythfrontend/guidegrid.cpp | 13 +++++++++++++
+ mythtv/programs/mythfrontend/guidegrid.h | 3 +++
+ 4 files changed, 22 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/tv_play.cpp b/mythtv/libs/libmythtv/tv_play.cpp
+index 7679b6acde0..9bfae2cd32f 100644
+--- a/mythtv/libs/libmythtv/tv_play.cpp
++++ b/mythtv/libs/libmythtv/tv_play.cpp
+@@ -2602,6 +2602,9 @@ void TV::StopStuff(PlayerContext *mctx, PlayerContext *ctx,
+ LOC + QString("For player ctx %1 -- begin")
+ .arg(find_player_index(ctx)));
+
++ emit PlaybackExiting(this);
++ m_isEmbedded = false;
++
+ SetActive(mctx, 0, false);
+
+ if (ctx->m_buffer)
+diff --git a/mythtv/libs/libmythtv/tv_play.h b/mythtv/libs/libmythtv/tv_play.h
+index 05940d35557..421cda89814 100644
+--- a/mythtv/libs/libmythtv/tv_play.h
++++ b/mythtv/libs/libmythtv/tv_play.h
+@@ -330,6 +330,9 @@ class MTV_PUBLIC TV : public QObject, public MenuItemDisplayer
+ void timerEvent(QTimerEvent *te) override; // QObject
+ void StopPlayback(void);
+
++ signals:
++ void PlaybackExiting(TV* Player);
++
+ protected:
+ // Protected event handling
+ void customEvent(QEvent *e) override; // QObject
+diff --git a/mythtv/programs/mythfrontend/guidegrid.cpp b/mythtv/programs/mythfrontend/guidegrid.cpp
+index 6bf47163aff..9001182bf47 100644
+--- a/mythtv/programs/mythfrontend/guidegrid.cpp
++++ b/mythtv/programs/mythfrontend/guidegrid.cpp
+@@ -518,6 +518,19 @@ GuideGrid::GuideGrid(MythScreenStack *parent,
+ m_originalStartTime.time().second());
+ m_currentStartTime = m_originalStartTime.addSecs(secsoffset);
+ m_threadPool.setMaxThreadCount(1);
++
++ if (m_player)
++ connect(m_player, &TV::PlaybackExiting, this, &GuideGrid::PlayerExiting);
++}
++
++void GuideGrid::PlayerExiting(TV* Player)
++{
++ if (Player && (Player == m_player))
++ {
++ m_player->StopEmbedding();
++ HideTVWindow();
++ m_player = nullptr;
++ }
+ }
+
+ bool GuideGrid::Create()
+diff --git a/mythtv/programs/mythfrontend/guidegrid.h b/mythtv/programs/mythfrontend/guidegrid.h
+index dde085348d6..83062a98eae 100644
+--- a/mythtv/programs/mythfrontend/guidegrid.h
++++ b/mythtv/programs/mythfrontend/guidegrid.h
+@@ -134,6 +134,9 @@ class GuideGrid : public ScheduleCommon, public JumpToChannelListener
+ uint GetCurrentStartChannel(void) const { return m_currentStartChannel; }
+ QDateTime GetCurrentStartTime(void) const { return m_currentStartTime; }
+
++ public slots:
++ void PlayerExiting(TV* Player);
++
+ protected slots:
+ void cursorLeft();
+ void cursorRight();
+
+From 0942abd9ec355443ad477dce735167206ba4faf5 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Thu, 17 Sep 2020 13:56:02 +0100
+Subject: [PATCH 095/165] macOS video: Fix scaling of OSD
+
+- when high dpi is in use
+
+Refs #206
+---
+ mythtv/libs/libmythui/opengl/mythpainteropengl.cpp | 13 +++++++------
+ 1 file changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
+index abbb7685f0a..0d58bc27e76 100644
+--- a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
++++ b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
+@@ -256,11 +256,12 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image,
+ {
+ if (m_render)
+ {
++ qreal pixelratio = (m_swapControl && m_usingHighDPI) ? m_pixelRatio : 1.0;
+ #ifdef Q_OS_MACOS
+- QRect dest = QRect(static_cast<int>(Dest.left() * m_pixelRatio),
+- static_cast<int>(Dest.top() * m_pixelRatio),
+- static_cast<int>(Dest.width() * m_pixelRatio),
+- static_cast<int>(Dest.height() * m_pixelRatio));
++ QRect dest = QRect(static_cast<int>(Dest.left() * pixelratio),
++ static_cast<int>(Dest.top() * pixelratio),
++ static_cast<int>(Dest.width() * pixelratio),
++ static_cast<int>(Dest.height() * pixelratio));
+ #endif
+
+ // Drawing an image multiple times with the same VBO will stall most GPUs as
+@@ -271,7 +272,7 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image,
+ QOpenGLBuffer *vbo = texture->m_vbo;
+ texture->m_vbo = m_mappedBufferPool[m_mappedBufferPoolIdx];
+ texture->m_destination = QRect();
+- m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio);
++ m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, pixelratio);
+ texture->m_destination = QRect();
+ texture->m_vbo = vbo;
+ if (++m_mappedBufferPoolIdx >= MAX_BUFFER_POOL)
+@@ -279,7 +280,7 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image,
+ }
+ else
+ {
+- m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio);
++ m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, pixelratio);
+ m_mappedTextures.append(texture);
+ }
+ }
+
+From b5f1d03fa8040dbf94621eb6d52cb30b7832fd3d Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Thu, 17 Sep 2020 13:58:13 +0100
+Subject: [PATCH 096/165] macOS video: Fix scaling of video after an input
+ change (high dpi)
+
+- when the display is using high DPI, after an input change we re-init MythVideoBounds but with the current display rectangle; that has already been scaled for high DPI - leading to a doubling of the display rectangle on each input change and a halving of the displayed video.
+- reset MythVideoBounds using its 'raw' window rect - which will then have the scaling applied again - and all is good
+- should only affect macos with high dpi in use
+
+Refs #206
+---
+ mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp b/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
+index 225c58fe259..d70eb05975a 100644
+--- a/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
++++ b/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
+@@ -424,7 +424,7 @@ void MythVideoOutputOpenGL::ProcessFrame(VideoFrame *Frame, OSD */*osd*/,
+ m_dbDisplayProfile->SetInput(m_window.GetVideoDispDim(), 0 , codecName);
+
+ bool ok = Init(m_newVideoDim, m_newVideoDispDim, m_newAspect,
+- m_display, m_window.GetDisplayVisibleRect(), m_newCodecId);
++ m_display, m_window.GetRawWindowRect(), m_newCodecId);
+ m_newCodecId = kCodec_NONE;
+ m_newVideoDim = QSize();
+ m_newVideoDispDim = QSize();
+
+From 674fd1eb16aa052afd277a857cf68b65870ac3e9 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Fri, 18 Sep 2020 09:55:13 +0100
+Subject: [PATCH 097/165] workflows: Don't build mythbrowser
+
+---
+ .github/workflows/buildfixes31.yml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.github/workflows/buildfixes31.yml b/.github/workflows/buildfixes31.yml
+index ec42bc4f6b8..894da832c27 100644
+--- a/.github/workflows/buildfixes31.yml
++++ b/.github/workflows/buildfixes31.yml
+@@ -78,7 +78,7 @@ jobs:
+
+ - name: Configure plugins
+ working-directory: ./mythplugins
+- run: ./configure $MYTHTV_CONFIG
++ run: ./configure $MYTHTV_CONFIG --disable-mythbrowser
+
+ - name: Make plugins
+ working-directory: ./mythplugins
+
+From 21ef6eed645d42488cc3506794f2a6f955cb227d Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Fri, 18 Sep 2020 10:47:11 +0100
+Subject: [PATCH 098/165] workflows: Don't build macOS
+
+Something has obviously changed in master versus .31 for QtWebkit support. As this build is just informative, disable macOS which does not have webkit
+---
+ .github/workflows/buildfixes31.yml | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/.github/workflows/buildfixes31.yml b/.github/workflows/buildfixes31.yml
+index 894da832c27..f3910f9ff91 100644
+--- a/.github/workflows/buildfixes31.yml
++++ b/.github/workflows/buildfixes31.yml
+@@ -11,7 +11,7 @@ jobs:
+ name: build
+ strategy:
+ matrix:
+- os: ['ubuntu-18.04', 'macos-10.15']
++ os: ['ubuntu-18.04']
+ cc: ['gcc', 'clang']
+ include:
+ - cc: 'gcc'
+@@ -78,7 +78,7 @@ jobs:
+
+ - name: Configure plugins
+ working-directory: ./mythplugins
+- run: ./configure $MYTHTV_CONFIG --disable-mythbrowser
++ run: ./configure $MYTHTV_CONFIG
+
+ - name: Make plugins
+ working-directory: ./mythplugins
+
+From 01bc4fc628a2b294c931b467894254eff1871c2f Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Sat, 19 Sep 2020 09:03:01 +0100
+Subject: [PATCH 099/165] High DPI scaling: Fix displaying the ProgramGuide
+ when embedded
+
+- previously, when playing video, we disabled framebuffer clearing, setting of the viewport and any high dpi scaling in the painter.
+- when high dpi was being used, this broke rendering of the programme guide etc
+- we now differentiate beween control over the framebuffer and the viewport, and allow the painter to set the viewport (and scaling) when embedding - and the guide is rendered correctly
+
+Adapted from 88add49a414aed581a in master
+
+Refs #206
+---
+ .../libmythtv/opengl/mythvideooutopengl.cpp | 8 ++++++--
+ .../libmythui/opengl/mythpainteropengl.cpp | 20 ++++++++++++-------
+ .../libs/libmythui/opengl/mythpainteropengl.h | 14 +++++++++++--
+ 3 files changed, 31 insertions(+), 11 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp b/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
+index d70eb05975a..0c8afe4e85f 100644
+--- a/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
++++ b/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
+@@ -139,7 +139,7 @@ MythVideoOutputOpenGL::MythVideoOutputOpenGL(QString Profile)
+ }
+
+ // we need to control buffer swapping
+- m_openGLPainter->SetSwapControl(false);
++ m_openGLPainter->SetViewControl(MythOpenGLPainter::None);
+
+ // Create OpenGLVideo
+ QRect dvr = GetDisplayVisibleRect();
+@@ -164,7 +164,7 @@ MythVideoOutputOpenGL::~MythVideoOutputOpenGL()
+ }
+ m_openGLVideoPiPsReady.clear();
+ if (m_openGLPainter)
+- m_openGLPainter->SetSwapControl(true);
++ m_openGLPainter->SetViewControl(MythOpenGLPainter::Viewport | MythOpenGLPainter::Framebuffer);
+ delete m_openGLVideo;
+ if (m_render)
+ {
+@@ -580,6 +580,9 @@ void MythVideoOutputOpenGL::PrepareFrame(VideoFrame *Frame, FrameScanType Scan,
+ // main UI when embedded
+ if (m_window.IsEmbedding())
+ {
++ // If we are using high dpi, the painter needs to set the appropriate
++ // viewport and enable scaling of its images
++ m_openGLPainter->SetViewControl(MythOpenGLPainter::Viewport);
+ MythMainWindow *win = GetMythMainWindow();
+ if (win && win->GetPaintWindow())
+ {
+@@ -595,6 +598,7 @@ void MythVideoOutputOpenGL::PrepareFrame(VideoFrame *Frame, FrameScanType Scan,
+ m_render->SetViewPort(main, true);
+ }
+ }
++ m_openGLPainter->SetViewControl(MythOpenGLPainter::None);
+ }
+
+ // video
+diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
+index 0d58bc27e76..2510d479c60 100644
+--- a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
++++ b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
+@@ -149,16 +149,20 @@ void MythOpenGLPainter::Begin(QPaintDevice *Parent)
+ DeleteTextures();
+ m_render->makeCurrent();
+
+- if (m_target || m_swapControl)
++ if (m_target || m_viewControl.testFlag(Framebuffer))
+ {
+- // If we are master and using high DPI then scale the viewport
+- if (m_swapControl && m_usingHighDPI)
+- currentsize *= m_pixelRatio;
+ m_render->BindFramebuffer(m_target);
+- m_render->SetViewPort(QRect(0, 0, currentsize.width(), currentsize.height()));
+ m_render->SetBackground(0, 0, 0, 0);
+ m_render->ClearFramebuffer();
+ }
++
++ if (m_target || m_viewControl.testFlag(Viewport))
++ {
++ // If using high DPI then scale the viewport
++ if (m_usingHighDPI)
++ currentsize *= m_pixelRatio;
++ m_render->SetViewPort(QRect(0, 0, currentsize.width(), currentsize.height()));
++ }
+ }
+
+ void MythOpenGLPainter::End(void)
+@@ -171,7 +175,7 @@ void MythOpenGLPainter::End(void)
+
+ if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
+ m_render->logDebugMarker("PAINTER_FRAME_END");
+- if (m_target == nullptr && m_swapControl)
++ if (m_target == nullptr && m_viewControl.testFlag(Framebuffer))
+ {
+ m_render->Flush();
+ m_render->swapBuffers();
+@@ -256,7 +260,9 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image,
+ {
+ if (m_render)
+ {
+- qreal pixelratio = (m_swapControl && m_usingHighDPI) ? m_pixelRatio : 1.0;
++ qreal pixelratio = 1.0;
++ if (m_usingHighDPI && m_viewControl.testFlag(Viewport))
++ pixelratio = m_pixelRatio;
+ #ifdef Q_OS_MACOS
+ QRect dest = QRect(static_cast<int>(Dest.left() * pixelratio),
+ static_cast<int>(Dest.top() * pixelratio),
+diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.h b/mythtv/libs/libmythui/opengl/mythpainteropengl.h
+index 540f7db79df..06407b93802 100644
+--- a/mythtv/libs/libmythui/opengl/mythpainteropengl.h
++++ b/mythtv/libs/libmythui/opengl/mythpainteropengl.h
+@@ -26,11 +26,19 @@ class MUI_PUBLIC MythOpenGLPainter : public MythPainter
+ Q_OBJECT
+
+ public:
++ enum ViewControl
++ {
++ None = 0x00,
++ Viewport = 0x01,
++ Framebuffer = 0x02
++ };
++ Q_DECLARE_FLAGS(ViewControls, ViewControl)
++
+ explicit MythOpenGLPainter(MythRenderOpenGL *Render = nullptr, QWidget *Parent = nullptr);
+ ~MythOpenGLPainter() override;
+
+ void SetTarget(QOpenGLFramebufferObject* NewTarget) { m_target = NewTarget; }
+- void SetSwapControl(bool Swap) { m_swapControl = Swap; }
++ void SetViewControl(ViewControls Control) { m_viewControl = Control; }
+ void DeleteTextures(void);
+
+ // MythPainter
+@@ -64,7 +72,7 @@ class MUI_PUBLIC MythOpenGLPainter : public MythPainter
+ QWidget *m_parent { nullptr };
+ MythRenderOpenGL *m_render { nullptr };
+ QOpenGLFramebufferObject* m_target { nullptr };
+- bool m_swapControl { true };
++ ViewControls m_viewControl { Viewport | Framebuffer };
+ QSize m_lastSize { };
+ qreal m_pixelRatio { 1.0 };
+ MythDisplay* m_display { nullptr };
+@@ -81,4 +89,6 @@ class MUI_PUBLIC MythOpenGLPainter : public MythPainter
+ bool m_mappedBufferPoolReady { false };
+ };
+
++Q_DECLARE_OPERATORS_FOR_FLAGS(MythOpenGLPainter::ViewControls)
++
+ #endif
+
+From ea0831c74a1c7bcb116eff79691ef24612343a62 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Sat, 19 Sep 2020 09:06:36 +0100
+Subject: [PATCH 100/165] macos High DPI: Fix scaling of embedded video
+
+- when high DPI is in use, we need to apply the correct scaling to
+ both the embedding rect and ITV resize rect
+- as for the window rect, store the 'raw' values and use these as
+ comparators for detecting change
+
+Refs #206
+---
+ mythtv/libs/libmythtv/videooutwindow.cpp | 29 ++++++++++++++++--------
+ mythtv/libs/libmythtv/videooutwindow.h | 6 ++---
+ 2 files changed, 22 insertions(+), 13 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/videooutwindow.cpp b/mythtv/libs/libmythtv/videooutwindow.cpp
+index 9c7326f11cb..a5d76e7ae7e 100644
+--- a/mythtv/libs/libmythtv/videooutwindow.cpp
++++ b/mythtv/libs/libmythtv/videooutwindow.cpp
+@@ -647,22 +647,25 @@ void VideoOutWindow::SetWindowSize(QSize Size)
+
+ void VideoOutWindow::SetITVResize(QRect Rect)
+ {
+- QRect oldrect = m_itvDisplayVideoRect;
++ QRect oldrect = m_rawItvDisplayVideoRect;
+ if (Rect.isEmpty())
+ {
+ m_itvResizing = false;
+ m_itvDisplayVideoRect = QRect();
++ m_rawItvDisplayVideoRect = QRect();
+ }
+ else
+ {
+ m_itvResizing = true;
+- m_itvDisplayVideoRect = Rect;
++ m_rawItvDisplayVideoRect = Rect;
++ m_itvDisplayVideoRect = SCALED_RECT(Rect, m_devicePixelRatio);
+ }
+- if (m_itvDisplayVideoRect != oldrect)
++ if (m_rawItvDisplayVideoRect != oldrect)
+ {
+- LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New ITV display rect: %1x%2+%3+%4")
++ LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New ITV display rect: %1x%2+%3+%4 (Scale: %1)")
+ .arg(m_itvDisplayVideoRect.width()).arg(m_itvDisplayVideoRect.height())
+- .arg(m_itvDisplayVideoRect.left()).arg(m_itvDisplayVideoRect.right()));
++ .arg(m_itvDisplayVideoRect.left()).arg(m_itvDisplayVideoRect.right())
++ .arg(m_devicePixelRatio));
+ MoveResize();
+ }
+ }
+@@ -701,14 +704,19 @@ void VideoOutWindow::ResizeDisplayWindow(const QRect &Rect, bool SaveVisibleRect
+ */
+ void VideoOutWindow::EmbedInWidget(const QRect &Rect)
+ {
+- if (m_embedding && (Rect == m_embeddingRect))
++ if (m_embedding && (Rect == m_rawEmbeddingRect))
+ return;
+- LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New embedding rect: %1x%2+%3+%4")
+- .arg(Rect.width()).arg(Rect.height()).arg(Rect.left()).arg(Rect.top()));
+- m_embeddingRect = Rect;
++
++ m_rawEmbeddingRect = Rect;
++ m_embeddingRect = SCALED_RECT(Rect, m_devicePixelRatio);
+ bool savevisiblerect = !m_embedding;
+ m_embedding = true;
+- m_displayVideoRect = Rect;
++ m_displayVideoRect = m_embeddingRect;
++
++ LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New embedding rect: %1x%2+%3+%4 (Scale: %1)")
++ .arg(m_embeddingRect.width()).arg(m_embeddingRect.height())
++ .arg(m_embeddingRect.left()).arg(m_embeddingRect.top())
++ .arg(m_devicePixelRatio));
+ ResizeDisplayWindow(m_displayVideoRect, savevisiblerect);
+ }
+
+@@ -721,6 +729,7 @@ void VideoOutWindow::StopEmbedding(void)
+ if (!m_embedding)
+ return;
+ LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopped embedding");
++ m_rawEmbeddingRect = QRect();
+ m_embeddingRect = QRect();
+ m_displayVisibleRect = m_tmpDisplayVisibleRect;
+ m_embedding = false;
+diff --git a/mythtv/libs/libmythtv/videooutwindow.h b/mythtv/libs/libmythtv/videooutwindow.h
+index cce077b5fde..a5efc757f52 100644
+--- a/mythtv/libs/libmythtv/videooutwindow.h
++++ b/mythtv/libs/libmythtv/videooutwindow.h
+@@ -79,10 +79,8 @@ class VideoOutWindow : public QObject
+ QRect GetScreenGeometry(void) const { return m_screenGeometry; }
+ QRect GetVideoRect(void) const { return m_videoRect; }
+ QRect GetDisplayVideoRect(void) const { return m_displayVideoRect; }
+- QRect GetEmbeddingRect(void) const { return m_embeddingRect; }
++ QRect GetEmbeddingRect(void) const { return m_rawEmbeddingRect; }
+ bool UsingGuiSize(void) const { return m_dbUseGUISize; }
+- bool GetITVResizing(void) const { return m_itvResizing; }
+- QRect GetITVDisplayRect(void) const { return m_itvDisplayVideoRect; }
+ QString GetZoomString(void) const;
+ AspectOverrideMode GetAspectOverride(void) const { return m_videoAspectOverrideMode; }
+ AdjustFillMode GetAdjustFill(void) const { return m_adjustFill; }
+@@ -157,10 +155,12 @@ class VideoOutWindow : public QObject
+ QRect m_tmpDisplayVisibleRect {0,0,0,0};
+ /// Embedded video rectangle
+ QRect m_embeddingRect;
++ QRect m_rawEmbeddingRect;
+
+ // Interactive TV (MHEG) video embedding
+ bool m_itvResizing {false};
+ QRect m_itvDisplayVideoRect;
++ QRect m_rawItvDisplayVideoRect;
+
+ /// State variables
+ bool m_embedding {false};
+
+From 55dd6a75dfb06e47b66d87874ae30470441decbb Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 21 Sep 2020 20:18:48 +0100
+Subject: [PATCH 101/165] AvFormatDecoder: Fix some DVD menus with VAAPI and
+ VDPAU
+
+- as noted in the code, overriding the aspect ratio from the DVD
+ringbuffer broke aspect ratio change detection and we were continually
+resetting the decoder - with fairly disastrous results
+
+Refs #225
+
+(cherry picked from commit 6f9825338a3622a2c433f31c08e11d07f7becfbe)
+---
+ mythtv/libs/libmythtv/decoders/avformatdecoder.cpp | 10 +++-------
+ 1 file changed, 3 insertions(+), 7 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
+index 7ce2f85b6e6..dce359edb5d 100644
+--- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
++++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
+@@ -3153,8 +3153,9 @@ void AvFormatDecoder::MpegPreProcessPkt(AVStream *stream, AVPacket *pkt)
+ int height = static_cast<int>(seq->height()) >> context->lowres;
+ float aspect = seq->aspect(context->codec_id == AV_CODEC_ID_MPEG1VIDEO);
+ if (stream->sample_aspect_ratio.num)
+- aspect = static_cast<float>(av_q2d(stream->sample_aspect_ratio) *
+- width / height);
++ aspect = static_cast<float>(av_q2d(stream->sample_aspect_ratio) * width / height);
++ if (aspect_override >= 0.0F)
++ aspect = aspect_override;
+ float seqFPS = seq->fps();
+
+ bool changed = (width != m_currentWidth );
+@@ -3166,13 +3167,8 @@ void AvFormatDecoder::MpegPreProcessPkt(AVStream *stream, AVPacket *pkt)
+ // ratio changes
+ bool forceaspectchange = !qFuzzyCompare(m_currentAspect + 10.0F, aspect + 10.0F) &&
+ m_mythCodecCtx && m_mythCodecCtx->DecoderWillResetOnAspect();
+-
+ m_currentAspect = aspect;
+
+- // N.B. this will break aspect ratio change detection above
+- if (aspect_override > 0.0F)
+- m_currentAspect = aspect_override;
+-
+ if (changed || forceaspectchange)
+ {
+ if (m_privateDec)
+
+From 2b753d95fdc7ea2223f32c790b2f84c901b6bc50 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 21 Sep 2020 20:28:41 +0100
+Subject: [PATCH 102/165] AvFormatDecoder: Fix potential error in DVD aspect
+ ratio
+
+- from last commit, zero is not a valid aspect ratio
+
+Refs #225
+
+(cherry picked from commit cc682b107deda27dd8a4caaf83a80044011723e3)
+---
+ mythtv/libs/libmythtv/decoders/avformatdecoder.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
+index dce359edb5d..7165b73581c 100644
+--- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
++++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
+@@ -3154,7 +3154,7 @@ void AvFormatDecoder::MpegPreProcessPkt(AVStream *stream, AVPacket *pkt)
+ float aspect = seq->aspect(context->codec_id == AV_CODEC_ID_MPEG1VIDEO);
+ if (stream->sample_aspect_ratio.num)
+ aspect = static_cast<float>(av_q2d(stream->sample_aspect_ratio) * width / height);
+- if (aspect_override >= 0.0F)
++ if (aspect_override > 0.0F)
+ aspect = aspect_override;
+ float seqFPS = seq->fps();
+
+
+From 8e36eda32195b9ad2cd3bc4f2120efb32e87a926 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Mon, 21 Sep 2020 20:51:38 +0100
+Subject: [PATCH 103/165] MythVideoOutput: Ensure deinterlacers are updated
+ after input change
+
+Closes #222
+---
+ mythtv/libs/libmythtv/mythvideoout.cpp | 10 ++++++++++
+ mythtv/libs/libmythtv/mythvideoout.h | 3 +++
+ mythtv/libs/libmythtv/mythvideooutnull.cpp | 4 ++++
+ mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp | 3 +++
+ 4 files changed, 20 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/mythvideoout.cpp b/mythtv/libs/libmythtv/mythvideoout.cpp
+index 888b283cfcc..2326c84b7c6 100644
+--- a/mythtv/libs/libmythtv/mythvideoout.cpp
++++ b/mythtv/libs/libmythtv/mythvideoout.cpp
+@@ -369,11 +369,18 @@ void MythVideoOutput::SetDeinterlacing(bool Enable, bool DoubleRate, MythDeintTy
+ {
+ if (!Enable)
+ {
++ m_deinterlacing = false;
++ m_deinterlacing2X = false;
++ m_forcedDeinterlacer = DEINT_NONE;
+ m_videoBuffers.SetDeinterlacing(DEINT_NONE, DEINT_NONE, m_videoCodecID);
+ LOG(VB_PLAYBACK, LOG_INFO, LOC + "Disabled all deinterlacing");
+ return;
+ }
+
++ m_deinterlacing = Enable;
++ m_deinterlacing2X = DoubleRate;
++ m_forcedDeinterlacer = Force;
++
+ MythDeintType singlerate = DEINT_NONE;
+ MythDeintType doublerate = DEINT_NONE;
+ if (DEINT_NONE != Force)
+@@ -427,6 +434,9 @@ bool MythVideoOutput::InputChanged(const QSize &VideoDim, const QSize &VideoDisp
+ m_dbDisplayProfile->SetInput(m_window.GetVideoDispDim(), 0 ,codecName);
+ m_videoCodecID = CodecID;
+ DiscardFrames(true, true);
++
++ // Update deinterlacers for any input change
++ SetDeinterlacing(m_deinterlacing, m_deinterlacing2X, m_forcedDeinterlacer);
+ return true;
+ }
+ /**
+diff --git a/mythtv/libs/libmythtv/mythvideoout.h b/mythtv/libs/libmythtv/mythvideoout.h
+index c26550a69dd..b2314cefec7 100644
+--- a/mythtv/libs/libmythtv/mythvideoout.h
++++ b/mythtv/libs/libmythtv/mythvideoout.h
+@@ -171,6 +171,9 @@ class MythVideoOutput
+ StereoscopicMode m_stereo {kStereoscopicModeNone};
+ MythAVCopy m_copyFrame;
+ MythDeinterlacer m_deinterlacer;
++ bool m_deinterlacing { false };
++ bool m_deinterlacing2X { false };
++ MythDeintType m_forcedDeinterlacer { DEINT_NONE };
+ };
+
+ #endif // MYTH_VIDEOOUT_H_
+diff --git a/mythtv/libs/libmythtv/mythvideooutnull.cpp b/mythtv/libs/libmythtv/mythvideooutnull.cpp
+index 2171bce90a4..c28ae10797a 100644
+--- a/mythtv/libs/libmythtv/mythvideooutnull.cpp
++++ b/mythtv/libs/libmythtv/mythvideooutnull.cpp
+@@ -205,6 +205,10 @@ void MythVideoOutputNull::SetDeinterlacing(bool Enable, bool DoubleRate, MythDei
+ MythVideoOutput::SetDeinterlacing(Enable, DoubleRate, Force);
+ return;
+ }
++
++ m_deinterlacing = false;
++ m_deinterlacing2X = false;
++ m_forcedDeinterlacer = DEINT_NONE;
+ m_videoBuffers.SetDeinterlacing(DEINT_NONE, DEINT_NONE, m_videoCodecID);
+ }
+
+diff --git a/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp b/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
+index 0c8afe4e85f..0e0447194f0 100644
+--- a/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
++++ b/mythtv/libs/libmythtv/opengl/mythvideooutopengl.cpp
+@@ -434,6 +434,9 @@ void MythVideoOutputOpenGL::ProcessFrame(VideoFrame *Frame, OSD */*osd*/,
+ if (wasembedding && ok)
+ EmbedInWidget(oldrect);
+
++ // Update deinterlacers for any input change
++ SetDeinterlacing(m_deinterlacing, m_deinterlacing2X, m_forcedDeinterlacer);
++
+ if (!ok)
+ return;
+ }
+
+From 2e1cccb628f92091641657f67f25852c746a7887 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <klaas(a)kldo.nl>
+Date: Mon, 21 Sep 2020 22:40:12 +0200
+Subject: [PATCH 104/165] Support DMBTH (DTMB) as DVB-T
+
+Select tuner type DVB-T when the card supports modulation system DMBTH (DTMB).
+This restore the behavior of MythTV v30 that was lost in v31.
+
+Refs #13472
+---
+ mythtv/libs/libmythtv/cardutil.cpp | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/cardutil.cpp b/mythtv/libs/libmythtv/cardutil.cpp
+index 5f2d8940ee2..005d0e5a197 100644
+--- a/mythtv/libs/libmythtv/cardutil.cpp
++++ b/mythtv/libs/libmythtv/cardutil.cpp
+@@ -770,6 +770,9 @@ DTVTunerType CardUtil::ConvertToTunerType(DTVModulationSystem delsys)
+ case DTVModulationSystem::kModulationSystem_DVBT2:
+ tunertype = DTVTunerType::kTunerTypeDVBT2;
+ break;
++ case DTVModulationSystem::kModulationSystem_DMBTH:
++ tunertype = DTVTunerType::kTunerTypeDVBT;
++ break;
+ case DTVModulationSystem::kModulationSystem_ATSC:
+ tunertype = DTVTunerType::kTunerTypeATSC;
+ break;
+
+From 8ca77476484b7179cbd1843d715e0145e13744c6 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Wed, 16 Sep 2020 21:00:25 +0200
+Subject: [PATCH 105/165] Time for preview max 10 minutes into the program
+
+The time of the preview is 1/3 of the total program duration
+limited to 10 minutes after the start of the program.
+This limit is added to prevent accidentally revealing the
+result of sport events in long recordings.
+
+(cherry picked from commit b571d81175804f3e8cbc4c47e40232f1f76f0f96)
+Signed-off-by: Klaas de Waal <klaas(a)kldo.nl>
+---
+ mythtv/libs/libmythtv/previewgenerator.cpp | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/previewgenerator.cpp b/mythtv/libs/libmythtv/previewgenerator.cpp
+index d78f9ab9bd2..e2e3ef6483b 100644
+--- a/mythtv/libs/libmythtv/previewgenerator.cpp
++++ b/mythtv/libs/libmythtv/previewgenerator.cpp
+@@ -677,7 +677,10 @@ bool PreviewGenerator::LocalPreviewRun(void)
+ }
+ if (programDuration > 0)
+ {
+- captime = startEarly + (programDuration / 3);
++ captime = programDuration / 3;
++ if (captime > 600)
++ captime = 600;
++ captime += startEarly;
+ }
+ if (captime < 0)
+ captime = 600;
+
+From abad9e2de7c771029e8b0333d55643855be2a6bf Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Tue, 29 Sep 2020 15:25:24 -0400
+Subject: [PATCH 106/165] Fix bug caused by commit a3ae3a8
+
+Problem was that in Terra theme, watched icon was not disappearing
+when an unwatched show was selected.
+
+This reverts part of the above mentioned commit that was causing
+this problem.
+
+(cherry picked from commit dbf9baa1b6caefe4132cbd9a06f8cf74a07f1d92)
+---
+ mythtv/libs/libmythui/mythuistatetype.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythui/mythuistatetype.cpp b/mythtv/libs/libmythui/mythuistatetype.cpp
+index 2d1237787b5..229d99be36c 100644
+--- a/mythtv/libs/libmythui/mythuistatetype.cpp
++++ b/mythtv/libs/libmythui/mythuistatetype.cpp
+@@ -93,7 +93,7 @@ bool MythUIStateType::DisplayState(const QString &name)
+ if (i != m_ObjectsByName.end())
+ m_CurrentState = i.value();
+ else
+- return false;
++ m_currentState = nullptr;
+
+ if (m_CurrentState != old)
+ {
+
+From 070623c6c65ffbc1fc635cf8a8333a4173d6b35f Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Wed, 7 Oct 2020 07:36:29 +0100
+Subject: [PATCH 107/165] Fix 0.31 build
+
+---
+ mythtv/libs/libmythui/mythuistatetype.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythui/mythuistatetype.cpp b/mythtv/libs/libmythui/mythuistatetype.cpp
+index 229d99be36c..39808cc1988 100644
+--- a/mythtv/libs/libmythui/mythuistatetype.cpp
++++ b/mythtv/libs/libmythui/mythuistatetype.cpp
+@@ -93,7 +93,7 @@ bool MythUIStateType::DisplayState(const QString &name)
+ if (i != m_ObjectsByName.end())
+ m_CurrentState = i.value();
+ else
+- m_currentState = nullptr;
++ m_CurrentState = nullptr;
+
+ if (m_CurrentState != old)
+ {
+
+From a7d51639918e7fd1fc0b896248817ac7db732bcf Mon Sep 17 00:00:00 2001
+From: Paul Gardiner <mythtv(a)glidos.net>
+Date: Sat, 26 Sep 2020 16:22:22 +0100
+Subject: [PATCH 108/165] Fix incorrect artwork urls returned from ttvdb
+ grabber
+
+When performing a manual search for metadata for a video, the artwork
+fetch was failing: no thumbnail was shown and no artwork was being
+associated with the video. Ticket 13518 mentioned strange urls containing
+"banners/_cache//banners". This commit fixes the bad urls and seems to
+restore the downloading of the artwork. I don't know what led to the need
+for this.
+
+(cherry picked from commit 08c7045c2f123a60b6d5d913daef7753cd53649d)
+---
+ .../bindings/python/MythTV/ttvdb/XSLT/tvdbQuery.xsl | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/ttvdb/XSLT/tvdbQuery.xsl b/mythtv/bindings/python/MythTV/ttvdb/XSLT/tvdbQuery.xsl
+index bf7ad06a82e..ee6c8ade816 100644
+--- a/mythtv/bindings/python/MythTV/ttvdb/XSLT/tvdbQuery.xsl
++++ b/mythtv/bindings/python/MythTV/ttvdb/XSLT/tvdbQuery.xsl
+@@ -48,22 +48,22 @@
+ <xsl:if test=".//poster/text() != ''">
+ <xsl:element name="image">
+ <xsl:attribute name="type">coverart</xsl:attribute>
+- <xsl:attribute name="url"><xsl:value-of select="concat('http://www.thetvdb.com/banners/', normalize-space(poster))"/></xsl:attribute>
+- <xsl:attribute name="thumb"><xsl:value-of select="concat('http://www.thetvdb.com/banners/_cache/', normalize-space(poster))"/></xsl:attribute>
++ <xsl:attribute name="url"><xsl:value-of select="concat('http://www.thetvdb.com', normalize-space(poster))"/></xsl:attribute>
++ <xsl:attribute name="thumb"><xsl:value-of select="tvdbXpath:replace(concat('http://www.thetvdb.com', normalize-space(poster)), '/banners/', '/banners/_cache/')"/></xsl:attribute>
+ </xsl:element>
+ </xsl:if>
+ <xsl:if test=".//fanart/text() != ''">
+ <xsl:element name="image">
+ <xsl:attribute name="type">fanart</xsl:attribute>
+- <xsl:attribute name="url"><xsl:value-of select="concat('http://www.thetvdb.com/banners/', normalize-space(fanart))"/></xsl:attribute>
+- <xsl:attribute name="thumb"><xsl:value-of select="concat('http://www.thetvdb.com/banners/_cache/', normalize-space(fanart))"/></xsl:attribute>
++ <xsl:attribute name="url"><xsl:value-of select="concat('http://www.thetvdb.com', normalize-space(fanart))"/></xsl:attribute>
++ <xsl:attribute name="thumb"><xsl:value-of select="tvdbXpath:replace(concat('http://www.thetvdb.com', normalize-space(fanart)), '/banners/', '/banners/_cache/')"/></xsl:attribute>
+ </xsl:element>
+ </xsl:if>
+ <xsl:if test=".//banner/text() != ''">
+ <xsl:element name="image">
+ <xsl:attribute name="type">banner</xsl:attribute>
+- <xsl:attribute name="url"><xsl:value-of select="concat('http://www.thetvdb.com/banners/', normalize-space(banner))"/></xsl:attribute>
+- <xsl:attribute name="thumb"><xsl:value-of select="concat('http://www.thetvdb.com/banners/_cache/', normalize-space(banner))"/></xsl:attribute>
++ <xsl:attribute name="url"><xsl:value-of select="concat('http://www.thetvdb.com', normalize-space(banner))"/></xsl:attribute>
++ <xsl:attribute name="thumb"><xsl:value-of select="tvdbXpath:replace(concat('http://www.thetvdb.com', normalize-space(banner)), '/banners/', '/banners/_cache/')"/></xsl:attribute>
+ </xsl:element>
+ </xsl:if>
+ </images>
+
+From 76edb08de77d76d2e7baf1ea1fa7072032b6dc4c Mon Sep 17 00:00:00 2001
+From: Bas Hulsken <bhulsken(a)hotmail.com>
+Date: Tue, 7 Jul 2020 10:09:32 +0200
+Subject: [PATCH 109/165] extend metadatagrabber timeout to 3 minutes
+
+(cherry picked from commit e17de9cd618838e14081322051b199e278100c2b)
+---
+ mythtv/libs/libmythmetadata/metadatagrabber.cpp | 2 +-
+ mythtv/programs/scripts/metadata/Movie/tmdb3.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatagrabber.cpp b/mythtv/libs/libmythmetadata/metadatagrabber.cpp
+index 9907eb4e95b..d9c0c1ebb7a 100644
+--- a/mythtv/libs/libmythmetadata/metadatagrabber.cpp
++++ b/mythtv/libs/libmythmetadata/metadatagrabber.cpp
+@@ -425,7 +425,7 @@ MetadataLookupList MetaGrabberScript::RunGrabber(const QStringList &args,
+ .arg(m_fullcommand).arg(args.join(" ")));
+
+ grabber.Run();
+- if (grabber.Wait(60) != GENERIC_EXIT_OK)
++ if (grabber.Wait(180) != GENERIC_EXIT_OK)
+ return list;
+
+ QByteArray result = grabber.ReadAll();
+diff --git a/mythtv/programs/scripts/metadata/Movie/tmdb3.py b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+index 2757e2fa7a4..972cfaa9eab 100755
+--- a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
++++ b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+@@ -289,7 +289,7 @@ def main():
+ opts, args = parser.parse_args()
+
+ signal.signal(signal.SIGALRM, timeouthandler)
+- signal.alarm(30)
++ signal.alarm(180)
+
+ if opts.version:
+ buildVersion()
+
+From 47a45b1e2e972ba2a5f0a464fef9a968e98138c4 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Thu, 1 Oct 2020 21:21:12 +0200
+Subject: [PATCH 110/165] Revert commit 2738b98, but add robustness
+
+Commit 2738b98 removed the functionality to download fanart and coverart
+upon a manual search for metadata.
+The reason was that it produces an endless loop inside the `Videodialog`
+selection of the correct item.
+
+I re-add this feature with more logging and - hopefully - adding
+robustness.
+We can now distinguish between a failure of the returned lookup data
+and a hiccup in the implementation of the 'ReferenceCounter' classes.
+
+(cherry picked from commit bbd25ebc0cbee76ad6f45604b8b023aa19b316ed)
+---
+ mythtv/programs/mythfrontend/videodlg.cpp | 27 +++++++++++++++++++++--
+ mythtv/programs/mythfrontend/videodlg.h | 2 +-
+ 2 files changed, 26 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/programs/mythfrontend/videodlg.cpp b/mythtv/programs/mythfrontend/videodlg.cpp
+index a9ae42cb097..345acb93eb8 100644
+--- a/mythtv/programs/mythfrontend/videodlg.cpp
++++ b/mythtv/programs/mythfrontend/videodlg.cpp
+@@ -51,6 +51,8 @@
+ // for ImageDLFailureEvent
+ #include "metadataimagedownload.h"
+
++#define LOC_MML QString("Manual Metadata Lookup: ")
++
+ static const QString _Location = "MythVideo";
+
+ namespace
+@@ -3506,12 +3508,33 @@ void VideoDialog::ToggleWatched()
+ }
+ }
+
+-void VideoDialog::OnVideoSearchListSelection(const RefCountHandler<MetadataLookup>& lookup)
++void VideoDialog::OnVideoSearchListSelection(RefCountHandler<MetadataLookup> lookup)
+ {
+ if (!lookup)
+ return;
+
+- OnVideoSearchDone(lookup);
++ if(!lookup->GetInetref().isEmpty() && lookup->GetInetref() != "00000000")
++ {
++ LOG(VB_GENERAL, LOG_INFO, LOC_MML +
++ QString("Selected Item: Type: %1%2 : Subtype: %3%4%5 : InetRef: %6")
++ .arg(lookup->GetType() == kMetadataVideo ? "Video" : "")
++ .arg(lookup->GetType() == kMetadataRecording ? "Recording" : "")
++ .arg(lookup->GetSubtype() == kProbableMovie ? "Movie" : "")
++ .arg(lookup->GetSubtype() == kProbableTelevision ? "Television" : "")
++ .arg(lookup->GetSubtype() == kUnknownVideo ? "Unknown" : "")
++ .arg(lookup->GetInetref()));
++
++ lookup->SetStep(kLookupData);
++ lookup->IncrRef();
++ m_metadataFactory->Lookup(lookup);
++ }
++ else
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC_MML +
++ QString("Selected Item has no InetRef Number!"));
++
++ OnVideoSearchDone(lookup);
++ }
+ }
+
+ void VideoDialog::OnParentalChange(int amount)
+diff --git a/mythtv/programs/mythfrontend/videodlg.h b/mythtv/programs/mythfrontend/videodlg.h
+index 3747f5a4d40..cdadd7ee5a3 100644
+--- a/mythtv/programs/mythfrontend/videodlg.h
++++ b/mythtv/programs/mythfrontend/videodlg.h
+@@ -131,7 +131,7 @@ class VideoDialog : public MythScreenType
+ void OnParentalChange(int amount);
+
+ // Called when the underlying data for an item changes
+- void OnVideoSearchListSelection(const RefCountHandler<MetadataLookup>& lookup);
++ void OnVideoSearchListSelection(RefCountHandler<MetadataLookup> lookup);
+
+ void doVideoScan();
+
+
+From 6ad0b90949c956048aa8daccfb3da49b45f62e7e Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Thu, 1 Oct 2020 21:45:08 +0200
+Subject: [PATCH 111/165] Fix running a metadata grabber twice in manual mode
+
+Commit e81c7fd added accuracy of retrieving metadata in automatic mode,
+but leads to some inefficiency in the use of manual mode:
+
+If the search for an exact match was not successful, the other grabber
+is called twice and appends its result twice.
+
+This commit combines the intention of commit 2f9424c for non-automatic
+mode and the commit for automatic mode (2738b98).
+
+(cherry picked from commit 6f85a40f370bf8e9b88b6ab0b98f6f8bebee4d8f)
+---
+ .../libs/libmythmetadata/metadatadownload.cpp | 21 ++++++-------------
+ 1 file changed, 6 insertions(+), 15 deletions(-)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatadownload.cpp b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+index 2ecebd0fef7..5b60f2b1b84 100644
+--- a/mythtv/libs/libmythmetadata/metadatadownload.cpp
++++ b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+@@ -93,20 +93,24 @@ void MetadataDownload::run()
+ if (lookup->GetSubtype() == kProbableTelevision)
+ {
+ list = handleTelevision(lookup);
+- if (findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0)
++ if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
++ (list.size() > 1 && !lookup->GetAutomatic()))
+ {
+ // There are no exact match prospects with artwork from TV search,
+ // so add in movies, where we might find a better match.
++ // In case of manual mode and ambiguous result, add it as well.
+ list.append(handleMovie(lookup));
+ }
+ }
+ else if (lookup->GetSubtype() == kProbableMovie)
+ {
+ list = handleMovie(lookup);
+- if (findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0)
++ if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
++ (list.size() > 1 && !lookup->GetAutomatic()))
+ {
+ // There are no exact match prospects with artwork from Movie search
+ // so add in television, where we might find a better match.
++ // In case of manual mode and ambiguous result, add it as well.
+ list.append(handleTelevision(lookup));
+ }
+ }
+@@ -115,19 +119,6 @@ void MetadataDownload::run()
+ // will try both movie and TV
+ list = handleVideoUndetermined(lookup);
+ }
+-
+- if ((list.isEmpty() ||
+- (list.size() > 1 && !lookup->GetAutomatic())) &&
+- lookup->GetSubtype() == kProbableTelevision)
+- {
+- list.append(handleMovie(lookup));
+- }
+- else if ((list.isEmpty() ||
+- (list.size() > 1 && !lookup->GetAutomatic())) &&
+- lookup->GetSubtype() == kProbableMovie)
+- {
+- list.append(handleTelevision(lookup));
+- }
+ }
+ else if (lookup->GetType() == kMetadataGame)
+ list = handleGame(lookup);
+
+From 4ab0425bd5e774978f170d3c76f9855072b09307 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Tue, 6 Oct 2020 19:52:32 +0200
+Subject: [PATCH 112/165] Automated metadata lookup: Return if no match found
+
+When performing a lookup in automatic mode, do not present a list
+of all similar matches. Instead, terminate that lookup with a
+failure notification only.
+
+If a metadata lookup is done on a fresh and large video library,
+the amount of popped up selection lists might get very big, and one
+loses the context to what filename the selection list is meant for.
+
+(cherry picked from commit c9ada72c7384fe1651b37d41d337c1af0459c780)
+---
+ mythtv/libs/libmythmetadata/metadatadownload.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatadownload.cpp b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+index 5b60f2b1b84..4566270ef74 100644
+--- a/mythtv/libs/libmythmetadata/metadatadownload.cpp
++++ b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+@@ -167,8 +167,10 @@ void MetadataDownload::run()
+ continue;
+ }
+
++ // nothing more we can do in automatic mode
+ QCoreApplication::postEvent(m_parent,
+ new MetadataLookupFailure(MetadataLookupList() << lookup));
++ continue;
+ }
+
+ LOG(VB_GENERAL, LOG_INFO,
+
+From f450e74550936e719134cf817d1fb7dc4e3e259e Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Tue, 6 Oct 2020 20:01:03 +0200
+Subject: [PATCH 113/165] Automated metadata lookup: Pass through automatic
+ flag
+
+If a new metadata lookup is spun off during lookup, pass through
+the 'automatic' flag if we are in automatic mode.
+
+(cherry picked from commit 87aac2f356444a80bb8d8f17e5b27e2f16ff1566)
+---
+ mythtv/libs/libmythmetadata/metadatadownload.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatadownload.cpp b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+index 4566270ef74..56d0cd87c83 100644
+--- a/mythtv/libs/libmythmetadata/metadatadownload.cpp
++++ b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+@@ -154,6 +154,8 @@ void MetadataDownload::run()
+ {
+ MetadataLookup *newlookup = bestLookup;
+
++ // pass through automatic type
++ newlookup->SetAutomatic(true);
+ // bestlookup is owned by list, we need an extra reference
+ newlookup->IncrRef();
+ newlookup->SetStep(kLookupData);
+
+From 30017d33d692a8aa5823d317ce741dbf092fd8cd Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Tue, 6 Oct 2020 20:25:31 +0200
+Subject: [PATCH 114/165] Metadata Lookup: Handle 'mxml' and 'nfo' files only
+ once
+
+Videos in the video storage group may have an associated file
+describing the metadata (ending with '.mxml' or '.nfo').
+See https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
+
+We should read that file only once, otherwise a lookup reports
+a single match twice.
+
+(cherry picked from commit 3f73316eb86fb018659f5607b6ad3604f0d03d1d)
+---
+ .../libs/libmythmetadata/metadatadownload.cpp | 105 ++++++++----------
+ 1 file changed, 44 insertions(+), 61 deletions(-)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatadownload.cpp b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+index 56d0cd87c83..d5ea413fe7b 100644
+--- a/mythtv/libs/libmythmetadata/metadatadownload.cpp
++++ b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+@@ -90,34 +90,51 @@ void MetadataDownload::run()
+ if (lookup->GetType() == kMetadataVideo ||
+ lookup->GetType() == kMetadataRecording)
+ {
+- if (lookup->GetSubtype() == kProbableTelevision)
++ // First, look for mxml and nfo files in video storage groups
++ if (lookup->GetType() == kMetadataVideo &&
++ !lookup->GetFilename().isEmpty())
+ {
+- list = handleTelevision(lookup);
+- if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
+- (list.size() > 1 && !lookup->GetAutomatic()))
+- {
+- // There are no exact match prospects with artwork from TV search,
+- // so add in movies, where we might find a better match.
+- // In case of manual mode and ambiguous result, add it as well.
+- list.append(handleMovie(lookup));
+- }
++ QString mxml = getMXMLPath(lookup->GetFilename());
++ QString nfo = getNFOPath(lookup->GetFilename());
++
++ if (!mxml.isEmpty())
++ list = readMXML(mxml, lookup);
++ else if (!nfo.isEmpty())
++ list = readNFO(nfo, lookup);
+ }
+- else if (lookup->GetSubtype() == kProbableMovie)
++
++ // If nothing found, create lookups based on filename
++ if (list.isEmpty())
+ {
+- list = handleMovie(lookup);
+- if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
+- (list.size() > 1 && !lookup->GetAutomatic()))
++ if (lookup->GetSubtype() == kProbableTelevision)
+ {
+- // There are no exact match prospects with artwork from Movie search
+- // so add in television, where we might find a better match.
+- // In case of manual mode and ambiguous result, add it as well.
+- list.append(handleTelevision(lookup));
++ list = handleTelevision(lookup);
++ if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
++ (list.size() > 1 && !lookup->GetAutomatic()))
++ {
++ // There are no exact match prospects with artwork from TV search,
++ // so add in movies, where we might find a better match.
++ // In case of manual mode and ambiguous result, add it as well.
++ list.append(handleMovie(lookup));
++ }
++ }
++ else if (lookup->GetSubtype() == kProbableMovie)
++ {
++ list = handleMovie(lookup);
++ if ((findExactMatchCount(list, lookup->GetBaseTitle(), true) == 0) ||
++ (list.size() > 1 && !lookup->GetAutomatic()))
++ {
++ // There are no exact match prospects with artwork from Movie search
++ // so add in television, where we might find a better match.
++ // In case of manual mode and ambiguous result, add it as well.
++ list.append(handleTelevision(lookup));
++ }
++ }
++ else
++ {
++ // will try both movie and TV
++ list = handleVideoUndetermined(lookup);
+ }
+- }
+- else
+- {
+- // will try both movie and TV
+- list = handleVideoUndetermined(lookup);
+ }
+ }
+ else if (lookup->GetType() == kMetadataGame)
+@@ -562,8 +579,8 @@ MetadataLookupList MetadataDownload::handleGame(MetadataLookup *lookup)
+ /**
+ * handleMovie:
+ * attempt to find movie data via the following (in order)
+- * 1- Local MXML
+- * 2- Local NFO
++ * 1- Local MXML: already done before
++ * 2- Local NFO: already done
+ * 3- By title
+ * 4- By inetref (if present)
+ */
+@@ -571,23 +588,6 @@ MetadataLookupList MetadataDownload::handleMovie(MetadataLookup *lookup)
+ {
+ MetadataLookupList list;
+
+- QString mxml;
+- QString nfo;
+-
+- if (!lookup->GetFilename().isEmpty())
+- {
+- mxml = getMXMLPath(lookup->GetFilename());
+- nfo = getNFOPath(lookup->GetFilename());
+- }
+-
+- if (!mxml.isEmpty())
+- list = readMXML(mxml, lookup);
+- else if (!nfo.isEmpty())
+- list = readNFO(nfo, lookup);
+-
+- if (!list.isEmpty())
+- return list;
+-
+ MetaGrabberScript grabber =
+ MetaGrabberScript::GetGrabber(kGrabberMovie, lookup);
+
+@@ -616,8 +616,8 @@ MetadataLookupList MetadataDownload::handleMovie(MetadataLookup *lookup)
+ /**
+ * handleTelevision
+ * attempt to find television data via the following (in order)
+- * 1- Local MXML
+- * 2- Local NFO
++ * 1- Local MXML: already done before
++ * 2- Local NFO: already done
+ * 3- By inetref with subtitle
+ * 4- By inetref with season and episode
+ * 5- By inetref
+@@ -628,23 +628,6 @@ MetadataLookupList MetadataDownload::handleTelevision(MetadataLookup *lookup)
+ {
+ MetadataLookupList list;
+
+- QString mxml;
+- QString nfo;
+-
+- if (!lookup->GetFilename().isEmpty())
+- {
+- mxml = getMXMLPath(lookup->GetFilename());
+- nfo = getNFOPath(lookup->GetFilename());
+- }
+-
+- if (!mxml.isEmpty())
+- list = readMXML(mxml, lookup);
+- else if (!nfo.isEmpty())
+- list = readNFO(nfo, lookup);
+-
+- if (!list.isEmpty())
+- return list;
+-
+ MetaGrabberScript grabber =
+ MetaGrabberScript::GetGrabber(kGrabberTelevision, lookup);
+ bool searchcollection = false;
+
+From 5a366872567c6c29390f8847cfaec6f7e027ae5b Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Tue, 6 Oct 2020 20:40:16 +0200
+Subject: [PATCH 115/165] Metadata Lookup: Add an experimental feature in
+ automatic mode
+
+In case that no exact matches have been found during automatic lookup,
+return the head of the list proposed by the grabber.
+
+This helps a lot, if a tv-series name is given by the file names
+in English, but the lookup was done in a different language.
+
+Anyway, it unconditionally sets the first presented item, even the
+match was not exactly found by MythTV.
+
+One needs to set the environment variable "EXPERIMENTAL_METADATA_GRAB"
+prior to the execution of mythfrontend or mythmetadatalookup.
+
+(cherry picked from commit 75cf2569817437f5282776193d4ef11f8261fa2a)
+---
+ .../libs/libmythmetadata/metadatadownload.cpp | 32 ++++++++++++++++---
+ 1 file changed, 28 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatadownload.cpp b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+index d5ea413fe7b..0ce2647e858 100644
+--- a/mythtv/libs/libmythmetadata/metadatadownload.cpp
++++ b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+@@ -1,3 +1,6 @@
++// C/C++
++#include <cstdlib>
++
+ // qt
+ #include <QCoreApplication>
+ #include <QEvent>
+@@ -186,10 +189,31 @@ void MetadataDownload::run()
+ continue;
+ }
+
+- // nothing more we can do in automatic mode
+- QCoreApplication::postEvent(m_parent,
+- new MetadataLookupFailure(MetadataLookupList() << lookup));
+- continue;
++ // Experimental:
++ // If nothing matches, always return the first found item
++ if (getenv("EXPERIMENTAL_METADATA_GRAB"))
++ {
++ MetadataLookup *newlookup = list.takeFirst();
++
++ // pass through automatic type
++ newlookup->SetAutomatic(true); // ### XXX RER
++ newlookup->SetStep(kLookupData);
++ // Type may have changed
++ LookupType ret = GuessLookupType(newlookup);
++ if (ret != kUnknownVideo)
++ {
++ newlookup->SetSubtype(ret);
++ }
++ prependLookup(newlookup);
++ continue;
++ }
++ else
++ {
++ // nothing more we can do in automatic mode
++ QCoreApplication::postEvent(m_parent,
++ new MetadataLookupFailure(MetadataLookupList() << lookup));
++ continue;
++ }
+ }
+
+ LOG(VB_GENERAL, LOG_INFO,
+
+From d285bdd9948655f98914ea47f61efa290bb86e85 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Wed, 7 Oct 2020 08:09:28 +0100
+Subject: [PATCH 116/165] MetadataDownload: Fix clang-tidy warning - else after
+ continue
+
+(cherry picked from commit d78f56e083f44224509bae53ffcdfbd21cca446a)
+---
+ mythtv/libs/libmythmetadata/metadatadownload.cpp | 12 +++++-------
+ 1 file changed, 5 insertions(+), 7 deletions(-)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatadownload.cpp b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+index 0ce2647e858..2869793c8b8 100644
+--- a/mythtv/libs/libmythmetadata/metadatadownload.cpp
++++ b/mythtv/libs/libmythmetadata/metadatadownload.cpp
+@@ -207,13 +207,11 @@ void MetadataDownload::run()
+ prependLookup(newlookup);
+ continue;
+ }
+- else
+- {
+- // nothing more we can do in automatic mode
+- QCoreApplication::postEvent(m_parent,
+- new MetadataLookupFailure(MetadataLookupList() << lookup));
+- continue;
+- }
++
++ // nothing more we can do in automatic mode
++ QCoreApplication::postEvent(m_parent,
++ new MetadataLookupFailure(MetadataLookupList() << lookup));
++ continue;
+ }
+
+ LOG(VB_GENERAL, LOG_INFO,
+
+From 577dd50df1cf6c6629eb708ee3a88a724bdbedff Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Sat, 10 Oct 2020 22:12:48 +0200
+Subject: [PATCH 117/165] Python Bindings: Allow searching for collections
+
+The ttvdb.py script does not return a valid xml when searching
+for an ID without season or episode, like
+'ttvdb.py -D 282022'.
+Provide means to search for a collection, which is proven to work:
+'ttvdb.py -C 282022'.
+
+(cherry picked from commit 708b35de5410b03c147c7af0822ae5a05897a9d7)
+---
+ mythtv/bindings/python/MythTV/system.py | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/system.py b/mythtv/bindings/python/MythTV/system.py
+index f7966fb6d69..3f4f5e2196f 100644
+--- a/mythtv/bindings/python/MythTV/system.py
++++ b/mythtv/bindings/python/MythTV/system.py
+@@ -390,9 +390,10 @@ def sortedSearch(self, phrase, subtitle=None, tolerance=None):
+ return sorted(self.search(phrase, subtitle, tolerance), \
+ key=lambda r: r.levenshtein)
+
+- def grabInetref(self, inetref, season=None, episode=None):
++ def grabInetref(self, inetref, season=None, episode=None, search_collection=False):
+ """
+- obj.grabInetref(inetref, season=None, episode=None) -> metadata object
++ obj.grabInetref(inetref, season=None, episode=None, search_collection=False)
++ -> metadata object
+
+ Returns a direct search for a specific movie or episode.
+ 'inetref' can be an existing VideoMetadata object, and
+@@ -411,7 +412,10 @@ def grabInetref(self, inetref, season=None, episode=None):
+ # 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))
++ if search_collection:
++ return next(self.command('-C', *args))
++ else:
++ return next(self.command('-D', *args))
+
+ class SystemEvent( System ):
+ """
+
+From eb3c84de5fe12831d386d15452aeb7658841e072 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Mon, 12 Oct 2020 15:36:27 -0500
+Subject: [PATCH 118/165] Fix longstanding issue with
+ Scheduler::getConflicting().
+
+Somewhere along the line, probably in a refactoring or cleanup,
+getConflicting() was broken by making it use FindNextConflict(). The
+problem is that FindNextConflict() strictly checks inputs and when
+getConflicting() is called, the inputs for non-recording programs have
+already been removed. This fix adds an ignoreinput parameter to be
+used when FindNextConflict() is called from getConflicting().
+
+(cherry picked from commit 93c278d430c9f7252aff762783556568433cda02)
+---
+ mythtv/programs/mythbackend/scheduler.cpp | 8 +++++---
+ mythtv/programs/mythbackend/scheduler.h | 3 ++-
+ 2 files changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
+index b787be27ce3..383f3c91ba5 100644
+--- a/mythtv/programs/mythbackend/scheduler.cpp
++++ b/mythtv/programs/mythbackend/scheduler.cpp
+@@ -1065,7 +1065,8 @@ bool Scheduler::FindNextConflict(
+ const RecordingInfo *p,
+ RecConstIter &iter,
+ OpenEndType openEnd,
+- uint *paffinity) const
++ uint *paffinity,
++ bool ignoreinput) const
+ {
+ uint affinity = 0;
+ for ( ; iter != cardlist.end(); ++iter)
+@@ -1082,7 +1083,7 @@ bool Scheduler::FindNextConflict(
+ if (debugConflicts)
+ msg = QString("comparing with '%1' ").arg(q->GetTitle());
+
+- if (p->GetInputID() != q->GetInputID())
++ if (p->GetInputID() != q->GetInputID() && !ignoreinput)
+ {
+ const vector <uint> &conflicting_inputs =
+ m_sinputInfoMap[p->GetInputID()].m_conflictingInputs;
+@@ -1715,7 +1716,8 @@ void Scheduler::getConflicting(RecordingInfo *pginfo, RecList *retlist)
+ QReadLocker tvlocker(&TVRec::s_inputsLock);
+
+ RecConstIter i = m_recList.begin();
+- for (; FindNextConflict(m_recList, pginfo, i); ++i)
++ for (; FindNextConflict(m_recList, pginfo, i, openEndNever,
++ nullptr, true); ++i)
+ {
+ const RecordingInfo *p = *i;
+ retlist->push_back(new RecordingInfo(*p));
+diff --git a/mythtv/programs/mythbackend/scheduler.h b/mythtv/programs/mythbackend/scheduler.h
+index d0cb90cac9c..bc8050c5f91 100644
+--- a/mythtv/programs/mythbackend/scheduler.h
++++ b/mythtv/programs/mythbackend/scheduler.h
+@@ -158,7 +158,8 @@ class Scheduler : public MThread, public MythScheduler
+ bool FindNextConflict(const RecList &cardlist,
+ const RecordingInfo *p, RecConstIter &iter,
+ OpenEndType openEnd = openEndNever,
+- uint *paffinity = nullptr) const;
++ uint *paffinity = nullptr,
++ bool ignoreinput = false) const;
+ const RecordingInfo *FindConflict(const RecordingInfo *p,
+ OpenEndType openEnd = openEndNever,
+ uint *affinity = nullptr,
+
+From f829ab8c6888dce04d79fbb5c5e38b6069894f78 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Wed, 25 Nov 2020 21:17:15 +0000
+Subject: [PATCH 119/165] Update buildfixes31.yml
+
+---
+ .github/workflows/buildfixes31.yml | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/.github/workflows/buildfixes31.yml b/.github/workflows/buildfixes31.yml
+index f3910f9ff91..9e7f8129d4e 100644
+--- a/.github/workflows/buildfixes31.yml
++++ b/.github/workflows/buildfixes31.yml
+@@ -26,7 +26,7 @@ jobs:
+ uses: actions/checkout@v2
+
+ - name: Setup build environment
+- run: echo "::set-env name=MYTHTV_CONFIG::--prefix=${{ github.workspace }}/build/install --cc=${{ matrix.cc }} --cxx=${{ matrix.cxx }}"
++ run: echo MYTHTV_CONFIG=--prefix=${{ github.workspace }}/build/install --cc=${{ matrix.cc }} --cxx=${{ matrix.cxx }}" >> $GITHUB_ENV
+
+ - name: Check ccache
+ uses: actions/cache@v2
+@@ -46,7 +46,7 @@ jobs:
+ run: |
+ brew install pkg-config ccache qt5 nasm libsamplerate taglib lzo libcec libbluray fftw libass libhdhomerun dav1d x264 x265 libvpx openssl exiv2
+ brew link qt5 --force
+- echo "::set-env name=MYTHTV_CONFIG::$MYTHTV_CONFIG --extra-cxxflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib"
++ echo MYTHTV_CONFIG=$MYTHTV_CONFIG --extra-cxxflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib" >> $GITHUB_ENV
+ if: runner.os == 'macOS'
+
+ - name: Configure core
+
+From 207ccf1aeb333152fa5f17d0b4898f8605ccc2ca Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Wed, 25 Nov 2020 21:20:12 +0000
+Subject: [PATCH 120/165] Typos in buildfixes31.yml
+
+---
+ .github/workflows/buildfixes31.yml | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/.github/workflows/buildfixes31.yml b/.github/workflows/buildfixes31.yml
+index 9e7f8129d4e..b230dac0009 100644
+--- a/.github/workflows/buildfixes31.yml
++++ b/.github/workflows/buildfixes31.yml
+@@ -26,7 +26,7 @@ jobs:
+ uses: actions/checkout@v2
+
+ - name: Setup build environment
+- run: echo MYTHTV_CONFIG=--prefix=${{ github.workspace }}/build/install --cc=${{ matrix.cc }} --cxx=${{ matrix.cxx }}" >> $GITHUB_ENV
++ run: echo "MYTHTV_CONFIG=--prefix=${{ github.workspace }}/build/install --cc=${{ matrix.cc }} --cxx=${{ matrix.cxx }}" >> $GITHUB_ENV
+
+ - name: Check ccache
+ uses: actions/cache@v2
+@@ -46,7 +46,7 @@ jobs:
+ run: |
+ brew install pkg-config ccache qt5 nasm libsamplerate taglib lzo libcec libbluray fftw libass libhdhomerun dav1d x264 x265 libvpx openssl exiv2
+ brew link qt5 --force
+- echo MYTHTV_CONFIG=$MYTHTV_CONFIG --extra-cxxflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib" >> $GITHUB_ENV
++ echo "MYTHTV_CONFIG=$MYTHTV_CONFIG --extra-cxxflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib" >> $GITHUB_ENV
+ if: runner.os == 'macOS'
+
+ - name: Configure core
+
+From a087a172311a253bfe488e9139c0c603b7ae4853 Mon Sep 17 00:00:00 2001
+From: John Hoyt <john.hoyt(a)gmail.com>
+Date: Thu, 26 Nov 2020 13:13:30 -0500
+Subject: [PATCH 121/165] backport master:5c180c6 to fixes/31 - Fix
+ deprecation warnings in OSX audio
+
+---
+ mythtv/libs/libmyth/audio/audiooutputca.cpp | 503 ++++++++++++--------
+ 1 file changed, 306 insertions(+), 197 deletions(-)
+
+diff --git a/mythtv/libs/libmyth/audio/audiooutputca.cpp b/mythtv/libs/libmyth/audio/audiooutputca.cpp
+index e5f52aa4f5f..8193d3b5b62 100644
+--- a/mythtv/libs/libmyth/audio/audiooutputca.cpp
++++ b/mythtv/libs/libmyth/audio/audiooutputca.cpp
+@@ -12,6 +12,8 @@
+ * Jeremiah Morris, Andrew Kimpton, Nigel Pearson, Jean-Yves Avenard
+ *****************************************************************************/
+
++#include <vector>
++
+ #include <CoreServices/CoreServices.h>
+ #include <CoreAudio/CoreAudio.h>
+ #include <AudioUnit/AudioUnit.h>
+@@ -27,6 +29,9 @@
+ #define CHANNELS_MIN 1
+ #define CHANNELS_MAX 8
+
++using AudioStreamIDVec = std::vector<AudioStreamID>;
++using AudioStreamRangedVec = std::vector<AudioStreamRangedDescription>;
++
+ #define OSS_STATUS(x) UInt32ToFourCC((UInt32*)&(x))
+ char* UInt32ToFourCC(UInt32* pVal)
+ {
+@@ -112,8 +117,8 @@ class CoreAudioData {
+ bool *ChannelsList(AudioDeviceID d, bool passthru);
+
+ // Helpers for iterating. Returns a malloc'd array
+- AudioStreamID *StreamsList(AudioDeviceID d);
+- AudioStreamBasicDescription *FormatsList(AudioStreamID s);
++ AudioStreamIDVec StreamsList(AudioDeviceID d);
++ AudioStreamRangedVec FormatsList(AudioStreamID s);
+
+ int AudioStreamChangeFormat(AudioStreamID s,
+ AudioStreamBasicDescription format);
+@@ -148,6 +153,7 @@ class CoreAudioData {
+ bool mInitialized {false};
+ bool mStarted {false};
+ bool mWasDigital {false};
++ AudioDeviceIOProcID mIoProcID {};
+ };
+
+ // These callbacks communicate with Core Audio.
+@@ -531,12 +537,28 @@ AudioDeviceID CoreAudioData::GetDeviceWithName(const QString &deviceName)
+ {
+ UInt32 size = 0;
+ AudioDeviceID deviceID = 0;
++ AudioObjectPropertyAddress pa
++ {
++ kAudioHardwarePropertyDevices,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
++ OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
++ 0, nullptr, &size);
++ if (err)
++ {
++ Warn(QString("GetPropertyDataSize: Unable to retrieve the property sizes. "
++ "Error [%1]")
++ .arg(err));
++ return deviceID;
++ }
+
+- AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, nullptr);
+ UInt32 deviceCount = size / sizeof(AudioDeviceID);
+ AudioDeviceID* pDevices = new AudioDeviceID[deviceCount];
+
+- OSStatus err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, pDevices);
++ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
++ 0, nullptr, &size, pDevices);
+ if (err)
+ {
+ Warn(QString("GetDeviceWithName: Unable to retrieve the list of available devices. "
+@@ -568,13 +590,18 @@ AudioDeviceID CoreAudioData::GetDeviceWithName(const QString &deviceName)
+ AudioDeviceID CoreAudioData::GetDefaultOutputDevice()
+ {
+ UInt32 paramSize;
+- OSStatus err;
+ AudioDeviceID deviceId = 0;
++ AudioObjectPropertyAddress pa
++ {
++ kAudioHardwarePropertyDefaultOutputDevice,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
+
+ // Find the ID of the default Device
+ paramSize = sizeof(deviceId);
+- err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
+- ¶mSize, &deviceId);
++ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
++ 0, nullptr, ¶mSize, &deviceId);
+ if (err == noErr)
+ Debug(QString("GetDefaultOutputDevice: default device ID = %1").arg(deviceId));
+ else
+@@ -592,13 +619,27 @@ int CoreAudioData::GetTotalOutputChannels()
+ return 0;
+ UInt32 channels = 0;
+ UInt32 size = 0;
+- AudioDeviceGetPropertyInfo(mDeviceID, 0, false,
+- kAudioDevicePropertyStreamConfiguration,
+- &size, nullptr);
++ AudioObjectPropertyAddress pa
++ {
++ kAudioDevicePropertyStreamConfiguration,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
++ OSStatus err = AudioObjectGetPropertyDataSize(mDeviceID, &pa,
++ 0, nullptr, &size);
++ if (err)
++ {
++ Warn(QString("GetTotalOutputChannels: Unable to get "
++ "size of device output channels - id: %1 Error = [%2]")
++ .arg(mDeviceID)
++ .arg(err));
++ return 0;
++ }
++
+ AudioBufferList *pList = (AudioBufferList *)malloc(size);
+- OSStatus err = AudioDeviceGetProperty(mDeviceID, 0, false,
+- kAudioDevicePropertyStreamConfiguration,
+- &size, pList);
++ err = AudioObjectGetPropertyData(mDeviceID, &pa,
++ 0, nullptr, &size, pList);
+ if (!err)
+ {
+ for (UInt32 buffer = 0; buffer < pList->mNumberBuffers; buffer++)
+@@ -621,15 +662,17 @@ QString *CoreAudioData::GetName()
+ {
+ if (!mDeviceID)
+ return nullptr;
+- UInt32 propertySize;
+- AudioObjectPropertyAddress propertyAddress;
++
++ AudioObjectPropertyAddress pa
++ {
++ kAudioObjectPropertyName,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
+
+ CFStringRef name;
+- propertySize = sizeof(CFStringRef);
+- propertyAddress.mSelector = kAudioObjectPropertyName;
+- propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
+- propertyAddress.mElement = kAudioObjectPropertyElementMaster;
+- OSStatus err = AudioObjectGetPropertyData(mDeviceID, &propertyAddress,
++ UInt32 propertySize = sizeof(CFStringRef);
++ OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa,
+ 0, nullptr, &propertySize, &name);
+ if (err)
+ {
+@@ -648,8 +691,14 @@ bool CoreAudioData::GetAutoHogMode()
+ {
+ UInt32 val = 0;
+ UInt32 size = sizeof(val);
+- OSStatus err = AudioHardwareGetProperty(kAudioHardwarePropertyHogModeIsAllowed,
+- &size, &val);
++ AudioObjectPropertyAddress pa
++ {
++ kAudioHardwarePropertyHogModeIsAllowed,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
++ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr, &size, &val);
+ if (err)
+ {
+ Warn(QString("GetAutoHogMode: Unable to get auto 'hog' mode. Error = [%1]")
+@@ -662,8 +711,15 @@ bool CoreAudioData::GetAutoHogMode()
+ void CoreAudioData::SetAutoHogMode(bool enable)
+ {
+ UInt32 val = enable ? 1 : 0;
+- OSStatus err = AudioHardwareSetProperty(kAudioHardwarePropertyHogModeIsAllowed,
+- sizeof(val), &val);
++ AudioObjectPropertyAddress pa
++ {
++ kAudioHardwarePropertyHogModeIsAllowed,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
++ OSStatus err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
++ sizeof(val), &val);
+ if (err)
+ {
+ Warn(QString("SetAutoHogMode: Unable to set auto 'hog' mode. Error = [%1]")
+@@ -673,12 +729,16 @@ void CoreAudioData::SetAutoHogMode(bool enable)
+
+ pid_t CoreAudioData::GetHogStatus()
+ {
+- OSStatus err;
+ pid_t PID;
+ UInt32 PIDsize = sizeof(PID);
++ AudioObjectPropertyAddress pa
++ {
++ kAudioDevicePropertyHogMode,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
+
+- err = AudioDeviceGetProperty(mDeviceID, 0, FALSE,
+- kAudioDevicePropertyHogMode,
++ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
+ &PIDsize, &PID);
+ if (err != noErr)
+ {
+@@ -693,6 +753,13 @@ pid_t CoreAudioData::GetHogStatus()
+
+ bool CoreAudioData::SetHogStatus(bool hog)
+ {
++ AudioObjectPropertyAddress pa
++ {
++ kAudioDevicePropertyHogMode,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
+ // According to Jeff Moore (Core Audio, Apple), Setting kAudioDevicePropertyHogMode
+ // is a toggle and the only way to tell if you do get hog mode is to compare
+ // the returned pid against getpid, if the match, you have hog mode, if not you don't.
+@@ -705,9 +772,8 @@ bool CoreAudioData::SetHogStatus(bool hog)
+ {
+ Debug(QString("SetHogStatus: Setting 'hog' status on device %1")
+ .arg(mDeviceID));
+- OSStatus err = AudioDeviceSetProperty(mDeviceID, nullptr, 0, false,
+- kAudioDevicePropertyHogMode,
+- sizeof(mHog), &mHog);
++ OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
++ sizeof(mHog), &mHog);
+ if (err || mHog != getpid())
+ {
+ Warn(QString("SetHogStatus: Unable to set 'hog' status. Error = [%1]")
+@@ -725,9 +791,8 @@ bool CoreAudioData::SetHogStatus(bool hog)
+ Debug(QString("SetHogStatus: Releasing 'hog' status on device %1")
+ .arg(mDeviceID));
+ pid_t hogPid = -1;
+- OSStatus err = AudioDeviceSetProperty(mDeviceID, nullptr, 0, false,
+- kAudioDevicePropertyHogMode,
+- sizeof(hogPid), &hogPid);
++ OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
++ sizeof(hogPid), &hogPid);
+ if (err || hogPid == getpid())
+ {
+ Warn(QString("SetHogStatus: Unable to release 'hog' status. Error = [%1]")
+@@ -751,9 +816,15 @@ bool CoreAudioData::SetMixingSupport(bool mix)
+ Debug(QString("SetMixingSupport: %1abling mixing for device %2")
+ .arg(mix ? "En" : "Dis")
+ .arg(mDeviceID));
+- OSStatus err = AudioDeviceSetProperty(mDeviceID, nullptr, 0, false,
+- kAudioDevicePropertySupportsMixing,
+- sizeof(mixEnable), &mixEnable);
++
++ AudioObjectPropertyAddress pa
++ {
++ kAudioDevicePropertySupportsMixing,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++ OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
++ sizeof(mixEnable), &mixEnable);
+ if (err)
+ {
+ Warn(QString("SetMixingSupport: Unable to set MixingSupport to %1. Error = [%2]")
+@@ -772,9 +843,14 @@ bool CoreAudioData::GetMixingSupport()
+ return false;
+ UInt32 val = 0;
+ UInt32 size = sizeof(val);
+- OSStatus err = AudioDeviceGetProperty(mDeviceID, 0, false,
+- kAudioDevicePropertySupportsMixing,
+- &size, &val);
++ AudioObjectPropertyAddress pa
++ {
++ kAudioDevicePropertySupportsMixing,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++ OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa, 0, nullptr,
++ &size, &val);
+ if (err)
+ return false;
+ return (val > 0);
+@@ -783,92 +859,91 @@ bool CoreAudioData::GetMixingSupport()
+ /**
+ * Get a list of all the streams on this device
+ */
+-AudioStreamID *CoreAudioData::StreamsList(AudioDeviceID d)
++AudioStreamIDVec CoreAudioData::StreamsList(AudioDeviceID d)
+ {
+ OSStatus err;
+ UInt32 listSize;
+- AudioStreamID *list;
++ AudioStreamIDVec vec {};
+
++ AudioObjectPropertyAddress pa
++ {
++ kAudioDevicePropertyStreams,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
+
+- err = AudioDeviceGetPropertyInfo(d, 0, FALSE,
+- kAudioDevicePropertyStreams,
+- &listSize, nullptr);
++ err = AudioObjectGetPropertyDataSize(d, &pa,
++ 0, nullptr, &listSize);
+ if (err != noErr)
+ {
+ Error(QString("StreamsList: could not get list size: [%1]")
+ .arg(OSS_STATUS(err)));
+- return nullptr;
++ return {};
+ }
+
+- // Space for a terminating ID:
+- listSize += sizeof(AudioStreamID);
+- list = (AudioStreamID *)malloc(listSize);
+-
+- if (list == nullptr)
++ try
++ {
++ vec.reserve(listSize / sizeof(AudioStreamID));
++ }
++ catch (...)
+ {
+ Error("StreamsList(): out of memory?");
+- return nullptr;
++ return {};
+ }
+
+- err = AudioDeviceGetProperty(d, 0, FALSE,
+- kAudioDevicePropertyStreams,
+- &listSize, list);
++ err = AudioObjectGetPropertyData(d, &pa,
++ 0, nullptr, &listSize, vec.data());
+ if (err != noErr)
+ {
+ Error(QString("StreamsList: could not get list: [%1]")
+ .arg(OSS_STATUS(err)));
+- return nullptr;
++ return {};
+ }
+- // Add a terminating ID:
+- list[listSize/sizeof(AudioStreamID)] = kAudioHardwareBadStreamError;
+
+- return list;
++ return vec;
+ }
+
+-AudioStreamBasicDescription *CoreAudioData::FormatsList(AudioStreamID s)
++AudioStreamRangedVec CoreAudioData::FormatsList(AudioStreamID s)
+ {
+ OSStatus err;
+- AudioStreamBasicDescription *list;
++ AudioStreamRangedVec vec;
+ UInt32 listSize;
+- AudioDevicePropertyID p;
+-
+
+- // This is deprecated for kAudioStreamPropertyAvailablePhysicalFormats,
+- // but compiling on 10.3 requires the older constant
+- p = kAudioStreamPropertyPhysicalFormats;
++ AudioObjectPropertyAddress pa
++ {
++ kAudioStreamPropertyPhysicalFormats,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
+
+ // Retrieve all the stream formats supported by this output stream
+- err = AudioStreamGetPropertyInfo(s, 0, p, &listSize, nullptr);
++ err = AudioObjectGetPropertyDataSize(s, &pa, 0, nullptr, &listSize);
+ if (err != noErr)
+ {
+ Warn(QString("FormatsList(): couldn't get list size: [%1]")
+ .arg(OSS_STATUS(err)));
+- return nullptr;
++ return {};
+ }
+
+- // Space for a terminating ID:
+- listSize += sizeof(AudioStreamBasicDescription);
+- list = (AudioStreamBasicDescription *)malloc(listSize);
+-
+- if (list == nullptr)
++ try
++ {
++ vec.reserve(listSize / sizeof(AudioStreamRangedDescription));
++ }
++ catch (...)
+ {
+ Error("FormatsList(): out of memory?");
+- return nullptr;
++ return {};
+ }
+
+- err = AudioStreamGetProperty(s, 0, p, &listSize, list);
++ err = AudioObjectGetPropertyData(s, &pa, 0, nullptr, &listSize, vec.data());
+ if (err != noErr)
+ {
+ Warn(QString("FormatsList: couldn't get list: [%1]")
+ .arg(OSS_STATUS(err)));
+- free(list);
+- return nullptr;
++ return {};
+ }
+
+- // Add a terminating ID:
+- list[listSize/sizeof(AudioStreamBasicDescription)].mFormatID = 0;
+-
+- return list;
++ return vec;
+ }
+
+ static UInt32 sNumberCommonSampleRates = 15;
+@@ -897,10 +972,15 @@ int *CoreAudioData::RatesList(AudioDeviceID d)
+ UInt32 listSize;
+ UInt32 nbitems = 0;
+
++ AudioObjectPropertyAddress pa
++ {
++ kAudioDevicePropertyAvailableNominalSampleRates,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
+ // retrieve size of rate list
+- err = AudioDeviceGetPropertyInfo(d, 0, 0,
+- kAudioDevicePropertyAvailableNominalSampleRates,
+- &listSize, nullptr);
++ err = AudioObjectGetPropertyDataSize(d, &pa, 0, nullptr, &listSize);
+ if (err != noErr)
+ {
+ Warn(QString("RatesList(): couldn't get data rate list size: [%1]")
+@@ -915,9 +995,7 @@ int *CoreAudioData::RatesList(AudioDeviceID d)
+ return nullptr;
+ }
+
+- err = AudioDeviceGetProperty(
+- d, 0, 0, kAudioDevicePropertyAvailableNominalSampleRates,
+- &listSize, list);
++ err = AudioObjectGetPropertyData(d, &pa, 0, nullptr, &listSize, list);
+ if (err != noErr)
+ {
+ Warn(QString("RatesList(): couldn't get list: [%1]")
+@@ -970,8 +1048,8 @@ int *CoreAudioData::RatesList(AudioDeviceID d)
+
+ bool *CoreAudioData::ChannelsList(AudioDeviceID /*d*/, bool passthru)
+ {
+- AudioStreamID *streams;
+- AudioStreamBasicDescription *formats;
++ AudioStreamIDVec streams;
++ AudioStreamRangedVec formats;
+ bool founddigital = false;
+ bool *list;
+
+@@ -981,7 +1059,7 @@ bool *CoreAudioData::ChannelsList(AudioDeviceID /*d*/, bool passthru)
+ memset(list, 0, (CHANNELS_MAX+1) * sizeof(bool));
+
+ streams = StreamsList(mDeviceID);
+- if (!streams)
++ if (streams.empty())
+ {
+ free(list);
+ return nullptr;
+@@ -989,37 +1067,35 @@ bool *CoreAudioData::ChannelsList(AudioDeviceID /*d*/, bool passthru)
+
+ if (passthru)
+ {
+- for (int i = 0; streams[i] != kAudioHardwareBadStreamError; i++)
++ for (auto stream : streams)
+ {
+- formats = FormatsList(streams[i]);
+- if (!formats)
++ formats = FormatsList(stream);
++ if (formats.empty())
+ continue;
+
+ // Find a stream with a cac3 stream
+- for (int j = 0; formats[j].mFormatID != 0; j++)
++ for (auto format : formats)
+ {
+- if (formats[j].mFormatID == 'IAC3' ||
+- formats[j].mFormatID == kAudioFormat60958AC3)
++ if (format.mFormat.mFormatID == 'IAC3' ||
++ format.mFormat.mFormatID == kAudioFormat60958AC3)
+ {
+- list[formats[j].mChannelsPerFrame] = true;
++ list[format.mFormat.mChannelsPerFrame] = true;
+ founddigital = true;
+ }
+ }
+- free(formats);
+ }
+ }
+
+ if (!founddigital)
+ {
+- for (int i = 0; streams[i] != kAudioHardwareBadStreamError; i++)
++ for (auto stream : streams)
+ {
+- formats = FormatsList(streams[i]);
+- if (!formats)
++ formats = FormatsList(stream);
++ if (formats.empty())
+ continue;
+- for (int j = 0; formats[j].mFormatID != 0; j++)
+- if (formats[j].mChannelsPerFrame <= CHANNELS_MAX)
+- list[formats[j].mChannelsPerFrame] = true;
+- free(formats);
++ for (auto format : formats)
++ if (format.mFormat.mChannelsPerFrame <= CHANNELS_MAX)
++ list[format.mFormat.mChannelsPerFrame] = true;
+ }
+ }
+ return list;
+@@ -1027,11 +1103,17 @@ bool *CoreAudioData::ChannelsList(AudioDeviceID /*d*/, bool passthru)
+
+ int CoreAudioData::OpenAnalog()
+ {
+- ComponentDescription desc;
++ AudioComponentDescription desc;
+ AudioStreamBasicDescription DeviceFormat;
+ AudioChannelLayout *layout;
+ AudioChannelLayout new_layout;
+ AudioDeviceID defaultDevice = GetDefaultOutputDevice();
++ AudioObjectPropertyAddress pa
++ {
++ kAudioHardwarePropertyDevices,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
+
+ Debug("OpenAnalog: Entering");
+
+@@ -1049,14 +1131,14 @@ int CoreAudioData::OpenAnalog()
+ desc.componentFlagsMask = 0;
+ mDigitalInUse = false;
+
+- Component comp = FindNextComponent(nullptr, &desc);
++ AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
+ if (comp == nullptr)
+ {
+ Error("OpenAnalog: AudioComponentFindNext failed");
+ return false;
+ }
+
+- OSErr err = OpenAComponent(comp, &mOutputUnit);
++ OSErr err = AudioComponentInstanceNew(comp, &mOutputUnit);
+ if (err)
+ {
+ Error(QString("OpenAnalog: AudioComponentInstanceNew returned %1")
+@@ -1134,23 +1216,16 @@ int CoreAudioData::OpenAnalog()
+ .arg(StreamDescriptionToString(DeviceFormat)));
+ }
+ /* Get the channel layout of the device side of the unit */
+- err = AudioUnitGetPropertyInfo(mOutputUnit,
+- kAudioDevicePropertyPreferredChannelLayout,
+- kAudioUnitScope_Output,
+- 0,
+- ¶m_size,
+- nullptr);
++ pa.mSelector = kAudioDevicePropertyPreferredChannelLayout;
++ err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
++ 0, nullptr, ¶m_size);
+
+ if(!err)
+ {
+ layout = (AudioChannelLayout *) malloc(param_size);
+
+- err = AudioUnitGetProperty(mOutputUnit,
+- kAudioDevicePropertyPreferredChannelLayout,
+- kAudioUnitScope_Output,
+- 0,
+- layout,
+- ¶m_size);
++ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
++ 0, nullptr, ¶m_size, layout);
+
+ /* We need to "fill out" the ChannelLayout, because there are multiple ways that it can be set */
+ if(layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
+@@ -1365,7 +1440,7 @@ void CoreAudioData::CloseAnalog()
+ Debug(QString("CloseAnalog: AudioUnitUninitialize %1")
+ .arg(err));
+ }
+- err = CloseComponent(mOutputUnit);
++ err = AudioComponentInstanceDispose(mOutputUnit);
+ Debug(QString("CloseAnalog: CloseComponent %1")
+ .arg(err));
+ mOutputUnit = nullptr;
+@@ -1379,46 +1454,43 @@ void CoreAudioData::CloseAnalog()
+ bool CoreAudioData::OpenSPDIF()
+ {
+ OSStatus err;
+- AudioStreamID *streams;
++ AudioStreamIDVec streams;
+ AudioStreamBasicDescription outputFormat {};
+
+ Debug("OpenSPDIF: Entering");
+
+ streams = StreamsList(mDeviceID);
+- if (!streams)
++ if (streams.empty())
+ {
+ Warn("OpenSPDIF: Couldn't retrieve list of streams");
+ return false;
+ }
+
+- for (int i = 0; streams[i] != kAudioHardwareBadStreamError; ++i)
++ for (size_t i = 0; i < streams.size(); ++i)
+ {
+- AudioStreamBasicDescription *formats = FormatsList(streams[i]);
+- if (!formats)
++ AudioStreamRangedVec formats = FormatsList(streams[i]);
++ if (formats.empty())
+ continue;
+
+ // Find a stream with a cac3 stream
+- for (int j = 0; formats[j].mFormatID != 0; j++)
++ for (auto format : formats)
+ {
+ Debug(QString("OpenSPDIF: Considering Physical Format: %1")
+- .arg(StreamDescriptionToString(formats[j])));
+- if ((formats[j].mFormatID == 'IAC3' ||
+- formats[j].mFormatID == kAudioFormat60958AC3) &&
+- formats[j].mSampleRate == mCA->m_sampleRate)
++ .arg(StreamDescriptionToString(format.mFormat)));
++ if ((format.mFormat.mFormatID == 'IAC3' ||
++ format.mFormat.mFormatID == kAudioFormat60958AC3) &&
++ format.mFormat.mSampleRate == mCA->m_sampleRate)
+ {
+ Debug("OpenSPDIF: Found digital format");
+ mStreamIndex = i;
+ mStreamID = streams[i];
+- outputFormat = formats[j];
++ outputFormat = format.mFormat;
+ break;
+ }
+ }
+- free(formats);
+-
+ if (outputFormat.mFormatID)
+ break;
+ }
+- free(streams);
+
+ if (!outputFormat.mFormatID)
+ {
+@@ -1428,12 +1500,18 @@ bool CoreAudioData::OpenSPDIF()
+
+ if (mRevertFormat == false)
+ {
++ AudioObjectPropertyAddress pa
++ {
++ kAudioStreamPropertyPhysicalFormat,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
+ // Retrieve the original format of this stream first
+ // if not done so already
+ UInt32 paramSize = sizeof(mFormatOrig);
+- err = AudioStreamGetProperty(mStreamID, 0,
+- kAudioStreamPropertyPhysicalFormat,
+- ¶mSize, &mFormatOrig);
++ err = AudioObjectGetPropertyData(mStreamID, &pa, 0, nullptr,
++ ¶mSize, &mFormatOrig);
+ if (err != noErr)
+ {
+ Warn(QString("OpenSPDIF - could not retrieve the original streamformat: [%1]")
+@@ -1465,19 +1543,19 @@ bool CoreAudioData::OpenSPDIF()
+ mBytesPerPacket = mFormatNew.mBytesPerPacket;
+
+ // Add IOProc callback
+- err = AudioDeviceAddIOProc(mDeviceID,
+- (AudioDeviceIOProc)RenderCallbackSPDIF,
+- (void *)this);
++ err = AudioDeviceCreateIOProcID(mDeviceID,
++ (AudioDeviceIOProc)RenderCallbackSPDIF,
++ (void *)this, &mIoProcID);
+ if (err != noErr)
+ {
+- Error(QString("OpenSPDIF: AudioDeviceAddIOProc failed: [%1]")
++ Error(QString("OpenSPDIF: AudioDeviceCreateIOProcID failed: [%1]")
+ .arg(OSS_STATUS(err)));
+ return false;
+ }
+ mIoProc = true;
+
+ // Start device
+- err = AudioDeviceStart(mDeviceID, (AudioDeviceIOProc)RenderCallbackSPDIF);
++ err = AudioDeviceStart(mDeviceID, mIoProcID);
+ if (err != noErr)
+ {
+ Error(QString("OpenSPDIF: AudioDeviceStart failed: [%1]")
+@@ -1499,7 +1577,7 @@ void CoreAudioData::CloseSPDIF()
+ // Stop device
+ if (mStarted)
+ {
+- err = AudioDeviceStop(mDeviceID, (AudioDeviceIOProc)RenderCallbackSPDIF);
++ err = AudioDeviceStop(mDeviceID, mIoProcID);
+ if (err != noErr)
+ Error(QString("CloseSPDIF: AudioDeviceStop failed: [%1]")
+ .arg(OSS_STATUS(err)));
+@@ -1509,10 +1587,9 @@ void CoreAudioData::CloseSPDIF()
+ // Remove IOProc callback
+ if (mIoProc)
+ {
+- err = AudioDeviceRemoveIOProc(mDeviceID,
+- (AudioDeviceIOProc)RenderCallbackSPDIF);
++ err = AudioDeviceDestroyIOProcID(mDeviceID, mIoProcID);
+ if (err != noErr)
+- Error(QString("CloseSPDIF: AudioDeviceRemoveIOProc failed: [%1]")
++ Error(QString("CloseSPDIF: AudioDeviceDestroyIOProcID failed: [%1]")
+ .arg(OSS_STATUS(err)));
+ mIoProc = false;
+ }
+@@ -1540,9 +1617,14 @@ int CoreAudioData::AudioStreamChangeFormat(AudioStreamID s,
+ .arg(s)
+ .arg(StreamDescriptionToString(format)));
+
+- OSStatus err = AudioStreamSetProperty(s, nullptr, 0,
+- kAudioStreamPropertyPhysicalFormat,
+- sizeof(format), &format);
++ AudioObjectPropertyAddress pa
++ {
++ kAudioStreamPropertyPhysicalFormat,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++ OSStatus err = AudioObjectSetPropertyData(s, &pa, 0, nullptr,
++ sizeof(format), &format);
+ if (err != noErr)
+ {
+ Error(QString("AudioStreamChangeFormat couldn't set stream format: [%1]")
+@@ -1554,37 +1636,31 @@ int CoreAudioData::AudioStreamChangeFormat(AudioStreamID s,
+
+ bool CoreAudioData::FindAC3Stream()
+ {
+- bool foundAC3Stream = false;
+- AudioStreamID *streams;
++ AudioStreamIDVec streams;
+
+
+ // Get a list of all the streams on this device
+ streams = StreamsList(mDeviceID);
+- if (!streams)
++ if (streams.empty())
+ return false;
+
+- for (int i = 0; !foundAC3Stream &&
+- streams[i] != kAudioHardwareBadStreamError; ++i)
++ for (auto stream : streams)
+ {
+- AudioStreamBasicDescription *formats = FormatsList(streams[i]);
+- if (!formats)
++ AudioStreamRangedVec formats = FormatsList(stream);
++ if (formats.empty())
+ continue;
+
+ // Find a stream with a cac3 stream
+- for (int j = 0; formats[j].mFormatID != 0; j++)
+- if (formats[j].mFormatID == 'IAC3' ||
+- formats[j].mFormatID == kAudioFormat60958AC3)
++ for (auto format : formats)
++ if (format.mFormat.mFormatID == 'IAC3' ||
++ format.mFormat.mFormatID == kAudioFormat60958AC3)
+ {
+ Debug("FindAC3Stream: found digital format");
+- foundAC3Stream = true;
+- break;
++ return true;
+ }
+-
+- free(formats);
+ }
+- free(streams);
+
+- return foundAC3Stream;
++ return false;
+ }
+
+ /**
+@@ -1593,34 +1669,46 @@ bool CoreAudioData::FindAC3Stream()
+ */
+ void CoreAudioData::ResetAudioDevices()
+ {
+- AudioDeviceID *devices;
+- int numDevices;
+ UInt32 size;
++ AudioObjectPropertyAddress pa
++ {
++ kAudioHardwarePropertyDevices,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
+
++ OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
++ 0, nullptr, &size);
++ if (err)
++ {
++ Warn(QString("GetPropertyDataSize: Unable to retrieve the property sizes. "
++ "Error [%1]")
++ .arg(err));
++ return;
++ }
+
+- AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, nullptr);
+- devices = (AudioDeviceID*)malloc(size);
+- if (!devices)
++ std::vector<AudioDeviceID> devices = {};
++ devices.resize(size / sizeof(AudioDeviceID));
++ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
++ 0, nullptr, &size, devices.data());
++ if (err)
+ {
+- Error("ResetAudioDevices: out of memory?");
+- return;
++ Warn(QString("GetPropertyData: Unable to retrieve the list of available devices. "
++ "Error [%1]")
++ .arg(err));
++ return;
+ }
+- numDevices = size / sizeof(AudioDeviceID);
+- AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, devices);
+
+- for (int i = 0; i < numDevices; i++)
++ for (const auto & dev : devices)
+ {
+- AudioStreamID *streams;
++ AudioStreamIDVec streams;
+
+- streams = StreamsList(devices[i]);
+- if (!streams)
++ streams = StreamsList(dev);
++ if (streams.empty())
+ continue;
+- for (int j = 0; streams[j] != kAudioHardwareBadStreamError; j++)
+- ResetStream(streams[j]);
+-
+- free(streams);
++ for (auto stream : streams)
++ ResetStream(stream);
+ }
+- free(devices);
+ }
+
+ void CoreAudioData::ResetStream(AudioStreamID s)
+@@ -1628,29 +1716,35 @@ void CoreAudioData::ResetStream(AudioStreamID s)
+ AudioStreamBasicDescription currentFormat;
+ OSStatus err;
+ UInt32 paramSize;
++ AudioObjectPropertyAddress pa
++ {
++ kAudioStreamPropertyPhysicalFormat,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
+
+ // Find the streams current physical format
+ paramSize = sizeof(currentFormat);
+- AudioStreamGetProperty(s, 0, kAudioStreamPropertyPhysicalFormat,
+- ¶mSize, ¤tFormat);
++ AudioObjectGetPropertyData(s, &pa, 0, nullptr,
++ ¶mSize, ¤tFormat);
+
+ // If it's currently AC-3/SPDIF then reset it to some mixable format
+ if (currentFormat.mFormatID == 'IAC3' ||
+ currentFormat.mFormatID == kAudioFormat60958AC3)
+ {
+- AudioStreamBasicDescription *formats = FormatsList(s);
++ AudioStreamRangedVec formats = FormatsList(s);
+ bool streamReset = false;
+
+
+- if (!formats)
++ if (formats.empty())
+ return;
+
+- for (int i = 0; !streamReset && formats[i].mFormatID != 0; i++)
+- if (formats[i].mFormatID == kAudioFormatLinearPCM)
++ for (auto format : formats)
++ if (format.mFormat.mFormatID == kAudioFormatLinearPCM)
+ {
+- err = AudioStreamSetProperty(s, nullptr, 0,
+- kAudioStreamPropertyPhysicalFormat,
+- sizeof(formats[i]), &(formats[i]));
++ err = AudioObjectSetPropertyData(s, &pa, 0, nullptr,
++ sizeof(format), &(format.mFormat));
+ if (err != noErr)
+ {
+ Warn(QString("ResetStream: could not set physical format: [%1]")
+@@ -1663,8 +1757,6 @@ void CoreAudioData::ResetStream(AudioStreamID s)
+ sleep(1); // For the change to take effect
+ }
+ }
+-
+- free(formats);
+ }
+ }
+
+@@ -1674,11 +1766,28 @@ QMap<QString, QString> *AudioOutputCA::GetDevices(const char */*type*/)
+
+ // Obtain a list of all available audio devices
+ UInt32 size = 0;
+- AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, nullptr);
++
++ AudioObjectPropertyAddress pa
++ {
++ kAudioHardwarePropertyDevices,
++ kAudioObjectPropertyScopeGlobal,
++ kAudioObjectPropertyElementMaster
++ };
++
++ OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
++ 0, nullptr, &size);
++ if (err)
++ {
++ VBAUDIO(QString("GetPropertyDataSize: Unable to retrieve the property sizes. "
++ "Error [%1]")
++ .arg(err));
++ return devs;
++ }
++
+ UInt32 deviceCount = size / sizeof(AudioDeviceID);
+ AudioDeviceID* pDevices = new AudioDeviceID[deviceCount];
+- OSStatus err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+- &size, pDevices);
++ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
++ 0, nullptr, &size, pDevices);
+ if (err)
+ VBAUDIO(QString("AudioOutputCA::GetDevices: Unable to retrieve the list of "
+ "available devices. Error [%1]")
+
+From 102dbe673325fccbc5bf073f94e43fb292bea4b6 Mon Sep 17 00:00:00 2001
+From: John Hoyt <john.hoyt(a)gmail.com>
+Date: Thu, 26 Nov 2020 13:14:27 -0500
+Subject: [PATCH 122/165] backport master:d0d9a4e to fixes/31 - Fix
+ audioconvert test failures when compiling X86 optimized code.
+
+---
+ mythtv/libs/libmyth/audio/audioconvert.cpp | 7 +++++++
+ mythtv/libs/libmyth/audio/audiooutpututil.cpp | 1 +
+ 2 files changed, 8 insertions(+)
+
+diff --git a/mythtv/libs/libmyth/audio/audioconvert.cpp b/mythtv/libs/libmyth/audio/audioconvert.cpp
+index bb8e0be5fc6..8dd3efdc7d0 100644
+--- a/mythtv/libs/libmyth/audio/audioconvert.cpp
++++ b/mythtv/libs/libmyth/audio/audioconvert.cpp
+@@ -142,6 +142,7 @@ static int toFloat8(float* out, const uchar* in, int len)
+ "jnz 1b \n\t"
+ :"+r"(out),"+r"(in)
+ :"c"(loops), "r"(a), "r"(f)
++ :"xmm0","xmm1","xmm2","xmm3","xmm4","xmm5","xmm6","xmm7"
+ );
+ }
+ #endif //ARCH_x86
+@@ -204,6 +205,7 @@ static int fromFloat8(uchar* out, const float* in, int len)
+ "jnz 1b \n\t"
+ :"+r"(out),"+r"(in)
+ :"c"(loops), "r"(a), "r"(f)
++ :"xmm0","xmm1","xmm2","xmm3","xmm4","xmm7"
+ );
+ }
+ #endif //ARCH_x86
+@@ -258,6 +260,7 @@ static int toFloat16(float* out, const short* in, int len)
+ "jnz 1b \n\t"
+ :"+r"(out),"+r"(in)
+ :"c"(loops), "r"(f)
++ :"xmm1","xmm2","xmm3","xmm4","xmm5","xmm6","xmm7"
+ );
+ }
+ #endif //ARCH_x86
+@@ -311,6 +314,7 @@ static int fromFloat16(short* out, const float* in, int len)
+ "jnz 1b \n\t"
+ :"+r"(out),"+r"(in)
+ :"c"(loops), "r"(f)
++ :"xmm1","xmm2","xmm3","xmm4","xmm7"
+ );
+ }
+ #endif //ARCH_x86
+@@ -367,6 +371,7 @@ static int toFloat32(AudioFormat format, float* out, const int* in, int len)
+ "jnz 1b \n\t"
+ :"+r"(out),"+r"(in)
+ :"c"(loops), "r"(f), "r"(shift)
++ :"xmm1","xmm2","xmm3","xmm4","xmm6","xmm7"
+ );
+ }
+ #endif //ARCH_x86
+@@ -439,6 +444,7 @@ static int fromFloat32(AudioFormat format, int* out, const float* in, int len)
+ "jnz 1b \n\t"
+ :"+r"(out), "+r"(in)
+ :"c"(loops), "r"(f), "m"(o), "m"(mo), "r"(shift)
++ :"xmm0","xmm1","xmm2","xmm3","xmm4","xmm5","xmm6","xmm7"
+ );
+ }
+ #endif //ARCH_x86
+@@ -504,6 +510,7 @@ static int fromFloatFLT(float* out, const float* in, int len)
+ "jnz 1b \n\t"
+ :"+r"(out), "+r"(in)
+ :"c"(loops), "m"(o), "m"(mo)
++ :"xmm1","xmm2","xmm3","xmm4","xmm6","xmm7"
+ );
+ }
+ #endif //ARCH_x86
+diff --git a/mythtv/libs/libmyth/audio/audiooutpututil.cpp b/mythtv/libs/libmyth/audio/audiooutpututil.cpp
+index 6f4703642d6..0f9eb8e5df6 100644
+--- a/mythtv/libs/libmyth/audio/audiooutpututil.cpp
++++ b/mythtv/libs/libmyth/audio/audiooutpututil.cpp
+@@ -148,6 +148,7 @@ void AudioOutputUtil::AdjustVolume(void *buf, int len, int volume,
+ "jnz 1b \n\t"
+ :"+r"(fptr)
+ :"c"(loops),"m"(g)
++ :"xmm0","xmm1","xmm2","xmm3","xmm4"
+ );
+ }
+ #endif //ARCH_X86
+
+From 6b93a8acd8c9eaf74b7ca66eedb54299edc5e012 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Tue, 8 Dec 2020 19:20:02 +0100
+Subject: [PATCH 123/165] Fix ttvdb.py to get coverarts for seasons.
+
+Fetching the coverart with the scrip ttvdb.py fails,
+if the artwork is listed in data['_banners']['season'],
+but the section data['_banners']['poster'] is missing.
+In this case, the xslt transformation does not work as expected.
+
+Checked with ttvdb.py -l de -a CH -D 89901 36 4
+
+(cherry picked from commit 8d6eaf2888f571c7e4c7d21b0909082c772ac659)
+---
+ mythtv/programs/scripts/metadata/Television/ttvdb.py | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/mythtv/programs/scripts/metadata/Television/ttvdb.py b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+index 76b98618920..1c0bb1f27d1 100755
+--- a/mythtv/programs/scripts/metadata/Television/ttvdb.py
++++ b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+@@ -2065,6 +2065,13 @@ def series_images_item_func(parent):
+ return "Banner"
+ for show_id in t.shows.keys():
+ break
++
++ # dict for 'data['_banners']['poster']['raw'] must exist for fetching coverarts,
++ # check with ttvdb.py -l de -a CH -D 89901 36 4
++ if 'poster' not in t.shows[show_id].data['_banners'].keys():
++ t.shows[show_id].data['_banners']['poster'] = {}
++ t.shows[show_id].data['_banners']['poster']['raw'] = {}
++
+ # sort the cast into sort order
+ t.shows[show_id].data['_actors'] = sorted(t.shows[show_id].data['_actors'], key=lambda k: k['sortOrder'])
+ t.searchTree = None
+
+From e174aa49f5841bb05ac038e0ec8e57200e3c3419 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Tue, 8 Dec 2020 19:27:52 +0100
+Subject: [PATCH 124/165] Temporary fix for missing coverart for seasons from
+ ttvdb.py
+
+If ttvdb.py does not return any coverart for specific seasons,
+use the global coverart from series and patch the xml output.
+
+(cherry picked from commit 735802a37a3e10792deb6d3b1a74be93fc4bdde9)
+---
+ mythtv/programs/scripts/metadata/Television/ttvdb.py | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+diff --git a/mythtv/programs/scripts/metadata/Television/ttvdb.py b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+index 1c0bb1f27d1..e244eec41c4 100755
+--- a/mythtv/programs/scripts/metadata/Television/ttvdb.py
++++ b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+@@ -2151,6 +2151,18 @@ def displaySeriesXML(tvdb_api, series_season_ep):
+
+ tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbVideo.xsl')))
+ items = tvdbQueryXslt(allDataElement)
++
++ # temporary fix for missing coverart: use global coverart from series
++ if len(items.xpath("//image[@type='coverart']")) == 0:
++ for el in allDataElement.iter("series"):
++ glob_poster = el.find("poster")
++ if glob_poster is not None:
++ glob_url = glob_poster.text
++ glob_thumb = glob_url.replace("posters", "_cache/posters")
++ glob_coverart = etree.Element("image", type = "coverart", url = glob_url, thumb = glob_thumb)
++ items.find("item").find("images").append(glob_coverart)
++ break
++
+ if items.getroot() is not None:
+ if len(items.xpath('//item')):
+ sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
+
+From f42a3dbbd159bfc6028d78b6f507596e196af1cc Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Tue, 8 Dec 2020 19:46:11 +0100
+Subject: [PATCH 125/165] TMDB3.py: Sort coverarts by system language or 'en',
+ if none found for given language
+
+The grabber script tmdb3.py already sorts the posters
+by the given language, but if no poster of given language
+was found, we sort by system language and then by language "en".
+
+Refs #180
+
+(cherry picked from commit 2940cbcaa26d22b65070ea5871db7b72b1c96fbc)
+---
+ .../programs/scripts/metadata/Movie/tmdb3.py | 42 +++++++++++++++++--
+ 1 file changed, 38 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/programs/scripts/metadata/Movie/tmdb3.py b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+index 972cfaa9eab..d82fcd63991 100755
+--- a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
++++ b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+@@ -10,8 +10,8 @@
+ # http://help.themoviedb.org/kb/api/about-3
+ #-----------------------
+ __title__ = "TheMovieDB.org V3"
+-__author__ = "Raymond Wagner"
+-__version__ = "0.3.7"
++__author__ = "Raymond Wagner, Roland Ernst"
++__version__ = "0.3.8"
+ # 0.1.0 Initial version
+ # 0.2.0 Add language support, move cache to home directory
+ # 0.3.0 Enable version detection to allow use in MythTV
+@@ -27,6 +27,7 @@
+ # 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
++# 0.3.8 Sort posters by system language or 'en', if not found for given language
+
+ from optparse import OptionParser
+ import sys
+@@ -45,11 +46,13 @@ def timeouthandler(signal, frame):
+
+ def buildSingle(inetref, opts):
+ from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
+- from MythTV.tmdb3 import Movie
++ from MythTV.tmdb3 import Movie, get_locale
+ from MythTV import VideoMetadata
+ from lxml import etree
+
++ import locale as py_locale
+ import re
++
+ if re.match('^0[0-9]{6}$', inetref):
+ movie = Movie.fromIMDB(inetref)
+ else:
+@@ -120,11 +123,42 @@ def buildSingle(inetref, opts):
+ 'thumb':backdrop.geturl(backdrop.sizes()[0]),
+ 'height':str(backdrop.height),
+ 'width':str(backdrop.width)})
+- for poster in movie.posters:
++
++ # tmdb already sorts the posters by language
++ # if no poster of given language was found,
++ # try to sort by system language and then by language "en"
++ system_language = py_locale.getdefaultlocale()[0].split("_")[0]
++ locale_language = get_locale().language
++ if opts.debug:
++ print("system_language : ", system_language)
++ print("locale_language : ", locale_language)
++
++ loc_posters = movie.posters
++ if loc_posters[0].language != locale_language \
++ and locale_language != system_language:
++ if opts.debug:
++ print("1: No poster found for language '%s', trying to sort posters by '%s' :"
++ %(locale_language, system_language))
++ loc_posters = sorted(movie.posters,
++ key = lambda x: x.language==system_language, reverse = True)
++
++ if loc_posters[0].language != system_language \
++ and loc_posters[0].language != locale_language:
++ if opts.debug:
++ print("2: No poster found for language '%s', trying to sort posters by '%s' :"
++ %(system_language, "en"))
++ loc_posters = sorted(movie.posters,
++ key = lambda x: x.language=="en", reverse = True)
++
++ for poster in loc_posters:
++ if opts.debug:
++ print("Poster : ", poster.language, " | ", poster.userrating,
++ "\t | ", poster.geturl())
+ m.images.append({'type':'coverart', 'url':poster.geturl(),
+ 'thumb':poster.geturl(poster.sizes()[0]),
+ 'height':str(poster.height),
+ 'width':str(poster.width)})
++
+ tree.append(m.toXML())
+ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+ xml_declaration=True))
+
+From f29a663a2577d832584ef289e118ff7d1cbade6a Mon Sep 17 00:00:00 2001
+From: Paul Harrison <paul(a)mythqml.net>
+Date: Sat, 5 Dec 2020 18:14:49 +0000
+Subject: [PATCH 126/165] VBox: use the common part of the UDN to identify
+ VBoxes found by UPnP
+
+(cherry picked from commit cc9b462e7254f96ad370db6405481421d4b8caf9)
+---
+ mythtv/libs/libmythtv/recorders/vboxutils.cpp | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/vboxutils.cpp b/mythtv/libs/libmythtv/recorders/vboxutils.cpp
+index d3fff12317b..265a53a0e2a 100644
+--- a/mythtv/libs/libmythtv/recorders/vboxutils.cpp
++++ b/mythtv/libs/libmythtv/recorders/vboxutils.cpp
+@@ -22,6 +22,7 @@
+
+ #define SEARCH_TIME 3000
+ #define VBOX_URI "urn:schemas-upnp-org:device:MediaServer:1"
++#define VBOX_UDN "uuid:b7531642-0123-3210"
+
+ VBox::VBox(const QString &url)
+ {
+@@ -102,11 +103,12 @@ QStringList VBox::doUPNPSearch(void)
+
+ QString friendlyName = BE->GetDeviceDesc()->m_rootDevice.m_sFriendlyName;
+ QString ip = BE->GetDeviceDesc()->m_hostUrl.host();
++ QString udn = BE->GetDeviceDesc()->m_rootDevice.m_sUDN;
+ int port = BE->GetDeviceDesc()->m_hostUrl.port();
+
+ LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Found possible VBox at %1 (%2:%3)").arg(friendlyName).arg(ip).arg(port));
+
+- if (friendlyName.startsWith("VBox"))
++ if (udn.startsWith(VBOX_UDN))
+ {
+ // we found one
+ QString id;
+
+From e9b795a1e43023b4141a28b9c620213097cdfbfe Mon Sep 17 00:00:00 2001
+From: Paul Harrison <paul(a)mythqml.net>
+Date: Tue, 8 Dec 2020 18:02:06 +0000
+Subject: [PATCH 127/165] Guide Data: allow for previously shown dates before
+ 1940
+
+According to Google the first commercial movie screening was December 28, 1895
+so allow all dates after that but still reject any dates before that just
+in case.
+
+(cherry picked from commit aeca3d714402abe9c9ef837231f88824eba373c4)
+---
+ mythtv/libs/libmyth/programinfo.cpp | 6 +++---
+ mythtv/libs/libmythtv/mpeg/dishdescriptors.cpp | 2 +-
+ mythtv/libs/libmythtv/recordinginfo.cpp | 4 ++--
+ 3 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp
+index 19b3a6498b3..eebb99a4fd5 100644
+--- a/mythtv/libs/libmyth/programinfo.cpp
++++ b/mythtv/libs/libmyth/programinfo.cpp
+@@ -405,7 +405,7 @@ ProgramInfo::ProgramInfo(
+ m_inputName(std::move(_inputname)),
+ m_bookmarkUpdate(std::move(_bookmarkupdate))
+ {
+- if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1940, 1, 1))
++ if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
+ m_originalAirDate = QDate();
+
+ SetPathname(_pathname);
+@@ -586,7 +586,7 @@ ProgramInfo::ProgramInfo(
+ m_programFlags |= (commfree) ? FL_CHANCOMMFREE : 0;
+ m_programFlags |= (repeat) ? FL_REPEAT : 0;
+
+- if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1940, 1, 1))
++ if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
+ m_originalAirDate = QDate();
+
+ for (auto *it : schedList)
+@@ -2064,7 +2064,7 @@ bool ProgramInfo::LoadProgramFromRecorded(
+ (query.value(42).toUInt() << kAudioPropertyOffset));
+ // ancillary data -- end
+
+- if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1940, 1, 1))
++ if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
+ m_originalAirDate = QDate();
+
+ // Extra stuff which is not serialized and may get lost.
+diff --git a/mythtv/libs/libmythtv/mpeg/dishdescriptors.cpp b/mythtv/libs/libmythtv/mpeg/dishdescriptors.cpp
+index b549379261f..62224aabf9a 100644
+--- a/mythtv/libs/libmythtv/mpeg/dishdescriptors.cpp
++++ b/mythtv/libs/libmythtv/mpeg/dishdescriptors.cpp
+@@ -158,7 +158,7 @@ QDate DishEventTagsDescriptor::originalairdate(void) const
+
+ QDate originalairdate = t.date();
+
+- if (originalairdate.year() < 1940)
++ if (originalairdate.year() < 1895)
+ return {};
+
+ return originalairdate;
+diff --git a/mythtv/libs/libmythtv/recordinginfo.cpp b/mythtv/libs/libmythtv/recordinginfo.cpp
+index 9e140a3fe5c..7138c2be097 100644
+--- a/mythtv/libs/libmythtv/recordinginfo.cpp
++++ b/mythtv/libs/libmythtv/recordinginfo.cpp
+@@ -126,7 +126,7 @@ RecordingInfo::RecordingInfo(
+
+ m_stars = clamp(_stars, 0.0F, 1.0F);
+ m_originalAirDate = _originalAirDate;
+- if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1940, 1, 1))
++ if (m_originalAirDate.isValid() && m_originalAirDate < QDate(1895, 12, 28))
+ m_originalAirDate = QDate();
+
+ m_programFlags &= ~FL_REPEAT;
+@@ -1093,7 +1093,7 @@ bool RecordingInfo::InsertProgram(RecordingInfo *pg,
+ query.bindValue(":ORIGAIRDATE", pg->m_originalAirDate);
+ // If there is no originalairdate use "year"
+ }
+- else if (pg->m_year >= 1940)
++ else if (pg->m_year >= 1895)
+ {
+ query.bindValue(":ORIGAIRDATE", QDate(pg->m_year,1,1));
+ }
+
+From 0bc3cdfbf7b649e6060db3fa6b67be54f788b00e Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Mon, 28 Dec 2020 17:18:13 -0600
+Subject: [PATCH 128/165] Fix handling of deleted channels in
+ Scheduler::GetAllScheduled()
+
+Deleted channels should not be joined at all. Joining them only on
+chanid and then filtering in the where clause caused valid rules to
+not be included.
+
+Fixes #295
+
+(cherry picked from commit 6157a1772ac4f005aa144c9f97cb317b3d341746)
+---
+ mythtv/programs/mythbackend/scheduler.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
+index 383f3c91ba5..666b67820f4 100644
+--- a/mythtv/programs/mythbackend/scheduler.cpp
++++ b/mythtv/programs/mythbackend/scheduler.cpp
+@@ -4899,7 +4899,7 @@ void Scheduler::GetAllScheduled(RecList &proglist, SchedSortColumn sortBy,
+ " channel.commmethod " // 25
+ "FROM record "
+ "LEFT JOIN channel ON channel.callsign = record.station "
+- "WHERE deleted IS NULL "
++ " AND deleted IS NULL "
+ "GROUP BY recordid "
+ "ORDER BY %1 %2");
+
+
+From e8d34eacf75bd7c1aa86c709aabc1098f0bfccc3 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Wed, 6 Jan 2021 15:10:54 +0100
+Subject: [PATCH 129/165] Be robust if grabber ttvdb.py does not return any
+ banners.
+
+Refs #298
+
+(cherry picked from commit 817d97101f211ad7d755e3ef3fe3eaa072384528)
+---
+ mythtv/programs/scripts/metadata/Television/ttvdb.py | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/programs/scripts/metadata/Television/ttvdb.py b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+index e244eec41c4..7421969948e 100755
+--- a/mythtv/programs/scripts/metadata/Television/ttvdb.py
++++ b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+@@ -2068,9 +2068,13 @@ def series_images_item_func(parent):
+
+ # dict for 'data['_banners']['poster']['raw'] must exist for fetching coverarts,
+ # check with ttvdb.py -l de -a CH -D 89901 36 4
+- if 'poster' not in t.shows[show_id].data['_banners'].keys():
+- t.shows[show_id].data['_banners']['poster'] = {}
+- t.shows[show_id].data['_banners']['poster']['raw'] = {}
++ try:
++ if 'poster' not in t.shows[show_id].data['_banners'].keys():
++ t.shows[show_id].data['_banners']['poster'] = {}
++ t.shows[show_id].data['_banners']['poster']['raw'] = {}
++ except KeyError:
++ # no banner fanart exists
++ pass
+
+ # sort the cast into sort order
+ t.shows[show_id].data['_actors'] = sorted(t.shows[show_id].data['_actors'], key=lambda k: k['sortOrder'])
+
+From 016630a35cd24d3d1e4eca11e62758161d5af92f Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Fri, 8 Jan 2021 18:06:54 +0100
+Subject: [PATCH 130/165] Fix adding missing coverart in ttvdb.py
+
+and ignore incomplete urls.
+
+Refs #298
+
+(cherry picked from commit 3b41c311d97a8dfb9ca87b365517d35b87e2db83)
+---
+ mythtv/programs/scripts/metadata/Television/ttvdb.py | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/programs/scripts/metadata/Television/ttvdb.py b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+index 7421969948e..a95ae8484cb 100755
+--- a/mythtv/programs/scripts/metadata/Television/ttvdb.py
++++ b/mythtv/programs/scripts/metadata/Television/ttvdb.py
+@@ -2160,10 +2160,13 @@ def displaySeriesXML(tvdb_api, series_season_ep):
+ if len(items.xpath("//image[@type='coverart']")) == 0:
+ for el in allDataElement.iter("series"):
+ glob_poster = el.find("poster")
+- if glob_poster is not None:
++ if glob_poster is not None and glob_poster.text != 'http://thetvdb.com/banners/':
+ glob_url = glob_poster.text
+ glob_thumb = glob_url.replace("posters", "_cache/posters")
+ glob_coverart = etree.Element("image", type = "coverart", url = glob_url, thumb = glob_thumb)
++ image_items = items.find("item").find("images")
++ if image_items is None:
++ etree.SubElement(items.find("item"), "images")
+ items.find("item").find("images").append(glob_coverart)
+ break
+
+
+From e152fb5aec6089c08c0628e133f073270f8510a5 Mon Sep 17 00:00:00 2001
+From: John Hoyt <jhoyt(a)Builbot-Cat.local>
+Date: Sat, 30 Jan 2021 03:22:59 -0800
+Subject: [PATCH 131/165] MythUIButtonListItem: Ensure all member vars are
+ initialised
+
+---
+ mythtv/libs/libmythui/mythuibuttonlist.h | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythui/mythuibuttonlist.h b/mythtv/libs/libmythui/mythuibuttonlist.h
+index aaf1bb31dff..c8d59a44f1a 100644
+--- a/mythtv/libs/libmythui/mythuibuttonlist.h
++++ b/mythtv/libs/libmythui/mythuibuttonlist.h
+@@ -115,8 +115,8 @@ class MUI_PUBLIC MythUIButtonListItem
+ QString m_fontState;
+ MythImage *m_image {nullptr};
+ QString m_imageFilename;
+- bool m_checkable;
+- CheckState m_state;
++ bool m_checkable {false};
++ CheckState m_state {CantCheck};
+ QVariant m_data {0};
+ bool m_showArrow {false};
+ bool m_isVisible {false};
+
+From 93adb6dc753fcdff1350df24cdaf473d823abdc2 Mon Sep 17 00:00:00 2001
+From: Dario <ddafre(a)gmail.com>
+Date: Mon, 1 Feb 2021 02:20:53 +0000
+Subject: [PATCH 132/165] Translations: update mythfrontend/mythplugins Italian
+ translations
+
+Signed-off-by: Nick Morrott <knowledgejunkie(a)gmail.com>
+(cherry picked from commit 6b44107d5520fad0d1622dd40fc10d9f2118598e)
+---
+ .../mytharchive/i18n/mytharchive_it.ts | 351 ++--
+ .../mythbrowser/i18n/mythbrowser_it.ts | 52 +-
+ mythplugins/mythgame/i18n/mythgame_it.ts | 109 +-
+ mythplugins/mythmusic/i18n/mythmusic_it.ts | 389 ++--
+ .../mythnetvision/i18n/mythnetvision_it.ts | 154 +-
+ mythplugins/mythnews/i18n/mythnews_it.ts | 72 +-
+ .../mythweather/i18n/mythweather_it.ts | 90 +-
+ .../mythzoneminder/i18n/mythzoneminder_it.ts | 89 +-
+ mythtv/i18n/mythfrontend_it.ts | 1681 +++++++++--------
+ 9 files changed, 1515 insertions(+), 1472 deletions(-)
+
+diff --git a/mythplugins/mytharchive/i18n/mytharchive_it.ts b/mythplugins/mytharchive/i18n/mytharchive_it.ts
+index 1e39c417ca4..2ff48266997 100644
+--- a/mythplugins/mytharchive/i18n/mytharchive_it.ts
++++ b/mythplugins/mytharchive/i18n/mytharchive_it.ts
+@@ -1290,300 +1290,300 @@ Attendere...</translation>
+ <message>
+ <location filename="themestrings.h" line="6"/>
+ <source>%SIZE% ~ %PROFILE%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%SIZE% ~ %PROFILE%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="8"/>
+ <source>%date% / %profile%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%date% / %profile%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+ <source>%size% (%profile%)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%size% (%profile%)</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="13"/>
+ <source>0.00Gb</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">0.00Gb</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="17"/>
+ <source>12.34GB</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">12.34GB</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="18"/>
+ <source><-- Details</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished"><-- Dettagli</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="19"/>
+ <source><-- Main Menu</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished"><-- Menu Principale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="26"/>
+ <source>Add Recordings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Agg.Registrazioni</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="28"/>
+ <source>Add Videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="29"/>
+ <source>Add a recording or video to archive.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi una registrazione o un video da archiviare.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="30"/>
+ <source>Add a recording, video or file to archive.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi una registrazione, un video o un file da archiviare.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="32"/>
+ <source>Archive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="33"/>
+ <source>Archive Callsign:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio Stazioni:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="34"/>
+ <source>Archive Chan ID:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio Chan ID:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="35"/>
+ <source>Archive Chan No:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio Chan No:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="36"/>
+ <source>Archive Files</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio File</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="38"/>
+ <source>Archive Item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio Elementi:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="40"/>
+ <source>Archive Items to DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio elementi a DVD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="41"/>
+ <source>Archive Log Viewer</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Visualizza Log archivio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="42"/>
+ <source>Archive Media</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio Media</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="43"/>
+ <source>Archive Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome archivio:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="46"/>
+ <source>Associate Channel</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Associa canale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="47"/>
+ <source>Associated Channel</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Canale associato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="49"/>
+ <source>Burn</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Masterizza</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="50"/>
+ <source>Burn Created DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Masterizza DVD creato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="51"/>
+ <source>Burn the last created archive to DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Masterizza su DVD l'ultimo archivio creato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="53"/>
+ <source>Burn to DVD:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Masterizza su DVD:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="54"/>
+ <source>CUTLIST</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">LISTATAGLI</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="55"/>
+ <source>Callsign</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Emittente</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="57"/>
+ <source>Callsign: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Emittente: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="59"/>
+ <source>Cancel Job</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Annulla lavoro</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="61"/>
+ <source>Chan. Id:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Id Can.:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="63"/>
+ <source>Change</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="65"/>
+ <source>Channel ID</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">ID Canale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="67"/>
+ <source>Channel ID: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">ID Canale: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="68"/>
+ <source>Channel Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome canale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="69"/>
+ <source>Channel No</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">N. canale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="71"/>
+ <source>Channel Number: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero canale: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="74"/>
+ <source>Chapter Menu --></source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Menu capitolo --></translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="75"/>
+ <source>Chapter Menu:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Menu capitolo:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="76"/>
+ <source>Chapters</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Capitoli</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="82"/>
+ <source>Create ISO Image:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea immagine ISO:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="83"/>
+ <source>Create a</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea un</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="84"/>
+ <source>Create a DVD of your videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea un DVD dei tuoi video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="85"/>
+ <source>Current Destination:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Destinazione attuale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="87"/>
+ <source>Current Position:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione attuale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="88"/>
+ <source>Current Size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione attuale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="89"/>
+ <source>Current selected item size: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione elemento selezionato: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="90"/>
+ <source>Current size: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione attuale: %1</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="themestrings.h" line="91"/>
+ <source>Current: %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>Corrente: %n</numerusform>
++ <numerusform>Correnti: %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="92"/>
+ <source>DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DVD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="93"/>
+ <source>DVD Media</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DVD Media</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="94"/>
+ <source>DVD Menu</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Menu DVD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="96"/>
+ <source>Date (time)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Data (ora)</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="98"/>
+ <source>Destination</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Destinazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="99"/>
+ <source>Destination Free Space:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Spazio libero su destinazione:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="103"/>
+ <source>Details:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="104"/>
+ <source>Done</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fatto</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="105"/>
+ <source>Edit Archive item Information</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica info elemento dell'archivio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="106"/>
+@@ -1598,52 +1598,52 @@ Attendere...</translation>
+ <message>
+ <location filename="themestrings.h" line="108"/>
+ <source>Eject your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Espelli il tuo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="110"/>
+ <source>Encode your video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Codifica il video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="111"/>
+ <source>Encode your video to a file</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Codifica il video su un file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="112"/>
+ <source>Encoded Size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione codificata:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="113"/>
+ <source>Encoder Profile:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Profilo encoder:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="114"/>
+ <source>Encoding Profile</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Profilo codifica</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="116"/>
+ <source>Error: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Errore: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="118"/>
+ <source>Export</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Esporta</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="119"/>
+ <source>Export your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Esporta il tuo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="120"/>
+ <source>Export your videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Esporta i tuoi video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="121"/>
+@@ -1653,37 +1653,37 @@ Attendere...</translation>
+ <message>
+ <location filename="themestrings.h" line="122"/>
+ <source>File Browser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Browser File</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="125"/>
+ <source>File browser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Browser file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="126"/>
+ <source>File...</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File...</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="127"/>
+ <source>Filename:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome file:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="128"/>
+ <source>Filesize</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="132"/>
+ <source>Find Location</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trova posizione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="134"/>
+ <source>First, select the thumb image you want to change from the overview. Second, press the 'tab' key to move to the select thumb frame button to change the image.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona prima dall'elenco l'icona che desideri modificare, poi premi il tasto "Tab" per passare al pulsante di selezione del riquadro dell'icona per modificare l'immagine.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="135"/>
+@@ -1693,254 +1693,255 @@ Attendere...</translation>
+ <message>
+ <location filename="themestrings.h" line="136"/>
+ <source>Force Overwrite of DVD-RW Media:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Forza sovrascrittura supporti DVD-RW:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="140"/>
+ <source>Import</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="142"/>
+ <source>Import an</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa un</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="143"/>
+ <source>Import recordings from a native archive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa registrazioni da archivio originale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="144"/>
+ <source>Import videos from an Archive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa registrazioni da un archivio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="146"/>
+ <source>Intro --></source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Intro --></translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="147"/>
+ <source>Intro:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Intro:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="148"/>
+ <source>Local Callsign:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Emittente locale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="149"/>
+ <source>Local Chan ID:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">ID emittente locale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="150"/>
+ <source>Local Chan No:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">N. Can. locale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="152"/>
+ <source>Local Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome locale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="153"/>
+ <source>Log</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Log</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="155"/>
+ <source>Log viewer</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Visualizz. Log</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="156"/>
+ <source>MENU changes focus. Numbers 0-9 jump to that thumb image.
+ When the preview image has focus, UP/DOWN changes the seek amount, LEFT/RIGHT jumps forward/backward by the seek amount, and SELECT chooses the current preview image for the selected thumb image.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">MENU cambia selezione. I numeri da 0 a 9 spostano sull'immagine dell'icona.
++Quando l'immagine di anteprima è selezionata, SU/GIÙ cambia l'entità dello spostamento, SINISTRA/DESTRA salta in avanti/indietro dell'entità di spostamento definita e SELEZIONA sceglie l'immagine di anteprima corrente come immagine della stazione selezionata.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="158"/>
+ <source>Main Menu:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Menù principale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="159"/>
+ <source>Main menu</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Menù principale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="161"/>
+ <source>Make ISO image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea immagine ISO</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="162"/>
+ <source>Make ISO image:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea immagine ISO:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="163"/>
+ <source>Media</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Media</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="165"/>
+ <source>Metadata</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Metadati</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="166"/>
+ <source>Name</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="168"/>
+ <source>Name: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="169"/>
+ <source>Navigation</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Navigazione</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="themestrings.h" line="172"/>
+ <source>New: %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>Nuovo: %n</numerusform>
++ <numerusform>Nuovi: %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="174"/>
+ <source>No files are selected for DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non ci sono file selezionati per il DVD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="177"/>
+ <source>Not Applicable</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non applicabile</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="178"/>
+ <source>Not Available in this Theme</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non disponibile in questo tema</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="179"/>
+ <source>Not available in this theme</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non disponibile in questo tema</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="184"/>
+ <source>Original Size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione originale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="186"/>
+ <source>Overwrite DVD-RW Media:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sovrascrivi media DVD-RW:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="188"/>
+ <source>Parental Level</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Livello parentale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="189"/>
+ <source>Parental Level:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Livello parentale:</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="themestrings.h" line="191"/>
+ <source>Parental Level: %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>Livello parentale: %n</numerusform>
++ <numerusform>Livelli parentali: %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="194"/>
+ <source>Play the last created archive DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci ultimo DVD archivio creato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="195"/>
+ <source>Position View:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista posizione:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="196"/>
+ <source>Press 'left' and 'right' arrows to move through the frames. Press 'up' and 'down' arrows to change seek amount. Press 'select' or 'enter' key to lock the selected thumb image.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Premi le frecce "sinistra" e "destra" per spostarti tra i frame. Premi le frecce "su" e "giù" per modificare l'entità della ricerca. Premi il tasto "seleziona" o "invio" per bloccare l'immagine dell'icona selezionata.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="198"/>
+ <source>Preview</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Anteprima</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="201"/>
+ <source>Read video from data dvd or file</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Leggi video da dvd o file dati</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="203"/>
+ <source>Recordings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Registrazioni</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="204"/>
+ <source>Recordings group:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gruppo registrazioni:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="206"/>
+ <source>Save recordings and videos to a native archive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salva registrazioni e video in un archivio originale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="207"/>
+ <source>Save recordings and videos to video DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salva registrazioni e video su DVD video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="209"/>
+ <source>Search Chan ID</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca ID Can</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="210"/>
+ <source>Search Chan NO</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca N. Can</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="212"/>
+ <source>Search Local</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca localmente</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="216"/>
+ <source>Seek Amount:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Entità spostamento:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="217"/>
+ <source>Seek amount:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Entità spostamento:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="220"/>
+ <source>Select Channel</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona canale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="224"/>
+ <source>Select Files</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Selesiona file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="225"/>
+@@ -1950,52 +1951,52 @@ When the preview image has focus, UP/DOWN changes the seek amount, LEFT/RIGHT ju
+ <message>
+ <location filename="themestrings.h" line="226"/>
+ <source>Select Recordings for your archive or image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona registrazioni per l'archivio o immagine</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="228"/>
+ <source>Select a destination for your archive or image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona destinazione per l'archivio o immagine</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="233"/>
+ <source>Select thumb image:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona immagine icona:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="234"/>
+ <source>Select your DVD menu theme</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona tema del menu del DVD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="235"/>
+ <source>Selected Items to be archived</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elementi selezionati da archiviare</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="236"/>
+ <source>Selected Items to be burned</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elementi selezionati da masterizzare</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="237"/>
+ <source>Selected recording item size: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensioni della registrazione selezionata: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="238"/>
+ <source>Selected video item size: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensioni del video selezionato: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="239"/>
+ <source>Sep 13, 2004 11:00 pm (1h 15m)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Set 13, 2004 11:00 pm (1h 15m)</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="240"/>
+ <source>Show</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="241"/>
+@@ -2005,7 +2006,7 @@ When the preview image has focus, UP/DOWN changes the seek amount, LEFT/RIGHT ju
+ <message>
+ <location filename="themestrings.h" line="293"/>
+ <source>~</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">~</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="227"/>
+@@ -2015,110 +2016,110 @@ When the preview image has focus, UP/DOWN changes the seek amount, LEFT/RIGHT ju
+ <message>
+ <location filename="themestrings.h" line="242"/>
+ <source>Show Videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="243"/>
+ <source>Show log with archive activities</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra log attività di archiviazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="244"/>
+ <source>Show the Archive Log Viewer</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra visualizzatore log dell'archivio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="245"/>
+ <source>Size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione:</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="themestrings.h" line="246"/>
+ <source>Size: %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>Dimensione: %n</numerusform>
++ <numerusform>Dimensioni: %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="249"/>
+ <source>Start Time: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ora inizio: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="250"/>
+ <source>Start to</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Inizia a</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="251"/>
+ <source>Start to Burn your archive to DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Inizia a masterizzare l'archivio su DVD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="253"/>
+ <source>Subtitle: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sottotitolo: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="254"/>
+ <source>Test</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Test</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="255"/>
+ <source>Test created</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Test creato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="256"/>
+ <source>Test created DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Prova il DVD creato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="257"/>
+ <source>Theme description</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Descrizione tema</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="258"/>
+ <source>Theme for the DVD menu:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tema del menu del DVD:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="259"/>
+ <source>Theme preview:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Anteprima tema:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="264"/>
+ <source>Title: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Titolo: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="265"/>
+ <source>Tools and Utilities for archives</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Strumenti e utilità per gli archivi</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="268"/>
+ <source>Use archive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Usa archivio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="269"/>
+ <source>Used: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Usato: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="270"/>
+ <source>Utilities</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Utilità</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="271"/>
+ <source>Utilities for MythArchive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Utilità per MythArchive</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="273"/>
+@@ -2128,70 +2129,70 @@ When the preview image has focus, UP/DOWN changes the seek amount, LEFT/RIGHT ju
+ <message>
+ <location filename="themestrings.h" line="274"/>
+ <source>Video category:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Categoria video:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="275"/>
+ <source>Videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="276"/>
+ <source>View progress of your archive or image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra stato di avanzamento dell'archivio o immagine</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="277"/>
+ <source>Write video to data dvd or file</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scrivi video su dvd o file dati</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="278"/>
+ <source>XML File to Import</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File XML da importare</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="279"/>
+ <source>decrease seek amount</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">riduci entità ricerca</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="281"/>
+ <source>frame</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">frame</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="282"/>
+ <source>increase seek amount</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">aumenta entità ricerca</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="283"/>
+ <source>move left</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">muovi a sinistra</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="284"/>
+ <source>move right</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">muovi a destra</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="themestrings.h" line="285"/>
+ <source>profile: %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>profilo: %n</numerusform>
++ <numerusform>profili: %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="286"/>
+ <source>sep 13, 2004 11:00 pm (1h 15m)</source>
+- <translation>sett 13, 2004 11:00 pm (1h 15m)</translation>
++ <translation type="unfinished">set 13, 2004 11:00 pm (1h 15m)</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="288"/>
+ <source>to</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">a</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="289"/>
+diff --git a/mythplugins/mythbrowser/i18n/mythbrowser_it.ts b/mythplugins/mythbrowser/i18n/mythbrowser_it.ts
+index 34735b1db92..5ce1e43bcdc 100644
+--- a/mythplugins/mythbrowser/i18n/mythbrowser_it.ts
++++ b/mythplugins/mythbrowser/i18n/mythbrowser_it.ts
+@@ -226,22 +226,22 @@ Usa l'opzione menù "Aggiungi segnalibro" per aggiungere nuovi se
+ <message>
+ <location filename="themestrings.h" line="46"/>
+ <source>Search</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="47"/>
+ <source>Select bookmark</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona segnalibro</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="48"/>
+ <source>Select bookmark or add a new one</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona segnalibro o aggiungine uno</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="49"/>
+ <source>Select bookmark:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona segnalibro:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="50"/>
+@@ -276,27 +276,27 @@ Usa l'opzione menù "Aggiungi segnalibro" per aggiungere nuovi se
+ <message>
+ <location filename="themestrings.h" line="51"/>
+ <source>Text size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione testo:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="53"/>
+ <source>URL Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome URL:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="56"/>
+ <source>WEB BROWSER</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">BROWSER WEB</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="60"/>
+ <source>Website Name</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome sito web</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="61"/>
+ <source>Website URL</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">URL sito web</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="62"/>
+@@ -368,7 +368,7 @@ Al termine della modifica, seleziona "OK" o "Annulla" per co
+ <message>
+ <location filename="themestrings.h" line="35"/>
+ <source>Find category</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trova categoria</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="38"/>
+@@ -378,12 +378,12 @@ Al termine della modifica, seleziona "OK" o "Annulla" per co
+ <message>
+ <location filename="themestrings.h" line="39"/>
+ <source>Myth Web Browser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Browser web Myth</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="43"/>
+ <source>Ok</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ok</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="45"/>
+@@ -399,59 +399,59 @@ Al termine della modifica, seleziona "OK" o "Annulla" per co
+ <location filename="themestrings.h" line="6"/>
+ <source>%name% (%url%)</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>%name% (%url%)</numerusform>
++ <numerusform>%name% (%url%)</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="8"/>
+ <source>Bookmark Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome segnalibro:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+ <source>Bookmark URL:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">URL segnalibro:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="11"/>
+ <source>Browse the web</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Naviga web</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="13"/>
+ <source>Browser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Browser</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="16"/>
+ <source>Browser command:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Comando browser:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="21"/>
+ <source>Choose a bookmark to open in the webbrowser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli un segnalibro da aprire nel browser web</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="23"/>
+ <source>Command:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Comando:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="25"/>
+ <source>Configure web browser settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura le impostazioni del browser</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="27"/>
+ <source>Edit your bookmarks</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica segnalibri</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="28"/>
+ <source>Enable Plugins:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Abilita plugin:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="29"/>
+@@ -461,7 +461,7 @@ Al termine della modifica, seleziona "OK" o "Annulla" per co
+ <message>
+ <location filename="themestrings.h" line="31"/>
+ <source>Enable browser plugins:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Abilita plugin del browser:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="32"/>
+@@ -501,7 +501,7 @@ Al termine della modifica, seleziona "OK" o "Annulla" per co
+ <message>
+ <location filename="themestrings.h" line="63"/>
+ <source>Zoom:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Zoom:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="64"/>
+diff --git a/mythplugins/mythgame/i18n/mythgame_it.ts b/mythplugins/mythgame/i18n/mythgame_it.ts
+index 823725d428e..3d97b9a1461 100644
+--- a/mythplugins/mythgame/i18n/mythgame_it.ts
++++ b/mythplugins/mythgame/i18n/mythgame_it.ts
+@@ -970,54 +970,57 @@ di volerlo fare?</translation>
+ <location filename="themestrings.h" line="5"/>
+ <source>%00x00| - %%"|SUBTITLE|"
+ %%|YEARSTARS| - %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%00x00| - %%"|SUBTITLE|"
++%%|YEARSTARS| - %%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="7"/>
+ <source>%Published by |publisher|%%, |YEAR%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%Pubblicato da|publisher|%%, |YEAR%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="8"/>
+ <source>%"|SUBTITLE|"
+
+ %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%"|SUBTITLE|"
++
++%%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+ <source>%cast%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%cast%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="10"/>
+ <source>%playcount% times</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%playcount% volte</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="11"/>
+ <source>%romname%%, |(rompath)%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%romname%%, |(rompath)%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="13"/>
+ <source>Add game to Favorites:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi gioco ai preferiti:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="14"/>
+ <source>Add games to your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi giochi al tuo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="16"/>
+ <source>Add to favorites:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi ai preferiti:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="19"/>
+ <source>CRC</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CRC</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="21"/>
+@@ -1027,57 +1030,57 @@ di volerlo fare?</translation>
+ <message>
+ <location filename="themestrings.h" line="23"/>
+ <source>Clear data of all your games</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cancella dati di tutti i giochi</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="24"/>
+ <source>Clear your game</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cancella il tuo gioco</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="29"/>
+ <source>Configure game emulators</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura emulatori giochi</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="30"/>
+ <source>Configure your game</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura gioco</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="31"/>
+ <source>Country</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Paese</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="34"/>
+ <source>Coverart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copertina</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="35"/>
+ <source>Coverart:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copertina:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="36"/>
+ <source>Customize meta data of your games</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza metadati dei giochi</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="37"/>
+ <source>Data</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dati</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="39"/>
+ <source>Description: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Descrizione: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="40"/>
+ <source>Detailed information about your game</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni dettagliate sul gioco</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="43"/>
+@@ -1087,57 +1090,57 @@ di volerlo fare?</translation>
+ <message>
+ <location filename="themestrings.h" line="46"/>
+ <source>Fanart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fanart</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="50"/>
+ <source>File:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="51"/>
+ <source>Filename</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="54"/>
+ <source>GAMES</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GIOCHI</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="58"/>
+ <source>Game Type:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tipo gioco:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="59"/>
+ <source>Game details</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli gioco</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="60"/>
+ <source>Game folders and general</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cartelle gioco e generale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="62"/>
+ <source>Game folders and general settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cartelle gioco e impostazioni generali</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="64"/>
+ <source>Game plot:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trama gioco:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="70"/>
+ <source>Library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Biblioteca</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="71"/>
+ <source>Metadata</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Metadati</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="72"/>
+@@ -1147,92 +1150,92 @@ di volerlo fare?</translation>
+ <message>
+ <location filename="themestrings.h" line="73"/>
+ <source>Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="74"/>
+ <source>Navigation</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Navigazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="77"/>
+ <source>Path:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Percorso:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="80"/>
+ <source>Players</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Giocatori</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="82"/>
+ <source>Publisher</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="86"/>
+ <source>ROM path</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Percorso ROM</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="88"/>
+ <source>ROM:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">ROM:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="91"/>
+ <source>Save</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salva</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="93"/>
+ <source>Screenshot</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Schermata</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="95"/>
+ <source>Search</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="96"/>
+ <source>Search Boxart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca boxart</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="97"/>
+ <source>Search Fanart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca fanart</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="98"/>
+ <source>Search Screenshot</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca schermata</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="99"/>
+ <source>Select Game</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona gioco</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="100"/>
+ <source>Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="101"/>
+ <source>Starring:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Protagonista:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="102"/>
+ <source>System</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sistema</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="106"/>
+ <source>Systems:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sistemi:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="107"/>
+@@ -1242,17 +1245,17 @@ di volerlo fare?</translation>
+ <message>
+ <location filename="themestrings.h" line="113"/>
+ <source>_</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">_</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="114"/>
+ <source>genre:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">genere:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="115"/>
+ <source>system:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">sistema:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="33"/>
+diff --git a/mythplugins/mythmusic/i18n/mythmusic_it.ts b/mythplugins/mythmusic/i18n/mythmusic_it.ts
+index 4b4e74386d1..704cca322e5 100644
+--- a/mythplugins/mythmusic/i18n/mythmusic_it.ts
++++ b/mythplugins/mythmusic/i18n/mythmusic_it.ts
+@@ -2809,7 +2809,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="520"/>
+ <source>Use Current Date:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Usa data corrente:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="523"/>
+@@ -2929,7 +2929,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="525"/>
+ <source>Visualizer</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Visualizzatore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="526"/>
+@@ -3254,7 +3254,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="6"/>
+ <source>%1 Matches found:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%1 Corrispondenze trovate:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+@@ -3279,7 +3279,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="15"/>
+ <source>%CD Track #: |TRACKNUM%%, |YEAR%%, |GENRE%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%CD Track #: |TRACKNUM%%, |YEAR%%, |GENRE%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="21"/>
+@@ -3329,7 +3329,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="35"/>
+ <source>%TRACKNUM| %%TITLE% by %ARTIST% of %ALBUM%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%TRACKNUM| %%TITLE% by %ARTIST% of %ALBUM%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="38"/>
+@@ -3349,7 +3349,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="42"/>
+ <source>%lastplayed% (played: %playcount% times)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%lastplayed% (riprodotto: %playcount% volte)</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="43"/>
+@@ -3359,12 +3359,12 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="44"/>
+ <source>%length% minutes</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%length% minuti</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="46"/>
+ <source>%playlistplayedtime% of %playlisttotaltime%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%playlistplayedtime% di %playlisttotaltime%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="47"/>
+@@ -3374,7 +3374,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="48"/>
+ <source>%title% by %artist%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%title% per %artist%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="49"/>
+@@ -3394,7 +3394,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="53"/>
+ <source>+/- Days:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">+/- Giorni:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="54"/>
+@@ -3429,12 +3429,12 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="61"/>
+ <source>Action</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Azione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="62"/>
+ <source>Action on Exit:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Azione all'uscita:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="65"/>
+@@ -3444,52 +3444,53 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="68"/>
+ <source>Add All New files</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi tutti i nuovi file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="69"/>
+ <source>Add Current</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi corrente</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="70"/>
+ <source>Add New</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi nuovo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="71"/>
+ <source>Add file</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="78"/>
+ <source>Album Image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine album</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="82"/>
+ <source>All ready in database</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tutto pronto nel database</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="88"/>
+ <source>Artist Image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine artista</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="94"/>
+ <source>Automatically eject CD after Ripping:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Autoespelli il CD una volta masterizzato:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="99"/>
+ <source>Automatically play CDs:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Autoriproduci i CD:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="100"/>
+ <source>Bitte Warten ...</source>
+- <translation type="unfinished"></translation>
++ <translatorcomment>Source is in german!</translatorcomment>
++ <translation type="unfinished">Attendere...</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="102"/>
+@@ -3499,7 +3500,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="103"/>
+ <source>Buffer:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Buffer:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="107"/>
+@@ -3509,87 +3510,87 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="112"/>
+ <source>CD burning options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Opzioni masterizzazione CD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="120"/>
+ <source>Channel Logo:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Logo canale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="124"/>
+ <source>Comp. Artist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Comp. Artista</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="129"/>
+ <source>Configure CD copying options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura opzioni di copia CD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="130"/>
+ <source>Configure Music Burn options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura opzioni per masterizzare musica</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="131"/>
+ <source>Configure Music folders and general options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura cartelle musicali e opzioni generali</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="132"/>
+ <source>Configure Music playback options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura opzioni di riproduzione brani</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="133"/>
+ <source>Configure Music rating options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura opzioni di valutazione brani</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="134"/>
+ <source>Configure Music visualization options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura opzioni di visualizzazione brani</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="136"/>
+ <source>Configure settings for</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura impostazioni per</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="137"/>
+ <source>Configure your CD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura CD</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="141"/>
+ <source>Copy your CD into the Music Library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copia il CD nella libreria musicale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="145"/>
+ <source>Covert Art</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copertina</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="146"/>
+ <source>Create playlists with favorite music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea playlist con la musica preferita</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="147"/>
+ <source>Create your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea la tua</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="148"/>
+ <source>Criteria:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Criterio:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="149"/>
+ <source>Current Play List View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista elenco riproduzione attuale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="150"/>
+@@ -3599,22 +3600,22 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="151"/>
+ <source>Current Streamlist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Streamlist attuale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="155"/>
+ <source>Day:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Giorno:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="156"/>
+ <source>Decrease</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riduci</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="158"/>
+ <source>Default Rip quality:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Qualità masterizz. predefinita:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="161"/>
+@@ -3624,12 +3625,12 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="166"/>
+ <source>Duration</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="168"/>
+ <source>Edit Album Art</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica copertina album</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="171"/>
+@@ -3639,57 +3640,57 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="172"/>
+ <source>Edit Order</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica ordine</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="174"/>
+ <source>Edit meta data of your music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica metadati dei brani</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="175"/>
+ <source>Edit meta data of your radio streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica metadati dei stream radio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="176"/>
+ <source>Edit metadata of the radio stream</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica metadati dello stream radio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="177"/>
+ <source>Edit your playlists with your favorite music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica playlist con i tuoi brani preferiti</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="178"/>
+ <source>Edit your smart playlist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica playlist smart</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="181"/>
+ <source>Eject your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Espelli il tuo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="189"/>
+ <source>Enter:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Inserire:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="190"/>
+ <source>Field:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Campo:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="191"/>
+ <source>Fields:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Campi:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="192"/>
+ <source>File</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="193"/>
+@@ -3699,137 +3700,137 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="194"/>
+ <source>File Information</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="195"/>
+ <source>File Storage location:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione archivio file:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="198"/>
+ <source>Filename</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="201"/>
+ <source>Files</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="202"/>
+ <source>Find Location</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trova posizione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="203"/>
+ <source>Find your radio streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trova stream radio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="204"/>
+ <source>Finish</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Termina</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="205"/>
+ <source>First Value</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Primo valore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="206"/>
+ <source>First Value:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Primo valore:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="208"/>
+ <source>Fixed Date:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Data stabilita:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="211"/>
+ <source>General</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Generale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="213"/>
+ <source>General music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Musica generale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="215"/>
+ <source>Genre Image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine genere</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="225"/>
+ <source>Image selected:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine selezionata:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="226"/>
+ <source>Imagetype:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tipo immagine:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="227"/>
+ <source>Import</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="231"/>
+ <source>Import Coverart into your Library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa copertina nella libreria</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="234"/>
+ <source>Import Music files into your Library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa brani musicali nella libreria</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="235"/>
+ <source>Import Music into your library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa brani nella libreria</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="236"/>
+ <source>Import music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa brani</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="238"/>
+ <source>Import your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa la tua</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="239"/>
+ <source>Import your CD into the music library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa CD nella libreria musicale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="240"/>
+ <source>Import your Cover Art into the music library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa copertina nella libreria musicale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="241"/>
+ <source>Import your Music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa brani</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="242"/>
+ <source>Import your music into the music library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa i brani nella libreria musicale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="245"/>
+ <source>Increase</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aumenta</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="246"/>
+ <source>Information about your music Track</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni sulla traccia musicale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="247"/>
+@@ -3839,235 +3840,235 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="250"/>
+ <source>Last</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ultimo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="253"/>
+ <source>Last Played</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ultimo riprodotto</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="256"/>
+ <source>Last played Weight:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Contatore ultimo riprodotto:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="268"/>
+ <source>MUSIC PLAYER</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">LETTORE MUSICALE</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="269"/>
+ <source>MUSIC Playlist Editor</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor playlist MUSICALE</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="274"/>
+ <source>Matches: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Corrispondenze: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="275"/>
+ <source>Media</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Media</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="279"/>
+ <source>Month:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mese:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="282"/>
+ <source>Multi Artist:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Multi-artista:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="285"/>
+ <source>Music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Musica</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="286"/>
+ <source>Music Directory:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cartella musicale:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="287"/>
+ <source>Music Playlist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Playlist musicale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="289"/>
+ <source>Music Stream Search</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca stream musicali</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="290"/>
+ <source>Music Stream Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni stream musicale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="291"/>
+ <source>Music Tools</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Strumenti musica</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="296"/>
+ <source>Mute:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Muto:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="298"/>
+ <source>Navigation</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Navigazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="299"/>
+ <source>New Field:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nuovo campo:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="302"/>
+ <source>Next Playing</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Brano successivo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="303"/>
+ <source>Next new file</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nuovo file successivo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="307"/>
+ <source>No Album Art Found!</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessuna copertina trovata!</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="308"/>
+ <source>No volume control configured</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessun controllo volume configurato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="310"/>
+ <source>Not muted</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non muto</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="311"/>
+ <source>Now Playing</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">In riproduzione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="312"/>
+ <source>Now Playing:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">In riproduzione:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="314"/>
+ <source>Now playing</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">In riproduzione</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="themestrings.h" line="315"/>
+ <source>Number of songs found: %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>Numero brani trovati: %n</numerusform>
++ <numerusform>Numero brani trovati: %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="318"/>
+ <source>Of The Following Conditions:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Delle seguenti condizioni:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="322"/>
+ <source>Operator:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Operatore:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="325"/>
+ <source>Order by Fields</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ordina per campi</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="326"/>
+ <source>Order by:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ordinato per:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="327"/>
+ <source>Overall Progress</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avanzamento globale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="328"/>
+ <source>PAUSED</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">IN PAUSA</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="329"/>
+ <source>PLAYING</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">IN RIPRODUZIONE</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="331"/>
+ <source>Paused</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">In pausa</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="335"/>
+ <source>Play Internet Radio</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci internet radio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="340"/>
+ <source>Play List Editor Gallery View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor playlist - vista galleria</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="341"/>
+ <source>Play List Editor Tree View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor playlist - vista albero</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="345"/>
+ <source>Play count</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">N. riproduzioni</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="346"/>
+ <source>Play count Weight:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Peso contatore ascolti:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="347"/>
+ <source>Play file</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci file</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="349"/>
+ <source>Play radio streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci stream radio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="350"/>
+ <source>Playcount:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">N.riproduzioni:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="352"/>
+ <source>Played track:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Traccia riprod.:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="355"/>
+ <source>Player</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduttore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="357"/>
+ <source>Playing</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduco</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="359"/>
+@@ -4077,27 +4078,27 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="360"/>
+ <source>Playlist Editor Gallery</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Galleria editor playlist</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="361"/>
+ <source>Playlist Editor Tree</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Albero editor playlist</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="362"/>
+ <source>Playlist Gallery</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Galleria playlist</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="364"/>
+ <source>Playlist Position:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione playlist:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="365"/>
+ <source>Playlist Tree</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Albero playlist</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="369"/>
+@@ -4107,32 +4108,32 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="372"/>
+ <source>Position</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="374"/>
+ <source>Position: %position%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione: %position%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="375"/>
+ <source>Post processing scripts:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Script post-processo:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="378"/>
+ <source>Quality :</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Qualità :</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="380"/>
+ <source>RADIO STREAM PLAYER</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">RIPROD. RADIO STREAM</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="386"/>
+ <source>Rating</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="390"/>
+@@ -4142,217 +4143,217 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="402"/>
+ <source>Result:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risultato:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="403"/>
+ <source>Results of the smartplaylist criteria</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risultati criteri smartplaylist</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="404"/>
+ <source>Resume Mode:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modalità ripresa:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="411"/>
+ <source>Rip quality:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Qualità masteriz.:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="412"/>
+ <source>Ripper</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Masterizzatore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="415"/>
+ <source>Runtime:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="416"/>
+ <source>STOPPED</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">STOPPATO</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="420"/>
+ <source>Scan Location</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione ricerca</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="421"/>
+ <source>Scan current location</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca in posizione attuale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="423"/>
+ <source>Scan for new</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca nuovo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="424"/>
+ <source>Scan for new Music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca nuovi brani</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="427"/>
+ <source>Search Album</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca album</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="428"/>
+ <source>Search Artist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca artista</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="429"/>
+ <source>Search Category</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca categoria</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="430"/>
+ <source>Search Comp Artist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca comp artista</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="431"/>
+ <source>Search First Value</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca primo valore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="433"/>
+ <source>Search Genre</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca genere</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="434"/>
+ <source>Search Music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca brano</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="435"/>
+ <source>Search Order</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ordine ricerca</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="437"/>
+ <source>Search Second Value</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca secondo valore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="438"/>
+ <source>Search Stream</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca stream</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="439"/>
+ <source>Search String</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca stringa</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="443"/>
+ <source>Search for Music Streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca stream musicali</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="444"/>
+ <source>Search for Radio Streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca stream radio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="446"/>
+ <source>Search for:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="447"/>
+ <source>Search library for your favorite music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca il brano preferito nella libreria</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="448"/>
+ <source>Search string</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca stringa</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="449"/>
+ <source>Second Value</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Secondo valore</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="450"/>
+ <source>Second Value:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Secondo valore:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="452"/>
+ <source>Select folder</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona cartella</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="453"/>
+ <source>Select tracks to rip:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona tracce da masterizzare:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="454"/>
+ <source>Selected</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Selezionate</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="455"/>
+ <source>Selected Field:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Campo selezionato:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="456"/>
+ <source>Selected Image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine selezionata</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="457"/>
+ <source>Set</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="458"/>
+ <source>Set the quality and then edit the artist,album,genre and year if needed. Then select the tracks below to rip.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta la qualità, quindi, se necessario, modifica l'artista, l'album, il genere e l'anno. Seleziona le tracce sottostanti per masterizzare.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="459"/>
+ <source>Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="460"/>
+ <source>Setup you music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta musica</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="461"/>
+ <source>Setup your music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta musica</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="475"/>
+ <source>Smart Playlist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Playlist smart</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="478"/>
+ <source>Smart Playlist Results</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risultati playlist smart</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="479"/>
+ <source>Smartlist order fields:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Campi ordinamento smartlist:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="480"/>
+ <source>Song title</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Titolo brano</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="483"/>
+@@ -4362,37 +4363,37 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="484"/>
+ <source>Stream Metadata</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stream metadati</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="485"/>
+ <source>Streaming Radio</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Streaming radio</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="486"/>
+ <source>Switch Title</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scambia titolo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="496"/>
+ <source>Track Info</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Info traccia</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="499"/>
+ <source>Track NO</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Traccia N.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="501"/>
+ <source>Track Number:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero Traccia:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="504"/>
+ <source>Tracks Lyrics View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista testo della traccia</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="506"/>
+@@ -4422,7 +4423,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="535"/>
+ <source>storage location:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione archivio:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="143"/>
+@@ -4602,7 +4603,7 @@ Broadcaster:%1 - canale:%2</translation>
+ <message>
+ <location filename="themestrings.h" line="511"/>
+ <source>URL Logo:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">URL Logo:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="512"/>
+diff --git a/mythplugins/mythnetvision/i18n/mythnetvision_it.ts b/mythplugins/mythnetvision/i18n/mythnetvision_it.ts
+index 269304b7d37..eca02e350e6 100644
+--- a/mythplugins/mythnetvision/i18n/mythnetvision_it.ts
++++ b/mythplugins/mythnetvision/i18n/mythnetvision_it.ts
+@@ -122,7 +122,7 @@
+ <message>
+ <location filename="../mythnetvision/netsearch.cpp" line="285"/>
+ <source>Searching %1 for "%2"...</source>
+- <translation>Ricerca %1 per "%2"...</translation>
++ <translation type="unfinished">Cerco %1 per "%2"...</translation>
+ </message>
+ <message>
+ <location filename="../mythnetvision/netsearch.cpp" line="309"/>
+@@ -163,7 +163,7 @@
+ <message>
+ <location filename="../mythnetvision/nettree.cpp" line="32"/>
+ <source>RSS Feeds</source>
+- <translation>RSS Feeds</translation>
++ <translation type="unfinished">Feed RSS</translation>
+ </message>
+ <message>
+ <location filename="../mythnetvision/nettree.cpp" line="33"/>
+@@ -331,7 +331,7 @@
+ <message>
+ <location filename="themestrings.h" line="110"/>
+ <source>Search:</source>
+- <translation>Ricerca:</translation>
++ <translation type="unfinished">Cerca:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="21"/>
+@@ -407,162 +407,168 @@ Per nuove sottoscrizioni, inserisci semplicemente l'URL e clicca su "s
+ <location filename="themestrings.h" line="5"/>
+ <source>%00x00| - %%"|SUBTITLE|"
+ %%|RATING| - %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%00x00| - %%"|SUBTITLE|"
++%%|RATING| - %%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="6"/>
+ <source>%00x00| - %%"|SUBTITLE|"
+ %%|YEARSTARS| - %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%00x00| - %%"|SUBTITLE|"
++%%|YEARSTARS| - %%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+ <source>%TITLE%% |SUBTITLE%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%TITLE%% |SUBTITLE%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="10"/>
+ <source>%TITLE| %%~ |AUTHOR%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%TITLE| %%~ |AUTHOR%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="11"/>
+ <source>%Userrating: |RATING|
+ %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%Userrating: |RATING|
++%%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="12"/>
+ <source>%"|SUBTITLE|"
+ %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%"|SUBTITLE|"
++%%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="13"/>
+ <source>%"|SUBTITLE|"
+
+ %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%"|SUBTITLE|"
++
++%%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="14"/>
+ <source>%author%%, |date%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%author%%, |date%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="15"/>
+ <source>%resolution%%, |filesize_str%% (|length|)%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%resolution%%, |filesize_str%% (|length|)%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="16"/>
+ <source>%resolution|, %%filesize_str| %</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%resolution|, %%filesize_str| %</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="17"/>
+ <source>%|(RATING)%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%|(RATING)%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="23"/>
+ <source>Browse Internet video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Naviga internet video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="28"/>
+ <source>Can't find any search scripts! This usually indicates missing search script prerequisities. Try running a script from the command line.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non trovo nessuno script di ricerca! Ciò di solito indica che nello script di ricerca mancano i prerequisiti. Prova a eseguire uno script dalla riga di comando.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="29"/>
+ <source>Can't find any search scripts! This usually indicates missing search script prerequisites. Try running a script from the command line.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non trovo nessuno script di ricerca! Ciò di solito indica che nello script di ricerca mancano i prerequisiti. Prova a eseguire uno script dalla riga di comando.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="35"/>
+ <source>Created by %author%%, |date%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Creato da %author%%, |date%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="36"/>
+ <source>Date Posted:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Pubblicato il:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="37"/>
+ <source>Date:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Data:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="45"/>
+ <source>Edit RSS Subscriptions</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica iscrizioni RSS</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="48"/>
+ <source>Episode %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Episodio %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="49"/>
+ <source>Episode:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Episodio:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="51"/>
+ <source>Feed Description:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Descrizione feed:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="54"/>
+ <source>File Size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione file:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="55"/>
+ <source>INTERNET VIDEO BROWSER</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">BROWSER INTERNET VIDEO</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="56"/>
+ <source>INTERNET VIDEOS LIST</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">ELENCO INTERNET VIDEO</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="57"/>
+ <source>Internet Video Browser</source>
+- <translation>Internet Video Browser</translation>
++ <translation type="unfinished">Browser Internet Video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="61"/>
+ <source>Manage RSS Subscribtions</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gestisci sottoscrizioni RSS</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="68"/>
+ <source>Netvision Browse View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista Browser NetVision</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="69"/>
+ <source>Netvision Gallery View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista galleria Netvision</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="70"/>
+ <source>Netvision List View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista elenco Netvision</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="71"/>
+ <source>Netvision Search</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca Netvision</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="72"/>
+ <source>Netvision Site Grabbers</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Grabber sito Netvision</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="76"/>
+@@ -572,157 +578,157 @@ Per nuove sottoscrizioni, inserisci semplicemente l'URL e clicca su "s
+ <message>
+ <location filename="themestrings.h" line="77"/>
+ <source>Not applicable</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non applicabile</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="78"/>
+ <source>Not available</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non disponibile</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="79"/>
+ <source>Ok</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ok</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="80"/>
+ <source>Page</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Pagina</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="82"/>
+ <source>Posted:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Inserito:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="84"/>
+ <source>Preview</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Anteprima</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="85"/>
+ <source>RSS Author:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Autore RSS:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="86"/>
+ <source>RSS Editor</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor RSS</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="87"/>
+ <source>RSS Feed Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome feed RSS:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="88"/>
+ <source>RSS Overview</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Panoramica RSS</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="89"/>
+ <source>RSS Subscriptions</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sottoscrizioni RSS</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="90"/>
+ <source>RSS URL:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">URL RSS:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="92"/>
+ <source>Rating:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="95"/>
+ <source>Resolution</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risoluzione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="96"/>
+ <source>Resolution:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risoluzione:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="98"/>
+ <source>Runtime:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="100"/>
+ <source>SEARCH INTERNET VIDEOS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CERCA INTERNET VIDEO</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="103"/>
+ <source>Search Internet Videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca internet video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="105"/>
+ <source>Search Site:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca nel sito:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="106"/>
+ <source>Search String:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stringa di ricerca:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="111"/>
+ <source>Season and Episode:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stagione e Episodio:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="112"/>
+ <source>Select icon image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Selez. immagine icona</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="116"/>
+ <source>Size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="118"/>
+ <source>Stream type:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tipo stream:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="122"/>
+ <source>Unknown</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sconosciuto</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="125"/>
+ <source>VIDEO GALLERY</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GALLERIA VIDEO</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="126"/>
+ <source>Video Browser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Browser video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="127"/>
+ <source>Video Gallery</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Galleria video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="128"/>
+ <source>Video List</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Lista video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="129"/>
+ <source>Video Search</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="130"/>
+ <source>Video length:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata video:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="131"/>
+@@ -737,7 +743,7 @@ Per nuove sottoscrizioni, inserisci semplicemente l'URL e clicca su "s
+ <message>
+ <location filename="themestrings.h" line="136"/>
+ <source>rating</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="64"/>
+@@ -747,7 +753,7 @@ Per nuove sottoscrizioni, inserisci semplicemente l'URL e clicca su "s
+ <message>
+ <location filename="themestrings.h" line="34"/>
+ <source>Choose the sites you wish to browse/search.</source>
+- <translation>Scegliere i siti che si desidera navigare/ricercare.</translation>
++ <translation type="unfinished">Scegli i siti che desideri navigare/cercare.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="74"/>
+@@ -767,12 +773,12 @@ Per nuove sottoscrizioni, inserisci semplicemente l'URL e clicca su "s
+ <message>
+ <location filename="themestrings.h" line="104"/>
+ <source>Search Net Videos</source>
+- <translation>Ricerca video in rete</translation>
++ <translation type="unfinished">Cerca video in rete</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="107"/>
+ <source>Search Term:</source>
+- <translation>Ricerca termine:</translation>
++ <translation type="unfinished">Cerca termine:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="117"/>
+@@ -872,7 +878,7 @@ Per nuove sottoscrizioni, inserisci semplicemente l'URL e clicca su "s
+ <message>
+ <location filename="themestrings.h" line="108"/>
+ <source>Search Videos</source>
+- <translation>Ricerca video</translation>
++ <translation type="unfinished">Cerca video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="30"/>
+@@ -948,7 +954,7 @@ Per aggiungere un sito, premere MENÙ, poi scegliere "Scansione/gestione so
+ <message>
+ <location filename="themestrings.h" line="102"/>
+ <source>Search Internet Video</source>
+- <translation>Ricerca internet video</translation>
++ <translation type="unfinished">Cerca internet video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="113"/>
+@@ -991,17 +997,17 @@ Autore: |AUTHOR%</translation>
+ <message>
+ <location filename="themestrings.h" line="109"/>
+ <source>Search popular video sites</source>
+- <translation>Ricerca di siti video popolari</translation>
++ <translation type="unfinished">Cerca di siti video popolari</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="22"/>
+ <source>Browse Internet Video</source>
+- <translation>Navigare internet video</translation>
++ <translation type="unfinished">Naviga internet video</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="25"/>
+ <source>Browse highlights and entire sites</source>
+- <translation>Navigare nelle caratteristiche e in interi siti</translation>
++ <translation type="unfinished">Naviga in evidenza e in interi siti</translation>
+ </message>
+ </context>
+ <context>
+diff --git a/mythplugins/mythnews/i18n/mythnews_it.ts b/mythplugins/mythnews/i18n/mythnews_it.ts
+index fcbcab4bef5..f1088dcdf06 100644
+--- a/mythplugins/mythnews/i18n/mythnews_it.ts
++++ b/mythplugins/mythnews/i18n/mythnews_it.ts
+@@ -249,7 +249,7 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="35"/>
+ <source>News Settings</source>
+- <translation>Impostazioni news</translation>
++ <translation type="unfinished">Impostaz. notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="5"/>
+@@ -264,7 +264,7 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="16"/>
+ <source>Choose the news sites you would like to read.</source>
+- <translation>Sceglie il sito di news che desideri leggere.</translation>
++ <translation type="unfinished">Scegli il sito di notizie che desideri leggere.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="31"/>
+@@ -284,7 +284,7 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="32"/>
+ <source>News Config</source>
+- <translation>Configurazione News</translation>
++ <translation type="unfinished">Config. notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="8"/>
+@@ -296,7 +296,9 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <source>%"|SUBTITLE|"
+
+ %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%"|SUBTITLE|"
++
++%%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+@@ -306,7 +308,7 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="12"/>
+ <source>Categories</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Categorie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="13"/>
+@@ -316,137 +318,137 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="15"/>
+ <source>Choose sites you want to receive news from</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli il sito dal quale vuoi ricevere le notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="17"/>
+ <source>Choose which news channels interest you</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli quali canali di notizie ti interessano</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="19"/>
+ <source>Date:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Data:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="23"/>
+ <source>Enclosures:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ambiti:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="26"/>
+ <source>Is Podcast:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Is Podcast:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="27"/>
+ <source>NEWS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">NOTIZIE</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="28"/>
+ <source>Name site:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome sito:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="30"/>
+ <source>Needs Download:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Richiede download:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="34"/>
+ <source>News Feeds Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni feed notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="37"/>
+ <source>News catagories</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Categorie notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="38"/>
+ <source>News settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostaz. notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="39"/>
+ <source>No</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">No</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="40"/>
+ <source>No news sites available, please configure your subscriptions using the configuration menu</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessun sito di notizie disponibile, configura i tuoi abbonamenti utilizzando il menu di configurazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="41"/>
+ <source>Not Applicable</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non applicabile</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="45"/>
+ <source>RSS Editor</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor RSS</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="46"/>
+ <source>RSS Icon:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Icona RSS:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="47"/>
+ <source>RSS Newsfeeds</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Newsfeed RSS</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="48"/>
+ <source>RSS URL:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">URL RSS:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="49"/>
+ <source>RSS feed is podcast:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Podcast è il feed RSS:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="50"/>
+ <source>RSS feed name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome feed RSS:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="51"/>
+ <source>Rating</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="52"/>
+ <source>Save</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salva</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="53"/>
+ <source>Select a feed to views</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona il feed da vedere</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="54"/>
+ <source>Site icon:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Icona sito:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="55"/>
+ <source>Subscribe to News Feeds</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Iscriviti ai feed notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="56"/>
+ <source>Title:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Titolo:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="57"/>
+ <source>URL site:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">URL sito:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="59"/>
+@@ -456,7 +458,7 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="60"/>
+ <source>Update</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiorna</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="64"/>
+@@ -471,7 +473,7 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="21"/>
+ <source>Edit RSS News Feed</source>
+- <translation>Modifica feed RSS</translation>
++ <translation type="unfinished">Modifica feed RSS notizie</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="43"/>
+@@ -503,7 +505,7 @@ Al termine della modifica, per continuare seleziona "Ok" o "Annul
+ <message>
+ <location filename="themestrings.h" line="33"/>
+ <source>News Feeds</source>
+- <translation>News Feeds</translation>
++ <translation type="unfinished">Feed notizie</translation>
+ </message>
+ </context>
+ </TS>
+diff --git a/mythplugins/mythweather/i18n/mythweather_it.ts b/mythplugins/mythweather/i18n/mythweather_it.ts
+index 3a583148967..160b5a501d5 100644
+--- a/mythplugins/mythweather/i18n/mythweather_it.ts
++++ b/mythplugins/mythweather/i18n/mythweather_it.ts
+@@ -965,17 +965,17 @@
+ <message>
+ <location filename="themestrings.h" line="41"/>
+ <source>Low: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Min: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="42"/>
+ <source>N/A</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">N/D</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="45"/>
+ <source>Precip: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Precip: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="46"/>
+@@ -985,42 +985,42 @@
+ <message>
+ <location filename="themestrings.h" line="48"/>
+ <source>Pressure: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Pressione: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="49"/>
+ <source>Results</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risultati</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="53"/>
+ <source>Save</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salva</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="56"/>
+ <source>Screen setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta schermo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="58"/>
+ <source>Search locations for your weather forecast</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca località per le previsioni meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="61"/>
+ <source>Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="62"/>
+ <source>Setup sources for your weather forecast</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta sorgenti previsioni meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="67"/>
+ <source>Source setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta sorgente</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="68"/>
+@@ -1030,7 +1030,7 @@
+ <message>
+ <location filename="themestrings.h" line="70"/>
+ <source>Temp: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Temp: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="75"/>
+@@ -1040,57 +1040,57 @@
+ <message>
+ <location filename="themestrings.h" line="77"/>
+ <source>Visibility: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Visibilità: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="78"/>
+ <source>Visibilty</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Visibilità</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="79"/>
+ <source>WEATHER</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">METEO</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="80"/>
+ <source>Weather</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="82"/>
+ <source>Weather Global Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni globali meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="84"/>
+ <source>Weather Report</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Bollettino meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="89"/>
+ <source>Weather Source Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni sorgente meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="91"/>
+ <source>Weather forecasts to</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Previsioni meteo a</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="92"/>
+ <source>Weather forecasts to display on screen</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Previsioni meteo da visualizzare sullo schermo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="97"/>
+ <source>Wind Direction: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Direzione del vento: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="98"/>
+ <source>Wind(Dir)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vento (dir)</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="99"/>
+@@ -1100,22 +1100,22 @@
+ <message>
+ <location filename="themestrings.h" line="100"/>
+ <source>Wind(Gust):</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vento (raffiche):</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="101"/>
+ <source>Wind(Gust): %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vento (raffiche): %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="102"/>
+ <source>dddd, MMMM dd, yyyy | hh:mm AP</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">gggg, MMMM gg, aaaa | hh:mm AP</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="103"/>
+ <source>updating</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">aggiorno</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="95"/>
+@@ -1125,27 +1125,27 @@
+ <message>
+ <location filename="themestrings.h" line="13"/>
+ <source>Configure your global weather settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura le impostazioni meteo globali</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="14"/>
+ <source>Configure your weather</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="16"/>
+ <source>Display</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Display</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="19"/>
+ <source>Enter location:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Inserisci Località:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="21"/>
+ <source>Enter your current location, nearest large town or city. In some countries postal codes are also supported. Hit the right or down arrow to continue.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immetti la località attuale, o una grande città vicina. In alcune nazioni sono supportati i codici postali.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="22"/>
+@@ -1155,17 +1155,17 @@
+ <message>
+ <location filename="themestrings.h" line="24"/>
+ <source>Feels Like: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Percepito: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="26"/>
+ <source>General Mythweather options</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Opzioni generali Mythweather</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="27"/>
+ <source>General weather</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Meteo globale</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="31"/>
+@@ -1176,12 +1176,12 @@
+ <message>
+ <location filename="themestrings.h" line="33"/>
+ <source>High: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Max: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="36"/>
+ <source>Humidity: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Umidità: %1</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="39"/>
+@@ -1278,7 +1278,7 @@ Al termine della configurazione del plugin meteo, fai clic "Finito" pe
+ <message>
+ <location filename="themestrings.h" line="88"/>
+ <source>Weather Setup</source>
+- <translation>Impostazioni meteo</translation>
++ <translation type="unfinished">Imposta meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+@@ -1293,7 +1293,7 @@ Al termine della configurazione del plugin meteo, fai clic "Finito" pe
+ <message>
+ <location filename="themestrings.h" line="87"/>
+ <source>Weather Script Settings</source>
+- <translation>Impostazioni script meteo</translation>
++ <translation type="unfinished">Imposta script meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="74"/>
+@@ -1332,12 +1332,12 @@ Al termine delle modifiche, fai clic su "Finito" per continuare.</tran
+ <message>
+ <location filename="themestrings.h" line="86"/>
+ <source>Weather Screen Setup</source>
+- <translation>Impostazioni schermo meteo</translation>
++ <translation type="unfinished">Imposta schermo meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="90"/>
+ <source>Weather Source Setup</source>
+- <translation>Impostazioni sorgente meteo</translation>
++ <translation type="unfinished">Imposta sorgente meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="63"/>
+@@ -1392,7 +1392,7 @@ Al termine delle modifiche, fai clic su "Finito" per continuare.</tran
+ <message>
+ <location filename="themestrings.h" line="30"/>
+ <source>Global Weather Settings</source>
+- <translation>Impostazioni meteo globali</translation>
++ <translation type="unfinished">Impostazioni globali meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="59"/>
+@@ -1407,7 +1407,7 @@ Al termine delle modifiche, fai clic su "Finito" per continuare.</tran
+ <message>
+ <location filename="themestrings.h" line="28"/>
+ <source>General weather options</source>
+- <translation>Opzioni meteo generali</translation>
++ <translation type="unfinished">Opzioni generali meteo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="54"/>
+@@ -1447,7 +1447,7 @@ Al termine delle modifiche, fai clic su "Finito" per continuare.</tran
+ <message>
+ <location filename="themestrings.h" line="55"/>
+ <source>Screen Setup</source>
+- <translation>Impostazioni schermo</translation>
++ <translation type="unfinished">Imposta schermo</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="38"/>
+@@ -1457,7 +1457,7 @@ Al termine delle modifiche, fai clic su "Finito" per continuare.</tran
+ <message>
+ <location filename="themestrings.h" line="20"/>
+ <source>Enter your current location, nearest large town or city. In some countries postal codes are also supported.</source>
+- <translation>Immetti la località corrente, o una grande città vicina. In alcune nazioni i codici postali sono supportati.</translation>
++ <translation type="unfinished">Immetti la località attuale, o una grande città vicina. In alcune nazioni sono supportati i codici postali.</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="18"/>
+diff --git a/mythplugins/mythzoneminder/i18n/mythzoneminder_it.ts b/mythplugins/mythzoneminder/i18n/mythzoneminder_it.ts
+index 02f288791d3..ba944227dc4 100644
+--- a/mythplugins/mythzoneminder/i18n/mythzoneminder_it.ts
++++ b/mythplugins/mythzoneminder/i18n/mythzoneminder_it.ts
+@@ -74,52 +74,52 @@
+ <message>
+ <location filename="themestrings.h" line="66"/>
+ <source>Monitor Function</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Funzione monitor</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="67"/>
+ <source>Monitors</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Monitor</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="71"/>
+ <source>Play</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="74"/>
+ <source>Save</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salva</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="75"/>
+ <source>See what your system is doing</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi cosa sta facendo il sistema</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="84"/>
+ <source>Show zoneminder</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra zoneminder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="85"/>
+ <source>Show zoneminder console</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra console zoneminder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="86"/>
+ <source>Show zoneminder events</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostr eventi zoneminder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="87"/>
+ <source>Show zoneminder live cctv views</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra viste cctv live di zoneminder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="89"/>
+ <source>Source:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sorgente:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="92"/>
+@@ -129,27 +129,27 @@
+ <message>
+ <location filename="themestrings.h" line="93"/>
+ <source>Storage:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="95"/>
+ <source>System Load:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Corico sistema:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="96"/>
+ <source>System:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sistema:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="99"/>
+ <source>Time:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ora:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="101"/>
+ <source>Zone Minder Console</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Console Zone Minder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="103"/>
+@@ -164,12 +164,12 @@
+ <message>
+ <location filename="themestrings.h" line="108"/>
+ <source>Zoneminder console</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Console Zoneminder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="110"/>
+ <source>[R] = Running, [S] = Stopped</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[R] = In esecuz., [S] = Fermato</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="112"/>
+@@ -229,7 +229,7 @@
+ <message>
+ <location filename="themestrings.h" line="51"/>
+ <source>Length</source>
+- <translation>Lunghezza</translation>
++ <translation type="unfinished">Durata</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="68"/>
+@@ -300,17 +300,18 @@
+ <location filename="themestrings.h" line="6"/>
+ <source>%buttontext%
+ %time%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%buttontext%
++%time%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="7"/>
+ <source>%camera%: %time% (%length%)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%camera%: %time% (%length%)</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="8"/>
+ <source>%length%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%length%</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="9"/>
+@@ -325,92 +326,92 @@
+ <message>
+ <location filename="themestrings.h" line="13"/>
+ <source>CCTV Console</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Console CCTV</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="14"/>
+ <source>CCTV Date:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Data CCTV:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="15"/>
+ <source>CCTV Event Player</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Lettore eventi CCTV</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="16"/>
+ <source>CCTV Live Player</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Live player CCTV</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="17"/>
+ <source>CCTV Status:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato CCTV:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="18"/>
+ <source>CCTV Time:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ora CCTV:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="26"/>
+ <source>Configure Zoneminder console</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura console di Zoneminder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="27"/>
+ <source>Console</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Console</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="31"/>
+ <source>Delete</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elimina</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="32"/>
+ <source>Disk Usage:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Utilizzo disco:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="36"/>
+ <source>Edit CCTV Monitor Function</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica funzione monitor CCTV</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="41"/>
+ <source>Event No:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">N. evento:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="43"/>
+ <source>Event number:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero evento:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="46"/>
+ <source>Events:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Eventi:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="49"/>
+ <source>Function:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Funzione:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="52"/>
+ <source>Length:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata:</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="54"/>
+ <source>Live View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">vista diretta</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="56"/>
+ <source>Look at the Zoneminder events list</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi elenco eventi di Zoneminder</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="59"/>
+@@ -552,7 +553,7 @@
+ <message>
+ <location filename="themestrings.h" line="94"/>
+ <source>Store</source>
+- <translation>Archiviare</translation>
++ <translation type="unfinished">Archivia</translation>
+ </message>
+ <message>
+ <location filename="themestrings.h" line="81"/>
+@@ -611,7 +612,7 @@
+ <message>
+ <location filename="../mythzoneminder/zmevents.cpp" line="84"/>
+ <source>Delete</source>
+- <translation>Cancella</translation>
++ <translation type="unfinished">Elimina</translation>
+ </message>
+ <message>
+ <location filename="../mythzoneminder/zmevents.cpp" line="165"/>
+@@ -649,12 +650,12 @@
+ <message>
+ <location filename="../mythzoneminder/zmevents.cpp" line="445"/>
+ <source>Delete All</source>
+- <translation>Cancellare tutto</translation>
++ <translation type="unfinished">Elimina tutto</translation>
+ </message>
+ <message>
+ <location filename="../mythzoneminder/zmevents.cpp" line="463"/>
+ <source>Delete All Events?</source>
+- <translation>Cancellare tutti gli eventi?</translation>
++ <translation type="unfinished">Elimino tutti gli eventi?</translation>
+ </message>
+ <message>
+ <location filename="../mythzoneminder/zmevents.cpp" line="464"/>
+@@ -731,7 +732,7 @@
+ <message>
+ <location filename="../mythzoneminder/zmplayer.cpp" line="98"/>
+ <source>Delete</source>
+- <translation>Cancellare</translation>
++ <translation type="unfinished">Elimina</translation>
+ </message>
+ <message>
+ <location filename="../mythzoneminder/zmplayer.cpp" line="104"/>
+diff --git a/mythtv/i18n/mythfrontend_it.ts b/mythtv/i18n/mythfrontend_it.ts
+index 9c8402ffa91..3eab2affce3 100644
+--- a/mythtv/i18n/mythfrontend_it.ts
++++ b/mythtv/i18n/mythfrontend_it.ts
+@@ -4144,7 +4144,7 @@ Dispositivo supporta fino a %1</translation>
+ <message>
+ <location filename="../programs/mythfrontend/customedit.cpp" line="466"/>
+ <source>Minimum star rating (0.0 to 1.0 for movies only)</source>
+- <translation>Valutazione gradimento minimo (solo film, da 0.0 a 1.0)</translation>
++ <translation type="unfinished">Valutazione minima (solo film, da 0.0 a 1.0)</translation>
+ </message>
+ <message>
+ <location filename="../programs/mythfrontend/customedit.cpp" line="351"/>
+@@ -4690,7 +4690,7 @@ Dispositivo supporta fino a %1</translation>
+ <message>
+ <location filename="../libs/libmyth/dbsettings.cpp" line="115"/>
+ <source>Reconnect time</source>
+- <translation>Ora riconnessione</translation>
++ <translation type="unfinished">Attesa riconnessione</translation>
+ </message>
+ <message>
+ <location filename="../libs/libmyth/dbsettings.cpp" line="117"/>
+@@ -15519,7 +15519,7 @@ non può essere vuoto.</translation>
+ <message>
+ <location filename="../programs/mythfrontend/programrecpriority.cpp" line="426"/>
+ <source>Schedule Priorities</source>
+- <translation>Priorità pianificazioni</translation>
++ <translation type="unfinished">Priorità programmazione</translation>
+ </message>
+ <message>
+ <location filename="../programs/mythfrontend/programrecpriority.cpp" line="1386"/>
+@@ -18871,7 +18871,7 @@ Il Grabber non fornisce i numeri dei canali, quindi devi impostarlo manualmente.
+ <message>
+ <location filename="../programs/mythtv-setup/backendsettings.cpp" line="312"/>
+ <source>This setting controls how the Storage Group scheduling code will balance new recordings across directories. 'Balanced Free Space' is the recommended method for most users.</source>
+- <translation>Questa impostazione controlla come il codice pianificazione del gruppo di archiviazione bilancierà le nuove registrazioni tra le directory. 'Spazio Libero bilanciato ' è il metodo raccomandato per la maggior parte degli utenti.</translation>
++ <translation type="unfinished">Questa impostazione controlla come il codice programmazione del gruppo di archiviazione bilancierà le nuove registrazioni tra le directory. 'Spazio Libero bilanciato ' è il metodo raccomandato per la maggior parte degli utenti.</translation>
+ </message>
+ <message>
+ <location filename="../programs/mythtv-setup/backendsettings.cpp" line="322"/>
+@@ -19200,7 +19200,7 @@ Il Grabber non fornisce i numeri dei canali, quindi devi impostarlo manualmente.
+ <message>
+ <location filename="../programs/mythtv-setup/backendsettings.cpp" line="864"/>
+ <source>Program Schedule Downloading Options</source>
+- <translation>Opzioni programma di pianificazione download</translation>
++ <translation type="unfinished">Opzioni download della pianificazione programma</translation>
+ </message>
+ <message>
+ <location filename="../programs/mythtv-setup/backendsettings.cpp" line="916"/>
+@@ -24074,7 +24074,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="21"/>
+ <source>%1 total</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%1 totale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="23"/>
+@@ -24166,7 +24166,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="59"/>
+ <source>%Directed by |DIRECTOR|, %%YEAR%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%Diretto da |DIRECTOR|, %%YEAR%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="61"/>
+@@ -24186,7 +24186,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="67"/>
+ <source>%LONGCHANNEL| %%on card: |CARD%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%LONGCHANNEL| %%su scheda: |CARD%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="68"/>
+@@ -24196,7 +24196,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="69"/>
+ <source>%LONGREPEAT% Size: %FILESIZE_STR%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%LONGREPEAT% Dimensione: %FILESIZE_STR%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="71"/>
+@@ -24236,12 +24236,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="83"/>
+ <source>%PLAYEDTIME| of% %TOTALTIME% %(|REMAININGTIME| remaining)%%(|BEHINDTIME| behind)%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%PLAYEDTIME| di% %TOTALTIME% %(|REMAININGTIME| rimanente)%%(|BEHINDTIME| fatto)%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="84"/>
+ <source>%PLAYGROUP% play group</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">gruppo riproduz. %PLAYGROUP% </translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="88"/>
+@@ -24298,12 +24298,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="109"/>
+ <source>%STORAGEGROUP% storage group</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">gruppo archiviaz. %STORAGEGROUP%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="110"/>
+ <source>%SUBTITLE%%[font]myverysmallsub[/font] [Part |PARTNUMBER|]%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%SUBTITLE%%[font]myverysmallsub[/font] [Part |PARTNUMBER|]%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="111"/>
+@@ -24383,7 +24383,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="131"/>
+ <source>%TITLE%% - [font]mymediumlight[/font]|SUBTITLE%%[font]myverysmallsub[/font] [Part |PARTNUMBER|]%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%TITLE%% - [font]mymediumlight[/font]|SUBTITLE%%[font]myverysmallsub[/font] [Part |PARTNUMBER|]%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="132"/>
+@@ -24438,7 +24438,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="150"/>
+ <source>%VIDEOFRAMES% frames</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%VIDEOFRAMES% frame</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="151"/>
+@@ -24538,7 +24538,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="182"/>
+ <source>%callsign% (Priority: %priority%)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%callsign% (Priorità: %priority%)</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="183"/>
+@@ -24573,12 +24573,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="192"/>
+ <source>%filesize_str% of %storagegroup% Storage</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%filesize_str% dell'Archivio %storagegroup%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="193"/>
+ <source>%lastplayed% (played: %playcount% times)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%lastplayed% (riprodotto: %playcount% volte)</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="194"/>
+@@ -24588,7 +24588,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="195"/>
+ <source>%longchannel% | %filesize_str% of %storagegroup% Storage</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%longchannel% | %filesize_str% dell'Archivio %storagegroup%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="196"/>
+@@ -24604,8 +24604,8 @@ Do you wish to continue watching?</source>
+ <location filename="../themes/themestrings.h" line="199"/>
+ <source>%n in current group</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>%n nel gruppo corrente</numerusform>
++ <numerusform>%n nel gruppo corrente</numerusform>
+ </translation>
+ </message>
+ <message numerus="yes">
+@@ -24628,8 +24628,8 @@ Do you wish to continue watching?</source>
+ <location filename="../themes/themestrings.h" line="204"/>
+ <source>%name% / %callsign% (Source: %sourcename%)</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>%name% / %callsign% (Sorgente: %sourcename%)</numerusform>
++ <numerusform>%name% / %callsign% (Sorgenti: %sourcename%)</numerusform>
+ </translation>
+ </message>
+ <message>
+@@ -24655,12 +24655,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="210"/>
+ <source>%rectypestatus% on card number %card%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%rectypestatus% sulla scheda numero: %card%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="211"/>
+ <source>%rectypestatus%% on card number |card%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%rectypestatus%% sulla scheda numero: |card%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="214"/>
+@@ -24727,7 +24727,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="230"/>
+ <source>%title% | %timedate% on %channel%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%title% | %timedate% su %channel%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="232"/>
+@@ -24747,7 +24747,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="236"/>
+ <source>%titlesubtitle% (Priority: %progpriority% / %finalpriority%)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%titlesubtitle% (Priorità: %progpriority% / %finalpriority%)</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="237"/>
+@@ -24772,7 +24772,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="241"/>
+ <source>%year%% by |director%%, |length%%, rating: |userrating%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">%year%% di |director%%, |length%%, valutazione: |userrating%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="242"/>
+@@ -24810,22 +24810,22 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="265"/>
+ <source>(%statusbefore%) -> (%statusafter%): %shorttimedate% on %channel%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">(%statusbefore%) -> (%statusafter%): %shorttimedate% su %channel%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="268"/>
+ <source>(1) Channel (2) Priority</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">(1) Canale (2) Priorità</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="271"/>
+ <source>(1) Title (2) Priority (4) Type</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">(1) Titolo (2) Priorità (4) Tipo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="274"/>
+ <source>(4) Program Guide **(6) Program Finder **</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">(4) Guida TV **(6) Ricerca Programma **</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="281"/>
+@@ -24835,16 +24835,18 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="282"/>
+ <source>0 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">0 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="285"/>
+ <source>0.0</source>
++ <translatorcomment>0% {0.0?}</translatorcomment>
+ <translation type="unfinished">0% {0.0?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="286"/>
+ <source>0.5</source>
++ <translatorcomment>0% {0.5?}</translatorcomment>
+ <translation type="unfinished">0% {0.5?}</translation>
+ </message>
+ <message>
+@@ -24860,72 +24862,81 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="292"/>
+ <source>1.0</source>
++ <translatorcomment>0% {1.0?}</translatorcomment>
+ <translation type="unfinished">0% {1.0?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="293"/>
+ <source>1.5</source>
++ <translatorcomment>0% {1.5?}</translatorcomment>
+ <translation type="unfinished">0% {1.5?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="294"/>
+ <source>10 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">10 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="298"/>
+ <source>16x9</source>
++ <translatorcomment>1h30m {16x?} {9?}</translatorcomment>
+ <translation type="unfinished">1h30m {16x?} {9?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="300"/>
+ <source>2 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">2 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="302"/>
+ <source>2.0</source>
++ <translatorcomment>0% {2.0?}</translatorcomment>
+ <translation type="unfinished">0% {2.0?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="303"/>
+ <source>2.5</source>
++ <translatorcomment>0% {2.5?}</translatorcomment>
+ <translation type="unfinished">0% {2.5?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="305"/>
+ <source>3 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">3 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="307"/>
+ <source>3.0</source>
++ <translatorcomment>0% {3.0?}</translatorcomment>
+ <translation type="unfinished">0% {3.0?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="308"/>
+ <source>3.5</source>
++ <translatorcomment>0% {3.5?}</translatorcomment>
+ <translation type="unfinished">0% {3.5?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="310"/>
+ <source>4 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">4 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="312"/>
+ <source>4.0</source>
++ <translatorcomment>0% {4.0?}</translatorcomment>
+ <translation type="unfinished">0% {4.0?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="313"/>
+ <source>4.5</source>
++ <translatorcomment>0% {4.5?}</translatorcomment>
+ <translation type="unfinished">0% {4.5?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="314"/>
+ <source>5 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">5 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="315"/>
+@@ -24935,17 +24946,19 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="316"/>
+ <source>5.0</source>
++ <translatorcomment>0% {5.0?}</translatorcomment>
+ <translation type="unfinished">0% {5.0?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="317"/>
+ <source>576p</source>
++ <translatorcomment>0% {576p?}</translatorcomment>
+ <translation type="unfinished">0% {576p?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="318"/>
+ <source>6 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">6 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="319"/>
+@@ -24955,7 +24968,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="320"/>
+ <source>7 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">7 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="321"/>
+@@ -24965,12 +24978,13 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="323"/>
+ <source>720p</source>
++ <translatorcomment>0% {720p?}</translatorcomment>
+ <translation type="unfinished">0% {720p?}</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="324"/>
+ <source>8 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">8 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="325"/>
+@@ -24980,17 +24994,17 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="326"/>
+ <source>9 Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">8 Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="327"/>
+ <source>:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="334"/>
+ <source>???</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">???</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="338"/>
+@@ -25000,97 +25014,97 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="341"/>
+ <source>A simple theme of black, white and shades of gray.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Un tema semplice binco e nero e sfumature di grigio.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="343"/>
+ <source>A version of Steppes with 25% larger fonts. Reminder: On Watch Recordings screen [Rew] and [FF] can be used to change the Rec. Group.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Una versione di Steppes con font più grandi del 25%. Promemoria: Nella schermata Registrazioni [Rew] e [FF] cambiano il Gruppo di Reg.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="346"/>
+ <source>A/V Sync:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">A/V Sync:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="347"/>
+ <source>A/V Sync: %avsync%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">A/V Sync: %avsync%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="350"/>
+ <source>AC3 Audio:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Audio AC3:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="351"/>
+ <source>AP</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AP</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="352"/>
+ <source>AUTOEXPIRE</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AUTOEXPIRE</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="353"/>
+ <source>AVCHD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AVCHD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="356"/>
+ <source>Actions:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Azioni:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="362"/>
+ <source>Add File Extensions</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi Estensioni File</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="364"/>
+ <source>Add your storage</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi il tuo archivio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="365"/>
+ <source>Add your storage Folders</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiungi le tue cartelle archivio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="375"/>
+ <source>Aired</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trasmesso</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="376"/>
+ <source>Aired:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trasmesso:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="377"/>
+ <source>Album:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Album:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="378"/>
+ <source>All programs alphabetically sorted</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tutti i programmi ordinati alfabeticamente</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="380"/>
+ <source>Allow recordings to expire:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Consenti scadenza registrazioni:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="382"/>
+ <source>Alphabet</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Alfabeto</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="385"/>
+ <source>Alternative player:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduttore alternativo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="387"/>
+@@ -25100,47 +25114,47 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="391"/>
+ <source>Apply filters to select your movies</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Applica filtri per selezionare i tuoi film</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="394"/>
+ <source>Archive Utilities</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Utilità archiviazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="395"/>
+ <source>Archives</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="397"/>
+ <source>Article item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Articolo numero:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="398"/>
+ <source>Artist:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Artista:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="399"/>
+ <source>Artwork Results</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risultati Artwork</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="402"/>
+ <source>Artwork and Metadata</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Artwork e Metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="404"/>
+ <source>Artwork selector</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">selettore Artwork</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="405"/>
+ <source>Artwork, Metadata Sources</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Artwork, Sorgenti Metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="406"/>
+@@ -25150,222 +25164,222 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="407"/>
+ <source>Aspect:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aspetto:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="408"/>
+ <source>Aspect: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aspetto: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="410"/>
+ <source>Associate File</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Associa File</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="412"/>
+ <source>Attempting to connect to Database.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sto cercando di collegarmi al Database.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="413"/>
+ <source>Attempting to connect to master backend.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sto cercando di collegarmi al master backend.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="414"/>
+ <source>Attempting to wake master backend.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sto cercando di avviare il master backend.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="418"/>
+ <source>Audio Details</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli audio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="420"/>
+ <source>Audio Setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura audio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="421"/>
+ <source>Audio Track:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Traccia audio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="422"/>
+ <source>Audio codec:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Codec Audio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="423"/>
+ <source>Audio volume:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Volume audio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="424"/>
+ <source>Audio: %audiocodec%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Audio: %audiocodec%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="425"/>
+ <source>Audio: 5 CH.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Audio: 5 CH.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="426"/>
+ <source>Audio: 7 CH.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Audio: 7 CH.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="427"/>
+ <source>Audio: 7.1 CH.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Audio: 7.1 CH.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="428"/>
+ <source>Audio: Mono</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Audio: Mono</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="429"/>
+ <source>AudioChannels: 5.1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Canali Audio: 5.1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="430"/>
+ <source>AudioChannels: Stereo</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Canali Audio: Stereo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="431"/>
+ <source>AudioCodec: AAC</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: AAC</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="432"/>
+ <source>AudioCodec: AC3</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: AC3</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="433"/>
+ <source>AudioCodec: BLURAY</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: BLURAY</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="434"/>
+ <source>AudioCodec: DTS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: DTS</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="435"/>
+ <source>AudioCodec: E-AC3</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: E-AC3</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="436"/>
+ <source>AudioCodec: MP2</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: MP2</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="437"/>
+ <source>AudioCodec: MP3</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: MP3</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="438"/>
+ <source>AudioCodec: PCM_DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: PCM_DVD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="439"/>
+ <source>AudioCodec: TRUEHD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: TRUEHD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="440"/>
+ <source>AudioCodec: VORBIS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: VORBIS</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="441"/>
+ <source>AudioCodec: WMAPRO</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">AudioCodec: WMAPRO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="442"/>
+ <source>Auto expire:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scade automaticamente:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="448"/>
+ <source>Available Buffer: %BUFFERAVAIL% of %BUFFERSIZE%Mb</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Buffer disponibile: %BUFFERAVAIL% di %BUFFERSIZE%Mb</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="452"/>
+ <source>BOOKMARKED</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">SEGNALIBRO INS</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="454"/>
+ <source>Back more</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Più indietro</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="455"/>
+ <source>Backend Selection</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Selezione backend</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="457"/>
+ <source>Backend is idle!</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Backend inattivo!</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="459"/>
+ <source>Backend is offline!</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Backend scollegato!</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="461"/>
+ <source>Backend is recording!</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Backend sta registrando!</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="466"/>
+ <source>Banner Image:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine banner:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="468"/>
+ <source>Banner: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Banner: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="470"/>
+ <source>Based on Mythbuntu theme with 14ch 4hr guide full menus</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Basato sul tema Mythbuntu con guida TV di 14 canali per 4 ore</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="471"/>
+ <source>Basic configuration</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configurazione base</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="472"/>
+ <source>Basic configuration wizard</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configurazione base guidata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="476"/>
+ <source>Blu-ray</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Blu-ray</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="483"/>
+ <source>BlueRay Disc</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Disco Blu-ray</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="485"/>
+@@ -25375,67 +25389,67 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="492"/>
+ <source>Browse Video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sfoglia video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="494"/>
+ <source>Browse Web</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Naviga sul web</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="496"/>
+ <source>Browse popular video sites</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Naviga sui siti di video popolari</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="497"/>
+ <source>Browse the</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sfoglia </translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="498"/>
+ <source>Browse the World Wide Web</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Naviga sul World Wide Web</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="503"/>
+ <source>Browse your movie library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sfoglia la tua libreria video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="506"/>
+ <source>Browse your video games library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sfoglia la tua libreria di videogiochi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="511"/>
+ <source>Browseable: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Navigabile: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="512"/>
+ <source>Browseble:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Navigabile:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="513"/>
+ <source>Browser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Browser</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="514"/>
+ <source>BrowsingLiveTV</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scorri DirettaTV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="517"/>
+ <source>Buffer to Decoder: %decoderrate%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Buffer al Decoder: %decoderrate%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="518"/>
+ <source>Buffer: %bufferstatus%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Buffer: %bufferstatus%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="521"/>
+@@ -25445,7 +25459,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="522"/>
+ <source>CCTV</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CCTV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="523"/>
+@@ -25455,192 +25469,193 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="525"/>
+ <source>CFLAGQUEUED</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CFLAGQUEUED</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="526"/>
+ <source>COMFLAGGED</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">COMFLAGGED</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="527"/>
+ <source>COMFLAGGING</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">COMFLAGGING</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="528"/>
+ <source>CPU load:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Carico CPU:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="529"/>
+ <source>CPU load: %load%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Carico CPU: %load%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="531"/>
+ <source>CUTLIST</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CUTLIST</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="536"/>
+ <source>Captions</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Didascalie</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="537"/>
+ <source>Card: %card%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scheda: %card%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="538"/>
+ <source>Cards</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Schede</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="542"/>
+ <source>Cast: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cast: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="543"/>
+ <source>Catagories</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Categorie</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="544"/>
+ <source>Catagory</source>
+- <translation type="unfinished"></translation>
++ <translatorcomment>Categoria</translatorcomment>
++ <translation type="unfinished">Catagory</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="551"/>
+ <source>Category: %CATEGORY% PlayBackGroup: %PLAYGROUP% RecGroup: %RECORDINGGROUP% StorageGroup: %STORAGEGROUP%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Categoria: %CATEGORY% GruppoRiproduz.: %PLAYGROUP% GruppoReg.: %RECORDINGGROUP% GruppoArchiv.: %STORAGEGROUP%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="563"/>
+ <source>Change display groups</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cambia gruppi visualizzazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="564"/>
+ <source>Change mythtv key bindings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cambia associazioni tasti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="565"/>
+ <source>Change recording group password</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cambia password gruppo di registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="566"/>
+ <source>Change:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="578"/>
+ <source>Channel Rec Priority</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Priorità Reg. canale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="583"/>
+ <source>Channel name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome canale:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="584"/>
+ <source>Channel number:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero canale:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="589"/>
+ <source>Channels:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Canali:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="590"/>
+ <source>Channels: %audiochannels%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Canali: %audiochannels%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="591"/>
+ <source>Check the</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Verifica il</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="592"/>
+ <source>Check your Surveillance</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Controlla la tua sorveglianza</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="593"/>
+ <source>Check your Surveillance Cameras</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Controlla le tue telecamere di sorveglianza</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="595"/>
+ <source>Choose Country and Language</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli Paese e lingua</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="596"/>
+ <source>Choose a</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli un</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="597"/>
+ <source>Choose a Theme</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli un Tema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="598"/>
+ <source>Choose a local theme or download new</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli un tema presente o scaricane uno</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="603"/>
+ <source>Choose sources for your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli le sorgenti per il tuo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="604"/>
+ <source>Choose which artwork you like most</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli quale artwork ti piace di più</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="605"/>
+ <source>Choose which movie or tvserie you look for</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli il film o la serie tv che stai cercando</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="607"/>
+ <source>Choose your mythtv backend server</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli il tuo server backend mythtv</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="610"/>
+ <source>Clear database contents (Resync required):</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cancella contenuto database (richiede risincronizzazione):</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="622"/>
+ <source>Codec :</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Codec :</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="625"/>
+ <source>Codec/Dec:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Codec/Dec:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="626"/>
+ <source>Codec/Dec: %VIDEOCODEC% %VIDEODECODER%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Codec/Dec: %VIDEOCODEC% %VIDEODECODER%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="630"/>
+ <source>Commercial Flag recordings:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca pubblicità nelle registrazioni:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="631"/>
+@@ -25670,217 +25685,217 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="638"/>
+ <source>Configure Mythtv key bindings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura associazioni tasti Mythtv</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="641"/>
+ <source>Configure audio settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura impostazioni audio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="642"/>
+ <source>Configure display of</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura visualizzazione di</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="643"/>
+ <source>Configure display of Pictures</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura visualizzazione immagini</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="645"/>
+ <source>Configure how mythtv</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura come mythtv</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="647"/>
+ <source>Configure information plugins</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura informazioni plugin</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="649"/>
+ <source>Configure media plugins</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura plugin multimedia</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="651"/>
+ <source>Configure mythtv core</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura core mythtv</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="652"/>
+ <source>Configure mythtv look and feel</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura aspetto mythtv</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="653"/>
+ <source>Configure playback</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="654"/>
+ <source>Configure playback and</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione e</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="656"/>
+ <source>Configure playback and TV settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione e impostazioni TV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="658"/>
+ <source>Configure playback of</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione di</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="659"/>
+ <source>Configure playback of Music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione Musica</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="660"/>
+ <source>Configure playback of Video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione Video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="662"/>
+ <source>Configure playback of media streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione media stream</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="663"/>
+ <source>Configure playing of</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione di</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="664"/>
+ <source>Configure playing of Games</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura riproduzione Giochi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="665"/>
+ <source>Configure sources for</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura sorgenti di</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="668"/>
+ <source>Configure system settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura impostazioni sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="669"/>
+ <source>Configure the metadata</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="671"/>
+ <source>Configure the skin and</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura skin e</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="672"/>
+ <source>Configure video and DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura video e DVD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="673"/>
+ <source>Configure video and DVD archives</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura archivi video e DVD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="677"/>
+ <source>Configure your capture</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura acquisizione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="678"/>
+ <source>Configure your capture cards</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura schede acquisizione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="679"/>
+ <source>Configure your video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="680"/>
+ <source>Configure your video Sources</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura sorgenti video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="681"/>
+ <source>Configure your web</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura web</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="682"/>
+ <source>Connect your cards to</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Connetti schede a</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="683"/>
+ <source>Connect your cards to video sources</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Connetti schede alle sorgenti video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="685"/>
+ <source>Context:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Contesto:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="687"/>
+ <source>Controls:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Controlli:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="689"/>
+ <source>Core Setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configurazione base</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="692"/>
+ <source>Country and Language selection</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona lingua e paese</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="700"/>
+ <source>Coverart Image:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine copertina:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="702"/>
+ <source>Coverart: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copertina: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="703"/>
+ <source>Coverfile:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File copertina:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="705"/>
+ <source>Create advanced recording rules</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Crea regole registrazione avanzate</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="707"/>
+ <source>Creating thumbnail %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Creazione miniatura: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="712"/>
+ <source>Current Rule Phrase:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Frase regola corrente:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="713"/>
+ <source>Current phrase to search for:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Frase corrente da cercare:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="715"/>
+ <source>Currently Playing:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">In riproduzione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="717"/>
+@@ -25890,57 +25905,57 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="718"/>
+ <source>Custom Edit</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica personalizzata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="726"/>
+ <source>Customize meta data of your Recordings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza metadati registrazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="727"/>
+ <source>Customize meta data of your videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza metadati video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="728"/>
+ <source>Customize your channel icons</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza icone canali</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="729"/>
+ <source>Customize your channels settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza impostazioni canale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="730"/>
+ <source>Customize your country settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza impostazioni paese</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="731"/>
+ <source>Customize your grabber settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza impostazioni grabber</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="732"/>
+ <source>Customize your recording schedule</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Personalizza calendario registrazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="733"/>
+ <source>DOLBY</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DOLBY</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="738"/>
+ <source>DVD drive:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DVD drive:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="739"/>
+ <source>DVD player:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DVD player:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="740"/>
+@@ -25950,32 +25965,32 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="742"/>
+ <source>Database failed to start.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mancato avvio database.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="743"/>
+ <source>Database machine failed to wake up.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mancato avvio macchina del database.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="746"/>
+ <source>Decoder :</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Decoder :</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="747"/>
+ <source>Decoder rate:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Velocità decoder:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="749"/>
+ <source>Default player:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riprodut. predefinito:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="751"/>
+ <source>Deint :</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Deint :</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="756"/>
+@@ -25985,72 +26000,72 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="758"/>
+ <source>Delete system Profile</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elimina profilo sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="765"/>
+ <source>Detailed information about your video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni dettagliate sul video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="766"/>
+ <source>Detailed information of your video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni dettagliate del video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="768"/>
+ <source>Details:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="769"/>
+ <source>Directed By:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Diretto da:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="772"/>
+ <source>Directed by %director%%, |year%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Diretto da %director%%, |year%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="774"/>
+ <source>Directed by: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Diretto da: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="775"/>
+ <source>Director</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regista</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="777"/>
+ <source>Director: %director% - %length%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regista: %director% - %length%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="778"/>
+ <source>Directories</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Directory</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="781"/>
+ <source>Disk space: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Spazio disco: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="782"/>
+ <source>Disks</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dischi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="786"/>
+ <source>Download icons</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scarica icone</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="788"/>
+ <source>Download status</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato scaricamento</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="789"/>
+@@ -26060,22 +26075,22 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="794"/>
+ <source>Duplicate Method:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Replica Metodo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="795"/>
+ <source>Duplicate Scope:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Replica Scopo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="796"/>
+ <source>Duplication Methode:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Replicazione Metodo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="797"/>
+ <source>Duplication Scope:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Replicazione Scopo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="798"/>
+@@ -26085,122 +26100,122 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="800"/>
+ <source>Duration of an image transition (ms):</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata intervallo immagini (ms):</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="805"/>
+ <source>Edit Command for</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica Comando per</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="810"/>
+ <source>Edit Metadata Information</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica Informazioni Metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="814"/>
+ <source>Edit Powersearch</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica Powersearch</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="816"/>
+ <source>Edit System events</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica eventi sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="820"/>
+ <source>Edit keys</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica tasti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="822"/>
+ <source>Edit search item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica elemento ricerca:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="825"/>
+ <source>Edit your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica il tuo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="826"/>
+ <source>Edit your Channels</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica Canali</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="827"/>
+ <source>Edit your television channels</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Modifica canali televisivi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="828"/>
+ <source>Editor</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="829"/>
+ <source>Eject</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Espelli</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="832"/>
+ <source>Eject optical from drive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Espelli disco dall'unità</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="842"/>
+ <source>End late:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fine entro:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="847"/>
+ <source>End time:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ora fine:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="848"/>
+ <source>End: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fine: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="849"/>
+ <source>Enter</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Inserisci</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="850"/>
+ <source>Enter Plot:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Inserisci trama:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="852"/>
+ <source>Enter Search Text:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immetti testo ricerca:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="854"/>
+ <source>Enter information for action</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immetti informazioni per l'azione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="855"/>
+ <source>Enter phrase to search for:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immetti frase da cercare:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="856"/>
+ <source>Enter:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immissione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="858"/>
+ <source>Episode %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Episodio %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="862"/>
+ <source>Events</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Eventi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="866"/>
+@@ -26210,27 +26225,27 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="867"/>
+ <source>Exit from MythTV.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Esci da MythTV.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="870"/>
+ <source>Extra</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Extra</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="874"/>
+ <source>FPS:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">FPS:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="875"/>
+ <source>FPS: %framerate%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">FPS: %framerate%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="878"/>
+ <source>Fanart: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fanart: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="879"/>
+@@ -26240,57 +26255,57 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="880"/>
+ <source>Fast fwd</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fast fwd</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="881"/>
+ <source>File :</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File :</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="882"/>
+ <source>File Associations</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Associazioni File</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="884"/>
+ <source>File Buffer:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Buffer file:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="886"/>
+ <source>File Manager</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">File Manager</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="888"/>
+ <source>File Size:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dimensione file:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="890"/>
+ <source>File browser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Browser file</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="891"/>
+ <source>File info:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Info file:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="894"/>
+ <source>FileBrowser</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">FileBrowser</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="898"/>
+ <source>Filename: %filename%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome file: %filename%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="899"/>
+ <source>Fill</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riempi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="900"/>
+@@ -26300,7 +26315,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="905"/>
+ <source>Filter: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Filtro: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="907"/>
+@@ -26310,27 +26325,27 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="909"/>
+ <source>Filters you can apply:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Filtri possibili:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="911"/>
+ <source>Final Priority</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Priorità ultima</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="920"/>
+ <source>Find OnNet</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trova su Net</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="928"/>
+ <source>Find broadcasted programs</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca programmi trasmessi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="936"/>
+ <source>Find your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trova il tuo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="937"/>
+@@ -26340,37 +26355,37 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="940"/>
+ <source>Flag</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Segno</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="944"/>
+ <source>Frame Number</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero Frame</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="945"/>
+ <source>Frame audio level +/-0.5</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Livello audio frame +/-0.5</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="946"/>
+ <source>Frame: %framedisplay% | %cutindicator%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Frame: %framedisplay% | %cutindicator%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="949"/>
+ <source>Frames decoded/free: %videoframes%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Frame decodificati/liberi: %videoframes%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="953"/>
+ <source>Full plot</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trama completa</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="954"/>
+ <source>Fwd more</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fwd ulteriore</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="956"/>
+@@ -26380,32 +26395,32 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="963"/>
+ <source>General movie</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Film generale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="968"/>
+ <source>Genres</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Generi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="977"/>
+ <source>Grabber</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Grabber</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="980"/>
+ <source>Group selector</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Selettore gruppo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="981"/>
+ <source>Group your favorite</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Raggruppa preferito</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="983"/>
+ <source>Grouplist item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Voce elenco gruppi:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="984"/>
+@@ -26415,7 +26430,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="985"/>
+ <source>Guide</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guida</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="989"/>
+@@ -26425,12 +26440,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="990"/>
+ <source>HD1080</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">HD1080</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="991"/>
+ <source>HD720P</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">HD720P</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="992"/>
+@@ -26440,72 +26455,72 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="993"/>
+ <source>Handlers</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gestori</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="994"/>
+ <source>Hardware Configuration</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configuraizone Hardware</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="998"/>
+ <source>Hide Channels without channel number</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nascondi Canali senza numero canale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1000"/>
+ <source>Hide Channels without channel number:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nascondi Canali senza numero canale:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1008"/>
+ <source>IMDB</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">IMDB</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1011"/>
+ <source>INETREF:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">INETREF:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1016"/>
+ <source>Icon Import</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa icona</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1023"/>
+ <source>Image Loading...</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Caricamento immagine ...</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1028"/>
+ <source>Import and Export</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa e Esporta</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1029"/>
+ <source>Import and Export your videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa e Esporta video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1032"/>
+ <source>Import your music from CD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Importa musica dal CD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1043"/>
+ <source>Info Setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura info</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1044"/>
+ <source>Info about your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Info riguardo al</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1045"/>
+ <source>Info about your system</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Info riguardo al sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1046"/>
+@@ -26515,37 +26530,37 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1048"/>
+ <source>Information about your System</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni riguardo al sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1050"/>
+ <source>Information and Tools</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni e strumenti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1052"/>
+ <source>Inputs</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ingressi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1054"/>
+ <source>Install Rule</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regola installazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1055"/>
+ <source>Installed</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Installata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1056"/>
+ <source>Internet</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Internet</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1062"/>
+ <source>Job 1 of 1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Lavoro 1 di 1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1063"/>
+@@ -26555,7 +26570,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1064"/>
+ <source>Jump Forward</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salta avanti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1065"/>
+@@ -26565,12 +26580,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1066"/>
+ <source>Jump bkmark</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vai al segno</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1067"/>
+ <source>Jump fwd</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vai avanti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1068"/>
+@@ -26580,27 +26595,27 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1069"/>
+ <source>Jump to Start</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vai all'inizio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1070"/>
+ <source>Jump to start</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vai all'inizio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1073"/>
+ <source>Key Bindings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Associazioni tasti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1074"/>
+ <source>Key Grabber</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Grabber tasto</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1076"/>
+ <source>Key pressed:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tasto premuto:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1077"/>
+@@ -26610,78 +26625,79 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1082"/>
+ <source>Language:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Lingua:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1083"/>
+ <source>Last Played:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ultimo riprodotto:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1086"/>
+ <source>Last Recorded:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ultimo registrato:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1087"/>
+ <source>Last recorded %lastrecordeddate%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ultimo registrato %lastrecordeddate%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1088"/>
+ <source>Last: %statusbefore%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ultimo: %statusbefore%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1090"/>
+ <source>Lenght</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1093"/>
+ <source>Level</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Livello</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1096"/>
+ <source>List item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Voce elenco:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1097"/>
+ <source>Listen to</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ascolta</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1098"/>
+ <source>Listen to Internet</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ascolta da Internet</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1099"/>
+ <source>Listen to Internet Radio Streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ascolta Internet Radio Stream</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1106"/>
+ <source>Lists</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Liste</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1108"/>
+ <source>Live Television</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Diretta TV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1110"/>
+ <source>Loading Video Library
+ If no videos appear your library is either empty or unconfigured (Press M to adjust settings)</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Caricamento libreria video
++ Se non vengono visualizzati video, la libreria è vuota o non configurata (premi M per cambiare le impostazioni)</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1111"/>
+ <source>Loading content for playback...</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Caricamento contenuto da riprodurre ...</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1112"/>
+@@ -26691,32 +26707,32 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1113"/>
+ <source>Local Banner</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Banner locale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1114"/>
+ <source>Local Coverart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copertina locale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1115"/>
+ <source>Local Fanart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fanart locale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1116"/>
+ <source>Local Search</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca locale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1118"/>
+ <source>Locally installed</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Installato localmente</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1119"/>
+ <source>Location and sources for</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione e sorgenti per</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1120"/>
+@@ -26726,67 +26742,67 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1121"/>
+ <source>Lock: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Lock: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1126"/>
+ <source>Look at the TV Guide</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guarda la guida TV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1128"/>
+ <source>Look what is on the</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guarda cosa c'è il</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1129"/>
+ <source>Looks</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Simile</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1132"/>
+ <source>MANAGE RECORDING RULES</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GESTISCI REGOLE REGISTRAZIONE</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1133"/>
+ <source>MONO</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">MONO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1147"/>
+ <source>Manual Schedule your Recording</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Programma registrazione manualmente</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1149"/>
+ <source>Manually Schedule programs</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Programma programmi manualmente</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1150"/>
+ <source>Master backend failed to wake.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Il Master backend non si è avviato.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1152"/>
+ <source>Max episodes:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Max episodi:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1154"/>
+ <source>Maximum Number of Episodes to Keep:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero massimo di episodi da conservare:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1155"/>
+ <source>Maximum Number of Newest Episodes:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero massimo di episodi più recenti:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1158"/>
+ <source>Media Setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta media</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1160"/>
+@@ -26796,17 +26812,17 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1161"/>
+ <source>Message</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Messaggio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1162"/>
+ <source>MetaData</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">MetaDati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1163"/>
+ <source>Metadata</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1164"/>
+@@ -26816,12 +26832,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1165"/>
+ <source>Metadata OPTIONS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">OPZIONI metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1170"/>
+ <source>Metadata settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1173"/>
+@@ -26831,47 +26847,47 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1177"/>
+ <source>More Actions</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Più azioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1179"/>
+ <source>Move BottomLine</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sposta BottomLine</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1181"/>
+ <source>Move the selected amber arrow to the corner of the TV screen. Press SELECT to edit the other arrow. Press MENU for options and ESC to quit.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sposta la freccia ambra sull'angolo dello schermo TV. Premi SELEZIONA per modificare l'altra posizione. Premi MENU per le opzioni e ESC per uscire.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1186"/>
+ <source>Move the selected corner of the preview image to the corner of the TV screen. Press SELECT to edit the other corner. Press MENU for options and ESC to quit.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sposta l'angolo selezionato dell'anteprima nell'angolo dello schermo TV. Premi SELEZIONA per modificare l'altro angolo. Premi MENU per le opzioni e ESC per uscire.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1187"/>
+ <source>Movie Details</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli film</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1190"/>
+ <source>Movie Rating:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Classifica film:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1191"/>
+ <source>Movie Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni film</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1192"/>
+ <source>Movie/TV DB</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Film/TV DB</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1193"/>
+ <source>Movie/TV DB:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Film/TV DB:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1199"/>
+@@ -26881,57 +26897,57 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1202"/>
+ <source>My audio subsystem supports DTS-HD:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Il sottosistema audio supporta DTS-HD:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1203"/>
+ <source>My audio subsystem supports DTS:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Il sottosistema audio supporta DTS:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1205"/>
+ <source>My audio subsystem supports Dolby Digital:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Il sottosistema audio supporta Dolby Digital:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1207"/>
+ <source>My audio subsystem supports E-AC-3:</source>
+- <translation>Il sottosistema audio supporta il formato E-AC-3:</translation>
++ <translation type="unfinished">Il sottosistema audio supporta E-AC-3:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1209"/>
+ <source>My audio subsystem supports TrueHD:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Il sottosistema audio supporta TrueHD:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1219"/>
+ <source>MythTV Settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impostazioni MythTV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1220"/>
+ <source>MythTV Startup Status</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato di avvio MythTV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1221"/>
+ <source>MythTV is a Free Open Source software digital video recorder (DVR) project distributed under the terms of the GNU GPL - It has been under heavy development since 2002 - Now it contains most features one would expect from a good DVR and many new ones that you soon won't be able to live without - This theme is based on and inspired by the original XBMC skin called Aeon by Duncan Harris - When using this theme you should select the customized menu called 'MythAeon' as this menu lay-out and items are tailored for this particular theme</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">MythTV è un software open source gratuito di videoregistrazione digitale (DVR) distribuito secondo i termini della GNU GPL - È stato sviluppato intensamente dal 2002 - Ora contiene la maggior parte delle funzionalità che ci si aspetterebbe da un buon DVR e da molte nuove che presto non potrai più farne a meno - Questo tema è basato e ispirato alla skin XBMC originale chiamata Aeon di Duncan Harris - Quando usi questo tema dovresti selezionare il menu personalizzato chiamato 'MythAeon' dato che questo layout di menu e le voci sono fatti su misura per questo particolare tema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1225"/>
+ <source>MythTV is in standby mode!</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">MythTV è in modalità standby!</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1227"/>
+ <source>NO</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">NO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1229"/>
+ <source>Name:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nome:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1231"/>
+@@ -26941,22 +26957,22 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1232"/>
+ <source>Navigation</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Navigazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1233"/>
+ <source>Navigation control:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Controllo navigazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1236"/>
+ <source>New Password</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nuova Password</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1240"/>
+ <source>New: %statusafter%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nuovo: %statusafter%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1241"/>
+@@ -26966,7 +26982,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1248"/>
+ <source>Next Song:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Prossima canzone:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1252"/>
+@@ -26976,97 +26992,97 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1256"/>
+ <source>No additional info available</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessuna ulteriore informazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1258"/>
+ <source>No extra info available</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessuna informazione aggiuntiva</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1259"/>
+ <source>No listings found for this program search.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non c'è nessun elenco relativo al programma cercato.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1262"/>
+ <source>No resolution specified</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessuna risoluzione specificata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1267"/>
+ <source>No videos in library, or no files found. If you have configured a video directory, press "M" (or the MENU key) and select "Scan for Change."</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessun video nella libreria o file. Se hai configurato una directory video, premi "M" (o il tasto MENU) e seleziona "Cerca modifiche".</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1271"/>
+ <source>No volume controls configured</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Nessun controllo volume configurato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1276"/>
+ <source>Not applicable</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non applicabile</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1277"/>
+ <source>Not recorded yet</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non ancora registrato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1278"/>
+ <source>Not sure which screen this is:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non sono sicuro quale schermata sia:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1279"/>
+ <source>Number of Recordings:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Numero di registrazioni:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1282"/>
+ <source>OSD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">OSD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1286"/>
+ <source>OSD Input Message</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Messaggio di input OSD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1289"/>
+ <source>OSD Notification Message</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Messaggio di notifica OSD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1297"/>
+ <source>Online Banner</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Banner oline</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1298"/>
+ <source>Online Coverart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copertina online</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1299"/>
+ <source>Online Fanart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Fanart online</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1302"/>
+ <source>Optional category:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Categoria opzionale:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1303"/>
+ <source>Optional channel:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Canale opzionale:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1305"/>
+ <source>Optional genre:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Genere opzionale:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1308"/>
+@@ -27076,7 +27092,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1309"/>
+ <source>Order By:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ordinato per:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1313"/>
+@@ -27086,42 +27102,42 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1316"/>
+ <source>PLAYLIST</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">PLAYLIST</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1317"/>
+ <source>POSTPROCESSING OPTIONS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">OPZIONI DI POSTPROCESSO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1318"/>
+ <source>PROGRAM DETAILS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DETTAGLI PROGRAMMA</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1319"/>
+ <source>PROGRAM GUIDE</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GUIDA PROGRAMMI</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1320"/>
+ <source>PROGRAM LIST</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">LISTA PROGRAMMI</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1321"/>
+ <source>PROGRAM SEARCH</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">RICERCA PROGRAMMI</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1322"/>
+ <source>Parental</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Parentale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1338"/>
+ <source>Parential level:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Livello parentale:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1340"/>
+@@ -27131,137 +27147,137 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1345"/>
+ <source>Perform daily updates of recording artwork:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiorna giornalmente le copertine delle registrazioni:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1346"/>
+ <source>Perform daily updates:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiorna ogni giorno:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1349"/>
+ <source>Perform query</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Esegui query</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1354"/>
+ <source>Pick a program from a set of lists</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli un programma da una serie di elenchi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1360"/>
+ <source>Pick programs to be recorded</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli i programmi da registrare</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1362"/>
+ <source>Pick the letter in which the show starts with, then press SELECT or the right arrow</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scegli la lettera con cui inizia lo spettacolo, quindi premi SELEZIONA o la freccia destra</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1367"/>
+ <source>Play Back Group:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci gruppo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1368"/>
+ <source>Play Back Group: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci gruppo: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1369"/>
+ <source>Play Blu-ray and DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci Blu-ray e DVD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1380"/>
+ <source>Play a film on</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci film su</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1381"/>
+ <source>Play a film on Blu-ray Disk</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci film su disco Blu-ray</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1383"/>
+ <source>Play a film on a DVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci film su DVD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1385"/>
+ <source>Play any of your recorded programs</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci uno dei programmi registrati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1387"/>
+ <source>Play group:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci gruppo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1389"/>
+ <source>Play next</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci seguente</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1397"/>
+ <source>Play your video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduci video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1401"/>
+ <source>Playback starting ...</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvio riproduzione ...</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1403"/>
+ <source>Player</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduttore</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1405"/>
+ <source>Player CMD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduttore CMD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1409"/>
+ <source>Player:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riproduttore:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1410"/>
+ <source>Playlist duration:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata playlist:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1411"/>
+ <source>Playlist item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elemento playlist:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1412"/>
+ <source>Playlist position:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Posizione playlist:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1413"/>
+ <source>Playlist progress:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avanzamento playlist:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1414"/>
+ <source>Playlist: %playlisttime%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Playlist: %playlisttime%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1416"/>
+ <source>Please Wait....</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Attendere ...</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1420"/>
+ <source>Plot: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Traccia: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1422"/>
+@@ -27271,17 +27287,17 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1424"/>
+ <source>Post Process Editor</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor di post-elaborazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1428"/>
+ <source>Post Processing Options Editor</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Opzioni editor di post-elaborazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1429"/>
+ <source>Post processing</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Post-elaborazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1430"/>
+@@ -27291,42 +27307,42 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1431"/>
+ <source>Powersearch</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca-avanzata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1432"/>
+ <source>PreRecorded</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">PreRegistrato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1433"/>
+ <source>Press 'up', 'down, 'left' and 'right' arrows to move through the files and folders. Press 'select' or 'enter' key to select the file or folder.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Premi le frecce "su", "giù,"sinistra" e "destra"per spostarti tra i file e le cartelle. Premi il tasto "seleziona" o "invio" per selezionare il file o la cartella.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1441"/>
+ <source>Preview Schedule Changes:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Anteprima modifiche programmazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1444"/>
+ <source>Priorities</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Priorità</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1451"/>
+ <source>Priority: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Priorità: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1452"/>
+ <source>Priority: %finalpriority%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Priorità: %finalpriority%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1453"/>
+ <source>Priority: %priority%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Priorità: %priority%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1455"/>
+@@ -27336,7 +27352,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1456"/>
+ <source>Profile:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Profilo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1457"/>
+@@ -27346,22 +27362,22 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1462"/>
+ <source>Program Guide Video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guida ai programmi Video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1466"/>
+ <source>Program Recording Priorities</source>
+- <translation>Priorità di registrazione del programma</translation>
++ <translation type="unfinished">Priorità registrazione del programma</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1468"/>
+ <source>Program Searches</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerche programmi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1470"/>
+ <source>Programid</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Programid</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1472"/>
+@@ -27371,12 +27387,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1475"/>
+ <source>Protocol: %PROTOCOLVERSION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Protocollo: %PROTOCOLVERSION%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1476"/>
+ <source>Quality:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Qualità:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1477"/>
+@@ -27391,105 +27407,105 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1480"/>
+ <source>Radio</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Radio</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../themes/themestrings.h" line="1482"/>
+ <source>Raise recording priority by %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>Aumenta priorità registrazione di %n</numerusform>
++ <numerusform>Aumenta priorità registrazione di %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1484"/>
+ <source>Rated: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutato: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1486"/>
+ <source>Rating</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1488"/>
+ <source>Ratings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1489"/>
+ <source>Ratings:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazioni:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1491"/>
+ <source>Read the</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Leggi il</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1492"/>
+ <source>Read the news online</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Leggi notizie online</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1494"/>
+ <source>Rec</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Rec</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1499"/>
+ <source>Recipe</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricetta</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1503"/>
+ <source>Record Group:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gruppo registrazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1504"/>
+ <source>Record Profile:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Profilo registrazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1505"/>
+ <source>Record Type:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tipo registrazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1508"/>
+ <source>Recording Group</source>
+- <translation>Gruppo di registrazione</translation>
++ <translation type="unfinished">Gruppo registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1510"/>
+ <source>Recording Group: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gruppo registrazione: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1511"/>
+ <source>Recording Groups</source>
+- <translation>Gruppi di registrazione</translation>
++ <translation type="unfinished">Gruppi registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1513"/>
+ <source>Recording Priority</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Priorità registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1515"/>
+ <source>Recording Profile: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Profilo registrazione: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1517"/>
+ <source>Recording Rule Active:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regola registrazione attiva:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1520"/>
+ <source>Recording Status</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1521"/>
+@@ -27499,22 +27515,22 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1522"/>
+ <source>Recording details</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1523"/>
+ <source>Recording group:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gruppo registrazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1524"/>
+ <source>Recording groups</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gruppi registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1525"/>
+ <source>Recording has Subtitles Available</source>
+- <translation>La registrazione ha sottotitoli disponibili</translation>
++ <translation type="unfinished">Registrazione con sottotitoli disponibili</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1526"/>
+@@ -27564,7 +27580,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1535"/>
+ <source>Recording is in WideScreen</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">La registrazione è WideScreen</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1536"/>
+@@ -27574,7 +27590,7 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1537"/>
+ <source>Recording is preserved</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Registrazione mantenuta</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1538"/>
+@@ -27590,55 +27606,56 @@ Do you wish to continue watching?</source>
+ <location filename="../themes/themestrings.h" line="1541"/>
+ <source>Reduce recording priority by %n</source>
+ <translation type="unfinished">
+- <numerusform></numerusform>
+- <numerusform></numerusform>
++ <numerusform>Riduci priorità registrazione di %n</numerusform>
++ <numerusform>Riduci priorità registrazione di %n</numerusform>
+ </translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1543"/>
+ <source>Remote</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Telecomando</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1545"/>
+ <source>Resolution %RESOLUTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risoluzione %RESOLUTION%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1546"/>
+ <source>Resolution Unknown</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risoluzione sconosciuta</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1547"/>
+ <source>Resolution:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risoluzione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1548"/>
+ <source>Resolution: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risoluzione: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1550"/>
+ <source>Resolution: %resolution% - %aspect|
+ %%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Risoluzione: %resolution% - %aspect|
++%%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1559"/>
+ <source>Retry</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riprova</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1560"/>
+ <source>Rewind</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Riavvolgi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1561"/>
+ <source>Rip/Transcode</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Copia/Transcodifica</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1562"/>
+@@ -27648,62 +27665,62 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1563"/>
+ <source>Rule Clause:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Clausola regola:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1567"/>
+ <source>Rule active:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regola attiva:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1568"/>
+ <source>Rules</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regole</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1574"/>
+ <source>Run user job four</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente quattro</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1575"/>
+ <source>Run user job one</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente uno</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1576"/>
+ <source>Run user job three</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente tre</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1577"/>
+ <source>Run user job two</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente due</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1578"/>
+ <source>Run userjob #1:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente #1:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1579"/>
+ <source>Run userjob #2:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente #2:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1580"/>
+ <source>Run userjob #3:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente #3:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1581"/>
+ <source>Run userjob #4:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvia processo utente #4:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1585"/>
+ <source>Runtime: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1586"/>
+@@ -27713,62 +27730,62 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1587"/>
+ <source>SCHEDULE EDITOR</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">EDITOR PROGRAMMAZIONE</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1588"/>
+ <source>SCHEDULE FILTERS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">FILTRI PROGRAMMAZIONE</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1589"/>
+ <source>SCHEDULE OPTIONS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">OPZIONI PROGRAMMAZIONE</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1590"/>
+ <source>SD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">SD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1591"/>
+ <source>SEARCH</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">RICERCA</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1592"/>
+ <source>STEREO</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">STEREO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1593"/>
+ <source>STORAGE OPTIONS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">OPZIONI ARCHIVIAZIONE</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1594"/>
+ <source>SUB</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">SUB</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1595"/>
+ <source>SUBTITLES</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">SOTTOTITOLI</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1596"/>
+ <source>SURROUND</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">SURROUND</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1600"/>
+ <source>Sample rate:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Freq. campionamento:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1601"/>
+ <source>Sample rate: %samplerate%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Freq. campionamento: %samplerate%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1604"/>
+@@ -27788,232 +27805,232 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1608"/>
+ <source>Scanning %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Scansiono %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1612"/>
+ <source>Schedule Filter Editor</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Editor filtri programmazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1618"/>
+ <source>Schedule Rule:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regola programmazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1623"/>
+ <source>Schedule info</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Info programmazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1624"/>
+ <source>Schedule programs</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Pianifica programmi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1626"/>
+ <source>Screen</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Schermo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1628"/>
+ <source>Screen setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Configura schermo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1630"/>
+ <source>Screenshot Image:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine schermo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1632"/>
+ <source>Screenshot: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Schermata: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1635"/>
+ <source>Search Field:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Campo ricerca:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1642"/>
+ <source>Search Local</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca locale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1643"/>
+ <source>Search Net</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca su Net</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1648"/>
+ <source>Search Terms:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca termini:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1649"/>
+ <source>Search Times</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca orari</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1650"/>
+ <source>Search Video</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1653"/>
+ <source>Search by</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca per</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1654"/>
+ <source>Search by alphabet</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca alfabetica</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1655"/>
+ <source>Search by category</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca per categoria</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1656"/>
+ <source>Search by channel</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerca per canale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1657"/>
+ <source>Search files on your harddisk</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca file sul tuo harddisk</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1659"/>
+ <source>Search item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elemento di ricerca:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1660"/>
+ <source>Search on</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca per</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1661"/>
+ <source>Search on broadcast times</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca in orari trasmissione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1662"/>
+ <source>Search on keywords</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca per parole chiave</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1663"/>
+ <source>Search on people</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca persone</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1664"/>
+ <source>Search on titles</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca nei titoli</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1665"/>
+ <source>Search or browse popular video sites</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca o esplora siti di video popolari</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1667"/>
+ <source>Search programs using sorted lists</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca programmi utilizzando elenchi ordinati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1668"/>
+ <source>Search programs using words</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca programmi con le parole</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1670"/>
+ <source>Search the local network for the MythTV Backend.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca nella rete locale il backend MythTV.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1672"/>
+ <source>Searches</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ricerche</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1673"/>
+ <source>Searchlist item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elemento di ricerca:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1676"/>
+ <source>Season and Episode</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stagione e Episodio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1677"/>
+ <source>Season and Episode:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stagione e Episodio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1680"/>
+ <source>Season/Episode: %s00e00% ProgramID: %PROGRAMID% SeriesID: %SERIESID%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stagione/Episodio: %s00e00% IDprogramma: %PROGRAMID% IDserie: %SERIESID%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1684"/>
+ <source>See what is previously</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guarda cosa c'è prima</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1685"/>
+ <source>See what is previously recorded</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guarda cosa è stato registrato in precedenza</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1690"/>
+ <source>Seek Amount</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Entità ricerca</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1691"/>
+ <source>Seek Back</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca Indietro</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1692"/>
+ <source>Seek Forward</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Cerca Avanti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1694"/>
+ <source>Select 'Program Guide' or 'Program Finder'</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona 'Guida programmi' o 'Trova programmi'</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1695"/>
+ <source>Select 'Schedule a Recording' from the Main Menu</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona 'Registra' dal menu principale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1696"/>
+ <source>Select Date:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona data:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1697"/>
+ <source>Select Event:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona evento:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1698"/>
+ <source>Select Icons</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona icone</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1706"/>
+ <source>Select Time:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona ora:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1709"/>
+@@ -28028,47 +28045,47 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1711"/>
+ <source>Select a recording to permanently erase.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona registrazione da cancellare definitivamente.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1712"/>
+ <source>Select a recording to watch.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona registrazione da vedere.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1713"/>
+ <source>Select a recording to watch:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona registrazione da vedere:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1715"/>
+ <source>Select an option below.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona un'opzione di seguito.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1718"/>
+ <source>Select country and language</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona paese e lingua</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1720"/>
+ <source>Select item to be searched for from the list:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona elemento di ricerca dall'elenco:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1721"/>
+ <source>Select recording rule:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona regola registrazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1723"/>
+ <source>Select the country where you reside.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona la Nazione in cui risiedi.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1729"/>
+ <source>Select the recording rule:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona regola di registrazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1731"/>
+@@ -28078,47 +28095,47 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1732"/>
+ <source>Select your language.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona la lingua.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1734"/>
+ <source>Select your theme</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona il tema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1736"/>
+ <source>Select:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Seleziona:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1737"/>
+ <source>Selected Image</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Immagine selezionata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1740"/>
+ <source>Send and receive</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Invia e ricevi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1743"/>
+ <source>Set Priorities for</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta priorità per</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1744"/>
+ <source>Set Priorities for channels</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta priorità per i canali</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1747"/>
+ <source>Set your recording</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1748"/>
+ <source>Set your recording priorities</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta le priorità registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1749"/>
+@@ -28128,137 +28145,137 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1750"/>
+ <source>Settings for</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Settings per</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1760"/>
+ <source>Setup Audio Configuration</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta configurazione audio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1761"/>
+ <source>Setup Custom Priorities rules</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta regole priorità personalizzate</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1762"/>
+ <source>Setup System event</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta evento sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1763"/>
+ <source>Setup System event handlers</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta gestori evento sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1764"/>
+ <source>Setup Video Configuration</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta configurazione video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1765"/>
+ <source>Setup Weather</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta meteo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1767"/>
+ <source>Setup behavior of the</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta comportamento del</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1768"/>
+ <source>Setup file associations for your videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta associazioni file dei tuoi video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1769"/>
+ <source>Setup filter for your video library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta filtro della tua libreria video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1770"/>
+ <source>Setup metadata for your video library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta metadati della tua libreria video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1771"/>
+ <source>Setup navigation and</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta navigazione e</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1772"/>
+ <source>Setup player to play your videos</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta lettore riproduzione video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1773"/>
+ <source>Setup recording</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1774"/>
+ <source>Setup recordings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta registrazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1775"/>
+ <source>Setup recordings profiles</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta profili registrazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1776"/>
+ <source>Setup the behavior of the system events</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta comportamento eventi sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1777"/>
+ <source>Setup your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta il tuo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1778"/>
+ <source>Setup your Movie</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta il film</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1779"/>
+ <source>Setup your system</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta il sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1780"/>
+ <source>Setup your system events</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta eventi sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1782"/>
+ <source>Share your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Condividi il tuo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1783"/>
+ <source>Share your hardware profile</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Condividi il tuo profilo hardware</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1797"/>
+ <source>Show files that are marked as hidden:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra i file nascosti:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1798"/>
+ <source>Show:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Mostra:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1801"/>
+ <source>Shutting Down!</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Arresto in corso!</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1802"/>
+ <source>Signal / Noise</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Segnale/Rumore</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1803"/>
+@@ -28273,192 +28290,192 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1805"/>
+ <source>Site item:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Elemento del sito:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1806"/>
+ <source>Skin</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Skin</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1807"/>
+ <source>Skins</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Skin</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1809"/>
+ <source>Sorry, No Recordings Available</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Spiacente, nessuna registrazione disponibile</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1818"/>
+ <source>Sort: (1) by Title, (2) by Priority, (4) by Type.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ordina: (1) per titolo, (2) per priorità, (4) per tipo.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1819"/>
+ <source>Sorting order of the shown images:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ordinamento immagini visualizzate:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1822"/>
+ <source>Sources</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sorgenti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1823"/>
+ <source>Sources for metadata grabbers</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sorgenti dei grabber metadati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1824"/>
+ <source>Space</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Spazio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1825"/>
+ <source>Sports running</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Corsa sportiva</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1827"/>
+ <source>Standby Accessing Database</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Database di accesso in standby</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1829"/>
+ <source>Star rating</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1830"/>
+ <source>Starring</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Protagonista</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1831"/>
+ <source>Starring:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Protagonista:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1833"/>
+ <source>Stars</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stelle</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1834"/>
+ <source>Stars:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stelle:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1835"/>
+ <source>Start Date:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Data inizio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1837"/>
+ <source>Start Hour:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ora inizio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1838"/>
+ <source>Start Minute:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Minuto inizio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1843"/>
+ <source>Start early:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Anticipo avvio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1849"/>
+ <source>Start time:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ora inizio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1850"/>
+ <source>Start: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvio: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1853"/>
+ <source>Stations</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1857"/>
+ <source>Status:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1858"/>
+ <source>Status: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1859"/>
+ <source>Status: %rectypechar%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato: %rectypechar%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1861"/>
+ <source>Storage</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1862"/>
+ <source>Storage Details</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli archiviazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1864"/>
+ <source>Storage Group: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gruppo archiviazione: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1867"/>
+ <source>Storage group:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Gruppo archiviazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1868"/>
+ <source>Storage rate:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Velocità archiviazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1871"/>
+ <source>Storage to Buffer: %storagerate%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivia su buffer:%storagerate%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1872"/>
+ <source>Storage:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Archivio:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1874"/>
+ <source>Store Rule</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regola salvataggio</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1875"/>
+ <source>Stored</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Salvato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1877"/>
+ <source>Streams</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stream</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1879"/>
+ <source>Sub Title:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sottotitolo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1882"/>
+ <source>Submit system Profile</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Invia profilo sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1886"/>
+@@ -28468,72 +28485,72 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1887"/>
+ <source>Subtitles:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sottotitoli:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1893"/>
+ <source>Syncing image %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sincronizzazione immagine %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1894"/>
+ <source>System</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1898"/>
+ <source>System Info</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Informazioni sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1900"/>
+ <source>System status</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Stato sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1901"/>
+ <source>T: 1%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">T: 1%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1905"/>
+ <source>TMDB/TVDB ID:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">TMDB/TVDB ID:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1907"/>
+ <source>TRANSCODING</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">TRANSCODFICO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1910"/>
+ <source>TV Guide</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guida TV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1913"/>
+ <source>TV Setup</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Imposta TV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1914"/>
+ <source>TV Shows</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">TV Show</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1917"/>
+ <source>TVGuide</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GuidaTV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1924"/>
+ <source>Terminal</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Terminale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1929"/>
+ <source>Test Rule</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Regola di test</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1931"/>
+@@ -28548,12 +28565,12 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1938"/>
+ <source>The recording schedule would not be affected.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">La registrazione programmi non verrà interessata.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1941"/>
+ <source>There are no Recordings Scheduled...</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non ci sono registrazioni programmate...</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1942"/>
+@@ -28563,87 +28580,87 @@ Do you wish to continue watching?</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="1950"/>
+ <source>This is a children's Christmas version of the popular MythCenter-wide theme with festive backgrounds and colour scheme.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Questa è una versione natalizia per bambini del popolare tema MythCenter con sfondi festivi e combinazioni di colori.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1957"/>
+ <source>Time to display each image during a slideshow (ms):</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata visualizzazione di ciascuna immagine durante la presentazione (ms):</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1958"/>
+ <source>Time:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Durata:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1962"/>
+ <source>Title 1 of 1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Titolo 1 di 1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1965"/>
+ <source>To be determined</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Da definire</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1973"/>
+ <source>To schedule a recording, exit this screen and</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Per programmare una registrazione, esci da questa videata e</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1974"/>
+ <source>Tools</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Strumenti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1975"/>
+ <source>Tools for your music library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Strumenti per la libreria musicale</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1976"/>
+ <source>Track: %time%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Traccia:%time%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1980"/>
+ <source>Trailer: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trailer: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1982"/>
+ <source>Trans</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trans</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1984"/>
+ <source>Transcode new recordings:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Transcodifica nuove registrazioni:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1985"/>
+ <source>Transcoding...</source>
+- <translation>In transcodifica...</translation>
++ <translation type="unfinished">Transcodifica...</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2253"/>
+ <source>upcoming recordings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">prossime registrazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2254"/>
+ <source>widescreen</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">widescreen</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2255"/>
+ <source>year</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Anno</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2256"/>
+ <source>~</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">~</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1311"/>
+@@ -29930,19 +29947,21 @@ Una volta completate le modifiche, premi il pulsante "OK".</translatio
+ <message>
+ <location filename="../themes/themestrings.h" line="2181"/>
+ <source>XXXXXX %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">XXXXXX %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2187"/>
+ <source>You Haven't Scheduled Any Programs To Be Recorded!
+ To schedule a recording, exit this screen and select 'Program Guide' or 'Program Finder'.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non hai pianificato nessun programma da registrare!
++Per programmare una registrazione, esci da questa videata e seleziona 'Guida programmi' o 'Cerca programmi'.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2194"/>
+ <source>You Haven't Scheduled Any Programs To Be Recorded
+ To schedule a recording, exit this screen and select 'Program Guide' or 'Program Finder' </source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non hai pianificato alcun programma da registrare!
++Per programmare una registrazione, esci da questa videata e seleziona 'Guida programmi' o 'Cerca programmi' </translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2195"/>
+@@ -29950,58 +29969,60 @@ To schedule a recording, exit this screen and select 'Program Guide' o
+
+ To schedule a recording, exit this screen and
+ Select 'Program Guide' or 'Program Finder'</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Per programmare una registrazione, esci da questa videata e
++seleziona 'Guida programmi' o 'Cerca programmi'</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2198"/>
+ <source>You haven't made any changes</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non hai apportato modifiche</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2199"/>
+ <source>You haven't made any changes in your Schedule.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non hai apportato modifiche alla programmazione.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2200"/>
+ <source>You may enter a value here or cancel to continue using the Spinbox.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Puoi immettere un valore qui o annullare e continuare ad utilizzare la casella di selezione.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2205"/>
+ <source>Zone Minder</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Zone Minder</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2209"/>
+ <source>[</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2210"/>
+ <source>[%statusbefore% >> %statusafter%]</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[%statusbefore% >> %statusafter%]</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2211"/>
+ <source>[O]k</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[O]k</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2212"/>
+ <source>[P]robe</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[P]robe</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2213"/>
+ <source>[font]myverysmalllabel[/font]%(|s00e00|) %%|RATING| %%|(STARS|) %[font]mydesc[/font]%DESCRIPTION%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[font]myverysmalllabel[/font]%(|s00e00|) %%|RATING| %%|(STARS|) %[font]mydesc[/font]%DESCRIPTION%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2214"/>
+ <source>[font]myverysmalllabel[/font]%(|s00e00|) %%|RATING| %%|(STARS|) %[font]mydesc[/font]%DESCRIPTION%%
+ [font]myverysmallsub[/font]|CAST| %</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[font]myverysmalllabel[/font]%(|s00e00|) %%|RATING| %%|(STARS|) %[font]mydesc[/font]%DESCRIPTION%%
++[font]myverysmallsub[/font]|CAST| %</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2215"/>
+@@ -30010,28 +30031,33 @@ Select 'Program Guide' or 'Program Finder'</source>
+ [font]myverysmallsub[/font]Part |PARTNUMBER| of %%PARTTOTAL%%
+ [font]myverysmallsub[/font]|CAST| %%[font]myverysmallsub[/font]
+ [font]myverysmalllabel[/font]Director: |DIRECTOR| %</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[font]myverysmalllabel[/font]%(|s00e00|) %%|RATING| %%|(STARS|) %[font]myverysmall[/font]%DESCRIPTION%%
++
++[font]myverysmallsub[/font]Part |PARTNUMBER| of %%PARTTOTAL%%
++[font]myverysmallsub[/font]|CAST| %%[font]myverysmallsub[/font]
++[font]myverysmalllabel[/font]Director: |DIRECTOR| %</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2216"/>
+ <source>[font]myverysmalllabel[/font]%|RATING| %%|(STARS|) %[font]mydesc[/font]%DESCRIPTION%%
+ [font]myverysmallsub[/font]|CAST| %</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">[font]myverysmalllabel[/font]%|RATING| %%|(STARS|) %[font]mydesc[/font]%DESCRIPTION%%
++[font]myverysmallsub[/font]|CAST| %</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2217"/>
+ <source>]</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">]</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2218"/>
+ <source>] =</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">] =</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2220"/>
+ <source>album</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">album</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2221"/>
+@@ -30041,22 +30067,22 @@ Select 'Program Guide' or 'Program Finder'</source>
+ <message>
+ <location filename="../themes/themestrings.h" line="2223"/>
+ <source>browse your Pictures library</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">sfoglia libreria immagini</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2224"/>
+ <source>callsign: %callsign%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">nome can.: %callsign%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2225"/>
+ <source>dddd, MMMM dd, yyyy | hh:mm AP</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">gggg, MMMM gg, aaaa | hh:mm AP</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2226"/>
+ <source>dddd, MMMM dd, yyyy |</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">gggg, MMMM gg, aaaa |</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2228"/>
+@@ -30749,52 +30775,52 @@ Regista: |DIRECTOR%</translation>
+ <message>
+ <location filename="../themes/themestrings.h" line="2149"/>
+ <source>Watch your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guarda il</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2152"/>
+ <source>Watched Programs</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Programmi già visti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2153"/>
+ <source>Watched programs</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Programmi già visti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2155"/>
+ <source>WatchingBD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">VisioneBD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2156"/>
+ <source>WatchingDVD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">VisioneDVD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2157"/>
+ <source>WatchingLiveTM</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">VisioneLiveTM</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2158"/>
+ <source>WatchingPreRecorded</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">VisionePreRegistrato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2159"/>
+ <source>WatchingRecording</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">VisioneRegistrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2160"/>
+ <source>WatchingVideo</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">VisioneVideo</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2161"/>
+ <source>Watchlist</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Watchlist</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2169"/>
+@@ -32199,112 +32225,112 @@ dd MMM yyyy</translation>
+ <message>
+ <location filename="../themes/themestrings.h" line="1986"/>
+ <source>Transports</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Trasporti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1988"/>
+ <source>Try again to establish connections.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Prova di nuovo a stabilire le connessioni.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1989"/>
+ <source>Tweets</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tweet</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1994"/>
+ <source>Type of transition between two images:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tipo di transizione tra due immagini:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1996"/>
+ <source>Types</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Tipi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1997"/>
+ <source>Unable to connect to Database.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impossibile connettersi al database.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="1998"/>
+ <source>Unable to connect to master backend.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Impossibile connettersi al master backend.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2001"/>
+ <source>Unknown</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Sconosciuto</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2002"/>
+ <source>Unmute</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Audio ON</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2005"/>
+ <source>Up to date</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggionato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2006"/>
+ <source>UpToDate</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggionato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2007"/>
+ <source>Upcoming</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Prossimi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2010"/>
+ <source>Update available</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiornamento disponibile</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2012"/>
+ <source>Update your channels</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiorna canali</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2014"/>
+ <source>UpdateAvailable</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggiornamento disponibile</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2015"/>
+ <source>Upload your hardware</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Carica il tuo hardware</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2016"/>
+ <source>Upload your hardware profile</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Carica il tuo profilo hardware</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2017"/>
+ <source>Uptodate</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Aggionato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2018"/>
+ <source>Usage:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Utilizzo:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2019"/>
+ <source>Use Database setup to enter database parameters.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Usa Configura database per inserire i parametri del database.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2024"/>
+ <source>Use music</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Usa musica</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2025"/>
+ <source>Use the</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Usa il</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2029"/>
+@@ -32314,162 +32340,162 @@ dd MMM yyyy</translation>
+ <message>
+ <location filename="../themes/themestrings.h" line="2034"/>
+ <source>UserRating:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2035"/>
+ <source>Userrating:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valutazione:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2040"/>
+ <source>VCD Drive:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Unità VCD:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2041"/>
+ <source>VCD Player:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Lettore VCD:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2043"/>
+ <source>VIDEO BROWSER</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">BROWSER VIDEO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2044"/>
+ <source>VIDEO GALLERY</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GALLERIA VIDEO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2045"/>
+ <source>VIDEO LIST</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">ELENCO VIDEO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2046"/>
+ <source>VIDEO MANAGER</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GESTORE VIDEO</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2047"/>
+ <source>VRC-Like recording scheduler</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Prograzione registrazioni simil-VRC</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2048"/>
+ <source>Values from %LOW% to %HIGH% in increments of %STEP%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Valori da %LOW% a %HIGH% con incrementi di %STEP%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2053"/>
+ <source>Video #:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Video #:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2054"/>
+ <source>Video #: %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Video #: %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2056"/>
+ <source>Video Browse View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista navigazione video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2063"/>
+ <source>Video Gallery View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista galleria video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2066"/>
+ <source>Video List View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista elenco video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2067"/>
+ <source>Video Manage View</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vista gestione video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2074"/>
+ <source>Video Res:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Ris. Video:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2078"/>
+ <source>Video details</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Dettagli video</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2079"/>
+ <source>Video sites you can</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Siti di video che puoi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2080"/>
+ <source>Video#:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Video#:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2081"/>
+ <source>Video: %VIDEOWIDTH%x%VIDEOHEIGHT%@%VIDEOFRAMERATE%fps</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Video: %VIDEOWIDTH%x%VIDEOHEIGHT%@%VIDEOFRAMERATE%fps</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2082"/>
+ <source>VideoCodec: H.263</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CodecVideo: H.263</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2083"/>
+ <source>VideoCodec: H.264</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CodecVideo: H.264</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2084"/>
+ <source>VideoCodec: MPEG-2</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CodecVideo: MPEG-2</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2085"/>
+ <source>VideoCodec: MPEG-4</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CodecVideo: MPEG-4</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2086"/>
+ <source>VideoCodec: RTjpeg</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CodecVideo: RTjpeg</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2087"/>
+ <source>VideoCodec: VC-1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CodecVideo: VC-1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2088"/>
+ <source>VideoCodec: WMV3</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">CodecVideo: WMV3</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2089"/>
+ <source>VideoDesc: HD_1080_I</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DescVideo: HD_1080_I</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2090"/>
+ <source>VideoDesc: HD_1080_P</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DescVideo: HD_1080_P</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2091"/>
+ <source>VideoDesc: HD_720_P</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DescVideo: HD_720_P</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2092"/>
+ <source>VideoDesc: SD</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">DescVideo: SD</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2093"/>
+@@ -33223,107 +33249,107 @@ Se si seleziona "Usa riproduttore predefinito" il riproduttore del let
+ <message>
+ <location filename="../themes/themestrings.h" line="2105"/>
+ <source>View all</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi tutti</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2106"/>
+ <source>View all movies</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi tutti i film</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2107"/>
+ <source>View all searches stored</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi tutte le ricerche salvate</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2110"/>
+ <source>View new</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi nuovi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2111"/>
+ <source>View new titles</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi i titoli nuovi</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2112"/>
+ <source>View stored</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi salvati</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2113"/>
+ <source>View system Profile</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi Profilo sistema</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2114"/>
+ <source>View the</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi i</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2116"/>
+ <source>View upcoming</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi prossime</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2117"/>
+ <source>View upcoming recordings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi prossime registrazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2119"/>
+ <source>View your</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi le tue</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2121"/>
+ <source>View your recording</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi le tue registrazioni</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2122"/>
+ <source>View your recording rules</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi le tue regole di registrazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2123"/>
+ <source>View:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Vedi:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2124"/>
+ <source>Viewing and TV</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Visualizzazione e TV</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2125"/>
+ <source>Visible</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Visibile</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2129"/>
+ <source>W</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">W</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2131"/>
+ <source>WATCH RECORDINGS</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">GUARDA REGISTRAZIONI</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2132"/>
+ <source>WATCHED</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">VISTE</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2135"/>
+ <source>Waiting for Database to start.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Attesa avvio Database.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2136"/>
+ <source>Waking up Database Machine.</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Avvio macchina del database.</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2137"/>
+@@ -33343,12 +33369,12 @@ Se si seleziona "Usa riproduttore predefinito" il riproduttore del let
+ <message>
+ <location filename="../themes/themestrings.h" line="2145"/>
+ <source>Watch list</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">In evidenza</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2146"/>
+ <source>Watch live</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guarda diretta</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2148"/>
+@@ -33627,80 +33653,83 @@ Se si seleziona "Usa riproduttore predefinito" il riproduttore del let
+ <message>
+ <location filename="../themes/themestrings.h" line="2229"/>
+ <source>decoded/free:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">decodificato/libero:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2231"/>
+ <source>h.264</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">h.264</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2233"/>
+ <source>hh:mm AP</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">hh:mm AP</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2234"/>
+ <source>key pressed:
+ %osd_number_entry%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">tasto premuto:
++%osd_number_entry%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2235"/>
+ <source>message:
+ %message_text%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">messaggio:
++%message_text%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2238"/>
+ <source>no info available</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">nessuna info disponibile</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2239"/>
+ <source>not recorded yet</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Non ancora registrato</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2240"/>
+ <source>ok</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">ok</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2242"/>
+ <source>publisher:</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">editore:</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2243"/>
+ <source>ratings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">valutazione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2244"/>
+ <source>recording group filter set on %1</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">filtro gruppo registrazione impostato su %1</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2246"/>
+ <source>save</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">salva</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2247"/>
+ <source>smart</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">inteligente</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2248"/>
+ <source>starring
+ %cast%</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">protagonisti
++%cast%</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2249"/>
+ <source>television</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">televisione</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2252"/>
+@@ -35107,17 +35136,17 @@ Per programmare una registrazione, esci da questa schermata e seleziona 'Gu
+ <message>
+ <location filename="../themes/themestrings.h" line="2173"/>
+ <source>Wizard</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Guidata</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2174"/>
+ <source>Wizard for main mythtv settings</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Procedura guidata impostazioni principali di mythtv</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2175"/>
+ <source>Words</source>
+- <translation type="unfinished"></translation>
++ <translation type="unfinished">Parole</translation>
+ </message>
+ <message>
+ <location filename="../themes/themestrings.h" line="2179"/>
+
+From 01a15fcf7c2c0bfee390f92199a450319dae7559 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sat, 23 Jan 2021 15:45:32 -0500
+Subject: [PATCH 133/165] Fix frontend setup showing musicbrainz as grabber for
+ movie metadata
+
+When a grabber does not return a type (movie or television), the system
+was assuming movie, so that the music grabber was incorrectly showing
+as a grabber for movies.
+
+(cherry picked from commit 03dd08c3eb6ad9998d19653e46236e0d2f9585fa)
+---
+ mythtv/libs/libmythmetadata/metadatagrabber.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythmetadata/metadatagrabber.cpp b/mythtv/libs/libmythmetadata/metadatagrabber.cpp
+index d9c0c1ebb7a..47e0f4d7d23 100644
+--- a/mythtv/libs/libmythmetadata/metadatagrabber.cpp
++++ b/mythtv/libs/libmythmetadata/metadatagrabber.cpp
+@@ -385,7 +385,7 @@ void MetaGrabberScript::ParseGrabberVersion(const QDomElement &item)
+ if (!m_typestring.isEmpty() && grabberTypeStrings.contains(m_typestring))
+ m_type = grabberTypeStrings[m_typestring];
+ else
+- m_type = kGrabberMovie;
++ m_type = kGrabberInvalid;
+
+ QDomElement accepts = item.firstChildElement("accepts");
+ if (!accepts.isNull())
+
+From 1aff6fd2db9258d7aab45c3f07f3442f17d04fdd Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Tue, 26 Jan 2021 12:32:54 -0500
+Subject: [PATCH 134/165] Modify tmdb3 grabber to support TV in addition to
+ Movies
+
+- Moved the logic of creating the xml files for MythTV into a new
+ module tmdb3/lookup.py.
+- Added support for TV series in new functions.
+- Changed the tmdb3.py and created tmdb3tv.py. Both are now small
+ frontends for the main logic that is in lookup.py
+- Improve python code using return instead of sys.exit()
+
+To enable this grabber, run in mythtv frontend
+"Setup" -> "Artwork and Data Sources"
+and select "TheMovieDB.org V3 television" for the default Television
+Grabber.
+
+(cherry picked from commit f53465de760c4f001b5933f7fd77af1e9312d6fe)
+---
+ .gitignore | 1 +
+ mythtv/bindings/python/tmdb3/tmdb3/lookup.py | 472 ++++++++++++++++++
+ .../bindings/python/tmdb3/tmdb3/tmdb_api.py | 42 +-
+ mythtv/bindings/python/tmdb3/tmdb3/util.py | 2 +-
+ .../programs/scripts/metadata/Movie/tmdb3.py | 347 +++----------
+ .../scripts/metadata/Television/tmdb3tv.py | 159 ++++++
+ 6 files changed, 719 insertions(+), 304 deletions(-)
+ create mode 100644 mythtv/bindings/python/tmdb3/tmdb3/lookup.py
+ create mode 100755 mythtv/programs/scripts/metadata/Television/tmdb3tv.py
+
+diff --git a/.gitignore b/.gitignore
+index edb01d85a7f..d3206e4af23 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -49,3 +49,4 @@ GPATH
+ GRTAGS
+ GTAGS
+ compile_commands.json
++__pycache__
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/lookup.py b/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
+new file mode 100644
+index 00000000000..89b1d74d5db
+--- /dev/null
++++ b/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
+@@ -0,0 +1,472 @@
++# -*- coding: UTF-8 -*-
++# ----------------------
++# Name: tmdb3.py
++# Python Script
++# Author: Raymond Wagner
++# Purpose: This python script is intended to translate lookups between the
++# TheMovieDB.org V3 API and MythTV's internal metadata format.
++# http://www.mythtv.org/wiki/MythVideo_Grabber_Script_Format
++# http://help.themoviedb.org/kb/api/about-3
++# Code was originally in metadata/Movie/tmdb3.py.
++# Moved here so it can be called for movies or TV
++#-----------------------
++__title__ = "TheMovieDB.org V3"
++__author__ = "Raymond Wagner, Roland Ernst"
++__version__ = "0.3.9"
++# 0.1.0 Initial version
++# 0.2.0 Add language support, move cache to home directory
++# 0.3.0 Enable version detection to allow use in MythTV
++# 0.3.1 Add --test parameter for proper compatibility with mythmetadatalookup
++# 0.3.2 Add --area parameter to allow country selection for release date and
++# parental ratings
++# 0.3.3 Use translated title if available
++# 0.3.4 Add support for finding by IMDB under -D (simulate previous version)
++# 0.3.5 Add debugging mode
++# 0.3.6 Add handling for TMDB site and library returning null results in
++# search. This should only need to be a temporary fix, and should be
++# 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
++# 0.3.8 Sort posters by system language or 'en', if not found for given language
++# 0.3.9 Support TV lookup
++
++# ~ 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] == 2:
++ sys.stdout.write(etostr)
++ else:
++ sys.stdout.write(etostr.decode())
++
++def timeouthandler(signal, frame):
++ raise RuntimeError("Timed out")
++
++def buildSingle(inetref, opts):
++ from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
++ from MythTV.tmdb3 import Movie, get_locale
++ from MythTV import VideoMetadata
++ from lxml import etree
++
++ import locale as py_locale
++ import re
++
++ if re.match('^0[0-9]{6}$', inetref):
++ movie = Movie.fromIMDB(inetref)
++ else:
++ movie = Movie(inetref)
++
++ tree = etree.XML(u'<metadata></metadata>')
++ mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
++ ['releasedate', 'releasedate'], ['tagline', 'tagline'],
++ ['description', 'overview'], ['homepage', 'homepage'],
++ ['userrating', 'userrating'], ['popularity', 'popularity'],
++ ['budget', 'budget'], ['revenue', 'revenue']]
++ m = VideoMetadata()
++ for i,j in mapping:
++ try:
++ if getattr(movie, j):
++ setattr(m, i, getattr(movie, j))
++ except TMDBRequestInvalid:
++ return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
++
++ if movie.title:
++ m.title = movie.title
++
++ 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)
++# if that is not part of the reply use whatever release date is first in list
++# 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 = 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.inetref = str(movie.id)
++ if movie.collection:
++ m.collectionref = str(movie.collection.id)
++ if m.releasedate:
++ m.year = m.releasedate.year
++ for country, release in releases:
++ if release.certification:
++ m.certifications[country] = release.certification
++ for genre in movie.genres:
++ m.categories.append(genre.name)
++ for studio in movie.studios:
++ m.studios.append(studio.name)
++ for country in movie.countries:
++ m.countries.append(country.name)
++ for cast in movie.cast:
++ d = {'name':cast.name, 'character':cast.character, 'department':'Actors',
++ 'job':'Actor', 'url':'http://www.themoviedb.org/people/{0}'.format(cast.id)}
++ if cast.profile: d['thumb'] = cast.profile.geturl()
++ m.people.append(d)
++ for crew in movie.crew:
++ d = {'name':crew.name, 'job':crew.job, 'department':crew.department,
++ 'url':'http://www.themoviedb.org/people/{0}'.format(crew.id)}
++ if crew.profile: d['thumb'] = crew.profile.geturl()
++ m.people.append(d)
++ for backdrop in movie.backdrops:
++ m.images.append({'type':'fanart', 'url':backdrop.geturl(),
++ 'thumb':backdrop.geturl(backdrop.sizes()[0]),
++ 'height':str(backdrop.height),
++ 'width':str(backdrop.width)})
++
++ # tmdb already sorts the posters by language
++ # if no poster of given language was found,
++ # try to sort by system language and then by language "en"
++ system_language = py_locale.getdefaultlocale()[0].split("_")[0]
++ locale_language = get_locale().language
++ if opts.debug:
++ print("system_language : ", system_language)
++ print("locale_language : ", locale_language)
++
++ loc_posters = movie.posters
++ if loc_posters[0].language != locale_language \
++ and locale_language != system_language:
++ if opts.debug:
++ print("1: No poster found for language '%s', trying to sort posters by '%s' :"
++ %(locale_language, system_language))
++ loc_posters = sorted(movie.posters,
++ key = lambda x: x.language==system_language, reverse = True)
++
++ if loc_posters[0].language != system_language \
++ and loc_posters[0].language != locale_language:
++ if opts.debug:
++ print("2: No poster found for language '%s', trying to sort posters by '%s' :"
++ %(system_language, "en"))
++ loc_posters = sorted(movie.posters,
++ key = lambda x: x.language=="en", reverse = True)
++
++ for poster in loc_posters:
++ if opts.debug:
++ print("Poster : ", poster.language, " | ", poster.userrating,
++ "\t | ", poster.geturl())
++ m.images.append({'type':'coverart', 'url':poster.geturl(),
++ 'thumb':poster.geturl(poster.sizes()[0]),
++ 'height':str(poster.height),
++ 'width':str(poster.width)})
++
++ tree.append(m.toXML())
++ return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
++
++def buildMovieList(query, opts):
++ # TEMPORARY FIX:
++ # replace all dashes from queries to work around search behavior
++ # as negative to all text that comes afterwards
++ query = query.replace('-',' ')
++
++ from MythTV.tmdb3 import searchMovie
++ from MythTV import VideoMetadata
++ from lxml import etree
++ results = iter(searchMovie(query))
++ tree = etree.XML(u'<metadata></metadata>')
++ mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
++ ['releasedate', 'releasedate'], ['tagline', 'tagline'],
++ ['description', 'overview'], ['homepage', 'homepage'],
++ ['userrating', 'userrating'], ['popularity', 'popularity']]
++
++ count = 0
++ while True:
++ try:
++ res = next(results)
++ except StopIteration:
++ # end of results
++ break
++ except IndexError:
++ # unexpected end of results
++ # we still want to return whatever we have so far
++ break
++
++ if res is None:
++ # faulty data, skip it and continue
++ continue
++
++ m = VideoMetadata()
++ for i,j in mapping:
++ if getattr(res, j):
++ setattr(m, i, getattr(res, j))
++ m.inetref = str(res.id)
++
++ if res.title:
++ m.title = res.title
++
++ #TODO:
++ # should releasedate and year be pulled from the country-specific data
++ # or should it be left to the default information to cut down on
++ # traffic from searches
++ if res.releasedate:
++ m.year = res.releasedate.year
++ if res.backdrop:
++ b = res.backdrop
++ m.images.append({'type':'fanart', 'url':b.geturl(),
++ 'thumb':b.geturl(b.sizes()[0])})
++ if res.poster:
++ p = res.poster
++ m.images.append({'type':'coverart', 'url':p.geturl(),
++ 'thumb':p.geturl(p.sizes()[0])})
++ tree.append(m.toXML())
++ count += 1
++ if count >= 60:
++ # page limiter, dont want to overload the server
++ break
++
++ return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
++
++def buildTVList(query, opts):
++ from MythTV.tmdb3 import searchSeries
++ from MythTV import VideoMetadata
++ from lxml import etree
++ from datetime import date
++
++ resultsx = searchSeries(query)
++ results = iter(resultsx) # searchSeries(query))
++ mapping = [['language', 'original_language'],
++ ['title', 'name'], ['inetref','id'],
++ ['collectionref','id'], ['description','overview'],
++ ['releasedate','first_air_date']]
++ tree = etree.XML(u'<metadata></metadata>')
++
++ count = 0
++ while True:
++ try:
++ res = next(results)
++ except StopIteration:
++ # end of results
++ break
++ except IndexError:
++ # unexpected end of results
++ # we still want to return whatever we have so far
++ break
++
++ if res is None:
++ # faulty data, skip it and continue
++ continue
++
++ m = VideoMetadata()
++ for i,j in mapping:
++ if getattr(res, j):
++ setattr(m, i, getattr(res, j))
++
++ # These need to be strings not ints
++ m.inetref = str(res.id)
++ m.collectionref = str(res.id)
++
++ if res.backdrop:
++ b = res.backdrop
++ m.images.append({'type':'fanart', 'url':b.geturl(),
++ 'thumb':b.geturl(b.sizes()[0])})
++ if res.poster:
++ p = res.poster
++ m.images.append({'type':'coverart', 'url':p.geturl(),
++ 'thumb':p.geturl(p.sizes()[0])})
++ tree.append(m.toXML())
++ count += 1
++ if count >= 60:
++ # page limiter, dont want to overload the server
++ break
++
++ return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
++
++# for buildEpisode, args are one of the following
++# - title subtitle (-N command line)
++# - inetref subtitle (-N command line)
++# - inetref season episode (-D command line)
++def buildEpisode(args, opts):
++
++ query = args[0]
++
++ from MythTV.tmdb3 import Series, Season, Episode
++ from MythTV import VideoMetadata
++ from lxml import etree
++ from MythTV.tmdb3 import searchSeries
++
++ if query.isnumeric():
++ inetref = query
++ else:
++ results = searchSeries(query)
++ series = results[0]
++ inetref = str(series.id)
++
++ series = Series(inetref)
++ season_number = series.number_of_seasons
++ episode_number = None
++ subtitle = None
++ if len(args) == 2:
++ subtitle = args[1]
++ elif len(args) == 3:
++ season_number = int(args[1])
++ episode_number = int(args[2])
++
++ episode = None
++ # process seasons backwards because it is more likely
++ # that you have a recent one than an old one
++ while season_number > 0:
++ season = Season(inetref,str(season_number))
++ if episode_number:
++ episode = season.episodes[episode_number]
++ break
++ for ep_num, ep in season.episodes.items():
++ if ep.name == subtitle:
++ episode = ep
++ episode_number = int(ep_num)
++ break
++ if episode:
++ break
++ season_number = season_number - 1
++
++ if not episode_number and not episode:
++ sys.stdout.write('ERROR: Episode not found: ' + str(args))
++ return 9
++
++ # reload episode with full details
++ episode = Episode(inetref,season_number,episode_number)
++
++ tree = etree.XML(u'<metadata></metadata>')
++ mapping = [['subtitle','name'],
++ ['description', 'overview'], ['season', 'season_number'],
++ ['episode', 'episode_number'], ['releasedate', 'air_date']]
++ m = VideoMetadata()
++ m.title = series.name
++ for i,j in mapping:
++ if getattr(episode, j):
++ setattr(m, i, getattr(episode, j))
++
++ # These need to be strings not ints
++ m.inetref = inetref
++ m.collectionref = inetref
++
++ for cast in episode.cast:
++ d = {'name':cast.name, 'character':cast.character, 'department':'Actors',
++ 'job':'Actor', 'url':'http://www.themoviedb.org/people/{0}'.format(cast.id)}
++ if cast.profile: d['thumb'] = cast.profile.geturl()
++ m.people.append(d)
++ for crew in episode.crew:
++ d = {'name':crew.name, 'job':crew.job, 'department':crew.department,
++ 'url':'http://www.themoviedb.org/people/{0}'.format(crew.id)}
++ if crew.profile: d['thumb'] = crew.profile.geturl()
++ m.people.append(d)
++ for guest in episode.guest_stars:
++ d = {'name':guest.name, 'job':"Guest Star",
++ 'url':'http://www.themoviedb.org/people/{0}'.format(guest.id)}
++ if guest.profile: d['thumb'] = guest.profile.geturl()
++ m.people.append(d)
++ if episode.still:
++ b = episode.still
++ m.images.append({'type':'screenshot', 'url':b.geturl(),
++ 'thumb':b.geturl(b.sizes()[0])})
++ if season.poster:
++ p = season.poster
++ m.images.append({'type':'coverart', 'url':p.geturl(),
++ 'thumb':p.geturl(p.sizes()[0])})
++ m.language = series.original_language
++ if series.backdrop:
++ b = series.backdrop
++ m.images.append({'type':'fanart', 'url':b.geturl(),
++ 'thumb':b.geturl(b.sizes()[0])})
++ for genre in series.genres:
++ m.categories.append(genre.name)
++
++ tree.append(m.toXML())
++
++ return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
++
++def buildCollection(inetref, opts):
++ from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
++ from MythTV.tmdb3 import Collection
++ from MythTV import VideoMetadata
++ from lxml import etree
++ collection = Collection(inetref)
++ tree = etree.XML(u'<metadata></metadata>')
++ m = VideoMetadata()
++ m.collectionref = str(collection.id)
++ try:
++ m.title = collection.name
++ except TMDBRequestInvalid:
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True))
++ sys.exit()
++ if collection.backdrop:
++ b = collection.backdrop
++ m.images.append({'type':'fanart', 'url':b.geturl(),
++ 'thumb':b.geturl(b.sizes()[0])})
++ if collection.poster:
++ p = collection.poster
++ m.images.append({'type':'coverart', 'url':p.geturl(),
++ 'thumb':p.geturl(p.sizes()[0])})
++ tree.append(m.toXML())
++ return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
++
++# For television only - get series info
++# Same results as for television search but only 1 result
++def buildTVSeries(inetref, opts):
++
++ from MythTV.tmdb3 import Series
++ from MythTV import VideoMetadata
++ from lxml import etree
++
++ series = Series(inetref)
++
++ mapping = [['language', 'original_language'],
++ ['title', 'name'], ['inetref','id'],
++ ['collectionref','id'], ['description','overview'],
++ ['releasedate','first_air_date']]
++ tree = etree.XML(u'<metadata></metadata>')
++ m = VideoMetadata()
++ for i,j in mapping:
++ if getattr(series, j):
++ setattr(m, i, getattr(series, j))
++
++ # These need to be strings not ints
++ m.inetref = str(series.id)
++ m.collectionref = str(series.id)
++
++ for genre in series.genres:
++ m.categories.append(genre.name)
++
++ if series.backdrop:
++ b = series.backdrop
++ m.images.append({'type':'fanart', 'url':b.geturl(),
++ 'thumb':b.geturl(b.sizes()[0])})
++ if series.poster:
++ p = series.poster
++ m.images.append({'type':'coverart', 'url':p.geturl(),
++ 'thumb':p.geturl(p.sizes()[0])})
++ tree.append(m.toXML())
++
++ return etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
++
++def buildVersion(showType, command):
++ from lxml import etree
++ version = etree.XML(u'<grabber></grabber>')
++ etree.SubElement(version, "name").text = __title__ + ' ' + showType
++ etree.SubElement(version, "author").text = __author__
++ etree.SubElement(version, "thumbnail").text = 'tmdb.png'
++ etree.SubElement(version, "command").text = command
++ etree.SubElement(version, "type").text = showType
++ etree.SubElement(version, "description").text = \
++ 'Search and metadata downloads for themoviedb.org'
++ etree.SubElement(version, "version").text = __version__
++ etree.SubElement(version, "accepts").text = 'tmdb.py'
++ etree.SubElement(version, "accepts").text = 'tmdb.pl'
++ return etree.tostring(version, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True)
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
+index d7897f302a3..44e1a35952f 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/tmdb_api.py
+@@ -21,7 +21,7 @@
+ Preliminary API specifications can be found at
+ http://help.themoviedb.org/kb/api/about-3"""
+
+-__version__ = "v0.7.0"
++__version__ = "v0.7.1"
+ # 0.1.0 Initial development
+ # 0.2.0 Add caching mechanism for API queries
+ # 0.2.1 Temporary work around for broken search paging
+@@ -62,6 +62,7 @@
+ # 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
++# 0.7.1 Changes to support TV series lookup.
+
+ from . import IS_PY2
+
+@@ -171,7 +172,7 @@ def __init__(self, request, locale=None):
+ locale = get_locale()
+ super(SeriesSearchResult, self).__init__(
+ request.new(language=locale.language),
+- lambda x: Series(raw=x, locale=locale))
++ lambda x: Series_base(raw=x, locale=locale))
+
+ def searchPerson(query, adult=False):
+ return PeopleSearchResult(Request('search/person', query=query,
+@@ -779,10 +780,12 @@ class Network(NameRepr,Element):
+ id = Datapoint('id', initarg=1)
+ name = Datapoint('name')
+
+-class Episode(NameRepr, Element):
+- episode_number = Datapoint('episode_number', initarg=3)
+- season_number = Datapoint('season_number', initarg=2)
++# This class excludes the _populate members because they are harmful
++# when doing a Season as they cause it to take forever.
++class Episode_base(NameRepr, Element):
+ series_id = Datapoint('series_id', initarg=1)
++ season_number = Datapoint('season_number', initarg=2)
++ episode_number = Datapoint('episode_number', initarg=3)
+ air_date = Datapoint('air_date', handler=process_date)
+ overview = Datapoint('overview')
+ name = Datapoint('name')
+@@ -791,16 +794,22 @@ class Episode(NameRepr, Element):
+ id = Datapoint('id')
+ production_code = Datapoint('production_code')
+ still = Datapoint('still_path', handler=Backdrop, raw=False, default=None)
++ guest_stars = Datalist('guest_stars', handler=Cast,
++ sort='order')
+
++class Episode(Episode_base):
+ def _populate(self):
+ return Request('tv/{0}/season/{1}/episode/{2}'.format(self.series_id, self.season_number, self.episode_number),
+ language=self._locale.language)
+-
+ def _populate_cast(self):
+ return Request('tv/{0}/season/{1}/episode/{2}/credits'.format(
+ self.series_id, self.season_number, self.episode_number),
+ language=self._locale.language)
++ cast = Datalist('cast', handler=Cast,
++ poller=_populate_cast, sort='order')
++ crew = Datalist('crew', handler=Crew, poller=_populate_cast)
+
++ # Items not currently used by tmdb3tv
+ def _populate_external_ids(self):
+ return Request('tv/{0}/season/{1}/episode/{2}/external_ids'.format(
+ self.series_id, self.season_number, self.episode_number))
+@@ -812,11 +821,6 @@ def _populate_images(self):
+ return Request('tv/{0}/season/{1}/episode/{2}/images'.format(
+ self.series_id, self.season_number, self.episode_number), **kwargs)
+
+- cast = Datalist('cast', handler=Cast,
+- poller=_populate_cast, sort='order')
+- guest_stars = Datalist('guest_stars', handler=Cast,
+- poller=_populate_cast, sort='order')
+- crew = Datalist('crew', handler=Crew, poller=_populate_cast)
+ imdb_id = Datapoint('imdb_id', poller=_populate_external_ids)
+ freebase_id = Datapoint('freebase_id', poller=_populate_external_ids)
+ freebase_mid = Datapoint('freebase_mid', poller=_populate_external_ids)
+@@ -825,20 +829,21 @@ def _populate_images(self):
+ stills = Datalist('stills', handler=Backdrop, poller=_populate_images, sort=True)
+
+ class Season(NameRepr, Element):
+- season_number = Datapoint('season_number', initarg=2)
+ series_id = Datapoint('series_id', initarg=1)
++ season_number = Datapoint('season_number', initarg=2)
+ id = Datapoint('id')
+ air_date = Datapoint('air_date', handler=process_date)
+ poster = Datapoint('poster_path', handler=Poster, raw=False, default=None)
+ overview = Datapoint('overview')
+ name = Datapoint('name')
+- episodes = Datadict('episodes', attr='episode_number', handler=Episode,
++ episodes = Datadict('episodes', attr='episode_number', handler=Episode_base,
+ passthrough={'series_id': 'series_id', 'season_number': 'season_number'})
+
+ def _populate(self):
+ return Request('tv/{0}/season/{1}'.format(self.series_id, self.season_number),
+ language=self._locale.language)
+
++ # Items not currently used by tmdb3tv
+ def _populate_images(self):
+ kwargs = {}
+ if not self._locale.fallthrough:
+@@ -856,9 +861,11 @@ def _populate_external_ids(self):
+ tvdb_id = Datapoint('tvdb_id', poller=_populate_external_ids)
+ tvrage_id = Datapoint('tvrage_id', poller=_populate_external_ids)
+
+-class Series(NameRepr, Element):
++# This class excludes the _populate members because they are harmful.
++# when doing a search as they cause the search to take forever.
++class Series_base(NameRepr, Element):
+ id = Datapoint('id', initarg=1)
+- backdrop = Datapoint('backdrop_path', handler=Backdrop, raw=False, default=None)
++ original_language = Datapoint('original_language')
+ authors = Datalist('created_by', handler=Person)
+ episode_run_times = Datalist('episode_run_time')
+ first_air_date = Datapoint('first_air_date', handler=process_date)
+@@ -873,6 +880,8 @@ class Series(NameRepr, Element):
+ number_of_episodes = Datapoint('number_of_episodes')
+ number_of_seasons = Datapoint('number_of_seasons')
+ overview = Datapoint('overview')
++ backdrop = Datapoint('backdrop_path', handler=Backdrop,
++ raw=False, default=None)
+ popularity = Datapoint('popularity')
+ status = Datapoint('status')
+ userrating = Datapoint('vote_average')
+@@ -881,10 +890,13 @@ class Series(NameRepr, Element):
+ networks = Datalist('networks', handler=Network)
+ seasons = Datadict('seasons', attr='season_number', handler=Season, passthrough={'id': 'series_id'})
+
++
++class Series(Series_base):
+ def _populate(self):
+ return Request('tv/{0}'.format(self.id),
+ language=self._locale.language)
+
++ # Items not currently used by tmdb3tv
+ def _populate_cast(self):
+ return Request('tv/{0}/credits'.format(self.id))
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/util.py b/mythtv/bindings/python/tmdb3/tmdb3/util.py
+index 5f3a9194ef4..c57726bb4f9 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/util.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/util.py
+@@ -157,7 +157,7 @@ def __get__(self, inst, owner):
+ if inst is None:
+ return self
+ if self.field not in inst._data:
+- if self.poller is None:
++ if self.poller is None or not callable(self.poller.func):
+ return None
+ self.poller.__get__(inst, owner)()
+ return inst._data[self.field]
+diff --git a/mythtv/programs/scripts/metadata/Movie/tmdb3.py b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+index d82fcd63991..abd67c82d07 100755
+--- a/mythtv/programs/scripts/metadata/Movie/tmdb3.py
++++ b/mythtv/programs/scripts/metadata/Movie/tmdb3.py
+@@ -3,277 +3,26 @@
+ # ----------------------
+ # Name: tmdb3.py
+ # Python Script
+-# Author: Raymond Wagner
+-# Purpose: This python script is intended to translate lookups between the
+-# TheMovieDB.org V3 API and MythTV's internal metadata format.
+-# http://www.mythtv.org/wiki/MythVideo_Grabber_Script_Format
+-# http://help.themoviedb.org/kb/api/about-3
+-#-----------------------
+-__title__ = "TheMovieDB.org V3"
+-__author__ = "Raymond Wagner, Roland Ernst"
+-__version__ = "0.3.8"
+-# 0.1.0 Initial version
+-# 0.2.0 Add language support, move cache to home directory
+-# 0.3.0 Enable version detection to allow use in MythTV
+-# 0.3.1 Add --test parameter for proper compatibility with mythmetadatalookup
+-# 0.3.2 Add --area parameter to allow country selection for release date and
+-# parental ratings
+-# 0.3.3 Use translated title if available
+-# 0.3.4 Add support for finding by IMDB under -D (simulate previous version)
+-# 0.3.5 Add debugging mode
+-# 0.3.6 Add handling for TMDB site and library returning null results in
+-# search. This should only need to be a temporary fix, and should be
+-# 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
+-# 0.3.8 Sort posters by system language or 'en', if not found for given language
++# Author: Peter Bennett
++# Purpose:
++# Frontend for the tmdb3 lookup.py script that now supports both Movies and
++# TV shows. This frontend supports Movies
++# Command example:
++# See help (-h) options
++#
++# Code that was originally here is now in tmdb3/lookup.py
++#
++# License:Creative Commons GNU GPL v2
++# (http://creativecommons.org/licenses/GPL/2.0/)
++# -------------------------------------
++#
++__title__ = "TheMovieDB.org for TV V3"
++__author__ = "Peter Bennett"
+
+ 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] == 2:
+- sys.stdout.write(etostr)
+- else:
+- sys.stdout.write(etostr.decode())
+-
+-def timeouthandler(signal, frame):
+- raise RuntimeError("Timed out")
+-
+-def buildSingle(inetref, opts):
+- from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
+- from MythTV.tmdb3 import Movie, get_locale
+- from MythTV import VideoMetadata
+- from lxml import etree
+-
+- import locale as py_locale
+- import re
+-
+- if re.match('^0[0-9]{6}$', inetref):
+- movie = Movie.fromIMDB(inetref)
+- else:
+- movie = Movie(inetref)
+-
+- tree = etree.XML(u'<metadata></metadata>')
+- mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
+- ['releasedate', 'releasedate'], ['tagline', 'tagline'],
+- ['description', 'overview'], ['homepage', 'homepage'],
+- ['userrating', 'userrating'], ['popularity', 'popularity'],
+- ['budget', 'budget'], ['revenue', 'revenue']]
+- m = VideoMetadata()
+- for i,j in mapping:
+- try:
+- if getattr(movie, j):
+- setattr(m, i, getattr(movie, j))
+- except TMDBRequestInvalid:
+- print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+- xml_declaration=True))
+- sys.exit()
+-
+- if movie.title:
+- m.title = movie.title
+-
+- 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)
+-# if that is not part of the reply use whatever release date is first in list
+-# 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 = 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.inetref = str(movie.id)
+- if movie.collection:
+- m.collectionref = str(movie.collection.id)
+- if m.releasedate:
+- m.year = m.releasedate.year
+- for country, release in releases:
+- if release.certification:
+- m.certifications[country] = release.certification
+- for genre in movie.genres:
+- m.categories.append(genre.name)
+- for studio in movie.studios:
+- m.studios.append(studio.name)
+- for country in movie.countries:
+- m.countries.append(country.name)
+- for cast in movie.cast:
+- d = {'name':cast.name, 'character':cast.character, 'department':'Actors',
+- 'job':'Actor', 'url':'http://www.themoviedb.org/people/{0}'.format(cast.id)}
+- if cast.profile: d['thumb'] = cast.profile.geturl()
+- m.people.append(d)
+- for crew in movie.crew:
+- d = {'name':crew.name, 'job':crew.job, 'department':crew.department,
+- 'url':'http://www.themoviedb.org/people/{0}'.format(crew.id)}
+- if crew.profile: d['thumb'] = crew.profile.geturl()
+- m.people.append(d)
+- for backdrop in movie.backdrops:
+- m.images.append({'type':'fanart', 'url':backdrop.geturl(),
+- 'thumb':backdrop.geturl(backdrop.sizes()[0]),
+- 'height':str(backdrop.height),
+- 'width':str(backdrop.width)})
+-
+- # tmdb already sorts the posters by language
+- # if no poster of given language was found,
+- # try to sort by system language and then by language "en"
+- system_language = py_locale.getdefaultlocale()[0].split("_")[0]
+- locale_language = get_locale().language
+- if opts.debug:
+- print("system_language : ", system_language)
+- print("locale_language : ", locale_language)
+-
+- loc_posters = movie.posters
+- if loc_posters[0].language != locale_language \
+- and locale_language != system_language:
+- if opts.debug:
+- print("1: No poster found for language '%s', trying to sort posters by '%s' :"
+- %(locale_language, system_language))
+- loc_posters = sorted(movie.posters,
+- key = lambda x: x.language==system_language, reverse = True)
+-
+- if loc_posters[0].language != system_language \
+- and loc_posters[0].language != locale_language:
+- if opts.debug:
+- print("2: No poster found for language '%s', trying to sort posters by '%s' :"
+- %(system_language, "en"))
+- loc_posters = sorted(movie.posters,
+- key = lambda x: x.language=="en", reverse = True)
+-
+- for poster in loc_posters:
+- if opts.debug:
+- print("Poster : ", poster.language, " | ", poster.userrating,
+- "\t | ", poster.geturl())
+- m.images.append({'type':'coverart', 'url':poster.geturl(),
+- 'thumb':poster.geturl(poster.sizes()[0]),
+- 'height':str(poster.height),
+- 'width':str(poster.width)})
+-
+- tree.append(m.toXML())
+- print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+- xml_declaration=True))
+- sys.exit()
+-
+-def buildList(query, opts):
+- # TEMPORARY FIX:
+- # replace all dashes from queries to work around search behavior
+- # as negative to all text that comes afterwards
+- query = query.replace('-',' ')
+-
+- from MythTV.tmdb3 import searchMovie
+- from MythTV import VideoMetadata
+- from lxml import etree
+- results = iter(searchMovie(query))
+- tree = etree.XML(u'<metadata></metadata>')
+- mapping = [['runtime', 'runtime'], ['title', 'originaltitle'],
+- ['releasedate', 'releasedate'], ['tagline', 'tagline'],
+- ['description', 'overview'], ['homepage', 'homepage'],
+- ['userrating', 'userrating'], ['popularity', 'popularity']]
+-
+- count = 0
+- while True:
+- try:
+- res = next(results)
+- except StopIteration:
+- # end of results
+- break
+- except IndexError:
+- # unexpected end of results
+- # we still want to return whatever we have so far
+- break
+-
+- if res is None:
+- # faulty data, skip it and continue
+- continue
+-
+- m = VideoMetadata()
+- for i,j in mapping:
+- if getattr(res, j):
+- setattr(m, i, getattr(res, j))
+- m.inetref = str(res.id)
+-
+- if res.title:
+- m.title = res.title
+-
+- #TODO:
+- # should releasedate and year be pulled from the country-specific data
+- # or should it be left to the default information to cut down on
+- # traffic from searches
+- if res.releasedate:
+- m.year = res.releasedate.year
+- if res.backdrop:
+- b = res.backdrop
+- m.images.append({'type':'fanart', 'url':b.geturl(),
+- 'thumb':b.geturl(b.sizes()[0])})
+- if res.poster:
+- p = res.poster
+- m.images.append({'type':'coverart', 'url':p.geturl(),
+- 'thumb':p.geturl(p.sizes()[0])})
+- tree.append(m.toXML())
+- count += 1
+- if count >= 60:
+- # page limiter, dont want to overload the server
+- break
+-
+- print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+- xml_declaration=True))
+- sys.exit(0)
+-
+-def buildCollection(inetref, opts):
+- from MythTV.tmdb3.tmdb_exceptions import TMDBRequestInvalid
+- from MythTV.tmdb3 import Collection
+- from MythTV import VideoMetadata
+- from lxml import etree
+- collection = Collection(inetref)
+- tree = etree.XML(u'<metadata></metadata>')
+- m = VideoMetadata()
+- m.collectionref = str(collection.id)
+- try:
+- m.title = collection.name
+- except TMDBRequestInvalid:
+- print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+- xml_declaration=True))
+- sys.exit()
+- if collection.backdrop:
+- b = collection.backdrop
+- m.images.append({'type':'fanart', 'url':b.geturl(),
+- 'thumb':b.geturl(b.sizes()[0])})
+- if collection.poster:
+- p = collection.poster
+- m.images.append({'type':'coverart', 'url':p.geturl(),
+- 'thumb':p.geturl(p.sizes()[0])})
+- tree.append(m.toXML())
+- print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
+- xml_declaration=True))
+- sys.exit()
+-
+-def buildVersion():
+- from lxml import etree
+- version = etree.XML(u'<grabber></grabber>')
+- etree.SubElement(version, "name").text = __title__
+- etree.SubElement(version, "author").text = __author__
+- etree.SubElement(version, "thumbnail").text = 'tmdb.png'
+- etree.SubElement(version, "command").text = 'tmdb3.py'
+- etree.SubElement(version, "type").text = 'movie'
+- etree.SubElement(version, "description").text = \
+- 'Search and metadata downloads for themoviedb.org'
+- etree.SubElement(version, "version").text = __version__
+- etree.SubElement(version, "accepts").text = 'tmdb.py'
+- etree.SubElement(version, "accepts").text = 'tmdb.pl'
+- print_etree(etree.tostring(version, encoding='UTF-8', pretty_print=True,
+- xml_declaration=True))
+- sys.exit(0)
+-
+ def performSelfTest():
+ err = 0
+ try:
+@@ -297,19 +46,22 @@ def performSelfTest():
+
+ if not err:
+ print ("Everything appears in order.")
+- sys.exit(err)
++ return err
++
++def main(showType, command):
+
+-def main():
+ parser = OptionParser()
+
+ parser.add_option('-v', "--version", action="store_true", default=False,
+ dest="version", help="Display version and author")
+ parser.add_option('-t', "--test", action="store_true", default=False,
+ dest="test", help="Perform self-test for dependencies.")
+- parser.add_option('-M', "--movielist", action="store_true", default=False,
+- dest="movielist", help="Get Movies matching search.")
+- parser.add_option('-D', "--moviedata", action="store_true", default=False,
+- dest="moviedata", help="Get Movie data.")
++ parser.add_option('-M', "--movielist", "--list", action="store_true", default=False,
++ dest="movielist",
++ help="Get Movies. Needs search key.")
++ parser.add_option('-D', "--moviedata", "--data", action="store_true", default=False,
++ dest="moviedata", help="Get Movie data. " \
++ "Needs inetref. ")
+ parser.add_option('-C', "--collection", action="store_true", default=False,
+ dest="collectiondata", help="Get Collection data.")
+ parser.add_option('-l', "--language", metavar="LANGUAGE", default=u'en',
+@@ -322,14 +74,12 @@ def main():
+
+ opts, args = parser.parse_args()
+
++ from MythTV.tmdb3.lookup import timeouthandler
+ signal.signal(signal.SIGALRM, timeouthandler)
+ signal.alarm(180)
+
+- if opts.version:
+- buildVersion()
+-
+ if opts.test:
+- performSelfTest()
++ return performSelfTest()
+
+ from MythTV.tmdb3 import set_key, set_cache, set_locale
+ set_key('c27cb71cff5bd76e1a7a009380562c62')
+@@ -345,7 +95,7 @@ def main():
+ confdir = os.environ.get('HOME', '')
+ if (not confdir) or (confdir == '/'):
+ print ("Unable to find MythTV directory for metadata cache.")
+- sys.exit(1)
++ return 1
+ confdir = os.path.join(confdir, '.mythtv')
+ cachedir = os.path.join(confdir, 'cache')
+ if not os.path.exists(cachedir):
+@@ -358,22 +108,43 @@ def main():
+ if opts.country:
+ set_locale(country=opts.country, fallthrough=True)
+
+- if (len(args) != 1) or (args[0] == ''):
+- sys.stdout.write('ERROR: tmdb3.py requires exactly one non-empty argument')
+- sys.exit(1)
++ if (not opts.version):
++ if (len(args) < 1) or (args[0] == ''):
++ sys.stdout.write('ERROR: tmdb3.py requires at least one non-empty argument.\n')
++ return 1
+
++ from MythTV.tmdb3.lookup import buildVersion, buildMovieList, \
++ buildSingle, buildCollection, print_etree
+ try:
+- if opts.movielist:
+- buildList(args[0], opts)
++ xml = None
++ if opts.version:
++ xml = buildVersion(showType, command)
++
++ elif opts.movielist:
++ xml = buildMovieList(args[0], opts)
+
+- if opts.moviedata:
+- buildSingle(args[0], opts)
++ elif opts.moviedata:
++ xml = buildSingle(args[0], opts)
++
++ elif opts.collectiondata:
++ xml = buildCollection(args[0], opts)
++
++ # if a number is returned, it is an error code return
++ if isinstance(xml,int):
++ return xml
++
++ if xml:
++ print_etree(xml)
++ else:
++ return 1
+
+- if opts.collectiondata:
+- buildCollection(args[0], opts)
+ except RuntimeError as exc:
+ sys.stdout.write('ERROR: ' + str(exc) + ' exception')
+- sys.exit(1)
++ import traceback
++ traceback.print_exc()
++ return 1
++
++ return 0
+
+ if __name__ == '__main__':
+- main()
++ sys.exit(main("movie",'tmdb3.py'))
+diff --git a/mythtv/programs/scripts/metadata/Television/tmdb3tv.py b/mythtv/programs/scripts/metadata/Television/tmdb3tv.py
+new file mode 100755
+index 00000000000..84d5dceadaf
+--- /dev/null
++++ b/mythtv/programs/scripts/metadata/Television/tmdb3tv.py
+@@ -0,0 +1,159 @@
++#!/usr/bin/env python
++# -*- coding: UTF-8 -*-
++# ----------------------
++# Name: tmdb3tv.py
++# Python Script
++# Author: Peter Bennett
++# Purpose:
++# Frontend for the tmdb3 lookup.py script that now supports both Movies and
++# TV shows. This frontend supports TV shows.
++# Command example:
++# See help (-h) options
++#
++# License:Creative Commons GNU GPL v2
++# (http://creativecommons.org/licenses/GPL/2.0/)
++# -------------------------------------
++#
++__title__ = "TheMovieDB.org for TV V3"
++__author__ = "Peter Bennett"
++
++from optparse import OptionParser
++import sys
++import signal
++
++def performSelfTest():
++ err = 0
++ try:
++ import MythTV
++ except:
++ err = 1
++ print ("Failed to import MythTV bindings. Check your `configure` output "
++ "to make sure installation was not disabled due to external "
++ "dependencies")
++ try:
++ import MythTV.tmdb3
++ except:
++ err = 1
++ print ("Failed to import PyTMDB3 library. This should have been included "
++ "with the python MythTV bindings.")
++ try:
++ import lxml
++ except:
++ err = 1
++ print ("Failed to import python lxml library.")
++
++ if not err:
++ print ("Everything appears in order.")
++ return err
++
++def main(showType, command):
++
++ parser = OptionParser()
++
++ parser.add_option('-v', "--version", action="store_true", default=False,
++ dest="version", help="Display version and author")
++ parser.add_option('-t', "--test", action="store_true", default=False,
++ dest="test", help="Perform self-test for dependencies.")
++ parser.add_option('-M', "--movielist", "--list", action="store_true", default=False,
++ dest="movielist",
++ help="Get Television Series List. Needs search key.")
++ parser.add_option('-D', "--moviedata", "--data", action="store_true", default=False,
++ dest="moviedata", help="Get TV Episode data. " \
++ "Needs inetref, season and episode. ")
++ parser.add_option('-C', "--collection", action="store_true", default=False,
++ dest="collectiondata",
++ help='Get a television Series (collection) "series" level information')
++ parser.add_option("-N", "--numbers", action="store_true", default=False, dest="numbers",
++ help="Get television Season and Episode numbers. " \
++ "Needs title and subtitle or inetref and subtitle.")
++ parser.add_option('-l', "--language", metavar="LANGUAGE", default=u'en',
++ dest="language", help="Specify language for filtering.")
++ parser.add_option('-a', "--area", metavar="COUNTRY", default=None,
++ dest="country", help="Specify country for custom data.")
++ parser.add_option('--debug', action="store_true", default=False,
++ dest="debug", help=("Disable caching and enable raw "
++ "data output."))
++
++ opts, args = parser.parse_args()
++
++ from MythTV.tmdb3.lookup import timeouthandler
++ signal.signal(signal.SIGALRM, timeouthandler)
++ signal.alarm(180)
++
++ if opts.test:
++ return performSelfTest()
++
++ from MythTV.tmdb3 import set_key, set_cache, set_locale
++ set_key('c27cb71cff5bd76e1a7a009380562c62')
++
++ if opts.debug:
++ import MythTV.tmdb3
++ MythTV.tmdb3.request.DEBUG = True
++ set_cache(engine='null')
++ else:
++ import os
++ confdir = os.environ.get('MYTHCONFDIR', '')
++ if (not confdir) or (confdir == '/'):
++ confdir = os.environ.get('HOME', '')
++ if (not confdir) or (confdir == '/'):
++ print ("Unable to find MythTV directory for metadata cache.")
++ return 1
++ confdir = os.path.join(confdir, '.mythtv')
++ cachedir = os.path.join(confdir, 'cache')
++ if not os.path.exists(cachedir):
++ os.makedirs(cachedir)
++ cachepath = os.path.join(cachedir, 'pytmdb3.cache')
++ set_cache(engine='file', filename=cachepath)
++
++ if opts.language:
++ set_locale(language=opts.language, fallthrough=True)
++ if opts.country:
++ set_locale(country=opts.country, fallthrough=True)
++
++ if (not opts.version):
++ if (len(args) < 1) or (args[0] == ''):
++ sys.stdout.write('ERROR: tmdb3.py requires at least one non-empty argument.\n')
++ return 1
++
++ if opts.moviedata and len(args) < 3:
++ sys.stdout.write('ERROR: tmdb3.py -D requires three non-empty arguments.\n')
++ return 1
++
++ from MythTV.tmdb3.lookup import buildVersion, buildTVList, \
++ buildEpisode, buildTVSeries, print_etree
++ try:
++ xml = None
++ if opts.version:
++ xml = buildVersion(showType, command)
++
++ elif opts.movielist:
++ xml = buildTVList(args[0], opts)
++
++ elif opts.numbers:
++ xml = buildEpisode(args[0:2], opts)
++
++ elif opts.moviedata:
++ xml = buildEpisode(args[0:3], opts)
++
++ elif opts.collectiondata:
++ xml = buildTVSeries(args[0], opts)
++
++ # if a number is returned, it is an error code return
++ if isinstance(xml,int):
++ return xml
++
++ if xml:
++ print_etree(xml)
++ else:
++ return 1
++
++ except RuntimeError as exc:
++ sys.stdout.write('ERROR: ' + str(exc) + ' exception')
++ import traceback
++ traceback.print_exc()
++ return 1
++
++ return 0
++
++if __name__ == '__main__':
++ sys.exit(main("television",'tmdb3tv.py'))
+
+From 7763a3363d7a05aaf45d6d4cb13cffa54274e2b4 Mon Sep 17 00:00:00 2001
+From: Paul Harrison <paul(a)mythqml.net>
+Date: Thu, 4 Feb 2021 18:24:29 +0000
+Subject: [PATCH 135/165] MythMusic: make parsePLS more robust and improve
+ logging
+
+This fixes playing some radio streams which send slightly out of spec PLS
+files with [Playlist] instead of [playlist] for the group.
+
+(cherry picked from commit dd9b15e364401a9943b85504d964afca75b6c78a)
+---
+ mythplugins/mythmusic/mythmusic/pls.cpp | 31 +++++++++++++++++++++----
+ 1 file changed, 27 insertions(+), 4 deletions(-)
+
+diff --git a/mythplugins/mythmusic/mythmusic/pls.cpp b/mythplugins/mythmusic/mythmusic/pls.cpp
+index bb08e0bba6d..4f2204d518b 100644
+--- a/mythplugins/mythmusic/mythmusic/pls.cpp
++++ b/mythplugins/mythmusic/mythmusic/pls.cpp
+@@ -51,14 +51,37 @@ int PlayListFile::parse(PlayListFile *pls, const QString &filename)
+
+ int PlayListFile::parsePLS(PlayListFile *pls, const QString &filename)
+ {
++ LOG(VB_FILE, LOG_DEBUG, QString("DecoderHandler: parsePLS - '%1'").arg(filename));
++
+ QSettings settings(filename, QSettings::IniFormat);
+- settings.beginGroup("playlist");
+
+- int num_entries = settings.value("numberofentries", -1).toInt();
++ // we allow both 'playlist' and 'Playlist' for the group name
++ QStringList groups = settings.childGroups();
++
++ if (groups.contains("playlist"))
++ settings.beginGroup("playlist");
++ else if (groups.contains("Playlist"))
++ settings.beginGroup("Playlist");
++ else
++ {
++ LOG(VB_GENERAL, LOG_ERR, QString("DecoderHandler: parsePLS - playlist group not found"));
++ return 0;
++ }
++
++ int num_entries = -1;
++
++ // Some pls files have "numberofentries", some have "NumberOfEntries".
++ QStringList keys = settings.childKeys();
+
+- // Some pls files have "numberofentries", some has "NumberOfEntries".
+- if (num_entries == -1)
++ if (keys.contains("numberofentries"))
++ num_entries = settings.value("numberofentries", -1).toInt();
++ else if (keys.contains("NumberOfEntries"))
+ num_entries = settings.value("NumberOfEntries", -1).toInt();
++ else
++ {
++ LOG(VB_GENERAL, LOG_ERR, QString("DecoderHandler: parsePLS - NumberOfEntries key not found"));
++ return 0;
++ }
+
+ for (int n = 1; n <= num_entries; n++)
+ {
+
+From ba4036099ffce6364c389211e7b9cd38489f7ad4 Mon Sep 17 00:00:00 2001
+From: Mark Kendall <mark.kendall(a)gmail.com>
+Date: Fri, 5 Feb 2021 17:56:56 +0000
+Subject: [PATCH 136/165] AudioOutputGraph: Fix buffer overflow
+
+- this just fixes the obvious and, on macos at least, fatal symptoms but
+this code needs a sizeable cleanup.
+
+(cherry picked from commit 0c76409c9b2650bc5824055e2e9c4b30067b3411)
+---
+ .../libs/libmyth/audio/audiooutputgraph.cpp | 21 +++++++++++++------
+ 1 file changed, 15 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/libs/libmyth/audio/audiooutputgraph.cpp b/mythtv/libs/libmyth/audio/audiooutputgraph.cpp
+index 13dc1a06542..286f4249662 100644
+--- a/mythtv/libs/libmyth/audio/audiooutputgraph.cpp
++++ b/mythtv/libs/libmyth/audio/audiooutputgraph.cpp
+@@ -22,7 +22,7 @@ const int kBufferMilliSecs = 500;
+ /*
+ * Audio data buffer
+ */
+-class AudioOutputGraph::Buffer : protected QByteArray
++class AudioOutputGraph::Buffer : public QByteArray
+ {
+ public:
+ Buffer() = default;
+@@ -268,7 +268,7 @@ MythImage *AudioOutputGraph::GetImage(int64_t timecode) const
+ .arg(timecode).arg(avail.first).arg(avail.second)
+ .arg(m_buffer->First()).arg(m_buffer->Next()) );
+
+- const int width = m_buffer->Samples(avail);
++ int width = m_buffer->Samples(avail);
+ if (width <= 0)
+ return nullptr;
+
+@@ -278,13 +278,22 @@ MythImage *AudioOutputGraph::GetImage(int64_t timecode) const
+ if (height <= 0)
+ return nullptr;
+
+- QImage image(width, height, QImage::Format_ARGB32);
+- image.fill(0);
+-
+ const int channels = m_buffer->Channels();
+
+ // Assume signed 16 bit/sample
+- const int16_t *p = m_buffer->Data16(avail);
++ const auto * p = m_buffer->Data16(avail);
++ const auto * max = reinterpret_cast<const int16_t*>(m_buffer->constData() + m_buffer->size());
++ if (p >= max)
++ return nullptr;
++
++ if ((p + (channels * width)) >= max)
++ {
++ LOG(VB_GENERAL, LOG_WARNING, LOC + " Buffer overflow. Clipping samples.");
++ width = static_cast<int>(max - p) / channels;
++ }
++
++ QImage image(width, height, QImage::Format_ARGB32);
++ image.fill(0);
+
+ for (int x = 0; x < width; ++x)
+ {
+
+From 525e3b0bb4ec0a160ea35591b28ca7d4ae067313 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Mon, 18 Jan 2021 22:50:54 +0100
+Subject: [PATCH 137/165] Add optional metadata grabber for television: tvmaze
+
+See
+https://www.tvmaze.com/
+https://www.tvmaze.com/api
+
+This commit adds an alternative metadata grabber for TV-Series.
+The tvmaze metadata grabber follows the defintions in
+https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
+
+To enable this grabber, run in mythtv frontend
+"Setup" -> "Artwork and Data Sources"
+and select 'TVMaze.com' for the default TelevisionGrabber.
+
+Once done, you can check this global setting with mysql:
+
+[mythconverg]> select * from settings where value like '%grabber%';
++--------------------+-------------------------------+----------+
+| value | data | hostname |
++--------------------+-------------------------------+----------+
+| TelevisionGrabber | metadata/Television/tvmaze.py | NULL |
+| MovieGrabber | metadata/Movie/tmdb3.py | NULL |
++--------------------+-------------------------------+----------+
+
+Any further metadata update will then use these grabbers.
+
+Note: The tvmaze grabber is compatible to python 2.7 and 3.6+.
+(cherry picked from commit eb1c3771a90b0a1badc9ef0341e9612ea0cca7f5)
+---
+ mythtv/bindings/python/setup.py | 4 +-
+ mythtv/bindings/python/tvmaze/__init__.py | 0
+ mythtv/bindings/python/tvmaze/embed.py | 54 ++
+ mythtv/bindings/python/tvmaze/endpoints.py | 40 ++
+ mythtv/bindings/python/tvmaze/episode.py | 55 ++
+ mythtv/bindings/python/tvmaze/locales.py | 571 ++++++++++++++++++
+ mythtv/bindings/python/tvmaze/person.py | 75 +++
+ mythtv/bindings/python/tvmaze/season.py | 56 ++
+ mythtv/bindings/python/tvmaze/show.py | 130 ++++
+ mythtv/bindings/python/tvmaze/tvmaze_api.py | 216 +++++++
+ mythtv/bindings/python/tvmaze/utils.py | 68 +++
+ .../scripts/metadata/Television/tvmaze.py | 553 +++++++++++++++++
+ .../metadata/Television/tvmaze_tests.txt | 350 +++++++++++
+ 13 files changed, 2170 insertions(+), 2 deletions(-)
+ create mode 100644 mythtv/bindings/python/tvmaze/__init__.py
+ create mode 100644 mythtv/bindings/python/tvmaze/embed.py
+ create mode 100644 mythtv/bindings/python/tvmaze/endpoints.py
+ create mode 100644 mythtv/bindings/python/tvmaze/episode.py
+ create mode 100644 mythtv/bindings/python/tvmaze/locales.py
+ create mode 100644 mythtv/bindings/python/tvmaze/person.py
+ create mode 100644 mythtv/bindings/python/tvmaze/season.py
+ create mode 100644 mythtv/bindings/python/tvmaze/show.py
+ create mode 100644 mythtv/bindings/python/tvmaze/tvmaze_api.py
+ create mode 100644 mythtv/bindings/python/tvmaze/utils.py
+ create mode 100755 mythtv/programs/scripts/metadata/Television/tvmaze.py
+ create mode 100644 mythtv/programs/scripts/metadata/Television/tvmaze_tests.txt
+
+diff --git a/mythtv/bindings/python/setup.py b/mythtv/bindings/python/setup.py
+index 9c2b143e969..c7270faee9b 100755
+--- a/mythtv/bindings/python/setup.py
++++ b/mythtv/bindings/python/setup.py
+@@ -82,10 +82,10 @@ def run(self):
+ version='31.0.-1',
+ description='MythTV Python bindings.',
+ long_description='Provides canned database and protocol access to the MythTV database, mythproto, mythxml, services_api and frontend remote control.',
+- packages=['MythTV', 'MythTV/tmdb3', 'MythTV/ttvdb',
++ packages=['MythTV', 'MythTV/tmdb3', 'MythTV/ttvdb', 'MythTV/tvmaze',
+ 'MythTV/wikiscripts', 'MythTV/utility',
+ 'MythTV/services_api'],
+- package_dir={'MythTV/tmdb3':'./tmdb3/tmdb3'},
++ package_dir={'MythTV/tmdb3':'./tmdb3/tmdb3', 'MythTV/tvmaze':'./tvmaze'},
+ data_files=[('MythTV/ttvdb/XSLT', glob.glob('MythTV/ttvdb/XSLT/*'))],
+ url=['http://www.mythtv.org/'],
+ scripts=SCRIPTS,
+diff --git a/mythtv/bindings/python/tvmaze/__init__.py b/mythtv/bindings/python/tvmaze/__init__.py
+new file mode 100644
+index 00000000000..e69de29bb2d
+diff --git a/mythtv/bindings/python/tvmaze/embed.py b/mythtv/bindings/python/tvmaze/embed.py
+new file mode 100644
+index 00000000000..8e3b2a3d115
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/embed.py
+@@ -0,0 +1,54 @@
++# -*- coding: UTF-8 -*-
++
++# Copyright (c) 2020 Lachlan Mackenzie
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++
++# ---------------------------------------------------
++# Roland Ernst
++# Changes implemented for MythTV:
++# - added python2 compatibility
++#
++# ---------------------------------------------------
++
++
++from __future__ import unicode_literals
++
++# python 3 doesn't have a unicode type
++try:
++ unicode
++except:
++ unicode = str
++
++
++class Embed(object):
++ def __init__(self, data):
++ self.key = 'embed'
++ self.value = None
++ if isinstance(data, unicode):
++ self.key = 'embed'
++ self.value = data
++ if isinstance(data, list) and len(data) > 0:
++ if all(isinstance(item, unicode) for item in data):
++ self.key = 'embed[]'
++ self.value = data
++
++ def __str__(self):
++ return self.key + ': ' + self.value
+diff --git a/mythtv/bindings/python/tvmaze/endpoints.py b/mythtv/bindings/python/tvmaze/endpoints.py
+new file mode 100644
+index 00000000000..5c6fc0b86d3
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/endpoints.py
+@@ -0,0 +1,40 @@
++# -*- coding: UTF-8 -*-
++
++# Copyright (c) 2020 Lachlan Mackenzie
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++
++from __future__ import unicode_literals
++
++
++API_URL = 'https://api.tvmaze.com'
++search_show_name = 'https://api.tvmaze.com/search/shows?'
++search_show_best_match = 'https://api.tvmaze.com/singlesearch/shows?'
++search_external_show_id = 'https://api.tvmaze.com/lookup/shows?'
++show_information = 'https://api.tvmaze.com/shows/{0}'
++show_episode_list = 'https://api.tvmaze.com/shows/{0}/episodes?'
++show_episode = 'http://api.tvmaze.com/shows/{0}/episodebynumber?'
++show_episodes_on_date = 'http://api.tvmaze.com/shows/{0}/episodesbydate?'
++show_season_list = 'http://api.tvmaze.com/shows/{0}/seasons'
++season_episode_list = 'http://api.tvmaze.com/seasons/{0}/episodes'
++show_alias_list = 'http://api.tvmaze.com/shows/{0}/akas'
++episode_information = 'http://api.tvmaze.com/episodes/{0}?'
++show_cast = 'http://api.tvmaze.com/shows/{0}/cast'
++show_crew = 'http://api.tvmaze.com/shows/{0}/crew'
+diff --git a/mythtv/bindings/python/tvmaze/episode.py b/mythtv/bindings/python/tvmaze/episode.py
+new file mode 100644
+index 00000000000..4db1dcc77a5
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/episode.py
+@@ -0,0 +1,55 @@
++# -*- coding: UTF-8 -*-
++
++# Copyright (c) 2020 Lachlan Mackenzie
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++
++# ---------------------------------------------------
++# Roland Ernst
++# Changes implemented for MythTV:
++# - added method utils.convert_date
++#
++# ---------------------------------------------------
++
++
++from __future__ import unicode_literals
++
++from . import utils
++
++
++class Episode(object):
++ def __init__(self, data):
++ self.id = data.get('id')
++ self.url = data.get('url')
++ self.name = data.get('name')
++ self.season = data.get('season')
++ self.number = data.get('number')
++ self.airdate = utils.convert_date(data.get('airdate'))
++ self.airtime = data.get('airtime')
++ self.timestamp = data.get('airstamp')
++ self.duration = data.get('runtime')
++ self.images = data.get('image')
++ self.summary = utils.strip_tags(data.get('summary'))
++ self.links = data.get('_links')
++ self.special = self.number == 0
++
++ def __str__(self):
++ return 'S{}E{} {}'.format(self.season, self.number, self.name)
++ # return f'S{self.season}E{self.number} {self.name}' if not self.special else f'Special: {self.name}'
+diff --git a/mythtv/bindings/python/tvmaze/locales.py b/mythtv/bindings/python/tvmaze/locales.py
+new file mode 100644
+index 00000000000..9bc6fdff238
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/locales.py
+@@ -0,0 +1,571 @@
++# -*- coding: utf-8 -*-
++
++#-----------------------
++# Name: locales.py Stores locale information for filtering results
++# Python Library
++# Author: Raymond Wagner
++#-----------------------
++
++# ----------------------
++# Roland Ernst
++# Changes implemented for MythTV/tvmaze:
++# - pulled from tmdb3 package and modified to the needs of tvmaze in Mythtv
++#
++# ----------------------
++
++
++from __future__ import unicode_literals
++
++
++class LocaleBase(object):
++ __slots__ = ['__immutable']
++ _stored = {}
++ fallthrough = False
++
++ def __init__(self, *keys):
++ for key in keys:
++ self._stored[key.lower()] = self
++ self.__immutable = True
++
++ def __setattr__(self, key, value):
++ if getattr(self, '__immutable', False):
++ raise NotImplementedError(self.__class__.__name__ +
++ ' does not support modification.')
++ super(LocaleBase, self).__setattr__(key, value)
++
++ def __delattr__(self, key):
++ if getattr(self, '__immutable', False):
++ raise NotImplementedError(self.__class__.__name__ +
++ ' does not support modification.')
++ super(LocaleBase, self).__delattr__(key)
++
++ def __lt__(self, other):
++ return (id(self) != id(other)) and (str(self) > str(other))
++
++ def __gt__(self, other):
++ return (id(self) != id(other)) and (str(self) < str(other))
++
++ def __eq__(self, other):
++ return (id(self) == id(other)) or (str(self) == str(other))
++
++ @classmethod
++ def getstored(cls, key):
++ if key is None:
++ return None
++ try:
++ return cls._stored[key.lower()]
++ except:
++ raise Exception("LocaleBase: '{0}' is not a known valid {1} code."
++ .format(key, cls.__name__))
++
++
++class Language(LocaleBase):
++ __slots__ = ['ISO639_1', 'ISO639_2', 'ISO639_2B', 'englishname',
++ 'nativename']
++ _stored = {}
++
++ def __init__(self, iso1, iso2, ename):
++ self.ISO639_1 = iso1
++ self.ISO639_2 = iso2
++# self.ISO639_2B = iso2b
++ self.englishname = ename
++# self.nativename = nname
++ super(Language, self).__init__(iso1, iso2, ename)
++
++ def __str__(self):
++ return self.ISO639_1
++
++ def __repr__(self):
++ return u"<Language '{0.englishname}' ({0.ISO639_1})>".format(self)
++
++
++class Country(LocaleBase):
++ __slots__ = ['alpha2', 'name']
++ _stored = {}
++
++ def __init__(self, alpha2, name):
++ self.alpha2 = alpha2
++ self.name = name
++ super(Country, self).__init__(alpha2)
++
++ def __str__(self):
++ return self.alpha2
++
++ def __repr__(self):
++ return u"<Country '{0.name}' ({0.alpha2})>".format(self)
++
++
++
++
++######## AUTOGENERATED LANGUAGE AND COUNTRY DATA BELOW HERE #########
++
++Language("ab", "abk", "Abkhazian")
++Language("aa", "aar", "Afar")
++Language("af", "afr", "Afrikaans")
++Language("ak", "aka", "Akan")
++Language("sq", "alb/sqi", "Albanian")
++Language("am", "amh", "Amharic")
++Language("ar", "ara", "Arabic")
++Language("an", "arg", "Aragonese")
++Language("hy", "arm/hye", "Armenian")
++Language("as", "asm", "Assamese")
++Language("av", "ava", "Avaric")
++Language("ae", "ave", "Avestan")
++Language("ay", "aym", "Aymara")
++Language("az", "aze", "Azerbaijani")
++Language("bm", "bam", "Bambara")
++Language("ba", "bak", "Bashkir")
++Language("eu", "baq/eus", "Basque")
++Language("be", "bel", "Belarusian")
++Language("bn", "ben", "Bengali")
++Language("bh", "bih", "Bihari languages")
++Language("bi", "bis", "Bislama")
++Language("nb", "nob", "Bokmål, Norwegian")
++Language("bs", "bos", "Bosnian")
++Language("br", "bre", "Breton")
++Language("bg", "bul", "Bulgarian")
++Language("my", "bur/mya", "Burmese")
++Language("es", "spa", "Castilian")
++Language("ca", "cat", "Catalan")
++Language("km", "khm", "Central Khmer")
++Language("ch", "cha", "Chamorro")
++Language("ce", "che", "Chechen")
++Language("ny", "nya", "Chewa")
++Language("ny", "nya", "Chichewa")
++Language("zh", "chi/zho", "Chinese")
++Language("za", "zha", "Chuang")
++Language("cu", "chu", "Church Slavic")
++Language("cu", "chu", "Church Slavonic")
++Language("cv", "chv", "Chuvash")
++Language("kw", "cor", "Cornish")
++Language("co", "cos", "Corsican")
++Language("cr", "cre", "Cree")
++Language("hr", "hrv", "Croatian")
++Language("cs", "cze/ces", "Czech")
++Language("da", "dan", "Danish")
++Language("dv", "div", "Dhivehi")
++Language("dv", "div", "Divehi")
++Language("nl", "dut/nld", "Dutch")
++Language("dz", "dzo", "Dzongkha")
++Language("en", "eng", "English")
++Language("eo", "epo", "Esperanto")
++Language("et", "est", "Estonian")
++Language("ee", "ewe", "Ewe")
++Language("fo", "fao", "Faroese")
++Language("fj", "fij", "Fijian")
++Language("fi", "fin", "Finnish")
++Language("nl", "dut/nld", "Flemish")
++Language("fr", "fre/fra", "French")
++Language("ff", "ful", "Fulah")
++Language("gd", "gla", "Gaelic")
++Language("gl", "glg", "Galician")
++Language("lg", "lug", "Ganda")
++Language("ka", "geo/kat", "Georgian")
++Language("de", "ger/deu", "German")
++Language("ki", "kik", "Gikuyu")
++Language("el", "gre/ell", "Greek, Modern (1453-)")
++Language("kl", "kal", "Greenlandic")
++Language("gn", "grn", "Guarani")
++Language("gu", "guj", "Gujarati")
++Language("ht", "hat", "Haitian")
++Language("ht", "hat", "Haitian Creole")
++Language("ha", "hau", "Hausa")
++Language("he", "heb", "Hebrew")
++Language("hz", "her", "Herero")
++Language("hi", "hin", "Hindi")
++Language("ho", "hmo", "Hiri Motu")
++Language("hu", "hun", "Hungarian")
++Language("is", "ice/isl", "Icelandic")
++Language("io", "ido", "Ido")
++Language("ig", "ibo", "Igbo")
++Language("id", "ind", "Indonesian")
++Language(
++ "ia", "ina", "Interlingua (International Auxiliary Language Association)"
++)
++Language("ie", "ile", "Interlingue")
++Language("iu", "iku", "Inuktitut")
++Language("ik", "ipk", "Inupiaq")
++Language("ga", "gle", "Irish")
++Language("it", "ita", "Italian")
++Language("ja", "jpn", "Japanese")
++Language("jv", "jav", "Javanese")
++Language("kl", "kal", "Kalaallisut")
++Language("kn", "kan", "Kannada")
++Language("kr", "kau", "Kanuri")
++Language("ks", "kas", "Kashmiri")
++Language("kk", "kaz", "Kazakh")
++Language("ki", "kik", "Kikuyu")
++Language("rw", "kin", "Kinyarwanda")
++Language("ky", "kir", "Kirghiz")
++Language("kv", "kom", "Komi")
++Language("kg", "kon", "Kongo")
++Language("ko", "kor", "Korean")
++Language("kj", "kua", "Kuanyama")
++Language("ku", "kur", "Kurdish")
++Language("kj", "kua", "Kwanyama")
++Language("ky", "kir", "Kyrgyz")
++Language("lo", "lao", "Lao")
++Language("la", "lat", "Latin")
++Language("lv", "lav", "Latvian")
++Language("lb", "ltz", "Letzeburgesch")
++Language("li", "lim", "Limburgan")
++Language("li", "lim", "Limburger")
++Language("li", "lim", "Limburgish")
++Language("ln", "lin", "Lingala")
++Language("lt", "lit", "Lithuanian")
++Language("lu", "lub", "Luba-Katanga")
++Language("lb", "ltz", "Luxembourgish")
++Language("mk", "mac/mkd", "Macedonian")
++Language("mg", "mlg", "Malagasy")
++Language("ms", "may/msa", "Malay")
++Language("ml", "mal", "Malayalam")
++Language("dv", "div", "Maldivian")
++Language("mt", "mlt", "Maltese")
++Language("gv", "glv", "Manx")
++Language("mi", "mao/mri", "Maori")
++Language("mr", "mar", "Marathi")
++Language("mh", "mah", "Marshallese")
++Language("ro", "rum/ron", "Moldavian")
++Language("ro", "rum/ron", "Moldovan")
++Language("mn", "mon", "Mongolian")
++Language("na", "nau", "Nauru")
++Language("nv", "nav", "Navaho")
++Language("nv", "nav", "Navajo")
++Language("nd", "nde", "Ndebele, North")
++Language("nr", "nbl", "Ndebele, South")
++Language("ng", "ndo", "Ndonga")
++Language("ne", "nep", "Nepali")
++Language("nd", "nde", "North Ndebele")
++Language("se", "sme", "Northern Sami")
++Language("no", "nor", "Norwegian")
++Language("nb", "nob", "Norwegian Bokmål")
++Language("nn", "nno", "Norwegian Nynorsk")
++Language("ii", "iii", "Nuosu")
++Language("ny", "nya", "Nyanja")
++Language("nn", "nno", "Nynorsk, Norwegian")
++Language("ie", "ile", "Occidental")
++Language("oc", "oci", "Occitan (post 1500)")
++Language("oj", "oji", "Ojibwa")
++Language("cu", "chu", "Old Bulgarian")
++Language("cu", "chu", "Old Church Slavonic")
++Language("cu", "chu", "Old Slavonic")
++Language("or", "ori", "Oriya")
++Language("om", "orm", "Oromo")
++Language("os", "oss", "Ossetian")
++Language("os", "oss", "Ossetic")
++Language("pi", "pli", "Pali")
++Language("pa", "pan", "Panjabi")
++Language("ps", "pus", "Pashto")
++Language("fa", "per/fas", "Persian")
++Language("pl", "pol", "Polish")
++Language("pt", "por", "Portuguese")
++Language("pa", "pan", "Punjabi")
++Language("ps", "pus", "Pushto")
++Language("qu", "que", "Quechua")
++Language("ro", "rum/ron", "Romanian")
++Language("rm", "roh", "Romansh")
++Language("rn", "run", "Rundi")
++Language("ru", "rus", "Russian")
++Language("sm", "smo", "Samoan")
++Language("sg", "sag", "Sango")
++Language("sa", "san", "Sanskrit")
++Language("sc", "srd", "Sardinian")
++Language("gd", "gla", "Scottish Gaelic")
++Language("sr", "srp", "Serbian")
++Language("sn", "sna", "Shona")
++Language("ii", "iii", "Sichuan Yi")
++Language("sd", "snd", "Sindhi")
++Language("si", "sin", "Sinhala")
++Language("si", "sin", "Sinhalese")
++Language("sk", "slo/slk", "Slovak")
++Language("sl", "slv", "Slovenian")
++Language("so", "som", "Somali")
++Language("st", "sot", "Sotho, Southern")
++Language("nr", "nbl", "South Ndebele")
++Language("es", "spa", "Spanish")
++Language("su", "sun", "Sundanese")
++Language("sw", "swa", "Swahili")
++Language("ss", "ssw", "Swati")
++Language("sv", "swe", "Swedish")
++Language("tl", "tgl", "Tagalog")
++Language("ty", "tah", "Tahitian")
++Language("tg", "tgk", "Tajik")
++Language("ta", "tam", "Tamil")
++Language("tt", "tat", "Tatar")
++Language("te", "tel", "Telugu")
++Language("th", "tha", "Thai")
++Language("bo", "tib/bod", "Tibetan")
++Language("ti", "tir", "Tigrinya")
++Language("to", "ton", "Tonga (Tonga Islands)")
++Language("ts", "tso", "Tsonga")
++Language("tn", "tsn", "Tswana")
++Language("tr", "tur", "Turkish")
++Language("tk", "tuk", "Turkmen")
++Language("tw", "twi", "Twi")
++Language("ug", "uig", "Uighur")
++Language("uk", "ukr", "Ukrainian")
++Language("ur", "urd", "Urdu")
++Language("ug", "uig", "Uyghur")
++Language("uz", "uzb", "Uzbek")
++Language("ca", "cat", "Valencian")
++Language("ve", "ven", "Venda")
++Language("vi", "vie", "Vietnamese")
++Language("vo", "vol", "Volapük")
++Language("wa", "wln", "Walloon")
++Language("cy", "wel/cym", "Welsh")
++Language("fy", "fry", "Western Frisian")
++Language("wo", "wol", "Wolof")
++Language("xh", "xho", "Xhosa")
++Language("yi", "yid", "Yiddish")
++Language("yo", "yor", "Yoruba")
++Language("za", "zha", "Zhuang")
++Language("zu", "zul", "Zulu")
++Country("AD", "Andorra")
++Country("AE", "United Arab Emirates")
++Country("AF", "Afghanistan")
++Country("AG", "Antigua and Barbuda")
++Country("AI", "Anguilla")
++Country("AL", "Albania")
++Country("AM", "Armenia")
++Country("AO", "Angola")
++Country("AQ", "Antarctica")
++Country("AR", "Argentina")
++Country("AS", "American Samoa")
++Country("AT", "Austria")
++Country("AU", "Australia")
++Country("AW", "Aruba")
++Country("AX", "Åland Islands")
++Country("AZ", "Azerbaijan")
++Country("BA", "Bosnia and Herzegovina")
++Country("BB", "Barbados")
++Country("BD", "Bangladesh")
++Country("BE", "Belgium")
++Country("BF", "Burkina Faso")
++Country("BG", "Bulgaria")
++Country("BH", "Bahrain")
++Country("BI", "Burundi")
++Country("BJ", "Benin")
++Country("BL", "Saint Barthélemy")
++Country("BM", "Bermuda")
++Country("BN", "Brunei Darussalam")
++Country("BO", "Bolivia (Plurinational State of)")
++Country("BQ", "Bonaire, Sint Eustatius and Saba")
++Country("BR", "Brazil")
++Country("BS", "Bahamas")
++Country("BT", "Bhutan")
++Country("BV", "Bouvet Island")
++Country("BW", "Botswana")
++Country("BY", "Belarus")
++Country("BZ", "Belize")
++Country("CA", "Canada")
++Country("CC", "Cocos (Keeling) Islands")
++Country("CD", "Congo, Democratic Republic of the")
++Country("CF", "Central African Republic")
++Country("CG", "Congo")
++Country("CH", "Switzerland")
++Country("CI", "Côte d'Ivoire")
++Country("CK", "Cook Islands")
++Country("CL", "Chile")
++Country("CM", "Cameroon")
++Country("CN", "China")
++Country("CO", "Colombia")
++Country("CR", "Costa Rica")
++Country("CU", "Cuba")
++Country("CV", "Cabo Verde")
++Country("CW", "Curaçao")
++Country("CX", "Christmas Island")
++Country("CY", "Cyprus")
++Country("CZ", "Czechia")
++Country("DE", "Germany")
++Country("DJ", "Djibouti")
++Country("DK", "Denmark")
++Country("DM", "Dominica")
++Country("DO", "Dominican Republic")
++Country("DZ", "Algeria")
++Country("EC", "Ecuador")
++Country("EE", "Estonia")
++Country("EG", "Egypt")
++Country("EH", "Western Sahara")
++Country("ER", "Eritrea")
++Country("ES", "Spain")
++Country("ET", "Ethiopia")
++Country("FI", "Finland")
++Country("FJ", "Fiji")
++Country("FK", "Falkland Islands (Malvinas)")
++Country("FM", "Micronesia (Federated States of)")
++Country("FO", "Faroe Islands")
++Country("FR", "France")
++Country("GA", "Gabon")
++Country("GB", "United Kingdom of Great Britain and Northern Ireland")
++Country("GD", "Grenada")
++Country("GE", "Georgia")
++Country("GF", "French Guiana")
++Country("GG", "Guernsey")
++Country("GH", "Ghana")
++Country("GI", "Gibraltar")
++Country("GL", "Greenland")
++Country("GM", "Gambia")
++Country("GN", "Guinea")
++Country("GP", "Guadeloupe")
++Country("GQ", "Equatorial Guinea")
++Country("GR", "Greece")
++Country("GS", "South Georgia and the South Sandwich Islands")
++Country("GT", "Guatemala")
++Country("GU", "Guam")
++Country("GW", "Guinea-Bissau")
++Country("GY", "Guyana")
++Country("HK", "Hong Kong")
++Country("HM", "Heard Island and McDonald Islands")
++Country("HN", "Honduras")
++Country("HR", "Croatia")
++Country("HT", "Haiti")
++Country("HU", "Hungary")
++Country("ID", "Indonesia")
++Country("IE", "Ireland")
++Country("IL", "Israel")
++Country("IM", "Isle of Man")
++Country("IN", "India")
++Country("IO", "British Indian Ocean Territory")
++Country("IQ", "Iraq")
++Country("IR", "Iran (Islamic Republic of)")
++Country("IS", "Iceland")
++Country("IT", "Italy")
++Country("JE", "Jersey")
++Country("JM", "Jamaica")
++Country("JO", "Jordan")
++Country("JP", "Japan")
++Country("KE", "Kenya")
++Country("KG", "Kyrgyzstan")
++Country("KH", "Cambodia")
++Country("KI", "Kiribati")
++Country("KM", "Comoros")
++Country("KN", "Saint Kitts and Nevis")
++Country("KP", "Korea (Democratic People's Republic of)")
++Country("KR", "Korea, Republic of")
++Country("KW", "Kuwait")
++Country("KY", "Cayman Islands")
++Country("KZ", "Kazakhstan")
++Country("LA", "Lao People's Democratic Republic")
++Country("LB", "Lebanon")
++Country("LC", "Saint Lucia")
++Country("LI", "Liechtenstein")
++Country("LK", "Sri Lanka")
++Country("LR", "Liberia")
++Country("LS", "Lesotho")
++Country("LT", "Lithuania")
++Country("LU", "Luxembourg")
++Country("LV", "Latvia")
++Country("LY", "Libya")
++Country("MA", "Morocco")
++Country("MC", "Monaco")
++Country("MD", "Moldova, Republic of")
++Country("ME", "Montenegro")
++Country("MF", "Saint Martin (French part)")
++Country("MG", "Madagascar")
++Country("MH", "Marshall Islands")
++Country("MK", "North Macedonia")
++Country("ML", "Mali")
++Country("MM", "Myanmar")
++Country("MN", "Mongolia")
++Country("MO", "Macao")
++Country("MP", "Northern Mariana Islands")
++Country("MQ", "Martinique")
++Country("MR", "Mauritania")
++Country("MS", "Montserrat")
++Country("MT", "Malta")
++Country("MU", "Mauritius")
++Country("MV", "Maldives")
++Country("MW", "Malawi")
++Country("MX", "Mexico")
++Country("MY", "Malaysia")
++Country("MZ", "Mozambique")
++Country("NA", "Namibia")
++Country("NC", "New Caledonia")
++Country("NE", "Niger")
++Country("NF", "Norfolk Island")
++Country("NG", "Nigeria")
++Country("NI", "Nicaragua")
++Country("NL", "Netherlands[note 1]")
++Country("NO", "Norway")
++Country("NP", "Nepal")
++Country("NR", "Nauru")
++Country("NU", "Niue")
++Country("NZ", "New Zealand")
++Country("OM", "Oman")
++Country("PA", "Panama")
++Country("PE", "Peru")
++Country("PF", "French Polynesia")
++Country("PG", "Papua New Guinea")
++Country("PH", "Philippines")
++Country("PK", "Pakistan")
++Country("PL", "Poland")
++Country("PM", "Saint Pierre and Miquelon")
++Country("PN", "Pitcairn")
++Country("PR", "Puerto Rico")
++Country("PS", "Palestine, State of")
++Country("PT", "Portugal")
++Country("PW", "Palau")
++Country("PY", "Paraguay")
++Country("QA", "Qatar")
++Country("RE", "Réunion")
++Country("RO", "Romania")
++Country("RS", "Serbia")
++Country("RU", "Russian Federation")
++Country("RW", "Rwanda")
++Country("SA", "Saudi Arabia")
++Country("SB", "Solomon Islands")
++Country("SC", "Seychelles")
++Country("SD", "Sudan")
++Country("SE", "Sweden")
++Country("SG", "Singapore")
++Country("SH", "Saint Helena, Ascension and Tristan da Cunha")
++Country("SI", "Slovenia")
++Country("SJ", "Svalbard and Jan Mayen")
++Country("SK", "Slovakia")
++Country("SL", "Sierra Leone")
++Country("SM", "San Marino")
++Country("SN", "Senegal")
++Country("SO", "Somalia")
++Country("SR", "Suriname")
++Country("SS", "South Sudan")
++Country("ST", "Sao Tome and Principe")
++Country("SV", "El Salvador")
++Country("SX", "Sint Maarten (Dutch part)")
++Country("SY", "Syrian Arab Republic")
++Country("SZ", "Eswatini")
++Country("TC", "Turks and Caicos Islands")
++Country("TD", "Chad")
++Country("TF", "French Southern Territories")
++Country("TG", "Togo")
++Country("TH", "Thailand")
++Country("TJ", "Tajikistan")
++Country("TK", "Tokelau")
++Country("TL", "Timor-Leste")
++Country("TM", "Turkmenistan")
++Country("TN", "Tunisia")
++Country("TO", "Tonga")
++Country("TR", "Turkey")
++Country("TT", "Trinidad and Tobago")
++Country("TV", "Tuvalu")
++Country("TW", "Taiwan, Province of China [note 2]")
++Country("TZ", "Tanzania, United Republic of")
++Country("UA", "Ukraine")
++Country("UG", "Uganda")
++Country("UM", "United States Minor Outlying Islands")
++Country("US", "United States of America")
++Country("UY", "Uruguay")
++Country("UZ", "Uzbekistan")
++Country("VA", "Holy See")
++Country("VC", "Saint Vincent and the Grenadines")
++Country("VE", "Venezuela (Bolivarian Republic of)")
++Country("VG", "Virgin Islands (British)")
++Country("VI", "Virgin Islands (U.S.)")
++Country("VN", "Viet Nam")
++Country("VU", "Vanuatu")
++Country("WF", "Wallis and Futuna")
++Country("WS", "Samoa")
++Country("YE", "Yemen")
++Country("YT", "Mayotte")
++Country("ZA", "South Africa")
++Country("ZM", "Zambia")
++Country("ZW", "Zimbabwe")
+diff --git a/mythtv/bindings/python/tvmaze/person.py b/mythtv/bindings/python/tvmaze/person.py
+new file mode 100644
+index 00000000000..c01f3ccbfd2
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/person.py
+@@ -0,0 +1,75 @@
++# -*- coding: UTF-8 -*-
++
++# Copyright (c) 2020 Lachlan Mackenzie
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++
++# ---------------------------------------------------
++# Roland Ernst
++# Changes implemented for MythTV:
++# - added method utils.convert_date
++#
++# ---------------------------------------------------
++
++
++from __future__ import unicode_literals
++
++from . import utils
++
++
++class Person(object):
++ def __init__(self, data):
++ self.id = data.get('id')
++ self.url = data.get('url')
++ self.name = data.get('name')
++ self.country = data.get('country')
++ self.birthday = utils.convert_date(data.get('birthday'))
++ self.deathday = utils.convert_date(data.get('deathday'))
++ self.gender = data.get('gender')
++ self.images = data.get('image')
++ self.links = data.get('_links')
++
++ def __str__(self):
++ return self.name
++
++
++class Character(object):
++ def __init__(self, data, person):
++ self.id = data.get('id')
++ self.url = data.get('url')
++ self.name = data.get('name')
++ self.images = data.get('image')
++ self.links = data.get('_links')
++ # self.self = data.get('self')
++ # self.voice = data.get('voice')
++ self.person = Person(person)
++
++ def __str__(self):
++ # return self.name + ': ' + self.person
++ return self.name + ': ' + self.person.name
++
++
++class Crew(Person):
++ def __init__(self, data):
++ super(Crew, self).__init__(data.get('person'))
++ self.job = data.get('type')
++
++ def __str__(self):
++ return self.job + ': ' + str(super())
+diff --git a/mythtv/bindings/python/tvmaze/season.py b/mythtv/bindings/python/tvmaze/season.py
+new file mode 100644
+index 00000000000..d407e2a2451
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/season.py
+@@ -0,0 +1,56 @@
++# -*- coding: UTF-8 -*-
++
++# Copyright (c) 2020 Lachlan Mackenzie
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++
++# ---------------------------------------------------
++# Roland Ernst
++# Changes implemented for MythTV:
++# - added method utils.convert_date
++#
++# ---------------------------------------------------
++
++
++from __future__ import unicode_literals
++
++import string
++from . import utils
++
++
++class Season(object):
++ def __init__(self, data, special=False):
++ self.id = data.get('id')
++ self.url = data.get('url')
++ self.number = data.get('number')
++ self.name = data.get('name')
++ self.num_episodes = data.get('episodeOrder')
++ self.episodes = {}
++ self.premiere_date = utils.convert_date(data.get('premiereDate'))
++ self.end_date = data.get('endDate')
++ self.network = data.get('network')
++ self.streaming_service = data.get('webChannel')
++ self.images = data.get('image')
++ self.summary = ""
++ self.summary = utils.strip_tags(data.get('summary'))
++ self.links = data.get('_links')
++
++ def __str__(self):
++ return string.capwords(' '.join(self.url.split('/')[-1].split('-')))
+diff --git a/mythtv/bindings/python/tvmaze/show.py b/mythtv/bindings/python/tvmaze/show.py
+new file mode 100644
+index 00000000000..4d7983f967d
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/show.py
+@@ -0,0 +1,130 @@
++# -*- coding: UTF-8 -*-
++
++# Copyright (c) 2020 Lachlan Mackenzie
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++
++# ---------------------------------------------------
++# Roland Ernst
++# Changes implemented for MythTV:
++# - added method utils.convert_date
++# - added embedded 'akas' and 'images'
++#
++# ---------------------------------------------------
++
++
++from __future__ import unicode_literals
++
++from . import utils
++from .season import Season
++from .episode import Episode
++from .person import Crew, Character
++
++
++class Show(object):
++ def __init__(self, data):
++ self.score = data.get('score') if 'score' in data else 100
++ show = data.get('show') if 'show' in data else data
++ self.id = show.get('id')
++ self.name = show.get('name')
++ self.url = show.get('url')
++ self.type = show.get('type')
++ self.language = show.get('language')
++ self.genres = show.get('genres')
++ self.status = show.get('status')
++ self.num_episodes = show.get('runtime')
++ self.seasons = {}
++ self._episode_list = []
++ self.specials = {}
++ self.cast = []
++ self.crew = []
++ self.akas = []
++ self.series_images = []
++ self._handle_embedded(data.get('_embedded'))
++ self.premiere_date = utils.convert_date(show.get('premiered'))
++ self.official_site = show.get('officialSite')
++ self.schedule = show.get('schedule')
++ self.rating = show.get('rating')
++ self.weight = show.get('weight')
++ self.network = show.get('network')
++ self.streaming_service = show.get('webChannel')
++ self.external_ids = show.get('externals')
++ self.images = show.get('image')
++ self.summary = utils.strip_tags(show.get('summary'))
++ self.links = show.get('_links')
++
++ def _handle_embedded(self, embedded):
++ if embedded is None:
++ return
++
++ special_num = 1
++ if 'seasons' in embedded:
++ seasons = [Season(season) for season in embedded.get('seasons')]
++ for season in seasons:
++ self.seasons[season.number] = season
++
++ if 'seasons' in embedded and 'episodes' in embedded:
++ episodes = [Episode(episode) for episode in embedded.get('episodes')]
++ for episode in episodes:
++ if not episode.special:
++ self.seasons[episode.season].episodes[episode.number] = episode
++ else:
++ episode.season = 0
++ episode.number = special_num
++ special_num += 1
++ self.specials[episode.id] = episode
++
++ if 'seasons' not in embedded and 'episodes' in embedded:
++ episodes = [Episode(episode) for episode in embedded.get('episodes')]
++ for episode in episodes:
++ if not episode.special:
++ self._episode_list.append(episode)
++ else:
++ episode.season = 0
++ episode.number = special_num
++ special_num += 1
++ self.specials[episode.id] = episode
++
++ self.cast = [Character(c.get('character'), c.get('person')) for c in embedded.get('cast')] if 'cast' in embedded else []
++ self.crew = [Crew(c) for c in embedded.get('crew')] if 'crew' in embedded else []
++
++ if 'akas' in embedded:
++ self.akas = [Alias(aka) for aka in embedded.get('akas')]
++
++ if 'images' in embedded:
++ self.series_images = [i for i in embedded.get('images')]
++
++ def __str__(self):
++ return str(self.id) + ': ' + self.name
++
++
++class Alias:
++ def __init__(self, data):
++ self.name = data.get('name')
++ if data['country'] is not None:
++ self.country = data.get('country')
++ else:
++ self.country = {}
++ self.country['name'] = 'Original Country'
++ self.country['code'] = 'OG'
++ self.country['timezome'] = 'Original Country Timezone'
++
++ def __str__(self):
++ return self.country.get('name') + ': ' + self.name
+diff --git a/mythtv/bindings/python/tvmaze/tvmaze_api.py b/mythtv/bindings/python/tvmaze/tvmaze_api.py
+new file mode 100644
+index 00000000000..3a5e2409973
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/tvmaze_api.py
+@@ -0,0 +1,216 @@
++# -*- coding: UTF-8 -*-
++
++# Copyright (c) 2020 Lachlan Mackenzie
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++
++
++# ---------------------------------------------------
++# Roland Ernst
++# Changes implemented for MythTV:
++# - added support for requests.Session()
++#
++# ---------------------------------------------------
++
++
++from __future__ import unicode_literals
++
++from datetime import datetime
++from requests import codes as requestcodes
++import sys
++import time
++from dateutil import parser
++from pprint import pprint
++
++from . import endpoints
++from .show import Show, Alias
++from .episode import Episode
++from .season import Season
++from .person import Character, Person, Crew
++from .embed import Embed
++
++
++MYTHTV_TVMAZE_API_VERSION = "0.1.0"
++
++# set this to true for showing raw json data
++JSONDEBUG = False
++
++# Set an open requests session here
++ReqSession = None
++
++
++def set_session(s):
++ global ReqSession
++ ReqSession = s
++
++
++def _query_api(url, params=None):
++ if JSONDEBUG:
++ print(params)
++
++ http_retries = 0
++ res = None
++ while http_retries < 2:
++ if ReqSession is None:
++ if JSONDEBUG:
++ print("Using requests directly, without cache")
++ print(url)
++ import requests
++ res = requests.get(url, params)
++ else:
++ if JSONDEBUG:
++ print("Using Request Session %s" % ReqSession)
++ print(url)
++ res = ReqSession.get(url, params=params)
++ if JSONDEBUG:
++ print(res.url)
++ print(res.request.headers)
++ if res.status_code == 429:
++ # wait a bit and do a retry once
++ if JSONDEBUG:
++ print("Rate Limiting, caused by 'HTTP Too Many Requests'")
++ if http_retries == 1:
++ print('Error: Exiting due to rate limitation')
++ sys.exit(1)
++ time.sleep(10)
++ http_retries += 1
++ elif res.status_code != requestcodes.OK:
++ print('Page request was unsuccessful: ' + str(res.status_code), res.reason)
++ sys.exit(1)
++ else:
++ if JSONDEBUG:
++ print("Successful http request '%s':" % res.url)
++ break
++
++ if JSONDEBUG:
++ pprint(res.json())
++ if res is None:
++ return None
++ else:
++ return res.json()
++
++
++def search_show(show_name):
++ res = _query_api(endpoints.search_show_name, {'q': show_name})
++ return [Show(show) for show in res] if res is not None else []
++
++
++def search_show_best_match(show_name, embed=None):
++ embed = Embed(embed)
++ res = _query_api(endpoints.search_show_best_match, {'q': show_name, embed.key: embed.value})
++ return Show(res) if res is not None else None
++
++
++def get_show_external(imdb_id=None, tvdb_id=None, tvrage_id=None):
++ if len(list(filter(None, [imdb_id, tvdb_id, tvrage_id]))) == 0:
++ return None
++ if imdb_id is not None:
++ return _get_show_external_id('imdb', imdb_id)
++ if tvdb_id is not None:
++ return _get_show_external_id('thetvdb', tvdb_id)
++ if tvrage_id is not None:
++ return _get_show_external_id('tvrage', tvrage_id)
++
++
++def _get_show_external_id(external_name, external_id):
++ res = _query_api(endpoints.search_external_show_id, {external_name: external_id})
++ return Show(res) if res is not None else None
++
++
++def get_show(tvmaze_id, populated=False, embed=None):
++ embed = Embed(embed) if not populated else Embed(['seasons', 'cast', 'crew', 'akas', 'images'])
++ res = _query_api(endpoints.show_information.format(str(tvmaze_id)), {embed.key: embed.value})
++ # print(res.keys())
++ if populated:
++ episodes = [episode for episode in _get_show_episode_list_raw(tvmaze_id, specials=True)]
++ # print(episodes)
++ res['_embedded']['episodes'] = episodes
++ return Show(res) if res is not None else None
++
++
++def get_show_episode_list(tvmaze_id, specials=False):
++ specials = 1 if specials else None
++ res = _get_show_episode_list_raw(tvmaze_id, specials)
++ return [Episode(episode) for episode in res] if res is not None else []
++
++
++def _get_show_episode_list_raw(tvmaze_id, specials):
++ return _query_api(endpoints.show_episode_list.format(str(tvmaze_id)), {'specials': specials})
++
++
++def get_show_specials(tvmaze_id):
++ res = _query_api(endpoints.show_episode_list.format(str(tvmaze_id)), {'specials': 1})
++ specials = [Episode(episode) for episode in res if episode['number'] is None] if res is not None else []
++ special_num = 1
++ for special in specials:
++ special.season = 0
++ special.number = special_num
++ special_num += 1
++ return specials
++
++
++def get_show_episode(tvmaze_id, season, episode):
++ res = _query_api(endpoints.show_episode.format(str(tvmaze_id)), {'season': season, 'number': episode})
++ return Episode(res) if res is not None else None
++
++
++def get_show_episodes_by_date(tvmaze_id, date_input):
++ if type(date_input) is str:
++ try:
++ date = parser.parse(date_input)
++ except parser._parser.ParserError: ### XXX check this
++ return []
++ elif type(date_input) is datetime:
++ date = date_input
++ else:
++ return []
++ res = _query_api(endpoints.show_episodes_on_date.format(str(tvmaze_id)), {'date': date.isoformat()[:10]})
++ return [Episode(episode) for episode in res] if res is not None else []
++
++
++def get_show_season_list(tvmaze_id):
++ res = _query_api(endpoints.show_season_list.format(str(tvmaze_id)))
++ return [Season(season) for season in res] if res is not None else []
++
++
++def get_season_episode_list(tvmaze_season_id):
++ res = _query_api(endpoints.season_episode_list.format(str(tvmaze_season_id)))
++ # episode['number'] is None when it is classed as a special, for now don't include specials
++ return [Episode(episode) for episode in res if episode['number'] is not None] if res is not None else []
++
++
++def get_show_aliases(tvmaze_id):
++ res = _query_api(endpoints.show_alias_list.format(str(tvmaze_id)))
++ return [Alias(alias) for alias in res] if res is not None else []
++
++
++def get_episode_information(tvmaze_episode_id, embed=None):
++ embed = Embed(embed)
++ res = _query_api(endpoints.episode_information.format(str(tvmaze_episode_id)), {embed.key: embed.value})
++ return Episode(res) if res is not None else None
++
++
++def get_show_cast(tvmaze_id):
++ res = _query_api(endpoints.show_cast.format(str(tvmaze_id)))
++ return [Character(cast['character'], cast['person']) for cast in res]
++
++
++def get_show_crew(tvmaze_id):
++ res = _query_api(endpoints.show_crew.format(str(tvmaze_id)))
++ return [Crew(crew_member) for crew_member in res]
+diff --git a/mythtv/bindings/python/tvmaze/utils.py b/mythtv/bindings/python/tvmaze/utils.py
+new file mode 100644
+index 00000000000..e89896be609
+--- /dev/null
++++ b/mythtv/bindings/python/tvmaze/utils.py
+@@ -0,0 +1,68 @@
++# -*- coding: UTF-8 -*-
++
++from __future__ import unicode_literals
++
++from datetime import datetime
++import sys
++
++
++if sys.version_info[0] == 2:
++ from HTMLParser import HTMLParser
++ from StringIO import StringIO
++
++
++ class MLStripper(HTMLParser):
++ def __init__(self):
++ self.reset()
++ self.text = StringIO()
++
++ def handle_data(self, d):
++ self.text.write(d)
++
++ def get_data(self):
++ return self.text.getvalue()
++
++else:
++ from io import StringIO
++ from html.parser import HTMLParser
++
++ class MLStripper(HTMLParser):
++ def __init__(self):
++ super().__init__()
++ self.reset()
++ self.strict = False
++ self.convert_charrefs= True
++ self.text = StringIO()
++
++ def handle_data(self, d):
++ self.text.write(d)
++
++ def get_data(self):
++ return self.text.getvalue()
++
++
++def strip_tags(html):
++ if html is not None and html is not "":
++ s = MLStripper()
++ s.feed(html)
++ return s.get_data()
++ else:
++ return ""
++
++
++def convert_date(tstring):
++ if tstring is None or tstring == '':
++ return None
++ try:
++ return datetime.strptime(tstring, '%Y-%m-%d').date()
++ except(TypeError, ValueError):
++ return None
++
++
++def convert_time(tstring):
++ if tstring is None or tstring == '':
++ return None
++ try:
++ return datetime.strptime(tstring, '%Y-%m-%d')
++ except(TypeError, ValueError):
++ return None
+diff --git a/mythtv/programs/scripts/metadata/Television/tvmaze.py b/mythtv/programs/scripts/metadata/Television/tvmaze.py
+new file mode 100755
+index 00000000000..078a646c187
+--- /dev/null
++++ b/mythtv/programs/scripts/metadata/Television/tvmaze.py
+@@ -0,0 +1,553 @@
++#!/usr/bin/env python3
++# -*- coding: UTF-8 -*-
++
++from __future__ import unicode_literals
++
++__title__ = "TVmaze.com"
++__author__ = "Roland Ernst"
++__version__ = "0.1.0"
++
++
++import sys
++import os
++import shlex
++from optparse import OptionParser
++
++
++def print_etree(etostr):
++ """lxml.etree.tostring is a bytes object in python3, and a str in python2.
++ """
++ if sys.version_info[0] == 2:
++ sys.stdout.write(etostr)
++ else:
++ sys.stdout.write(etostr.decode("utf-8"))
++
++
++def check_item(m, mitem, ignore=True):
++ # item is a tuple of (str, value)
++ # ToDo: Add this to the 'Metadata' class of MythTV's python bindings
++ try:
++ k, v = mitem
++ if v is None:
++ return None
++ m._inv_trans[m._global_type[k]](v)
++ return v
++ except:
++ if ignore:
++ return None
++ else:
++ raise
++
++
++def _get_series_image(item):
++ url = item['resolutions']['original']['url']
++ if item['resolutions'].get('medium') is not None:
++ thumb = item['resolutions']['medium']['url']
++ pdict = {'type': 'fanart', 'url': url, 'thumb': thumb}
++ else:
++ pdict = {'type': 'fanart', 'url': url}
++ return pdict
++
++
++def buildList(tvtitle, opts):
++ # option -M title
++ from lxml import etree
++ from MythTV import VideoMetadata, datetime
++ from MythTV.utility import levenshtein
++ from MythTV.tvmaze import tvmaze_api as tvmaze
++ from MythTV.tvmaze import locales
++
++ # set the session
++ if opts.session:
++ tvmaze.set_session(opts.session)
++
++ if opts.debug:
++ print("Function 'buildList' called with argument '%s'" % tvtitle)
++
++ showlist = tvmaze.search_show(tvtitle)
++
++ if opts.debug:
++ print("tvmaze.search_show(%s) returned :" % tvtitle)
++ for l in showlist:
++ print(l, type(l))
++ for k, v in l.__dict__.items():
++ print(k, " : ", v)
++
++ tree = etree.XML(u'<metadata></metadata>')
++
++ for show_info in showlist:
++ m = VideoMetadata()
++ m.title = check_item(m, ("title", show_info.name), ignore=False)
++ m.description = check_item(m, ("description", show_info.summary))
++ m.inetref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
++ m.collectionref = check_item(m, ("collectionref", str(show_info.id)), ignore=False)
++ m.language = check_item(m, ("language", str(locales.Language.getstored(show_info.language))))
++ if show_info.premiere_date:
++ m.releasedate = check_item(m, ("releasedate", show_info.premiere_date))
++ m.year = check_item(m, ("year", show_info.premiere_date.year))
++ if show_info.images is not None and len(show_info.images) > 0:
++ m.images.append({'type': 'coverart', 'url': show_info.images['original'],
++ 'thumb': show_info.images['medium']})
++ tree.append(m.toXML())
++
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True))
++
++
++def buildNumbers(args, opts):
++ # either option -N inetref subtitle
++ # or option -N title subtitle
++ from lxml import etree
++ from MythTV import VideoMetadata, datetime
++ from MythTV.utility import levenshtein
++ from MythTV.tvmaze import tvmaze_api as tvmaze
++ from MythTV.tvmaze import locales
++
++ if opts.debug:
++ print("Function 'buildNumbers' called with arguments: " +
++ (" ".join(["'%s'" % i for i in args])))
++
++ # set the session
++ if opts.session:
++ tvmaze.set_session(opts.session)
++
++ # ToDo:
++ # below check shows a deficiency of the MythTV grabber API itself:
++ # TV-Shows or Movies with an integer as title are not recognized correctly.
++ # see https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
++ # and https://code.mythtv.org/trac/ticket/11850
++ try:
++ inetref = int(args[0])
++ tvsubtitle = args[1]
++ except ValueError:
++ tvtitle = args[0]
++ tvsubtitle = args[1]
++ serie = tvmaze.search_show_best_match(tvtitle)
++ try:
++ # inetref = str(serie.id)
++ inetref = int(serie.id)
++ if opts.debug:
++ print("tvmaze.search_show_best_match(%s) returned inetref : %s"
++ % (tvtitle, inetref))
++ if 0 and opts.debug:
++ print(serie, type(serie))
++ for k, v in serie.__dict__.items():
++ print(k, " : ", v)
++ except(TypeError, ValueError):
++ raise Exception("Cannot resolve argument 'inetref'")
++
++ # get episode based on subtitle
++ episodes = tvmaze.get_show_episode_list(inetref)
++
++ best_ep_index = None
++ ep_distance = 5 ### XXX read distance from settings
++ for i, ep in enumerate(episodes):
++ if 0 and opts.debug:
++ print("tvmaze.vmaze.get_show_episode_list(%s) returned :" % inetref)
++ for k, v in ep.__dict__.items():
++ print(k, " : ", v)
++ distance = levenshtein(ep.name, tvsubtitle)
++ if distance < ep_distance:
++ best_ep_index = i
++ ep_distance = distance
++ if len(episodes) > 0 and best_ep_index is not None:
++ season_nr = str(episodes[best_ep_index].season)
++ episode_nr = str(episodes[best_ep_index].number)
++ episode_id = episodes[best_ep_index].id
++ if opts.debug:
++ print("tvmaze.vmaze.get_show_episode_list(%s) returned :" % inetref)
++ print("with season : %s and episode %s" % (season_nr, episode_nr))
++ print("Chosen episode index '%d' based on levenshtein distance '%d'."
++ % (best_ep_index, ep_distance))
++
++ # we have now inetref, season, episode, episode_id
++ buildSingle([inetref, season_nr, episode_nr], opts, tvmaze_episode_id=episode_id)
++ else:
++ # tvmaze.py -N 4711 "Episode 42"
++ raise Exception("Cannot find episodes for inetref '%s'." % inetref)
++
++
++def buildSingle(args, opts, tvmaze_episode_id=None):
++ """
++ The tvmaze api returns different id's for season, episode and series.
++ MythTV stores only the series-id, therefore we need to fetch the correct id's
++ for season and episode for that series-id.
++ """
++ # option -D inetref season episode
++
++ from lxml import etree
++ from MythTV import VideoMetadata, datetime
++ from MythTV.utility import levenshtein
++ from MythTV.tvmaze import tvmaze_api as tvmaze
++ from MythTV.tvmaze import locales
++
++ if opts.debug:
++ dstr = "Function 'buildSingle' called with arguments: " + \
++ (" ".join(["'%s'" % i for i in args]))
++ if tvmaze_episode_id is not None:
++ dstr += "tvmaze_episode_id = %d" % tvmaze_episode_id
++ print(dstr)
++ inetref = args[0]
++ season = args[1]
++ episode = args[2]
++
++ # set the session
++ if opts.session:
++ tvmaze.set_session(opts.session)
++
++ # get the episode_id if not provided:
++ if tvmaze_episode_id is None:
++ episodes = tvmaze.get_show_episode_list(inetref)
++ for ep in (episodes):
++ if 0 and opts.debug:
++ print("tvmaze.vmaze.get_show_episode_list(%s) returned :" % inetref)
++ for k, v in ep.__dict__.items():
++ print(k, " : ", v)
++ if ep.season == int(season) and ep.number == int(episode):
++ tvmaze_episode_id = ep.id
++ ### XXX check if season == int(ep.season) ### XXX
++ if opts.debug:
++ print(" Found tvmaze_episode_id : %d" % tvmaze_episode_id)
++ break
++
++ # get info for dedicated season:
++ ep_info = tvmaze.get_episode_information(tvmaze_episode_id)
++ if opts.debug:
++ for k, v in ep_info.__dict__.items():
++ print(k, " : ", v)
++
++ # get global info for all seasons/episodes:
++ show_info = tvmaze.get_show(inetref, populated=True)
++ if opts.debug:
++ print("tvmaze.get_show(%s, populated=True) returned :" % inetref)
++ for k, v in show_info.__dict__.items():
++ print(k, " : ", v)
++ if 0 and opts.debug:
++ for c in show_info.cast:
++ for k, v in c.__dict__.items():
++ print(k, " : ", v)
++ for c in show_info.crew:
++ for k, v in c.__dict__.items():
++ print(k, " : ", v)
++
++ # get info for dedicated season:
++ season_info = show_info.seasons[int(season)]
++ if opts.debug:
++ for k, v in season_info.__dict__.items():
++ print(k, " : ", v)
++
++ # build xml:
++ tree = etree.XML(u'<metadata></metadata>')
++ m = VideoMetadata()
++ if show_info.genres is not None and len(show_info.genres) > 0:
++ for g in show_info.genres:
++ try:
++ if g is not None and len(g) > 0:
++ m.categories.append(g)
++ except:
++ pass
++ m.title = check_item(m, ("title", show_info.name), ignore=False)
++ m.subtitle = check_item(m, ("title", ep_info.name), ignore=False)
++ m.season = check_item(m, ("season", ep_info.season), ignore=False)
++ m.episode = check_item(m, ("episode", ep_info.number), ignore=False)
++ m.description = check_item(m, ("description", ep_info.summary))
++ if m.description is None:
++ m.description = check_item(m, ("description", show_info.summary))
++ try:
++ sinfo = show_info.network['name']
++ if sinfo is not None and len(sinfo) > 0:
++ m.studios.append(sinfo)
++ except:
++ pass
++ m.inetref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
++ m.collectionref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
++ m.language = check_item(m, ("language", str(locales.Language.getstored(show_info.language))))
++ # prefer episode airdate dates:
++ if ep_info.airdate:
++ m.releasedate = check_item(m, ("releasedate", ep_info.airdate))
++ m.year = check_item(m, ("year", ep_info.airdate.year))
++ elif show_info.premiere_date:
++ m.releasedate = check_item(m, ("releasedate", show_info.premiere_date))
++ m.year = check_item(m, ("year", show_info.premiere_date.year))
++ m.runtime = check_item(m, ("runtime", int(ep_info.duration)))
++
++ for actor in show_info.cast:
++ try:
++ if len(actor.person.name) > 0 and len(actor.name) > 0:
++ d = {'name': actor.person.name, 'character': actor.name, 'job': 'Actor'}
++ ### , 'department': 'Actors'}
++ m.people.append(d)
++ except:
++ pass
++
++ for member in show_info.crew:
++ try:
++ if len(member.name) > 0 and len(member.job) > 0:
++ d = {'name': member.name, 'job': member.job}
++ m.people.append(d)
++ except:
++ pass
++
++ # prefer season coverarts over series coverart:
++ if season_info.images is not None and len(season_info.images) > 0:
++ m.images.append({'type': 'coverart', 'url': season_info.images['original'],
++ 'thumb': season_info.images['medium']})
++ if show_info.images is not None and len(show_info.images) > 0:
++ m.images.append({'type': 'coverart', 'url': show_info.images['original'],
++ 'thumb': show_info.images['medium']})
++ # screenshot is associated to episode
++ if ep_info.images is not None and len(ep_info.images) > 0:
++ m.images.append({'type': 'screenshot', 'url': ep_info.images['original'],
++ 'thumb': ep_info.images['medium']})
++
++ if show_info.series_images is not None and len(show_info.series_images) > 0:
++ # sort for 'background' and then for 'poster'
++ for item in show_info.series_images:
++ if item['type'] == 'background' and not item['main']:
++ try:
++ pdict = _get_series_image(item)
++ if pdict not in m.images:
++ m.images.append(pdict)
++ except(KeyError, ValueError):
++ pass
++
++ ## posters cannot be stretched to fullscreen as fanart ### XXX
++ #for item in show_info.series_images:
++ # if item['type'] == 'poster' and not item['main']:
++ # try:
++ # pdict = _get_series_image(item)
++ # if pdict not in m.images:
++ # m.images.append(pdict)
++ # except(KeyError, ValueError):
++ # pass
++
++ tree.append(m.toXML())
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True))
++
++
++def buildCollection(tvinetref, opts):
++ # option -C inetref
++ from lxml import etree
++ from MythTV import VideoMetadata, datetime
++ from MythTV.utility import levenshtein
++ from MythTV.tvmaze import tvmaze_api as tvmaze
++ from MythTV.tvmaze import locales
++
++ # set the session
++ if opts.session:
++ tvmaze.set_session(opts.session)
++
++ if opts.debug:
++ print("Function 'buildCollection' called with argument '%s'" % tvinetref)
++
++ show_info = tvmaze.get_show(tvinetref)
++ if opts.debug:
++ for k, v in show_info.__dict__.items():
++ print(k, " : ", v)
++
++ tree = etree.XML(u'<metadata></metadata>')
++ m = VideoMetadata()
++ m.title = check_item(m, ("title", show_info.name), ignore=False)
++ m.description = check_item(m, ("description", show_info.summary))
++ if show_info.genres is not None and len(show_info.genres) > 0:
++ for g in show_info.genres:
++ try:
++ if g is not None and len(g) > 0:
++ m.categories.append(g)
++ except:
++ pass
++ m.inetref = check_item(m, ("inetref", str(show_info.id)), ignore=False)
++ m.collectionref = check_item(m, ("collectionref", str(show_info.id)), ignore=False)
++ m.imdb = check_item(m, ("imdb", str(show_info.external_ids['imdb'])))
++ m.language = check_item(m, ("language", str(locales.Language.getstored(show_info.language))))
++ m.userrating = check_item(m, ("userrating", show_info.rating['average']))
++ if show_info.premiere_date:
++ m.releasedate = check_item(m, ("releasedate", show_info.premiere_date))
++ m.year = check_item(m, ("year", show_info.premiere_date.year))
++ try:
++ sinfo = show_info.network['name']
++ if sinfo is not None and len(sinfo) > 0:
++ m.studios.append(sinfo)
++ except:
++ pass
++ if show_info.images is not None and len(show_info.images) > 0:
++ m.images.append({'type': 'coverart', 'url': show_info.images['original'],
++ 'thumb': show_info.images['medium']})
++ tree.append(m.toXML())
++
++ print_etree(etree.tostring(tree, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True))
++
++
++def buildVersion():
++ from lxml import etree
++ version = etree.XML(u'<grabber></grabber>')
++ etree.SubElement(version, "name").text = __title__
++ etree.SubElement(version, "author").text = __author__
++ etree.SubElement(version, "thumbnail").text = 'tvmaze.png'
++ etree.SubElement(version, "command").text = 'tvmaze.py'
++ etree.SubElement(version, "type").text = 'television'
++ etree.SubElement(version, "description").text = \
++ 'Search and metadata downloads for tvmaze.com'
++ etree.SubElement(version, "version").text = __version__
++ print_etree(etree.tostring(version, encoding='UTF-8', pretty_print=True,
++ xml_declaration=True))
++ sys.exit(0)
++
++
++def performSelfTest(opts):
++ err = 0
++ try:
++ import lxml
++ except:
++ err = 1
++ print("Failed to import python lxml library.")
++ try:
++ import requests
++ import requests_cache
++ except:
++ err = 1
++ print("Failed to import python-requests or python-request-cache library.")
++ try:
++ import MythTV
++ except:
++ err = 1
++ print("Failed to import MythTV bindings. Check your `configure` output "
++ "to make sure installation was not disabled due to external "
++ "dependencies")
++ try:
++ from MythTV.tvmaze import tvmaze_api as tvmaze
++ if opts.debug:
++ print("File location: ", tvmaze.__file__)
++ print("TVMAZE Script Version: ", __version__)
++ print("TVMAZE-API version: ", tvmaze.MYTHTV_TVMAZE_API_VERSION)
++ except:
++ err = 1
++ print("Failed to import PyTVmaze library. This should have been included "
++ "with the python MythTV bindings.")
++ if not err:
++ print("Everything appears in order.")
++ sys.exit(err)
++
++
++def main():
++ """
++ Main executor for MythTV's tvmaze grabber.
++ """
++
++ parser = OptionParser()
++
++ parser.add_option('-v', "--version", action="store_true", default=False,
++ dest="version", help="Display version and author")
++ parser.add_option('-t', "--test", action="store_true", default=False,
++ dest="test", help="Perform self-test for dependencies.")
++ parser.add_option('-M', "--list", action="store_true", default=False,
++ dest="tvlist", help="Get TV Shows matching search.")
++ parser.add_option('-D', "--data", action="store_true", default=False,
++ dest="tvdata", help="Get TV Show data.")
++ parser.add_option('-C', "--collection", action="store_true", default=False,
++ dest="collectiondata", help="Get Collection data.")
++ parser.add_option('-N', "--numbers", action="store_true", default=False,
++ dest="tvnumbers", help="Get Season and Episode numbers")
++ parser.add_option('-l', "--language", metavar="LANGUAGE", default=u'en',
++ dest="language", help="Specify language for filtering.")
++ parser.add_option('-a', "--area", metavar="COUNTRY", default=None,
++ dest="country", help="Specify country for custom data.")
++ parser.add_option('--debug', action="store_true", default=False,
++ dest="debug", help=("Disable caching and enable raw "
++ "data output."))
++ parser.add_option('--doctest', action="store_true", default=False,
++ dest="doctest", help="Run doctests")
++
++ opts, args = parser.parse_args()
++
++ if opts.debug:
++ print("Args: ", args)
++ print("Opts: ", opts)
++
++ if opts.doctest:
++ import doctest
++ opts.debug = False
++
++ try:
++ with open("tvmaze_tests.txt") as f:
++ if sys.version_info[0] == 2:
++ dtests = b"".join(f.readlines()).decode('utf-8')
++ else:
++ dtests = "".join(f.readlines())
++ main.__doc__ += dtests
++ except IOError:
++ pass
++ # perhaps try optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
++ doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS)
++
++ if opts.version:
++ buildVersion()
++
++ if opts.test:
++ performSelfTest(opts)
++
++ if opts.debug:
++ import requests
++ pass
++ else:
++ confdir = os.environ.get('MYTHCONFDIR', '')
++ if (not confdir) or (confdir == '/'):
++ confdir = os.environ.get('HOME', '')
++ if (not confdir) or (confdir == '/'):
++ print("Unable to find MythTV directory for metadata cache.")
++ sys.exit(1)
++ confdir = os.path.join(confdir, '.mythtv')
++ cachedir = os.path.join(confdir, 'cache')
++ if not os.path.exists(cachedir):
++ os.makedirs(cachepath)
++ if sys.version_info[0] == 2:
++ cache_name = os.path.join(cachedir, 'py2tvmaze')
++ else:
++ cache_name = os.path.join(cachedir, 'py3tvmaze')
++ import requests
++ import requests_cache
++ requests_cache.install_cache(cache_name, backend='sqlite', expire_after=3600)
++
++ with requests.Session() as s:
++ s.headers.update({'Accept': 'application/json',
++ 'User-Agent': 'mythtv tvmaze grabber %s' % __version__})
++ opts.session = s
++ try:
++ if opts.tvlist:
++ # option -M title
++ if (len(args) != 1) or (len(args[0]) == 0):
++ sys.stdout.write('ERROR: tvmaze -M requires exactly one non-empty argument')
++ sys.exit(1)
++ buildList(args[0], opts)
++
++ if opts.tvnumbers:
++ # either option -N inetref subtitle
++ # or option -N title subtitle
++ if (len(args) != 2) or (len(args[0]) == 0) or (len(args[1]) == 0):
++ sys.stdout.write('ERROR: tvmaze -N requires exactly two non-empty arguments')
++ sys.exit(1)
++ buildNumbers(args, opts)
++
++ if opts.tvdata:
++ # option -D inetref season episode
++ if (len(args) != 3) or (len(args[0]) == 0) or (len(args[1]) == 0) or (len(args[2]) == 0):
++ sys.stdout.write('ERROR: tvmaze -D requires exactly three non-empty arguments')
++ sys.exit(1)
++ buildSingle(args, opts)
++
++ if opts.collectiondata:
++ if (len(args) != 1) or (len(args[0]) == 0):
++ sys.stdout.write('ERROR: tvmaze -C requires exactly one non-empty argument')
++ sys.exit(1)
++ buildCollection(args[0], opts)
++ except:
++ if opts.debug:
++ raise
++ sys.stdout.write('ERROR: ' + str(sys.exc_info()[0]) + ' : ' + str(sys.exc_info()[1]) + '\n')
++ sys.exit(1)
++
++
++if __name__ == "__main__":
++ main()
+diff --git a/mythtv/programs/scripts/metadata/Television/tvmaze_tests.txt b/mythtv/programs/scripts/metadata/Television/tvmaze_tests.txt
+new file mode 100644
+index 00000000000..644b6c10fe1
+--- /dev/null
++++ b/mythtv/programs/scripts/metadata/Television/tvmaze_tests.txt
+@@ -0,0 +1,350 @@
++Doc-Tests for tvmaze
++
++### Important Notice:
++### tvmaze shuffles the search order results which have an equal distance from time to time.
++### Various python versions ( 2.7, 3.6+ ) and/or lxml versions display xml attributes
++### in a different order. Please double check the output for these failure conditions.
++### Please do not raise an issue in one of above cases.
++
++
++# This might be sorted in different way: update this file if the sort order does not match
++# tvmaze.py -M "night a"
++>>> sys.argv = shlex.split('tvmaze -M "night a"')
++>>> main()
++<?xml version='1.0' encoding='UTF-8'?>
++<metadata>
++ <item>
++ <title>Tales for a Halloween Night</title>
++ <description>Tales for a Halloween Night is based on John Carpenter's award-winning graphic novel anthology of stories where he unites storytellers from movies, novels and comics for a collection of horror stories featuring graveyards, sunken ships and ghosts, among others.</description>
++ <inetref>30203</inetref>
++ <collectionref>30203</collectionref>
++ <language>en</language>
++ </item>
++ <item>
++ <title>A Clear Midsummer Night</title>
++ <description>The daughter of a real estate mogul Xia Wan Qing, has seemingly no way of retreating after a friend's betrayal and her boyfriend backing out of their wedding. Fortunately, she's saved by business genius Qiao Jin Fan. Jin Fan is a "playboy" and the future successor for Qiao corporation. He extends an offering hand and together they embark on a path of revenge. Each for reasons of their own, begin a love with "uncertain motives". After enduring circumstances because of their families' competing interests and a number of conspiracies the two find true love.</description>
++ <inetref>24598</inetref>
++ <collectionref>24598</collectionref>
++ <language>zh</language>
++ <releasedate>2013-02-15</releasedate>
++ <year>2013</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/94/235015.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/94/235015.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>A Night of Exploration</title>
++ <description>Weekly look at different parts of the world investigated and documented by National Geographic. </description>
++ <inetref>31623</inetref>
++ <collectionref>31623</collectionref>
++ <language>en</language>
++ <releasedate>2013-01-11</releasedate>
++ <year>2013</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/126/316260.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/126/316260.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>A Night at the Theatre</title>
++ <description>Entertainment and comedy at Glasgow's Theatre Royal.</description>
++ <inetref>42539</inetref>
++ <collectionref>42539</collectionref>
++ <language>en</language>
++ <releasedate>2019-02-24</releasedate>
++ <year>2019</year>
++ </item>
++ <item>
++ <title>A Night at The Classic</title>
++ <description>Mixing the best bits of stand-up comedy with hilarious backstage action, A Night at The Classic is raw, uncensored comedy at its best.</description>
++ <inetref>19206</inetref>
++ <collectionref>19206</collectionref>
++ <language>en</language>
++ <releasedate>2010-11-03</releasedate>
++ <year>2010</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/67/169985.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/67/169985.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>A Night with My Ex</title>
++ <description>What would you do if you had the chance to spend an entire night with your ex? Would you seek answers, try to gain closure, or make a play to get back together? For the first time since their split, ex-couples will be brought together for one night in a camera-rigged apartment with no crew. What they do or say during their night is completely up to them, but one thing is for sure. A Night with My Ex will be surprising, emotionally raw, and in many cases, totally hilarious.</description>
++ <inetref>27687</inetref>
++ <collectionref>27687</collectionref>
++ <language>en</language>
++ <releasedate>2017-07-18</releasedate>
++ <year>2017</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/118/296875.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/118/296875.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>Night Stalker: The Hunt for a Serial Killer</title>
++ <description>Beneath the sunlit glamour of 1985 LA lurks a relentlessly evil serial killer. In this true-crime story, two detectives won't rest until they catch him.</description>
++ <inetref>52474</inetref>
++ <collectionref>52474</collectionref>
++ <language>en</language>
++ <releasedate>2021-01-13</releasedate>
++ <year>2021</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/289/723250.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/289/723250.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>Boys on Film - A Night with Duran Duran</title>
++ <description>Iconic British rock band Duran Duran take over BBC Four for one night with exclusive access to the band, two newly-filmed documentaries bring incredible insights, showcase previously unseen archive footage and rare demo tapes, and reveal the thoughts and inspirations of the band across the decades.</description>
++ <inetref>37161</inetref>
++ <collectionref>37161</collectionref>
++ <language>en</language>
++ <releasedate>2018-06-29</releasedate>
++ <year>2018</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/158/396533.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/158/396533.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>Going Out</title>
++ <description>A mother and daughter who stand before the truth after a sudden tragic accident and deal with the meaning of family.</description>
++ <inetref>47528</inetref>
++ <collectionref>47528</collectionref>
++ <language>ko</language>
++ <releasedate>2020-05-04</releasedate>
++ <year>2020</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/253/634317.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/253/634317.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>Una luz en el camino</title>
++ <description></description>
++ <inetref>48110</inetref>
++ <collectionref>48110</collectionref>
++ <language>es</language>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/257/642920.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/257/642920.jpg"/>
++ </images>
++ </item>
++</metadata>
++
++# tvmaze.py -N "A Clear Midsummer Night" "Episode 42"
++>>> sys.argv = shlex.split('tvmaze -N "A Clear Midsummer Night" "Episode 42"')
++>>> main()
++<?xml version='1.0' encoding='UTF-8'?>
++<metadata>
++ <item>
++ <title>A Clear Midsummer Night</title>
++ <subtitle>Episode 42</subtitle>
++ <description></description>
++ <season>1</season>
++ <episode>42</episode>
++ <inetref>24598</inetref>
++ <collectionref>24598</collectionref>
++ <language>zh</language>
++ <releasedate>2013-02-23</releasedate>
++ <year>2013</year>
++ <runtime>50</runtime>
++ <categories>
++ <category name="Drama"/>
++ <category name="Romance"/>
++ </categories>
++ <studios>
++ <studio name="Hunan TV"/>
++ </studios>
++ <people>
++ <person name="Yang Mi" character="Xia Wan Qing" job="Actor"/>
++ <person name="Lau Hawick" character="Qiao Jin Fan" job="Actor"/>
++ <person name="Wu Jerry" character="Mo Ling Tian" job="Actor"/>
++ </people>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/94/235015.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/94/235015.jpg"/>
++ </images>
++ </item>
++</metadata>
++
++
++# python3 tvmaze.py -C 525
++>>> sys.argv = shlex.split('tvmaze -C 525')
++>>> main()
++<?xml version='1.0' encoding='UTF-8'?>
++<metadata>
++ <item>
++ <title>Gilmore Girls</title>
++ <description>Gilmore Girls is a drama centering around the relationship between a thirtysomething single mother and her teen daughter living in Stars Hollow, Connecticut.</description>
++ <inetref>525</inetref>
++ <imdb>tt0238784</imdb>
++ <collectionref>525</collectionref>
++ <language>en</language>
++ <releasedate>2000-10-05</releasedate>
++ <userrating>8.400000</userrating>
++ <year>2000</year>
++ <categories>
++ <category name="Drama"/>
++ <category name="Comedy"/>
++ <category name="Romance"/>
++ </categories>
++ <studios>
++ <studio name="The CW"/>
++ </studios>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/4/11308.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/4/11308.jpg"/>
++ </images>
++ </item>
++</metadata>
++
++
++# python3 tvmaze.py -D 525 5 2
++>>> sys.argv = shlex.split('tvmaze -D 525 5 2')
++>>> main()
++<?xml version='1.0' encoding='UTF-8'?>
++<metadata>
++ <item>
++ <title>Gilmore Girls</title>
++ <subtitle>A Messenger, Nothing More</subtitle>
++ <description>T.J. is milking his injuries for all he's worth, but Luke decides that it's time to go home after seven weeks on the Ren Faire circuit; Rory calls to apologize and to ask her mother to deliver a letter to Dean; Sookie accurately diagnoses Lorelai with a bad case of management burnout, and the two friends decide to decompress with a girls-day-out; Emily and Rory come home exhausted from fending off the attentions of randy European men; Michel is wildly unenthusiastic when he's pressed into babysitting for guests whose children take an inexplicable shine to him; after Lindsay finds Rory's letter, she throws Dean and all his possessions out of the house, and her mother has an angry confrontation with Lorelai in the town square; Lane realizes that she's falling for Zack; Rory is saddened when Dean expresses regret for the shift in their relationship and the harm it caused to his wife and their families.</description>
++ <season>5</season>
++ <episode>2</episode>
++ <inetref>525</inetref>
++ <collectionref>525</collectionref>
++ <language>en</language>
++ <releasedate>2004-09-28</releasedate>
++ <year>2004</year>
++ <runtime>60</runtime>
++ <categories>
++ <category name="Drama"/>
++ <category name="Comedy"/>
++ <category name="Romance"/>
++ </categories>
++ <studios>
++ <studio name="The CW"/>
++ </studios>
++ <people>
++ <person name="Lauren Graham" character="Lorelai Gilmore" job="Actor"/>
++ <person name="Alexis Bledel" character="Rory Gilmore" job="Actor"/>
++ <person name="Scott Patterson" character="Luke Danes" job="Actor"/>
++ <person name="Melissa McCarthy" character="Sookie St. James" job="Actor"/>
++ <person name="Kelly Bishop" character="Emily Gilmore" job="Actor"/>
++ <person name="Sean Gunn" character="Kirk Gleason" job="Actor"/>
++ <person name="Keiko Agena" character="Lane Kim" job="Actor"/>
++ <person name="Liza Weil" character="Paris Geller" job="Actor"/>
++ <person name="Yanic Truesdale" character="Michel Gerard" job="Actor"/>
++ <person name="Edward Herrmann" character="Richard Gilmore" job="Actor"/>
++ <person name="Jared Padalecki" character="Dean Forester" job="Actor"/>
++ <person name="Matt Czuchry" character="Logan Huntzberger" job="Actor"/>
++ <person name="Milo Ventimiglia" character="Jess Mariano" job="Actor"/>
++ <person name="Chris Eigeman" character="Jason Stiles" job="Actor"/>
++ <person name="Helen Pai" job="Co-Producer"/>
++ <person name="Helen Pai" job="Producer"/>
++ <person name="Jane Espenson" job="Co-Executive Producer"/>
++ <person name="Chris Long" job="Co-Executive Producer"/>
++ <person name="Chris Long" job="Supervising Producer"/>
++ <person name="Michael Katleman" job="Co-Executive Producer"/>
++ <person name="Michael Katleman" job="Consulting Producer"/>
++ <person name="Rina Mimoun" job="Consulting Producer"/>
++ <person name="Lauren Graham" job="Producer"/>
++ <person name="Chad Savage" job="Associate Producer"/>
++ <person name="John Stephens" job="Co-Producer"/>
++ <person name="Bill Prady" job="Co-Executive Producer"/>
++ <person name="Allan Heinberg" job="Consulting Producer"/>
++ <person name="Lee Shallat-Chemel" job="Co-Executive Producer"/>
++ <person name="David S. Rosenthal" job="Executive Producer"/>
++ <person name="Gina Fattore" job="Co-Executive Producer"/>
++ <person name="Daniel Palladino" job="Executive Producer"/>
++ <person name="Jennie Snyder Urman" job="Co-Producer"/>
++ <person name="David Babcock" job="Consulting Producer"/>
++ <person name="Keith Eisner" job="Supervising Producer"/>
++ <person name="Steve Turner" job="Associate Producer"/>
++ <person name="Jonathan C. Brody" job="Associate Producer"/>
++ <person name="David Grae" job="Co-Producer"/>
++ <person name="Geoffrey Hemwall" job="Associate Producer"/>
++ <person name="Rebecca Kirschner" job="Co-Executive Producer"/>
++ <person name="Rebecca Kirschner" job="Supervising Producer"/>
++ <person name="Rebecca Kirschner" job="Producer"/>
++ <person name="Jenji Kohan" job="Producer"/>
++ <person name="Hynndie Wali" job="Associate Producer"/>
++ <person name="Jessica Queller" job="Co-Producer"/>
++ <person name="Keira Morrisette" job="Associate Producer"/>
++ <person name="Gavin Polone" job="Executive Producer"/>
++ <person name="William Klug" job="Associate Producer"/>
++ <person name="Sheila R. Lawrence" job="Co-Executive Producer"/>
++ <person name="Sheila R. Lawrence" job="Supervising Producer"/>
++ <person name="Sheila R. Lawrence" job="Producer"/>
++ <person name="Janet Leahy" job="Consulting Producer"/>
++ <person name="Barbara Brace" job="Associate Producer"/>
++ <person name="Scott Kaufer" job="Supervising Producer"/>
++ <person name="Jed Seidel" job="Supervising Producer"/>
++ <person name="Amy Sherman-Palladino" job="Creator"/>
++ <person name="Amy Sherman-Palladino" job="Executive Producer"/>
++ <person name="Lynn Stevenson" job="Associate Producer"/>
++ <person name="Mea Squires" job="Associate Producer"/>
++ <person name="David Glazier" job="Associate Producer"/>
++ <person name="Gayle Abrams" job="Co-Executive Producer"/>
++ <person name="James Berg" job="Consulting Producer"/>
++ <person name="Mel Efros" job="Producer"/>
++ <person name="Joanne Waters" job="Consulting Producer"/>
++ <person name="Patricia Fass Palmer" job="Producer"/>
++ <person name="Kimberly Costello" job="Co-Executive Producer"/>
++ <person name="Stan Zimmerman" job="Consulting Producer"/>
++ <person name="Chrisann Verges" job="Producer"/>
++ </people>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/260/652344.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/260/652344.jpg"/>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/4/11308.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/4/11308.jpg"/>
++ <image type="fanart" url="http://static.tvmaze.com/uploads/images/original_untouched/87/217545.jpg"/>
++ <image type="fanart" url="http://static.tvmaze.com/uploads/images/original_untouched/87/217546.jpg"/>
++ <image type="fanart" url="http://static.tvmaze.com/uploads/images/original_untouched/222/557215.jpg"/>
++ </images>
++ </item>
++</metadata>
++
++
++
++# python3 tvmaze.py -M BÖsterreich
++>>> if sys.version_info[0] == 2:
++... sys.argv = shlex.split(u'tvmaze -M BÖsterreich'.encode('utf-8'))
++... else:
++... sys.argv = shlex.split('tvmaze -M BÖsterreich')
++...
++
++>>> main()
++<?xml version='1.0' encoding='UTF-8'?>
++<metadata>
++ <item>
++ <title>BÖsterreich</title>
++ <description></description>
++ <inetref>23361</inetref>
++ <collectionref>23361</collectionref>
++ <language>de</language>
++ <releasedate>2014-04-01</releasedate>
++ <year>2014</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/87/219221.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/87/219221.jpg"/>
++ </images>
++ </item>
++ <item>
++ <title>Willkommen Österreich</title>
++ <description></description>
++ <inetref>34194</inetref>
++ <collectionref>34194</collectionref>
++ <language>de</language>
++ <releasedate>2007-05-31</releasedate>
++ <year>2007</year>
++ <images>
++ <image type="coverart" url="http://static.tvmaze.com/uploads/images/original_untouched/141/352576.jpg" thumb="http://static.tvmaze.com/uploads/images/medium_portrait/141/352576.jpg"/>
++ </images>
++ </item>
++</metadata>
++
++
++
++
++
++
++
++
++##
++## Testing this traceback does not work:
++##
++# python3 tvmaze.py -N 4711 "Episode 42"
++# >>> sys.argv = shlex.split('tvmaze -N 4711 "Episode 42"')
++# >>> main()
++
++
+
+From b6ddf202a496dac180218a6581344251804f2086 Mon Sep 17 00:00:00 2001
+From: Roland Ernst <rcrernst(a)gmail.com>
+Date: Thu, 25 Feb 2021 19:18:27 +0100
+Subject: [PATCH 138/165] Fix minor issues on the TV grabber TVMmaze
+
+Syntax warnig during installation:
+/tvmaze/utils.py:45: SyntaxWarning: "is not" with a literal. Did you mean "!="?
+
+Create correct path to the cache file:
+Use 'cachedir' to create the cahe folder, if it does not exits.
+
+(cherry picked from commit 116a7154353742bf826cd6256f90f22c9531a322)
+---
+ mythtv/bindings/python/tvmaze/utils.py | 2 +-
+ mythtv/programs/scripts/metadata/Television/tvmaze.py | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/bindings/python/tvmaze/utils.py b/mythtv/bindings/python/tvmaze/utils.py
+index e89896be609..521d25ed422 100644
+--- a/mythtv/bindings/python/tvmaze/utils.py
++++ b/mythtv/bindings/python/tvmaze/utils.py
+@@ -42,7 +42,7 @@ def get_data(self):
+
+
+ def strip_tags(html):
+- if html is not None and html is not "":
++ if html is not None and html != "":
+ s = MLStripper()
+ s.feed(html)
+ return s.get_data()
+diff --git a/mythtv/programs/scripts/metadata/Television/tvmaze.py b/mythtv/programs/scripts/metadata/Television/tvmaze.py
+index 078a646c187..c80b5d9eb38 100755
+--- a/mythtv/programs/scripts/metadata/Television/tvmaze.py
++++ b/mythtv/programs/scripts/metadata/Television/tvmaze.py
+@@ -501,7 +501,7 @@ def main():
+ confdir = os.path.join(confdir, '.mythtv')
+ cachedir = os.path.join(confdir, 'cache')
+ if not os.path.exists(cachedir):
+- os.makedirs(cachepath)
++ os.makedirs(cachedir)
+ if sys.version_info[0] == 2:
+ cache_name = os.path.join(cachedir, 'py2tvmaze')
+ else:
+
+From e705d36a368134ef8871f92656ded94293bb7e0d Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sun, 28 Feb 2021 20:03:59 +0100
+Subject: [PATCH 139/165] No multirec for V4L2ENC and HDPVR devices
+
+In commit f58f474bb1 on 6 nov. 2019 the default for field schedgroup in new entries in
+table capturecard is set to 1 for all types of devices.
+This is the "Schedule as Group" setting that controls, among others, multirec.
+This means that since that commit multirec is by default enabled for all new capturecard entries.
+Multirec means that multiple recordings can be made simultaneously from one capturecard;
+the term multirec in MythTV refers both to recording different channels from one multiplex
+and to overlapping recordings of the same channel.
+
+The devices that encode an analog signal, the V4L2ENC and the HDPVR, can obviously receive only
+one channel at a time. They can however in principle support overlapping recordings but
+in MythTV configuration there is no differentiation between this form of multirec and the multirec
+that allows recording different channels.
+
+For both the V4L2ENC and the HDPVR the default for field schedgroup in new entries in
+table capturecard is now set to 0, disabling multirec for these types of devices.
+The general default, as defined in the database schema, is not changed.
+
+Note that the V4L2ENC devices have partial support for multirec and hence for these devices can,
+in the "Input Connections" dialog of mythtv-setup, the "Schedule as Group" be checked or unchecked.
+Multirec recordings will however fail.
+
+The HDPVR devices have no support for multirec at all and therefore they do not have
+the "Schedule as Group" configuration option.
+
+See also ticket #trac-13501.
+
+(cherry picked from commit 40e0a9889442c0828a499e34efc9bef137648097)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/videosource.cpp | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/videosource.cpp b/mythtv/libs/libmythtv/videosource.cpp
+index 9ddad8ba034..6ca651f2989 100644
+--- a/mythtv/libs/libmythtv/videosource.cpp
++++ b/mythtv/libs/libmythtv/videosource.cpp
+@@ -2320,6 +2320,19 @@ void ExternalConfigurationGroup::probeApp(const QString & path)
+ }
+ #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
+
++// Override database schema default, set schedgroup false
++class SchedGroupFalse : public MythUICheckBoxSetting
++{
++ public:
++ explicit SchedGroupFalse(const CaptureCard &parent) :
++ MythUICheckBoxSetting(new CaptureCardDBStorage(this, parent,
++ "schedgroup"))
++ {
++ setValue(false);
++ setVisible(false);
++ };
++};
++
+ HDPVRConfigurationGroup::HDPVRConfigurationGroup(CaptureCard &a_parent,
+ CardType &a_cardtype) :
+ m_parent(a_parent), m_cardInfo(new GroupSetting()),
+@@ -2339,6 +2352,9 @@ HDPVRConfigurationGroup::HDPVRConfigurationGroup(CaptureCard &a_parent,
+ a_cardtype.addTargetedChild("HDPVR", m_audioInput);
+ a_cardtype.addTargetedChild("HDPVR", new ChannelTimeout(m_parent, 15000, 2000));
+
++ // Override database schema default, set schedgroup false
++ a_cardtype.addTargetedChild("HDPVR", new SchedGroupFalse(m_parent));
++
+ connect(device, SIGNAL(valueChanged(const QString&)),
+ this, SLOT( probeCard( const QString&)));
+
+@@ -2376,6 +2392,9 @@ V4L2encGroup::V4L2encGroup(CaptureCard &parent, CardType& cardtype) :
+ m_cardInfo->setLabel(tr("Probed info"));
+ cardtype.addTargetedChild("V4L2ENC", m_cardInfo);
+
++ // Override database schema default, set schedgroup false
++ cardtype.addTargetedChild("V4L2ENC", new SchedGroupFalse(m_parent));
++
+ setVisible(false);
+
+ connect(m_device, SIGNAL(valueChanged(const QString&)),
+
+From 6c7c8b0351cd227fdfae5534b0eab710e1a5f8fe Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Fri, 5 Mar 2021 13:35:49 -0600
+Subject: [PATCH 140/165] database: legacy version of mc.sql
+
+Currently, for MariaDB versions below 10.3
+
+(cherry picked from commit fecb7766c22b54cfb5d8ecb6251be0b09b66f9eb)
+---
+ mythtv/database/legacy.mc.sql | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+ create mode 100644 mythtv/database/legacy.mc.sql
+
+diff --git a/mythtv/database/legacy.mc.sql b/mythtv/database/legacy.mc.sql
+new file mode 100644
+index 00000000000..be9554b5c8f
+--- /dev/null
++++ b/mythtv/database/legacy.mc.sql
+@@ -0,0 +1,21 @@
++# File: legacy.mc.sql
++#
++# For:
++# MariaDB versions below 10.3
++
++# Database creation:
++CREATE DATABASE IF NOT EXISTS mythconverg;
++
++# Localhost user creation.
++GRANT ALL ON mythconverg.* TO mythtv@localhost IDENTIFIED BY 'mythtv';
++FLUSH PRIVILEGES;
++
++# Repeate the above for a mythtv user on remote host(s). For example,
++# for all hosts on 192.168.1.0/24:
++# GRANT ALL ON mythconverg.* TO 'mythtv'@'192.168.1.%' IDENTIFIED BY 'mythtv';
++# FLUSH PRIVILEGES;
++
++# Temporary table privileges required for backend only (not additional users.)
++GRANT CREATE TEMPORARY TABLES ON mythconverg.* TO mythtv@localhost IDENTIFIED BY 'mythtv';
++FLUSH PRIVILEGES;
++ALTER DATABASE mythconverg DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+From 5efa91cf604f0f6af282f440b37b78d0613790d2 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Mon, 22 Mar 2021 00:16:59 +0100
+Subject: [PATCH 141/165] ATSC Closed Captions
+
+In avformatdecoder.cppp the closed captions packets are extracted from
+the video stream and sent to the decoders for processing.
+In commit 4880fe2427c1c83b861c3158fe7899865093cba0 of Nov 19, 2016 a check
+is introduced to prevent potential out-of-bound memory access.
+This check is not correct, causing the last closed caption packet
+in each buffer to be always discarded. This causes all sorts of
+issues in the rendering of the closed captions.
+The check on out-of-bound memory access is now corrected.
+
+Refs #326
+
+(cherry picked from commit 4528c7050c339500e4246ab16b9da36191b11df1)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/decoders/avformatdecoder.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
+index 7165b73581c..13cc3fa49ad 100644
+--- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
++++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp
+@@ -2876,7 +2876,7 @@ void AvFormatDecoder::DecodeCCx08(const uint8_t *buf, uint buf_size, bool scte)
+
+ bool had_608 = false;
+ bool had_708 = false;
+- for (uint cur = 0; cur + 3 < buf_size; cur += 3)
++ for (uint cur = 0; cur + 2 < buf_size; cur += 3)
+ {
+ uint cc_code = buf[cur];
+ bool cc_valid = (cc_code & 0x04) != 0U;
+
+From dbac81d4f979bce594646bc8e09819442f8f4d0f Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Fri, 12 Mar 2021 22:09:48 +0100
+Subject: [PATCH 142/165] CEA-608/VBI CC3 closed captions/subtitles
+
+The CEA-608 closed captions can show two different subtitle streams
+for two different languages, called CC1 and CC3.
+When present, these streams can be selected in the subtitle menu.
+Due to a bug only the CC1 stream is actually shown.
+This is now fixed and also the CC3 stream can now be shown.
+
+Refs #326
+
+(cherry picked from commit 78edc37a607ff95c08556cf53e41de692ec3c3dd)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/cc608reader.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/cc608reader.cpp b/mythtv/libs/libmythtv/cc608reader.cpp
+index 865c466c9d2..1d0f58f98e6 100644
+--- a/mythtv/libs/libmythtv/cc608reader.cpp
++++ b/mythtv/libs/libmythtv/cc608reader.cpp
+@@ -43,7 +43,7 @@ CC608Buffer *CC608Reader::GetOutputText(bool &changed)
+ last_changed = false;
+ int streamIdx = -1;
+ CC608Buffer *tmp = GetOutputText(last_changed, streamIdx);
+- if (last_changed && (streamIdx == m_ccMode))
++ if (last_changed && (((streamIdx << 4) & CC_MODE_MASK) == m_ccMode))
+ {
+ changed = true;
+ return tmp;
+
+From 563a05b7a89b09a18edf874fbe347b6255ee07d3 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Thu, 1 Apr 2021 21:57:53 +0200
+Subject: [PATCH 143/165] Show "Use FFmpeg's original MPEG-TS demuxer" option
+
+Make the option "Use FFmpeg's original MPEG-TS demuxer" in mythfrontend
+menu Setup/Video/Playback/General Playback available in all builds instead
+of only in the debug builds.
+This option has been proven useful when playing back problematic recordings
+of the Finnish YLE channel.
+
+Refs #351
+Refs #trac-13557
+
+(cherry picked from commit cdcbb4c7f920213a1383d14520be2d2611b6f043)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/programs/mythfrontend/globalsettings.cpp | 8 ++------
+ 1 file changed, 2 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp b/mythtv/programs/mythfrontend/globalsettings.cpp
+index f64c4f12882..d55226f620c 100644
+--- a/mythtv/programs/mythfrontend/globalsettings.cpp
++++ b/mythtv/programs/mythfrontend/globalsettings.cpp
+@@ -128,7 +128,6 @@ static HostTextEditSetting *VAAPIDevice()
+ }
+ #endif
+
+-#if CONFIG_DEBUGTYPE
+ static HostCheckBoxSetting *FFmpegDemuxer()
+ {
+ HostCheckBoxSetting *gc = new HostCheckBoxSetting("FFMPEGTS");
+@@ -138,11 +137,10 @@ static HostCheckBoxSetting *FFmpegDemuxer()
+ gc->setValue(false);
+
+ gc->setHelpText(PlaybackSettings::tr("Experimental: Enable this setting to "
+- "use FFmpeg's native demuxer. Things "
+- "will be broken."));
++ "use FFmpeg's native demuxer. "
++ "Try this when encountering playback issues."));
+ return gc;
+ }
+-#endif
+
+ static HostComboBoxSetting *PIPLocationComboBox()
+ {
+@@ -4263,9 +4261,7 @@ void PlaybackSettings::Load(void)
+ general->addChild(ContinueEmbeddedTVPlay());
+ general->addChild(LiveTVIdleTimeout());
+
+-#if CONFIG_DEBUGTYPE
+ general->addChild(FFmpegDemuxer());
+-#endif
+
+ general->addChild(new PlayBackScaling());
+
+
+From d4997f4629833ae0f7e25b116a291109bf9535dd Mon Sep 17 00:00:00 2001
+From: kingsley <krt(a)krt.com.au>
+Date: Sun, 18 Apr 2021 17:39:15 +1000
+Subject: [PATCH 144/165] Increase cut-off for subtitle buffer clear, too small
+ for SSA karaoke subs
+
+---
+ mythtv/libs/libmythtv/subtitlereader.cpp | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/subtitlereader.cpp b/mythtv/libs/libmythtv/subtitlereader.cpp
+index a8ff9382b46..33b1285c0ff 100644
+--- a/mythtv/libs/libmythtv/subtitlereader.cpp
++++ b/mythtv/libs/libmythtv/subtitlereader.cpp
+@@ -1,6 +1,10 @@
+ #include "mythlogging.h"
+ #include "subtitlereader.h"
+
++// If the count of subtitle buffers is greater than this, force a clear
++static const int MAX_BUFFERS_BEFORE_CLEAR = 175; // 125 too low for karaoke
++
++
+ SubtitleReader::~SubtitleReader()
+ {
+ ClearAVSubtitles();
+@@ -54,10 +58,12 @@ bool SubtitleReader::AddAVSubtitle(AVSubtitle &subtitle,
+ m_avSubtitles.m_buffers.push_back(subtitle);
+ // in case forced subtitles aren't displayed, avoid leaking by
+ // manually clearing the subtitles
+- if (m_avSubtitles.m_buffers.size() > 40)
++ if ( m_avSubtitles.m_buffers.size() > MAX_BUFFERS_BEFORE_CLEAR )
+ {
+- LOG(VB_GENERAL, LOG_ERR,
+- "SubtitleReader: >40 AVSubtitles queued - clearing.");
++ LOG( VB_GENERAL, LOG_ERR,
++ QString( "SubtitleReader: >%1 AVSubtitles queued - clearing." )
++ .arg( MAX_BUFFERS_BEFORE_CLEAR )
++ );
+ clearsubs = true;
+ }
+ m_avSubtitles.m_lock.unlock();
+
+From 05c16580e185435b45722cc590f715e1b78eff83 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sun, 18 Apr 2021 15:39:35 -0400
+Subject: [PATCH 145/165] tmdb3tv: Prevent exception when non-existent season
+ is requested.
+
+(cherry picked from commit a064e11921d19fd3ee08a09553e48b80b5eba6cf)
+---
+ mythtv/bindings/python/tmdb3/tmdb3/lookup.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/lookup.py b/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
+index 89b1d74d5db..e7d2229c41b 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
+@@ -315,6 +315,11 @@ def buildEpisode(args, opts):
+ episode_number = int(args[2])
+
+ episode = None
++
++ if season_number > series.number_of_seasons:
++ sys.stdout.write('ERROR: Episode not found: ' + str(args))
++ return 9
++
+ # process seasons backwards because it is more likely
+ # that you have a recent one than an old one
+ while season_number > 0:
+
+From b2ed400037de11c7e5546381e60cc9de7b1e15eb Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Sat, 1 May 2021 21:48:02 +0200
+Subject: [PATCH 146/165] Update file size before skip forward
+
+Get the actual file size before determining if it is
+possible to skip the requested amount of seconds forward.
+When the skip forward would go beyond the end of the file
+the skip is modified so that it goes to the end of the
+file instead. This gives the wrong result when the file
+size information is not correct.
+
+Backported from commit dbe165f81aa118871958b0663934a2f42edf464a in master.
+
+Refs #352
+---
+ mythtv/libs/libmythtv/mythplayer.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
+index 1eecc103cfe..1e4b34216b8 100644
+--- a/mythtv/libs/libmythtv/mythplayer.cpp
++++ b/mythtv/libs/libmythtv/mythplayer.cpp
+@@ -2337,6 +2337,10 @@ bool MythPlayer::FastForward(float seconds)
+ if (!m_videoOutput)
+ return false;
+
++ // Update m_totalFrames so we know how far we can skip
++ if (m_decoder)
++ m_decoder->SyncPositionMap();
++
+ if (m_ffTime <= 0)
+ {
+ float current = ComputeSecs(m_framesPlayed, true);
+
+From f7de8cea580dfd215dfe6d3e83201edb85647b8d Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Thu, 6 May 2021 11:21:36 -0400
+Subject: [PATCH 147/165] tmdb3 lookup: Prevent exception when there are no
+ posters.
+
+Index out of range occurred in number of posters was 0.
+
+(cherry picked from commit 25d4e223f600ba09fc0e3a7cabf26503846396b9)
+---
+ mythtv/bindings/python/tmdb3/tmdb3/lookup.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/lookup.py b/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
+index e7d2229c41b..9482280c2e6 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/lookup.py
+@@ -135,7 +135,7 @@ def buildSingle(inetref, opts):
+ print("locale_language : ", locale_language)
+
+ loc_posters = movie.posters
+- if loc_posters[0].language != locale_language \
++ if len(loc_posters) and loc_posters[0].language != locale_language \
+ and locale_language != system_language:
+ if opts.debug:
+ print("1: No poster found for language '%s', trying to sort posters by '%s' :"
+@@ -143,7 +143,7 @@ def buildSingle(inetref, opts):
+ loc_posters = sorted(movie.posters,
+ key = lambda x: x.language==system_language, reverse = True)
+
+- if loc_posters[0].language != system_language \
++ if len(loc_posters) and loc_posters[0].language != system_language \
+ and loc_posters[0].language != locale_language:
+ if opts.debug:
+ print("2: No poster found for language '%s', trying to sort posters by '%s' :"
+
+From 200e3f47576d29af307ba26aed8710f02e0df227 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Mon, 19 Apr 2021 20:56:39 +0200
+Subject: [PATCH 148/165] Update changed streams on PMT update
+
+On playback, when a new version of the PMT (program map table) is received, start
+updating the streams at the first stream that is changed, beginning with stream 0,
+instead of resetting all streams.
+This fixes playback problems that occur when there is a PMT version update in the
+first part of the recording that is scanned before the playback starts.
+At the start of the playback there is then a PMT change back to the previous version;
+this change causes a reset of all stream data; the video playback is then started
+with default size of 640x480 and default framerate of 29.97Hz.
+
+Fixes #351
+Fixes #trac-13557
+
+(cherry picked from commit ebeea33ad284f8827cd6caed6b30d100c4132eaf)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ .../FFmpeg/libavformat/mpegts-mythtv.c | 72 +++++++++----------
+ 1 file changed, 32 insertions(+), 40 deletions(-)
+
+diff --git a/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c b/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c
+index 149c737dda6..a361106164c 100644
+--- a/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c
++++ b/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c
+@@ -80,8 +80,8 @@ static int is_pat_same(MpegTSContext *mpegts_ctx,
+ int *pmt_pnums, int *pmts_pids, unsigned int pmt_count);
+
+ static void mpegts_add_stream(MpegTSContext *ts, int id, pmt_entry_t* item, uint32_t prog_reg_desc, int pcr_pid);
+-static int is_pmt_same(MpegTSContext *mpegts_ctx, pmt_entry_t* items,
+- int item_cnt);
++static int pmt_equal_streams(MpegTSContext *mpegts_ctx,
++ pmt_entry_t* items, int item_cnt);
+
+ typedef int PESCallback(MpegTSFilter *f, const uint8_t *buf, int len, int is_start, int64_t pos);
+
+@@ -1863,18 +1863,20 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
+ /* if the pmt has changed delete old streams,
+ * create new ones, and notify any listener.
+ */
+- if (!is_pmt_same(ts, items, last_item))
++ int equal_streams = pmt_equal_streams(ts, items, last_item);
++ if (equal_streams != last_item || ts->pid_cnt != last_item)
+ {
+ AVFormatContext *avctx = ts->stream;
+ int idx;
+ /* flush out old AVPackets */
+ ff_read_frame_flush(avctx);
++
+ /* delete old streams */
+- for (idx = ts->pid_cnt-1; idx>=0; idx--)
++ for (idx = ts->pid_cnt-1; idx >= equal_streams; idx--)
+ av_remove_stream(ts->stream, ts->pmt_pids[idx], 1);
+
+ /* create new streams */
+- for (idx = 0; idx < last_item; idx++)
++ for (idx = equal_streams; idx < last_item; idx++)
+ mpegts_add_stream(ts, h->id, &items[idx], prog_reg_desc, pcr_pid);
+
+ /* cache pmt */
+@@ -1918,19 +1920,15 @@ static int is_pat_same(MpegTSContext *mpegts_ctx,
+ return 1;
+ }
+
+-static int is_pmt_same(MpegTSContext *mpegts_ctx,
+- pmt_entry_t* items, int item_cnt)
++// Find number of equal streams in old and new pmt starting at 0
++// and stopping at the first different stream.
++static int pmt_equal_streams(MpegTSContext *mpegts_ctx,
++ pmt_entry_t* items, int item_cnt)
+ {
++ int limit = mpegts_ctx->pid_cnt < item_cnt ? mpegts_ctx->pid_cnt : item_cnt;
+ int idx;
+- if (mpegts_ctx->pid_cnt != item_cnt)
+- {
+-#ifdef DEBUG
+- av_log(NULL, AV_LOG_DEBUG, "mpegts_ctx->pid_cnt=%d != item_cnt=%d\n",
+- mpegts_ctx->pid_cnt, item_cnt);
+-#endif
+- return 0;
+- }
+- for (idx = 0; idx < item_cnt; idx++)
++
++ for (idx = 0; idx < limit; idx++)
+ {
+ /* check for pid */
+ int loc = find_in_list(mpegts_ctx->pmt_pids, items[idx].pid);
+@@ -1938,11 +1936,10 @@ static int is_pmt_same(MpegTSContext *mpegts_ctx,
+ {
+ #ifdef DEBUG
+ av_log(NULL, AV_LOG_DEBUG,
+- "find_in_list(..,[%d].pid=%d) => -1\n"
+- "is_pmt_same() => false\n",
++ "find_in_list(..,[%d].pid=%d) => -1\n",
+ idx, items[idx].pid);
+ #endif
+- return 0;
++ break;
+ }
+
+ /* check stream type */
+@@ -1951,11 +1948,10 @@ static int is_pmt_same(MpegTSContext *mpegts_ctx,
+ {
+ #ifdef DEBUG
+ av_log(NULL, AV_LOG_DEBUG,
+- "mpegts_ctx->pids[items[%d].pid=%d] => null\n"
+- "is_pmt_same() => false\n",
++ "mpegts_ctx->pids[items[%d].pid=%d] => null\n",
+ idx, items[idx].pid);
+ #endif
+- return 0;
++ break;
+ }
+ if (tss->type == MPEGTS_PES)
+ {
+@@ -1963,19 +1959,17 @@ static int is_pmt_same(MpegTSContext *mpegts_ctx,
+ if (!pes)
+ {
+ #ifdef DEBUG
+- av_log(NULL, AV_LOG_DEBUG, "pes == null, where idx %d\n"
+- "is_pmt_same() => false\n", idx);
++ av_log(NULL, AV_LOG_DEBUG, "pes == null, where idx %d\n", idx);
+ #endif
+- return 0;
++ break;
+ }
+ if (pes->stream_type != items[idx].type)
+ {
+ #ifdef DEBUG
+ av_log(NULL, AV_LOG_DEBUG,
+- "pes->stream_type != items[%d].type\n"
+- "is_pmt_same() => false\n", idx);
++ "pes->stream_type != items[%d].type\n", idx);
+ #endif
+- return 0;
++ break;
+ }
+ }
+ else if (tss->type == MPEGTS_SECTION)
+@@ -1984,35 +1978,33 @@ static int is_pmt_same(MpegTSContext *mpegts_ctx,
+ if (!sect)
+ {
+ #ifdef DEBUG
+- av_log(NULL, AV_LOG_DEBUG, "sect == null, where idx %d\n"
+- "is_pmt_same() => false\n", idx);
++ av_log(NULL, AV_LOG_DEBUG, "sect == null, where idx %d\n", idx);
+ #endif
+- return 0;
++ break;
+ }
+ if (sect->stream_type != items[idx].type)
+ {
+ #ifdef DEBUG
+ av_log(NULL, AV_LOG_DEBUG,
+- "sect->stream_type != items[%d].type\n"
+- "is_pmt_same() => false\n", idx);
++ "sect->stream_type != items[%d].type\n", idx);
+ #endif
+- return 0;
+- }
++ break;
++ }
+ }
+ else
+ {
+ #ifdef DEBUG
+ av_log(NULL, AV_LOG_DEBUG,
+- "tss->type != MPEGTS_PES, where idx %d\n"
+- "is_pmt_same() => false\n", idx);
++ "tss->type != MPEGTS_PES, where idx %d\n", idx);
+ #endif
+- return 0;
++ break;
+ }
+ }
+ #ifdef DEBUG
+- av_log(NULL, AV_LOG_DEBUG, "is_pmt_same() => true\n", idx);
++ av_log(NULL, AV_LOG_DEBUG, "pmt_equal_streams:%d old:%d new:%d limit:%d\n",
++ idx, mpegts_ctx->pid_cnt, item_cnt, limit);
+ #endif
+- return 1;
++ return idx;
+ }
+
+ static void mpegts_cleanup_streams(MpegTSContext *ts)
+
+From bcc01811cabd66eabc79987c5672a6a3d0c02243 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Thu, 27 Aug 2020 22:32:41 +0100
+Subject: [PATCH 149/165] V4LChannel: Remove unneeded definitions
+
+Refs #13653
+
+Signed-off-by: Mark Kendall <mark.kendall(a)gmail.com>
+(cherry picked from commit 4b3b92db97a6fabfcd2e4c857000db701e55571d)
+Signed-off-by: Stuart Auchterlonie <stuarta(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/recorders/v4lchannel.h | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/v4lchannel.h b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+index 34a6f5d7f59..0367e81ede1 100644
+--- a/mythtv/libs/libmythtv/recorders/v4lchannel.h
++++ b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+@@ -10,8 +10,6 @@
+
+ #ifdef USING_V4L2
+ #include "videodev2.h" // needed for v4l2_std_id type
+-#else
+-using v4l2_std_id = uint64_t;
+ #endif
+
+ using namespace std;
+@@ -20,8 +18,6 @@ using namespace std;
+
+ class TVRec;
+
+-using VidModV4L2 = QMap<int,v4l2_std_id>;
+-
+ /** \class V4LChannel
+ * \brief Implements tuning for TV cards using the V4L driver API,
+ * both versions 1 and 2.
+
+From 9b74773e3ca4c762c884eaaeb68077f236a45395 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Wed, 30 Sep 2020 12:08:32 +0100
+Subject: [PATCH 150/165] Use system videodev2.h
+
+The project no longer supports distributions with the older kernels that
+required having a local copy of videodev2.h.
+
+Refs #224
+
+Signed-off-by: Mark Kendall <mark.kendall(a)gmail.com>
+(cherry picked from commit 6e5e58b3e5ebb48789c9f191ff5b4cf2d13736c4)
+Signed-off-by: Stuart Auchterlonie <stuarta(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/recorders/v4lchannel.h | 2 +-
+ mythtv/libs/libmythtv/v4l2util.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/v4lchannel.h b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+index 0367e81ede1..66589066b4c 100644
+--- a/mythtv/libs/libmythtv/recorders/v4lchannel.h
++++ b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+@@ -9,7 +9,7 @@
+ #include "dtvchannel.h"
+
+ #ifdef USING_V4L2
+-#include "videodev2.h" // needed for v4l2_std_id type
++#include <linux/videodev2.h>
+ #endif
+
+ using namespace std;
+diff --git a/mythtv/libs/libmythtv/v4l2util.h b/mythtv/libs/libmythtv/v4l2util.h
+index 48322c78450..aeaa14df2f5 100644
+--- a/mythtv/libs/libmythtv/v4l2util.h
++++ b/mythtv/libs/libmythtv/v4l2util.h
+@@ -2,7 +2,7 @@
+ #define _V4L2_util_h_
+
+ #ifdef USING_V4L2
+-#include "videodev2.h" // our copy
++#include <linux/videodev2.h>
+ #endif
+
+ #include "tv.h"
+
+From fe5a6243cef9a838786225fb70c330d42be15bd0 Mon Sep 17 00:00:00 2001
+From: Stuart Auchterlonie <stuarta(a)mythtv.org>
+Date: Mon, 17 May 2021 23:31:46 +0100
+Subject: [PATCH 151/165] Revert "Use system videodev2.h" - Broke
+ debian-stretch and centos7 builds
+
+This reverts commit 9b74773e3ca4c762c884eaaeb68077f236a45395.
+---
+ mythtv/libs/libmythtv/recorders/v4lchannel.h | 2 +-
+ mythtv/libs/libmythtv/v4l2util.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/v4lchannel.h b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+index 66589066b4c..0367e81ede1 100644
+--- a/mythtv/libs/libmythtv/recorders/v4lchannel.h
++++ b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+@@ -9,7 +9,7 @@
+ #include "dtvchannel.h"
+
+ #ifdef USING_V4L2
+-#include <linux/videodev2.h>
++#include "videodev2.h" // needed for v4l2_std_id type
+ #endif
+
+ using namespace std;
+diff --git a/mythtv/libs/libmythtv/v4l2util.h b/mythtv/libs/libmythtv/v4l2util.h
+index aeaa14df2f5..48322c78450 100644
+--- a/mythtv/libs/libmythtv/v4l2util.h
++++ b/mythtv/libs/libmythtv/v4l2util.h
+@@ -2,7 +2,7 @@
+ #define _V4L2_util_h_
+
+ #ifdef USING_V4L2
+-#include <linux/videodev2.h>
++#include "videodev2.h" // our copy
+ #endif
+
+ #include "tv.h"
+
+From f8c59ee69d71beb825d8765ad6bc729b1cfc0150 Mon Sep 17 00:00:00 2001
+From: Stuart Auchterlonie <stuarta(a)mythtv.org>
+Date: Mon, 17 May 2021 23:32:29 +0100
+Subject: [PATCH 152/165] Revert "V4LChannel: Remove unneeded definitions" -
+ Broke debian-stretch and centos7 builds
+
+This reverts commit bcc01811cabd66eabc79987c5672a6a3d0c02243.
+---
+ mythtv/libs/libmythtv/recorders/v4lchannel.h | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/recorders/v4lchannel.h b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+index 0367e81ede1..34a6f5d7f59 100644
+--- a/mythtv/libs/libmythtv/recorders/v4lchannel.h
++++ b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+@@ -10,6 +10,8 @@
+
+ #ifdef USING_V4L2
+ #include "videodev2.h" // needed for v4l2_std_id type
++#else
++using v4l2_std_id = uint64_t;
+ #endif
+
+ using namespace std;
+@@ -18,6 +20,8 @@ using namespace std;
+
+ class TVRec;
+
++using VidModV4L2 = QMap<int,v4l2_std_id>;
++
+ /** \class V4LChannel
+ * \brief Implements tuning for TV cards using the V4L driver API,
+ * both versions 1 and 2.
+
+From 1510439288cb874e51aacd14e248bf14c32269b5 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Tue, 25 May 2021 12:18:56 -0500
+Subject: [PATCH 153/165] DB: Reconnect if MySQL error code 4031 is received
+
+A new error code was introduced in MySQL client v8.0.24 that
+mythdbcon.cpp didn't handle. Users would loose the ability
+to make any recordings.
+
+Reported on the -users list and Forum for Ubuntu 20.04 users
+after a recent updat to mysql* v8.0.25 and MythTV v31.0 or
+master. Two MariaDB users also reported.
+
+Tests with wait_timeout set to 300 (seconds).
+
+18.04 (which doesn't have the problem):
+Scheduler scheduler.cpp:2310 (HandleReschedule) - Reschedule requested for PLACE PrepareToRecord
+Scheduler mythdbcon.cpp:947 (lostConnectionCheck) - SQL Native Error Code: 2006
+Scheduler mythdbcon.cpp:260 (Reconnect) - MySQL reconnected successfully
+
+20.04:
+Scheduler scheduler.cpp:2309 (HandleReschedule) - Reschedule requested for MATCH 0 0 0 - MythUtilCommand
+Scheduler mythdbcon.cpp:879 (prepare) - SQL Native Error Code: 4031
+Scheduler mythdbcon.cpp:260 (Reconnect) - MySQL reconnected successfully
+
+No testing on MariaDB has been yet.
+
+Fixes #359
+
+(cherry picked from commit e55471f0776911a82ded3e008abbd3158f2e40b5)
+---
+ mythtv/libs/libmythbase/mythdbcon.cpp | 44 ++++++++++++++++-----------
+ mythtv/libs/libmythbase/mythdbcon.h | 4 +++
+ 2 files changed, 30 insertions(+), 18 deletions(-)
+
+diff --git a/mythtv/libs/libmythbase/mythdbcon.cpp b/mythtv/libs/libmythbase/mythdbcon.cpp
+index 1df2f65be3d..0a1a81ddedb 100644
+--- a/mythtv/libs/libmythbase/mythdbcon.cpp
++++ b/mythtv/libs/libmythbase/mythdbcon.cpp
+@@ -638,12 +638,7 @@ bool MSqlQuery::exec()
+ bool result = QSqlQuery::exec();
+ qint64 elapsed = timer.elapsed();
+
+- // if the query failed with "MySQL server has gone away"
+- // Close and reopen the database connection and retry the query if it
+- // connects again
+- if (!result
+- && QSqlQuery::lastError().nativeErrorCode() == "2006"
+- && Reconnect())
++ if (!result && lostConnectionCheck())
+ result = QSqlQuery::exec();
+
+ if (!result)
+@@ -727,12 +722,7 @@ bool MSqlQuery::exec(const QString &query)
+
+ bool result = QSqlQuery::exec(query);
+
+- // if the query failed with "MySQL server has gone away"
+- // Close and reopen the database connection and retry the query if it
+- // connects again
+- if (!result
+- && QSqlQuery::lastError().nativeErrorCode() == "2006"
+- && Reconnect())
++ if (!result && lostConnectionCheck())
+ result = QSqlQuery::exec(query);
+
+ LOG(VB_DATABASE, LOG_INFO,
+@@ -830,12 +820,7 @@ bool MSqlQuery::prepare(const QString& query)
+
+ bool ok = QSqlQuery::prepare(query);
+
+- // if the prepare failed with "MySQL server has gone away"
+- // Close and reopen the database connection and retry the query if it
+- // connects again
+- if (!ok
+- && QSqlQuery::lastError().nativeErrorCode() == "2006"
+- && Reconnect())
++ if (!ok && lostConnectionCheck())
+ ok = true;
+
+ if (!ok && !(GetMythDB()->SuppressDBMessages()))
+@@ -904,6 +889,29 @@ bool MSqlQuery::Reconnect(void)
+ return true;
+ }
+
++bool MSqlQuery::lostConnectionCheck()
++{
++ // MySQL: Error number: 2006; Symbol: CR_SERVER_GONE_ERROR
++ // MySQL: Error number: 2013; Symbol: CR_SERVER_LOST
++ // MySQL: Error number: 4031; Symbol: ER_CLIENT_INTERACTION_TIMEOUT
++ // Note: In MariaDB, 4031 = ER_REFERENCED_TRG_DOES_NOT_EXIST
++
++ static QStringList kLostConnectionCodes = { "2006", "2013", "4031" };
++
++ QString error_code = QSqlQuery::lastError().nativeErrorCode();
++
++ // Make capturing of new 'lost connection' like error codes easy.
++ LOG(VB_GENERAL, LOG_DEBUG, QString("SQL Native Error Code: %1")
++ .arg(error_code));
++
++ // If the query failed with any of the error codes that say the server
++ // is gone, close and reopen the database connection.
++ if (kLostConnectionCodes.contains(error_code) && Reconnect())
++ return true;
++
++ return false;
++}
++
+ void MSqlAddMoreBindings(MSqlBindings &output, MSqlBindings &addfrom)
+ {
+ MSqlBindings::Iterator it;
+diff --git a/mythtv/libs/libmythbase/mythdbcon.h b/mythtv/libs/libmythbase/mythdbcon.h
+index 18efb81158d..17d9a4190d9 100644
+--- a/mythtv/libs/libmythbase/mythdbcon.h
++++ b/mythtv/libs/libmythbase/mythdbcon.h
+@@ -194,6 +194,10 @@ class MBASE_PUBLIC MSqlQuery : private QSqlQuery
+ /// query.
+ bool Reconnect(void);
+
++ /// lostConnectionCheck tests for SQL error codes that indicate the
++ /// connection to the server has been lost.
++ bool lostConnectionCheck(void);
++
+ // Thunks that allow us to make QSqlQuery private
+ QVariant value(int i) const { return QSqlQuery::value(i); }
+ QString executedQuery(void) const { return QSqlQuery::executedQuery(); }
+
+From df29c72b4d1d5aa6728bf1ad4264439a4d121855 Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Tue, 25 May 2021 22:44:51 -0500
+Subject: [PATCH 154/165] tidy: simplified return from lostConnectionCheck()
+
+(cherry picked from commit eafe170b260ce4d6128e14a10af54d80e297d1b9)
+---
+ mythtv/libs/libmythbase/mythdbcon.cpp | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythbase/mythdbcon.cpp b/mythtv/libs/libmythbase/mythdbcon.cpp
+index 0a1a81ddedb..dfd07f0a966 100644
+--- a/mythtv/libs/libmythbase/mythdbcon.cpp
++++ b/mythtv/libs/libmythbase/mythdbcon.cpp
+@@ -906,10 +906,8 @@ bool MSqlQuery::lostConnectionCheck()
+
+ // If the query failed with any of the error codes that say the server
+ // is gone, close and reopen the database connection.
+- if (kLostConnectionCodes.contains(error_code) && Reconnect())
+- return true;
++ return (kLostConnectionCodes.contains(error_code) && Reconnect());
+
+- return false;
+ }
+
+ void MSqlAddMoreBindings(MSqlBindings &output, MSqlBindings &addfrom)
+
+From 3162473370b31c749e8417b1b19fe6dc2186cb95 Mon Sep 17 00:00:00 2001
+From: John Hoyt <john.hoyt(a)gmail.com>
+Date: Sun, 30 May 2021 07:52:59 -0400
+Subject: [PATCH 155/165] OSX: Rename VERSION file to SRC_VERSION to correct
+ conflict with C++17 version header on case insensitive filesystems
+
+---
+ mythtv/{VERSION => SRC_VERSION} | 0
+ mythtv/version.sh | 16 ++++++++--------
+ 2 files changed, 8 insertions(+), 8 deletions(-)
+ rename mythtv/{VERSION => SRC_VERSION} (100%)
+
+diff --git a/mythtv/VERSION b/mythtv/SRC_VERSION
+similarity index 100%
+rename from mythtv/VERSION
+rename to mythtv/SRC_VERSION
+diff --git a/mythtv/version.sh b/mythtv/version.sh
+index d412cc0505d..9b8f144bdb0 100755
+--- a/mythtv/version.sh
++++ b/mythtv/version.sh
+@@ -28,7 +28,7 @@ if test -e $GITTREEDIR/DESCRIBE ; then
+ echo "BRANCH: $BRANCH"
+ echo "SOURCE_VERSION: $SOURCE_VERSION"
+ else
+- # get the branch and version from git or fall back to EXPORTED_VERSION then VERSION as last resort
++ # get the branch and version from git or fall back to EXPORTED_VERSION then SRC_VERSION as last resort
+ git status > /dev/null 2>&1
+ SOURCE_VERSION=$(git describe --dirty || git describe || echo Unknown)
+ echo "SOURCE_VERSION: $SOURCE_VERSION"
+@@ -57,14 +57,14 @@ else
+ SOURCE_VERSION="$BRANCH"
+ SOURCE_VERSION=`echo "$SOURCE_VERSION" | sed "s/tag: *//"`
+ if ! echo "$SOURCE_VERSION" | grep "^v[0-9]" ; then
+- . $GITTREEDIR/VERSION
++ . $GITTREEDIR/SRC_VERSION
+ fi
+ SOURCE_VERSION="${SOURCE_VERSION}-${hash}"
+ echo "Source Version created as $SOURCE_VERSION"
+ echo "Branch created as $BRANCH"
+- elif test -e $GITTREEDIR/VERSION ; then
+- echo "Using $GITTREEDIR/VERSION"
+- . $GITTREEDIR/VERSION
++ elif test -e $GITTREEDIR/SRC_VERSION ; then
++ echo "Using $GITTREEDIR/SRC_VERSION"
++ . $GITTREEDIR/SRC_VERSION
+ echo "BRANCH: $BRANCH"
+ echo "SOURCE_VERSION: $SOURCE_VERSION"
+ fi
+@@ -81,9 +81,9 @@ else
+ fi
+
+ if ! echo "${SOURCE_VERSION}" | egrep -i "v[0-9]+.*" ; then
+- # Invalid version - use VERSION file
+- echo "WARNING: Invalid source version ${SOURCE_VERSION}, must start with v and a number, will use VERSION file instead"
+- . $GITTREEDIR/VERSION
++ # Invalid version - use SRC_VERSION file
++ echo "WARNING: Invalid source version ${SOURCE_VERSION}, must start with v and a number, will use SRC_VERSION file instead"
++ . $GITTREEDIR/SRC_VERSION
+ fi
+
+ src_vn=`echo "${SOURCE_VERSION}" | sed "s/^[Vv]// ; s/-.*// ; s/\..*//"`
+
+From 0680b37c6841e10f494cf836862c6d0879e573b0 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Thu, 10 Jun 2021 23:23:01 +0200
+Subject: [PATCH 156/165] Crash in libCEC on mythfrontend GUI rebuild
+
+Fix mythfrontend crash in GUI rebuild due to a theme change or due
+to a change in the window size. This happens only when mythfrontend
+is used with a monitor or a TV that does not support CEC.
+In that case the CEC library is closed after startup but due
+to a bug in libcec/cecloader.h the handle to libcec.so is not cleared.
+On theme change or window resize the GUI is rebuilt and the CEC
+library is opened a second time. This uses the now-invalid handle
+and this causes the crash.
+The real solution is to fix libcec.
+For now the problem is solved by zeroing the handle ourselves, but this
+requires accessing the handle which should be internal to the CEC library.
+See also Pulse-Eight/libcec issue #555 on Github.
+
+This bug can also cause mythtv-setup to crash.
+This fix is backported from master.
+See commit 10b5291
+See commit e787645
+
+Refs #299
+---
+ mythtv/libs/libmythui/devices/mythcecadapter.cpp | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/mythtv/libs/libmythui/devices/mythcecadapter.cpp b/mythtv/libs/libmythui/devices/mythcecadapter.cpp
+index 3f05df23309..2de86217ded 100644
+--- a/mythtv/libs/libmythui/devices/mythcecadapter.cpp
++++ b/mythtv/libs/libmythui/devices/mythcecadapter.cpp
+@@ -256,6 +256,9 @@ void MythCECAdapter::Close(void)
+ HandleActions(PowerOffTV);
+ m_adapter->Close();
+ UnloadLibCec(m_adapter);
++ // Workaround for bug in libcec/cecloader.h
++ // MythTV issue #299, libcec issue #555
++ g_libCEC = nullptr;
+ LOG(VB_GENERAL, LOG_INFO, LOC + "Closing down CEC.");
+ }
+ m_valid = false;
+
+From 7037525723b56a30959d6fdf8e4b5a72c336a6ac Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <klaas(a)kldo.nl>
+Date: Sun, 25 Jul 2021 08:51:33 +0200
+Subject: [PATCH 157/165] DB access for information about existing transports
+
+In mythtv-setup, the channels found in a scan are compared with the channels in the database.
+Do not retrieve the sourceid from the the first channel of the first transport
+anymore but use the sourceid that is passed as parameter.
+This fixes a possible segmentation fault when the first transport does not have
+any channels at all due to filtering of unwanted channels.
+
+(cherry picked from commit 7f48b7007e387030822874d810c453985e7331b8)
+Signed-off-by: Klaas de Waal <kdewaal(a)mythtv.org>
+---
+ mythtv/libs/libmythtv/channelscan/channelimporter.cpp | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+index 9573feb9344..99154dbdcc7 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+@@ -133,7 +133,6 @@ void ChannelImporter::Process(const ScanDTVTransportList &_transports,
+
+ // Pull in DB info in transports
+ // Channels not found in scan but only in DB are returned in db_trans
+- sourceid = transports[0].m_channels[0].m_sourceId;
+ ScanDTVTransportList db_trans = GetDBTransports(sourceid, transports);
+ if (VERBOSE_LEVEL_CHECK(VB_CHANSCAN, LOG_ANY))
+ {
+
+From d59053b747b64b2efd654aacbbc8d049be173257 Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Tue, 27 Jul 2021 16:45:56 -0400
+Subject: [PATCH 158/165] Fix videodev2.h compilation errors on FreeBSD.
+
+The file v4lchannel.cpp should include our own copy of videodev2.h.
+Make the same change to this file as was made to the v4l2 files in
+commit 730975bc18.
+
+Note: This change has not been made to the development branch, as the
+local copy of videodev2.h has been completely removed there.
+---
+ mythtv/libs/libmythtv/recorders/v4lchannel.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/v4lchannel.cpp b/mythtv/libs/libmythtv/recorders/v4lchannel.cpp
+index d6c16e81da7..84d5f93fd59 100644
+--- a/mythtv/libs/libmythtv/recorders/v4lchannel.cpp
++++ b/mythtv/libs/libmythtv/recorders/v4lchannel.cpp
+@@ -16,7 +16,7 @@
+ #include <iostream>
+ using namespace std;
+
+-#include <linux/videodev2.h>
++#include "videodev2.h" // our copy
+
+ // MythTV headers
+ #include "v4lchannel.h"
+
+From 46cf50b44d1d9dd5e0234ffc6200179e6930b44a Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Tue, 27 Jul 2021 01:34:13 +0000
+Subject: [PATCH 159/165] mytharchive: change offset to `offset` for MariaDB
+ 10.6
+
+MariaDB 10.6 has added a new reserved word offset which
+is used in MythTV as a column name in the tables filemarkup
+and recordedseek requiring quoting of the name.
+
+(cherry picked from commit 9c16537d2212962627c810a3cfc48dad53933f5c)
+Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+---
+ mythplugins/mytharchive/mytharchivehelper/main.cpp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythplugins/mytharchive/mytharchivehelper/main.cpp b/mythplugins/mytharchive/mytharchivehelper/main.cpp
+index 13ca02ddeed..ba0f8feb166 100644
+--- a/mythplugins/mytharchive/mytharchivehelper/main.cpp
++++ b/mythplugins/mytharchive/mytharchivehelper/main.cpp
+@@ -566,7 +566,7 @@ int NativeArchive::exportRecording(QDomElement &itemNode,
+
+ // add the recordedseek table
+ QDomElement recordedseek = doc.createElement("recordedseek");
+- query.prepare("SELECT chanid, starttime, mark, offset, type "
++ query.prepare("SELECT chanid, starttime, mark, `offset`, type "
+ "FROM recordedseek "
+ "WHERE chanid = :CHANID and starttime = :STARTTIME;");
+ query.bindValue(":CHANID", chanID);
+@@ -1110,7 +1110,7 @@ int NativeArchive::importRecording(const QDomElement &itemNode,
+ QDomNode n6 = nodeList.item(x);
+ QDomElement e = n6.toElement();
+ query.prepare("INSERT INTO recordedseek (chanid, starttime, "
+- "mark, offset, type)"
++ "mark, `offset`, type)"
+ "VALUES(:CHANID,:STARTTIME,:MARK,:OFFSET,:TYPE);");
+ query.bindValue(":CHANID", chanID);
+ query.bindValue(":STARTTIME", startTime);
+
+From 5824c588db24b4e71a7d94e829e6419f71089297 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Tue, 27 Jul 2021 01:36:31 +0000
+Subject: [PATCH 160/165] programinfo: change offset to `offset` for MariaDB
+ 10.6
+
+MariaDB 10.6 has added a new reserved word offset which
+is used in MythTV as a column name in the tables filemarkup
+and recordedseek requiring quoting of the name.
+
+(cherry picked from commit 8fd76381dbf3d5c61c6c61dc19c9f891d322d9cd)
+Signed-off-by: Bill Meek <billmeek(a)mythtv.org>
+---
+ mythtv/libs/libmyth/programinfo.cpp | 48 ++++++++++++++---------------
+ 1 file changed, 24 insertions(+), 24 deletions(-)
+
+diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp
+index eebb99a4fd5..fced95fc0a5 100644
+--- a/mythtv/libs/libmyth/programinfo.cpp
++++ b/mythtv/libs/libmyth/programinfo.cpp
+@@ -3618,14 +3618,14 @@ void ProgramInfo::QueryPositionMap(
+
+ if (IsVideo())
+ {
+- query.prepare("SELECT mark, offset FROM filemarkup"
++ query.prepare("SELECT mark, `offset` FROM filemarkup"
+ " WHERE filename = :PATH"
+ " AND type = :TYPE ;");
+ query.bindValue(":PATH", StorageGroup::GetRelativePathname(m_pathname));
+ }
+ else if (IsRecording())
+ {
+- query.prepare("SELECT mark, offset FROM recordedseek"
++ query.prepare("SELECT mark, `offset` FROM recordedseek"
+ " WHERE chanid = :CHANID"
+ " AND starttime = :STARTTIME"
+ " AND type = :TYPE ;");
+@@ -3783,7 +3783,7 @@ void ProgramInfo::SavePositionMap(
+ QString qfields;
+ if (IsVideo())
+ {
+- q << "filemarkup (filename, type, mark, offset)";
++ q << "filemarkup (filename, type, mark, `offset`)";
+ qfields = QString("('%1',%2,") .
+ // ideally, this should be escaped
+ arg(videoPath) .
+@@ -3791,7 +3791,7 @@ void ProgramInfo::SavePositionMap(
+ }
+ else // if (IsRecording())
+ {
+- q << "recordedseek (chanid, starttime, type, mark, offset)";
++ q << "recordedseek (chanid, starttime, type, mark, `offset`)";
+ qfields = QString("(%1,'%2',%3,") .
+ arg(m_chanId) .
+ arg(m_recStartTs.toString(Qt::ISODate)) .
+@@ -3851,7 +3851,7 @@ void ProgramInfo::SavePositionMapDelta(
+ QString qfields;
+ if (IsVideo())
+ {
+- q << "filemarkup (filename, type, mark, offset)";
++ q << "filemarkup (filename, type, mark, `offset`)";
+ qfields = QString("('%1',%2,") .
+ // ideally, this should be escaped
+ arg(StorageGroup::GetRelativePathname(m_pathname)) .
+@@ -3859,7 +3859,7 @@ void ProgramInfo::SavePositionMapDelta(
+ }
+ else if (IsRecording())
+ {
+- q << "recordedseek (chanid, starttime, type, mark, offset)";
++ q << "recordedseek (chanid, starttime, type, mark, `offset`)";
+ qfields = QString("(%1,'%2',%3,") .
+ arg(m_chanId) .
+ arg(m_recStartTs.toString(Qt::ISODate)) .
+@@ -3898,56 +3898,56 @@ void ProgramInfo::SavePositionMapDelta(
+ }
+
+ static const char *from_filemarkup_offset_asc =
+- "SELECT mark, offset FROM filemarkup"
++ "SELECT mark, `offset` FROM filemarkup"
+ " WHERE filename = :PATH"
+ " AND type = :TYPE"
+ " AND mark >= :QUERY_ARG"
+ " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
+ static const char *from_filemarkup_offset_desc =
+- "SELECT mark, offset FROM filemarkup"
++ "SELECT mark, `offset` FROM filemarkup"
+ " WHERE filename = :PATH"
+ " AND type = :TYPE"
+ " AND mark <= :QUERY_ARG"
+ " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
+ static const char *from_recordedseek_offset_asc =
+- "SELECT mark, offset FROM recordedseek"
++ "SELECT mark, `offset` FROM recordedseek"
+ " WHERE chanid = :CHANID"
+ " AND starttime = :STARTTIME"
+ " AND type = :TYPE"
+ " AND mark >= :QUERY_ARG"
+ " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
+ static const char *from_recordedseek_offset_desc =
+- "SELECT mark, offset FROM recordedseek"
++ "SELECT mark, `offset` FROM recordedseek"
+ " WHERE chanid = :CHANID"
+ " AND starttime = :STARTTIME"
+ " AND type = :TYPE"
+ " AND mark <= :QUERY_ARG"
+ " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
+ static const char *from_filemarkup_mark_asc =
+- "SELECT offset,mark FROM filemarkup"
++ "SELECT `offset`,mark FROM filemarkup"
+ " WHERE filename = :PATH"
+ " AND type = :TYPE"
+- " AND offset >= :QUERY_ARG"
++ " AND `offset` >= :QUERY_ARG"
+ " ORDER BY filename ASC, type ASC, mark ASC LIMIT 1;";
+ static const char *from_filemarkup_mark_desc =
+- "SELECT offset,mark FROM filemarkup"
++ "SELECT `offset`,mark FROM filemarkup"
+ " WHERE filename = :PATH"
+ " AND type = :TYPE"
+- " AND offset <= :QUERY_ARG"
++ " AND `offset` <= :QUERY_ARG"
+ " ORDER BY filename DESC, type DESC, mark DESC LIMIT 1;";
+ static const char *from_recordedseek_mark_asc =
+- "SELECT offset,mark FROM recordedseek"
++ "SELECT `offset`,mark FROM recordedseek"
+ " WHERE chanid = :CHANID"
+ " AND starttime = :STARTTIME"
+ " AND type = :TYPE"
+- " AND offset >= :QUERY_ARG"
++ " AND `offset` >= :QUERY_ARG"
+ " ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;";
+ static const char *from_recordedseek_mark_desc =
+- "SELECT offset,mark FROM recordedseek"
++ "SELECT `offset`,mark FROM recordedseek"
+ " WHERE chanid = :CHANID"
+ " AND starttime = :STARTTIME"
+ " AND type = :TYPE"
+- " AND offset <= :QUERY_ARG"
++ " AND `offset` <= :QUERY_ARG"
+ " ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;";
+
+ bool ProgramInfo::QueryKeyFrameInfo(uint64_t * result,
+@@ -4368,7 +4368,7 @@ void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
+ // Get the markup
+ if (IsVideo())
+ {
+- query.prepare("SELECT type, mark, offset FROM filemarkup"
++ query.prepare("SELECT type, mark, `offset` FROM filemarkup"
+ " WHERE filename = :PATH"
+ " AND type NOT IN (:KEYFRAME,:DURATION)"
+ " ORDER BY mark, type;");
+@@ -4408,7 +4408,7 @@ void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
+ // Get the seektable
+ if (IsVideo())
+ {
+- query.prepare("SELECT type, mark, offset FROM filemarkup"
++ query.prepare("SELECT type, mark, `offset` FROM filemarkup"
+ " WHERE filename = :PATH"
+ " AND type IN (:KEYFRAME,:DURATION)"
+ " ORDER BY mark, type;");
+@@ -4418,7 +4418,7 @@ void ProgramInfo::QueryMarkup(QVector<MarkupEntry> &mapMark,
+ }
+ else if (IsRecording())
+ {
+- query.prepare("SELECT type, mark, offset FROM recordedseek"
++ query.prepare("SELECT type, mark, `offset` FROM recordedseek"
+ " WHERE chanid = :CHANID"
+ " AND STARTTIME = :STARTTIME"
+ " ORDER BY mark, type");
+@@ -4481,7 +4481,7 @@ void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
+ else
+ {
+ query.prepare("INSERT INTO filemarkup"
+- " (filename,type,mark,offset)"
++ " (filename,type,mark,`offset`)"
+ " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
+ query.bindValue(":OFFSET", (quint64)entry.data);
+ }
+@@ -4524,7 +4524,7 @@ void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
+ }
+ const MarkupEntry &entry = mapSeek[i];
+ query.prepare("INSERT INTO filemarkup"
+- " (filename,type,mark,offset)"
++ " (filename,type,mark,`offset`)"
+ " VALUES (:PATH,:TYPE,:MARK,:OFFSET)");
+ query.bindValue(":PATH", path);
+ query.bindValue(":TYPE", entry.type);
+@@ -4613,7 +4613,7 @@ void ProgramInfo::SaveMarkup(const QVector<MarkupEntry> &mapMark,
+ }
+ const MarkupEntry &entry = mapSeek[i];
+ query.prepare("INSERT INTO recordedseek"
+- " (chanid,starttime,type,mark,offset)"
++ " (chanid,starttime,type,mark,`offset`)"
+ " VALUES (:CHANID,:STARTTIME,"
+ " :TYPE,:MARK,:OFFSET)");
+ query.bindValue(":CHANID", m_chanId);
+
+From 5da25231540158faa10ee2309eb94103ff74f417 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Sat, 7 Aug 2021 15:44:37 +0200
+Subject: [PATCH 161/165] Correct FSF address
+
+Correct FSF address (low hanging fruit) in a few files that seem to have been added/updated
+since the last pass was performed to correct the FSF address (initially identified by rpmlint).
+
+Fixes #trac-13496
+
+Signed-off-by: Klaas de Waal <klaas(a)kldo.nl>
+(cherry picked from commit fdee91cd73f63c68daa7b87f914fb01fbd2002c9)
+---
+ mythtv/libs/libmyth/audio/eldutils.h | 4 ++--
+ mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp | 4 ++--
+ mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h | 4 ++--
+ mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp | 4 ++--
+ 4 files changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/mythtv/libs/libmyth/audio/eldutils.h b/mythtv/libs/libmyth/audio/eldutils.h
+index 32f88504c4d..fc2fe088948 100644
+--- a/mythtv/libs/libmyth/audio/eldutils.h
++++ b/mythtv/libs/libmyth/audio/eldutils.h
+@@ -16,8 +16,8 @@
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+- * this program; if not, write to the Free Software Foundation, Inc., 59
+- * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++ * this program; if not, write to the Free Software Foundation, Inc., 51
++ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ #ifndef __ELDUTILS_H
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
+index 3a2385989ae..420591e0f17 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
+@@ -14,8 +14,8 @@
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+- * along with this program; if not, write to the Free Software
+- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ * along with this program; if not, write to the Free Software Foundation,
++ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ // C/C++ includes
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
+index 638b81becf1..c97076f3102 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
+@@ -14,8 +14,8 @@
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+- * along with this program; if not, write to the Free Software
+- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ * along with this program; if not, write to the Free Software Foundation,
++ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ // Qt includes
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp b/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
+index a2acc946557..1c14e85ce6b 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
+@@ -14,8 +14,8 @@
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+- * along with this program; if not, write to the Free Software
+- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ * along with this program; if not, write to the Free Software Foundation,
++ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ // Qt includes
+
+From 1db67b26d8bfae8d5a43dca2eeb012efa5a1d776 Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Thu, 22 Jul 2021 17:29:03 +0000
+Subject: [PATCH 162/165] Adjust for the removal of the ABCs from the python
+ collections module
+
+Python 3.10 will finally remove the long deprecated aliases to the
+Abstract Base Classes from the collections module.
+
+ https://docs.python.org/3.10/whatsnew/3.10.html#removed
+
+Fixes #374
+
+(cherry picked from commit 4b6a3a7b8b027f7df2b65117bd3ca9b14413a442)
+---
+ mythtv/bindings/python/MythTV/utility/dicttoxml.py | 13 ++++++++-----
+ mythtv/bindings/python/tmdb3/tmdb3/pager.py | 5 ++++-
+ 2 files changed, 12 insertions(+), 6 deletions(-)
+
+diff --git a/mythtv/bindings/python/MythTV/utility/dicttoxml.py b/mythtv/bindings/python/MythTV/utility/dicttoxml.py
+index f856cacc60c..3c1d2eb8754 100644
+--- a/mythtv/bindings/python/MythTV/utility/dicttoxml.py
++++ b/mythtv/bindings/python/MythTV/utility/dicttoxml.py
+@@ -14,7 +14,10 @@
+ version = __version__
+
+ from random import randint
+-import collections
++try:
++ from collections.abc import Iterable
++except ImportError:
++ from collections import Iterable
+ import numbers
+ import logging
+ from xml.dom.minidom import parseString
+@@ -95,7 +98,7 @@ def get_xml_type(val):
+ return 'null'
+ if isinstance(val, dict):
+ return 'dict'
+- if isinstance(val, collections.Iterable):
++ if isinstance(val, Iterable):
+ return 'list'
+ return type(val).__name__
+
+@@ -187,7 +190,7 @@ def convert(obj, ids, attr_type, item_func, cdata, parent='root'):
+ if isinstance(obj, dict):
+ return convert_dict(obj, ids, parent, attr_type, item_func, cdata)
+
+- if isinstance(obj, collections.Iterable):
++ if isinstance(obj, Iterable):
+ return convert_list(obj, ids, parent, attr_type, item_func, cdata)
+
+ raise TypeError('Unsupported data type: %s (%s)' % (obj, type(obj).__name__))
+@@ -231,7 +234,7 @@ def convert_dict(obj, ids, parent, attr_type, item_func, cdata):
+ )
+ )
+
+- elif isinstance(val, collections.Iterable):
++ elif isinstance(val, Iterable):
+ if attr_type:
+ attr['type'] = get_xml_type(val)
+ addline('<%s%s>%s</%s>' % (
+@@ -294,7 +297,7 @@ def convert_list(items, ids, parent, attr_type, item_func, cdata):
+ )
+ )
+
+- elif isinstance(item, collections.Iterable):
++ elif isinstance(item, Iterable):
+ if not attr_type:
+ addline('<%s %s>%s</%s>' % (
+ item_name, make_attrstring(attr),
+diff --git a/mythtv/bindings/python/tmdb3/tmdb3/pager.py b/mythtv/bindings/python/tmdb3/tmdb3/pager.py
+index 6fb9d91a8fe..0964ed8b789 100644
+--- a/mythtv/bindings/python/tmdb3/tmdb3/pager.py
++++ b/mythtv/bindings/python/tmdb3/tmdb3/pager.py
+@@ -5,7 +5,10 @@
+ # Author: Raymond Wagner
+ #-----------------------
+
+-from collections import Sequence, Iterator
++try:
++ from collections.abc import Sequence, Iterator
++except ImportError:
++ from collections import Sequence, Iterator
+
+ try:
+ xrange
+
+From 5d6f06cc3a72e46700df268d11e20c78274359d5 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Thu, 28 Oct 2021 14:32:23 -0500
+Subject: [PATCH 163/165] Fix boolean conversion issue with new MySQL 8.
+
+MySQL 8.0.27 changed something with how it handles the implicit
+conversions of booleans when used with arithmetic operators. The
+change caused the power, priority calculations in the scheduler to
+fail due to mixed signed and unsigned values. Fix the issue by
+explicitly converting to 1 or 0.
+
+(cherry picked from commit 8ebfeb3bbf2ed192bc42213b2a3645df4bfa6685)
+---
+ mythtv/programs/mythbackend/scheduler.cpp | 30 ++++++++++++++---------
+ 1 file changed, 19 insertions(+), 11 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
+index 666b67820f4..20672bb194a 100644
+--- a/mythtv/programs/mythbackend/scheduler.cpp
++++ b/mythtv/programs/mythbackend/scheduler.cpp
+@@ -4374,41 +4374,49 @@ void Scheduler::AddNewRecords(void)
+
+ if (prefinputpri)
+ pwrpri += QString(" + "
+- "(capturecard.cardid = RECTABLE.prefinput) * %1").arg(prefinputpri);
++ "IF(capturecard.cardid = RECTABLE.prefinput, 1, 0) * %1")
++ .arg(prefinputpri);
+
+ if (hdtvpriority)
+- pwrpri += QString(" + (program.hdtv > 0 OR "
+- "FIND_IN_SET('HDTV', program.videoprop) > 0) * %1").arg(hdtvpriority);
++ pwrpri += QString(" + IF(program.hdtv > 0 OR "
++ "FIND_IN_SET('HDTV', program.videoprop) > 0, 1, 0) * %1")
++ .arg(hdtvpriority);
+
+ if (wspriority)
+ pwrpri += QString(" + "
+- "(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0) * %1").arg(wspriority);
++ "IF(FIND_IN_SET('WIDESCREEN', program.videoprop) > 0, 1, 0) * %1")
++ .arg(wspriority);
+
+ if (slpriority)
+ pwrpri += QString(" + "
+- "(FIND_IN_SET('SIGNED', program.subtitletypes) > 0) * %1").arg(slpriority);
++ "IF(FIND_IN_SET('SIGNED', program.subtitletypes) > 0, 1, 0) * %1")
++ .arg(slpriority);
+
+ if (onscrpriority)
+ pwrpri += QString(" + "
+- "(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0) * %1").arg(onscrpriority);
++ "IF(FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0, 1, 0) * %1")
++ .arg(onscrpriority);
+
+ if (ccpriority)
+ {
+ pwrpri += QString(" + "
+- "(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
+- "program.closecaptioned > 0 OR program.subtitled > 0) * %1").arg(ccpriority);
++ "IF(FIND_IN_SET('NORMAL', program.subtitletypes) > 0 OR "
++ "program.closecaptioned > 0 OR program.subtitled > 0, 1, 0) * %1")
++ .arg(ccpriority);
+ }
+
+ if (hhpriority)
+ {
+ pwrpri += QString(" + "
+- "(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
+- "FIND_IN_SET('HARDHEAR', program.audioprop) > 0) * %1").arg(hhpriority);
++ "IF(FIND_IN_SET('HARDHEAR', program.subtitletypes) > 0 OR "
++ "FIND_IN_SET('HARDHEAR', program.audioprop) > 0, 1, 0) * %1")
++ .arg(hhpriority);
+ }
+
+ if (adpriority)
+ pwrpri += QString(" + "
+- "(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0) * %1").arg(adpriority);
++ "IF(FIND_IN_SET('VISUALIMPAIR', program.audioprop) > 0, 1, 0) * %1")
++ .arg(adpriority);
+
+ MSqlQuery result(m_dbConn);
+
+
+From bb507cf183e26b5c61fd12b4aa7b2d2434b2489a Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Mon, 1 Nov 2021 13:22:15 -0500
+Subject: [PATCH 164/165] Fix boolean conversion issue with new MySQL 8.
+
+Care for Custom Priorities.
+
+(cherry picked from commit 8646ad33eafe345120f58005d6e7783f6448f1e6)
+---
+ mythtv/programs/mythbackend/scheduler.cpp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp
+index 20672bb194a..cb475e75ed6 100644
+--- a/mythtv/programs/mythbackend/scheduler.cpp
++++ b/mythtv/programs/mythbackend/scheduler.cpp
+@@ -4436,8 +4436,8 @@ void Scheduler::AddNewRecords(void)
+ QString sclause = result.value(1).toString();
+ sclause.remove(QRegExp("^\\s*AND\\s+", Qt::CaseInsensitive));
+ sclause.remove(';');
+- pwrpri += QString(" + (%1) * %2").arg(sclause)
+- .arg(result.value(0).toInt());
++ pwrpri += QString(" + IF(%1, 1, 0) * %2")
++ .arg(sclause).arg(result.value(0).toInt());
+ }
+ }
+ pwrpri += QString(" AS powerpriority ");
+
+From 25f1bb1d12fdee5b9ea3841fd39332db9431e4a2 Mon Sep 17 00:00:00 2001
+From: Klaas de Waal <kdewaal(a)mythtv.org>
+Date: Wed, 3 Nov 2021 21:18:47 +0100
+Subject: [PATCH 165/165] Restarting playback with VDPAU after PMT change
+
+Playback of some recordings of the Finnish broadcaster YLE fails
+when the VDPAU hardware accelerator is selected in the playback profile.
+This is caused by a restart of the video decoding after a PMT change.
+
+This problem only occurs with the MythTV-specific demuxer (mpegts-mythtv.c)
+and not with the original FFmpeg demuxer (mpegts.c) because the FFmpeg
+demuxer largely ignores PMT changes once playback has started.
+
+The first time the VDPAU codec support is checked, the pixel format is
+given as AV_PIX_FMT_YUV420P. This pixel format translates to video frame type FMT_YV12.
+There is a check if the video frame type is FMT_YV12 and this check succeeds.
+At the end, when it is decided that VDPAU can be used, the pixel format is set to AV_PIX_FMT_VDPAU.
+
+After a playback restart following a PMT change the VDPAU codec support is checked again.
+The AVCodecContext is re-used and the pixel format is still set to AV_PIX_FMT_VDPAU.
+This pixel format translate to video frame type FMT_VDPAU.
+
+The check if the frame type is FMT_YV12 now fails and there is an attempt to continue
+with FFMPEG decoding. This also fails, then resulting in a black screen while waiting forever.
+
+The fix is to accept also video frame type FMT_VDPAU for use with VDPAU decoding.
+This does solve the playback problems of all available YLE test clips.
+It can possibly also solve playback problems in other cases where the PMT changes such
+as when a channel that is broadcasting only part of the day does start broadcasting.
+
+Note that in the beginning of MythVDPAUContext::GetSupportedCodec there is a check
+if frame type FMT_VDPAU is supported and VDPAU can only be used if this is the case.
+It therefore makes sense to accept FMT_VDPAU as a valid video frame type and
+it was probably an omission in the original code that this frame type was not accepted.
+
+Refs #402
+---
+ mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp b/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
+index 56e57ee2d26..bd6659f4c54 100644
+--- a/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
++++ b/mythtv/libs/libmythtv/decoders/mythvdpaucontext.cpp
+@@ -76,7 +76,7 @@ int MythVDPAUContext::InitialiseContext(AVCodecContext* Context)
+ return -1;
+ }
+
+- // allocate the hardware frames context
++ // Allocate the hardware frames context
+ Context->hw_frames_ctx = av_hwframe_ctx_alloc(hwdeviceref);
+ if (!Context->hw_frames_ctx)
+ {
+@@ -158,7 +158,7 @@ MythCodecID MythVDPAUContext::GetSupportedCodec(AVCodecContext **Context,
+
+ // VDPAU only supports 8bit 420p:(
+ VideoFrameType type = PixelFormatToFrameType((*Context)->pix_fmt);
+- bool vdpau = (type == FMT_YV12) && MythVDPAUHelper::HaveVDPAU() &&
++ bool vdpau = (type == FMT_YV12 || type == FMT_VDPAU) && MythVDPAUHelper::HaveVDPAU() &&
+ (decodeonly ? codec_is_vdpau_dechw(success) : codec_is_vdpau_hw(success));
+
+ if (vdpau)
2 years, 11 months
[zoneminder/el7: 2/2] Merge branch 'master' into el7
by Andrew Bauer
commit e983f5bd6f645cf82d247bf345158f32790c79bc
Merge: eed12cc 9ea9e32
Author: Andrew Bauer <zonexpertconsulting(a)outlook.com>
Date: Sat Dec 11 08:43:25 2021 -0600
Merge branch 'master' into el7
.gitignore | 1 +
sources | 2 +-
zoneminder.spec | 5 ++++-
3 files changed, 6 insertions(+), 2 deletions(-)
---
2 years, 11 months
[zoneminder/el8] 1.36.12 release
by Andrew Bauer
Summary of changes:
9ea9e32... 1.36.12 release (*)
(*) This commit already existed in another branch; no separate mail sent
2 years, 11 months