commit d6248919464b06e660733f450082491d8729b46f
Author: sagitter <sagitter(a)fedoraproject.org>
Date: Sat Dec 8 21:35:22 2018 +0100
Rebuild for ffmpeg-3.4.5 on el7
ChangeLog => mythtv-ChangeLog | 0
PACKAGE-LICENSING => mythtv-PACKAGE-LICENSING | 0
mythtv.spec | 340 +-
sources | 1 -
update_fixes.sh | 37 -
v29.1..9f0acf372d.patch | 9065 +++++++++++++++++++++++++
6 files changed, 9256 insertions(+), 187 deletions(-)
---
diff --git a/ChangeLog b/mythtv-ChangeLog
similarity index 100%
rename from ChangeLog
rename to mythtv-ChangeLog
diff --git a/PACKAGE-LICENSING b/mythtv-PACKAGE-LICENSING
similarity index 100%
rename from PACKAGE-LICENSING
rename to mythtv-PACKAGE-LICENSING
diff --git a/mythtv.spec b/mythtv.spec
index 15b1756..54b90db 100644
--- a/mythtv.spec
+++ b/mythtv.spec
@@ -72,7 +72,7 @@
#
Name: mythtv
Version: 29.1
-Release: 26%{?rel_string}%{?dist}
+Release: 27%{?rel_string}%{?dist}
Summary: A digital video recorder (DVR) application
# The primary license is GPLv2+, but bits are borrowed from a number of
@@ -81,7 +81,7 @@ License: GPLv2+ and LGPLv2+ and LGPLv2 and (GPLv2 or QPL) and
(GPLv2+ or
URL:
http://www.mythtv.org/
Source0:
https://github.com/MythTV/%{name}/archive/v%{version}/%{name}-%{version}....
Patch0:
https://github.com/MythTV/%{name}/compare/v%{version}..%{shorthash}.patch
-Patch1: mythtv-space_in_GB.patch
+Patch1: %{name}-space_in_GB.patch
################################################################################
@@ -121,6 +121,17 @@ Patch1: mythtv-space_in_GB.patch
%bcond_with mythnetvision
%endif
+# Python2 prefix for building on rhel
+%if 0%{?rhel}
+%global py_prefix python
+%endif
+
+%if 0%{?fedora} && 0%{?fedora} > 29
+%global py_prefix python3
+%else
+%global py_prefix python2
+%endif
+
################################################################################
#
@@ -131,17 +142,17 @@ Patch1: mythtv-space_in_GB.patch
# Also update ChangeLog with git log v0.28..HEAD > ChangeLog
# and update define vers_string to v0.28-52-ge6a60f7 with git describe
-Source10: PACKAGE-LICENSING
-Source11: ChangeLog
+Source10: %{name}-PACKAGE-LICENSING
+Source11: %{name}-ChangeLog
Source101: mythbackend.sysconfig
Source102: mythbackend.init
-Source103: mythtv.logrotate.sysv
-Source104: mythtv.logrotate.sysd
+Source103: %{name}.logrotate.sysv
+Source104: %{name}.logrotate.sysd
Source105: mythbackend.service
Source106: mythfrontend.png
Source107: mythfrontend.desktop
-Source108: mythtv-setup.png
-Source109: mythtv-setup.desktop
+Source108: %{name}-setup.png
+Source109: %{name}-setup.desktop
Source111: 99-mythbackend.rules
Source112: mythjobqueue.service
Source113: mythdb-optimize.service
@@ -236,11 +247,11 @@ BuildRequires: libavc1394-devel
BuildRequires: libiec61883-devel
BuildRequires: libraw1394-devel
-BuildRequires: python2-future
+BuildRequires: %{py_prefix}-future
%if 0%{?fedora} || 0%{?rhel} > 7
# For ttvdb.py, not available in EPEL
-BuildRequires: python2-requests
-BuildRequires: python-requests-cache
+BuildRequires: %{py_prefix}-requests
+BuildRequires: %{py_prefix}-requests-cache
%else
BuildRequires: python-requests
%endif
@@ -284,13 +295,13 @@ BuildRequires: perl(IO::Socket::INET6)
%endif
%if %{with python}
-BuildRequires: python2-devel
+BuildRequires: %{py_prefix}-devel
+BuildRequires: %{py_prefix}-urlgrabber
%if 0%{?fedora} || 0%{?rhel} > 7
-BuildRequires: python2-mysql
+BuildRequires: %{py_prefix}-mysql
%else
BuildRequires: MySQL-python
%endif
-BuildRequires: python-urlgrabber
%endif
# Plugin Build Requirements
@@ -334,9 +345,9 @@ Requires: perl(JSON)
%endif
%if %{with mythnetvision}
-BuildRequires: python-pycurl
-BuildRequires: python-lxml
-BuildRequires: python-oauth
+BuildRequires: %{py_prefix}-pycurl
+BuildRequires: %{py_prefix}-lxml
+BuildRequires: %{py_prefix}-oauth
%endif
%endif
@@ -344,21 +355,21 @@ BuildRequires: python-oauth
################################################################################
# Requirements for the mythtv meta package
-Requires: mythtv-libs = %{version}-%{release}
-Requires: mythtv-backend = %{version}-%{release}
-Requires: mythtv-base-themes = %{version}-%{release}
-Requires: mythtv-common = %{version}-%{release}
-Requires: mythtv-docs = %{version}-%{release}
-Requires: mythtv-frontend = %{version}-%{release}
-Requires: mythtv-setup = %{version}-%{release}
-Requires: perl-MythTV = %{version}-%{release}
-Requires: php-MythTV = %{version}-%{release}
-Requires: python-MythTV = %{version}-%{release}
+Requires: mythtv-libs%{?_isa} = %{version}-%{release}
+Requires: mythtv-backend%{?_isa} = %{version}-%{release}
+Requires: mythtv-base-themes%{?_isa} = %{version}-%{release}
+Requires: mythtv-common%{?_isa} = %{version}-%{release}
+Requires: mythtv-docs = %{version}-%{release}
+Requires: mythtv-frontend%{?_isa} = %{version}-%{release}
+Requires: mythtv-setup%{?_isa} = %{version}-%{release}
+Requires: perl-MythTV = %{version}-%{release}
+Requires: php-MythTV = %{version}-%{release}
+Requires: python%{py_prefix}-MythTV = %{version}-%{release}
%if %{with plugins}
-Requires: mythplugins = %{version}-%{release}
+Requires: mythplugins%{?_isa} = %{version}-%{release}
%endif
-Requires: mythweb = %{version}
-Requires: mythffmpeg = %{version}-%{release}
+Requires: mythweb%{?_isa} = %{version}
+Requires: mythffmpeg%{?_isa} = %{version}-%{release}
Requires: mysql-compat-server >= 5
Requires: mysql >= 5
%{?fedora:Recommends: xmltv}
@@ -405,10 +416,11 @@ and miscellaneous other bits and pieces.
%package libs
Summary: Library providing mythtv support
-Requires: freetype >= 2
-Requires: lame
-Requires: qt5-qtbase-mysql
-Requires: udisks2
+%{?el7:BuildRequires: epel-rpm-macros}
+Requires: freetype%{?_isa} >= 2
+Requires: lame%{?_isa}
+Requires: qt5-qtbase-mysql%{?_isa}
+Requires: udisks2%{?_isa}
%description libs
Common library code for MythTV and add-on modules (development)
@@ -420,75 +432,75 @@ television programs. Refer to the mythtv package for more
information.
%package devel
Summary: Development files for mythtv
-Requires: mythtv-libs = %{version}-%{release}
+Requires: mythtv-libs%{?_isa} = %{version}-%{release}
-Requires: freetype-devel >= 2
+Requires: freetype-devel%{?_isa} >= 2
%if 0%{?fedora} || 0%{?rhel} > 7
BuildRequires: mariadb-connector-c-devel
%else
BuildRequires: mariadb-devel >= 5
%endif
-Requires: qt5-qtbase-devel >= 5.2
-Requires: qt5-qtscript-devel >= 5.2
-Requires: qt5-qtwebkit-devel >= 5.2
-Requires: lm_sensors-devel
-Requires: lirc-devel
+Requires: qt5-qtbase-devel%{?_isa} >= 5.2
+Requires: qt5-qtscript-devel%{?_isa} >= 5.2
+Requires: qt5-qtwebkit-devel%{?_isa} >= 5.2
+Requires: lm_sensors-devel%{?_isa}
+Requires: lirc-devel%{?_isa}
# X, and Xv video support
-Requires: libXmu-devel
-Requires: libXv-devel
-Requires: libXvMC-devel
-Requires: libXxf86vm-devel
-Requires: mesa-libGLU-devel
-Requires: xorg-x11-proto-devel
+Requires: libXmu-devel%{?_isa}
+Requires: libXv-devel%{?_isa}
+Requires: libXvMC-devel%{?_isa}
+Requires: libXxf86vm-devel%{?_isa}
+Requires: mesa-libGLU-devel%{?_isa}
+Requires: xorg-x11-proto-devel%{?_isa}
# OpenGL video output and vsync support
-Requires: libGL-devel
-Requires: libGLU-devel
+Requires: libGL-devel%{?_isa}
+Requires: libGLU-devel%{?_isa}
# Misc A/V format support
-Requires: fftw-devel >= 3
-Requires: flac-devel >= 1.0.4
-Requires: gsm-devel
-Requires: lame-devel
-Requires: libdca-devel
-Requires: libdvdnav-devel
-Requires: libdvdread-devel >= 0.9.4
-Requires: libfame-devel >= 0.9.0
-Requires: libogg-devel
-Requires: libtheora-devel
-Requires: libvorbis-devel >= 1.0
-Requires: mjpegtools-devel >= 1.6.1
-Requires: taglib-devel >= 1.5
-Requires: x264-devel
-Requires: x265-devel
-Requires: xvidcore-devel >= 0.9.1
+Requires: fftw-devel%{?_isa} >= 3
+Requires: flac-devel%{?_isa} >= 1.0.4
+Requires: gsm-devel%{?_isa}
+Requires: lame-devel%{?_isa}
+Requires: libdca-devel%{?_isa}
+Requires: libdvdnav-devel%{?_isa}
+Requires: libdvdread-devel%{?_isa} >= 0.9.4
+Requires: libfame-devel%{?_isa} >= 0.9.0
+Requires: libogg-devel%{?_isa}
+Requires: libtheora-devel%{?_isa}
+Requires: libvorbis-devel%{?_isa} >= 1.0
+Requires: mjpegtools-devel%{?_isa} >= 1.6.1
+Requires: taglib-devel%{?_isa} >= 1.5
+Requires: x264-devel%{?_isa}
+Requires: x265-devel%{?_isa}
+Requires: xvidcore-devel%{?_isa} >= 0.9.1
# Audio framework support
-Requires: alsa-lib-devel
-Requires: jack-audio-connection-kit-devel
+Requires: alsa-lib-devel%{?_isa}
+Requires: jack-audio-connection-kit-devel%{?_isa}
%if %{with pulseaudio}
-Requires: pulseaudio-libs-devel
+Requires: pulseaudio-libs-devel%{?_isa}
%endif
# Need dvb headers for dvb support
-Requires: kernel-headers
+Requires: kernel-headers%{?_isa}
# FireWire cable box support
-Requires: libavc1394-devel
-Requires: libiec61883-devel
-Requires: libraw1394-devel
+Requires: libavc1394-devel%{?_isa}
+Requires: libiec61883-devel%{?_isa}
+Requires: libraw1394-devel%{?_isa}
%if %{with vdpau}
-Requires: libvdpau-devel
+Requires: libvdpau-devel%{?_isa}
%endif
%if %{with vaapi}
-Requires: libva-devel
+Requires: libva-devel%{?_isa}
%endif
%if %{with crystalhd}
-Requires: libcrystalhd-devel
+Requires: libcrystalhd-devel%{?_isa}
%endif
%description devel
@@ -510,21 +522,21 @@ This package contains the base themes for the mythtv user
interface.
%package frontend
Summary: Client component of mythtv (a DVR)
-Requires: freetype
-Requires: lame
+Requires: freetype%{?_isa}
+Requires: lame%{?_isa}
Requires: perl(XML::Simple)
-Requires: mythtv-common = %{version}-%{release}
-Requires: mythtv-base-themes = %{version}-%{release}
-Requires: mysql >= 5
-Requires: python-MythTV = %{version}-%{release}
+Requires: mythtv-common%{?_isa} = %{version}-%{release}
+Requires: mythtv-base-themes%{?_isa} = %{version}-%{release}
+Requires: mysql%{?_isa} >= 5
+Requires: python%{py_prefix}-MythTV = %{version}-%{release}
%if 0%{?fedora} || 0%{?rhel} > 7
-Recommends: libaacs
+Recommends: libaacs%{?_isa}
%else
-Requires: libaacs
+Requires: libaacs%{?_isa}
%endif
%{?fedora:Requires: google-droid-sans-mono-fonts}
-%{?fedora:Recommends: mesa-vdpau-drivers}
-Provides: mythtv-frontend-api = %{mythfeapiver}
+%{?fedora:Recommends: mesa-vdpau-drivers%{?_isa}}
+Provides: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
%description frontend
MythTV provides a unified graphical interface for recording and viewing
@@ -539,14 +551,14 @@ reachable via the network.
%package backend
Summary: Server component of mythtv (a DVR)
-Requires: lame
-Requires: mythtv-common = %{version}-%{release}
-Requires: mythtv-libs = %{version}-%{release}
-Requires: mythtv-setup
-Requires: python2-future
+Requires: lame%{?_isa}
+Requires: mythtv-common%{?_isa} = %{version}-%{release}
+Requires: mythtv-libs%{?_isa} = %{version}-%{release}
+Requires: mythtv-setup%{?_isa}
+Requires: %{py_prefix}-future
%if 0%{?fedora} || 0%{?rhel} > 7
-Requires: python2-requests
-Requires: python-requests-cache
+Requires: %{py_prefix}-requests
+Requires: %{py_prefix}-requests-cache
%else
Requires: python-requests
%endif
@@ -567,9 +579,9 @@ one reachable via the network.
%package setup
Summary: Setup the mythtv backend
-Requires: freetype
-Requires: mythtv-backend = %{version}-%{release}
-Requires: mythtv-base-themes = %{version}
+Requires: freetype%{?_isa}
+Requires: mythtv-backend%{?_isa} = %{version}-%{release}
+Requires: mythtv-base-themes%{?_isa} = %{version}
Requires: google-droid-sans-fonts
%description setup
@@ -584,10 +596,10 @@ mythtv backend.
%package common
Summary: Common components needed by multiple other MythTV components
# For ttvdb.py
-Requires: python2-future
+Requires: %{py_prefix}-future
%if 0%{?fedora} || 0%{?rhel} > 7
-Requires: python2-requests
-Requires: python-requests-cache
+Requires: %{py_prefix}-requests
+Requires: %{py_prefix}-requests-cache
%else
Requires: python-requests
%endif
@@ -645,14 +657,20 @@ Provides a PHP-based interface to interacting with MythTV.
%if %{with python}
-%package -n python-MythTV
-Summary: Python bindings for MythTV
+%package -n python%{py_prefix}-MythTV
+Summary: Python2 bindings for MythTV
+%if 0%{?fedora} > 29
+%{?python_provide:%python_provide python3-%{name}}
+%else
+%{?python_provide:%python_provide python2-%{name}}
+%{?python_provide:%python_provide python2-MythTV}
+%endif
BuildArch: noarch
-Requires: MySQL-python
-Requires: python-lxml
+Requires: %{py_prefix}-mysql
+Requires: %{py_prefix}-lxml
-%description -n python-MythTV
+%description -n python%{py_prefix}-MythTV
Provides a python-based interface to interacting with MythTV.
%endif
@@ -667,31 +685,31 @@ Provides a python-based interface to interacting with MythTV.
Summary: Main MythTV plugins
%if %{with mythmusic}
-Requires: mythmusic = %{version}-%{release}
+Requires: mythmusic%{?_isa} = %{version}-%{release}
%endif
%if %{with mythweather}
-Requires: mythweather = %{version}-%{release}
+Requires: mythweather%{?_isa} = %{version}-%{release}
%endif
%if %{with mythgallery}
-Requires: mythgallery = %{version}-%{release}
+Requires: mythgallery%{?_isa} = %{version}-%{release}
%endif
%if %{with mythgame}
-Requires: mythgame = %{version}-%{release}
+Requires: mythgame%{?_isa} = %{version}-%{release}
%endif
%if %{with mythnews}
-Requires: mythnews = %{version}-%{release}
+Requires: mythnews%{?_isa} = %{version}-%{release}
%endif
%if %{with mythbrowser}
-Requires: mythbrowser = %{version}-%{release}
+Requires: mythbrowser%{?_isa} = %{version}-%{release}
%endif
%if %{with mytharchive}
-Requires: mytharchive = %{version}-%{release}
+Requires: mytharchive%{?_isa} = %{version}-%{release}
%endif
%if %{with mythzoneminder}
-Requires: mythzoneminder = %{version}-%{release}
+Requires: mythzoneminder%{?_isa} = %{version}-%{release}
%endif
%if %{with mythnetvision}
-Requires: mythnetvision = %{version}-%{release}
+Requires: mythnetvision%{?_isa} = %{version}-%{release}
%endif
%description -n mythplugins
@@ -704,17 +722,17 @@ distributed as separate downloads from
mythtv.org.
%package -n mytharchive
Summary: A module for MythTV for creating and burning DVDs
-Requires: mythtv-frontend-api = %{mythfeapiver}
-Requires: MySQL-python
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
Requires: wodim
-Requires: dvd+rw-tools >= 5.21.4.10.8
-Requires: dvdauthor >= 0.6.11
-Requires: ffmpeg >= 0.4.9
-Requires: mjpegtools >= 1.6.2
-Requires: genisoimage
-Requires: python2 >= 2.3.5
-Requires: python-imaging
-Requires: pmount
+Requires: dvd+rw-tools%{?_isa} >= 5.21.4.10.8
+Requires: dvdauthor%{?_isa} >= 0.6.11
+Requires: ffmpeg%{?_isa} >= 0.4.9
+Requires: mjpegtools%{?_isa} >= 1.6.2
+Requires: genisoimage%{?_isa}
+Requires: %{py_prefix}-mysql
+Requires: %{py_prefix} >= 2.3.5
+Requires: %{py_prefix}-imaging
+Requires: pmount%{?_isa}
%description -n mytharchive
MythArchive is a new plugin for MythTV that lets you create DVDs from
@@ -727,7 +745,7 @@ your system.
%package -n mythbrowser
Summary: A small web browser module for MythTV
-Requires: mythtv-frontend-api = %{mythfeapiver}
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
%description -n mythbrowser
MythBrowser is a full fledged web-browser (multiple tabs) to display
@@ -744,7 +762,7 @@ links in a simple mythplugin.
%package -n mythgallery
Summary: A gallery/slideshow module for MythTV
-Requires: mythtv-frontend-api = %{mythfeapiver}
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
%description -n mythgallery
A gallery/slideshow module for MythTV.
@@ -755,7 +773,7 @@ A gallery/slideshow module for MythTV.
%package -n mythgame
Summary: A game frontend (xmame, nes, snes, pc) for MythTV
-Requires: mythtv-frontend-api = %{mythfeapiver}
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
%description -n mythgame
A game frontend (xmame, nes, snes, pc) for MythTV.
@@ -766,7 +784,7 @@ A game frontend (xmame, nes, snes, pc) for MythTV.
%package -n mythmusic
Summary: The music player add-on module for MythTV
-Requires: mythtv-frontend-api = %{mythfeapiver}
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
%description -n mythmusic
Music add-on for mythtv.
@@ -777,7 +795,7 @@ Music add-on for mythtv.
%package -n mythnews
Summary: An RSS news feed plugin for MythTV
-Requires: mythtv-frontend-api = %{mythfeapiver}
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
%description -n mythnews
An RSS news feed reader plugin for MythTV.
@@ -788,7 +806,7 @@ An RSS news feed reader plugin for MythTV.
%package -n mythweather
Summary: A MythTV module that displays a weather forecast
-Requires: mythtv-frontend-api = %{mythfeapiver}
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
Requires: perl(XML::SAX::Base)
%description -n mythweather
@@ -800,7 +818,7 @@ A MythTV module that displays a weather forecast.
%package -n mythzoneminder
Summary: A module for MythTV for camera security and surveillance
-Requires: mythtv-frontend-api = %{mythfeapiver}
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
%description -n mythzoneminder
MythZoneMinder is a plugin to interface to some of the features of
@@ -814,12 +832,12 @@ and replay recorded events.
%package -n mythnetvision
Summary: A MythTV module for Internet video on demand
-Requires: mythtv-frontend-api = %{mythfeapiver}
-Requires: mythbrowser = %{version}-%{release}
-Requires: python-MythTV = %{version}-%{release}
-Requires: python-pycurl
-Requires: python2 >= 2.5
-Requires: python-lxml
+Requires: mythtv-frontend-api%{?_isa} = %{mythfeapiver}
+Requires: mythbrowser%{?_isa} = %{version}-%{release}
+Requires: python%{py_prefix}-MythTV = %{version}-%{release}
+Requires: %{py_prefix}-pycurl
+Requires: %{py_prefix} >= 2.5
+Requires: %{py_prefix}-lxml
# This is packaged in adobe's yum repo
#Requires: flash-plugin
@@ -843,8 +861,11 @@ on demand content.
# Remove compiled python file
#find -name *.pyc -exec rm -f {} \;
+# Remove exe permissions
+find . -type f -name "*.cpp" -exec chmod 0644 '{}' \;
+
# Install ChangeLog
-install -m 0644 %{SOURCE11} .
+install -m 0644 %{SOURCE11} ./ChangeLog
pushd mythtv
@@ -862,7 +883,7 @@ EOF
bindings/perl/Makefile
# Install other source files
- cp -a %{SOURCE10} .
+ cp -a %{SOURCE10} ./PACKAGE-LICENSING
cp -a %{SOURCE106} %{SOURCE107} %{SOURCE108} %{SOURCE109} .
popd
@@ -897,7 +918,11 @@ pushd mythtv
--disable-vaapi \
%endif
--enable-bdjava \
+%if 0%{?fedora} > 29
+ --python=%{__python3} \
+%else
--python=%{__python2} \
+%endif
--enable-libmp3lame \
--enable-libtheora --enable-libvorbis \
--enable-libx264 \
@@ -928,8 +953,7 @@ pushd mythtv
%endif
# Make
-%make_build
-
+%make_build V=1
popd
# Prepare to build the plugins
@@ -1007,11 +1031,14 @@ pushd mythplugins
--disable-mythnetvision \
%endif
--enable-opengl \
- --python=%{__python2} \
+%if 0%{?fedora} > 29
+ --python=%{__python3} \
+%else
+ --python=%{__python2} \
+%endif
--enable-fftw
-%make_build
-
+%make_build V=1
popd
%endif
@@ -1107,9 +1134,13 @@ popd
%endif
# Fixes ERROR: ambiguous python shebang in F30
-find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/env python:#!/usr/bin/env python2:' {} ';'
-find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/python:#!/usr/bin/python2:' {} ';'
-
+%if 0%{?fedora} > 29
+find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/env python:#!%{__python3}:' {} ';'
+find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/python:#!%{__python3}:' {} ';'
+%else
+find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/env python:#!%{__python2}:' {} ';'
+find %{buildroot}%{_datadir}/mythtv/ -type f -name "*.py" -exec sed -i
'1s:#!/usr/bin/python:#!%{__python2}:' {} ';'
+%endif
%pre common
# Add the "mythtv" user, with membership in the audio and video group
@@ -1133,7 +1164,8 @@ getent passwd mythtv >/dev/null || \
usermod -a -G audio,video mythtv
exit 0
-%post libs -p /sbin/ldconfig
+%ldconfig_scriptlets libs
+%ldconfig_scriptlets -n mythffmpeg
%post backend
%systemd_post mythbackend.service
@@ -1145,8 +1177,6 @@ exit 0
%systemd_preun mythjobqueue.service
%systemd_preun mythdb-optimize.service
-%postun libs -p /sbin/ldconfig
-
%postun backend
%systemd_postun_with_restart mythbackend.service
%systemd_postun_with_restart mythjobqueue.service
@@ -1282,11 +1312,16 @@ exit 0
%endif
%if %{with python}
-%files -n python-MythTV
+%files -n python%{py_prefix}-MythTV
%{_bindir}/mythpython
+%if 0%{?fedora} > 29
+%{python3_sitelib}/MythTV/
+%{python3_sitelib}/MythTV-*.egg-info
+%else
%{python2_sitelib}/MythTV/
%{python2_sitelib}/MythTV-*.egg-info
%endif
+%endif
%if %{with plugins}
%files -n mythplugins
@@ -1398,6 +1433,13 @@ exit 0
%changelog
+* Sat Dec 08 2018 Antonio Trande <sagitter(a)fedoraproject.org> -
29.1-27.53.20181105git9f0acf372d
+- Rebuild for ffmpeg-3.4.5 on el7
+- Rebuild for x264-0.148 on el7
+- Rebuild for x265-2.9 on el7
+- Use ldconfig_scriptlets
+- Force python2- prefix
+
* Thu Nov 08 2018 Sérgio Basto <sergio(a)serjux.com> -
29.1-26.53.20181105git9f0acf372d
- Update to 29.1.53.20181105git9f0acf372d from branch fixes/29
diff --git a/sources b/sources
index a86a392..0bdee32 100644
--- a/sources
+++ b/sources
@@ -1,2 +1 @@
52f47a4288ce95e0edaddab84d5cc901 mythtv-29.1.tar.gz
-46598e9d5a5faa6ff684fdaa76cc110d v29.1..9f0acf372d.patch
diff --git a/v29.1..9f0acf372d.patch b/v29.1..9f0acf372d.patch
new file mode 100644
index 0000000..05683fa
--- /dev/null
+++ b/v29.1..9f0acf372d.patch
@@ -0,0 +1,9065 @@
+From f25203ca1827d8997e9ea8ebf6cc06de78cad2ab Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sat, 24 Feb 2018 14:21:07 -0500
+Subject: [PATCH 01/52] settings: fix bug where updates are not taking
+ immediate effect
+
+Change to video playback profile was not taking effect.
+Solution: Clear out cached value when updating a setting.
+
+(cherry picked from commit e7173e38b8767571a973050512e4afc288785ec5)
+---
+ mythtv/libs/libmythbase/mythstorage.cpp | 13 +++++++++++++
+ mythtv/libs/libmythbase/mythstorage.h | 4 ++++
+ 2 files changed, 17 insertions(+)
+
+diff --git a/mythtv/libs/libmythbase/mythstorage.cpp
b/mythtv/libs/libmythbase/mythstorage.cpp
+index 63a2905ef2d..1967cf41f52 100644
+--- a/mythtv/libs/libmythbase/mythstorage.cpp
++++ b/mythtv/libs/libmythbase/mythstorage.cpp
+@@ -3,6 +3,7 @@
+ // Myth headers
+ #include "mythstorage.h"
+ #include "mythdb.h"
++#include "mythcorecontext.h"
+
+ void SimpleDBStorage::Load(void)
+ {
+@@ -167,6 +168,12 @@ QString HostDBStorage::GetSetClause(MSqlBindings &bindings)
const
+ return clause;
+ }
+
++void HostDBStorage::Save(void)
++{
++ SimpleDBStorage::Save();
++ gCoreContext->ClearSettingsCache(settingname);
++}
++
+ //////////////////////////////////////////////////////////////////////
+
+ GlobalDBStorage::GlobalDBStorage(
+@@ -197,3 +204,9 @@ QString GlobalDBStorage::GetSetClause(MSqlBindings &bindings)
const
+
+ return clause;
+ }
++
++void GlobalDBStorage::Save(void)
++{
++ SimpleDBStorage::Save();
++ gCoreContext->ClearSettingsCache(settingname);
++}
+diff --git a/mythtv/libs/libmythbase/mythstorage.h
b/mythtv/libs/libmythbase/mythstorage.h
+index de225a94095..01a8515dc39 100644
+--- a/mythtv/libs/libmythbase/mythstorage.h
++++ b/mythtv/libs/libmythbase/mythstorage.h
+@@ -107,6 +107,8 @@ class MBASE_PUBLIC HostDBStorage : public SimpleDBStorage
+ {
+ public:
+ HostDBStorage(StorageUser *_user, const QString &name);
++ using SimpleDBStorage::Save; // prevent compiler warning
++ virtual void Save(void);
+
+ protected:
+ virtual QString GetWhereClause(MSqlBindings &bindings) const;
+@@ -120,6 +122,8 @@ class MBASE_PUBLIC GlobalDBStorage : public SimpleDBStorage
+ {
+ public:
+ GlobalDBStorage(StorageUser *_user, const QString &name);
++ using SimpleDBStorage::Save; // prevent compiler warning
++ virtual void Save(void);
+
+ protected:
+ virtual QString GetWhereClause(MSqlBindings &bindings) const;
+
+From 0f4ac3e6539a4eee70ea660c1f361d8c253b7af9 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sat, 17 Feb 2018 16:32:16 -0700
+Subject: [PATCH 02/52] External Recorder: Allow user to specify the tunning
+ timeout.
+
+(cherry picked from commit d092966109b9713d84e225da092f780d8f88c33f)
+---
+ mythtv/libs/libmythtv/videosource.cpp | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/videosource.cpp
b/mythtv/libs/libmythtv/videosource.cpp
+index 7fa15a28e3e..c4ebebb456e 100644
+--- a/mythtv/libs/libmythtv/videosource.cpp
++++ b/mythtv/libs/libmythtv/videosource.cpp
+@@ -2563,6 +2563,9 @@ ExternalConfigurationGroup::ExternalConfigurationGroup(CaptureCard
&a_parent,
+ info->setEnabled(false);
+ a_cardtype.addTargetedChild("EXTERNAL", info);
+
++ a_cardtype.addTargetedChild("EXTERNAL",
++ new ChannelTimeout(parent, 20000, 1750));
++
+ connect(device, SIGNAL(valueChanged(const QString&)),
+ this, SLOT( probeApp( const QString&)));
+
+@@ -4190,4 +4193,3 @@ void DVBConfigurationGroup::Save(void)
+ DiSEqCDev trees;
+ trees.InvalidateTrees();
+ }
+-
+
+From 0831e888842125c315906694c092a3012a1bdfd8 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sat, 17 Feb 2018 16:44:03 -0700
+Subject: [PATCH 03/52] ExternalRecorder: Allow user to specify arguments to
+ the external recorder.
+
+FileDevice does not (currently) support arguments to the file, so just let
+the user hand-type in the path.
+
+(cherry picked from commit dab5b5dd0cd971e82a2a50d4378a8e447cffad84)
+---
+ mythtv/libs/libmythtv/videosource.cpp | 27 +++++++++++++++++----------
+ 1 file changed, 17 insertions(+), 10 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/videosource.cpp
b/mythtv/libs/libmythtv/videosource.cpp
+index c4ebebb456e..02117a41f47 100644
+--- a/mythtv/libs/libmythtv/videosource.cpp
++++ b/mythtv/libs/libmythtv/videosource.cpp
+@@ -957,15 +957,18 @@ class VBIDevice : public CaptureCardComboBoxSetting
+ }
+ };
+
+-class ArgumentString : public LineEditSetting, public CaptureCardDBStorage
++class CommandPath : public MythUITextEditSetting
+ {
+ public:
+- explicit ArgumentString(const CaptureCard &parent) :
+- LineEditSetting(this, true),
+- CaptureCardDBStorage(this, parent, "audiodevice") // change to
arguments
++ explicit CommandPath(const CaptureCard &parent) :
++ MythUITextEditSetting(new CaptureCardDBStorage(this, parent,
++ "videodevice"))
+ {
+- setLabel(QObject::tr("Arguments"));
+- }
++ setLabel(QObject::tr(""));
++ setValue("");
++ setHelpText(QObject::tr("Specify the command to run, with any "
++ "needed arguments."));
++ };
+ };
+
+ class FileDevice : public MythUIFileBrowserSetting
+@@ -2553,7 +2556,8 @@ ExternalConfigurationGroup::ExternalConfigurationGroup(CaptureCard
&a_parent,
+ info(new TransTextEditSetting())
+ {
+ setVisible(false);
+- FileDevice *device = new FileDevice(parent);
++ CommandPath *device = new CommandPath(parent);
++ device->setLabel(tr("Command path"));
+ device->setHelpText(tr("A 'black box' application controlled via
"
+ "stdin, status on stderr and TransportStream "
+ "read from stdout"));
+@@ -2584,13 +2588,16 @@ void ExternalConfigurationGroup::probeApp(const QString &
path)
+ {
+ ci = tr("'%1' is valid.").arg(fileInfo.absoluteFilePath());
+ if (!fileInfo.isReadable() || !fileInfo.isFile())
+- ci = tr("'%1' is not
readable.").arg(fileInfo.absoluteFilePath());
++ ci = tr("WARNING: '%1' is not readable.")
++ .arg(fileInfo.absoluteFilePath());
+ if (!fileInfo.isExecutable())
+- ci = tr("'%1' is not
executable.").arg(fileInfo.absoluteFilePath());
++ ci = tr("WARNING: '%1' is not executable.")
++ .arg(fileInfo.absoluteFilePath());
+ }
+ else
+ {
+- ci = tr("'%1' does not
exist.").arg(fileInfo.absoluteFilePath());
++ ci = tr("WARNING: '%1' does not exist.")
++ .arg(fileInfo.absoluteFilePath());
+ }
+
+ info->setValue(ci);
+
+From 00355e29bb289532e0a592cfb04c0b08bd62541c Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sat, 17 Feb 2018 17:24:10 -0700
+Subject: [PATCH 04/52] ExternalRecorder: Allow use of the "External Channel
+ Changer".
+
+(cherry picked from commit 23d74c719ac440e4c45bf01ae21b8cdd945af483)
+---
+ mythtv/libs/libmythtv/recorders/ExternalChannel.h | 3 +++
+ .../libs/libmythtv/recorders/ExternalSignalMonitor.cpp | 9 +++++++++
+ 2 files changed, 12 insertions(+)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalChannel.h
b/mythtv/libs/libmythtv/recorders/ExternalChannel.h
+index da3b79f51b7..39e2672f967 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalChannel.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalChannel.h
+@@ -38,6 +38,9 @@ class ExternalChannel : public DTVChannel
+ virtual QString GetDevice(void) const { return m_device; }
+ virtual bool IsPIDTuningSupported(void) const { return true; }
+
++ protected:
++ virtual bool IsExternalChannelChangeSupported(void) { return true; }
++
+ private:
+ QString m_device;
+ QStringList m_args;
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+index 6ddfa4ffc1f..84fe98e0d67 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+@@ -90,6 +90,15 @@ void ExternalSignalMonitor::UpdateValues(void)
+ if (!running || exit)
+ return;
+
++ if (GetExternalChannel()->IsExternalChannelChangeInUse())
++ {
++ SignalMonitor::UpdateValues();
++
++ QMutexLocker locker(&statusLock);
++ if (!scriptStatus.IsGood())
++ return;
++ }
++
+ if (m_stream_handler_started)
+ {
+ if (!m_stream_handler->IsRunning())
+
+From 21d4e9714b6765041e669ee2bea416182ae2d3ce Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Thu, 22 Feb 2018 17:29:24 -0700
+Subject: [PATCH 05/52] ExternalStreamHandler: Kill miss-behaving external
+ recorders.
+
+(cherry picked from commit 002c7606372f1657fb4857ad708c055d28275cb7)
+---
+ .../recorders/ExternalStreamHandler.cpp | 36 ++++++++++++-------
+ .../recorders/ExternalStreamHandler.h | 4 +--
+ 2 files changed, 26 insertions(+), 14 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index b2bf0500128..774a44742fd 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -237,11 +237,6 @@ bool ExternIO::Run(void)
+ return true;
+ }
+
+-void ExternIO::Term(bool /*force*/)
+-{
+- LOG(VB_RECORD, LOG_INFO, QString("ExternIO::Term()"));
+-}
+-
+ /* Return true if the process is not, or is no longer running */
+ bool ExternIO::KillIfRunning(const QString & cmd)
+ {
+@@ -307,6 +302,7 @@ void ExternIO::Fork(void)
+ }
+
+ QString full_command = QString("%1").arg(m_args.join(" "));
++
+ if (!KillIfRunning(full_command))
+ {
+ // Give it one more chance.
+@@ -737,7 +733,8 @@ void ExternalStreamHandler::run(void)
+ break;
+ }
+ }
+- LOG(VB_RECORD, LOG_INFO, LOC + "run(): " + "shutdown");
++ LOG(VB_RECORD, LOG_INFO, LOC + "run(): " +
++ QString("%1 shutdown").arg(_error ? "Error" :
"Normal"));
+
+ RemoveAllPIDFilters();
+ SetRunning(false, true, false);
+@@ -873,9 +870,21 @@ void ExternalStreamHandler::CloseApp(void)
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC +
+ "CloseRecorder failed, sending kill.");
+- m_IO->Term();
+- sleep(5);
+- m_IO->Term(true);
++
++ QString full_command = QString("%1").arg(m_args.join("
"));
++
++ if (!m_IO->KillIfRunning(full_command))
++ {
++ // Give it one more chance.
++ usleep(50000);
++ if (!m_IO->KillIfRunning(full_command))
++ {
++ LOG(VB_GENERAL, LOG_ERR,
++ QString("Unable to kill existing '%1'.")
++ .arg(full_command));
++ return;
++ }
++ }
+ }
+ delete m_IO;
+ m_IO = 0;
+@@ -1018,7 +1027,7 @@ bool ExternalStreamHandler::StopStreaming(void)
+ .arg(result));
+ if (!result.startsWith("Warn"))
+ {
+- LOG(VB_RECORD, LOG_WARNING, LOC +
++ LOG(VB_RECORD, LOG_ERR, LOC +
+ QString("StopStreaming failed: '%1'").arg(result));
+ _error = true;
+ }
+@@ -1062,7 +1071,7 @@ bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
uint timeout,
+
+ if (m_IO->Error())
+ {
+- LOG(VB_GENERAL, LOG_ERR, "External Recorder in bad state: " +
++ LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: "
+
+ m_IO->ErrorString());
+ return false;
+ }
+@@ -1079,7 +1088,7 @@ bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
uint timeout,
+ result = m_IO->GetStatus(timeout);
+ if (m_IO->Error())
+ {
+- LOG(VB_GENERAL, LOG_ERR,
++ LOG(VB_GENERAL, LOG_ERR, LOC +
+ "Failed to read from External Recorder: " +
+ m_IO->ErrorString());
+ _error = true;
+@@ -1118,7 +1127,10 @@ bool ExternalStreamHandler::ProcessCommand(const QString &
cmd, uint timeout,
+ }
+
+ if (++m_io_errcnt > 10)
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
+ _error = true;
++ }
+ usleep(timeout);
+ }
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+index 635e35fb240..c25cb8bd287 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+@@ -30,13 +30,13 @@ class ExternIO
+ QString GetStatus(int timeout = 2500);
+ int Write(const QByteArray & buffer);
+ bool Run(void);
+- void Term(bool force = false);
+ bool Error(void) const { return !m_error.isEmpty(); }
+ QString ErrorString(void) const { return m_error; }
+ void ClearError(void) { m_error.clear(); }
+
+- private:
+ bool KillIfRunning(const QString & cmd);
++
++ private:
+ void Fork(void);
+
+ QFileInfo m_app;
+
+From b33705b0b4f30c5ebb88b2f25699bc16bf6b0a91 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Thu, 22 Feb 2018 18:51:40 -0700
+Subject: [PATCH 06/52] Allow the SignalHandler's StreamHandler to be used by
+ the Recorder.
+
+Currently only supported by the External Recorder, this allows the data seen
+by the Signal Monitor to be used by the recorder. This is helpful for
+'slow' recorders, and the IPTV recorder would also benifit.
+
+(cherry picked from commit ed85b14c137f88d0e1da8c1488f460cf68f5d5af)
+---
+ .../libmythtv/channelscan/channelscan_sm.cpp | 2 +-
+ .../libmythtv/recorders/ExternalRecorder.cpp | 19 +++--
+ .../recorders/ExternalSignalMonitor.cpp | 18 +++--
+ .../recorders/ExternalSignalMonitor.h | 2 +-
+ .../recorders/ExternalStreamHandler.cpp | 71 +++++++++++--------
+ .../recorders/ExternalStreamHandler.h | 7 +-
+ .../recorders/analogsignalmonitor.cpp | 12 ++--
+ .../libmythtv/recorders/analogsignalmonitor.h | 6 +-
+ .../libmythtv/recorders/asisignalmonitor.cpp | 10 +--
+ .../libmythtv/recorders/asisignalmonitor.h | 2 +-
+ .../recorders/cetonsignalmonitor.cpp | 10 +--
+ .../libmythtv/recorders/cetonsignalmonitor.h | 2 +-
+ .../libmythtv/recorders/dtvsignalmonitor.cpp | 3 +-
+ .../libmythtv/recorders/dtvsignalmonitor.h | 1 +
+ .../libmythtv/recorders/dvbsignalmonitor.cpp | 3 +-
+ .../libmythtv/recorders/dvbsignalmonitor.h | 1 +
+ .../recorders/firewiresignalmonitor.cpp | 20 +++---
+ .../recorders/firewiresignalmonitor.h | 1 +
+ .../libmythtv/recorders/hdhrsignalmonitor.cpp | 10 +--
+ .../libmythtv/recorders/hdhrsignalmonitor.h | 2 +-
+ .../libmythtv/recorders/iptvsignalmonitor.cpp | 7 +-
+ .../libmythtv/recorders/iptvsignalmonitor.h | 2 +-
+ .../libmythtv/recorders/scriptsignalmonitor.h | 5 +-
+ .../libmythtv/recorders/signalmonitor.cpp | 39 ++++++----
+ .../libs/libmythtv/recorders/signalmonitor.h | 6 +-
+ .../libmythtv/recorders/streamhandler.cpp | 11 +--
+ .../recorders/v4l2encsignalmonitor.cpp | 12 ++--
+ .../recorders/v4l2encsignalmonitor.h | 2 +-
+ mythtv/libs/libmythtv/tv_rec.cpp | 3 +-
+ 29 files changed, 170 insertions(+), 119 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+index be20a270fbb..f39b9c2bfc1 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+@@ -144,7 +144,7 @@ ChannelScanSM::ChannelScanSM(ScanMonitor *_scan_monitor,
+ : // Set in constructor
+ m_scanMonitor(_scan_monitor),
+ m_channel(_channel),
+- m_signalMonitor(SignalMonitor::Init(_cardtype, -1, _channel)),
++ m_signalMonitor(SignalMonitor::Init(_cardtype, -1, _channel, true)),
+ m_sourceID(_sourceID),
+ m_signalTimeout(signal_timeout),
+ m_channelTimeout(channel_timeout),
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
b/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
+index b95f7d29f19..7529bfeb4e0 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecorder.cpp
+@@ -81,11 +81,19 @@ void ExternalRecorder::run(void)
+
+ StartNewFile();
+
++ m_stream_handler->LockReplay();
++
++ m_h264_parser.Reset();
++ _wait_for_keyframe_option = true;
++ _seen_sps = false;
++
+ _stream_data->AddAVListener(this);
+ _stream_data->AddWritingListener(this);
+ m_stream_handler->AddListener(_stream_data, false, true);
+
+- StartStreaming();
++ m_stream_handler->ReplayStream();
++ m_stream_handler->UnlockReplay();
++
+
+ while (IsRecordingRequested() && !IsErrored())
+ {
+@@ -189,7 +197,8 @@ bool ExternalRecorder::PauseAndWait(int timeout)
+ else if (IsPaused(true))
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC + "PauseAndWait unpause");
+- StartStreaming();
++
++ // The SignalMonitor will StartStreaming
+
+ if (_stream_data)
+ _stream_data->Reset(_stream_data->DesiredProgram());
+@@ -205,12 +214,8 @@ bool ExternalRecorder::PauseAndWait(int timeout)
+
+ bool ExternalRecorder::StartStreaming(void)
+ {
+- m_h264_parser.Reset();
+- _wait_for_keyframe_option = true;
+- _seen_sps = false;
+-
+ LOG(VB_RECORD, LOG_INFO, LOC + "StartStreaming");
+- if (m_stream_handler && m_stream_handler->StartStreaming(true))
++ if (m_stream_handler && m_stream_handler->StartStreaming())
+ return true;
+
+ return false;
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+index 84fe98e0d67..8f406b7597d 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+@@ -36,10 +36,12 @@
+ * \param _channel ExternalChannel for card
+ * \param _flags Flags to start with
+ */
+-ExternalSignalMonitor::ExternalSignalMonitor(
+- int db_cardnum, ExternalChannel *_channel, uint64_t _flags) :
+- DTVSignalMonitor(db_cardnum, _channel, _flags),
+- m_stream_handler(NULL), m_stream_handler_started(false), m_lock_timeout(0)
++ExternalSignalMonitor::ExternalSignalMonitor(int db_cardnum,
++ ExternalChannel *_channel,
++ bool _release_stream,
++ uint64_t _flags)
++ : DTVSignalMonitor(db_cardnum, _channel, _flags, _release_stream),
++ m_stream_handler(NULL), m_stream_handler_started(false), m_lock_timeout(0)
+ {
+ QString result;
+
+@@ -69,11 +71,13 @@ void ExternalSignalMonitor::Stop(void)
+ QString result;
+
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "Stop() -- begin");
+- m_stream_handler->StopStreaming();
+
+ SignalMonitor::Stop();
+- if (GetStreamData())
++ if (release_stream && GetStreamData())
++ {
++ m_stream_handler->StopStreaming();
+ m_stream_handler->RemoveListener(GetStreamData());
++ }
+ m_stream_handler_started = false;
+
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "Stop() -- end");
+@@ -143,7 +147,7 @@ void ExternalSignalMonitor::UpdateValues(void)
+ if (!m_stream_handler_started)
+ {
+ m_stream_handler->AddListener(GetStreamData());
+- m_stream_handler->StartStreaming(false);
++ m_stream_handler->StartStreaming();
+ m_stream_handler_started = true;
+ }
+ }
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.h
b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.h
+index 96b78b8b023..28fea9a31eb 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.h
+@@ -16,7 +16,7 @@ class ExternalSignalMonitor: public DTVSignalMonitor
+ {
+ public:
+ ExternalSignalMonitor(int db_cardnum, ExternalChannel *_channel,
+- uint64_t _flags = 0);
++ bool _release_stream, uint64_t _flags = 0);
+ virtual ~ExternalSignalMonitor();
+
+ void Stop(void);
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index 774a44742fd..88f70bad49a 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -633,8 +633,8 @@ void ExternalStreamHandler::run(void)
+ m_notify = false;
+ }
+
+- while (read_len = 0, buffer.size() > 188*50 ||
+- (read_len = m_IO->Read(buffer, PACKET_SIZE, 100)) > 0)
++ while (read_len = 0, buffer.size() == PACKET_SIZE ||
++ (read_len = m_IO->Read(buffer, PACKET_SIZE - remainder, 100)) > 0)
+ {
+ if (m_IO->Error())
+ {
+@@ -651,12 +651,6 @@ void ExternalStreamHandler::run(void)
+ if (read_len > 0)
+ empty_cnt = 0;
+
+- if (_stream_data_list.empty())
+- {
+- LOG(VB_GENERAL, LOG_ERR, LOC + "_stream_data_list is empty");
+- continue;
+- }
+-
+ if (xon)
+ {
+ if (timer.elapsed() >= 2000)
+@@ -693,6 +687,11 @@ void ExternalStreamHandler::run(void)
+ }
+ }
+
++ len = remainder = buffer.size();
++
++ if (!m_stream_lock.tryLock())
++ continue;
++
+ if (!_listener_lock.tryLock())
+ continue;
+
+@@ -704,8 +703,6 @@ void ExternalStreamHandler::run(void)
+
+ _listener_lock.unlock();
+
+- len = buffer.size();
+-
+ if (m_replay)
+ {
+ m_replay_buffer += buffer.left(len - remainder);
+@@ -718,11 +715,14 @@ void ExternalStreamHandler::run(void)
+ }
+ }
+
+- if (remainder > 0 && (len > remainder)) // leftover bytes
+- buffer.remove(0, len - remainder);
+- else
++ m_stream_lock.unlock();
++
++ if (remainder == 0)
+ buffer.clear();
++ else if (len > remainder) // leftover bytes
++ buffer.remove(0, len - remainder);
+ }
++
+ if (m_IO->Error())
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC +
+@@ -908,30 +908,22 @@ bool ExternalStreamHandler::RestartStream(void)
+
+ if (streaming)
+ {
+- return StartStreaming(false);
++ return StartStreaming();
+ }
+
+ return true;
+ }
+
+-bool ExternalStreamHandler::StartStreaming(bool flush_buffer)
++void ExternalStreamHandler::ReplayStream(void)
+ {
+- QString result;
+-
+- QMutexLocker locker(&m_stream_lock);
+-
+- LOG(VB_RECORD, LOG_INFO, LOC +
+- QString("StartStreaming with %1 current listeners")
+- .arg(StreamingCount()));
+-
+- if (!IsAppOpen())
++ if (m_replay)
+ {
+- LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
+- return false;
+- }
++ QString result;
++
++ // Let the external app know that we could be busy for a little while
++ if (!m_poll_mode)
++ ProcessCommand(QString("XOFF"), 50, result);
+
+- if (flush_buffer && m_replay)
+- {
+ /* If the input is not a 'broadcast' it may only have one
+ * copy of the SPS right at the beginning of the stream,
+ * so make sure we don't miss it!
+@@ -950,6 +942,27 @@ bool ExternalStreamHandler::StartStreaming(bool flush_buffer)
+ .arg(m_replay_buffer.size()));
+ m_replay_buffer.clear();
+ m_replay = false;
++
++ // Let the external app know that we are ready
++ if (!m_poll_mode)
++ ProcessCommand(QString("XON"), 50, result);
++ }
++}
++
++bool ExternalStreamHandler::StartStreaming(void)
++{
++ QString result;
++
++ QMutexLocker locker(&m_stream_lock);
++
++ LOG(VB_RECORD, LOG_INFO, LOC +
++ QString("StartStreaming with %1 current listeners")
++ .arg(StreamingCount()));
++
++ if (!IsAppOpen())
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
++ return false;
+ }
+
+ if (StreamingCount() == 0)
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+index c25cb8bd287..e841c33d058 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+@@ -80,7 +80,11 @@ class ExternalStreamHandler : public StreamHandler
+
+ bool RestartStream(void);
+
+- bool StartStreaming(bool flush_buffer);
++ void LockReplay(void) { m_replay_lock.lock(); }
++ void UnlockReplay(bool enable_replay = false)
++ { m_replay = enable_replay; m_replay_lock.unlock(); }
++ void ReplayStream(void);
++ bool StartStreaming(void);
+ bool StopStreaming(void);
+
+ bool CheckForError(void);
+@@ -117,6 +121,7 @@ class ExternalStreamHandler : public StreamHandler
+
+ QAtomicInt m_streaming_cnt;
+ QMutex m_stream_lock;
++ QMutex m_replay_lock;
+ };
+
+ #endif // _ExternalSTREAMHANDLER_H_
+diff --git a/mythtv/libs/libmythtv/recorders/analogsignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/analogsignalmonitor.cpp
+index e1cd24bede0..e64db3bd24b 100644
+--- a/mythtv/libs/libmythtv/recorders/analogsignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/analogsignalmonitor.cpp
+@@ -17,11 +17,13 @@
+ #define LOC QString("AnalogSigMon[%1](%2): ") \
+ .arg(capturecardnum).arg(channel->GetDevice())
+
+-AnalogSignalMonitor::AnalogSignalMonitor(
+- int db_cardnum, V4LChannel *_channel, uint64_t _flags) :
+- SignalMonitor(db_cardnum, _channel, _flags),
+- m_usingv4l2(false), m_version(0), m_width(0), m_stable_time(2000),
+- m_lock_cnt(0), m_log_idx(40)
++AnalogSignalMonitor::AnalogSignalMonitor(int db_cardnum,
++ V4LChannel *_channel,
++ bool _release_stream,
++ uint64_t _flags)
++ : SignalMonitor(db_cardnum, _channel, _release_stream, _flags),
++ m_usingv4l2(false), m_version(0), m_width(0), m_stable_time(2000),
++ m_lock_cnt(0), m_log_idx(40)
+ {
+ int videofd = channel->GetFd();
+ if (videofd >= 0)
+diff --git a/mythtv/libs/libmythtv/recorders/analogsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/analogsignalmonitor.h
+index c581ec6beed..5125c29dc36 100644
+--- a/mythtv/libs/libmythtv/recorders/analogsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/analogsignalmonitor.h
+@@ -12,9 +12,9 @@ class V4LChannel;
+ class AnalogSignalMonitor : public SignalMonitor
+ {
+ public:
+- AnalogSignalMonitor(
+- int db_cardnum, V4LChannel *_channel,
+- uint64_t _flags = kSigMon_WaitForSig);
++ AnalogSignalMonitor(int db_cardnum, V4LChannel *_channel,
++ bool _release_stream,
++ uint64_t _flags = kSigMon_WaitForSig);
+
+ virtual void UpdateValues(void);
+
+diff --git a/mythtv/libs/libmythtv/recorders/asisignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/asisignalmonitor.cpp
+index e63341dd5ad..b7117503506 100644
+--- a/mythtv/libs/libmythtv/recorders/asisignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/asisignalmonitor.cpp
+@@ -38,10 +38,12 @@
+ * \param _channel ASIChannel for card
+ * \param _flags Flags to start with
+ */
+-ASISignalMonitor::ASISignalMonitor(
+- int db_cardnum, ASIChannel *_channel, uint64_t _flags) :
+- DTVSignalMonitor(db_cardnum, _channel, _flags),
+- streamHandlerStarted(false), streamHandler(NULL)
++ASISignalMonitor::ASISignalMonitor(int db_cardnum,
++ ASIChannel *_channel,
++ bool _release_stream,
++ uint64_t _flags)
++ : DTVSignalMonitor(db_cardnum, _channel, _release_stream, _flags),
++ streamHandlerStarted(false), streamHandler(NULL)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "ctor");
+ streamHandler = ASIStreamHandler::Get(_channel->GetDevice());
+diff --git a/mythtv/libs/libmythtv/recorders/asisignalmonitor.h
b/mythtv/libs/libmythtv/recorders/asisignalmonitor.h
+index 17c95422c85..8494739df62 100644
+--- a/mythtv/libs/libmythtv/recorders/asisignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/asisignalmonitor.h
+@@ -14,7 +14,7 @@ class ASISignalMonitor: public DTVSignalMonitor
+ {
+ public:
+ ASISignalMonitor(int db_cardnum, ASIChannel *_channel,
+- uint64_t _flags = 0);
++ bool _release_stream = true, uint64_t _flags = 0);
+ virtual ~ASISignalMonitor();
+
+ void Stop(void);
+diff --git a/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.cpp
+index f218af7dcff..9598997ec80 100644
+--- a/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.cpp
+@@ -30,10 +30,12 @@
+ * \param _channel CetonChannel for card
+ * \param _flags Flags to start with
+ */
+-CetonSignalMonitor::CetonSignalMonitor(
+- int db_cardnum, CetonChannel* _channel, uint64_t _flags) :
+- DTVSignalMonitor(db_cardnum, _channel, _flags),
+- streamHandlerStarted(false), streamHandler(NULL)
++CetonSignalMonitor::CetonSignalMonitor(int db_cardnum,
++ CetonChannel* _channel,
++ bool _release_stream,
++ uint64_t _flags)
++ : DTVSignalMonitor(db_cardnum, _channel, _release_stream, _flags),
++ streamHandlerStarted(false), streamHandler(NULL)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "ctor");
+
+diff --git a/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.h
+index c865b75d763..030c48c45e6 100644
+--- a/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/cetonsignalmonitor.h
+@@ -19,7 +19,7 @@ class CetonSignalMonitor: public DTVSignalMonitor
+ {
+ public:
+ CetonSignalMonitor(int db_cardnum, CetonChannel* _channel,
+- uint64_t _flags = 0);
++ bool _release_stream, uint64_t _flags = 0);
+ virtual ~CetonSignalMonitor();
+
+ void Stop(void);
+diff --git a/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.cpp
+index f6f9f67e454..ff8d4269d3c 100644
+--- a/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.cpp
+@@ -43,8 +43,9 @@ static bool insert_crc(QList<uint64_t> &seen_crc, const
PSIPTable &psip)
+
+ DTVSignalMonitor::DTVSignalMonitor(int db_cardnum,
+ DTVChannel *_channel,
++ bool _release_stream,
+ uint64_t wait_for_mask)
+- : SignalMonitor(db_cardnum, _channel, wait_for_mask),
++ : SignalMonitor(db_cardnum, _channel, _release_stream, wait_for_mask),
+ stream_data(NULL),
+ seenPAT(QObject::tr("Seen")+" PAT", "seen_pat", 1,
true, 0, 1, 0),
+ seenPMT(QObject::tr("Seen")+" PMT", "seen_pmt", 1,
true, 0, 1, 0),
+diff --git a/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.h
+index bc5cfd3a37c..28d401aa9ac 100644
+--- a/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/dtvsignalmonitor.h
+@@ -21,6 +21,7 @@ class DTVSignalMonitor : public SignalMonitor,
+ public:
+ DTVSignalMonitor(int db_cardnum,
+ DTVChannel *_channel,
++ bool _release_stream,
+ uint64_t wait_for_mask);
+ virtual ~DTVSignalMonitor();
+
+diff --git a/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.cpp
+index 9a51f9cf932..d795d8e481f 100644
+--- a/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.cpp
+@@ -43,8 +43,9 @@
+ * \param _flags Flags to start with
+ */
+ DVBSignalMonitor::DVBSignalMonitor(int db_cardnum, DVBChannel* _channel,
++ bool _release_stream,
+ uint64_t _flags)
+- : DTVSignalMonitor(db_cardnum, _channel, _flags),
++ : DTVSignalMonitor(db_cardnum, _channel, _release_stream, _flags),
+ // This snr setup is incorrect for API 3.x but works better
+ // than int16_t range in practice, however this is correct
+ // for the 4.0 DVB API which uses a uint16_t for the snr
+diff --git a/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.h
+index 9a370d5f7d9..18dc1722b49 100644
+--- a/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/dvbsignalmonitor.h
+@@ -20,6 +20,7 @@ class DVBSignalMonitor: public DTVSignalMonitor
+
+ public:
+ DVBSignalMonitor(int db_cardnum, DVBChannel* _channel,
++ bool _release_stream,
+ uint64_t _flags =
+ kSigMon_WaitForSig | kDVBSigMon_WaitForSNR |
+ kDVBSigMon_WaitForBER | kDVBSigMon_WaitForUB);
+diff --git a/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.cpp
+index 343220f3ae3..f6eb2a5f02b 100644
+--- a/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.cpp
+@@ -44,16 +44,16 @@ QMutex FirewireSignalMonitor::pat_keys_lock;
+ * \param _channel FirewireChannel for card
+ * \param _flags Flags to start with
+ */
+-FirewireSignalMonitor::FirewireSignalMonitor(
+- int db_cardnum,
+- FirewireChannel *_channel,
+- uint64_t _flags) :
+- DTVSignalMonitor(db_cardnum, _channel, _flags),
+- dtvMonitorRunning(false),
+- tableMonitorThread(NULL),
+- stb_needs_retune(true),
+- stb_needs_to_wait_for_pat(false),
+- stb_needs_to_wait_for_power(false)
++FirewireSignalMonitor::FirewireSignalMonitor(int db_cardnum,
++ FirewireChannel *_channel,
++ bool _release_stream,
++ uint64_t _flags)
++ : DTVSignalMonitor(db_cardnum, _channel, _release_stream, _flags),
++ dtvMonitorRunning(false),
++ tableMonitorThread(NULL),
++ stb_needs_retune(true),
++ stb_needs_to_wait_for_pat(false),
++ stb_needs_to_wait_for_power(false)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "ctor");
+
+diff --git a/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.h
b/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.h
+index 1a3d043943c..42848eba311 100644
+--- a/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/firewiresignalmonitor.h
+@@ -36,6 +36,7 @@ class FirewireSignalMonitor : public DTVSignalMonitor, public
TSDataListener
+ friend class FirewireTableMonitorThread;
+ public:
+ FirewireSignalMonitor(int db_cardnum, FirewireChannel *_channel,
++ bool _release_stream,
+ uint64_t _flags = kFWSigMon_WaitForPower);
+
+ virtual void HandlePAT(const ProgramAssociationTable*);
+diff --git a/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.cpp
+index 417a9336a76..e654687fc06 100644
+--- a/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.cpp
+@@ -38,10 +38,12 @@
+ * \param _channel HDHRChannel for card
+ * \param _flags Flags to start with
+ */
+-HDHRSignalMonitor::HDHRSignalMonitor(
+- int db_cardnum, HDHRChannel* _channel, uint64_t _flags) :
+- DTVSignalMonitor(db_cardnum, _channel, _flags),
+- streamHandlerStarted(false), streamHandler(NULL)
++HDHRSignalMonitor::HDHRSignalMonitor(int db_cardnum,
++ HDHRChannel* _channel,
++ bool _release_stream,
++ uint64_t _flags)
++ : DTVSignalMonitor(db_cardnum, _channel, _release_stream, _flags),
++ streamHandlerStarted(false), streamHandler(NULL)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "ctor");
+
+diff --git a/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.h
+index 016632f6e58..4cffa25b640 100644
+--- a/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/hdhrsignalmonitor.h
+@@ -14,7 +14,7 @@ class HDHRSignalMonitor: public DTVSignalMonitor
+ {
+ public:
+ HDHRSignalMonitor(int db_cardnum, HDHRChannel* _channel,
+- uint64_t _flags = 0);
++ bool _release_stream, uint64_t _flags = 0);
+ virtual ~HDHRSignalMonitor();
+
+ void Stop(void);
+diff --git a/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.cpp
+index 8e8362ef1ae..05afcf108a2 100644
+--- a/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.cpp
+@@ -24,9 +24,10 @@
+ */
+ IPTVSignalMonitor::IPTVSignalMonitor(int db_cardnum,
+ IPTVChannel *_channel,
+- uint64_t _flags) :
+- DTVSignalMonitor(db_cardnum, _channel, _flags),
+- m_streamHandlerStarted(false), m_locked(false)
++ bool _release_stream,
++ uint64_t _flags)
++ : DTVSignalMonitor(db_cardnum, _channel, _release_stream, _flags),
++ m_streamHandlerStarted(false), m_locked(false)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "ctor");
+ signalLock.SetValue(0);
+diff --git a/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.h
+index a2462d8fcc1..42e8bab3b4e 100644
+--- a/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/iptvsignalmonitor.h
+@@ -13,7 +13,7 @@ class IPTVSignalMonitor : public DTVSignalMonitor
+ friend class IPTVTableMonitorThread;
+ public:
+ IPTVSignalMonitor(int db_cardnum, IPTVChannel *_channel,
+- uint64_t _flags = 0);
++ bool _release_stream, uint64_t _flags = 0);
+ virtual ~IPTVSignalMonitor();
+
+ void Stop(void);
+diff --git a/mythtv/libs/libmythtv/recorders/scriptsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/scriptsignalmonitor.h
+index d8497922b79..40da6583727 100644
+--- a/mythtv/libs/libmythtv/recorders/scriptsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/scriptsignalmonitor.h
+@@ -10,8 +10,9 @@ class ScriptSignalMonitor : public SignalMonitor
+ {
+ public:
+ ScriptSignalMonitor(int db_cardnum, ChannelBase *_channel,
++ bool _release_stream,
+ uint64_t _flags = 0) :
+- SignalMonitor(db_cardnum, _channel, _flags)
++ SignalMonitor(db_cardnum, _channel, _release_stream, _flags)
+ {
+ signalLock.SetValue(true);
+ signalStrength.SetValue(100);
+@@ -20,7 +21,7 @@ class ScriptSignalMonitor : public SignalMonitor
+ virtual void UpdateValues(void)
+ {
+ SignalMonitor::UpdateValues();
+-
++
+ EmitStatus();
+ if (IsAllGood())
+ SendMessageAllGood();
+diff --git a/mythtv/libs/libmythtv/recorders/signalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/signalmonitor.cpp
+index d19567abc42..b0717097f6e 100644
+--- a/mythtv/libs/libmythtv/recorders/signalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/signalmonitor.cpp
+@@ -83,7 +83,8 @@ extern "C" {
+ */
+
+ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+- ChannelBase *channel)
++ ChannelBase *channel,
++ bool release_stream)
+ {
+ (void) cardtype;
+ (void) db_cardnum;
+@@ -103,7 +104,8 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+ {
+ DVBChannel *dvbc = dynamic_cast<DVBChannel*>(channel);
+ if (dvbc)
+- signalMonitor = new DVBSignalMonitor(db_cardnum, dvbc);
++ signalMonitor = new DVBSignalMonitor(db_cardnum, dvbc,
++ release_stream);
+ }
+ #endif
+
+@@ -112,13 +114,15 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int
db_cardnum,
+ {
+ V4LChannel *chan = dynamic_cast<V4LChannel*>(channel);
+ if (chan)
+- signalMonitor = new AnalogSignalMonitor(db_cardnum, chan);
++ signalMonitor = new AnalogSignalMonitor(db_cardnum, chan,
++ release_stream);
+ }
+ else if (cardtype.toUpper() == "V4L2ENC")
+ {
+ V4LChannel *chan = dynamic_cast<V4LChannel*>(channel);
+ if (chan)
+- signalMonitor = new V4L2encSignalMonitor(db_cardnum, chan);
++ signalMonitor = new V4L2encSignalMonitor(db_cardnum, chan,
++ release_stream);
+ }
+ #endif
+
+@@ -127,7 +131,8 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+ {
+ HDHRChannel *hdhrc = dynamic_cast<HDHRChannel*>(channel);
+ if (hdhrc)
+- signalMonitor = new HDHRSignalMonitor(db_cardnum, hdhrc);
++ signalMonitor = new HDHRSignalMonitor(db_cardnum, hdhrc,
++ release_stream);
+ }
+ #endif
+
+@@ -136,7 +141,8 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+ {
+ CetonChannel *cetonchan = dynamic_cast<CetonChannel*>(channel);
+ if (cetonchan)
+- signalMonitor = new CetonSignalMonitor(db_cardnum, cetonchan);
++ signalMonitor = new CetonSignalMonitor(db_cardnum, cetonchan,
++ release_stream);
+ }
+ #endif
+
+@@ -145,7 +151,8 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+ {
+ IPTVChannel *fbc = dynamic_cast<IPTVChannel*>(channel);
+ if (fbc)
+- signalMonitor = new IPTVSignalMonitor(db_cardnum, fbc);
++ signalMonitor = new IPTVSignalMonitor(db_cardnum, fbc,
++ release_stream);
+ }
+ #endif
+
+@@ -154,7 +161,8 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+ {
+ IPTVChannel *fbc = dynamic_cast<IPTVChannel*>(channel);
+ if (fbc)
+- signalMonitor = new IPTVSignalMonitor(db_cardnum, fbc);
++ signalMonitor = new IPTVSignalMonitor(db_cardnum, fbc,
++ release_stream);
+ }
+ #endif
+
+@@ -163,7 +171,8 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+ {
+ FirewireChannel *fc = dynamic_cast<FirewireChannel*>(channel);
+ if (fc)
+- signalMonitor = new FirewireSignalMonitor(db_cardnum, fc);
++ signalMonitor = new FirewireSignalMonitor(db_cardnum, fc,
++ release_stream);
+ }
+ #endif
+
+@@ -172,7 +181,8 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum,
+ {
+ ASIChannel *fc = dynamic_cast<ASIChannel*>(channel);
+ if (fc)
+- signalMonitor = new ASISignalMonitor(db_cardnum, fc);
++ signalMonitor = new ASISignalMonitor(db_cardnum, fc,
++ release_stream);
+ }
+ #endif
+
+@@ -180,12 +190,14 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int
db_cardnum,
+ {
+ ExternalChannel *fc = dynamic_cast<ExternalChannel*>(channel);
+ if (fc)
+- signalMonitor = new ExternalSignalMonitor(db_cardnum, fc);
++ signalMonitor = new ExternalSignalMonitor(db_cardnum, fc,
++ release_stream);
+ }
+
+ if (!signalMonitor && channel)
+ {
+- signalMonitor = new ScriptSignalMonitor(db_cardnum, channel);
++ signalMonitor = new ScriptSignalMonitor(db_cardnum, channel,
++ release_stream);
+ }
+
+ if (!signalMonitor)
+@@ -212,10 +224,11 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int
db_cardnum,
+ * \param wait_for_mask SignalMonitorFlags to start with.
+ */
+ SignalMonitor::SignalMonitor(int _capturecardnum, ChannelBase *_channel,
+- uint64_t wait_for_mask)
++ uint64_t wait_for_mask, bool _release_stream)
+ : MThread("SignalMonitor"),
+ channel(_channel), pParent(NULL),
+ capturecardnum(_capturecardnum), flags(wait_for_mask),
++ release_stream(_release_stream),
+ update_rate(25), minimum_update_rate(5),
+ update_done(false), notify_frontend(true),
+ tablemon(false), eit_scan(false),
+diff --git a/mythtv/libs/libmythtv/recorders/signalmonitor.h
b/mythtv/libs/libmythtv/recorders/signalmonitor.h
+index 545eda5b2bc..c4b9b97a37b 100644
+--- a/mythtv/libs/libmythtv/recorders/signalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/signalmonitor.h
+@@ -38,7 +38,8 @@ class SignalMonitor : protected MThread
+ static inline bool IsRequired(const QString &cardtype);
+ static inline bool IsSupported(const QString &cardtype);
+ static SignalMonitor *Init(QString cardtype, int db_cardnum,
+- ChannelBase *channel);
++ ChannelBase *channel,
++ bool release_stream);
+ virtual ~SignalMonitor();
+
+ // // // // // // // // // // // // // // // // // // // // // // // //
+@@ -115,7 +116,7 @@ class SignalMonitor : protected MThread
+
+ protected:
+ SignalMonitor(int db_cardnum, ChannelBase *_channel,
+- uint64_t wait_for_mask);
++ uint64_t wait_for_mask, bool _release_stream);
+
+ virtual void run(void);
+
+@@ -197,6 +198,7 @@ class SignalMonitor : protected MThread
+ TVRec *pParent;
+ int capturecardnum;
+ volatile uint64_t flags;
++ bool release_stream;
+ int update_rate;
+ uint minimum_update_rate;
+ bool update_done;
+diff --git a/mythtv/libs/libmythtv/recorders/streamhandler.cpp
b/mythtv/libs/libmythtv/recorders/streamhandler.cpp
+index 6042939a1a0..be5f9625fe6 100644
+--- a/mythtv/libs/libmythtv/recorders/streamhandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/streamhandler.cpp
+@@ -105,16 +105,7 @@ void StreamHandler::AddListener(MPEGStreamData *data,
+ _needs_buffering |= needs_buffering;
+ }
+
+- StreamDataList::iterator it = _stream_data_list.find(data);
+- if (it != _stream_data_list.end())
+- {
+- LOG(VB_GENERAL, LOG_ERR, LOC + "Programmer Error, attempted "
+- "to add a listener which is already being listened to.");
+- }
+- else
+- {
+- _stream_data_list[data] = output_file;
+- }
++ _stream_data_list[data] = output_file;
+
+ _listener_lock.unlock();
+
+diff --git a/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.cpp
b/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.cpp
+index 71d003507c1..2f8b24956f2 100644
+--- a/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.cpp
+@@ -35,11 +35,13 @@
+ * \param _channel V4lChannel for card
+ * \param _flags Flags to start with
+ */
+-V4L2encSignalMonitor::V4L2encSignalMonitor(int db_cardnum, V4LChannel *_channel,
+- uint64_t _flags) :
+- DTVSignalMonitor(db_cardnum, _channel, _flags),
+- m_stream_handler(nullptr), m_isTS(false),
+- m_strength(0), m_stable_time(1500), m_width(0), m_height(0), m_lock_cnt(0)
++V4L2encSignalMonitor::V4L2encSignalMonitor(int db_cardnum,
++ V4LChannel *_channel,
++ bool _release_stream,
++ uint64_t _flags)
++ : DTVSignalMonitor(db_cardnum, _channel, _release_stream, _flags),
++ m_stream_handler(nullptr), m_isTS(false),
++ m_strength(0), m_stable_time(1500), m_width(0), m_height(0), m_lock_cnt(0)
+ {
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "ctor");
+
+diff --git a/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.h
b/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.h
+index 1861741ab43..bb7226c8b51 100644
+--- a/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.h
++++ b/mythtv/libs/libmythtv/recorders/v4l2encsignalmonitor.h
+@@ -17,7 +17,7 @@ class V4L2encSignalMonitor: public DTVSignalMonitor
+ {
+ public:
+ V4L2encSignalMonitor(int db_cardnum, V4LChannel *_channel,
+- uint64_t _flags = 0);
++ bool _release_stream, uint64_t _flags = 0);
+ virtual ~V4L2encSignalMonitor();
+
+ void Stop(void);
+diff --git a/mythtv/libs/libmythtv/tv_rec.cpp b/mythtv/libs/libmythtv/tv_rec.cpp
+index 6861698fb01..ecb43af3923 100644
+--- a/mythtv/libs/libmythtv/tv_rec.cpp
++++ b/mythtv/libs/libmythtv/tv_rec.cpp
+@@ -2052,7 +2052,8 @@ bool TVRec::SetupSignalMonitor(bool tablemon, bool EITscan, bool
notify)
+ SignalMonitorValue::Init();
+
+ if (SignalMonitor::IsSupported(genOpt.inputtype) && channel->Open())
+- signalMonitor = SignalMonitor::Init(genOpt.inputtype, inputid, channel);
++ signalMonitor = SignalMonitor::Init(genOpt.inputtype, inputid,
++ channel, false);
+
+ if (signalMonitor)
+ {
+
+From 77a0ff883d171d2e8a393b8ff9740d9b2477f4e1 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sun, 25 Feb 2018 19:28:57 -0700
+Subject: [PATCH 07/52] ExternIO::KillIfRunning: Fix QString arguments.
+
+(cherry picked from commit f9c9dd44b68c49530a7f84102483a22772b77a9c)
+---
+ mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index 88f70bad49a..be187ffe6e3 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -263,7 +263,7 @@ bool ExternIO::KillIfRunning(const QString & cmd)
+ .arg(cmd));
+ res_kil = system(kil.toUtf8().constData());
+ if (WEXITSTATUS(res_kil) == 1)
+- LOG(VB_GENERAL, LOG_WARNING, QString("'%1' failed")
++ LOG(VB_GENERAL, LOG_WARNING, QString("'%1' failed: %2")
+ .arg(kil).arg(ENO));
+
+ res_grp = system(grp.toUtf8().constData());
+
+From 925ceea0fb269b005ae94381374419b1c8389f5e Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Wed, 28 Feb 2018 17:02:07 -0700
+Subject: [PATCH 08/52] ExternalStreamHandler::ProcessCommand: Stop looping
+ after hitting io error limit.
+
+(cherry picked from commit 03bc275cf9533c8fa9d767598815d720f7dc9efd)
+---
+ mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index be187ffe6e3..c555fca2e10 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -1143,6 +1143,7 @@ bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
uint timeout,
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
+ _error = true;
++ break;
+ }
+ usleep(timeout);
+ }
+
+From 67e8d62d222e1a9addcd384900d77592ca178ffa Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Thu, 8 Mar 2018 15:25:51 -0500
+Subject: [PATCH 09/52] Video Playback: Fix external subtitle problem
+
+Due to an incorrect usage of QString::right, any video which has
+one of the following strings anywhere in the filename after the 4th character
+would not look for external subtitles:
+ass srt ssa sub txt gif png
+
+Fixes #13231
+
+(cherry picked from commit 02de2e4ba39416553d1df8494f1d586b87013c26)
+---
+ mythtv/libs/libmythtv/fileringbuffer.cpp | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/fileringbuffer.cpp
b/mythtv/libs/libmythtv/fileringbuffer.cpp
+index a0925333b7b..f0409997dfd 100644
+--- a/mythtv/libs/libmythtv/fileringbuffer.cpp
++++ b/mythtv/libs/libmythtv/fileringbuffer.cpp
+@@ -352,7 +352,9 @@ bool FileRingBuffer::OpenFile(const QString &lfilename, uint
retry_ms)
+ if (suffixPos > 0)
+ {
+ baseName = tmpSubName.left(suffixPos);
+- extension = tmpSubName.right(suffixPos-1);
++ int extnleng = tmpSubName.size() - baseName.size() - 1;
++ extension = tmpSubName.right(extnleng);
++
+ if (is_subtitle_possible(extension))
+ {
+ QMutexLocker locker(&subExtLock);
+
+From c0ac5283ab2a4af571837520a40cc718e9e842c8 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Fri, 16 Mar 2018 10:49:38 -0500
+Subject: [PATCH 10/52] Fix issues with inputs that support multiple, physical
+ connections
+
+There was some superflous and flat out wrong code left over from the
+card/input to input conversion that prevented inputs from switching
+among connections like tuner, s-video and composite. Thanks to
+ltskinol(a)gmail.com for doing most of the leg work and testing the
+fixes.
+
+Refs #13247
+
+(cherry picked from commit 1f8cd4dbf9196fd1c8f8e6096fd2ccee24514878)
+---
+ mythtv/libs/libmythbase/mythversion.h | 2 +-
+ .../libs/libmythtv/recorders/v4lchannel.cpp | 8 +--
+ mythtv/libs/libmythtv/recorders/v4lchannel.h | 1 -
+ mythtv/libs/libmythtv/tv_rec.cpp | 58 +------------------
+ mythtv/libs/libmythtv/tv_rec.h | 3 -
+ 5 files changed, 5 insertions(+), 67 deletions(-)
+
+diff --git a/mythtv/libs/libmythbase/mythversion.h
b/mythtv/libs/libmythbase/mythversion.h
+index 68d5b5b1b56..e708a5f2fbb 100644
+--- a/mythtv/libs/libmythbase/mythversion.h
++++ b/mythtv/libs/libmythbase/mythversion.h
+@@ -13,7 +13,7 @@
+ /// Update this whenever the plug-in ABI changes.
+ /// Including changes in the libmythbase, libmyth, libmythtv, libmythav* and
+ /// libmythui class methods in exported headers.
+-#define MYTH_BINARY_VERSION "29.20180131-3"
++#define MYTH_BINARY_VERSION "29.20180316-1"
+
+ /** \brief Increment this whenever the MythTV network protocol changes.
+ * Note that the token currently cannot contain spaces.
+diff --git a/mythtv/libs/libmythtv/recorders/v4lchannel.cpp
b/mythtv/libs/libmythtv/recorders/v4lchannel.cpp
+index f4e268628eb..fccbaea9668 100644
+--- a/mythtv/libs/libmythtv/recorders/v4lchannel.cpp
++++ b/mythtv/libs/libmythtv/recorders/v4lchannel.cpp
+@@ -40,7 +40,6 @@ V4LChannel::V4LChannel(TVRec *parent, const QString &videodevice,
+ audio_device(audiodevice), videofd(-1),
+ device_name(), driver_name(),
+ curList(NULL), totalChannels(0),
+- currentFormat(),
+ has_stream_io(false), has_std_io(false),
+ has_async_io(false),
+ has_tuner(false), has_sliced_vbi(false),
+@@ -301,10 +300,9 @@ void V4LChannel::SetFormat(const QString &format)
+ LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SetFormat(%1) fmt(%2)
input(%3)")
+ .arg(format).arg(fmt).arg(inputNum));
+
+- if ((fmt == currentFormat) || SetInputAndFormat(inputNum, fmt))
+- {
+- currentFormat = fmt;
+- }
++ if (!SetInputAndFormat(inputNum, fmt))
++ LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to set format." + ENO);
++
+ }
+
+ int V4LChannel::SetDefaultFreqTable(const QString &name)
+diff --git a/mythtv/libs/libmythtv/recorders/v4lchannel.h
b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+index 6b6b54ae889..bdabb473598 100644
+--- a/mythtv/libs/libmythtv/recorders/v4lchannel.h
++++ b/mythtv/libs/libmythtv/recorders/v4lchannel.h
+@@ -94,7 +94,6 @@ class V4LChannel : public DTVChannel
+ struct CHANLIST *curList;
+ int totalChannels;
+
+- QString currentFormat;
+ bool has_stream_io;
+ bool has_std_io;
+ bool has_async_io;
+diff --git a/mythtv/libs/libmythtv/tv_rec.cpp b/mythtv/libs/libmythtv/tv_rec.cpp
+index ecb43af3923..6556380a1fb 100644
+--- a/mythtv/libs/libmythtv/tv_rec.cpp
++++ b/mythtv/libs/libmythtv/tv_rec.cpp
+@@ -3551,48 +3551,6 @@ void TVRec::HandleTuning(void)
+ }
+ }
+
+-/** \fn TVRec::TuningCheckForHWChange(const
TuningRequest&,QString&,QString&)
+- * \brief Returns inputid for device info row in capturecard if it changes.
+- */
+-uint TVRec::TuningCheckForHWChange(const TuningRequest &request,
+- QString &channum,
+- QString &inputname)
+-{
+- LOG(VB_RECORD, LOG_INFO, LOC + QString("request (%1) channum (%2) inputname
(%3)")
+-
.arg(request.toString()).arg(channum).arg(inputname));
+- if (!channel)
+- return 0;
+-
+- uint curInputID = 0, newInputID = 0;
+- channum = request.channel;
+- inputname = request.input;
+-
+- if (request.program)
+- request.program->QueryTuningInfo(channum, inputname);
+-
+- if (!channum.isEmpty() && inputname.isEmpty())
+- channel->CheckChannel(channum);
+-
+- if (!inputname.isEmpty())
+- {
+- curInputID = channel->GetInputID();
+- newInputID = channel->GetInputID();
+- LOG(VB_GENERAL, LOG_INFO, LOC + QString("HW Tuner: %1->%2")
+- .arg(curInputID).arg(newInputID));
+- }
+-
+- if (curInputID != newInputID || !CardUtil::IsChannelReusable(genOpt.inputtype))
+- {
+- LOG(VB_RECORD, LOG_INFO, LOC + QString("Inputtype HW Tuner newinputid
channum curinputid: %1->%2 %3")
+-
.arg(curInputID).arg(newInputID).arg(channum));
+- if (channum.isEmpty())
+- channum = GetStartChannel(newInputID);
+- return newInputID;
+- }
+-
+- return 0;
+-}
+-
+ /** \fn TVRec::TuningShutdowns(const TuningRequest&)
+ * \brief This shuts down anything that needs to be shut down
+ * before handling the passed in tuning request.
+@@ -3603,7 +3561,6 @@ void TVRec::TuningShutdowns(const TuningRequest &request)
+ .arg(request.toString()));
+
+ QString channum, inputname;
+- uint newInputID = TuningCheckForHWChange(request, channum, inputname);
+
+ if (scanner && !(request.flags & kFlagEITScan) &&
+ HasFlags(kFlagEITScannerRunning))
+@@ -3635,7 +3592,7 @@ void TVRec::TuningShutdowns(const TuningRequest &request)
+
+ // At this point any waits are canceled.
+
+- if (newInputID || (request.flags & kFlagNoRec))
++ if (request.flags & kFlagNoRec)
+ {
+ if (HasFlags(kFlagDummyRecorderRunning))
+ {
+@@ -3659,19 +3616,6 @@ void TVRec::TuningShutdowns(const TuningRequest &request)
+ // At this point the channel is shut down
+ }
+
+- // handle HW change for digital/analog inputs
+- if (newInputID)
+- {
+- LOG(VB_CHANNEL, LOG_INFO, LOC +
+- "TuningShutdowns: Recreating channel...");
+- channel->Close();
+- delete channel;
+- channel = NULL;
+-
+- GetDevices(newInputID, genOpt, dvbOpt, fwOpt);
+- CreateChannel(channum, false);
+- }
+-
+ if (ringBuffer && (request.flags & kFlagKillRingBuffer))
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC + "Tearing down RingBuffer");
+diff --git a/mythtv/libs/libmythtv/tv_rec.h b/mythtv/libs/libmythtv/tv_rec.h
+index e8106d8b406..20fb29b21ea 100644
+--- a/mythtv/libs/libmythtv/tv_rec.h
++++ b/mythtv/libs/libmythtv/tv_rec.h
+@@ -299,9 +299,6 @@ class MTV_PUBLIC TVRec : public SignalMonitorListener, public
QRunnable
+ void TuningNewRecorder(MPEGStreamData*);
+ void TuningRestartRecorder(void);
+ QString TuningGetChanNum(const TuningRequest&, QString &input) const;
+- uint TuningCheckForHWChange(const TuningRequest&,
+- QString &channum,
+- QString &inputname);
+ bool TuningOnSameMultiplex(TuningRequest &request);
+
+ void HandleStateChange(void);
+
+From bd764db51fed90450a9c96eb2102f2f27b6285ad Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Wed, 14 Feb 2018 18:01:07 -0500
+Subject: [PATCH 11/52] Fix compiler error when compiling with USING_ASI.
+
+Cherry picked from master and cut to the single change to correct a
+missing variable in a function definition.
+
+(cherry picked from commit cadee49a417edc121d6babaacfabef63e508809e)
+---
+ mythtv/libs/libmythtv/recorders/asistreamhandler.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/asistreamhandler.cpp
b/mythtv/libs/libmythtv/recorders/asistreamhandler.cpp
+index fc3e1f9d783..5105f637525 100644
+--- a/mythtv/libs/libmythtv/recorders/asistreamhandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/asistreamhandler.cpp
+@@ -65,7 +65,7 @@ ASIStreamHandler *ASIStreamHandler::Get(const QString &devname,
+ return _handlers[devkey];
+ }
+
+-void ASIStreamHandler::Return(ASIStreamHandler * & ref)
++void ASIStreamHandler::Return(ASIStreamHandler * & ref, int recorder_id)
+ {
+ QMutexLocker locker(&_handlers_lock);
+
+
+From 60e40b352ab95a135ec2ab8f9f1ee93b4f9d245e Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sat, 24 Mar 2018 14:32:16 -0400
+Subject: [PATCH 12/52] Raspberry Pi Openmax Audio: Fix swapped channels when
+ using PCM output
+
+Fixes #13249
+
+(cherry picked from commit 5377e5089e243ec282514b6d68afb800c02aca03)
+---
+ mythtv/libs/libmyth/audio/audiooutput_omx.cpp | 68 +++++++++++++++++++
+ mythtv/libs/libmyth/audio/audiooutput_omx.h | 4 ++
+ 2 files changed, 72 insertions(+)
+
+diff --git a/mythtv/libs/libmyth/audio/audiooutput_omx.cpp
b/mythtv/libs/libmyth/audio/audiooutput_omx.cpp
+index 53344db10f9..bf119470116 100644
+--- a/mythtv/libs/libmyth/audio/audiooutput_omx.cpp
++++ b/mythtv/libs/libmyth/audio/audiooutput_omx.cpp
+@@ -438,6 +438,55 @@ void AudioOutputOMX::CloseDevice(void)
+ m_audiorender.Shutdown();
+ }
+
++// HDMI uses a different channel order from WAV and others
++// See CEA spec: Table 20, Audio InfoFrame
++
++#define REORD_NUMCHAN 6 // Min Num of channels for reorder to be done
++#define REORD_A 2 // First channel to switch
++#define REORD_B 3 // Second channel to switch
++
++void AudioOutputOMX::reorderChannels(int *aubuf, int size)
++{
++ int t_size = size;
++ int *sample = aubuf;
++ while (t_size >= REORD_NUMCHAN*4)
++ {
++ int savefirst = sample[REORD_A];
++ sample[REORD_A] = sample[REORD_B];
++ sample[REORD_B] = savefirst;
++ sample += channels;
++ t_size -= output_bytes_per_frame;
++ }
++}
++
++void AudioOutputOMX::reorderChannels(short *aubuf, int size)
++{
++ int t_size = size;
++ short *sample = aubuf;
++ while (t_size >= REORD_NUMCHAN*2)
++ {
++ short savefirst = sample[REORD_A];
++ sample[REORD_A] = sample[REORD_B];
++ sample[REORD_B] = savefirst;
++ sample += channels;
++ t_size -= output_bytes_per_frame;
++ }
++}
++
++void AudioOutputOMX::reorderChannels(uchar *aubuf, int size)
++{
++ int t_size = size;
++ uchar *sample = aubuf;
++ while (t_size >= REORD_NUMCHAN)
++ {
++ uchar savefirst = sample[REORD_A];
++ sample[REORD_A] = sample[REORD_B];
++ sample[REORD_B] = savefirst;
++ sample += channels;
++ t_size -= output_bytes_per_frame;
++ }
++}
++
+ // virtual
+ void AudioOutputOMX::WriteAudio(uchar *aubuf, int size)
+ {
+@@ -447,6 +496,25 @@ void AudioOutputOMX::WriteAudio(uchar *aubuf, int size)
+ return;
+ }
+
++ // Reorder channels for CEA format
++ // See CEA spec: Table 20, Audio InfoFrame
++ if (!enc && !reenc && channels >= REORD_NUMCHAN)
++ {
++ int samplesize = output_bytes_per_frame / channels;
++ switch (samplesize)
++ {
++ case 1:
++ reorderChannels(aubuf, size);
++ break;
++ case 2:
++ reorderChannels((short*)aubuf, size);
++ break;
++ case 4:
++ reorderChannels((int*)aubuf, size);
++ break;
++ }
++ }
++
+ while (size > 0)
+ {
+ if (!m_ibufs_sema.tryAcquire(1, 3000))
+diff --git a/mythtv/libs/libmyth/audio/audiooutput_omx.h
b/mythtv/libs/libmyth/audio/audiooutput_omx.h
+index 1903d3718ec..c56cebd7089 100644
+--- a/mythtv/libs/libmyth/audio/audiooutput_omx.h
++++ b/mythtv/libs/libmyth/audio/audiooutput_omx.h
+@@ -49,6 +49,10 @@ class AudioOutputOMX : public AudioOutputBase, private
OMXComponentCtx
+ typedef OMX_ERRORTYPE ComponentCB();
+ ComponentCB FreeBuffersCB, AllocBuffersCB;
+
++ void reorderChannels(int *aubuf, int size);
++ void reorderChannels(short *aubuf, int size);
++ void reorderChannels(uchar *aubuf, int size);
++
+ private:
+ OMXComponent m_audiorender;
+
+
+From 329c235b572f9aa5de3758ca8b311f225de45d1e Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Wed, 11 Apr 2018 19:01:21 -0400
+Subject: [PATCH 13/52] Raspberry Pi: Fix decoder error on mp4 files
+
+There was test code in OpenMAX support that corrupts
+AVCodecContext. New versions of FFmpeg fail to open the
+codec with that corrupted data in existence.
+
+Fixes #13244
+
+(cherry picked from commit 4a828998bf0cd82430e1854429742a125eb80815)
+---
+ mythtv/libs/libmythtv/privatedecoder_omx.cpp | 15 ---------------
+ 1 file changed, 15 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/privatedecoder_omx.cpp
b/mythtv/libs/libmythtv/privatedecoder_omx.cpp
+index d787ba94990..b0760da2345 100644
+--- a/mythtv/libs/libmythtv/privatedecoder_omx.cpp
++++ b/mythtv/libs/libmythtv/privatedecoder_omx.cpp
+@@ -478,21 +478,6 @@ bool PrivateDecoderOMX::CreateFilter(AVCodecContext *avctx)
+ return false;
+ }
+
+- // Test the filter
+- static const uint8_t test[] = { 0U,0U,0U,2U,0U,0U };
+- int outbuf_size = 0;
+- uint8_t *outbuf = NULL;
+- int res = av_bitstream_filter_filter(m_filter, avctx, NULL, &outbuf,
+- &outbuf_size, test, sizeof test, 0);
+- if (res < 0)
+- {
+- LOG(VB_PLAYBACK, LOG_ERR, LOC + "h264_mp4toannexb filter test
failed");
+- av_bitstream_filter_close(m_filter);
+- m_filter = NULL;
+- return false;
+- }
+-
+- av_freep(&outbuf);
+ LOG(VB_GENERAL, LOG_INFO, LOC + "Installed h264_mp4toannexb filter");
+ return true;
+ }
+
+From 56030f3f2a29ec67ccb2849719070c50d149587a Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Sun, 22 Apr 2018 21:17:10 -0400
+Subject: [PATCH 14/52] Raspberry Pi: Fix problem with startup on recent
+ Raspbian Stretch builds
+
+There is no longer a libGLESv2.so or libEGL.so installed with raspbian
+so the build will now install links to the new files that provide
+those libraries.
+
+(cherry picked from commit e1c1e873859276363b429ac0032e90d05b879d8d)
+---
+ mythtv/programs/mythfrontend/mythfrontend.pro | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/mythtv/programs/mythfrontend/mythfrontend.pro
b/mythtv/programs/mythfrontend/mythfrontend.pro
+index 2bc61565d0d..0be1ee2fb5a 100644
+--- a/mythtv/programs/mythfrontend/mythfrontend.pro
++++ b/mythtv/programs/mythfrontend/mythfrontend.pro
+@@ -164,8 +164,10 @@ using_openmax {
+ createlinks.path = $${PREFIX}/share/mythtv/lib
+ createlinks.extra = ln -fs /opt/vc/lib/libbrcmEGL.so
$(INSTALL_ROOT)/$${PREFIX}/share/mythtv/lib/libEGL.so.1.0.0 ;
+ createlinks.extra += ln -fs /opt/vc/lib/libbrcmEGL.so
$(INSTALL_ROOT)/$${PREFIX}/share/mythtv/lib/libEGL.so.1 ;
++ createlinks.extra += ln -fs /opt/vc/lib/libbrcmEGL.so
$(INSTALL_ROOT)/$${PREFIX}/share/mythtv/lib/libEGL.so ;
+ createlinks.extra += ln -fs /opt/vc/lib/libbrcmGLESv2.so
$(INSTALL_ROOT)/$${PREFIX}/share/mythtv/lib/libGLESv2.so.2.0.0 ;
+ createlinks.extra += ln -fs /opt/vc/lib/libbrcmGLESv2.so
$(INSTALL_ROOT)/$${PREFIX}/share/mythtv/lib/libGLESv2.so.2 ;
++ createlinks.extra += ln -fs /opt/vc/lib/libbrcmGLESv2.so
$(INSTALL_ROOT)/$${PREFIX}/share/mythtv/lib/libGLESv2.so ;
+ INSTALLS += createlinks
+ } else {
+ # For raspberry Pi Raspbian pre-stretch
+
+From 280138b452c16c3ce02734b57d9245f91002af81 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Wed, 25 Apr 2018 16:40:24 -0500
+Subject: [PATCH 15/52] Stop active EIT scans from interfering with recordings
+
+Because DiSEqC inputs no longer share file descriptors, extra care
+must be taken with active EIT scans to prevent them from holding an
+open file descripter and causing recordings to fail. This change more
+aggressively stops active scans when recordings are pending and also
+releases all resources when they are stopped.
+
+Many, many, many thanks go to jmwislez at
gmail.com for being so
+patient and trying so many patches to identify and ultimately fix the
+problem.
+
+Refs #13207
+
+(cherry picked from commit ba4c52b9e5d9a9f31ebbd223c166759235dfd379)
+---
+ mythtv/libs/libmythtv/tv_rec.cpp | 28 ++++++++++++++++------------
+ 1 file changed, 16 insertions(+), 12 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/tv_rec.cpp b/mythtv/libs/libmythtv/tv_rec.cpp
+index 6556380a1fb..40d55aa7b0d 100644
+--- a/mythtv/libs/libmythtv/tv_rec.cpp
++++ b/mythtv/libs/libmythtv/tv_rec.cpp
+@@ -1567,13 +1567,6 @@ void TVRec::HandlePendingRecordings(void)
+ {
+ QMutexLocker pendlock(&pendingRecLock);
+
+- if (pendingRecordings.empty())
+- return;
+-
+- // If we have a pending recording and AskAllowRecording
+- // or DoNotAskAllowRecording is set and the frontend is
+- // ready send an ASK_RECORDING query to frontend.
+-
+ PendingMap::iterator it, next;
+
+ for (it = pendingRecordings.begin(); it != pendingRecordings.end();)
+@@ -1592,6 +1585,21 @@ void TVRec::HandlePendingRecordings(void)
+ it = next;
+ }
+
++ if (pendingRecordings.empty())
++ return;
++
++ // Make sure EIT scan is stopped so it does't interfere
++ if (scanner && HasFlags(kFlagEITScannerRunning))
++ {
++ LOG(VB_CHANNEL, LOG_INFO,
++ LOC + "Stopping active EIT scan for pending recording.");
++ tuningRequests.enqueue(TuningRequest(kFlagNoRec));
++ }
++
++ // If we have a pending recording and AskAllowRecording
++ // or DoNotAskAllowRecording is set and the frontend is
++ // ready send an ASK_RECORDING query to frontend.
++
+ bool has_rec = false;
+ it = pendingRecordings.begin();
+ if ((1 == pendingRecordings.size()) &&
+@@ -3937,11 +3945,7 @@ MPEGStreamData *TVRec::TuningSignalCheck(void)
+
+ if (scanner && HasFlags(kFlagEITScannerRunning))
+ {
+- scanner->StopActiveScan();
+- ClearFlags(kFlagEITScannerRunning, __FILE__, __LINE__);
+- eitScanStartTime = current_time;
+- eitScanStartTime = eitScanStartTime.addSecs(eitCrawlIdleStart +
+- eit_start_rand(inputid, eitTransportTimeout));
++ tuningRequests.enqueue(TuningRequest(kFlagNoRec));
+ }
+ }
+ else if (curRecording && !reachedPreFail && current_time >
preFailDeadline)
+
+From 81d2fb020fd1e6e5c1114ff3e6c02f2e33a78188 Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Mon, 7 May 2018 19:45:09 -0400
+Subject: [PATCH 16/52] Fix crash (abort) in MythSocket::ResetReal
+
+In libmythbase mythsocket.cpp MythSocket::ResetReal there can be cases
+where there are no bytes to available to read, so the vector is never
+resized to greater than zero, which results in an abort when
+referencing the 1st (non-existant) element which is now checked by
+gcc8 (libstdc++).
+
+Patch from Gary Buhrmaster, fixes #13264.
+
+(cherry picked from commit 1f78097f0cef0ca15e1b7eee4e94fd17ca3b07b7)
+---
+ mythtv/libs/libmythbase/mythsocket.cpp | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythbase/mythsocket.cpp
b/mythtv/libs/libmythbase/mythsocket.cpp
+index 63f10b53313..bed0172cad9 100644
+--- a/mythtv/libs/libmythbase/mythsocket.cpp
++++ b/mythtv/libs/libmythbase/mythsocket.cpp
+@@ -1003,8 +1003,11 @@ void MythSocket::ResetReal(void)
+ do
+ {
+ uint avail = m_tcpSocket->bytesAvailable();
+- trash.resize(max((uint)trash.size(),avail));
+- m_tcpSocket->read(&trash[0], avail);
++ if (avail)
++ {
++ trash.resize(max((uint)trash.size(),avail));
++ m_tcpSocket->read(&trash[0], avail);
++ }
+
+ LOG(VB_NETWORK, LOG_INFO, LOC + "Reset() " +
+ QString("%1 bytes available").arg(avail));
+
+From c2fa4ba7981032edf6dcafec90d1e64a8139144b Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Mon, 7 May 2018 22:58:04 -0400
+Subject: [PATCH 17/52] Fix crash in ProgramMapTable::Create
+
+Abort in libmythtv due to accessing beyond the size of the vector.
+
+With at least some recording sources (I am using an OCUR device) it is
+apparently possible to end up having zero descriptors in the stream
+when ProgramMapTable::Create is called, but GCC 8 (and libstdc++) now
+includes AddressSanitizer integration for std::vector, detecting
+out-of-range accesses to a vector, which means that referencing the
+0th element is now an error (and an abort) even though the called code
+would not typically copy any data.
+
+Patch from Gary Buhrmaster, fixes #13263.
+
+(cherry picked from commit 7f00642ba11eb0d9d633a23ce74e5b695c05153e)
+---
+ mythtv/libs/libmythtv/mpeg/mpegtables.cpp | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
b/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
+index 867581a0e8b..372c421a31e 100644
+--- a/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
++++ b/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
+@@ -445,7 +445,8 @@ ProgramMapTable* ProgramMapTable::Create(
+ uint len = global_desc[i][1] + 2;
+ gdesc.insert(gdesc.end(), global_desc[i], global_desc[i] + len);
+ }
+- pmt->SetProgramInfo(&gdesc[0], gdesc.size());
++ if (!gdesc.empty())
++ pmt->SetProgramInfo(&gdesc[0], gdesc.size());
+
+ for (uint i = 0; i < count; i++)
+ {
+@@ -457,7 +458,8 @@ ProgramMapTable* ProgramMapTable::Create(
+ prog_desc[i][j], prog_desc[i][j] + len);
+ }
+
+- pmt->AppendStream(pids[i], types[i], &pdesc[0], pdesc.size());
++ if (!pdesc.empty())
++ pmt->AppendStream(pids[i], types[i], &pdesc[0], pdesc.size());
+ }
+ pmt->Finalize();
+
+
+From 5f20e4f3f7e1dc3199888e9004d91b835a78f302 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Wed, 9 May 2018 19:22:44 -0400
+Subject: [PATCH 18/52] configure: new version of freetype2 does not support
+ freetype-config
+
+freetype2 v2.9.1 uses only pkgconfig and removes support for freetype-config
+
+Fixes #13262
+---
+ mythtv/configure | 51 ++++++++----------------------------------------
+ 1 file changed, 8 insertions(+), 43 deletions(-)
+
+diff --git a/mythtv/configure b/mythtv/configure
+index fdf897b4497..14535b0611d 100755
+--- a/mythtv/configure
++++ b/mythtv/configure
+@@ -1452,26 +1452,6 @@ require_pkg_config(){
+ use_pkg_config "$@" || die "ERROR: $pkg not found using
pkg-config$pkg_config_fail_message"
+ }
+
+-require_libfreetype(){
+- log require_libfreetype "$@"
+- pkg="freetype2"
+- check_cmd $pkg_config --exists --print-errors $pkg \
+- || die "ERROR: $pkg not found"
+- pkg_cflags=$($pkg_config --cflags $pkg_config_flags $pkg)
+- pkg_libs=$($pkg_config --libs $pkg_config_flags $pkg)
+- {
+- echo "#include <ft2build.h>"
+- echo "#include FT_FREETYPE_H"
+- echo "long check_func(void) { return (long) FT_Init_FreeType; }"
+- echo "int main(void) { return 0; }"
+- } | check_ld "cc" $pkg_cflags $pkg_libs \
+- && set_safe "${pkg}_cflags" $pkg_cflags \
+- && set_safe "${pkg}_libs" $pkg_libs \
+- || die "ERROR: $pkg not found"
+- add_cflags $(get_safe "${pkg}_cflags")
+- add_extralibs $(get_safe "${pkg}_libs")
+-}
+-
+ hostcc_e(){
+ eval printf '%s\\n' $HOSTCC_E
+ }
+@@ -1628,25 +1608,6 @@ check_exec_cxx(){
+ check_ld_cxx "$@" && { enabled cross_compile || $TMPE >>
$logfile 2>&1; }
+ }
+
+-check_foo_config(){
+- cfg=$1
+- pkg=$2
+- header=$3
+- func=$4
+- shift 4
+- disable $cfg
+- check_cmd ${pkg}-config --version
+- err=$?
+- if test "$err" = 0; then
+- backup_cflags=$CFLAGS
+- temp_extralibs=$(${pkg}-config --libs)
+- add_cflags $(${pkg}-config --cflags)
+- check_lib "$@" $header $func $temp_extralibs && enable $cfg
+- CFLAGS=$backup_cflags
+- fi
+- return $err
+-}
+-
+ non_standard_header(){
+ test x"$1" != "x/usr/include"
+ }
+@@ -6921,7 +6882,7 @@ flite_libs="-lflite_cmu_time_awb -lflite_cmu_us_awb
-lflite_cmu_us_kal -lflite_c
+ enabled libflite && require2 libflite "flite/flite.h"
flite_init $flite_libs
+ enabled fontconfig && enable libfontconfig
+ enabled libfontconfig && require_pkg_config fontconfig
"fontconfig/fontconfig.h" FcInit
+-enabled libfreetype && require_libfreetype
++enabled libfreetype && require_pkg_config freetype2 "ft2build.h
FT_FREETYPE_H" FT_Init_FreeType
+ enabled libfribidi && require_pkg_config fribidi fribidi.h
fribidi_version_info
+ enabled libgme && require libgme gme/gme.h gme_new_emu -lgme
-lstdc++
+ enabled libgsm && { for gsm_hdr in "gsm.h"
"gsm/gsm.h"; do
+@@ -7134,7 +7095,11 @@ void foo(){switch (0) case 0: case (sizeof(long) == $sizeof):;}
+ EOF
+ done
+
+-check_foo_config freetype2 freetype ft2build.h FT_Init_FreeType
++if require_pkg_config freetype2 "ft2build.h FT_FREETYPE_H" FT_Init_FreeType ;
then
++ enable freetype2
++else
++ disable freetype2
++fi
+
+ enabled freetype2 ||
+ die "ERROR! You must have FreeType installed to compile MythTV."
+@@ -8571,8 +8536,8 @@ if enabled darwin_da; then
+ fi
+
+ if enabled freetype2; then
+- echo "FREETYPE_CFLAGS=`freetype-config --cflags`" >> $TMPMAK
+- echo "FREETYPE_LIBS=`freetype-config --libs`" >> $TMPMAK
++ echo "FREETYPE_CFLAGS=$($pkg_config --cflags $pkg_config_flags freetype2)"
>> $TMPMAK
++ echo "FREETYPE_LIBS=$($pkg_config --libs $pkg_config_flags freetype2)"
>> $TMPMAK
+ fi
+
+ if test $target_os = darwin; then
+
+From 0849e9959639dd8dfeab0af4ce0dfc166da1f896 Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Thu, 10 May 2018 15:05:38 -0400
+Subject: [PATCH 19/52] Revert "Fix crash in ProgramMapTable::Create"
+
+This reverts commit c2fa4ba7. It is causing failed recordings on
+DVB-S/S2 SAT multiplexes.
+---
+ mythtv/libs/libmythtv/mpeg/mpegtables.cpp | 6 ++----
+ 1 file changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
b/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
+index 372c421a31e..867581a0e8b 100644
+--- a/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
++++ b/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
+@@ -445,8 +445,7 @@ ProgramMapTable* ProgramMapTable::Create(
+ uint len = global_desc[i][1] + 2;
+ gdesc.insert(gdesc.end(), global_desc[i], global_desc[i] + len);
+ }
+- if (!gdesc.empty())
+- pmt->SetProgramInfo(&gdesc[0], gdesc.size());
++ pmt->SetProgramInfo(&gdesc[0], gdesc.size());
+
+ for (uint i = 0; i < count; i++)
+ {
+@@ -458,8 +457,7 @@ ProgramMapTable* ProgramMapTable::Create(
+ prog_desc[i][j], prog_desc[i][j] + len);
+ }
+
+- if (!pdesc.empty())
+- pmt->AppendStream(pids[i], types[i], &pdesc[0], pdesc.size());
++ pmt->AppendStream(pids[i], types[i], &pdesc[0], pdesc.size());
+ }
+ pmt->Finalize();
+
+
+From ccbded85c9bff63275013e230a5f19fa4a955640 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pb.mythtv(a)gmail.com>
+Date: Wed, 16 May 2018 14:20:37 -0400
+Subject: [PATCH 20/52] Setup: Support typed input into any spinbox
+
+Allows for typed entry into any spinbox in the system, including those
+on the front end. Pressing enter or a number button or key into a
+spinbox opens a dialog where you can type any number that is valid
+for the spinbox. If a number is not valid for the spinbox
+the "OK" button is disabled.
+
+Fixes #13204
+
+(cherry picked from commit 862e510f589e3fedb8f37b61ac7967a219b7d8f4)
+---
+ mythtv/libs/libmythui/mythuispinbox.cpp | 164 +++++++++++++++++++++++-
+ mythtv/libs/libmythui/mythuispinbox.h | 38 ++++++
+ mythtv/themes/default/base.xml | 38 ++++++
+ 3 files changed, 239 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythui/mythuispinbox.cpp
b/mythtv/libs/libmythui/mythuispinbox.cpp
+index 5abd851ba16..1b18d4d9de2 100644
+--- a/mythtv/libs/libmythui/mythuispinbox.cpp
++++ b/mythtv/libs/libmythui/mythuispinbox.cpp
+@@ -1,5 +1,10 @@
+
++#include "mythmainwindow.h"
+ #include "mythuispinbox.h"
++#include "mythlogging.h"
++#include "mythuibutton.h"
++#include "mythuitextedit.h"
++#include "mythuitext.h"
+
+ // QT headers
+ #include <QDomDocument>
+@@ -7,7 +12,7 @@
+
+ MythUISpinBox::MythUISpinBox(MythUIType *parent, const QString &name)
+ : MythUIButtonList(parent, name), m_hasTemplate(false),
+- m_moveAmount(0)
++ m_moveAmount(0), m_low(0), m_high(0), m_step(0)
+ {
+ }
+
+@@ -31,6 +36,10 @@ void MythUISpinBox::SetRange(int low, int high, int step, uint
pageMultiple)
+ if ((high == low) || step == 0)
+ return;
+
++ m_low = low;
++ m_high = high;
++ m_step = step;
++
+ m_moveAmount = pageMultiple;
+
+ bool reverse = false;
+@@ -206,3 +215,156 @@ void MythUISpinBox::CopyFrom(MythUIType *base)
+
+ MythUIButtonList::CopyFrom(base);
+ }
++
++// Open the entry dialog on certain key presses. A select or search action will
++// open the dialog. A number or minus sign will open the entry dialog with the
++// given key as the first digit.
++// If the spinbox uses a template, the entries are not just numbers
++// but can be sentences. The whole sentence is put in the entry field,
++// allowing the user to change the number part of it.
++
++bool MythUISpinBox::keyPressEvent(QKeyEvent *event)
++{
++ QStringList actions;
++ bool handled = false;
++ handled = GetMythMainWindow()->TranslateKeyPress("Global", event,
actions);
++ if (handled)
++ return true;
++
++ QString initialEntry = GetItemCurrent()->GetText();
++ bool doEntry = false;
++ for (int i = 0; i < actions.size(); ++i)
++ {
++ if (actions[i] >= ACTION_0 && actions[i] <= ACTION_9)
++ {
++ if (!m_hasTemplate)
++ initialEntry = actions[i];
++ doEntry=true;
++ break;
++ }
++ if (actions[i] == ACTION_SELECT || actions[i] == "SEARCH")
++ {
++ doEntry=true;
++ break;
++ }
++ }
++ if (actions.size() == 0 && event->text() == "-")
++ {
++ if (!m_hasTemplate)
++ initialEntry = "-";
++ doEntry=true;
++ }
++
++ if (doEntry)
++ {
++ ShowEntryDialog(initialEntry);
++ handled = true;
++ }
++
++ if (handled)
++ return true;
++ else
++ return MythUIButtonList::keyPressEvent(event);
++}
++
++void MythUISpinBox::ShowEntryDialog(QString initialEntry)
++{
++ MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup
stack");
++
++ SpinBoxEntryDialog *dlg = new SpinBoxEntryDialog(popupStack,
"SpinBoxEntryDialog",
++ this, initialEntry, m_low, m_high, m_step);
++
++ if (dlg->Create())
++ popupStack->AddScreen(dlg);
++ else
++ delete dlg;
++}
++
++// Convenience Dialog to allow entry of a Spinbox value
++
++SpinBoxEntryDialog::SpinBoxEntryDialog(MythScreenStack *parent, const char *name,
++ MythUIButtonList *parentList, QString searchText,
++ int low, int high, int step)
++ : MythScreenType(parent, name, false),
++ m_parentList(parentList),
++ m_searchText(searchText),
++ m_entryEdit(NULL),
++ m_cancelButton(NULL),
++ m_okButton(NULL),
++ m_rulesText(NULL),
++ m_okClicked(false),
++ m_low(low),
++ m_high(high),
++ m_step(step)
++
++{
++ m_selection = parentList->GetCurrentPos();
++}
++
++
++SpinBoxEntryDialog::~SpinBoxEntryDialog()
++{
++}
++
++bool SpinBoxEntryDialog::Create(void)
++{
++ if (!CopyWindowFromBase("SpinBoxEntryDialog", this))
++ return false;
++
++ bool err = false;
++ UIUtilE::Assign(this, m_entryEdit, "entry", &err);
++ UIUtilW::Assign(this, m_cancelButton,"cancel", &err);
++ UIUtilW::Assign(this, m_rulesText,"rules", &err);
++ UIUtilE::Assign(this, m_okButton, "ok", &err);
++
++ if (err)
++ {
++ LOG(VB_GENERAL, LOG_ERR, "Cannot load screen
'SpinBoxEntryDialog'");
++ return false;
++ }
++
++ m_entryEdit->SetText(m_searchText);
++ entryChanged();
++ if (m_rulesText)
++ {
++ InfoMap infoMap;
++ infoMap["low"] = QString::number(m_low);
++ infoMap["high"] = QString::number(m_high);
++ infoMap["step"] = QString::number(m_step);
++ m_rulesText->SetTextFromMap(infoMap);
++ }
++
++ connect(m_entryEdit, SIGNAL(valueChanged()), SLOT(entryChanged()));
++ if (m_cancelButton)
++ connect(m_cancelButton, SIGNAL(Clicked()), SLOT(Close()));
++ connect(m_okButton, SIGNAL(Clicked()), SLOT(okClicked()));
++
++ BuildFocusList();
++
++ return true;
++}
++
++void SpinBoxEntryDialog::entryChanged(void)
++{
++ int currPos = 0;
++ int count = m_parentList->GetCount();
++ QString searchText = m_entryEdit->GetText();
++ bool found = false;
++ for (currPos = 0; currPos < count; currPos++)
++ {
++ if (searchText.compare(m_parentList->GetItemAt(currPos)->GetText(),
++ Qt::CaseInsensitive) == 0)
++ {
++ found = true;
++ m_selection = currPos;
++ break;
++ }
++ }
++ m_okButton->SetEnabled(found);
++}
++
++void SpinBoxEntryDialog::okClicked(void)
++{
++ m_parentList->SetItemCurrent(m_selection);
++ Close();
++}
+diff --git a/mythtv/libs/libmythui/mythuispinbox.h
b/mythtv/libs/libmythui/mythuispinbox.h
+index 27a72c100cf..aa239d52764 100644
+--- a/mythtv/libs/libmythui/mythuispinbox.h
++++ b/mythtv/libs/libmythui/mythuispinbox.h
+@@ -27,6 +27,7 @@ class MUI_PUBLIC MythUISpinBox : public MythUIButtonList
+ void AddSelection (int value, const QString &label = "");
+ QString GetValue(void) const { return GetDataValue().toString(); }
+ int GetIntValue(void) const { return GetDataValue().toInt(); }
++ bool keyPressEvent(QKeyEvent *event);
+
+ protected:
+ virtual bool ParseElement(
+@@ -36,6 +37,7 @@ class MUI_PUBLIC MythUISpinBox : public MythUIButtonList
+
+ virtual bool MoveDown(MovementUnit unit = MoveItem, uint amount = 0);
+ virtual bool MoveUp(MovementUnit unit = MoveItem, uint amount = 0);
++ void ShowEntryDialog(QString initialEntry);
+
+ bool m_hasTemplate;
+ QString m_negativeTemplate;
+@@ -43,6 +45,42 @@ class MUI_PUBLIC MythUISpinBox : public MythUIButtonList
+ QString m_positiveTemplate;
+
+ uint m_moveAmount;
++ int m_low;
++ int m_high;
++ int m_step;
++};
++
++// Convenience Dialog to allow entry of a Spinbox value
++
++class MUI_PUBLIC SpinBoxEntryDialog : public MythScreenType
++{
++ Q_OBJECT
++ public:
++ SpinBoxEntryDialog(MythScreenStack *parent, const char *name,
++ MythUIButtonList *parentList, QString searchText,
++ int low, int high, int step);
++ ~SpinBoxEntryDialog(void);
++
++ bool Create(void);
++
++ protected slots:
++ void entryChanged(void);
++ void okClicked(void);
++
++ protected:
++ MythUIButtonList *m_parentList;
++ QString m_searchText;
++ MythUITextEdit *m_entryEdit;
++ MythUIButton *m_cancelButton;
++ MythUIButton *m_okButton;
++ MythUIText *m_rulesText;
++ int m_selection;
++ bool m_okClicked;
++ int m_low;
++ int m_high;
++ int m_step;
++
++
+ };
+
+ #endif
+diff --git a/mythtv/themes/default/base.xml b/mythtv/themes/default/base.xml
+index 0723b25eff7..7e28b56d1de 100644
+--- a/mythtv/themes/default/base.xml
++++ b/mythtv/themes/default/base.xml
+@@ -1243,6 +1243,44 @@
+ </statetype>
+ </window>
+
++ <!-- Popup dialog to enter into a spin box -->
++ <window name="SpinBoxEntryDialog">
++ <area>-1,-1,620,165,800x600</area>
++ <imagetype name="backimg">
++ <area>0,0,620,165,800x600</area>
++ <filename>mythprogressdialog-background.png</filename>
++ </imagetype>
++
++ <textarea name="title" from="basetextarea">
++ <area>5,10,600,30,800x600</area>
++ <multiline>yes</multiline>
++ <align>allcenter</align>
++ <value>You may enter a value here or cancel to continue using the
Spinbox.</value>
++ </textarea>
++
++ <textedit name="entry" from="basetextedit">
++ <area>120,40,375,50,800x600</area>
++ </textedit>
++
++ <button name="cancel" from="basebutton">
++ <area>20,105,150,40,800x600</area>
++ <value>Cancel</value>
++ </button>
++
++ <button name="ok" from="basebutton">
++ <area>445,105,150,40,800x600</area>
++ <value>OK</value>
++ </button>
++
++ <textarea name="rules" from="basetextarea">
++ <area>210,95,200,60,800x600</area>
++ <multiline>yes</multiline>
++ <align>allcenter</align>
++ <template>Values from %LOW% to %HIGH% in increments of
%STEP%</template>
++ </textarea>
++
++ </window>
++
+ <window name="MythFileBrowser">
+ <area>-1,-1,730,510</area>
+ <imagetype name="backimg">
+
+From 1777cc442597e5c4d64124ba3de554514961df6b Mon Sep 17 00:00:00 2001
+From: Gary Buhrmaster <gary.buhrmaster(a)gmail.com>
+Date: Fri, 25 May 2018 15:43:51 -0400
+Subject: [PATCH 21/52] Fix crash during recording with newer compilers
+
+Invalid attempt to access subscript 0 of a vector of size 0 causes
+an abort with newer compilers. Fixed in three places.
+
+Fixes #13263
+
+Signed-off-by: Peter Bennett <pbennett(a)mythtv.org>
+(cherry picked from commit eba1a192dbc5d81c29f0e08300a760038d20e1cd)
+---
+ mythtv/libs/libmythbase/mythsocket.cpp | 2 +-
+ mythtv/libs/libmythtv/mpeg/mpegtables.cpp | 4 ++--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythbase/mythsocket.cpp
b/mythtv/libs/libmythbase/mythsocket.cpp
+index bed0172cad9..66a2d3f6756 100644
+--- a/mythtv/libs/libmythbase/mythsocket.cpp
++++ b/mythtv/libs/libmythbase/mythsocket.cpp
+@@ -1006,7 +1006,7 @@ void MythSocket::ResetReal(void)
+ if (avail)
+ {
+ trash.resize(max((uint)trash.size(),avail));
+- m_tcpSocket->read(&trash[0], avail);
++ m_tcpSocket->read(trash.data(), avail);
+ }
+
+ LOG(VB_NETWORK, LOG_INFO, LOC + "Reset() " +
+diff --git a/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
b/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
+index 867581a0e8b..1b054275599 100644
+--- a/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
++++ b/mythtv/libs/libmythtv/mpeg/mpegtables.cpp
+@@ -445,7 +445,7 @@ ProgramMapTable* ProgramMapTable::Create(
+ uint len = global_desc[i][1] + 2;
+ gdesc.insert(gdesc.end(), global_desc[i], global_desc[i] + len);
+ }
+- pmt->SetProgramInfo(&gdesc[0], gdesc.size());
++ pmt->SetProgramInfo(gdesc.data(), gdesc.size());
+
+ for (uint i = 0; i < count; i++)
+ {
+@@ -457,7 +457,7 @@ ProgramMapTable* ProgramMapTable::Create(
+ prog_desc[i][j], prog_desc[i][j] + len);
+ }
+
+- pmt->AppendStream(pids[i], types[i], &pdesc[0], pdesc.size());
++ pmt->AppendStream(pids[i], types[i], pdesc.data(), pdesc.size());
+ }
+ pmt->Finalize();
+
+
+From 675676bb38ea8142931bd63861eef07e0164f2e6 Mon Sep 17 00:00:00 2001
+From: Jonatan Lindblad <jlindblad(a)mythtv.org>
+Date: Sat, 9 Jun 2018 21:08:30 +0200
+Subject: [PATCH 22/52] GuideGrid::fillChannelInfos: Use the ChannelOrdering
+ setting
+
+Fixes #13154
+
+(cherry picked from commit 529d3c6c281352a441f1a58dadf86890f422ac0b)
+---
+ mythtv/programs/mythfrontend/guidegrid.cpp | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/programs/mythfrontend/guidegrid.cpp
b/mythtv/programs/mythfrontend/guidegrid.cpp
+index f9444469ce3..103ac3cd3c1 100644
+--- a/mythtv/programs/mythfrontend/guidegrid.cpp
++++ b/mythtv/programs/mythfrontend/guidegrid.cpp
+@@ -1386,8 +1386,10 @@ void GuideGrid::fillChannelInfos(bool gotostartchannel)
+ m_currentStartChannel = 0;
+
+ uint avail = 0;
++ const ChannelUtil::OrderBy ordering = m_channelOrdering == "channum" ?
++ ChannelUtil::kChanOrderByChanNum : ChannelUtil::kChanOrderByName;
+ ChannelInfoList channels = ChannelUtil::LoadChannels(0, 0, avail, true,
+- ChannelUtil::kChanOrderByChanNum,
++ ordering,
+ ChannelUtil::kChanGroupByChanid,
+ 0,
+ (m_changrpid < 0) ? 0 : m_changrpid);
+
+From adc810a8bec396247349bde04eff78e399c5550e Mon Sep 17 00:00:00 2001
+From: Bill Meek <billmeek(a)mythtv.org>
+Date: Thu, 21 Jun 2018 19:53:11 -0500
+Subject: [PATCH 23/52] User Jobs/System Events: Add %RECORDEDID%
+
+(cherry picked from commit b3d46206b87ca2e078a980df14a6128ba68239fa)
+---
+ mythtv/libs/libmyth/programinfo.cpp | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp
+index 93c4a825704..d5b485d7745 100644
+--- a/mythtv/libs/libmyth/programinfo.cpp
++++ b/mythtv/libs/libmyth/programinfo.cpp
+@@ -5323,6 +5323,7 @@ void ProgramInfo::SubstituteMatches(QString &str)
+ str.replace(QString("%%1ISOUTC%").arg(time_str[i]),
+ time_dtr[i]->toString(Qt::ISODate));
+ }
++ str.replace(QString("%RECORDEDID%"), QString::number(recordedid));
+ }
+
+ QMap<QString,uint32_t> ProgramInfo::QueryInUseMap(void)
+
+From 8bbe329e0cbda69f919276c26078017304d3bd7d Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Sat, 23 Jun 2018 11:38:38 -0500
+Subject: [PATCH 24/52] Only consider visible channels when scanning EIT.
+
+Refs #13267
+
+(cherry picked from commit a1466c3cc5f7ffa7c9aea255a6a4fc5cbb35bf98)
+---
+ mythtv/libs/libmythtv/eitscanner.cpp | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/mythtv/libs/libmythtv/eitscanner.cpp b/mythtv/libs/libmythtv/eitscanner.cpp
+index 677feddab88..cb7deddf437 100644
+--- a/mythtv/libs/libmythtv/eitscanner.cpp
++++ b/mythtv/libs/libmythtv/eitscanner.cpp
+@@ -238,6 +238,7 @@ void EITScanner::StartActiveScan(TVRec *_rec, uint
max_seconds_per_source)
+ "WHERE capturecard.sourceid = channel.sourceid AND "
+ " videosource.sourceid = channel.sourceid AND "
+ " channel.mplexid IS NOT NULL AND "
++ " visible = 1 AND "
+ " useonairguide = 1 AND "
+ " useeit = 1 AND "
+ " channum != '' AND "
+
+From 57757c1d9988939c246a54a4fa140c01e31c3eaf Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Sun, 24 Jun 2018 14:42:11 -0500
+Subject: [PATCH 25/52] Fix a couple more EIT issues.
+
+Reset the next scan time after a tuning failure. This causes scanning
+to resume after a reasonable time instead of 1 year as previously
+done.
+
+Start on a random channel the first time scanning is done. After
+that, resume on the next channel. This ensures that channels
+immediately after a channel the fails to tune get scanned in a timely
+manner.
+
+Refs #12106
+Refs #13269
+
+(cherry picked from commit 36a1ff5a6fdefe4da0e4c39c6605ddb87611baea)
+---
+ mythtv/libs/libmythtv/eitscanner.cpp | 21 ++++++++++++++++++---
+ mythtv/libs/libmythtv/eitscanner.h | 1 +
+ mythtv/libs/libmythtv/tv_rec.cpp | 8 ++++++++
+ 3 files changed, 27 insertions(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/eitscanner.cpp b/mythtv/libs/libmythtv/eitscanner.cpp
+index cb7deddf437..eb25920f0b6 100644
+--- a/mythtv/libs/libmythtv/eitscanner.cpp
++++ b/mythtv/libs/libmythtv/eitscanner.cpp
+@@ -35,6 +35,7 @@ EITScanner::EITScanner(uint _cardnum)
+ exitThread(false),
+ rec(NULL), activeScan(false),
+ activeScanStopped(true), activeScanTrigTime(0),
++ activeScanNextChanIndex(random()),
+ cardnum(_cardnum)
+ {
+ QStringList langPref = iso639_get_language_list();
+@@ -128,7 +129,10 @@ void EITScanner::run(void)
+ }
+
+ if (activeScanNextChan == activeScanChannels.end())
++ {
+ activeScanNextChan = activeScanChannels.begin();
++ activeScanNextChanIndex = 0;
++ }
+
+ if (!(*activeScanNextChan).isEmpty())
+ {
+@@ -146,7 +150,12 @@ void EITScanner::run(void)
+
+ activeScanNextTrig = MythDate::current()
+ .addSecs(activeScanTrigTime);
+- ++activeScanNextChan;
++ if (activeScanChannels.size())
++ {
++ ++activeScanNextChan;
++ activeScanNextChanIndex =
++ (activeScanNextChanIndex+1) % activeScanChannels.size();
++ }
+
+ // 24 hours ago
+ eitHelper->PruneEITCache(activeScanNextTrig.toTime_t() - 86400);
+@@ -269,8 +278,14 @@ void EITScanner::StartActiveScan(TVRec *_rec, uint
max_seconds_per_source)
+ // order when the backend is first started up.
+ if (activeScanChannels.size())
+ {
+- uint randomStart = random() % activeScanChannels.size();
+- activeScanNextChan = activeScanChannels.begin()+randomStart;
++ // The start channel is random. From now on, start on the
++ // next channel. This makes sure the immediately following
++ // channels get scanned in a timely manner if we keep erroring
++ // out on the previous channel.
++ activeScanNextChanIndex =
++ (activeScanNextChanIndex+1) % activeScanChannels.size();
++ activeScanNextChan =
++ activeScanChannels.begin() + activeScanNextChanIndex;
+
+ activeScanNextTrig = MythDate::current();
+ activeScanTrigTime = max_seconds_per_source;
+diff --git a/mythtv/libs/libmythtv/eitscanner.h b/mythtv/libs/libmythtv/eitscanner.h
+index 9d604021aa4..e8853f7b756 100644
+--- a/mythtv/libs/libmythtv/eitscanner.h
++++ b/mythtv/libs/libmythtv/eitscanner.h
+@@ -65,6 +65,7 @@ class EITScanner : public QRunnable
+ uint activeScanTrigTime;
+ QStringList activeScanChannels;
+ QStringList::iterator activeScanNextChan;
++ uint activeScanNextChanIndex;
+
+ uint cardnum;
+
+diff --git a/mythtv/libs/libmythtv/tv_rec.cpp b/mythtv/libs/libmythtv/tv_rec.cpp
+index 40d55aa7b0d..b41f55ae803 100644
+--- a/mythtv/libs/libmythtv/tv_rec.cpp
++++ b/mythtv/libs/libmythtv/tv_rec.cpp
+@@ -1051,6 +1051,8 @@ void TVRec::HandleStateChange(void)
+ {
+ scanner->StopActiveScan();
+ ClearFlags(kFlagEITScannerRunning, __FILE__, __LINE__);
++ eitScanStartTime = MythDate::current().addSecs(
++ eitCrawlIdleStart + eit_start_rand(eitTransportTimeout));
+ }
+
+ // Handle different state transitions
+@@ -1098,7 +1100,9 @@ void TVRec::HandleStateChange(void)
+ eitCrawlIdleStart + eit_start_rand(inputid, eitTransportTimeout));
+ }
+ else
++ {
+ eitScanStartTime = eitScanStartTime.addYears(1);
++ }
+ }
+ #undef TRANSITION
+ #undef SET_NEXT
+@@ -1304,7 +1308,9 @@ void TVRec::run(void)
+ eitCrawlIdleStart + eit_start_rand(inputid, eitTransportTimeout));
+ }
+ else
++ {
+ eitScanStartTime = eitScanStartTime.addYears(1);
++ }
+
+ while (HasFlags(kFlagRunMainLoop))
+ {
+@@ -3575,6 +3581,8 @@ void TVRec::TuningShutdowns(const TuningRequest &request)
+ {
+ scanner->StopActiveScan();
+ ClearFlags(kFlagEITScannerRunning, __FILE__, __LINE__);
++ eitScanStartTime = MythDate::current().addSecs(
++ eitCrawlIdleStart + eit_start_rand(eitTransportTimeout));
+ }
+
+ if (scanner && !request.IsOnSameMultiplex())
+
+From 03d4baabf36088da8cc30840b4bdb4a3ef0a8a22 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Sun, 24 Jun 2018 16:34:14 -0500
+Subject: [PATCH 26/52] Remove unused inputid variable from eit_start_rand.
+
+Backport of commit 01687187 from master to fix compilation error
+caused by cherry picking commit 36a1ff5a.
+---
+ mythtv/libs/libmythtv/tv_rec.cpp | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/tv_rec.cpp b/mythtv/libs/libmythtv/tv_rec.cpp
+index b41f55ae803..e3782cdb18c 100644
+--- a/mythtv/libs/libmythtv/tv_rec.cpp
++++ b/mythtv/libs/libmythtv/tv_rec.cpp
+@@ -55,7 +55,7 @@ static bool is_dishnet_eit(uint inputid);
+ static int init_jobs(const RecordingInfo *rec, RecordingProfile &profile,
+ bool on_host, bool transcode_bfr_comm, bool on_line_comm);
+ static void apply_broken_dvb_driver_crc_hack(ChannelBase*, MPEGStreamData*);
+-static int eit_start_rand(uint inputid, int eitTransportTimeout);
++static int eit_start_rand(int eitTransportTimeout);
+
+ /** \class TVRec
+ * \brief This is the coordinating class of the \ref recorder_subsystem.
+@@ -1097,7 +1097,7 @@ void TVRec::HandleStateChange(void)
+ if (scanner && (internalState == kState_None))
+ {
+ eitScanStartTime = eitScanStartTime.addSecs(
+- eitCrawlIdleStart + eit_start_rand(inputid, eitTransportTimeout));
++ eitCrawlIdleStart + eit_start_rand(eitTransportTimeout));
+ }
+ else
+ {
+@@ -1278,7 +1278,7 @@ static int num_inputs(void)
+ return -1;
+ }
+
+-static int eit_start_rand(uint inputid, int eitTransportTimeout)
++static int eit_start_rand(int eitTransportTimeout)
+ {
+ // randomize start time a bit
+ int timeout = random() % (eitTransportTimeout / 3);
+@@ -1305,7 +1305,7 @@ void TVRec::run(void)
+ {
+ scanner = new EITScanner(inputid);
+ eitScanStartTime = eitScanStartTime.addSecs(
+- eitCrawlIdleStart + eit_start_rand(inputid, eitTransportTimeout));
++ eitCrawlIdleStart + eit_start_rand(eitTransportTimeout));
+ }
+ else
+ {
+
+From 3237c4236599a57afb231931d6c496d2277b193f Mon Sep 17 00:00:00 2001
+From: Stuart Morgan <smorgan(a)mythtv.org>
+Date: Tue, 26 Jun 2018 10:41:26 +0100
+Subject: [PATCH 27/52] Fix build of mythfrontend on fixes/29.
+
+This is an odd one, it shouldn't have been building on a lot more
+systems and I cannot find where these ifdefs were removed, they exist
+in master and the corresponding ones in the cpp are present and correct.
+---
+ mythtv/programs/mythfrontend/globalsettings.h | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/mythtv/programs/mythfrontend/globalsettings.h
b/mythtv/programs/mythfrontend/globalsettings.h
+index e069ec1cfbb..404f2c42b2e 100644
+--- a/mythtv/programs/mythfrontend/globalsettings.h
++++ b/mythtv/programs/mythfrontend/globalsettings.h
+@@ -52,7 +52,9 @@ class VideoModeSettings : public HostCheckBoxSetting
+
+ public:
+ VideoModeSettings(const char *c);
++#if defined(USING_XRANDR) || CONFIG_DARWIN
+ virtual void updateButton(MythUIButtonListItem *item);
++#endif
+ };
+
+ class LcdSettings
+@@ -150,7 +152,9 @@ class HostRefreshRateComboBoxSetting : public HostComboBoxSetting
+ virtual ~HostRefreshRateComboBoxSetting() { }
+
+ public slots:
++#if defined(USING_XRANDR) || CONFIG_DARWIN
+ virtual void ChangeResolution(StandardSetting *);
++#endif
+
+ private:
+ static const vector<double> GetRefreshRates(const QString &resolution);
+
+From 4f633441cf362c3772e9260e21da0426af5158dd Mon Sep 17 00:00:00 2001
+From: Paul Harrison <pharrison(a)mythtv.org>
+Date: Wed, 20 Jun 2018 11:37:53 +0100
+Subject: [PATCH 28/52] mythuiguidegrid: bump the number of time slots to 48
+
+This fixes a problem were themes could set a maximum of 4 hours in the
+guide grid but the code only allowed for 3 hours.
+
+Thanks to Roger Siddons for finding the problem.
+
+(cherry picked from commit d6bb161e8aa5f87ec23de361e7212de00f14ab9f)
+---
+ mythtv/libs/libmythui/mythuiguidegrid.cpp | 2 +-
+ mythtv/libs/libmythui/mythuiguidegrid.h | 8 +++++++-
+ mythtv/programs/mythfrontend/guidegrid.h | 2 --
+ 3 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/libs/libmythui/mythuiguidegrid.cpp
b/mythtv/libs/libmythui/mythuiguidegrid.cpp
+index 593b5e50182..c014fe45a12 100644
+--- a/mythtv/libs/libmythui/mythuiguidegrid.cpp
++++ b/mythtv/libs/libmythui/mythuiguidegrid.cpp
+@@ -113,7 +113,7 @@ bool MythUIGuideGrid::ParseElement(
+ {
+ m_timeCount = getFirstText(element).toInt();
+ m_timeCount = max(m_timeCount, 1);
+- m_timeCount = min(m_timeCount, 8);
++ m_timeCount = min(m_timeCount, MAX_DISPLAY_TIMES / 6);
+ }
+ else if (element.tagName() == "solidcolor")
+ {
+diff --git a/mythtv/libs/libmythui/mythuiguidegrid.h
b/mythtv/libs/libmythui/mythuiguidegrid.h
+index 8b508770b76..ec3e4000459 100644
+--- a/mythtv/libs/libmythui/mythuiguidegrid.h
++++ b/mythtv/libs/libmythui/mythuiguidegrid.h
+@@ -14,7 +14,13 @@
+
+ #define ARROWIMAGESIZE 4
+ #define RECSTATUSSIZE 8
+-#define MAX_DISPLAY_CHANS 40
++
++
++// max number of channels to display in the guide grid
++#define MAX_DISPLAY_CHANS 4
++
++// max number of 5 minute time slots to show in guide grid (48 = 4hrs)
++#define MAX_DISPLAY_TIMES 48
+
+ class MythFontProperties;
+
+diff --git a/mythtv/programs/mythfrontend/guidegrid.h
b/mythtv/programs/mythfrontend/guidegrid.h
+index f32391b6e3e..51060e0e167 100644
+--- a/mythtv/programs/mythfrontend/guidegrid.h
++++ b/mythtv/programs/mythfrontend/guidegrid.h
+@@ -31,8 +31,6 @@ class QTimer;
+ class MythUIButtonList;
+ class MythUIGuideGrid;
+
+-#define MAX_DISPLAY_TIMES 36
+-
+ typedef vector<ChannelInfo> db_chan_list_t;
+ typedef vector<db_chan_list_t> db_chan_list_list_t;
+ typedef ProgramInfo *ProgInfoGuideArray[MAX_DISPLAY_CHANS][MAX_DISPLAY_TIMES];
+
+From 2a0dadb37cf93216627fc07805b7bd50162687ca Mon Sep 17 00:00:00 2001
+From: Paul Harrison <pharrison(a)mythtv.org>
+Date: Mon, 9 Jul 2018 16:53:22 +0100
+Subject: [PATCH 29/52] mythuiguidegrid.h: fix copy/paste error when cherry
+ picking 4f633441cf
+
+---
+ mythtv/libs/libmythui/mythuiguidegrid.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythui/mythuiguidegrid.h
b/mythtv/libs/libmythui/mythuiguidegrid.h
+index ec3e4000459..2dfc18ff37f 100644
+--- a/mythtv/libs/libmythui/mythuiguidegrid.h
++++ b/mythtv/libs/libmythui/mythuiguidegrid.h
+@@ -17,7 +17,7 @@
+
+
+ // max number of channels to display in the guide grid
+-#define MAX_DISPLAY_CHANS 4
++#define MAX_DISPLAY_CHANS 40
+
+ // max number of 5 minute time slots to show in guide grid (48 = 4hrs)
+ #define MAX_DISPLAY_TIMES 48
+
+From 563a4b829cbcda5cd366a53e517f17510364a884 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Thu, 26 Jul 2018 14:33:09 -0400
+Subject: [PATCH 30/52] Frontend Setup: Fix shift key handling in Edit Keys
+
+Detection of key codes in Edit Keys was inconsistent with the way they are checked
+in the main window. Shift is ignored for cetain key ranges in order to allow
+alphabetic commands to be case insensitive. This check was not done in
+Edit Keys. The result was there were certain keys it was not possible
+to use once assigned, for example "@" or Ctrl-Shift-B.
+
+Fixes #13279
+
+(cherry picked from commit f20f976d3572dadf46b305eb1d8c760acd44cf58)
+---
+ mythtv/programs/mythfrontend/keygrabber.cpp | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/programs/mythfrontend/keygrabber.cpp
b/mythtv/programs/mythfrontend/keygrabber.cpp
+index 8345eb43d0c..2cfb963cefb 100644
+--- a/mythtv/programs/mythfrontend/keygrabber.cpp
++++ b/mythtv/programs/mythfrontend/keygrabber.cpp
+@@ -96,7 +96,9 @@ bool KeyGrabPopupBox::keyPressEvent(QKeyEvent *event)
+ QString modifiers;
+
+ /* key modifier strings as defined by the QT docs */
+- if (event->modifiers() & Qt::ShiftModifier)
++ if (event->modifiers() & Qt::ShiftModifier
++ && keycode > 0x7f
++ && keycode != Qt::Key_Backtab)
+ modifiers += "Shift+";
+ if (event->modifiers() & Qt::ControlModifier)
+ modifiers += "Ctrl+";
+
+From 4065df3c9aa42eacb1b7d0382e33e904204a9757 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Mon, 13 Aug 2018 11:15:08 -0400
+Subject: [PATCH 31/52] Frontend Settings: Fix Channel Group Save
+
+Change so that saving is done in the Save method.
+
+Fixes #13205
+
+(cherry picked from commit c2621a984b7c1f6d9945ff52009797cc6c24a292)
+---
+ mythtv/programs/mythfrontend/globalsettings.cpp | 2 +-
+ mythtv/programs/mythfrontend/globalsettings.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp
b/mythtv/programs/mythfrontend/globalsettings.cpp
+index 45076191704..e4d4097f9d9 100644
+--- a/mythtv/programs/mythfrontend/globalsettings.cpp
++++ b/mythtv/programs/mythfrontend/globalsettings.cpp
+@@ -4380,7 +4380,7 @@ ChannelGroupSetting::ChannelGroupSetting(const QString
&groupName,
+ m_groupName->setLabel(groupName);
+ }
+
+-void ChannelGroupSetting::Close()
++void ChannelGroupSetting::Save()
+ {
+ //Change the name
+ if ((m_groupName && m_groupName->haveChanged())
+diff --git a/mythtv/programs/mythfrontend/globalsettings.h
b/mythtv/programs/mythfrontend/globalsettings.h
+index 404f2c42b2e..0d244128a90 100644
+--- a/mythtv/programs/mythfrontend/globalsettings.h
++++ b/mythtv/programs/mythfrontend/globalsettings.h
+@@ -266,7 +266,7 @@ class ChannelGroupSetting : public GroupSetting
+ public:
+ ChannelGroupSetting(const QString &groupName, int groupId);
+ virtual void Load();
+- virtual void Close();
++ virtual void Save();
+ virtual bool canDelete(void);
+ virtual void deleteEntry(void);
+
+
+From b46a64f0859113cea7654c25944564a587a579d1 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Tue, 14 Aug 2018 16:46:47 -0400
+Subject: [PATCH 32/52] Fix error in theme used by spinbox enhancement
+
+commit ccbded85c9bff6 added the ability to type spinbox input.
+The theme xml for that used a feature that is only available in master
+so it did not work in v29. This downgrades the default theme file
+to be v29 compatible.
+
+Refs #13204
+---
+ mythtv/themes/default/base.xml | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/mythtv/themes/default/base.xml b/mythtv/themes/default/base.xml
+index 7e28b56d1de..90de3a88d6f 100644
+--- a/mythtv/themes/default/base.xml
++++ b/mythtv/themes/default/base.xml
+@@ -1245,35 +1245,35 @@
+
+ <!-- Popup dialog to enter into a spin box -->
+ <window name="SpinBoxEntryDialog">
+- <area>-1,-1,620,165,800x600</area>
++ <area>-1,-1,780,330</area>
+ <imagetype name="backimg">
+- <area>0,0,620,165,800x600</area>
++ <area>0,0,780,330</area>
+ <filename>mythprogressdialog-background.png</filename>
+ </imagetype>
+
+ <textarea name="title" from="basetextarea">
+- <area>5,10,600,30,800x600</area>
++ <area>6,20,756,60</area>
+ <multiline>yes</multiline>
+ <align>allcenter</align>
+- <value>You may enter a value here or cancel to continue using the
Spinbox.</value>
++ <value>You may enter a value here or cancel to go back.</value>
+ </textarea>
+
+ <textedit name="entry" from="basetextedit">
+- <area>120,40,375,50,800x600</area>
++ <area>150,80,473,75</area>
+ </textedit>
+
+ <button name="cancel" from="basebutton">
+- <area>20,105,150,40,800x600</area>
++ <area>25,210,189,80</area>
+ <value>Cancel</value>
+ </button>
+
+ <button name="ok" from="basebutton">
+- <area>445,105,150,40,800x600</area>
++ <area>560,210,189,80</area>
+ <value>OK</value>
+ </button>
+
+ <textarea name="rules" from="basetextarea">
+- <area>210,95,200,60,800x600</area>
++ <area>264,190,252,120</area>
+ <multiline>yes</multiline>
+ <align>allcenter</align>
+ <template>Values from %LOW% to %HIGH% in increments of
%STEP%</template>
+
+From e5fc66e822072037aeaf8c54c4292f17f65cef56 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Mon, 20 Aug 2018 14:23:14 -0500
+Subject: [PATCH 33/52] Correctly clear the settings cache when saving host
+ settings.
+
+Previously, the wrong cache entries were cleared. This caused the new
+values to not be used until the cache was reset either by restarting
+the frontend or re-entering the settings tree.
+
+Backported from commit 767b301e4807b9de757ae17420822ff39fd84ada
+---
+ mythtv/libs/libmythbase/mythstorage.cpp | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythbase/mythstorage.cpp
b/mythtv/libs/libmythbase/mythstorage.cpp
+index 1967cf41f52..684195971c6 100644
+--- a/mythtv/libs/libmythbase/mythstorage.cpp
++++ b/mythtv/libs/libmythbase/mythstorage.cpp
+@@ -171,7 +171,8 @@ QString HostDBStorage::GetSetClause(MSqlBindings &bindings)
const
+ void HostDBStorage::Save(void)
+ {
+ SimpleDBStorage::Save();
+- gCoreContext->ClearSettingsCache(settingname);
++ gCoreContext->ClearSettingsCache(
++ MythDB::getMythDB()->GetHostName() + ' ' + settingname);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+From 0b689476c7920e9541e6307a79d793ce41f96f86 Mon Sep 17 00:00:00 2001
+From: David Hampton <mythtv(a)love2code.net>
+Date: Fri, 24 Aug 2018 23:09:27 -0400
+Subject: [PATCH 34/52] Automatically disable vaapi on armv7l architectures.
+
+(cherry picked from commit 296fadf64d70215f6e00f0fcaa0a565ba340c852)
+---
+ mythtv/configure | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/mythtv/configure b/mythtv/configure
+index 14535b0611d..0031d828931 100755
+--- a/mythtv/configure
++++ b/mythtv/configure
+@@ -5742,6 +5742,9 @@ case $target_os in
+ enable libudev
+ enable libuuid
+ enable pic
++ if [ x`uname -m` = x"armv7l" ]; then
++ disable vaapi
++ fi
+ ;;
+ irix*)
+ target_os=irix
+
+From dde16d475a8d76490324fb32ce905a3145d26a50 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Thu, 6 Sep 2018 15:28:21 -0400
+Subject: [PATCH 35/52] MythGUI: Change spinbox entry dialog to only appear for
+ numeric spin boxes
+
+The popup entry dialog is really only intended for entry of numeric values
+when there is a huge range. The popup with alphabetic entry is interfering
+with custom record. It was not useful anyway so will be disabled.
+
+Fixes #13318
+
+(cherry picked from commit dc0755bbd656cedff4a0af21919306ef3019e593)
+---
+ mythtv/libs/libmythui/mythuispinbox.cpp | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/mythtv/libs/libmythui/mythuispinbox.cpp
b/mythtv/libs/libmythui/mythuispinbox.cpp
+index 1b18d4d9de2..55f6041a1ff 100644
+--- a/mythtv/libs/libmythui/mythuispinbox.cpp
++++ b/mythtv/libs/libmythui/mythuispinbox.cpp
+@@ -233,6 +233,13 @@ bool MythUISpinBox::keyPressEvent(QKeyEvent *event)
+
+ QString initialEntry = GetItemCurrent()->GetText();
+ bool doEntry = false;
++
++ // Only invoke the entry dialog if the entry is a number
++ bool isNumber = false;
++ (void)initialEntry.toLongLong(&isNumber,10);
++ if (!isNumber)
++ return MythUIButtonList::keyPressEvent(event);
++
+ for (int i = 0; i < actions.size(); ++i)
+ {
+ if (actions[i] >= ACTION_0 && actions[i] <= ACTION_9)
+
+From 183ac5d5833dc0d822083c3846a2675c6a000a52 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Wed, 12 Sep 2018 20:58:05 -0600
+Subject: [PATCH 36/52] OpenGL2 Painter: Fix drawing shapes at the dimmensions
+ and line widths requested
+
+Don't artificially draw shapes indented by half their line width.
+
+(cherry picked from commit 7a96ba659a1c3f251f6601749cf1d01aa7280e03)
+---
+ mythtv/libs/libmythui/mythrender_opengl2.cpp | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/mythtv/libs/libmythui/mythrender_opengl2.cpp
b/mythtv/libs/libmythui/mythrender_opengl2.cpp
+index dcac95381d7..92db0decab7 100644
+--- a/mythtv/libs/libmythui/mythrender_opengl2.cpp
++++ b/mythtv/libs/libmythui/mythrender_opengl2.cpp
+@@ -562,9 +562,7 @@ void MythRenderOpenGL2::DrawRoundRectPriv(const QRect &area, int
cornerRadius,
+ rad = area.height() / 2;
+ int dia = rad * 2;
+
+-
+- QRect r(area.left() + halfline, area.top() + halfline,
+- area.width() - (halfline * 2), area.height() - (halfline * 2));
++ QRect r(area.left(), area.top(), area.width(), area.height());
+
+ QRect tl(r.left(), r.top(), rad, rad);
+ QRect tr(r.left() + r.width() - rad, r.top(), rad, rad);
+
+From b9c5f8b2ff983343d2545ec87022d18fcf65fe1f Mon Sep 17 00:00:00 2001
+From: Mark Spieth <mspieth(a)digivation.com.au>
+Date: Fri, 30 Mar 2018 15:27:12 +1100
+Subject: [PATCH 37/52] Android mythplugin build updates
+
+refs #13252
+
+Also required for minimyth builds.
+
+Signed-off-by: Stuart Auchterlonie <stuarta(a)mythtv.org>
+(cherry picked from commit 443bbeffabdfd02d334a0df47200332ecf6ee1d1)
+---
+ mythplugins/configure | 24 +++++++++++++++----
+ .../mythgame/mythgame/external/ioapi.c | 2 +-
+ mythplugins/programs-libs.pro | 2 +-
+ mythplugins/targetdep.pro | 2 +-
+ 4 files changed, 23 insertions(+), 7 deletions(-)
+
+diff --git a/mythplugins/configure b/mythplugins/configure
+index 7c07d428042..941d15de8ef 100755
+--- a/mythplugins/configure
++++ b/mythplugins/configure
+@@ -220,7 +220,7 @@ EOF
+ }
+
+ add_ldflags(){
+- append LDFLAGS "$@"
++ append ELDFLAGS "$@"
+ }
+
+ add_cxxflags(){
+@@ -307,6 +307,7 @@ disable $DEPRECATED_PLUGIN_LIST
+ prefix="/usr/local"
+ libdir_name="lib"
+ sysroot=""
++mythroot=""
+ ln_s="ln -sf"
+
+ logfile="config.ep"
+@@ -326,13 +327,16 @@ NB: all --enable-* options can be reversed with --disable-*
+
+ Generic options:
+ --prefix=PREFIX MythTV install location PREFIX [$prefix]
+- --sysroot=SYSROOT MythTV sysroot location SYSROOT [$sysroot]
++ --sysroot=SYSROOT General sysroot location SYSROOT [$sysroot]
++ --mythroot=SYSROOT MythTV specific sysroot location [$mythroot]
+ --qmake=QMAKE use specified qmake [$qmake]
++ --qmakespecs=QMAKE use specified qmakespecs [$qmakespecs]
+ --python=PATH Force a specific python executable to use [$python]
+ --libdir-name=LIBNAME install/look for libs in PREFIX/LIBNAME [$libdir_name]
+ --help print this message
+ --enable-all Enable all options
+ --previous use previous configure parameters if possible
++ --extra-ldflags=ELDFLAGS add ELDFLAGS to LDFLAGS [$LDFLAGS]
+
+ MythArchive related options:
+ --enable-mytharchive build the mytharchive plugin [$archive]
+@@ -397,8 +401,10 @@ fi
+ CMDLINE_SET="
+ logfile
+ qmake
++ qmakespecs
+ python
+ sysroot
++ mythroot
+ "
+
+ CONFIGURATION_OPTS=""
+@@ -452,6 +458,9 @@ for opt do
+ die_unknown $opt
+ fi
+ ;;
++ --extra-ldflags=*)
++ add_ldflags $optval
++ ;;
+ *)
+ optname="${opt%%=*}"
+ optname="${optname#--}"
+@@ -528,7 +537,11 @@ EOF
+ fi
+
+ # bring in mythtv config
+-if [ -e $prefix/include/mythtv/mythconfig.mak ] ; then
++if [ -e $mythroot$prefix/include/mythtv/mythconfig.mak ] ; then
++ rm mythconfig.mak 2> /dev/null
++ cp $mythroot$prefix/include/mythtv/mythconfig.mak mythconfig.mak
++ sed -i -e "s,^SYSROOT\=.*,SYSROOT=$mythroot,g" mythconfig.mak
++elif [ -e $prefix/include/mythtv/mythconfig.mak ] ; then
+ rm mythconfig.mak 2> /dev/null
+ ${ln_s} $prefix/include/mythtv/mythconfig.mak mythconfig.mak
+ else
+@@ -549,7 +562,7 @@ CFLAGS=${CFLAGS#CFLAGS=}
+ CPPFLAGS=$(cat mythconfig.mak | grep -e "^CPPFLAGS=")
+ CPPFLAGS=${CPPFLAGS#CPPFLAGS=}
+ LDFLAGS=$(cat mythconfig.mak | grep -e "^LDFLAGS=")
+-LDFLAGS=${LDFLAGS#LDFLAGS=}
++LDFLAGS="${LDFLAGS#LDFLAGS=} ${ELDFLAGS}"
+ CXX=$(cat mythconfig.mak | grep -e "^QMAKE_CXX=")
+ CXX=${CXX#QMAKE_CXX=}
+ ECXXFLAGS=$(cat mythconfig.mak | grep -e "^ECXXFLAGS=")
+@@ -581,6 +594,9 @@ if [ x"$qmake" = "xqmake" ]; then
+ else
+ CHECK_QMAKE=`which $qmake 2>/dev/null`" "`which qmake-qt5
2>/dev/null`" /usr/lib64/qt5/bin/qmake /usr/lib/x86_64-linux-gnu/qt5/bin/qmake
/usr/lib/i386-linux-gnu/qt5/bin/qmake /usr/lib/arm-linux-gnueabihf/qt5/bin/qmake
/usr/local/lib/qt5/bin/qmake"
+ fi
++if [ -n "qmakespec" ]; then
++ qmake="$qmake -spec $qmakespec"
++fi
+ # try to find a qt5 qmake to use
+ found_qmake=''
+ for i in $CHECK_QMAKE; do
+diff --git a/mythplugins/mythgame/mythgame/external/ioapi.c
b/mythplugins/mythgame/mythgame/external/ioapi.c
+index 7f5c191b2af..8db1d9f9be7 100644
+--- a/mythplugins/mythgame/mythgame/external/ioapi.c
++++ b/mythplugins/mythgame/mythgame/external/ioapi.c
+@@ -14,7 +14,7 @@
+ #define _CRT_SECURE_NO_WARNINGS
+ #endif
+
+-#if defined(__APPLE__) || defined(IOAPI_NO_64)
++#if defined(__APPLE__) || defined(IOAPI_NO_64) || defined(ANDROID)
+ // In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for
specific 64 bit functions
+ #define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+ #define FTELLO_FUNC(stream) ftello(stream)
+diff --git a/mythplugins/programs-libs.pro b/mythplugins/programs-libs.pro
+index 905b5933754..c8dce9abb18 100644
+--- a/mythplugins/programs-libs.pro
++++ b/mythplugins/programs-libs.pro
+@@ -5,7 +5,7 @@ INCLUDEPATH += $${SYSROOT}$${PREFIX}/include/mythtv/libmyth
+ INCLUDEPATH += $${SYSROOT}$${PREFIX}/include/mythtv/libmythservicecontracts
+ DEPENDPATH *= $${INCLUDEPATH}
+
+-LIBS += -L$${LIBDIR} $$EXTRA_LIBS -lmythbase-$$LIBVERSION
++LIBS += -L$${SYSROOT}$${LIBDIR} $$EXTRA_LIBS -lmythbase-$$LIBVERSION
+ LIBS += -lmyth-$$LIBVERSION -lmythui-$$LIBVERSION -lmythupnp-$$LIBVERSION
+ LIBS += -lmythservicecontracts-$$LIBVERSION
+ LIBS += -lmythavcodec
+diff --git a/mythplugins/targetdep.pro b/mythplugins/targetdep.pro
+index 55f714bde9f..1a447f0ef08 100644
+--- a/mythplugins/targetdep.pro
++++ b/mythplugins/targetdep.pro
+@@ -10,7 +10,7 @@ MYTH_SHLIB_EXT=$${LIBVERSION}.$${QMAKE_EXTENSION_SHLIB}
+ MYTH_LIB_EXT =$${LIBVERSION}.$${QMAKE_EXTENSION_LIB}
+
+
+-DEPLIBS = $${LIBDIR}
++DEPLIBS = $${SYSROOT}$${LIBDIR}
+
+ # On Windows, dlls were installed with exes:
+ mingw : DEPLIBS = $${PREFIX}/bin
+
+From 74fff5c2856d592b8b2dfd41ac5cc08f372a8993 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Thu, 4 Oct 2018 11:06:54 -0500
+Subject: [PATCH 38/52] Add support for libx264 version >= 153.
+
+This commit is from the FFmpeg release/3.2 branch. It has the
+following commit information.
+
+commit 6d654eb036ce44856ca14c176baba8fb1410db32
+Author: Luca Barbato <lu_zero(a)gentoo.org>
+Date: Tue Dec 26 12:32:42 2017 +0100
+
+ x264: Support version 153
+
+ It has native simultaneus 8 and 10 bit support.
+
+ (cherry picked from commit c6558e8840fbb2386bf8742e4d68dd6e067d262e)
+ (cherry picked from commit 96e8400553ae47f8f8df5b66cc268297ba38824c)
+ Signed-off-by: Michael Niedermayer <michael(a)niedermayer.cc>
+---
+ mythtv/external/FFmpeg/libavcodec/libx264.c | 29 +++++++++++++++++++++
+ 1 file changed, 29 insertions(+)
+
+diff --git a/mythtv/external/FFmpeg/libavcodec/libx264.c
b/mythtv/external/FFmpeg/libavcodec/libx264.c
+index 9e1246477e6..fd35962cc60 100644
+--- a/mythtv/external/FFmpeg/libavcodec/libx264.c
++++ b/mythtv/external/FFmpeg/libavcodec/libx264.c
+@@ -279,7 +279,11 @@ static int X264_frame(AVCodecContext *ctx, AVPacket *pkt, const
AVFrame *frame,
+
+ x264_picture_init( &x4->pic );
+ x4->pic.img.i_csp = x4->params.i_csp;
++#if X264_BUILD >= 153
++ if (x4->params.i_bitdepth > 8)
++#else
+ if (x264_bit_depth > 8)
++#endif
+ x4->pic.img.i_csp |= X264_CSP_HIGH_DEPTH;
+ x4->pic.img.i_plane = avfmt2_num_planes(ctx->pix_fmt);
+
+@@ -490,6 +494,9 @@ static av_cold int X264_init(AVCodecContext *avctx)
+ x4->params.p_log_private = avctx;
+ x4->params.i_log_level = X264_LOG_DEBUG;
+ x4->params.i_csp = convert_pix_fmt(avctx->pix_fmt);
++#if X264_BUILD >= 153
++ x4->params.i_bitdepth =
av_pix_fmt_desc_get(avctx->pix_fmt)->comp[0].depth;
++#endif
+
+ PARSE_X264_OPT("weightp", wpredp);
+
+@@ -878,6 +885,24 @@ static const enum AVPixelFormat pix_fmts_10bit[] = {
+ AV_PIX_FMT_NV20,
+ AV_PIX_FMT_NONE
+ };
++static const enum AVPixelFormat pix_fmts_all[] = {
++ AV_PIX_FMT_YUV420P,
++ AV_PIX_FMT_YUVJ420P,
++ AV_PIX_FMT_YUV422P,
++ AV_PIX_FMT_YUVJ422P,
++ AV_PIX_FMT_YUV444P,
++ AV_PIX_FMT_YUVJ444P,
++ AV_PIX_FMT_NV12,
++ AV_PIX_FMT_NV16,
++#ifdef X264_CSP_NV21
++ AV_PIX_FMT_NV21,
++#endif
++ AV_PIX_FMT_YUV420P10,
++ AV_PIX_FMT_YUV422P10,
++ AV_PIX_FMT_YUV444P10,
++ AV_PIX_FMT_NV20,
++ AV_PIX_FMT_NONE
++};
+ #if CONFIG_LIBX264RGB_ENCODER
+ static const enum AVPixelFormat pix_fmts_8bit_rgb[] = {
+ AV_PIX_FMT_BGR0,
+@@ -889,12 +914,16 @@ static const enum AVPixelFormat pix_fmts_8bit_rgb[] = {
+
+ static av_cold void X264_init_static(AVCodec *codec)
+ {
++#if X264_BUILD < 153
+ if (x264_bit_depth == 8)
+ codec->pix_fmts = pix_fmts_8bit;
+ else if (x264_bit_depth == 9)
+ codec->pix_fmts = pix_fmts_9bit;
+ else if (x264_bit_depth == 10)
+ codec->pix_fmts = pix_fmts_10bit;
++#else
++ codec->pix_fmts = pix_fmts_all;
++#endif
+ }
+
+ #define OFFSET(x) offsetof(X264Context, x)
+
+From 951f1fe7ea0bd54a8bb5ef08f3b55feee5169340 Mon Sep 17 00:00:00 2001
+From: David Engel <dengel(a)mythtv.org>
+Date: Mon, 8 Oct 2018 17:52:10 -0500
+Subject: [PATCH 39/52] Fix a major scheduling performance regression.
+
+Commit 38d9ba25 incorrectly included a change which bypassed the
+ability to skip scheduling steps that did not change. The result was
+every change caused a much longer, full reschedule. This commit fixes
+that and performs the orginally intended change correctly.
+
+(cherry picked from commit 33e31a01baa84cdcc83e36d5a1c6544a661d2752)
+---
+ mythtv/programs/mythbackend/mainserver.cpp | 37 ++++++++++++++++++--
+ mythtv/programs/mythbackend/services/dvr.cpp | 6 ++--
+ 2 files changed, 36 insertions(+), 7 deletions(-)
+
+diff --git a/mythtv/programs/mythbackend/mainserver.cpp
b/mythtv/programs/mythbackend/mainserver.cpp
+index e70796f80ae..380e9a6dc38 100644
+--- a/mythtv/programs/mythbackend/mainserver.cpp
++++ b/mythtv/programs/mythbackend/mainserver.cpp
+@@ -3140,15 +3140,46 @@ void MainServer::DoHandleUndeleteRecording(
+ SendResponse(pbssock, outputlist);
+ }
+
++/**
++ * \fn MainServer::HandleRescheduleRecordings
++ *
++ * This function processes the received network protocol message to
++ * reschedule recordings. It ignores the parameters supplied by the
++ * caller and always asks the scheduling system to reschedule all
++ * recordings.
++ *
++ * The network message should look like this:
++ *
++ * RESCHEDULE_RECORDINGS[]:[]MATCH 0 0 0 - MythUtilCommand
++ *
++ * The five values after the 'MATCH' keyword control which recordings
++ * should be rescheduled. They are described in the BuildMatchRequest
++ * function.
++ *
++ * \sa ScheduledRecording::BuildMatchRequest
++ *
++ * \param request Ignored. This function doesn't parse any additional
++ * parameters.
++ * \param pbs The socket used to send the response.
++ *
++ * \addtogroup myth_network_protocol
++ * \par RESCHEDULE_RECORDINGS
++ * Requests that all recordings after the current time be rescheduled.
++ */
+ void MainServer::HandleRescheduleRecordings(const QStringList &request,
+ PlaybackSock *pbs)
+ {
+- ScheduledRecording::RescheduleMatch(0, 0, 0, QDateTime(),
+- "HandleRescheduleRecordings");
++ QStringList result;
++ if (m_sched)
++ {
++ m_sched->Reschedule(request);
++ result = QStringList(QString::number(1));
++ }
++ else
++ result = QStringList(QString::number(0));
+
+ if (pbs)
+ {
+- QStringList result = QStringList( QString::number(1) );
+ MythSocket *pbssock = pbs->getSocket();
+ if (pbssock)
+ SendResponse(pbssock, result);
+diff --git a/mythtv/programs/mythbackend/services/dvr.cpp
b/mythtv/programs/mythbackend/services/dvr.cpp
+index fa9f170bae4..c9e44045997 100644
+--- a/mythtv/programs/mythbackend/services/dvr.cpp
++++ b/mythtv/programs/mythbackend/services/dvr.cpp
+@@ -403,10 +403,8 @@ bool Dvr::ReactivateRecording(int RecordedId)
+
+ bool Dvr::RescheduleRecordings(void)
+ {
+- QString cmd = QString("RESCHEDULE_RECORDINGS");
+- MythEvent me(cmd);
+-
+- gCoreContext->dispatch(me);
++ ScheduledRecording::RescheduleMatch(0, 0, 0, QDateTime(),
++ "RescheduleRecordings");
+ return true;
+ }
+
+
+From 1e926a5cc81ad21ec05b3ef9dace1a0e52699ebf Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 12 Oct 2018 09:25:47 -0600
+Subject: [PATCH 41/52] Prevent possible deadlock in CheckForRingBufferSwitch()
+
+CheckForRingBufferSwitch can call FinishRecording, which calls
+SavePositionMap which call CheckForRingBufferSwitch...
+
+(cherry picked from commit 1a4aa1e4d56028abf4c4a8be990f863380db7107)
+---
+ mythtv/libs/libmythtv/recorders/recorderbase.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/recorderbase.cpp
b/mythtv/libs/libmythtv/recorders/recorderbase.cpp
+index f7571d94411..ed868751978 100644
+--- a/mythtv/libs/libmythtv/recorders/recorderbase.cpp
++++ b/mythtv/libs/libmythtv/recorders/recorderbase.cpp
+@@ -644,7 +644,7 @@ void RecorderBase::SavePositionMap(bool force, bool finished)
+ // seconds. Otherwise, this check is only performed on keyframes,
+ // and if there is a problem with the input we may never see one
+ // again, resulting in a wedged recording.
+- if (ringBufferCheckTimer.isRunning() &&
++ if (!finished && ringBufferCheckTimer.isRunning() &&
+ ringBufferCheckTimer.elapsed() > 60000)
+ {
+ LOG(VB_RECORD, LOG_WARNING, LOC + "It has been over 60 seconds "
+
+From 237d16fc02cfe5faaaa5a5419e6c486f81a63acb Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sat, 6 Oct 2018 15:05:43 -0600
+Subject: [PATCH 42/52] Channel Scanner: use pre-increment instead of
+ post-increment, where appropriate.
+
+(cherry picked from commit 93b344048a47e637b4722d9406649049648a5399)
+---
+ .../libmythtv/channelscan/channelimporter.cpp | 91 ++++++++++---------
+ .../libmythtv/channelscan/channelscan_sm.cpp | 52 +++++------
+ .../channelscan/channelscanner_gui.cpp | 2 +-
+ .../channelscan/inputselectorsetting.cpp | 2 +-
+ .../channelscan/iptvchannelfetcher.cpp | 2 +-
+ mythtv/libs/libmythtv/channelscan/loglist.cpp | 2 +-
+ .../channelscan/multiplexsetting.cpp | 2 +-
+ .../libs/libmythtv/channelscan/scaninfo.cpp | 4 +-
+ .../channelscan/vboxchannelfetcher.cpp | 6 +-
+ 9 files changed, 83 insertions(+), 80 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+index 59ed1dba64a..d92ed58ebbd 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+@@ -40,7 +40,8 @@ void ChannelImporter::Process(const ScanDTVTransportList
&_transports)
+
+ LOG(VB_GENERAL, LOG_INFO, LOC + (channels ?
+ (m_success ?
+- QString("Found %1
channels").arg(channels) :
++ QString("Found %1 channels")
++ .arg(channels) :
+ "No new channels to process") :
+ "No channels to process.."));
+
+@@ -91,7 +92,7 @@ void ChannelImporter::Process(const ScanDTVTransportList
&_transports)
+ if (do_delete)
+ {
+ ScanDTVTransportList trans = transports;
+- for (uint i = 0; i < db_trans.size(); i++)
++ for (uint i = 0; i < db_trans.size(); ++i)
+ trans.push_back(db_trans[i]);
+ deleted_count = DeleteChannels(trans);
+ if (deleted_count)
+@@ -148,9 +149,9 @@ uint ChannelImporter::DeleteChannels(
+ vector<uint> off_air_list;
+ QMap<uint,bool> deleted;
+
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ ChannelInsertInfo chan = transports[i].channels[j];
+ bool was_in_db = chan.db_mplexid && chan.channel_id;
+@@ -178,7 +179,7 @@ uint ChannelImporter::DeleteChannels(
+
+ if (kDeleteAll == action)
+ {
+- for (uint k = 0; k < off_air_list.size(); k++)
++ for (uint k = 0; k < off_air_list.size(); ++k)
+ {
+ int i = off_air_list[k] >> 16, j = off_air_list[k] & 0xFFFF;
+ ChannelUtil::DeleteChannel(
+@@ -188,7 +189,7 @@ uint ChannelImporter::DeleteChannels(
+ }
+ else if (kDeleteInvisibleAll == action)
+ {
+- for (uint k = 0; k < off_air_list.size(); k++)
++ for (uint k = 0; k < off_air_list.size(); ++k)
+ {
+ int i = off_air_list[k] >> 16, j = off_air_list[k] & 0xFFFF;
+ int chanid = transports[i].channels[j].channel_id;
+@@ -209,11 +210,11 @@ uint ChannelImporter::DeleteChannels(
+ return 0;
+
+ // Create a new transports list without the deleted channels
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+ newlist.push_back(transports[i]);
+ newlist.back().channels.clear();
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ if (!deleted.contains(i<<16|j))
+ {
+@@ -293,7 +294,7 @@ void ChannelImporter::InsertChannels(
+ // and complete tuning information.
+
+ uint chantype = (uint) kChannelTypeNonConflictingFirst;
+- for (; chantype <= (uint) kChannelTypeNonConflictingLast; chantype++)
++ for (; chantype <= (uint) kChannelTypeNonConflictingLast; ++chantype)
+ {
+ ChannelType type = (ChannelType) chantype;
+ uint new_chan, old_chan;
+@@ -340,7 +341,7 @@ void ChannelImporter::InsertChannels(
+ // for remaining channels with complete tuning information
+ // insert channels with contiguous list of numbers as the channums
+ chantype = (uint) kChannelTypeConflictingFirst;
+- for (; chantype <= (uint) kChannelTypeConflictingLast; chantype++)
++ for (; chantype <= (uint) kChannelTypeConflictingLast; ++chantype)
+ {
+
+ ChannelType type = (ChannelType) chantype;
+@@ -385,14 +386,14 @@ ScanDTVTransportList ChannelImporter::InsertChannels(
+
+ // insert all channels with non-conflicting channum
+ // and complete tuning information.
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+ bool created_new_transport = false;
+ ScanDTVTransport new_transport;
+ bool created_filter_transport = false;
+ ScanDTVTransport filter_transport;
+
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ ChannelInsertInfo chan = transports[i].channels[j];
+
+@@ -595,14 +596,14 @@ ScanDTVTransportList ChannelImporter::UpdateChannels(
+
+ // update all channels with non-conflicting channum
+ // and complete tuning information.
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+ bool created_transport = false;
+ ScanDTVTransport new_transport;
+ bool created_filter_transport = false;
+ ScanDTVTransport filter_transport;
+
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ ChannelInsertInfo chan = transports[i].channels[j];
+
+@@ -753,12 +754,12 @@ void ChannelImporter::CleanupDuplicates(ScanDTVTransportList
&transports) const
+
+ vector<bool> ignore;
+ ignore.resize(transports.size());
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+ if (ignore[i])
+ continue;
+
+- for (uint j = i+1; j < transports.size(); j++)
++ for (uint j = i+1; j < transports.size(); ++j)
+ {
+ if (!transports[i].IsEqual(
+ tuner_type, transports[j], 500 * freq_mult))
+@@ -766,10 +767,10 @@ void ChannelImporter::CleanupDuplicates(ScanDTVTransportList
&transports) const
+ continue;
+ }
+
+- for (uint k = 0; k < transports[j].channels.size(); k++)
++ for (uint k = 0; k < transports[j].channels.size(); ++k)
+ {
+ bool found_same = false;
+- for (uint l = 0; l < transports[i].channels.size(); l++)
++ for (uint l = 0; l < transports[i].channels.size(); ++l)
+ {
+ if (transports[j].channels[k].IsSameChannel(
+ transports[i].channels[l]))
+@@ -795,10 +796,10 @@ void ChannelImporter::FilterServices(ScanDTVTransportList
&transports) const
+ bool require_av = (m_service_requirements & kRequireAV) == kRequireAV;
+ bool require_a = m_service_requirements & kRequireAudio;
+
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+ ChannelInsertInfoList filtered;
+- for (uint k = 0; k < transports[i].channels.size(); k++)
++ for (uint k = 0; k < transports[i].channels.size(); ++k)
+ {
+ if (m_fta_only && transports[i].channels[k].is_encrypted &&
+ transports[i].channels[k].decryption_status != kEncDecrypted)
+@@ -870,17 +871,17 @@ ScanDTVTransportList ChannelImporter::GetDBTransports(
+ bool newt_found = false;
+ QMap<uint,bool> found_chan;
+
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+ if (!transports[i].IsEqual(tuner_type, newt, 500 * freq_mult, true))
+ continue;
+
+ transports[i].mplex = mplexid;
+ newt_found = true;
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ ChannelInsertInfo &chan = transports[i].channels[j];
+- for (uint k = 0; k < newt.channels.size(); k++)
++ for (uint k = 0; k < newt.channels.size(); ++k)
+ {
+ if (newt.channels[k].IsSameChannel(chan, true))
+ {
+@@ -907,7 +908,7 @@ ScanDTVTransportList ChannelImporter::GetDBTransports(
+ ScanDTVTransport tmp = newt;
+ tmp.channels.clear();
+
+- for (uint k = 0; k < newt.channels.size(); k++)
++ for (uint k = 0; k < newt.channels.size(); ++k)
+ {
+ if (!found_chan[k])
+ tmp.channels.push_back(newt.channels[k]);
+@@ -924,9 +925,9 @@ ScanDTVTransportList ChannelImporter::GetDBTransports(
+ void ChannelImporter::FixUpOpenCable(ScanDTVTransportList &transports)
+ {
+ ChannelImporterBasicStats info;
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ ChannelInsertInfo &chan = transports[i].channels[j];
+ if (((chan.could_be_opencable && (chan.si_standard ==
"mpeg")) ||
+@@ -942,9 +943,9 @@ ChannelImporterBasicStats ChannelImporter::CollectStats(
+ const ScanDTVTransportList &transports)
+ {
+ ChannelImporterBasicStats info;
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ const ChannelInsertInfo &chan = transports[i].channels[j];
+ int enc = (chan.is_encrypted) ?
+@@ -956,20 +957,20 @@ ChannelImporterBasicStats ChannelImporter::CollectStats(
+ info.ntsc_channels[enc] += (chan.si_standard == "ntsc");
+ if (chan.si_standard != "ntsc")
+ {
+- info.prognum_cnt[chan.service_id]++;
+- info.channum_cnt[map_str(chan.chan_num)]++;
++ ++info.prognum_cnt[chan.service_id];
++ ++info.channum_cnt[map_str(chan.chan_num)];
+ }
+ if (chan.si_standard == "atsc")
+ {
+- info.atscnum_cnt[(chan.atsc_major_channel << 16) |
+- (chan.atsc_minor_channel)]++;
+- info.atscmin_cnt[chan.atsc_minor_channel]++;
+- info.atscmaj_cnt[chan.atsc_major_channel]++;
++ ++info.atscnum_cnt[(chan.atsc_major_channel << 16) |
++ (chan.atsc_minor_channel)];
++ ++info.atscmin_cnt[chan.atsc_minor_channel];
++ ++info.atscmaj_cnt[chan.atsc_major_channel];
+ }
+ if (chan.si_standard == "ntsc")
+ {
+- info.atscnum_cnt[(chan.atsc_major_channel << 16) |
+- (chan.atsc_minor_channel)]++;
++ ++info.atscnum_cnt[(chan.atsc_major_channel << 16) |
++ (chan.atsc_minor_channel)];
+ }
+ }
+ }
+@@ -983,9 +984,9 @@ ChannelImporterUniquenessStats
ChannelImporter::CollectUniquenessStats(
+ {
+ ChannelImporterUniquenessStats stats;
+
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ const ChannelInsertInfo &chan = transports[i].channels[j];
+ stats.unique_prognum +=
+@@ -1135,8 +1136,8 @@ QString ChannelImporter::FormatChannels(
+ {
+ QString msg;
+
+- for (uint i = 0; i < transports.size(); i++)
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint i = 0; i < transports.size(); ++i)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ msg += FormatChannel(transports[i], transports[i].channels[j],
+ &info) + "\n";
+
+@@ -1234,17 +1235,17 @@ void ChannelImporter::CountChannels(
+ ChannelType type, uint &new_chan, uint &old_chan)
+ {
+ new_chan = old_chan = 0;
+- for (uint i = 0; i < transports.size(); i++)
++ for (uint i = 0; i < transports.size(); ++i)
+ {
+- for (uint j = 0; j < transports[i].channels.size(); j++)
++ for (uint j = 0; j < transports[i].channels.size(); ++j)
+ {
+ ChannelInsertInfo chan = transports[i].channels[j];
+ if (IsType(info, chan, type))
+ {
+ if (chan.channel_id)
+- old_chan++;
++ ++old_chan;
+ else
+- new_chan++;
++ ++new_chan;
+ }
+ }
+ }
+@@ -1289,7 +1290,7 @@ QString ChannelImporter::ComputeSuggestedChannelNum(
+
+ QMutexLocker locker(&last_free_lock);
+ uint last_free_chan_num = last_free_chan_num_map[chan.source_id];
+- for (last_free_chan_num++; ; last_free_chan_num++)
++ for (last_free_chan_num++; ; ++last_free_chan_num)
+ {
+ chan_num = QString::number(last_free_chan_num);
+ if (!ChannelUtil::IsConflicting(chan_num, chan.source_id))
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+index f39b9c2bfc1..e387b1776fd 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp
+@@ -338,7 +338,7 @@ bool ChannelScanSM::ScanExistingTransports(uint sourceid, bool
follow_nit)
+ return false;
+ }
+
+- for (uint i = 0; i < multiplexes.size(); i++)
++ for (uint i = 0; i < multiplexes.size(); ++i)
+ AddToList(multiplexes[i]);
+
+ m_extendScanList = follow_nit;
+@@ -372,7 +372,7 @@ void ChannelScanSM::HandlePAT(const ProgramAssociationTable *pat)
+
+ // Add pmts to list, so we can do MPEG scan properly.
+ ScanStreamData *sd = GetDTVSignalMonitor()->GetScanStreamData();
+- for (uint i = 0; i < pat->ProgramCount(); i++)
++ for (uint i = 0; i < pat->ProgramCount(); ++i)
+ {
+ if (pat->ProgramPID(i)) // don't add NIT "program", MPEG/ATSC
safe.
+ sd->AddListeningPID(pat->ProgramPID(i));
+@@ -399,7 +399,7 @@ void ChannelScanSM::HandleVCT(uint, const VirtualChannelTable *vct)
+ QString("Got a Virtual Channel Table for %1")
+ .arg((*m_current).FriendlyName) + "\n" + vct->toString());
+
+- for (uint i = 0; !m_currentTestingDecryption && i <
vct->ChannelCount(); i++)
++ for (uint i = 0; !m_currentTestingDecryption && i <
vct->ChannelCount(); ++i)
+ {
+ if (vct->IsAccessControlled(i))
+ {
+@@ -455,7 +455,7 @@ void ChannelScanSM::HandleSDT(uint tsid, const
ServiceDescriptionTable *sdt)
+ uint id = sdt->OriginalNetworkID() << 16 | sdt->TSID();
+ m_tsScanned.insert(id);
+
+- for (uint i = 0; !m_currentTestingDecryption && i <
sdt->ServiceCount(); i++)
++ for (uint i = 0; !m_currentTestingDecryption && i <
sdt->ServiceCount(); ++i)
+ {
+ if (sdt->IsEncrypted(i))
+ {
+@@ -486,7 +486,7 @@ void ChannelScanSM::HandleBAT(const BouquetAssociationTable *bat)
+
+ m_otherTableTime = m_timer.elapsed() + m_otherTableTimeout;
+
+- for (uint i = 0; i < bat->TransportStreamCount(); i++)
++ for (uint i = 0; i < bat->TransportStreamCount(); ++i)
+ {
+ uint tsid = bat->TSID(i);
+ uint netid = bat->OriginalNetworkID(i);
+@@ -504,7 +504,7 @@ void ChannelScanSM::HandleBAT(const BouquetAssociationTable *bat)
+ DefaultAuthorityDescriptor authority(def_auth);
+ ServiceListDescriptor services(serv_list);
+
+- for (uint j = 0; j < services.ServiceCount(); j++)
++ for (uint j = 0; j < services.ServiceCount(); ++j)
+ {
+ // If the default authority is given in the SDT this
+ // overrides any definition in the BAT (or in the NIT)
+@@ -531,7 +531,7 @@ void ChannelScanSM::HandleSDTo(uint tsid, const
ServiceDescriptionTable *sdt)
+
+ uint netid = sdt->OriginalNetworkID();
+
+- for (uint i = 0; i < sdt->ServiceCount(); i++)
++ for (uint i = 0; i < sdt->ServiceCount(); ++i)
+ {
+ uint serviceId = sdt->ServiceID(i);
+ desc_list_t parsed =
+@@ -604,7 +604,7 @@ bool ChannelScanSM::TestNextProgramEncryption(void)
+ }
+
+ const ProgramMapTable *pmt = NULL;
+- for (uint i = 0; !pmt && (i < m_currentInfo->pmts.size()); i++)
++ for (uint i = 0; !pmt && (i < m_currentInfo->pmts.size()); ++i)
+ {
+ pmt = (m_currentInfo->pmts[i]->ProgramNumber() == pnum) ?
+ m_currentInfo->pmts[i] : NULL;
+@@ -771,7 +771,7 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+ // Grab PAT tables
+ pat_vec_t pattmp = sd->GetCachedPATs();
+ QMap<uint,bool> tsid_checked;
+- for (uint i = 0; i < pattmp.size(); i++)
++ for (uint i = 0; i < pattmp.size(); ++i)
+ {
+ uint tsid = pattmp[i]->TransportStreamID();
+ if (tsid_checked[tsid])
+@@ -826,7 +826,7 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+
+ sdt_vec_t sdttmp = sd->GetCachedSDTs();
+ tsid_checked.clear();
+- for (uint i = 0; i < sdttmp.size(); i++)
++ for (uint i = 0; i < sdttmp.size(); ++i)
+ {
+ uint tsid = sdttmp[i]->TSID();
+ if (tsid_checked[tsid])
+@@ -1002,7 +1002,7 @@ bool ChannelScanSM::UpdateChannelInfo(bool wait_until_complete)
+
+ if (m_scanning)
+ {
+- m_transportsScanned++;
++ ++m_transportsScanned;
+ UpdateScanPercentCompleted();
+ m_waitingForTables = false;
+ m_nextIt = m_current.nextTransport();
+@@ -1158,7 +1158,7 @@ uint ChannelScanSM::GetCurrentTransportInfo(
+
+ QMap<uint,ChannelInsertInfo> list = GetChannelList(m_current, m_currentInfo);
+ {
+- for (int i = 0; i < list.size(); i++)
++ for (int i = 0; i < list.size(); ++i)
+ {
+ max_chan_cnt +=
+ (list[i].in_pat || list[i].in_pmt ||
+@@ -1192,7 +1192,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+
+ // channels.conf
+ const DTVChannelInfoList &echan = (*trans_info).expectedChannels;
+- for (uint i = 0; i < echan.size(); i++)
++ for (uint i = 0; i < echan.size(); ++i)
+ {
+ uint pnum = echan[i].serviceid;
+ PCM_INFO_INIT("mpeg");
+@@ -1208,7 +1208,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ for (; pat_it != (*pat_list_it).end(); ++pat_it)
+ {
+ bool could_be_opencable = false;
+- for (uint i = 0; i < (*pat_it)->ProgramCount(); i++)
++ for (uint i = 0; i < (*pat_it)->ProgramCount(); ++i)
+ {
+ if (((*pat_it)->ProgramNumber(i) == 0) &&
+ ((*pat_it)->ProgramPID(i) == 0x1ffc))
+@@ -1217,7 +1217,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ }
+ }
+
+- for (uint i = 0; i < (*pat_it)->ProgramCount(); i++)
++ for (uint i = 0; i < (*pat_it)->ProgramCount(); ++i)
+ {
+ uint pnum = (*pat_it)->ProgramNumber(i);
+ if (pnum)
+@@ -1238,7 +1238,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ const ProgramMapTable *pmt = *pmt_it;
+ uint pnum = pmt->ProgramNumber();
+ PCM_INFO_INIT("mpeg");
+- for (uint i = 0; i < pmt->StreamCount(); i++)
++ for (uint i = 0; i < pmt->StreamCount(); ++i)
+ {
+ info.could_be_opencable |=
+ (StreamID::OpenCableVideo == pmt->StreamType(i));
+@@ -1248,7 +1248,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ pmt->ProgramInfo(), pmt->ProgramInfoLength(),
+ DescriptorID::registration);
+
+- for (uint i = 0; i < descs.size(); i++)
++ for (uint i = 0; i < descs.size(); ++i)
+ {
+ RegistrationDescriptor reg(descs[i]);
+ if (reg.FormatIdentifierString() == "CUEI" ||
+@@ -1264,7 +1264,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ cvct_vec_t::const_iterator cvct_it = scan_info->cvcts.begin();
+ for (; cvct_it != scan_info->cvcts.end(); ++cvct_it)
+ {
+- for (uint i = 0; i < (*cvct_it)->ChannelCount(); i++)
++ for (uint i = 0; i < (*cvct_it)->ChannelCount(); ++i)
+ {
+ uint pnum = (*cvct_it)->ProgramNumber(i);
+ PCM_INFO_INIT("atsc");
+@@ -1276,7 +1276,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ tvct_vec_t::const_iterator tvct_it = scan_info->tvcts.begin();
+ for (; tvct_it != scan_info->tvcts.end(); ++tvct_it)
+ {
+- for (uint i = 0; i < (*tvct_it)->ChannelCount(); i++)
++ for (uint i = 0; i < (*tvct_it)->ChannelCount(); ++i)
+ {
+ uint pnum = (*tvct_it)->ProgramNumber(i);
+ PCM_INFO_INIT("atsc");
+@@ -1291,7 +1291,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ sdt_vec_t::const_iterator sdt_it = (*sdt_list_it).begin();
+ for (; sdt_it != (*sdt_list_it).end(); ++sdt_it)
+ {
+- for (uint i = 0; i < (*sdt_it)->ServiceCount(); i++)
++ for (uint i = 0; i < (*sdt_it)->ServiceCount(); ++i)
+ {
+ uint pnum = (*sdt_it)->ServiceID(i);
+ PCM_INFO_INIT("dvb");
+@@ -1312,7 +1312,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ nit_vec_t::const_iterator nits_it = scan_info->nits.begin();
+ for (; nits_it != scan_info->nits.end(); ++nits_it)
+ {
+- for (uint i = 0; i < (*nits_it)->TransportStreamCount(); i++)
++ for (uint i = 0; i < (*nits_it)->TransportStreamCount(); ++i)
+ {
+ const NetworkInformationTable *nit = (*nits_it);
+ if ((nit->TSID(i) == info.sdt_tsid) &&
+@@ -1338,7 +1338,7 @@ ChannelScanSM::GetChannelList(transport_scan_items_it_t
trans_info,
+ if (desc)
+ {
+ DVBLogicalChannelDescriptor uklist(desc);
+- for (uint j = 0; j < uklist.ChannelCount(); j++)
++ for (uint j = 0; j < uklist.ChannelCount(); ++j)
+ {
+ ukChanNums[((qlonglong)info.orig_netid<<32) |
+ uklist.ServiceID(j)] =
+@@ -1870,7 +1870,7 @@ bool ChannelScanSM::ScanTransports(
+ item.toString());
+ }
+
+- name_num++;
++ ++name_num;
+ freq += ft.frequencyStep;
+
+ if (!end.isEmpty() && name == end)
+@@ -1909,7 +1909,7 @@ bool ChannelScanSM::ScanForChannels(uint sourceid,
+ tunertype.Parse(cardtype);
+
+ DTVChannelList::const_iterator it = channels.begin();
+- for (uint i = 0; it != channels.end(); ++it, i++)
++ for (uint i = 0; it != channels.end(); ++it, ++i)
+ {
+ DTVTransport tmp = *it;
+ tmp.sistandard = std;
+@@ -2075,7 +2075,7 @@ bool ChannelScanSM::AddToList(uint mplexid)
+ struct CHANLIST *curList = chanlists[0].list;
+ int totalChannels = chanlists[0].count;
+ int findFrequency = (query.value(3).toInt() / 1000) - 1750;
+- for (int x = 0 ; x < totalChannels ; x++)
++ for (int x = 0 ; x < totalChannels ; ++x)
+ {
+ if ((curList[x].freq <= findFrequency + 200) &&
+ (curList[x].freq >= findFrequency - 200))
+@@ -2157,7 +2157,7 @@ bool ChannelScanSM::CheckImportedList(
+ return true;
+
+ bool found = false;
+- for (uint i = 0; i < channels.size(); i++)
++ for (uint i = 0; i < channels.size(); ++i)
+ {
+ LOG(VB_GENERAL, LOG_DEBUG, LOC +
+ QString("comparing %1 %2 against %3 %4")
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
b/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
+index 456a45c4cc0..d0a041d3e4e 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
+@@ -170,7 +170,7 @@ void ChannelScannerGUI::MonitorProgress(bool lock, bool strength,
+ {
+ connect(scanStage, SIGNAL(Exiting()), SLOT(quitScanning()));
+
+- for (uint i = 0; i < (uint) messageList.size(); i++)
++ for (uint i = 0; i < (uint) messageList.size(); ++i)
+ scanStage->AppendLine(messageList[i]);
+ mainStack->AddScreen(scanStage);
+ }
+diff --git a/mythtv/libs/libmythtv/channelscan/inputselectorsetting.cpp
b/mythtv/libs/libmythtv/channelscan/inputselectorsetting.cpp
+index 351a6639890..1c317bdc673 100644
+--- a/mythtv/libs/libmythtv/channelscan/inputselectorsetting.cpp
++++ b/mythtv/libs/libmythtv/channelscan/inputselectorsetting.cpp
+@@ -66,7 +66,7 @@ void InputSelector::Load(void)
+ }
+
+ uint which = 0, cnt = 0;
+- for (; query.next(); cnt++)
++ for (; query.next(); ++cnt)
+ {
+ uint cardid = query.value(0).toUInt();
+ QString inputname = query.value(3).toString();
+diff --git a/mythtv/libs/libmythtv/channelscan/iptvchannelfetcher.cpp
b/mythtv/libs/libmythtv/channelscan/iptvchannelfetcher.cpp
+index ec4c6886723..f1269066e2e 100644
+--- a/mythtv/libs/libmythtv/channelscan/iptvchannelfetcher.cpp
++++ b/mythtv/libs/libmythtv/channelscan/iptvchannelfetcher.cpp
+@@ -324,7 +324,7 @@ fbox_chan_map_t IPTVChannelFetcher::ParsePlaylist(
+
+ // Parse each channel
+ uint lineNum = 1;
+- for (uint i = 1; true; i++)
++ for (uint i = 1; true; ++i)
+ {
+ IPTVChannelInfo info;
+ QString channum = QString::null;
+diff --git a/mythtv/libs/libmythtv/channelscan/loglist.cpp
b/mythtv/libs/libmythtv/channelscan/loglist.cpp
+index 751fbb4ad01..0286f6ec31f 100644
+--- a/mythtv/libs/libmythtv/channelscan/loglist.cpp
++++ b/mythtv/libs/libmythtv/channelscan/loglist.cpp
+@@ -13,5 +13,5 @@ void LogList::AppendLine(const QString &text)
+ {
+ addSelection(text, QString::number(idx));
+ setCurrentItem(idx);
+- idx++;
++ ++idx;
+ }
+diff --git a/mythtv/libs/libmythtv/channelscan/multiplexsetting.cpp
b/mythtv/libs/libmythtv/channelscan/multiplexsetting.cpp
+index 0f57b45df2e..f09f20d2bcf 100644
+--- a/mythtv/libs/libmythtv/channelscan/multiplexsetting.cpp
++++ b/mythtv/libs/libmythtv/channelscan/multiplexsetting.cpp
+@@ -60,7 +60,7 @@ void MultiplexSetting::Load(void)
+ struct CHANLIST* curList = chanlists[0].list;
+ int totalChannels = chanlists[0].count;
+ int findFrequency = (query.value(3).toInt() / 1000) - 1750;
+- for (int x = 0 ; x < totalChannels ; x++)
++ for (int x = 0 ; x < totalChannels ; ++x)
+ {
+ if ((curList[x].freq <= findFrequency + 200) &&
+ (curList[x].freq >= findFrequency - 200))
+diff --git a/mythtv/libs/libmythtv/channelscan/scaninfo.cpp
b/mythtv/libs/libmythtv/channelscan/scaninfo.cpp
+index 3a73180a2c1..97a61eb89c0 100644
+--- a/mythtv/libs/libmythtv/channelscan/scaninfo.cpp
++++ b/mythtv/libs/libmythtv/channelscan/scaninfo.cpp
+@@ -34,7 +34,7 @@ uint SaveScan(const ScanDTVTransportList &scan)
+
+ // Delete very old scans
+ const vector<ScanInfo> list = LoadScanList();
+- for (uint i = 0; i < list.size(); i++)
++ for (uint i = 0; i < list.size(); ++i)
+ {
+ if (list[i].scandate > MythDate::current().addDays(-14))
+ continue;
+@@ -65,7 +65,7 @@ uint SaveScan(const ScanDTVTransportList &scan)
+ if (!scanid)
+ return scanid;
+
+- for (uint i = 0; i < scan.size(); i++)
++ for (uint i = 0; i < scan.size(); ++i)
+ scan[i].SaveScan(scanid);
+
+ return scanid;
+diff --git a/mythtv/libs/libmythtv/channelscan/vboxchannelfetcher.cpp
b/mythtv/libs/libmythtv/channelscan/vboxchannelfetcher.cpp
+index 8ddd0704b84..2d2545edff8 100644
+--- a/mythtv/libs/libmythtv/channelscan/vboxchannelfetcher.cpp
++++ b/mythtv/libs/libmythtv/channelscan/vboxchannelfetcher.cpp
+@@ -190,7 +190,8 @@ void VBoxChannelFetcher::run(void)
+ // ignore this encrypted channel
+ if (_scan_monitor)
+ {
+- _scan_monitor->ScanAppendTextToLog(tr("Ignoring Encrypted
%1").arg(msg));
++ _scan_monitor->ScanAppendTextToLog(tr("Ignoring Encrypted
%1")
++ .arg(msg));
+ }
+ }
+ else if (chanType == "Radio" && _serviceType !=
kRequireAudio)
+@@ -198,7 +199,8 @@ void VBoxChannelFetcher::run(void)
+ // ignore this radio channel
+ if (_scan_monitor)
+ {
+- _scan_monitor->ScanAppendTextToLog(tr("Ignoring Radio
%1").arg(msg));
++ _scan_monitor->ScanAppendTextToLog(tr("Ignoring Radio %1")
++ .arg(msg));
+ }
+ }
+ else if (!SupportedTransmission(transType))
+
+From d1b8555ad58ca7ea3889be65406dbb84aed6bb79 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Fri, 5 Oct 2018 14:54:50 -0600
+Subject: [PATCH 43/52] Add ability to "scan" channels from External Recorder.
+
+(cherry picked from commit 3f58c7869c90b177478004a48f8df52e88428235)
+(cherry picked from commit f46e781a04ec2ac345dbf7ceda08ae91362d9081)
+---
+ .../libmythtv/channelscan/channelimporter.cpp | 7 +-
+ .../libmythtv/channelscan/channelimporter.h | 2 +-
+ .../libmythtv/channelscan/channelscanner.cpp | 60 ++++-
+ .../libmythtv/channelscan/channelscanner.h | 11 +
+ .../channelscan/channelscanner_cli.cpp | 2 +-
+ .../channelscan/channelscanner_gui.cpp | 13 +-
+ .../channelscan/externrecscanner.cpp | 242 ++++++++++++++++++
+ .../libmythtv/channelscan/externrecscanner.h | 57 +++++
+ .../channelscan/scanwizardconfig.cpp | 2 +
+ .../libmythtv/channelscan/scanwizardconfig.h | 2 +
+ mythtv/libs/libmythtv/libmythtv.pro | 7 +
+ .../recorders/ExternalRecChannelFetcher.cpp | 141 ++++++++++
+ .../recorders/ExternalRecChannelFetcher.h | 65 +++++
+ .../recorders/ExternalStreamHandler.cpp | 60 ++---
+ .../recorders/ExternalStreamHandler.h | 6 +-
+ mythtv/libs/libmythtv/scanwizard.cpp | 7 +-
+ 16 files changed, 631 insertions(+), 53 deletions(-)
+ create mode 100644 mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
+ create mode 100644 mythtv/libs/libmythtv/channelscan/externrecscanner.h
+ create mode 100644 mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
+ create mode 100644 mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
+
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+index d92ed58ebbd..4ad2d6ac091 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.cpp
+@@ -30,13 +30,14 @@ static QString map_str(QString str)
+ return str;
+ }
+
+-void ChannelImporter::Process(const ScanDTVTransportList &_transports)
++void ChannelImporter::Process(const ScanDTVTransportList &_transports,
++ int sourceid)
+ {
+ if (_transports.empty())
+ {
+ if (use_gui)
+ {
+- int channels = ChannelUtil::GetChannelCount();
++ int channels = ChannelUtil::GetChannelCount(sourceid);
+
+ LOG(VB_GENERAL, LOG_INFO, LOC + (channels ?
+ (m_success ?
+@@ -81,7 +82,7 @@ void ChannelImporter::Process(const ScanDTVTransportList
&_transports)
+ FilterServices(transports);
+
+ // Pull in DB info
+- uint sourceid = transports[0].channels[0].source_id;
++ sourceid = transports[0].channels[0].source_id;
+ ScanDTVTransportList db_trans = GetDBTransports(sourceid, transports);
+
+ // Make sure "Open Cable" channels are marked that way.
+diff --git a/mythtv/libs/libmythtv/channelscan/channelimporter.h
b/mythtv/libs/libmythtv/channelscan/channelimporter.h
+index f70de7750f0..e87492d183f 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelimporter.h
++++ b/mythtv/libs/libmythtv/channelscan/channelimporter.h
+@@ -89,7 +89,7 @@ class MTV_PUBLIC ChannelImporter
+ m_success(success),
+ m_service_requirements(service_requirements) { }
+
+- void Process(const ScanDTVTransportList&);
++ void Process(const ScanDTVTransportList&, int sourceid = -1);
+
+ protected:
+ typedef enum
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
+index 2cd83ce2331..99fc3814ef6 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp
+@@ -47,12 +47,20 @@ using namespace std;
+
+ #define LOC QString("ChScan: ")
+
+-ChannelScanner::ChannelScanner() :
+- scanMonitor(NULL), channel(NULL), sigmonScanner(NULL), iptvScanner(NULL),
++ChannelScanner::ChannelScanner()
++ : scanMonitor(NULL)
++ , channel(NULL)
++ , sigmonScanner(NULL)
++ , iptvScanner(NULL)
+ #ifdef USING_VBOX
+- vboxScanner(NULL),
++ , vboxScanner(NULL)
+ #endif
+- freeToAirOnly(false), serviceRequirements(kRequireAV)
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
++ , m_ExternRecScanner(NULL)
++#endif
++ , freeToAirOnly(false)
++ , m_sourceid(-1)
++ , serviceRequirements(kRequireAV)
+ {
+ }
+
+@@ -97,6 +105,15 @@ void ChannelScanner::Teardown(void)
+ }
+ #endif
+
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
++ if (m_ExternRecScanner)
++ {
++ m_ExternRecScanner->Stop();
++ delete m_ExternRecScanner;
++ m_ExternRecScanner = nullptr;
++ }
++#endif
++
+ if (scanMonitor)
+ {
+ scanMonitor->deleteLater();
+@@ -127,6 +144,7 @@ void ChannelScanner::Scan(
+ {
+ freeToAirOnly = do_fta_only;
+ serviceRequirements = service_requirements;
++ m_sourceid = sourceid;
+
+ PreScanCommon(scantype, cardid, inputname,
+ sourceid, do_ignore_signal_timeout, do_test_decryption);
+@@ -272,6 +290,7 @@ void ChannelScanner::Scan(
+ DTVConfParser::return_t ChannelScanner::ImportDVBUtils(
+ uint sourceid, int cardtype, const QString &file)
+ {
++ m_sourceid = sourceid;
+ channels.clear();
+
+ DTVConfParser::cardtype_t type = DTVConfParser::UNKNOWN;
+@@ -314,7 +333,7 @@ bool ChannelScanner::ImportM3U(uint cardid, const QString
&inputname,
+ {
+ (void) cardid;
+ (void) inputname;
+- (void) sourceid;
++ m_sourceid = sourceid;
+
+ if (!scanMonitor)
+ scanMonitor = new ScanMonitor(this);
+@@ -336,6 +355,7 @@ bool ChannelScanner::ImportM3U(uint cardid, const QString
&inputname,
+ bool ChannelScanner::ImportVBox(uint cardid, const QString &inputname, uint
sourceid,
+ bool ftaOnly, ServiceRequirements serviceType)
+ {
++ m_sourceid = sourceid;
+ #ifdef USING_VBOX
+ if (!scanMonitor)
+ scanMonitor = new ScanMonitor(this);
+@@ -351,7 +371,33 @@ bool ChannelScanner::ImportVBox(uint cardid, const QString
&inputname, uint sour
+ #else
+ (void) cardid;
+ (void) inputname;
+- (void) sourceid;
++ return false;
++#endif
++}
++
++bool ChannelScanner::ImportExternRecorder(uint cardid, const QString &inputname,
++ uint sourceid)
++{
++ m_sourceid = sourceid;
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
++ if (!scanMonitor)
++ scanMonitor = new ScanMonitor(this);
++
++ // Create a External Recorder Channel Fetcher
++ m_ExternRecScanner = new ExternRecChannelScanner(cardid,
++ inputname,
++ sourceid,
++ scanMonitor);
++
++ MonitorProgress(false, false, false, false);
++
++ m_ExternRecScanner->Scan();
++
++ return true;
++#else
++ (void) cardid;
++ (void) inputname;
++
+ return false;
+ #endif
+ }
+@@ -443,10 +489,12 @@ void ChannelScanner::PreScanCommon(
+ }
+ #endif
+
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
+ if ("EXTERNAL" == card_type)
+ {
+ channel = new ExternalChannel(NULL, device);
+ }
++#endif
+
+ if (!channel)
+ {
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner.h
b/mythtv/libs/libmythtv/channelscan/channelscanner.h
+index 28e754a50b5..f269f224abf 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner.h
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner.h
+@@ -44,8 +44,13 @@
+ #include "vboxchannelfetcher.h"
+ #endif
+
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
++#include "externrecscanner.h"
++#endif
++
+ class ScanMonitor;
+ class IPTVChannelFetcher;
++class ExternRecChannelFetcher;
+ class ChannelScanSM;
+ class ChannelBase;
+
+@@ -88,6 +93,8 @@ class MTV_PUBLIC ChannelScanner
+ uint sourceid, bool is_mpts);
+ virtual bool ImportVBox(uint cardid, const QString &inputname, uint sourceid,
+ bool ftaOnly, ServiceRequirements serviceType);
++ virtual bool ImportExternRecorder(uint cardid, const QString &inputname,
++ uint sourceid);
+
+ protected:
+ virtual void Teardown(void);
+@@ -120,9 +127,13 @@ class MTV_PUBLIC ChannelScanner
+ #ifdef USING_VBOX
+ VBoxChannelFetcher *vboxScanner;
+ #endif
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
++ ExternRecChannelScanner *m_ExternRecScanner;
++#endif
+
+ /// Only fta channels desired post scan?
+ bool freeToAirOnly;
++ int m_sourceid;
+
+ /// Services desired post scan
+ ServiceRequirements serviceRequirements;
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp
b/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp
+index 2bfbb25d074..e2206f426b4 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner_cli.cpp
+@@ -146,7 +146,7 @@ void ChannelScannerCLI::Process(const ScanDTVTransportList
&_transports)
+ {
+ ChannelImporter ci(false, interactive, !onlysavescan, !onlysavescan, true,
+ freeToAirOnly, serviceRequirements);
+- ci.Process(_transports);
++ ci.Process(_transports, m_sourceid);
+ }
+
+ void ChannelScannerCLI::MonitorProgress(
+diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
b/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
+index d0a041d3e4e..7d68cda46c8 100644
+--- a/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
++++ b/mythtv/libs/libmythtv/channelscan/channelscanner_gui.cpp
+@@ -87,10 +87,12 @@ void ChannelScannerGUI::HandleEvent(const ScannerEvent *scanEvent)
+ transports = sigmonScanner->GetChannelList();
+ }
+
++ bool success = (iptvScanner != nullptr);
+ #ifdef USING_VBOX
+- bool success = (iptvScanner != NULL || vboxScanner != NULL);
+-#else
+- bool success = iptvScanner != NULL;
++ success |= (vboxScanner != nullptr);
++#endif
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
++ success |= (m_ExternRecScanner != nullptr);
+ #endif
+
+ Teardown();
+@@ -136,11 +138,12 @@ void ChannelScannerGUI::HandleEvent(const ScannerEvent *scanEvent)
+ scanStage->SetStatusSignalStrength(scanEvent->intValue());
+ }
+
+-void ChannelScannerGUI::Process(const ScanDTVTransportList &_transports, bool
success)
++void ChannelScannerGUI::Process(const ScanDTVTransportList &_transports,
++ bool success)
+ {
+ ChannelImporter ci(true, true, true, true, true,
+ freeToAirOnly, serviceRequirements, success);
+- ci.Process(_transports);
++ ci.Process(_transports, m_sourceid);
+ }
+
+ void ChannelScannerGUI::InformUser(const QString &error)
+diff --git a/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
b/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
+new file mode 100644
+index 00000000000..24a3a5b8f45
+--- /dev/null
++++ b/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
+@@ -0,0 +1,242 @@
++// -*- Mode: c++ -*-
++
++// Std C headers
++#include <cmath>
++#include <unistd.h>
++
++// Qt headers
++#include <QFile>
++#include <QTextStream>
++
++// MythTV headers
++#include "mythcontext.h"
++#include "cardutil.h"
++#include "channelutil.h"
++#include "externrecscanner.h"
++#include "scanmonitor.h"
++#include "mythlogging.h"
++#include "ExternalRecChannelFetcher.h"
++
++#define LOC QString("ExternRecChanFetch: ")
++
++ExternRecChannelScanner::ExternRecChannelScanner(uint cardid,
++ const QString &inputname,
++ uint sourceid,
++ ScanMonitor *monitor)
++ : m_scan_monitor(monitor)
++ , m_cardid(cardid)
++ , m_inputname(inputname)
++ , m_sourceid(sourceid)
++ , m_channel_cnt(1)
++ , m_thread_running(false)
++ , m_stop_now(false)
++ , m_thread(new MThread("ExternRecChannelScanner", this))
++ , m_lock()
++{
++ LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Has ScanMonitor %1")
++ .arg(monitor ? "true" : "false"));
++}
++
++ExternRecChannelScanner::~ExternRecChannelScanner(void)
++{
++ Stop();
++ delete m_thread;
++ m_thread = NULL;
++}
++
++/** \fn ExternRecChannelScanner::Stop(void)
++ * \brief Stops the scanning thread running
++ */
++void ExternRecChannelScanner::Stop(void)
++{
++ m_lock.lock();
++
++ while (m_thread_running)
++ {
++ m_stop_now = true;
++ m_lock.unlock();
++ m_thread->wait(5);
++ m_lock.lock();
++ }
++
++ m_lock.unlock();
++
++ m_thread->wait();
++}
++
++/// \brief Scans the list.
++void ExternRecChannelScanner::Scan(void)
++{
++ Stop();
++ m_stop_now = false;
++ m_thread->start();
++}
++
++void ExternRecChannelScanner::run(void)
++{
++ m_lock.lock();
++ m_thread_running = true;
++ m_lock.unlock();
++
++
++ // Step 1/4 : Get info from DB
++ QString cmd = CardUtil::GetVideoDevice(m_cardid);
++
++ if (m_stop_now || cmd.isEmpty())
++ {
++ LOG(VB_CHANNEL, LOG_INFO, LOC + "Invalid external command");
++ QMutexLocker locker(&m_lock);
++ m_thread_running = false;
++ m_stop_now = true;
++ return;
++ }
++
++ LOG(VB_CHANNEL, LOG_INFO, LOC + QString("External Command:
'%1'").arg(cmd));
++
++
++ // Step 2/4 : Download
++ if (m_scan_monitor)
++ {
++ m_scan_monitor->ScanPercentComplete(1);
++ m_scan_monitor->ScanAppendTextToLog(tr("Creating channel list"));
++ }
++
++ ExternalRecChannelFetcher fetch(m_cardid, cmd);
++
++ if ((m_channel_total = fetch.LoadChannels()) < 1)
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + "Failed to load channels");
++ QMutexLocker locker(&m_lock);
++ m_thread_running = false;
++ m_stop_now = true;
++ return;
++ }
++
++ vector<uint> existing = ChannelUtil::GetChanIDs(m_sourceid);
++ vector<uint>::iterator Iold;
++
++ // Step 3/4 : Process
++ if (m_scan_monitor)
++ m_scan_monitor->ScanAppendTextToLog(tr("Processing channels"));
++
++ QString channum;
++ QString name;
++ QString callsign;
++ QString xmltvid;
++ int cnt = 0;
++
++ if (!fetch.FirstChannel(channum, name, callsign, xmltvid))
++ {
++ LOG(VB_CHANNEL, LOG_WARNING, LOC + "No channels found.");
++ QMutexLocker locker(&m_lock);
++ m_thread_running = false;
++ m_stop_now = true;
++ return;
++ }
++
++ if (m_scan_monitor)
++ m_scan_monitor->ScanAppendTextToLog(tr("Adding Channels"));
++
++ uint idx = 0;
++ for (;;)
++ {
++ QString msg = tr("Channel #%1 : %2").arg(channum).arg(name);
++
++ LOG(VB_CHANNEL, LOG_INFO, QString("Handling channel %1 %2")
++ .arg(channum).arg(name));
++
++ int chanid = ChannelUtil::GetChanID(m_sourceid, channum);
++
++ if (m_scan_monitor)
++ m_scan_monitor->ScanPercentComplete(++cnt * 100 / m_channel_total);
++
++ if (chanid <= 0)
++ {
++ if (m_scan_monitor)
++ m_scan_monitor->ScanAppendTextToLog(tr("Adding
%1").arg(msg));
++
++ chanid = ChannelUtil::CreateChanID(m_sourceid, channum);
++ ChannelUtil::CreateChannel(0, m_sourceid, chanid, name, callsign,
++ channum, 1, 0, 0,
++ false, false, false, QString(),
++ QString(), "Default", xmltvid);
++ }
++ else
++ {
++ if (m_scan_monitor)
++ m_scan_monitor->ScanAppendTextToLog(tr("Updating
%1").arg(msg));
++
++ ChannelUtil::UpdateChannel(0, m_sourceid, chanid, name, callsign,
++ channum, 1, 0, 0,
++ false, false, false, QString(),
++ QString(), "Default", xmltvid);
++ }
++
++ SetNumChannelsInserted(cnt);
++
++ if ((Iold = std::find(existing.begin(), existing.end(), chanid)) !=
++ existing.end())
++ {
++ existing.erase(Iold);
++ }
++
++ if (++idx < m_channel_total)
++ fetch.NextChannel(channum, name, callsign, xmltvid);
++ else
++ break;
++ }
++
++ // Remove any channels which are no longer valid
++ for (Iold = existing.begin(); Iold != existing.end(); ++Iold)
++ {
++ channum = ChannelUtil::GetChanNum(*Iold);
++
++ if (m_scan_monitor)
++ m_scan_monitor->ScanAppendTextToLog
++ (tr("Removing unused Channel #%1").arg(channum));
++
++ ChannelUtil::DeleteChannel(*Iold);
++ }
++
++ // Step 4/4 : Finish up
++ LOG(VB_CHANNEL, LOG_INFO, LOC + QString("Found %1 channels").arg(cnt));
++ if (m_scan_monitor)
++ m_scan_monitor->ScanAppendTextToLog
++ (tr("Found %1 channels.").arg(cnt));
++
++ if (m_scan_monitor)
++ {
++ m_scan_monitor->ScanAppendTextToLog(tr("Done"));
++ m_scan_monitor->ScanAppendTextToLog("");
++ m_scan_monitor->ScanPercentComplete(100);
++ m_scan_monitor->ScanComplete();
++ }
++
++ QMutexLocker locker(&m_lock);
++ m_thread_running = false;
++ m_stop_now = true;
++}
++
++void ExternRecChannelScanner::SetNumChannelsParsed(uint val)
++{
++ uint minval = 35, range = 70 - minval;
++ uint pct = minval + (uint) truncf((((float)val) / m_channel_cnt) * range);
++ if (m_scan_monitor)
++ m_scan_monitor->ScanPercentComplete(pct);
++}
++
++void ExternRecChannelScanner::SetNumChannelsInserted(uint val)
++{
++ uint minval = 70, range = 100 - minval;
++ uint pct = minval + (uint) truncf((((float)val) / m_channel_cnt) * range);
++ if (m_scan_monitor)
++ m_scan_monitor->ScanPercentComplete(pct);
++}
++
++void ExternRecChannelScanner::SetMessage(const QString &status)
++{
++ if (m_scan_monitor)
++ m_scan_monitor->ScanAppendTextToLog(status);
++}
++
++/* vim: set expandtab tabstop=4 shiftwidth=4: */
+diff --git a/mythtv/libs/libmythtv/channelscan/externrecscanner.h
b/mythtv/libs/libmythtv/channelscan/externrecscanner.h
+new file mode 100644
+index 00000000000..379e76dd94e
+--- /dev/null
++++ b/mythtv/libs/libmythtv/channelscan/externrecscanner.h
+@@ -0,0 +1,57 @@
++/** -*- Mode: c++ -*-
++ * External Recorder Channel Fetcher
++ * Distributed as part of MythTV under GPL v2 and later.
++ */
++
++#ifndef _EXTERNREC_CHANNEL_FETCHER_H_
++#define _EXTERNREC_CHANNEL_FETCHER_H_
++
++// Qt headers
++#include <QString>
++#include <QRunnable>
++#include <QObject>
++#include <QMutex>
++#include <QMap>
++#include <QCoreApplication>
++
++// MythTV headers
++#include "mthread.h"
++
++class ScanMonitor;
++
++class ExternRecChannelScanner : public QRunnable
++{
++ Q_DECLARE_TR_FUNCTIONS(ExternRecChannelScanner);
++
++ public:
++ ExternRecChannelScanner(uint cardid, const QString &inputname, uint sourceid,
++ ScanMonitor *monitor = nullptr);
++ ~ExternRecChannelScanner();
++
++ void Scan(void);
++ void Stop(void);
++
++ private:
++ void SetNumChannelsParsed(uint val);
++ void SetNumChannelsInserted(uint val);
++ void SetMessage(const QString &status);
++
++ protected:
++ virtual void run(void); // QRunnable
++
++ private:
++ ScanMonitor *m_scan_monitor;
++ uint m_cardid;
++ QString m_inputname;
++ uint m_sourceid;
++ uint m_channel_total;
++ uint m_channel_cnt;
++ bool m_thread_running;
++ bool m_stop_now;
++ MThread *m_thread;
++ QMutex m_lock;
++};
++
++#endif // _EXTERNREC_CHANNEL_FETCHER_H_
++
++/* vim: set expandtab tabstop=4 shiftwidth=4: */
+diff --git a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp
b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp
+index 02ce957e296..65da1629c96 100644
+--- a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp
++++ b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp
+@@ -192,6 +192,8 @@ void ScanTypeSetting::SetInput(const QString &cardids_inputname)
+ case CardUtil::EXTERNAL:
+ addSelection(tr("MPTS Scan"),
+ QString::number(CurrentTransportScan), true);
++ addSelection(tr("Import from ExternalRecorder"),
++ QString::number(ExternRecImport), true);
+ return;
+ case CardUtil::ERROR_PROBE:
+ addSelection(tr("Failed to probe the card"),
+diff --git a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h
b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h
+index 996f0438550..1221d892190 100644
+--- a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h
++++ b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h
+@@ -95,6 +95,8 @@ class ScanTypeSetting : public TransMythUIComboBoxSetting
+ ExistingScanImport,
+ // Import using the VBox API to get the channel list
+ VBoxImport,
++ // Import using the ExternalRecorder API to get the channel list
++ ExternRecImport
+ };
+
+ ScanTypeSetting() : hw_cardid(0)
+diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro
+index 4c9b17e40c7..9ede9f66dd3 100644
+--- a/mythtv/libs/libmythtv/libmythtv.pro
++++ b/mythtv/libs/libmythtv/libmythtv.pro
+@@ -580,6 +580,11 @@ using_backend {
+ SOURCES += channelscan/scanmonitor.cpp
+ SOURCES += channelscan/scanwizardconfig.cpp
+
++#if !defined( USING_MINGW ) && !defined( _MSC_VER )
++ HEADERS += channelscan/externrecscanner.h
++ SOURCES += channelscan/externrecscanner.cpp
++#endif
++
+ # EIT stuff
+ HEADERS += eithelper.h eitscanner.h
+ HEADERS += eitfixup.h eitcache.h
+@@ -812,6 +817,8 @@ using_backend {
+ # External recorder
+ HEADERS += recorders/ExternalChannel.h
+ SOURCES += recorders/ExternalChannel.cpp
++ HEADERS += recorders/ExternalRecChannelFetcher.h
++ SOURCES += recorders/ExternalRecChannelFetcher.cpp
+ HEADERS += recorders/ExternalRecorder.h
+ SOURCES += recorders/ExternalRecorder.cpp
+ HEADERS += recorders/ExternalStreamHandler.h
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
+new file mode 100644
+index 00000000000..a0e2d16bd2f
+--- /dev/null
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.cpp
+@@ -0,0 +1,141 @@
++/* -*- Mode: c++ -*-
++ * Class ExternalFetcher
++ *
++ * Copyright (C) John Poet 2018
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++
++// Qt includes
++#include <QString>
++
++// MythTV includes
++#include "ExternalRecChannelFetcher.h"
++#include "ExternalStreamHandler.h"
++
++#define LOC QString("ExternalRec[%1](%2): ").arg(m_cardid).arg(m_command)
++
++ExternalRecChannelFetcher::ExternalRecChannelFetcher(int cardid,
++ const QString & cmd)
++ : m_cardid(cardid)
++ , m_command(cmd)
++ , m_stream_handler(nullptr)
++{
++ m_stream_handler = ExternalStreamHandler::Get(m_command, m_cardid);
++}
++
++ExternalRecChannelFetcher::~ExternalRecChannelFetcher(void)
++{
++ Close();
++}
++
++void ExternalRecChannelFetcher::Close(void)
++{
++ if (m_stream_handler)
++ ExternalStreamHandler::Return(m_stream_handler, m_cardid);
++}
++
++bool ExternalRecChannelFetcher::Valid(void) const
++{
++ if (!m_stream_handler)
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + "Failed to open external app.");
++ return false;
++ }
++
++ if (!m_stream_handler->HasTuner())
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + "External app does not have a
tuner.");
++ return false;
++ }
++
++ return true;
++}
++
++bool ExternalRecChannelFetcher::FetchChannel(const QString & cmd,
++ QString & channum,
++ QString & name,
++ QString & callsign,
++ QString & xmltvid)
++{
++ if (!Valid())
++ return false;
++
++ QString result;
++
++ if (!m_stream_handler->ProcessCommand(cmd, 5000, result))
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + QString("%1 command
failed.").arg(cmd));
++ return false;
++ }
++
++ if (result.startsWith("ERR"))
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + QString("%1: %2")
++ .arg(cmd).arg(result));
++ return false;
++ }
++ if (result.startsWith("OK:DONE"))
++ {
++ LOG(VB_CHANNEL, LOG_INFO, LOC + result);
++ return false;
++ }
++
++ // Expect csv: channum, name, callsign, xmltvid
++ QStringList fields = result.mid(3).split(",");
++
++ if (fields.size() != 4)
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC +
++ QString("Expecting channum, name, callsign, xmltvid; "
++ "Received '%1").arg(result));
++ return false;
++ }
++
++ channum = fields[0];
++ name = fields[1];
++ callsign = fields[2];
++ xmltvid = fields[3];
++
++ return true;
++}
++
++int ExternalRecChannelFetcher::LoadChannels(void)
++{
++ if (!Valid())
++ return false;
++
++ QString result;
++ int cnt = -1;
++
++ if (!m_stream_handler->ProcessCommand("LoadChannels", 50000, result,
++ 10, 100))
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + "LoadChannels command failed.");
++ return -1;
++ }
++
++ if (result.startsWith("FOUND"))
++ cnt = result.mid(6).toInt();
++ else if (result.startsWith("OK"))
++ cnt = result.mid(3).toInt();
++ else
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + QString("LoadChannels:
%1").arg(result));
++ return -1;
++ }
++
++ return cnt;
++}
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
+new file mode 100644
+index 00000000000..61c0f3a6b71
+--- /dev/null
++++ b/mythtv/libs/libmythtv/recorders/ExternalRecChannelFetcher.h
+@@ -0,0 +1,65 @@
++/* -*- Mode: c++ -*-
++ * Class ExternalFetcher
++ *
++ * Copyright (C) John Poet 2018
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++
++// Qt includes
++#include <QString>
++
++class ExternalStreamHandler;
++
++class ExternalRecChannelFetcher
++{
++ public:
++ ExternalRecChannelFetcher(int cardid, const QString & cmd);
++ ~ExternalRecChannelFetcher(void);
++
++ bool Valid(void) const;
++
++ int LoadChannels(void);
++ bool FirstChannel(QString & channum,
++ QString & name,
++ QString & callsign,
++ QString & xmltvid)
++ {
++ return FetchChannel("FirstChannel", channum, name, callsign,
xmltvid);
++ }
++ bool NextChannel(QString & channum,
++ QString & name,
++ QString & callsign,
++ QString & xmltvid)
++ {
++ return FetchChannel("NextChannel", channum, name, callsign, xmltvid);
++ }
++
++
++ protected:
++ void Close(void);
++ bool FetchChannel(const QString & cmd,
++ QString & channum,
++ QString & name,
++ QString & callsign,
++ QString & xmltvid);
++
++
++ private:
++ int m_cardid;
++ QString m_command;
++
++ ExternalStreamHandler *m_stream_handler;
++};
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index c555fca2e10..42a501e9a74 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -58,8 +58,6 @@ ExternIO::ExternIO(const QString & app,
+ }
+
+ m_args = args;
+- if (!m_args.contains("-q"))
+- m_args << "-q";
+ m_args.prepend(m_app.baseName());
+
+ m_status.setString(&m_status_buf);
+@@ -534,9 +532,14 @@ void ExternalStreamHandler::Return(ExternalStreamHandler * &
ref,
+ ExternalStreamHandler
+ */
+
+-ExternalStreamHandler::ExternalStreamHandler(const QString & path) :
+- StreamHandler(path), m_IO(0), m_tsopen(false), m_io_errcnt(0),
+- m_poll_mode(false), m_notify(false), m_replay(true)
++ExternalStreamHandler::ExternalStreamHandler(const QString & path)
++ : StreamHandler(path)
++ , m_IO(NULL)
++ , m_tsopen(false)
++ , m_io_errcnt(0)
++ , m_poll_mode(false)
++ , m_notify(false)
++ , m_replay(true)
+ {
+ setObjectName("ExternSH");
+
+@@ -658,7 +661,7 @@ void ExternalStreamHandler::run(void)
+ // Since we may never need to send the XOFF
+ // command, occationally check to see if the
+ // External recorder needs to report an issue.
+- if (CheckForError())
++ if (CheckForStatus())
+ {
+ buffer.clear();
+ RestartStream();
+@@ -788,7 +791,7 @@ bool ExternalStreamHandler::OpenApp(void)
+ }
+
+ // Gather capabilities
+- if (!ProcessCommand("HasTuner?", 2500, result))
++ if (!ProcessCommand("HasTuner?", 10000, result))
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC +
+ QString("Bad response to 'HasTuner?' -
'%1'").arg(result));
+@@ -836,7 +839,7 @@ bool ExternalStreamHandler::IsAppOpen(void)
+ }
+
+ QString result;
+- return ProcessCommand("Version?", 5000, result);
++ return ProcessCommand("Version?", 10000, result);
+ }
+
+ bool ExternalStreamHandler::IsTSOpen(void)
+@@ -980,13 +983,6 @@ bool ExternalStreamHandler::StartStreaming(void)
+ return false;
+ }
+
+- if (result != "OK:Started")
+- {
+- LOG(VB_GENERAL, LOG_WARNING, LOC +
+- QString("StartStreaming failed: '%1'").arg(result));
+- return false;
+- }
+-
+ LOG(VB_RECORD, LOG_INFO, LOC + "Streaming started");
+ }
+ else
+@@ -1047,14 +1043,6 @@ bool ExternalStreamHandler::StopStreaming(void)
+ return false;
+ }
+
+- if (!result.startsWith("OK:Stopped"))
+- {
+- LOG(VB_GENERAL, LOG_WARNING, LOC +
+- QString("Unexpected response to StopStreaming: '%1'")
+- .arg(result));
+- return false;
+- }
+-
+ PurgeBuffer();
+ LOG(VB_RECORD, LOG_INFO, LOC + "Streaming stopped");
+
+@@ -1062,14 +1050,15 @@ bool ExternalStreamHandler::StopStreaming(void)
+ }
+
+ bool ExternalStreamHandler::ProcessCommand(const QString & cmd, uint timeout,
+- QString & result)
++ QString & result,
++ int retry_cnt, int wait_cnt)
+ {
+ bool okay;
+
+ LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessCommand('%1')")
+ .arg(cmd));
+
+- for (int idx = 0; idx < 10; ++idx)
++ for (int idx = 0; idx < retry_cnt; ++idx)
+ {
+ QMutexLocker locker(&m_IO_lock);
+
+@@ -1096,7 +1085,7 @@ bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
uint timeout,
+ /* Send new query */
+ m_IO->Write(buf);
+
+- for (int cnt = 0; cnt < 5; ++cnt)
++ for (int cnt = 0; cnt < wait_cnt; ++cnt)
+ {
+ result = m_IO->GetStatus(timeout);
+ if (m_IO->Error())
+@@ -1123,13 +1112,17 @@ bool ExternalStreamHandler::ProcessCommand(const QString &
cmd, uint timeout,
+ okay = result.startsWith("OK");
+ if (okay || result.startsWith("WARN") ||
result.startsWith("ERR"))
+ {
++ LogLevel_t level = LOG_INFO;
++
+ m_io_errcnt = 0;
++ if (!okay)
++ level = LOG_WARNING;
++ else if (cmd.startsWith("SendBytes"))
++ level = LOG_DEBUG;
+
+- m_notify |= (!okay || !cmd.startsWith("SendBytes"));
+- LOG(VB_RECORD, m_notify ? LOG_INFO : LOG_DEBUG,
+- LOC + QString("ProcessCommand('%1') =
'%2'")
+- .arg(cmd).arg(result));
+- m_notify = false;
++ LOG(VB_RECORD, level,
++ LOC + QString("ProcessCommand('%1') = '%2'
%3")
++ .arg(cmd).arg(result).arg(okay ? "" : "<--
NOTE"));
+
+ return okay;
+ }
+@@ -1145,13 +1138,14 @@ bool ExternalStreamHandler::ProcessCommand(const QString &
cmd, uint timeout,
+ _error = true;
+ break;
+ }
+- usleep(timeout);
++
++ usleep(timeout > 5000 ? 5000 : timeout);
+ }
+
+ return false;
+ }
+
+-bool ExternalStreamHandler::CheckForError(void)
++bool ExternalStreamHandler::CheckForStatus(void)
+ {
+ QString result;
+ bool err = false;
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+index e841c33d058..0db16c2f7e4 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+@@ -87,12 +87,12 @@ class ExternalStreamHandler : public StreamHandler
+ bool StartStreaming(void);
+ bool StopStreaming(void);
+
+- bool CheckForError(void);
++ bool CheckForStatus(void);
+
+ void PurgeBuffer(void);
+
+- bool ProcessCommand(const QString & cmd, uint timeout,
+- QString & result);
++ bool ProcessCommand(const QString & cmd, uint timeout, QString & result,
++ int retry_cnt = 10, int wait_cnt = 5);
+
+ private:
+ int StreamingCount(void) const;
+diff --git a/mythtv/libs/libmythtv/scanwizard.cpp b/mythtv/libs/libmythtv/scanwizard.cpp
+index 3ede79b058d..6f988577dd6 100644
+--- a/mythtv/libs/libmythtv/scanwizard.cpp
++++ b/mythtv/libs/libmythtv/scanwizard.cpp
+@@ -111,6 +111,11 @@ void ScanWizard::Scan()
+ DoFreeToAirOnly(),
+ GetServiceRequirements());
+ }
++ else if (scantype == ScanTypeSetting::ExternRecImport)
++ {
++ do_scan = false;
++ scannerPane->ImportExternRecorder(cardid, inputname, sourceid);
++ }
+ else if (scantype == ScanTypeSetting::IPTVImportMPTS)
+ {
+ scannerPane->ImportM3U(cardid, inputname, sourceid, true);
+@@ -134,7 +139,7 @@ void ScanWizard::Scan()
+ ChannelImporter ci(true, true, true, true, false,
+ DoFreeToAirOnly(),
+ GetServiceRequirements());
+- ci.Process(transports);
++ ci.Process(transports, sourceid);
+ }
+ else
+ {
+
+From 5a709647abeb6e5d70f4ab86276711cd3fc3619f Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Wed, 24 Oct 2018 13:42:36 -0600
+Subject: [PATCH 44/52] Add a mythexternrecorder application which in turn can
+ invoke any system command to generate the transport stream.
+
+(cherry picked from commit 826d57e3517da912371c18f2ea51521d7ec0cc7b)
+(cherry picked from commit f46e781a04ec2ac345dbf7ceda08ae91362d9081)
+(cherry picked from commit 130b0667ca0745c90091391b64ca0c24a905b68a)
+(cherry picked from commit d8a3e6a75680ced79a97ca4f758a07019cea0f63)
+(cherry picked from commit 34b24c100787f02be5286f293d369e9b28505c62)
+---
+ mythtv/libs/libmythbase/mythcorecontext.h | 1 +
+ .../recorders/ExternalStreamHandler.cpp | 1 +
+ .../mythexternrecorder/MythExternControl.cpp | 558 ++++++++++++++++++
+ .../mythexternrecorder/MythExternControl.h | 178 ++++++
+ .../mythexternrecorder/MythExternRecApp.cpp | 558 ++++++++++++++++++
+ .../mythexternrecorder/MythExternRecApp.h | 119 ++++
+ .../mythexternrecorder/commandlineparser.cpp | 39 ++
+ .../mythexternrecorder/commandlineparser.h | 13 +
+ .../external-ffmpeg-channels.conf | 11 +
+ .../mythexternrecorder/external-ffmpeg.conf | 31 +
+ .../external-twitch-channels.conf | 95 +++
+ .../mythexternrecorder/external-twitch.conf | 32 +
+ .../external-vlc-channels.conf | 14 +
+ .../mythexternrecorder/external-vlc.conf | 32 +
+ mythtv/programs/mythexternrecorder/main.cpp | 135 +++++
+ .../mythexternrecorder/mythexternrecorder.pro | 31 +
+ mythtv/programs/programs.pro | 1 +
+ 17 files changed, 1849 insertions(+)
+ create mode 100644 mythtv/programs/mythexternrecorder/MythExternControl.cpp
+ create mode 100644 mythtv/programs/mythexternrecorder/MythExternControl.h
+ create mode 100644 mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+ create mode 100644 mythtv/programs/mythexternrecorder/MythExternRecApp.h
+ create mode 100644 mythtv/programs/mythexternrecorder/commandlineparser.cpp
+ create mode 100644 mythtv/programs/mythexternrecorder/commandlineparser.h
+ create mode 100644 mythtv/programs/mythexternrecorder/external-ffmpeg-channels.conf
+ create mode 100644 mythtv/programs/mythexternrecorder/external-ffmpeg.conf
+ create mode 100644 mythtv/programs/mythexternrecorder/external-twitch-channels.conf
+ create mode 100644 mythtv/programs/mythexternrecorder/external-twitch.conf
+ create mode 100644 mythtv/programs/mythexternrecorder/external-vlc-channels.conf
+ create mode 100644 mythtv/programs/mythexternrecorder/external-vlc.conf
+ create mode 100644 mythtv/programs/mythexternrecorder/main.cpp
+ create mode 100644 mythtv/programs/mythexternrecorder/mythexternrecorder.pro
+
+diff --git a/mythtv/libs/libmythbase/mythcorecontext.h
b/mythtv/libs/libmythbase/mythcorecontext.h
+index 6fb6adc9b98..0b3def62264 100644
+--- a/mythtv/libs/libmythbase/mythcorecontext.h
++++ b/mythtv/libs/libmythbase/mythcorecontext.h
+@@ -32,6 +32,7 @@
+ #define MYTH_APPNAME_MYTHLOGSERVER "mythlogserver"
+ #define MYTH_APPNAME_MYTHSCREENWIZARD "mythscreenwizard"
+ #define MYTH_APPNAME_MYTHFFPROBE "mythffprobe"
++#define MYTH_APPNAME_MYTHEXTERNRECORDER "mythexternrecorder"
+
+ class MDBManager;
+ class MythCoreContextPrivate;
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index 42a501e9a74..adfdfd88e29 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -1,6 +1,7 @@
+ // -*- Mode: c++ -*-
+
+ // POSIX headers
++#include <thread>
+ #include <iostream>
+ #include <fcntl.h>
+ #include <unistd.h>
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+new file mode 100644
+index 00000000000..68e08c0c8d2
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+@@ -0,0 +1,558 @@
++/* -*- Mode: c++ -*-
++ *
++ * Copyright (C) John Poet 2018
++ *
++ * This file is part of MythTV
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <
http://www.gnu.org/licenses/>.
++ */
++
++#include "MythExternControl.h"
++#include "mythlogging.h"
++
++#include <QFile>
++#include <QTextStream>
++
++#include <unistd.h>
++#include <poll.h>
++
++#include <iostream>
++
++using namespace std;
++
++const QString VERSION = "0.1";
++
++#define LOC Desc()
++
++MythExternControl::MythExternControl(void)
++ : m_buffer(this)
++ , m_commands(this)
++ , m_run(true)
++ , m_commands_running(true)
++ , m_buffer_running(true)
++ , m_fatal(false)
++ , m_streaming(false)
++ , m_xon(false)
++ , m_ready(false)
++{
++ setObjectName("Control");
++
++ m_buffer.Start();
++ m_commands.Start();
++}
++
++MythExternControl::~MythExternControl(void)
++{
++ Terminate();
++ m_commands.Join();
++ m_buffer.Join();
++}
++
++Q_SLOT void MythExternControl::Opened(void)
++{
++ std::lock_guard<std::mutex> lock(m_flow_mutex);
++
++ m_ready = true;
++ m_flow_cond.notify_all();
++}
++
++Q_SLOT void MythExternControl::Streaming(bool val)
++{
++ m_streaming = val;
++ m_flow_cond.notify_all();
++}
++
++void MythExternControl::Terminate(void)
++{
++ emit Close();
++}
++
++Q_SLOT void MythExternControl::Done(void)
++{
++ if (m_commands_running || m_buffer_running)
++ {
++ m_run = false;
++ m_flow_cond.notify_all();
++ m_run_cond.notify_all();
++
++ std::this_thread::sleep_for(std::chrono::microseconds(50));
++
++ while (m_commands_running || m_buffer_running)
++ {
++ std::unique_lock<std::mutex> lk(m_flow_mutex);
++ m_flow_cond.wait_for(lk, std::chrono::milliseconds(1000));
++ }
++
++ LOG(VB_RECORD, LOG_CRIT, LOC + "Terminated.");
++ }
++}
++
++void MythExternControl::Error(const QString & msg)
++{
++ LOG(VB_RECORD, LOG_CRIT, LOC + msg);
++
++ std::unique_lock<std::mutex> lk(m_msg_mutex);
++ if (m_errmsg.isEmpty())
++ m_errmsg = msg;
++ else
++ m_errmsg += "; " + msg;
++}
++
++void MythExternControl::Fatal(const QString & msg)
++{
++ Error(msg);
++ m_fatal = true;
++ Terminate();
++}
++
++Q_SLOT void MythExternControl::SendMessage(const QString & cmd,
++ const QString & msg)
++{
++ std::unique_lock<std::mutex> lk(m_msg_mutex);
++ m_commands.SendStatus(cmd, msg);
++}
++
++Q_SLOT void MythExternControl::ErrorMessage(const QString & msg)
++{
++ std::unique_lock<std::mutex> lk(m_msg_mutex);
++ if (m_errmsg.isEmpty())
++ m_errmsg = msg;
++ else
++ m_errmsg += "; " + msg;
++}
++
++#undef LOC
++#define LOC QString("%1").arg(m_parent->Desc())
++
++Commands::Commands(MythExternControl * parent)
++ : m_thread()
++ , m_parent(parent)
++{
++}
++
++Commands::~Commands(void)
++{
++}
++
++void Commands::Close(void)
++{
++ std::lock_guard<std::mutex> lock(m_parent->m_flow_mutex);
++
++ emit m_parent->Close();
++ m_parent->m_ready = false;
++ m_parent->m_flow_cond.notify_all();
++}
++
++void Commands::StartStreaming(void)
++{
++ emit m_parent->StartStreaming();
++}
++
++void Commands::StopStreaming(bool silent)
++{
++ emit m_parent->StopStreaming(silent);
++}
++
++void Commands::LockTimeout(void) const
++{
++ emit m_parent->LockTimeout();
++}
++
++void Commands::HasTuner(void) const
++{
++ emit m_parent->HasTuner();
++}
++
++void Commands::HasPictureAttributes(void) const
++{
++ emit m_parent->HasPictureAttributes();
++}
++
++void Commands::SetBlockSize(int blksz)
++{
++ emit m_parent->SetBlockSize(blksz);
++}
++
++void Commands::TuneChannel(const QString & channum)
++{
++ emit m_parent->TuneChannel(channum);
++}
++
++void Commands::LoadChannels(void)
++{
++ emit m_parent->LoadChannels();
++}
++
++void Commands::FirstChannel(void)
++{
++ emit m_parent->FirstChannel();
++}
++
++void Commands::NextChannel(void)
++{
++ emit m_parent->NextChannel();
++}
++
++bool Commands::SendStatus(const QString & command, const QString & status)
++{
++ int len = write(2, status.toUtf8().constData(), status.size());
++ write(2, "\n", 1);
++
++ if (len != static_cast<int>(status.size()))
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ QString("%1: Only wrote %2 of %3 bytes of status '%4'.")
++ .arg(command).arg(len).arg(status.size()).arg(status));
++ return false;
++ }
++
++ if (!command.isEmpty())
++ {
++ LOG(VB_RECORD, LOG_INFO, LOC + QString("Processing '%1' -->
'%2'")
++ .arg(command).arg(status));
++ }
++ else
++ LOG(VB_RECORD, LOG_INFO, LOC + QString("%1").arg(status));
++
++ m_parent->ClearError();
++ return true;
++}
++
++bool Commands::ProcessCommand(const QString & cmd)
++{
++ LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Processing
'%1'").arg(cmd));
++
++ std::unique_lock<std::mutex> lk(m_parent->m_msg_mutex);
++
++ if (cmd.startsWith("Version?"))
++ {
++ if (m_parent->m_fatal)
++ SendStatus(cmd, "ERR:" + m_parent->ErrorString());
++ else
++ SendStatus(cmd, "OK:" + VERSION);
++ }
++ else if (cmd.startsWith("HasLock?"))
++ {
++ if (m_parent->m_ready)
++ SendStatus(cmd, "OK:Yes");
++ else
++ SendStatus(cmd, "OK:No");
++ }
++ else if (cmd.startsWith("SignalStrengthPercent"))
++ {
++ if (m_parent->m_ready)
++ SendStatus(cmd, "OK:100");
++ else
++ SendStatus(cmd, "OK:20");
++ }
++ else if (cmd.startsWith("LockTimeout"))
++ {
++ LockTimeout();
++ }
++ else if (cmd.startsWith("HasTuner"))
++ {
++ HasTuner();
++ }
++ else if (cmd.startsWith("HasPictureAttributes"))
++ {
++ HasPictureAttributes();
++ }
++ else if (cmd.startsWith("SendBytes"))
++ {
++ // Used when FlowControl is Polling
++ SendStatus(cmd, "ERR:Not supported");
++ }
++ else if (cmd.startsWith("XON"))
++ {
++ // Used when FlowControl is XON/XOFF
++ SendStatus(cmd, "OK");
++ m_parent->m_xon = true;
++ m_parent->m_flow_cond.notify_all();
++ }
++ else if (cmd.startsWith("XOFF"))
++ {
++ SendStatus(cmd, "OK");
++ // Used when FlowControl is XON/XOFF
++ m_parent->m_xon = false;
++ m_parent->m_flow_cond.notify_all();
++ }
++ else if (cmd.startsWith("TuneChannel"))
++ {
++ TuneChannel(cmd.mid(12));
++ }
++ else if (cmd.startsWith("LoadChannels"))
++ {
++ LoadChannels();
++ }
++ else if (cmd.startsWith("FirstChannel"))
++ {
++ FirstChannel();
++ }
++ else if (cmd.startsWith("NextChannel"))
++ {
++ NextChannel();
++ }
++ else if (cmd.startsWith("IsOpen?"))
++ {
++ std::unique_lock<std::mutex> lk(m_parent->m_run_mutex);
++ if (m_parent->m_fatal)
++ SendStatus(cmd, "ERR:" + m_parent->ErrorString());
++ else if (m_parent->m_ready)
++ SendStatus(cmd, "OK:Open");
++ else
++ SendStatus(cmd, "WARN:Not Open yet");
++ }
++ else if (cmd.startsWith("CloseRecorder"))
++ {
++ if (m_parent->m_streaming)
++ StopStreaming(true);
++ m_parent->Terminate();
++ SendStatus(cmd, "OK:Terminating");
++ }
++ else if (cmd.startsWith("FlowControl?"))
++ {
++ SendStatus(cmd, "OK:XON/XOFF");
++ }
++ else if (cmd.startsWith("BlockSize"))
++ {
++ SetBlockSize(cmd.mid(10).toInt());
++ }
++ else if (cmd.startsWith("StartStreaming"))
++ {
++ StartStreaming();
++ }
++ else if (cmd.startsWith("StopStreaming"))
++ {
++ /* This does not close the stream! When Myth is done with
++ * this 'recording' ExternalChannel::EnterPowerSavingMode()
++ * will be called, which invokes CloseRecorder() */
++ StopStreaming(false);
++ }
++ else
++ SendStatus(cmd, "ERR:Unrecognized command");
++
++ return true;
++}
++
++void Commands::Run(void)
++{
++ setObjectName("Commands");
++
++ QString cmd;
++ int timeout = 250;
++
++ int ret;
++ int poll_cnt = 1;
++ struct pollfd polls[2];
++ memset(polls, 0, sizeof(polls));
++
++ polls[0].fd = 0;
++ polls[0].events = POLLIN | POLLPRI;
++ polls[0].revents = 0;
++
++ QFile input;
++ input.open(stdin, QIODevice::ReadOnly);
++ QTextStream qtin(&input);
++
++ LOG(VB_RECORD, LOG_INFO, LOC + "Command parser ready.");
++
++ while (m_parent->m_run)
++ {
++ ret = poll(polls, poll_cnt, timeout);
++
++ if (polls[0].revents & POLLHUP)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + "poll eof (POLLHUP)");
++ break;
++ }
++ else if (polls[0].revents & POLLNVAL)
++ {
++ m_parent->Fatal("poll error");
++ return;
++ }
++
++ if (polls[0].revents & POLLIN)
++ {
++ if (ret > 0)
++ {
++ cmd = qtin.readLine();
++ if (!ProcessCommand(cmd))
++ m_parent->Fatal("Invalid command");
++ }
++ else if (ret < 0)
++ {
++ if ((EOVERFLOW == errno))
++ {
++ LOG(VB_RECORD, LOG_ERR, "command overflow");
++ break; // we have an error to handle
++ }
++
++ if ((EAGAIN == errno) || (EINTR == errno))
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + "retry command read.");
++ continue; // errors that tell you to try again
++ }
++
++ LOG(VB_RECORD, LOG_ERR, LOC + "unknown error reading
command.");
++ }
++ }
++ }
++
++ LOG(VB_RECORD, LOG_INFO, LOC + "Command parser: shutting down");
++ m_parent->m_commands_running = false;
++ m_parent->m_flow_cond.notify_all();
++}
++
++Buffer::Buffer(MythExternControl * parent)
++ : m_parent(parent), m_thread()
++{
++ m_heartbeat = std::chrono::system_clock::now();
++}
++
++Buffer::~Buffer(void)
++{
++}
++
++bool Buffer::Fill(const QByteArray & buffer)
++{
++ if (buffer.size() < 1)
++ return false;
++
++ static int dropped = 0;
++
++ m_parent->m_flow_mutex.lock();
++ if (m_data.size() < MAX_QUEUE)
++ {
++ block_t blk(reinterpret_cast<const uint8_t *>(buffer.constData()),
++ reinterpret_cast<const uint8_t *>(buffer.constData())
++ + buffer.size());
++
++ m_data.push(blk);
++ dropped = 0;
++
++ LOG(VB_GENERAL, LOG_DEBUG, LOC +
++ QString("Adding %1 bytes").arg(buffer.size()));
++ }
++ else
++ {
++ LOG(VB_RECORD, LOG_WARNING, LOC +
++ QString("Packet queue overrun. Dropped %1 packets.")
++ .arg(++dropped));
++ }
++
++ m_parent->m_flow_mutex.unlock();
++ m_parent->m_flow_cond.notify_all();
++
++ m_heartbeat = std::chrono::system_clock::now();
++
++ return true;
++}
++
++void Buffer::Run(void)
++{
++ setObjectName("Buffer");
++
++ bool is_empty = false;
++ bool wait = false;
++ time_t send_time = time (NULL) + (60 * 5);
++ uint64_t write_total = 0;
++ uint64_t written = 0;
++ uint64_t write_cnt = 0;
++ uint64_t empty_cnt = 0;
++ uint sz;
++
++ LOG(VB_RECORD, LOG_INFO, LOC + "Buffer: Ready for data.");
++
++ while (m_parent->m_run)
++ {
++ {
++ std::unique_lock<std::mutex> lk(m_parent->m_flow_mutex);
++ m_parent->m_flow_cond.wait_for(lk,
++ std::chrono::milliseconds
++ (wait ? 5000 : 25));
++ wait = false;
++ }
++
++ if (send_time < static_cast<double>(time (NULL)))
++ {
++ // Every 5 minutes, write out some statistics.
++ send_time = time (NULL) + (60 * 5);
++ write_total += written;
++ if (m_parent->m_streaming)
++ LOG(VB_RECORD, LOG_NOTICE, LOC +
++ QString("Count: %1, Empty cnt %2, Written %3, Total %4")
++ .arg(write_cnt).arg(empty_cnt)
++ .arg(written).arg(write_total));
++ else
++ LOG(VB_GENERAL, LOG_NOTICE, LOC + "Not streaming.");
++
++ write_cnt = empty_cnt = written = 0;
++ }
++
++ if (m_parent->m_streaming)
++ {
++ if (m_parent->m_xon)
++ {
++ block_t pkt;
++ m_parent->m_flow_mutex.lock();
++ if (!m_data.empty())
++ {
++ pkt = m_data.front();
++ m_data.pop();
++ is_empty = m_data.empty();
++ }
++ m_parent->m_flow_mutex.unlock();
++
++ if (!pkt.empty())
++ {
++ sz = write(1, pkt.data(), pkt.size());
++ written += sz;
++ ++write_cnt;
++
++ if (sz != pkt.size())
++ {
++ LOG(VB_GENERAL, LOG_WARNING, LOC +
++ QString("Only wrote %1 of %2 bytes to
mythbackend")
++ .arg(sz).arg(pkt.size()));
++ }
++ }
++
++ if (is_empty)
++ {
++ wait = true;
++ ++empty_cnt;
++ }
++ }
++ else
++ wait = true;
++ }
++ else
++ {
++ // Clear packet queue
++ m_parent->m_flow_mutex.lock();
++ if (!m_data.empty())
++ {
++ stack_t dummy;
++ std::swap(m_data, dummy);
++ }
++ m_parent->m_flow_mutex.unlock();
++
++ wait = true;
++ }
++ }
++
++ LOG(VB_RECORD, LOG_INFO, LOC + "Buffer: shutting down");
++ m_parent->m_buffer_running = false;
++ m_parent->m_flow_cond.notify_all();
++}
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.h
b/mythtv/programs/mythexternrecorder/MythExternControl.h
+new file mode 100644
+index 00000000000..b3af726e0ac
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.h
+@@ -0,0 +1,178 @@
++/* -*- Mode: c++ -*-
++ *
++ * Copyright (C) John Poet 2018
++ *
++ * This file is part of MythTV
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <
http://www.gnu.org/licenses/>.
++ */
++
++#ifndef _MythExternControl_H_
++#define _MythExternControl_H_
++
++#include "MythExternRecApp.h"
++
++#include <atomic>
++#include <vector>
++#include <queue>
++#include <condition_variable>
++#include <thread>
++
++#include <QString>
++
++class MythExternControl;
++
++class Buffer : QObject
++{
++ Q_OBJECT
++
++ public:
++ enum constants {MAX_QUEUE = 500};
++
++ Buffer(MythExternControl * parent);
++ ~Buffer(void);
++ void Start(void) {
++ m_thread = std::thread(&Buffer::Run, this);
++ }
++ void Join(void) {
++ if (m_thread.joinable())
++ m_thread.join();
++ }
++ bool Fill(const QByteArray & buffer);
++
++ std::chrono::time_point<std::chrono::system_clock> HeartBeat(void) const
++ { return m_heartbeat; }
++
++ protected:
++ void Run(void);
++
++ private:
++ using block_t = std::vector<uint8_t>;
++ using stack_t = std::queue<block_t>;
++
++ MythExternControl* m_parent;
++
++ std::thread m_thread;
++
++ stack_t m_data;
++
++ std::chrono::time_point<std::chrono::system_clock> m_heartbeat;
++};
++
++class Commands : public QObject
++{
++ Q_OBJECT
++
++ public:
++ Commands(MythExternControl * parent);
++ ~Commands(void);
++ void Start(void) {
++ m_thread = std::thread(&Commands::Run, this);
++ }
++ void Join(void) {
++ if (m_thread.joinable())
++ m_thread.join();
++ }
++
++ bool SendStatus(const QString & cmd, const QString & status);
++ bool ProcessCommand(const QString & cmd);
++
++ protected:
++ void Run(void);
++ bool Open(void);
++ void Close(void);
++ void StartStreaming(void);
++ void StopStreaming(bool silent);
++ void LockTimeout(void) const;
++ void HasTuner(void) const;
++ void HasPictureAttributes(void) const;
++ void SetBlockSize(int blksz);
++ void TuneChannel(const QString & channum);
++ void LoadChannels(void);
++ void FirstChannel(void);
++ void NextChannel(void);
++
++ private:
++ std::thread m_thread;
++
++ MythExternControl* m_parent;
++};
++
++class MythExternControl : public QObject
++{
++ Q_OBJECT
++
++ friend class Buffer;
++ friend class Commands;
++
++ public:
++ MythExternControl(void);
++ ~MythExternControl(void);
++
++ QString Desc(void) const { return QString("%1: ").arg(m_desc); }
++
++ void Terminate(void);
++
++ void Error(const QString & msg);
++ void Fatal(const QString & msg);
++
++ QString ErrorString(void) const { return m_errmsg; }
++ void ClearError(void) { m_errmsg.clear(); }
++
++ signals:
++ void Open(void);
++ void Close(void);
++ void StartStreaming(void);
++ void StopStreaming(bool silent);
++ void LockTimeout(void) const;
++ void HasTuner(void) const;
++ void HasPictureAttributes(void) const;
++ void SetBlockSize(int blksz);
++ void TuneChannel(const QString & channum);
++ void LoadChannels(void);
++ void FirstChannel(void);
++ void NextChannel(void);
++
++ public slots:
++ void SetDescription(const QString & desc) { m_desc = desc; }
++ void SendMessage(const QString & command, const QString & msg);
++ void ErrorMessage(const QString & msg);
++ void Opened(void);
++ void Done(void);
++ void Streaming(bool val);
++ void Fill(const QByteArray & buffer) { m_buffer.Fill(buffer); }
++
++ protected:
++ Buffer m_buffer;
++ Commands m_commands;
++ QString m_desc;
++
++ std::atomic<bool> m_run;
++ std::atomic<bool> m_commands_running;
++ std::atomic<bool> m_buffer_running;
++ std::mutex m_run_mutex;
++ std::condition_variable m_run_cond;
++ std::mutex m_msg_mutex;
++
++ bool m_fatal;
++ QString m_errmsg;
++
++ std::mutex m_flow_mutex;
++ std::condition_variable m_flow_cond;
++ std::atomic<bool> m_streaming;
++ std::atomic<bool> m_xon;
++ std::atomic<bool> m_ready;
++};
++
++#endif
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+new file mode 100644
+index 00000000000..1f5af94d54c
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -0,0 +1,558 @@
++/* -*- Mode: c++ -*-
++ *
++ * Copyright (C) John Poet 2018
++ *
++ * This file is part of MythTV
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <
http://www.gnu.org/licenses/>.
++ */
++
++#include <thread>
++#include "commandlineparser.h"
++#include "MythExternRecApp.h"
++
++#include <QtCore/QtCore>
++#include <QFileInfo>
++#include <QProcess>
++
++#define LOC Desc()
++
++MythExternRecApp::MythExternRecApp(const QString & command,
++ const QString & conf_file)
++ : QObject()
++ , m_fatal(false)
++ , m_run(true)
++ , m_result(0)
++ , m_buffer_max(188 * 10000)
++ , m_block_size(m_buffer_max / 4)
++ , m_rec_command(command)
++ , m_lock_timeout(0)
++ , m_scan_timeout(120000)
++ , m_config_ini(conf_file)
++ , m_tuned(false)
++ , m_chan_settings(nullptr)
++ , m_channel_idx(-1)
++{
++ if (m_config_ini.isEmpty() || !config())
++ m_rec_desc = m_rec_command;
++
++ if (m_tune_command.isEmpty())
++ m_command = m_rec_command;
++
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ QString("Channels in '%1', Tuner: '%2', Scanner:
'%3'")
++ .arg(m_channels_ini).arg(m_tune_command).arg(m_scan_command));
++
++ m_desc = m_rec_desc;
++ m_desc.replace("%URL%", "");
++ m_desc.replace("%CHANNUM%", "");
++ m_desc.replace("%CHANNAME%", "");
++ m_desc.replace("%CALLSIGN%", "");
++ emit SetDescription(m_desc);
++}
++
++MythExternRecApp::~MythExternRecApp(void)
++{
++ Close();
++}
++
++QString MythExternRecApp::Desc(void) const
++{
++ QString extra;
++
++ if (m_proc.processId() > 0)
++ extra = QString("(pid %1) ").arg(m_proc.processId());
++
++ return QString("%1%2 ").arg(extra).arg(m_desc);
++}
++
++bool MythExternRecApp::config(void)
++{
++ QSettings settings(m_config_ini, QSettings::IniFormat);
++
++ if (!settings.contains("RECORDER/command"))
++ {
++ LOG(VB_GENERAL, LOG_CRIT, "ini file missing [RECORDER]/command");
++ m_fatal = true;
++ return false;
++ }
++
++ m_rec_command = settings.value("RECORDER/command").toString();
++ m_rec_desc = settings.value("RECORDER/desc").toString();
++ m_tune_command = settings.value("TUNER/command",
"").toString();
++ m_channels_ini = settings.value("TUNER/channels",
"").toString();
++ m_lock_timeout = settings.value("TUNER/timeout", "").toInt();
++ m_scan_command = settings.value("SCANNER/command",
"").toString();
++ m_scan_timeout = settings.value("SCANNER/timeout", "").toInt();
++
++ if (!m_channels_ini.isEmpty())
++ {
++ if (!QFileInfo::exists(m_channels_ini))
++ {
++ // Assume the channels config is in the same directory as main config
++ QDir conf_path = QFileInfo(m_config_ini).absolutePath();
++ QFileInfo ini(conf_path, m_channels_ini);
++ m_channels_ini = ini.absoluteFilePath();
++ }
++ }
++
++ return true;
++}
++
++bool MythExternRecApp::Open(void)
++{
++ if (m_fatal)
++ {
++ emit SendMessage("Open", "ERR:Already dead.");
++ return false;
++ }
++
++ if (m_command.isEmpty())
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": No recorder provided.");
++ emit SendMessage("Open", "ERR:No recorder provided.");
++ return false;
++ }
++
++ QObject::connect(&m_proc, &QProcess::started, this,
++ &MythExternRecApp::ProcStarted);
++#if 0
++ QObject::connect(&m_proc, &QProcess::readyReadStandardOutput, this,
++ &MythExternRecApp::ProcReadStandardOutput);
++#endif
++ QObject::connect(&m_proc, &QProcess::readyReadStandardError, this,
++ &MythExternRecApp::ProcReadStandardError);
++
++#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
++
qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
++ QObject::connect(&m_proc,
++ static_cast<void (QProcess::*)(QProcess::ProcessError)>
++ (&QProcess::errorOccurred),
++ this, &MythExternRecApp::ProcError);
++#endif
++
++ qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
++ QObject::connect(&m_proc,
++ static_cast<void (QProcess::*)(int,QProcess::ExitStatus
exitStatus)>
++ (&QProcess::finished),
++ this, &MythExternRecApp::ProcFinished);
++
++
qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState");
++ QObject::connect(&m_proc, &QProcess::stateChanged, this,
++ &MythExternRecApp::ProcStateChanged);
++
++ LOG(VB_RECORD, LOG_INFO, LOC + ": Opened");
++
++ emit Opened();
++ return true;
++}
++
++Q_SLOT void MythExternRecApp::Close(void)
++{
++ if (m_run)
++ {
++ LOG(VB_RECORD, LOG_INFO, LOC + ": Closing application.");
++ m_run = false;
++ m_run_cond.notify_all();
++ std::this_thread::sleep_for(std::chrono::microseconds(50));
++ }
++
++ if (m_proc.state() == QProcess::Running)
++ {
++ m_proc.closeReadChannel(QProcess::StandardOutput);
++ m_proc.terminate();
++ m_proc.waitForFinished();
++ m_proc.kill();
++ m_proc.waitForFinished();
++ std::this_thread::sleep_for(std::chrono::microseconds(50));
++ }
++
++ emit Done();
++}
++
++void MythExternRecApp::Run(void)
++{
++ QByteArray buf;
++
++ while (m_run)
++ {
++ {
++ std::unique_lock<std::mutex> lk(m_run_mutex);
++ m_run_cond.wait_for(lk, std::chrono::milliseconds(10));
++ }
++
++ if (m_proc.state() == QProcess::Running)
++ {
++ if (m_proc.waitForReadyRead(50))
++ {
++ buf = m_proc.read(m_block_size);
++ if (!buf.isEmpty())
++ emit Fill(buf);
++ }
++ }
++
++ qApp->processEvents();
++ }
++
++ if (m_proc.state() == QProcess::Running)
++ {
++ m_proc.closeReadChannel(QProcess::StandardOutput);
++ m_proc.terminate();
++ m_proc.waitForFinished();
++ m_proc.kill();
++ m_proc.waitForFinished();
++ }
++
++ emit Done();
++}
++
++Q_SLOT void MythExternRecApp::LoadChannels(void)
++{
++ if (m_channels_ini.isEmpty())
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
++ emit SendMessage("LoadChannels", "ERR:No channels
configured.");
++ return;
++ }
++
++ if (!m_scan_command.isEmpty())
++ {
++ QString cmd = m_scan_command;
++ cmd.replace("%CHANCONF%", m_channels_ini);
++
++ QProcess scanner;
++ scanner.start(cmd);
++
++ if (!scanner.waitForStarted())
++ {
++ QString errmsg = QString("Failed to start '%1':
").arg(cmd) + ENO;
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
++ emit SendMessage("LoadChannels",
QString("ERR:%1").arg(errmsg));
++ return;
++ }
++
++ QByteArray buf;
++ QElapsedTimer timer;
++
++ timer.start();
++ while (timer.elapsed() < m_scan_timeout)
++ {
++ if (scanner.waitForReadyRead(50))
++ {
++ buf = scanner.readLine();
++ if (!buf.isEmpty())
++ {
++ LOG(VB_RECORD, LOG_INFO, LOC + ": " + buf);
++ MythLog(buf);
++ }
++ }
++
++ if (scanner.state() != QProcess::Running)
++ break;
++
++ if (scanner.waitForFinished(50 /* msecs */))
++ break;
++ }
++ if (timer.elapsed() >= m_scan_timeout)
++ {
++ QString errmsg = QString("Timedout waiting for
'%1'").arg(cmd);
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
++ emit SendMessage("LoadChannels",
QString("ERR:%1").arg(errmsg));
++ return;
++ }
++ }
++
++ if (m_chan_settings == nullptr)
++ m_chan_settings = new QSettings(m_channels_ini,
++ QSettings::IniFormat);
++ m_chan_settings->sync();
++ m_channels = m_chan_settings->childGroups();
++
++ emit SendMessage("LoadChannels",
QString("OK:%1").arg(m_channels.size()));
++}
++
++void MythExternRecApp::GetChannel(QString func)
++{
++ if (m_channels_ini.isEmpty() || m_channels.size() == 0)
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
++ emit SendMessage("FirstChannel", QString("ERR:No channels
configured."));
++ return;
++ }
++
++ if (m_chan_settings == nullptr)
++ {
++ LOG(VB_CHANNEL, LOG_WARNING, LOC + ": Invalid channel
configuration.");
++ emit SendMessage(func, "ERR:Invalid channel configuration.");
++ return;
++ }
++
++ if (m_channels.size() <= m_channel_idx)
++ {
++ LOG(VB_CHANNEL, LOG_WARNING, LOC + ": No more channels.");
++ emit SendMessage(func, "ERR:No more channels.");
++ return;
++ }
++
++ QString channum = m_channels[m_channel_idx++];
++
++ m_chan_settings->beginGroup(channum);
++
++ QString name = m_chan_settings->value("NAME").toString();
++ QString callsign = m_chan_settings->value("CALLSIGN").toString();
++ QString xmltvid = m_chan_settings->value("XMLTVID").toString();
++
++ m_chan_settings->endGroup();
++
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ QString(": NextChannel
Name:'%1',Callsign:'%2',xmltvid:%3")
++ .arg(name).arg(callsign).arg(xmltvid));
++
++ emit SendMessage(func, QString("OK:%1,%2,%3,%4")
++ .arg(channum).arg(name).arg(callsign).arg(xmltvid));
++}
++
++Q_SLOT void MythExternRecApp::FirstChannel(void)
++{
++ m_channel_idx = 0;
++ GetChannel("FirstChannel");
++}
++
++Q_SLOT void MythExternRecApp::NextChannel(void)
++{
++ GetChannel("NextChannel");
++}
++
++Q_SLOT void MythExternRecApp::TuneChannel(const QString & channum)
++{
++ if (m_channels_ini.isEmpty())
++ {
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
++ emit SendMessage("TuneChannel", "ERR:No channels
configured.");
++ return;
++ }
++
++ QSettings settings(m_channels_ini, QSettings::IniFormat);
++ settings.beginGroup(channum);
++
++ QString url(settings.value("URL").toString());
++
++ if (url.isEmpty())
++ {
++ QString msg = QString("Channel number %1 is missing a URL.")
++ .arg(channum);
++
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + msg);
++
++ emit SendMessage("TuneChannel",
QString("ERR:%1").arg(msg));
++ return;
++ }
++
++ if (!m_tune_command.isEmpty())
++ {
++ // Repalce URL in command and execute it
++ QString tune = m_tune_command;
++ tune.replace("%URL%", url);
++
++ if (system(tune.toUtf8().constData()) != 0)
++ {
++ QString errmsg = QString("'%1' failed: ").arg(tune) +
ENO;
++ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
++ emit SendMessage("TuneChannel",
QString("ERR:%1").arg(errmsg));
++ return;
++ }
++ LOG(VB_CHANNEL, LOG_INFO, LOC +
++ QString(": TuneChannel, ran '%1'").arg(tune));
++ }
++
++ // Replace URL in recorder command
++ m_command = m_rec_command;
++
++ if (!url.isEmpty() && m_command.indexOf("%URL%") >= 0)
++ {
++ m_command.replace("%URL%", url);
++ LOG(VB_CHANNEL, LOG_DEBUG, LOC +
++ QString(": '%URL%' replaced in cmd:
'%1'").arg(m_command));
++ }
++
++ m_desc = m_rec_desc;
++ 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));
++ m_tuned = true;
++
++ emit SetDescription(m_desc);
++ emit SendMessage("TuneChannel", QString("OK:Tunned to
%1").arg(channum));
++}
++
++Q_SLOT void MythExternRecApp::LockTimeout(void)
++{
++ if (!Open())
++ return;
++
++ if (m_lock_timeout > 0)
++ emit SendMessage("LockTimeout",
QString("OK:%1").arg(m_lock_timeout));
++ emit SendMessage("LockTimeout", QString("OK:%1")
++ .arg(m_scan_command.isEmpty() ? 12000 : 120000));
++}
++
++Q_SLOT void MythExternRecApp::HasTuner(void)
++{
++ emit SendMessage("HasTuner", QString("OK:%1")
++ .arg(m_channels_ini.isEmpty() ? "No" :
"Yes"));
++}
++
++Q_SLOT void MythExternRecApp::HasPictureAttributes(void)
++{
++ emit SendMessage("HasPictureAttributes", "OK:No");
++}
++
++Q_SLOT void MythExternRecApp::SetBlockSize(int blksz)
++{
++ m_block_size = blksz;
++ emit SendMessage("BlockSize", "OK");
++}
++
++Q_SLOT void MythExternRecApp::StartStreaming(void)
++{
++ if (!m_tuned && !m_channels_ini.isEmpty())
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": No channel has been tuned");
++ emit SendMessage("StartStreaming", "ERR:No channel has been
tuned");
++ return;
++ }
++
++ if (m_proc.state() == QProcess::Running)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": Application already running");
++ emit SendMessage("StartStreaming", "WARN:Application already
running");
++ return;
++ }
++
++ m_proc.start(m_command, QIODevice::ReadOnly|QIODevice::Unbuffered);
++ m_proc.setTextModeEnabled(false);
++ m_proc.setReadChannel(QProcess::StandardOutput);
++
++ LOG(VB_RECORD, LOG_INFO, LOC + QString(": Starting process '%1'")
++ .arg(m_proc.program()));
++
++ if (!m_proc.waitForStarted())
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to start application.");
++ emit SendMessage("StartStreaming", "ERR:Failed to start
application.");
++ return;
++ }
++
++ LOG(VB_RECORD, LOG_INFO, LOC + QString(": Started process '%1' PID
%2")
++ .arg(m_proc.program()).arg(m_proc.processId()));
++
++ emit Streaming(true);
++ emit SendMessage("StartStreaming", "OK:Streaming Started");
++}
++
++Q_SLOT void MythExternRecApp::StopStreaming(bool silent)
++{
++ if (m_proc.state() == QProcess::Running)
++ {
++ m_proc.terminate();
++ m_proc.waitForFinished();
++ m_proc.kill();
++
++ LOG(VB_RECORD, LOG_INFO, LOC + ": External application terminated.");
++ if (silent)
++ emit SendMessage("StopStreaming", "STATUS:Streaming
Stopped");
++ else
++ emit SendMessage("StopStreaming", "OK:Streaming
Stopped");
++ }
++ else
++ {
++ if (silent)
++ emit SendMessage("StopStreaming", "STATUS:Already not
Streaming");
++ else
++ emit SendMessage("StopStreaming", "WARN:Already not
Streaming");
++ }
++
++ emit Streaming(false);
++}
++
++Q_SLOT void MythExternRecApp::ProcStarted(void)
++{
++ QString msg = QString("Process '%1'
started").arg(m_proc.program());
++ LOG(VB_RECORD, LOG_INFO, LOC + ": " + msg);
++ MythLog(msg);
++}
++
++Q_SLOT void MythExternRecApp::ProcFinished(int exitCode,
++ QProcess::ExitStatus exitStatus)
++{
++ m_result = exitCode;
++ QString msg = QString("Finished: %1 (exit code: %2)")
++ .arg(exitStatus == QProcess::NormalExit ? "OK" :
"Failed")
++ .arg(m_result);
++ LOG(VB_RECORD, LOG_INFO, LOC + ": " + msg);
++ MythLog(msg);
++}
++
++Q_SLOT void MythExternRecApp::ProcStateChanged(QProcess::ProcessState newState)
++{
++ QString msg = "State Changed: ";
++ switch (newState)
++ {
++ case QProcess::NotRunning:
++ msg += "Not running";
++ break;
++ case QProcess::Starting:
++ msg += "Starting ";
++ break;
++ case QProcess::Running:
++ msg += QString("Running PID %1").arg(m_proc.processId());
++ break;
++ }
++ LOG(VB_RECORD, LOG_INFO, LOC + ": " + msg);
++}
++
++Q_SLOT void MythExternRecApp::ProcError(QProcess::ProcessError /*error */)
++{
++ LOG(VB_RECORD, LOG_ERR, LOC + QString(": Error: %1")
++ .arg(m_proc.errorString()));
++ MythLog(m_proc.errorString());
++}
++
++Q_SLOT void MythExternRecApp::ProcReadStandardError(void)
++{
++ MythLog("Application message: see external recorder log");
++
++ // Log any error messages
++ QByteArray buf = m_proc.readAllStandardError();
++ if (!buf.isEmpty())
++ {
++ LOG(VB_RECORD, LOG_INFO, LOC + QString(">>> %1")
++ .arg(QString::fromUtf8(buf).trimmed()));
++ }
++}
++
++Q_SLOT void MythExternRecApp::ProcReadStandardOutput(void)
++{
++ LOG(VB_RECORD, LOG_WARNING, LOC + ": Data ready.");
++
++ QByteArray buf = m_proc.read(m_block_size);
++ if (!buf.isEmpty())
++ emit Fill(buf);
++}
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+new file mode 100644
+index 00000000000..d3e18d566a4
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+@@ -0,0 +1,119 @@
++/* -*- Mode: c++ -*-
++ *
++ * Copyright (C) John Poet 2018
++ *
++ * This file is part of MythTV
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <
http://www.gnu.org/licenses/>.
++ */
++
++#ifndef _MythTVExternRecApp_H_
++#define _MythTVExternRecApp_H_
++
++#include <QObject>
++#include <QtCore/QtCore>
++
++#include <atomic>
++#include <condition_variable>
++
++class MythExternControl;
++
++class MythExternRecApp : public QObject
++{
++ Q_OBJECT
++
++ public:
++ MythExternRecApp(const QString & command, const QString & conf_file);
++ ~MythExternRecApp(void);
++
++ bool Open(void);
++ void Run(void);
++
++ QString Desc(void) const;
++ void MythLog(const QString & msg)
++ { SendMessage("", QString("STATUS:%1").arg(msg)); }
++ void SetErrorMsg(const QString & msg) { emit ErrorMessage(msg); }
++
++ signals:
++ void SetDescription(const QString & desc);
++ void SendMessage(const QString & func, const QString & msg);
++ void ErrorMessage(const QString & msg);
++ void Opened(void);
++ void Done(void);
++ void Streaming(bool val);
++ void Fill(const QByteArray & buffer);
++
++ public slots:
++ void ProcStarted(void);
++ void ProcFinished(int exitCode, QProcess::ExitStatus exitStatus);
++ void ProcStateChanged(QProcess::ProcessState newState);
++ void ProcError(QProcess::ProcessError error);
++ void ProcReadStandardError(void);
++ void ProcReadStandardOutput(void);
++
++ void Close(void);
++ void StartStreaming(void);
++ void StopStreaming(bool silent);
++ void LockTimeout(void);
++ void HasTuner(void);
++ void LoadChannels(void);
++ void FirstChannel(void);
++ void NextChannel(void);
++
++ void TuneChannel(const QString & channum);
++ void HasPictureAttributes(void);
++ void SetBlockSize(int blksz);
++
++ protected:
++ void GetChannel(QString func);
++
++ private:
++ bool config(void);
++
++ bool m_fatal;
++
++ std::atomic<bool> m_run;
++ std::condition_variable m_run_cond;
++ std::mutex m_run_mutex;
++ int m_result;
++
++ uint m_buffer_max;
++ uint m_block_size;
++
++ QProcess m_proc;
++ QString m_command;
++
++ QString m_rec_command;
++ QString m_rec_desc;
++
++ QString m_tune_command;
++ QString m_channels_ini;
++ uint m_lock_timeout;
++
++ QString m_scan_command;
++ uint m_scan_timeout;
++
++ QString m_config_ini;
++ QString m_desc;
++
++ bool m_tuned;
++
++ // Channel scanning
++ QSettings *m_chan_settings;
++ QStringList m_channels;
++ int m_channel_idx;
++
++};
++
++#endif
+diff --git a/mythtv/programs/mythexternrecorder/commandlineparser.cpp
b/mythtv/programs/mythexternrecorder/commandlineparser.cpp
+new file mode 100644
+index 00000000000..43e2af601b7
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/commandlineparser.cpp
+@@ -0,0 +1,39 @@
++#include <QString>
++
++#include "mythcorecontext.h"
++#include "commandlineparser.h"
++
++MythExternRecorderCommandLineParser::MythExternRecorderCommandLineParser() :
++ MythCommandLineParser(MYTH_APPNAME_MYTHEXTERNRECORDER)
++{ LoadArguments(); }
++
++QString MythExternRecorderCommandLineParser::GetHelpHeader(void) const
++{
++ return "MythFileRecorder is a go-between app which interfaces "
++ "between a recording device and mythbackend.";
++}
++
++void MythExternRecorderCommandLineParser::LoadArguments(void)
++{
++ allowArgs();
++ addHelp();
++ addSettingsOverride();
++ addVersion();
++ addLogging();
++
++#if 0
++ add(QStringList(QStringList() << "--conf"),
++ "conf", "", "Path to a configuration file in INI
format.", "")
++ ->SetGroup("ExternalRecorder");
++#else
++ add("--conf", "conf", "", "Path to a
configuration file in INI format.", "")
++ ->SetGroup("ExternalRecorder");
++#endif
++
++ add(QStringList(QStringList() << "--exec"),
++ "exec", false,
++ "Execute a program to retrieve Transport Stream from. "
++ "Data is expected to be on stdout.", "")
++ ->SetGroup("ExternalRecorder");
++
++}
+diff --git a/mythtv/programs/mythexternrecorder/commandlineparser.h
b/mythtv/programs/mythexternrecorder/commandlineparser.h
+new file mode 100644
+index 00000000000..5fe641f7910
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/commandlineparser.h
+@@ -0,0 +1,13 @@
++
++#include <QString>
++
++#include "mythcommandlineparser.h"
++
++class MythExternRecorderCommandLineParser : public MythCommandLineParser
++{
++ public:
++ MythExternRecorderCommandLineParser();
++ void LoadArguments(void);
++ protected:
++ QString GetHelpHeader(void) const;
++};
+diff --git a/mythtv/programs/mythexternrecorder/external-ffmpeg-channels.conf
b/mythtv/programs/mythexternrecorder/external-ffmpeg-channels.conf
+new file mode 100644
+index 00000000000..dc14e3523b1
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/external-ffmpeg-channels.conf
+@@ -0,0 +1,11 @@
++[1]
++NAME=Test Channel 1
++CALLSIGN=TEST1
++XMLTVID=
++URL=/mythtv/1/TV/60004_20181016040000.ts
++
++[2]
++NAME=Test Channel 2
++CALLSIGN=TEST2
++XMLTVID=
++URL=/mythtv/1/TV/50002_20181015070000.ts
+diff --git a/mythtv/programs/mythexternrecorder/external-ffmpeg.conf
b/mythtv/programs/mythexternrecorder/external-ffmpeg.conf
+new file mode 100644
+index 00000000000..afaaf469690
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/external-ffmpeg.conf
+@@ -0,0 +1,31 @@
++[RECORDER]
++# The recorder command to execute. %URL% is optional, and
++# will be replaced with the channel's "URL" as defined in the
++# [TUNER/channels] (channel conf) configuration file
++command=/opt/ffmpeg/bin/ffmpeg -hide_banner -nostats -loglevel panic -re -i
\"%URL%\" -c:v copy -c:a copy -f mpegts -
++
++# Used in logging events
++desc=ffmpeg \"%URL%\" \"%CHANNUM%\" \"%CHANNAME%\"
\"%CALLSIGN%\"
++
++[TUNER]
++# An optional CONF file which provides channel details. If it does not
++# exist, then channel changes are not supported.
++channels=external-ffmpeg-channels.conf
++
++# If [TUNER/command] is provided, it will be executed to "tune" the
++# channel. A %URL% parameter will be substituted with the "URL" as
++# defined in the [TUNER/channels] configuration file
++#command=
++
++# Timeout for changing channels in msecs
++#timeout=
++
++[SCANNER]
++# When MythTV scans for channels, The contents of the [TUNER/channels]
++# config file are used to populate MythTV's channel information.
++# If a command is provided here, it will be executed first, so it can
++# populate the [TUNER/channels] config file
++#command=/home/myth/bin/scan.sh "%CHANCONF%"
++
++# Timeout for scan command in msecs
++#timeout=60000
+diff --git a/mythtv/programs/mythexternrecorder/external-twitch-channels.conf
b/mythtv/programs/mythexternrecorder/external-twitch-channels.conf
+new file mode 100644
+index 00000000000..b9de00eb121
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/external-twitch-channels.conf
+@@ -0,0 +1,95 @@
++[1]
++NAME=Twitch Arcade Cloud
++CALLSIGN=ArcadeCloud
++XMLTVID=
++URL=https://www.twitch.tv/arcadecloud
++
++[2]
++NAME=Twitch Buzzr
++CALLSIGN=Buzzr
++XMLTVID=
++URL=https://www.twitch.tv/buzzr
++
++[3]
++NAME=Twitch Cosmos
++CALLSIGN=Cosmos
++XMLTVID=
++URL=https://www.twitch.tv/cosmos
++
++[4]
++NAME=Twitch Eleven Sports Next
++CALLSIGN=11SportsNext
++XMLTVID=
++URL=https://www.twitch.tv/elevensportsnext
++
++[5]
++NAME=Twitch Fail Army
++CALLSIGN=FailArmy
++XMLTVID=
++URL=https://www.twitch.tv/failarmy
++
++[6]
++NAME=Twitch Junior
++CALLSIGN=Junior
++XMLTVID=
++URL=https://www.twitch.tv/junior
++
++[7]
++NAME=Twitch Lucha Libreaaa
++CALLSIGN=LuchaLibreaa
++XMLTVID=
++URL=https://www.twitch.tv/luchalibreaaa
++
++[8]
++NAME=Twitch Riff Trax
++CALLSIGN=TiffTrax
++XMLTVID=
++URL=https://www.twitch.tv/rifftrax
++
++[9]
++NAME=Twitch Stadium
++CALLSIGN=Stadium
++XMLTVID=
++URL=https://www.twitch.tv/stadium
++
++[10]
++NAME=Twitch Presents
++CALLSIGN=Presents
++XMLTVID=
++URL=https://www.twitch.tv/twitchpresents
++
++[11]
++NAME=Twitch Presents2
++CALLSIGN=Presents2
++XMLTVID=
++URL=https://www.twitch.tv/twitchpresents2
++
++[12]
++NAME=Twitch Presents DE
++CALLSIGN=PresentsDE
++XMLTVID=
++URL=https://www.twitch.tv/twitchpresentsde
++
++[13]
++NAME=Twitch Presents ES
++CALLSIGN=PresentsES
++XMLTVID=
++URL=https://www.twitch.tv/twitchpresentses
++
++[14]
++NAME=Twitch Presents FR
++CALLSIGN=PresentsFR
++XMLTVID=
++URL=https://www.twitch.tv/twitchpresentsfr
++
++[15]
++NAME=Twitch Presents IT
++CALLSIGN=PresentsIT
++XMLTVID=
++URL=https://www.twitch.tv/twitchpresentsit
++
++[16]
++NAME=Twitch Presents PT
++CALLSIGN=PresentsPT
++XMLTVID=
++URL=https://www.twitch.tv/twitchpresentspt
+diff --git a/mythtv/programs/mythexternrecorder/external-twitch.conf
b/mythtv/programs/mythexternrecorder/external-twitch.conf
+new file mode 100644
+index 00000000000..12a45aa0c4e
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/external-twitch.conf
+@@ -0,0 +1,32 @@
++[RECORDER]
++# The recorder command to execute. %URL% is optional, and
++# will be replaced with the channel's "URL" as defined in the
++# [TUNER/channels] (channel conf) configuration file
++
++command="/usr/bin/youtube-dl --hls-use-mpegts --ffmpeg-location /opt/ffmpeg/bin
--external-downloader-args \"-hide_banner -nostats -loglevel panic -re\" -o -
\"%URL%\""
++
++# Used in logging events, %ARG% are replaced from the channel info
++desc=Twitch \"%URL%\" \"%CHANNUM%\" \"%CHANNAME%\"
\"%CALLSIGN%\"
++
++[TUNER]
++# An optional CONF file which provides channel details. If it does not
++# exist, then channel changes are not supported.
++channels=external-twitch-channels.conf
++
++# If [TUNER/command] is provided, it will be executed to "tune" the
++# channel. A %URL% parameter will be substituted with the "URL" as
++# defined in the [TUNER/channels] configuration file
++#command=
++
++# Timeout for changing channels in msecs
++#timeout=
++
++[SCANNER]
++# When MythTV scans for channels, The contents of the [TUNER/channels]
++# config file are used to populate MythTV's channel information.
++# If a command is provided here, it will be executed first, so it can
++# populate the [TUNER/channels] config file
++#command=/home/mythtv/bin/scan.sh "%CHANCONF%"
++
++# Timeout for scan command in msecs
++#timeout=60000
+diff --git a/mythtv/programs/mythexternrecorder/external-vlc-channels.conf
b/mythtv/programs/mythexternrecorder/external-vlc-channels.conf
+new file mode 100644
+index 00000000000..6df049cc977
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/external-vlc-channels.conf
+@@ -0,0 +1,14 @@
++# Channels
++
++[2]
++NAME=Second Channel
++CALLSIGN=Second
++XMLTVID=
++URL="https://manifest.auditude.com/auditude/variant/samsung_pet_collective/aHR0cDovL21hbmlmZXN0Lnd1cmwuY29tL2NoYW5uZWxzL3NhbXN1bmcvdGhlX3BldF9jb2xsZWN0aXZlX2xpbmVhcl8vcGxheWxpc3QubTN1OA==.m3u8?u=19287a324825cc5aacb7e46183c72324&z=268729&g=1000003&k=channel_id=227516;page_url=http%253A%252F%252Fwww.wurl.com%252Fwatch%252Fpet_collective;dnt=0;wurl_channel=91;distributor=samsung&ptcueformat=turner&pttrackingmode=sstm&pttrackingversion=v2&__sid__=abcdeab"
++
++[3]
++NAME=Third channel
++CALLSIGN=Third
++XMLTVID=
++URL=/mythtv/1/TV/1071_20180817030000.ts
++
+diff --git a/mythtv/programs/mythexternrecorder/external-vlc.conf
b/mythtv/programs/mythexternrecorder/external-vlc.conf
+new file mode 100644
+index 00000000000..22edbe94193
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/external-vlc.conf
+@@ -0,0 +1,32 @@
++[RECORDER]
++# The recorder command to execute. %URL% is optional, and
++# will be replaced with the channel's "URL" as defined in the
++# [TUNER/channels] (channel conf) configuration file
++command="cvlc \"%URL%\" --sout
\"#std{mux=ts,access=file,dst=-}\""
++
++# Used in logging events, %ARG% are replaced from the channel info
++desc=cvlc \"%URL%\" \"%CHANNUM%\" \"%CHANNAME%\"
\"%CALLSIGN%\"
++
++[TUNER]
++# An optional CONF file which provides channel details. If it does not
++# exist, then channel changes are not supported.
++channels=external-vlc-channels.conf
++
++# If [TUNER/command] is provided, it will be executed to "tune" the
++# channel. A %URL% parameter will be substituted with the "URL" as
++# defined in the [TUNER/channels] configuration file
++command=
++
++# Timeout for changing channels in msecs
++#timeout=
++
++
++[SCANNER]
++# When MythTV scans for channels, The contents of the [TUNER/channels]
++# config file are used to populate MythTV's channel information.
++# If a command is provided here, it will be executed first, so it can
++# populate the [TUNER/channels] config file
++#command=/home/myth/bin/scan.sh "%CHANCONF%"
++
++# Timeout for scan command in msecs
++#timeout=60000
+diff --git a/mythtv/programs/mythexternrecorder/main.cpp
b/mythtv/programs/mythexternrecorder/main.cpp
+new file mode 100644
+index 00000000000..f190af4578a
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/main.cpp
+@@ -0,0 +1,135 @@
++/* -*- Mode: c++ -*-
++ *
++ * Copyright (C) John Poet 2018
++ *
++ * This file is part of MythTV
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <
http://www.gnu.org/licenses/>.
++ */
++
++#include "commandlineparser.h"
++#include "MythExternRecApp.h"
++#include "MythExternControl.h"
++
++#include "exitcodes.h"
++#include "mythcontext.h"
++#include "mythversion.h"
++#include "mythlogging.h"
++
++#include <unistd.h>
++
++#include <sys/types.h>
++#include <sys/stat.h>
++
++#include <QCoreApplication>
++
++int main(int argc, char *argv[])
++{
++ MythExternRecorderCommandLineParser cmdline;
++
++ if (!cmdline.Parse(argc, argv))
++ {
++ cmdline.PrintHelp();
++ return GENERIC_EXIT_INVALID_CMDLINE;
++ }
++
++ if (cmdline.toBool("showhelp"))
++ {
++ cmdline.PrintHelp();
++ return GENERIC_EXIT_OK;
++ }
++
++ if (cmdline.toBool("showversion"))
++ {
++ cmdline.PrintVersion();
++ return GENERIC_EXIT_OK;
++ }
++
++ QCoreApplication app(argc, argv);
++ QCoreApplication::setApplicationName("mythexternrecorder");
++
++ int retval;
++ if ((retval = cmdline.ConfigureLogging()) != GENERIC_EXIT_OK)
++ return retval;
++
++ MythExternControl *control = new MythExternControl();
++ MythExternRecApp *process = nullptr;
++
++ QString conf_file = cmdline.toString("conf");
++ if (!conf_file.isEmpty())
++ {
++ process = new MythExternRecApp("", conf_file);
++ }
++ else if (!cmdline.toString("exec").isEmpty())
++ {
++ QString command = cmdline.toString("exec");
++ process = new MythExternRecApp(command, "");
++ }
++ else if (!cmdline.toString("infile").isEmpty())
++ {
++ QString filename = cmdline.toString("infile");
++ QString command = QString("ffmpeg -re -i \"%1\" "
++ "-c:v copy -c:a copy -f mpegts -")
++ .arg(filename);
++ process = new MythExternRecApp(command, "");
++ }
++
++ QObject::connect(process, &MythExternRecApp::Opened,
++ control, &MythExternControl::Opened);
++ QObject::connect(process, &MythExternRecApp::Done,
++ control, &MythExternControl::Done);
++ QObject::connect(process, &MythExternRecApp::SetDescription,
++ control, &MythExternControl::SetDescription);
++ QObject::connect(process, &MythExternRecApp::SendMessage,
++ control, &MythExternControl::SendMessage);
++ QObject::connect(process, &MythExternRecApp::ErrorMessage,
++ control, &MythExternControl::ErrorMessage);
++ QObject::connect(process, &MythExternRecApp::Streaming,
++ control, &MythExternControl::Streaming);
++ QObject::connect(process, &MythExternRecApp::Fill,
++ control, &MythExternControl::Fill);
++
++ QObject::connect(control, &MythExternControl::Close,
++ process, &MythExternRecApp::Close);
++ QObject::connect(control, &MythExternControl::StartStreaming,
++ process, &MythExternRecApp::StartStreaming);
++ QObject::connect(control, &MythExternControl::StopStreaming,
++ process, &MythExternRecApp::StopStreaming);
++ QObject::connect(control, &MythExternControl::LockTimeout,
++ process, &MythExternRecApp::LockTimeout);
++ QObject::connect(control, &MythExternControl::HasTuner,
++ process, &MythExternRecApp::HasTuner);
++ QObject::connect(control, &MythExternControl::LoadChannels,
++ process, &MythExternRecApp::LoadChannels);
++ QObject::connect(control, &MythExternControl::FirstChannel,
++ process, &MythExternRecApp::FirstChannel);
++ QObject::connect(control, &MythExternControl::NextChannel,
++ process, &MythExternRecApp::NextChannel);
++ QObject::connect(control, &MythExternControl::TuneChannel,
++ process, &MythExternRecApp::TuneChannel);
++ QObject::connect(control, &MythExternControl::HasPictureAttributes,
++ process, &MythExternRecApp::HasPictureAttributes);
++ QObject::connect(control, &MythExternControl::SetBlockSize,
++ process, &MythExternRecApp::SetBlockSize);
++
++ process->Run();
++
++ delete process;
++ delete control;
++ logStop();
++
++ LOG(VB_GENERAL, LOG_WARNING, "Finished.");
++
++ return GENERIC_EXIT_OK;
++}
+diff --git a/mythtv/programs/mythexternrecorder/mythexternrecorder.pro
b/mythtv/programs/mythexternrecorder/mythexternrecorder.pro
+new file mode 100644
+index 00000000000..4ad1cebee5f
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/mythexternrecorder.pro
+@@ -0,0 +1,31 @@
++include (../../settings.pro)
++include (../../version.pro)
++include ( ../programs-libs.pro )
++
++QT += network xml sql script core
++
++TEMPLATE = app
++CONFIG += thread
++target.files = mythexternrecorder
++target.path = $${PREFIX}/bin
++INSTALLS = target
++
++QMAKE_CLEAN += $(TARGET)
++
++config.path = $${PREFIX}/share/mythtv/
++config.files += ffmpeg-channels.conf ffmpeg.conf
++config.files += twitch-channels.conf twitch.conf
++config.files += vlc-channels.conf vlc.conf
++
++INSTALLS += config
++
++# Input
++HEADERS += commandlineparser.h
++HEADERS += MythExternControl.h
++HEADERS += MythExternRecApp.h
++
++SOURCES += commandlineparser.cpp
++SOURCES += MythExternControl.cpp
++SOURCES += MythExternRecApp.cpp
++SOURCES += main.cpp
++
+diff --git a/mythtv/programs/programs.pro b/mythtv/programs/programs.pro
+index 9cb4a58c995..e6fb20ab821 100644
+--- a/mythtv/programs/programs.pro
++++ b/mythtv/programs/programs.pro
+@@ -21,6 +21,7 @@ using_backend {
+
+ !win32-msvc*:SUBDIRS += scripts
+ !mingw:!win32-msvc*: SUBDIRS += mythfilerecorder
++ !mingw:!win32-msvc*: SUBDIRS += mythexternrecorder
+ }
+
+ using_mythtranscode: SUBDIRS += mythtranscode
+
+From 889b44108f637c5ca7e1a7008417ac5b8c10b4af Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Tue, 16 Oct 2018 13:05:13 -0600
+Subject: [PATCH 45/52] ExternalRecorder: Add API verison 2.
+
+Messages between MythTV and the ExternalRecorder now include a serial
+number, allowing "out of band" messages. If The External Recorder does not
+properly respond to the new APIVersion? message, then version 1 is assumed.
+
+(cherry picked from commit 7c78ee863c27283c6b7eb7284c01cb3512c02a39)
+---
+ .../recorders/ExternalStreamHandler.cpp | 291 +++++++++++++++---
+ .../recorders/ExternalStreamHandler.h | 13 +-
+ .../libs/libmythtv/recorders/recorderbase.cpp | 2 +-
+ .../mythexternrecorder/MythExternControl.cpp | 205 +++++++-----
+ .../mythexternrecorder/MythExternControl.h | 45 +--
+ .../mythexternrecorder/MythExternRecApp.cpp | 113 ++++---
+ .../mythexternrecorder/MythExternRecApp.h | 30 +-
+ 7 files changed, 491 insertions(+), 208 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index adfdfd88e29..8fcf2982de7 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -140,9 +140,12 @@ int ExternIO::Read(QByteArray & buffer, int maxlen, int
timeout)
+ "External Recorder not ready. Giving up.");
+ }
+ else
++ {
+ LOG(VB_RECORD, LOG_WARNING,
+ QString("External Recorder not ready. Will retry
(%1/%2).")
+ .arg(m_errcnt).arg(kMaxErrorCnt));
++ std::this_thread::sleep_for(std::chrono::milliseconds(100));
++ }
+ }
+ else
+ {
+@@ -540,6 +543,8 @@ ExternalStreamHandler::ExternalStreamHandler(const QString &
path)
+ , m_io_errcnt(0)
+ , m_poll_mode(false)
+ , m_notify(false)
++ , m_apiVersion(1)
++ , m_serialNo(0)
+ , m_replay(true)
+ {
+ setObjectName("ExternSH");
+@@ -576,9 +581,10 @@ void ExternalStreamHandler::run(void)
+ QByteArray buffer;
+ uint len, read_len;
+ uint empty_cnt = 0;
+- MythTimer timer;
++ uint restart_cnt = 0;
++ MythTimer status_timer;
+
+- timer.start();
++ status_timer.start();
+
+ RunProlog();
+
+@@ -629,6 +635,16 @@ void ExternalStreamHandler::run(void)
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC + "No data from external app");
+ m_notify = true;
++
++ if (empty_cnt % 5000)
++ {
++ if (restart_cnt++)
++ std::this_thread::sleep_for(std::chrono::seconds(5));
++ if (!RestartStream())
++ _error = true;
++ xon = false;
++ continue;
++ }
+ }
+ }
+ else
+@@ -637,8 +653,29 @@ void ExternalStreamHandler::run(void)
+ m_notify = false;
+ }
+
+- while (read_len = 0, buffer.size() == PACKET_SIZE ||
+- (read_len = m_IO->Read(buffer, PACKET_SIZE - remainder, 100)) > 0)
++ if (xon)
++ {
++ if (status_timer.elapsed() >= 2000)
++ {
++ // Since we may never need to send the XOFF
++ // command, occationally check to see if the
++ // External recorder needs to report an issue.
++ if (CheckForError())
++ {
++ if (restart_cnt++)
++ std::this_thread::sleep_for(std::chrono::seconds(5));
++ if (!RestartStream())
++ _error = true;
++ xon = false;
++ continue;
++ }
++
++ status_timer.restart();
++ }
++ }
++
++ while ((read_len = m_IO->Read(buffer, PACKET_SIZE - remainder, 100)) > 0
||
++ !buffer.isEmpty())
+ {
+ if (m_IO->Error())
+ {
+@@ -653,25 +690,13 @@ void ExternalStreamHandler::run(void)
+ break;
+
+ if (read_len > 0)
++ {
+ empty_cnt = 0;
++ restart_cnt = 0;
++ }
+
+ if (xon)
+ {
+- if (timer.elapsed() >= 2000)
+- {
+- // Since we may never need to send the XOFF
+- // command, occationally check to see if the
+- // External recorder needs to report an issue.
+- if (CheckForStatus())
+- {
+- buffer.clear();
+- RestartStream();
+- xon = false;
+- break;
+- }
+- timer.restart();
+- }
+-
+ if (buffer.size() > TOO_FAST_SIZE)
+ {
+ if (!m_poll_mode)
+@@ -730,11 +755,10 @@ void ExternalStreamHandler::run(void)
+ if (m_IO->Error())
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC +
+- QString("Error from External Recorder: %1")
++ QString("Fatal Error from External Recorder: %1")
+ .arg(m_IO->ErrorString()));
+ CloseApp();
+ _error = true;
+- break;
+ }
+ }
+ LOG(VB_RECORD, LOG_INFO, LOC + "run(): " +
+@@ -784,6 +808,21 @@ bool ExternalStreamHandler::OpenApp(void)
+
+ QString result;
+
++ if (ProcessCommand("APIVersion?", 10000, result))
++ {
++ QStringList tokens = result.split(':', QString::SkipEmptyParts);
++
++ if (tokens.size() > 1)
++ m_apiVersion = tokens[1].toUInt();
++ if (m_apiVersion != 1 && m_apiVersion != 2)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ QString("Bad response to 'APIVersion?' - '%1'.
"
++ "Expecting 1 or 2").arg(result));
++ m_apiVersion = 1;
++ }
++ }
++
+ if (!IsAppOpen())
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC + "Application is not responding.");
+@@ -853,8 +892,7 @@ bool ExternalStreamHandler::IsTSOpen(void)
+ if (!ProcessCommand("IsOpen?", 2500, result))
+ return false;
+
+- if (result.startsWith("OK:Open"))
+- m_tsopen = true;
++ m_tsopen = true;
+ return m_tsopen;
+ }
+
+@@ -870,7 +908,7 @@ void ExternalStreamHandler::CloseApp(void)
+ ProcessCommand("CloseRecorder", 30000, result);
+ m_IO_lock.lock();
+
+- if (!result.startsWith("OK:Terminating"))
++ if (!result.startsWith("OK"))
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC +
+ "CloseRecorder failed, sending kill.");
+@@ -903,17 +941,12 @@ bool ExternalStreamHandler::RestartStream(void)
+ LOG(VB_RECORD, LOG_INFO, LOC + "Restarting stream.");
+
+ if (streaming)
+- {
+- if (!StopStreaming())
+- return false;
+- }
++ StopStreaming();
+
+ usleep(1000000); // 1 second
+
+ if (streaming)
+- {
+ return StartStreaming();
+- }
+
+ return true;
+ }
+@@ -973,14 +1006,15 @@ bool ExternalStreamHandler::StartStreaming(void)
+ {
+ if (!ProcessCommand("StartStreaming", 15000, result))
+ {
+- LOG(VB_GENERAL, LOG_ERR, LOC + QString("StartStreaming failed:
'%1'")
+- .arg(result));
+- if (!result.startsWith("Warn"))
+- {
+- LOG(VB_RECORD, LOG_WARNING, LOC +
+- QString("StartStreaming failed:
'%1'").arg(result));
++ LogLevel_t level = LOG_ERR;
++ if (!result.toLower().startsWith("warn"))
++ level = LOG_WARNING;
++ else
+ _error = true;
+- }
++
++ LOG(VB_GENERAL, level, LOC + QString("StartStreaming failed:
'%1'")
++ .arg(result));
++
+ return false;
+ }
+
+@@ -1033,14 +1067,15 @@ bool ExternalStreamHandler::StopStreaming(void)
+
+ if (!ProcessCommand("StopStreaming", 15000, result))
+ {
+- LOG(VB_GENERAL, LOG_ERR, LOC + QString("StopStreaming: '%1'")
+- .arg(result));
+- if (!result.startsWith("Warn"))
+- {
+- LOG(VB_RECORD, LOG_ERR, LOC +
+- QString("StopStreaming failed: '%1'").arg(result));
++ LogLevel_t level = LOG_ERR;
++ if (!result.toLower().startsWith("warn"))
++ level = LOG_WARNING;
++ else
+ _error = true;
+- }
++
++ LOG(VB_GENERAL, level, LOC + QString("StopStreaming: '%1'")
++ .arg(result));
++
+ return false;
+ }
+
+@@ -1052,14 +1087,30 @@ bool ExternalStreamHandler::StopStreaming(void)
+
+ bool ExternalStreamHandler::ProcessCommand(const QString & cmd, uint timeout,
+ QString & result,
+- int retry_cnt, int wait_cnt)
++ uint retry_cnt, uint wait_cnt)
++{
++ QMutexLocker locker(&m_process_lock);
++
++ if (m_apiVersion == 2)
++ return ProcessVer2(cmd, timeout, result, retry_cnt, wait_cnt);
++ else if (m_apiVersion == 1)
++ return ProcessVer1(cmd, timeout, result, retry_cnt, wait_cnt);
++
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ QString("Invalid API version %1. Expected 1 or
2").arg(m_apiVersion));
++ return false;
++}
++
++bool ExternalStreamHandler::ProcessVer1(const QString & cmd, uint timeout,
++ QString & result,
++ uint retry_cnt, uint wait_cnt)
+ {
+ bool okay;
+
+- LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessCommand('%1')")
++ LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer1('%1')")
+ .arg(cmd));
+
+- for (int idx = 0; idx < retry_cnt; ++idx)
++ for (uint idx = 0; idx < retry_cnt; ++idx)
+ {
+ QMutexLocker locker(&m_IO_lock);
+
+@@ -1086,7 +1137,7 @@ bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
uint timeout,
+ /* Send new query */
+ m_IO->Write(buf);
+
+- for (int cnt = 0; cnt < wait_cnt; ++cnt)
++ for (uint cnt = 0; cnt < wait_cnt; ++cnt)
+ {
+ result = m_IO->GetStatus(timeout);
+ if (m_IO->Error())
+@@ -1146,7 +1197,136 @@ bool ExternalStreamHandler::ProcessCommand(const QString &
cmd, uint timeout,
+ return false;
+ }
+
+-bool ExternalStreamHandler::CheckForStatus(void)
++bool ExternalStreamHandler::ProcessVer2(const QString & command, uint timeout,
++ QString & result,
++ uint retry_cnt, uint wait_cnt)
++{
++ bool okay;
++ bool err;
++ QString status;
++ uint cnt;
++
++ QString cmd = QString("%1:%2").arg(++m_serialNo).arg(command);
++
++ LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer2('%1')
serial(%2)")
++ .arg(cmd).arg(m_serialNo));
++
++ for (uint idx = 0; idx < retry_cnt; ++idx)
++ {
++ QMutexLocker locker(&m_IO_lock);
++
++ if (!m_IO)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
++ return false;
++ }
++
++ QByteArray buf(cmd.toUtf8(), cmd.size());
++ buf += '\n';
++
++ if (m_IO->Error())
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: "
+
++ m_IO->ErrorString());
++ return false;
++ }
++
++ /* Send query */
++ m_IO->Write(buf);
++
++ QStringList tokens;
++
++ for (cnt = 0; cnt < wait_cnt; ++cnt)
++ {
++ result = m_IO->GetStatus(timeout);
++ if (m_IO->Error())
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC +
++ "Failed to read from External Recorder: " +
++ m_IO->ErrorString());
++ _error = true;
++ return false;
++ }
++
++ if (!result.isEmpty())
++ {
++ tokens = result.split(':', QString::SkipEmptyParts);
++
++ // Look for result with the serial number of this query
++ if (tokens.size() > 1 && tokens[0].toUInt() >=
m_serialNo)
++ break;
++
++ // Other messages are "out of band"
++ tokens.removeFirst();
++ result = tokens.join(':');
++ err = (tokens.size() > 1 &&
tokens[1].startsWith("ERR"));
++ LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + result);
++ }
++ }
++
++ if (cnt == wait_cnt)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ QString("ProcessVer2: Giving up waiting for response for "
++ "serial number %1 command '%2'")
++ .arg(m_serialNo).arg(cmd));
++ }
++ else if (tokens.size() < 2)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ QString("Did not receive a valid response "
++ "for command '%1', received
'%2'").arg(cmd).arg(result));
++ }
++ else if (tokens[0].toUInt() > m_serialNo)
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ QString("ProcessVer2: Looking for serial no %1, "
++ "but received %2 for command '%2'")
++ .arg(m_serialNo).arg(tokens[0]).arg(cmd));
++ }
++ else
++ {
++ tokens.removeFirst();
++ status = tokens[0].trimmed();
++ result = tokens.join(':');
++
++ okay = (status == "OK");
++ if (okay || status.startsWith("WARN") ||
status.startsWith("ERR"))
++ {
++ LogLevel_t level = LOG_INFO;
++
++ m_io_errcnt = 0;
++ if (!okay)
++ level = LOG_WARNING;
++ else if (command.startsWith("SendBytes"))
++ level = LOG_DEBUG;
++
++ LOG(VB_RECORD, level,
++ LOC + QString("ProcessCommand('%1') = '%2'
%3")
++ .arg(cmd).arg(result).arg(okay ? "" : "<--
NOTE"));
++
++ return okay;
++ }
++ else
++ LOG(VB_GENERAL, LOG_WARNING, LOC +
++ QString("External Recorder invalid response to '%1':
'%2'")
++ .arg(cmd).arg(result));
++ }
++
++ if (++m_io_errcnt > 10)
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
++ _error = true;
++ break;
++ }
++
++ usleep(timeout > 5000 ? 5000 : timeout);
++ }
++
++ return false;
++}
++
++bool ExternalStreamHandler::CheckForError(void)
+ {
+ QString result;
+ bool err = false;
+@@ -1171,7 +1351,18 @@ bool ExternalStreamHandler::CheckForStatus(void)
+ result = m_IO->GetStatus(0);
+ if (!result.isEmpty())
+ {
+- err |= result.startsWith("STATUS:ERR");
++ if (m_apiVersion > 1)
++ {
++ QStringList tokens = result.split(':',
QString::SkipEmptyParts);
++
++ tokens.removeFirst();
++ result = tokens.join(':');
++ for (int idx = 1; idx < tokens.size(); ++idx)
++ err |= tokens[idx].startsWith("ERR");
++ }
++ else
++ err |= result.startsWith("STATUS:ERR");
++
+ LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + result);
+ }
+ }
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+index 0db16c2f7e4..922beb58d01 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+@@ -19,7 +19,7 @@ class ExternalChannel;
+
+ class ExternIO
+ {
+- enum constants { kMaxErrorCnt = 5 };
++ enum constants { kMaxErrorCnt = 20 };
+
+ public:
+ ExternIO(const QString & app, const QStringList & args);
+@@ -87,12 +87,16 @@ class ExternalStreamHandler : public StreamHandler
+ bool StartStreaming(void);
+ bool StopStreaming(void);
+
+- bool CheckForStatus(void);
++ bool CheckForError(void);
+
+ void PurgeBuffer(void);
+
+ bool ProcessCommand(const QString & cmd, uint timeout, QString & result,
+- int retry_cnt = 10, int wait_cnt = 5);
++ uint retry_cnt = 10, uint wait_cnt = 10);
++ bool ProcessVer1(const QString & cmd, uint timeout, QString & result,
++ uint retry_cnt, uint wait_cnt);
++ bool ProcessVer2(const QString & cmd, uint timeout, QString & result,
++ uint retry_cnt, uint wait_cnt);
+
+ private:
+ int StreamingCount(void) const;
+@@ -108,6 +112,8 @@ class ExternalStreamHandler : public StreamHandler
+ bool m_poll_mode;
+ bool m_notify;
+
++ uint m_apiVersion;
++ uint m_serialNo;
+ bool m_hasTuner;
+ bool m_hasPictureAttributes;
+
+@@ -122,6 +128,7 @@ class ExternalStreamHandler : public StreamHandler
+ QAtomicInt m_streaming_cnt;
+ QMutex m_stream_lock;
+ QMutex m_replay_lock;
++ QMutex m_process_lock;
+ };
+
+ #endif // _ExternalSTREAMHANDLER_H_
+diff --git a/mythtv/libs/libmythtv/recorders/recorderbase.cpp
b/mythtv/libs/libmythtv/recorders/recorderbase.cpp
+index ed868751978..05cb62be082 100644
+--- a/mythtv/libs/libmythtv/recorders/recorderbase.cpp
++++ b/mythtv/libs/libmythtv/recorders/recorderbase.cpp
+@@ -640,7 +640,7 @@ void RecorderBase::SavePositionMap(bool force, bool finished)
+ positionMapLock.unlock();
+ }
+
+- // Make sure a ringbuffer switch is checked at least every 10
++ // Make sure a ringbuffer switch is checked at least every 60
+ // seconds. Otherwise, this check is only performed on keyframes,
+ // and if there is a problem with the input we may never see one
+ // again, resulting in a wedged recording.
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+index 68e08c0c8d2..99d1a21c040 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+@@ -31,7 +31,7 @@
+
+ using namespace std;
+
+-const QString VERSION = "0.1";
++const QString VERSION = "0.5";
+
+ #define LOC Desc()
+
+@@ -117,10 +117,11 @@ void MythExternControl::Fatal(const QString & msg)
+ }
+
+ Q_SLOT void MythExternControl::SendMessage(const QString & cmd,
++ const QString & serial,
+ const QString & msg)
+ {
+ std::unique_lock<std::mutex> lk(m_msg_mutex);
+- m_commands.SendStatus(cmd, msg);
++ m_commands.SendStatus(cmd, serial, msg);
+ }
+
+ Q_SLOT void MythExternControl::ErrorMessage(const QString & msg)
+@@ -154,76 +155,101 @@ void Commands::Close(void)
+ m_parent->m_flow_cond.notify_all();
+ }
+
+-void Commands::StartStreaming(void)
++void Commands::StartStreaming(const QString & serial)
+ {
+- emit m_parent->StartStreaming();
++ emit m_parent->StartStreaming(serial);
+ }
+
+-void Commands::StopStreaming(bool silent)
++void Commands::StopStreaming(const QString & serial, bool silent)
+ {
+- emit m_parent->StopStreaming(silent);
++ emit m_parent->StopStreaming(serial, silent);
+ }
+
+-void Commands::LockTimeout(void) const
++void Commands::LockTimeout(const QString & serial) const
+ {
+- emit m_parent->LockTimeout();
++ emit m_parent->LockTimeout(serial);
+ }
+
+-void Commands::HasTuner(void) const
++void Commands::HasTuner(const QString & serial) const
+ {
+- emit m_parent->HasTuner();
++ emit m_parent->HasTuner(serial);
+ }
+
+-void Commands::HasPictureAttributes(void) const
++void Commands::HasPictureAttributes(const QString & serial) const
+ {
+- emit m_parent->HasPictureAttributes();
++ emit m_parent->HasPictureAttributes(serial);
+ }
+
+-void Commands::SetBlockSize(int blksz)
++void Commands::SetBlockSize(const QString & serial, int blksz)
+ {
+- emit m_parent->SetBlockSize(blksz);
++ emit m_parent->SetBlockSize(serial, blksz);
+ }
+
+-void Commands::TuneChannel(const QString & channum)
++void Commands::TuneChannel(const QString & serial, const QString & channum)
+ {
+- emit m_parent->TuneChannel(channum);
++ emit m_parent->TuneChannel(serial, channum);
+ }
+
+-void Commands::LoadChannels(void)
++void Commands::LoadChannels(const QString & serial)
+ {
+- emit m_parent->LoadChannels();
++ emit m_parent->LoadChannels(serial);
+ }
+
+-void Commands::FirstChannel(void)
++void Commands::FirstChannel(const QString & serial)
+ {
+- emit m_parent->FirstChannel();
++ emit m_parent->FirstChannel(serial);
+ }
+
+-void Commands::NextChannel(void)
++void Commands::NextChannel(const QString & serial)
+ {
+- emit m_parent->NextChannel();
++ emit m_parent->NextChannel(serial);
+ }
+
+-bool Commands::SendStatus(const QString & command, const QString & status)
++bool Commands::SendStatus(const QString & command, const QString & msg)
+ {
+- int len = write(2, status.toUtf8().constData(), status.size());
++ int len = write(2, msg.toUtf8().constData(), msg.size());
+ write(2, "\n", 1);
+
+- if (len != static_cast<int>(status.size()))
++ if (len != static_cast<int>(msg.size()))
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC +
+- QString("%1: Only wrote %2 of %3 bytes of status '%4'.")
+- .arg(command).arg(len).arg(status.size()).arg(status));
++ QString("%1: Only wrote %2 of %3 bytes of message '%4'.")
++ .arg(command).arg(len).arg(msg.size()).arg(msg));
++ return false;
++ }
++
++ LOG(VB_RECORD, LOG_INFO, LOC + QString("Processing '%1' -->
'%2'")
++ .arg(command).arg(msg));
++
++ m_parent->ClearError();
++ return true;
++}
++
++bool Commands::SendStatus(const QString & command, const QString & serial,
++ const QString & status)
++{
++ QString msg = QString("%1:%2").arg(serial).arg(status);
++
++ int len = write(2, msg.toUtf8().constData(), msg.size());
++ write(2, "\n", 1);
++
++ if (len != static_cast<int>(msg.size()))
++ {
++ LOG(VB_RECORD, LOG_ERR, LOC +
++ QString("%1: Only wrote %2 of %3 bytes of message '%4'.")
++ .arg(command).arg(len).arg(msg.size()).arg(msg));
+ return false;
+ }
+
+ if (!command.isEmpty())
+ {
+ LOG(VB_RECORD, LOG_INFO, LOC + QString("Processing '%1' -->
'%2'")
+- .arg(command).arg(status));
++ .arg(command).arg(msg));
+ }
++#if 0
+ else
+- LOG(VB_RECORD, LOG_INFO, LOC + QString("%1").arg(status));
++ LOG(VB_RECORD, LOG_INFO, LOC + QString("%1").arg(msg));
++#endif
+
+ m_parent->ClearError();
+ return true;
+@@ -235,112 +261,137 @@ bool Commands::ProcessCommand(const QString & cmd)
+
+ std::unique_lock<std::mutex> lk(m_parent->m_msg_mutex);
+
+- if (cmd.startsWith("Version?"))
++ if (cmd.startsWith("APIVersion?"))
+ {
+ if (m_parent->m_fatal)
+ SendStatus(cmd, "ERR:" + m_parent->ErrorString());
+ else
+- SendStatus(cmd, "OK:" + VERSION);
++ SendStatus(cmd, "OK:2");
++ return true;
+ }
+- else if (cmd.startsWith("HasLock?"))
++
++ QStringList tokens = cmd.split(':', QString::SkipEmptyParts);
++ if (tokens.size() < 2)
++ {
++ SendStatus(cmd, "0",
++ QString("0:ERR:Version 2 API expects serial_no:msg format.
"
++ "Saw '%1' instead").arg(cmd));
++ return true;
++ }
++
++ if (tokens[1].startsWith("Version?"))
++ {
++ if (m_parent->m_fatal)
++ SendStatus(cmd, tokens[0], "ERR:" + m_parent->ErrorString());
++ else
++ SendStatus(cmd, tokens[0], "OK:" + VERSION);
++ }
++ else if (tokens[1].startsWith("HasLock?"))
+ {
+ if (m_parent->m_ready)
+- SendStatus(cmd, "OK:Yes");
++ SendStatus(cmd, tokens[0], "OK:Yes");
+ else
+- SendStatus(cmd, "OK:No");
++ SendStatus(cmd, tokens[0], "OK:No");
+ }
+- else if (cmd.startsWith("SignalStrengthPercent"))
++ else if (tokens[1].startsWith("SignalStrengthPercent"))
+ {
+ if (m_parent->m_ready)
+- SendStatus(cmd, "OK:100");
++ SendStatus(cmd, tokens[0], "OK:100");
+ else
+- SendStatus(cmd, "OK:20");
++ SendStatus(cmd, tokens[0], "OK:20");
+ }
+- else if (cmd.startsWith("LockTimeout"))
++ else if (tokens[1].startsWith("LockTimeout"))
+ {
+- LockTimeout();
++ LockTimeout(tokens[0]);
+ }
+- else if (cmd.startsWith("HasTuner"))
++ else if (tokens[1].startsWith("HasTuner"))
+ {
+- HasTuner();
++ HasTuner(tokens[0]);
+ }
+- else if (cmd.startsWith("HasPictureAttributes"))
++ else if (tokens[1].startsWith("HasPictureAttributes"))
+ {
+- HasPictureAttributes();
++ HasPictureAttributes(tokens[0]);
+ }
+- else if (cmd.startsWith("SendBytes"))
++ else if (tokens[1].startsWith("SendBytes"))
+ {
+ // Used when FlowControl is Polling
+- SendStatus(cmd, "ERR:Not supported");
++ SendStatus(cmd, tokens[0], "ERR:Not supported");
+ }
+- else if (cmd.startsWith("XON"))
++ else if (tokens[1].startsWith("XON"))
+ {
+ // Used when FlowControl is XON/XOFF
+- SendStatus(cmd, "OK");
++ SendStatus(cmd, tokens[0], "OK");
+ m_parent->m_xon = true;
+ m_parent->m_flow_cond.notify_all();
+ }
+- else if (cmd.startsWith("XOFF"))
++ else if (tokens[1].startsWith("XOFF"))
+ {
+- SendStatus(cmd, "OK");
++ SendStatus(cmd, tokens[0], "OK");
+ // Used when FlowControl is XON/XOFF
+ m_parent->m_xon = false;
+ m_parent->m_flow_cond.notify_all();
+ }
+- else if (cmd.startsWith("TuneChannel"))
++ else if (tokens[1].startsWith("TuneChannel"))
+ {
+- TuneChannel(cmd.mid(12));
++ if (tokens.size() > 1)
++ TuneChannel(tokens[0], tokens[2]);
++ else
++ SendStatus(cmd, tokens[0], "ERR:Missing channum");
+ }
+- else if (cmd.startsWith("LoadChannels"))
++ else if (tokens[1].startsWith("LoadChannels"))
+ {
+- LoadChannels();
++ LoadChannels(tokens[0]);
+ }
+- else if (cmd.startsWith("FirstChannel"))
++ else if (tokens[1].startsWith("FirstChannel"))
+ {
+- FirstChannel();
++ FirstChannel(tokens[0]);
+ }
+- else if (cmd.startsWith("NextChannel"))
++ else if (tokens[1].startsWith("NextChannel"))
+ {
+- NextChannel();
++ NextChannel(tokens[0]);
+ }
+- else if (cmd.startsWith("IsOpen?"))
++ else if (tokens[1].startsWith("IsOpen?"))
+ {
+ std::unique_lock<std::mutex> lk(m_parent->m_run_mutex);
+ if (m_parent->m_fatal)
+- SendStatus(cmd, "ERR:" + m_parent->ErrorString());
++ SendStatus(cmd, tokens[0], "ERR:" + m_parent->ErrorString());
+ else if (m_parent->m_ready)
+- SendStatus(cmd, "OK:Open");
++ SendStatus(cmd, tokens[0], "OK:Open");
+ else
+- SendStatus(cmd, "WARN:Not Open yet");
++ SendStatus(cmd, tokens[0], "WARN:Not Open yet");
+ }
+- else if (cmd.startsWith("CloseRecorder"))
++ else if (tokens[1].startsWith("CloseRecorder"))
+ {
+ if (m_parent->m_streaming)
+- StopStreaming(true);
++ StopStreaming(tokens[0], true);
+ m_parent->Terminate();
+- SendStatus(cmd, "OK:Terminating");
++ SendStatus(cmd, tokens[0], "OK:Terminating");
+ }
+- else if (cmd.startsWith("FlowControl?"))
++ else if (tokens[1].startsWith("FlowControl?"))
+ {
+- SendStatus(cmd, "OK:XON/XOFF");
++ SendStatus(cmd, tokens[0], "OK:XON/XOFF");
+ }
+- else if (cmd.startsWith("BlockSize"))
++ else if (tokens[1].startsWith("BlockSize"))
+ {
+- SetBlockSize(cmd.mid(10).toInt());
++ if (tokens.size() > 1)
++ SetBlockSize(tokens[0], tokens[2].toUInt());
++ else
++ SendStatus(cmd, tokens[0], "ERR:Missing block size");
+ }
+- else if (cmd.startsWith("StartStreaming"))
++ else if (tokens[1].startsWith("StartStreaming"))
+ {
+- StartStreaming();
++ StartStreaming(tokens[0]);
+ }
+- else if (cmd.startsWith("StopStreaming"))
++ else if (tokens[1].startsWith("StopStreaming"))
+ {
+ /* This does not close the stream! When Myth is done with
+ * this 'recording' ExternalChannel::EnterPowerSavingMode()
+ * will be called, which invokes CloseRecorder() */
+- StopStreaming(false);
++ StopStreaming(tokens[0], false);
+ }
+ else
+- SendStatus(cmd, "ERR:Unrecognized command");
++ SendStatus(cmd, tokens[0],
++ QString("ERR:Unrecognized command
'%1'").arg(tokens[1]));
+
+ return true;
+ }
+@@ -430,6 +481,7 @@ bool Buffer::Fill(const QByteArray & buffer)
+ return false;
+
+ static int dropped = 0;
++ static int dropped_bytes = 0;
+
+ m_parent->m_flow_mutex.lock();
+ if (m_data.size() < MAX_QUEUE)
+@@ -446,9 +498,12 @@ bool Buffer::Fill(const QByteArray & buffer)
+ }
+ else
+ {
++ dropped_bytes += buffer.size();
+ LOG(VB_RECORD, LOG_WARNING, LOC +
+- QString("Packet queue overrun. Dropped %1 packets.")
+- .arg(++dropped));
++ QString("Packet queue overrun. Dropped %1 packets, %2 bytes.")
++ .arg(++dropped).arg(dropped_bytes));
++
++ std::this_thread::sleep_for(std::chrono::microseconds(250));
+ }
+
+ m_parent->m_flow_mutex.unlock();
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.h
b/mythtv/programs/mythexternrecorder/MythExternControl.h
+index b3af726e0ac..45b77244916 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.h
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.h
+@@ -86,22 +86,24 @@ class Commands : public QObject
+ }
+
+ bool SendStatus(const QString & cmd, const QString & status);
++ bool SendStatus(const QString & cmd, const QString & serial,
++ const QString & status);
+ bool ProcessCommand(const QString & cmd);
+
+ protected:
+ void Run(void);
+ bool Open(void);
+ void Close(void);
+- void StartStreaming(void);
+- void StopStreaming(bool silent);
+- void LockTimeout(void) const;
+- void HasTuner(void) const;
+- void HasPictureAttributes(void) const;
+- void SetBlockSize(int blksz);
+- void TuneChannel(const QString & channum);
+- void LoadChannels(void);
+- void FirstChannel(void);
+- void NextChannel(void);
++ void StartStreaming(const QString & serial);
++ void StopStreaming(const QString & serial, bool silent);
++ void LockTimeout(const QString & serial) const;
++ void HasTuner(const QString & serial) const;
++ void HasPictureAttributes(const QString & serial) const;
++ void SetBlockSize(const QString & serial, int blksz);
++ void TuneChannel(const QString & serial, const QString & channum);
++ void LoadChannels(const QString & serial);
++ void FirstChannel(const QString & serial);
++ void NextChannel(const QString & serial);
+
+ private:
+ std::thread m_thread;
+@@ -133,20 +135,21 @@ class MythExternControl : public QObject
+ signals:
+ void Open(void);
+ void Close(void);
+- void StartStreaming(void);
+- void StopStreaming(bool silent);
+- void LockTimeout(void) const;
+- void HasTuner(void) const;
+- void HasPictureAttributes(void) const;
+- void SetBlockSize(int blksz);
+- void TuneChannel(const QString & channum);
+- void LoadChannels(void);
+- void FirstChannel(void);
+- void NextChannel(void);
++ void StartStreaming(const QString & serial);
++ void StopStreaming(const QString & serial, bool silent);
++ void LockTimeout(const QString & serial) const;
++ void HasTuner(const QString & serial) const;
++ void HasPictureAttributes(const QString & serial) const;
++ void SetBlockSize(const QString & serial, int blksz);
++ void TuneChannel(const QString & serial, const QString & channum);
++ void LoadChannels(const QString & serial);
++ void FirstChannel(const QString & serial);
++ void NextChannel(const QString & serial);
+
+ public slots:
+ void SetDescription(const QString & desc) { m_desc = desc; }
+- void SendMessage(const QString & command, const QString & msg);
++ void SendMessage(const QString & command, const QString & serial,
++ const QString & msg);
+ void ErrorMessage(const QString & msg);
+ void Opened(void);
+ void Done(void);
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+index 1f5af94d54c..198aae1db3c 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.cpp
+@@ -25,6 +25,7 @@
+ #include <QtCore/QtCore>
+ #include <QFileInfo>
+ #include <QProcess>
++#include <QElapsedTimer>
+
+ #define LOC Desc()
+
+@@ -33,6 +34,7 @@ MythExternRecApp::MythExternRecApp(const QString & command,
+ : QObject()
+ , m_fatal(false)
+ , m_run(true)
++ , m_streaming(false)
+ , m_result(0)
+ , m_buffer_max(188 * 10000)
+ , m_block_size(m_buffer_max / 4)
+@@ -114,14 +116,14 @@ bool MythExternRecApp::Open(void)
+ {
+ if (m_fatal)
+ {
+- emit SendMessage("Open", "ERR:Already dead.");
++ emit SendMessage("Open", "0", "ERR:Already
dead.");
+ return false;
+ }
+
+ if (m_command.isEmpty())
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC + ": No recorder provided.");
+- emit SendMessage("Open", "ERR:No recorder provided.");
++ emit SendMessage("Open", "0", "ERR:No recorder
provided.");
+ return false;
+ }
+
+@@ -217,12 +219,12 @@ void MythExternRecApp::Run(void)
+ emit Done();
+ }
+
+-Q_SLOT void MythExternRecApp::LoadChannels(void)
++Q_SLOT void MythExternRecApp::LoadChannels(const QString & serial)
+ {
+ if (m_channels_ini.isEmpty())
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
+- emit SendMessage("LoadChannels", "ERR:No channels
configured.");
++ emit SendMessage("LoadChannels", serial, "ERR:No channels
configured.");
+ return;
+ }
+
+@@ -238,7 +240,8 @@ Q_SLOT void MythExternRecApp::LoadChannels(void)
+ {
+ QString errmsg = QString("Failed to start '%1':
").arg(cmd) + ENO;
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
+- emit SendMessage("LoadChannels",
QString("ERR:%1").arg(errmsg));
++ emit SendMessage("LoadChannels", serial,
++ QString("ERR:%1").arg(errmsg));
+ return;
+ }
+
+@@ -268,7 +271,8 @@ Q_SLOT void MythExternRecApp::LoadChannels(void)
+ {
+ QString errmsg = QString("Timedout waiting for
'%1'").arg(cmd);
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
+- emit SendMessage("LoadChannels",
QString("ERR:%1").arg(errmsg));
++ emit SendMessage("LoadChannels", serial,
++ QString("ERR:%1").arg(errmsg));
+ return;
+ }
+ }
+@@ -279,29 +283,32 @@ Q_SLOT void MythExternRecApp::LoadChannels(void)
+ m_chan_settings->sync();
+ m_channels = m_chan_settings->childGroups();
+
+- emit SendMessage("LoadChannels",
QString("OK:%1").arg(m_channels.size()));
++ emit SendMessage("LoadChannels", serial,
++ QString("OK:%1").arg(m_channels.size()));
+ }
+
+-void MythExternRecApp::GetChannel(QString func)
++void MythExternRecApp::GetChannel(const QString & serial, const QString & func)
+ {
+ if (m_channels_ini.isEmpty() || m_channels.size() == 0)
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
+- emit SendMessage("FirstChannel", QString("ERR:No channels
configured."));
++ emit SendMessage("FirstChannel", serial,
++ QString("ERR:No channels configured."));
+ return;
+ }
+
+ if (m_chan_settings == nullptr)
+ {
+ LOG(VB_CHANNEL, LOG_WARNING, LOC + ": Invalid channel
configuration.");
+- emit SendMessage(func, "ERR:Invalid channel configuration.");
++ emit SendMessage(func, serial,
++ "ERR:Invalid channel configuration.");
+ return;
+ }
+
+ if (m_channels.size() <= m_channel_idx)
+ {
+ LOG(VB_CHANNEL, LOG_WARNING, LOC + ": No more channels.");
+- emit SendMessage(func, "ERR:No more channels.");
++ emit SendMessage(func, serial, "ERR:No more channels.");
+ return;
+ }
+
+@@ -319,27 +326,28 @@ void MythExternRecApp::GetChannel(QString func)
+ QString(": NextChannel
Name:'%1',Callsign:'%2',xmltvid:%3")
+ .arg(name).arg(callsign).arg(xmltvid));
+
+- emit SendMessage(func, QString("OK:%1,%2,%3,%4")
++ emit SendMessage(func, serial, QString("OK:%1,%2,%3,%4")
+ .arg(channum).arg(name).arg(callsign).arg(xmltvid));
+ }
+
+-Q_SLOT void MythExternRecApp::FirstChannel(void)
++Q_SLOT void MythExternRecApp::FirstChannel(const QString & serial)
+ {
+ m_channel_idx = 0;
+- GetChannel("FirstChannel");
++ GetChannel(serial, "FirstChannel");
+ }
+
+-Q_SLOT void MythExternRecApp::NextChannel(void)
++Q_SLOT void MythExternRecApp::NextChannel(const QString & serial)
+ {
+- GetChannel("NextChannel");
++ GetChannel(serial, "NextChannel");
+ }
+
+-Q_SLOT void MythExternRecApp::TuneChannel(const QString & channum)
++Q_SLOT void MythExternRecApp::TuneChannel(const QString & serial,
++ const QString & channum)
+ {
+ if (m_channels_ini.isEmpty())
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": No channels configured.");
+- emit SendMessage("TuneChannel", "ERR:No channels
configured.");
++ emit SendMessage("TuneChannel", serial, "ERR:No channels
configured.");
+ return;
+ }
+
+@@ -350,12 +358,12 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString &
channum)
+
+ if (url.isEmpty())
+ {
+- QString msg = QString("Channel number %1 is missing a URL.")
++ QString msg = QString("Channel number [%1] is missing a URL.")
+ .arg(channum);
+
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + msg);
+
+- emit SendMessage("TuneChannel",
QString("ERR:%1").arg(msg));
++ emit SendMessage("TuneChannel", serial,
QString("ERR:%1").arg(msg));
+ return;
+ }
+
+@@ -369,7 +377,7 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString &
channum)
+ {
+ QString errmsg = QString("'%1' failed: ").arg(tune) +
ENO;
+ LOG(VB_CHANNEL, LOG_ERR, LOC + ": " + errmsg);
+- emit SendMessage("TuneChannel",
QString("ERR:%1").arg(errmsg));
++ emit SendMessage("TuneChannel", serial,
QString("ERR:%1").arg(errmsg));
+ return;
+ }
+ LOG(VB_CHANNEL, LOG_INFO, LOC +
+@@ -398,51 +406,56 @@ Q_SLOT void MythExternRecApp::TuneChannel(const QString &
channum)
+ QString(": TuneChannel %1: URL '%2'").arg(channum).arg(url));
+ m_tuned = true;
+
+- emit SetDescription(m_desc);
+- emit SendMessage("TuneChannel", QString("OK:Tunned to
%1").arg(channum));
++ emit SetDescription(Desc());
++ emit SendMessage("TuneChannel", serial,
++ QString("OK:Tunned to %1").arg(channum));
+ }
+
+-Q_SLOT void MythExternRecApp::LockTimeout(void)
++Q_SLOT void MythExternRecApp::LockTimeout(const QString & serial)
+ {
+ if (!Open())
+ return;
+
+ if (m_lock_timeout > 0)
+- emit SendMessage("LockTimeout",
QString("OK:%1").arg(m_lock_timeout));
+- emit SendMessage("LockTimeout", QString("OK:%1")
++ emit SendMessage("LockTimeout", serial,
++ QString("OK:%1").arg(m_lock_timeout));
++ emit SendMessage("LockTimeout", serial, QString("OK:%1")
+ .arg(m_scan_command.isEmpty() ? 12000 : 120000));
+ }
+
+-Q_SLOT void MythExternRecApp::HasTuner(void)
++Q_SLOT void MythExternRecApp::HasTuner(const QString & serial)
+ {
+- emit SendMessage("HasTuner", QString("OK:%1")
++ emit SendMessage("HasTuner", serial, QString("OK:%1")
+ .arg(m_channels_ini.isEmpty() ? "No" :
"Yes"));
+ }
+
+-Q_SLOT void MythExternRecApp::HasPictureAttributes(void)
++Q_SLOT void MythExternRecApp::HasPictureAttributes(const QString & serial)
+ {
+- emit SendMessage("HasPictureAttributes", "OK:No");
++ emit SendMessage("HasPictureAttributes", serial, "OK:No");
+ }
+
+-Q_SLOT void MythExternRecApp::SetBlockSize(int blksz)
++Q_SLOT void MythExternRecApp::SetBlockSize(const QString & serial, int blksz)
+ {
+ m_block_size = blksz;
+- emit SendMessage("BlockSize", "OK");
++ emit SendMessage("BlockSize", serial, "OK");
+ }
+
+-Q_SLOT void MythExternRecApp::StartStreaming(void)
++Q_SLOT void MythExternRecApp::StartStreaming(const QString & serial)
+ {
++ m_streaming = true;
+ if (!m_tuned && !m_channels_ini.isEmpty())
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC + ": No channel has been tuned");
+- emit SendMessage("StartStreaming", "ERR:No channel has been
tuned");
++ emit SendMessage("StartStreaming", serial,
++ "ERR:No channel has been tuned");
+ return;
+ }
+
+ if (m_proc.state() == QProcess::Running)
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC + ": Application already running");
+- emit SendMessage("StartStreaming", "WARN:Application already
running");
++ emit SendMessage("StartStreaming", serial,
++ "WARN:Application already running");
+ return;
+ }
+
+@@ -456,7 +469,8 @@ Q_SLOT void MythExternRecApp::StartStreaming(void)
+ if (!m_proc.waitForStarted())
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC + ": Failed to start application.");
+- emit SendMessage("StartStreaming", "ERR:Failed to start
application.");
++ emit SendMessage("StartStreaming", serial,
++ "ERR:Failed to start application.");
+ return;
+ }
+
+@@ -464,11 +478,13 @@ Q_SLOT void MythExternRecApp::StartStreaming(void)
+ .arg(m_proc.program()).arg(m_proc.processId()));
+
+ emit Streaming(true);
+- emit SendMessage("StartStreaming", "OK:Streaming Started");
++ emit SetDescription(Desc());
++ emit SendMessage("StartStreaming", serial, "OK:Streaming
Started");
+ }
+
+-Q_SLOT void MythExternRecApp::StopStreaming(bool silent)
++Q_SLOT void MythExternRecApp::StopStreaming(const QString & serial, bool silent)
+ {
++ m_streaming = false;
+ if (m_proc.state() == QProcess::Running)
+ {
+ m_proc.terminate();
+@@ -477,19 +493,22 @@ Q_SLOT void MythExternRecApp::StopStreaming(bool silent)
+
+ LOG(VB_RECORD, LOG_INFO, LOC + ": External application terminated.");
+ if (silent)
+- emit SendMessage("StopStreaming", "STATUS:Streaming
Stopped");
++ emit SendMessage("StopStreaming", serial, "STATUS:Streaming
Stopped");
+ else
+- emit SendMessage("StopStreaming", "OK:Streaming
Stopped");
++ emit SendMessage("StopStreaming", serial, "OK:Streaming
Stopped");
+ }
+ else
+ {
+ if (silent)
+- emit SendMessage("StopStreaming", "STATUS:Already not
Streaming");
++ emit SendMessage("StopStreaming", serial,
++ "STATUS:Already not Streaming");
+ else
+- emit SendMessage("StopStreaming", "WARN:Already not
Streaming");
++ emit SendMessage("StopStreaming", serial,
++ "WARN:Already not Streaming");
+ }
+
+ emit Streaming(false);
++ emit SetDescription(Desc());
+ }
+
+ Q_SLOT void MythExternRecApp::ProcStarted(void)
+@@ -503,7 +522,8 @@ Q_SLOT void MythExternRecApp::ProcFinished(int exitCode,
+ QProcess::ExitStatus exitStatus)
+ {
+ m_result = exitCode;
+- QString msg = QString("Finished: %1 (exit code: %2)")
++ QString msg = QString("%1Finished: %2 (exit code: %3)")
++ .arg(exitStatus != QProcess::NormalExit ? "ERR " :
"")
+ .arg(exitStatus == QProcess::NormalExit ? "OK" :
"Failed")
+ .arg(m_result);
+ LOG(VB_RECORD, LOG_INFO, LOC + ": " + msg);
+@@ -512,11 +532,13 @@ Q_SLOT void MythExternRecApp::ProcFinished(int exitCode,
+
+ Q_SLOT void MythExternRecApp::ProcStateChanged(QProcess::ProcessState newState)
+ {
++ bool unexpected = false;
+ QString msg = "State Changed: ";
+ switch (newState)
+ {
+ case QProcess::NotRunning:
+ msg += "Not running";
++ unexpected = m_streaming;
+ break;
+ case QProcess::Starting:
+ msg += "Starting ";
+@@ -525,7 +547,10 @@ Q_SLOT void
MythExternRecApp::ProcStateChanged(QProcess::ProcessState newState)
+ msg += QString("Running PID %1").arg(m_proc.processId());
+ break;
+ }
+- LOG(VB_RECORD, LOG_INFO, LOC + ": " + msg);
++
++ LOG(VB_RECORD, LOG_INFO, LOC + msg);
++ if (unexpected)
++ MythLog("ERR Unexpected " + msg);
+ }
+
+ Q_SLOT void MythExternRecApp::ProcError(QProcess::ProcessError /*error */)
+diff --git a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+index d3e18d566a4..e6960cdda54 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternRecApp.h
++++ b/mythtv/programs/mythexternrecorder/MythExternRecApp.h
+@@ -42,12 +42,13 @@ class MythExternRecApp : public QObject
+
+ QString Desc(void) const;
+ void MythLog(const QString & msg)
+- { SendMessage("", QString("STATUS:%1").arg(msg)); }
++ { SendMessage("", "0", QString("STATUS:%1").arg(msg));
}
+ void SetErrorMsg(const QString & msg) { emit ErrorMessage(msg); }
+
+ signals:
+ void SetDescription(const QString & desc);
+- void SendMessage(const QString & func, const QString & msg);
++ void SendMessage(const QString & func, const QString & serial,
++ const QString & msg);
+ void ErrorMessage(const QString & msg);
+ void Opened(void);
+ void Done(void);
+@@ -63,20 +64,20 @@ class MythExternRecApp : public QObject
+ void ProcReadStandardOutput(void);
+
+ void Close(void);
+- void StartStreaming(void);
+- void StopStreaming(bool silent);
+- void LockTimeout(void);
+- void HasTuner(void);
+- void LoadChannels(void);
+- void FirstChannel(void);
+- void NextChannel(void);
+-
+- void TuneChannel(const QString & channum);
+- void HasPictureAttributes(void);
+- void SetBlockSize(int blksz);
++ void StartStreaming(const QString & serial);
++ void StopStreaming(const QString & serial, bool silent);
++ void LockTimeout(const QString & serial);
++ void HasTuner(const QString & serial);
++ void LoadChannels(const QString & serial);
++ void FirstChannel(const QString & serial);
++ void NextChannel(const QString & serial);
++
++ void TuneChannel(const QString & serial, const QString & channum);
++ void HasPictureAttributes(const QString & serial);
++ void SetBlockSize(const QString & serial, int blksz);
+
+ protected:
+- void GetChannel(QString func);
++ void GetChannel(const QString & serial, const QString & func);
+
+ private:
+ bool config(void);
+@@ -86,6 +87,7 @@ class MythExternRecApp : public QObject
+ std::atomic<bool> m_run;
+ std::condition_variable m_run_cond;
+ std::mutex m_run_mutex;
++ std::atomic<bool> m_streaming;
+ int m_result;
+
+ uint m_buffer_max;
+
+From 3af3fab0c3839e22591cdbc2c778b15c031d1437 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Mon, 22 Oct 2018 19:16:34 -0600
+Subject: [PATCH 46/52] ExternRecChannelScanner: Fix argument order bug.
+
+(cherry picked from commit fc1b82c227b9f34ae3fefb05d6a634be4b236982)
+---
+ mythtv/libs/libmythtv/channelscan/externrecscanner.cpp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
b/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
+index 24a3a5b8f45..03a1ea08d25 100644
+--- a/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
++++ b/mythtv/libs/libmythtv/channelscan/externrecscanner.cpp
+@@ -156,7 +156,7 @@ void ExternRecChannelScanner::run(void)
+ m_scan_monitor->ScanAppendTextToLog(tr("Adding
%1").arg(msg));
+
+ chanid = ChannelUtil::CreateChanID(m_sourceid, channum);
+- ChannelUtil::CreateChannel(0, m_sourceid, chanid, name, callsign,
++ ChannelUtil::CreateChannel(0, m_sourceid, chanid, callsign, name,
+ channum, 1, 0, 0,
+ false, false, false, QString(),
+ QString(), "Default", xmltvid);
+@@ -166,7 +166,7 @@ void ExternRecChannelScanner::run(void)
+ if (m_scan_monitor)
+ m_scan_monitor->ScanAppendTextToLog(tr("Updating
%1").arg(msg));
+
+- ChannelUtil::UpdateChannel(0, m_sourceid, chanid, name, callsign,
++ ChannelUtil::UpdateChannel(0, m_sourceid, chanid, callsign, name,
+ channum, 1, 0, 0,
+ false, false, false, QString(),
+ QString(), "Default", xmltvid);
+
+From 9cd90a0b8845f52aea7c2a0376ca793efe871b27 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Wed, 24 Oct 2018 11:02:03 -0600
+Subject: [PATCH 47/52] ExternalStreamHandler: Make sure external application
+ knows the maximum API version support by mythbackend.
+
+(cherry picked from commit 02f1104e924584e3d12403dacbbf4135e032877a)
+---
+ .../libmythtv/recorders/ExternalStreamHandler.cpp | 6 +++++-
+ .../libmythtv/recorders/ExternalStreamHandler.h | 6 ++++--
+ .../mythexternrecorder/MythExternControl.cpp | 15 +++++++++++++--
+ .../mythexternrecorder/MythExternControl.h | 1 +
+ 4 files changed, 23 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index 8fcf2982de7..edf826efa41 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -5,6 +5,7 @@
+ #include <iostream>
+ #include <fcntl.h>
+ #include <unistd.h>
++#include <algorithm>
+ #if !defined( USING_MINGW ) && !defined( _MSC_VER )
+ #include <poll.h>
+ #include <sys/ioctl.h>
+@@ -814,13 +815,16 @@ bool ExternalStreamHandler::OpenApp(void)
+
+ if (tokens.size() > 1)
+ m_apiVersion = tokens[1].toUInt();
+- if (m_apiVersion != 1 && m_apiVersion != 2)
++ m_apiVersion = min(m_apiVersion, static_cast<int>(MAX_API_VERSION));
++ if (m_apiVersion < 1)
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC +
+ QString("Bad response to 'APIVersion?' - '%1'.
"
+ "Expecting 1 or 2").arg(result));
+ m_apiVersion = 1;
+ }
++
++ ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), 2500,
result);
+ }
+
+ if (!IsAppOpen())
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+index 922beb58d01..cc5f875513d 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+@@ -59,7 +59,9 @@ class ExternIO
+
+ class ExternalStreamHandler : public StreamHandler
+ {
+- enum constants {PACKET_SIZE = 188 * 32768, TOO_FAST_SIZE = 188 * 4196 };
++ enum constants { MAX_API_VERSION = 2,
++ PACKET_SIZE = 188 * 32768,
++ TOO_FAST_SIZE = 188 * 4196 };
+
+ public:
+ static ExternalStreamHandler *Get(const QString &devicename,
+@@ -112,7 +114,7 @@ class ExternalStreamHandler : public StreamHandler
+ bool m_poll_mode;
+ bool m_notify;
+
+- uint m_apiVersion;
++ int m_apiVersion;
+ uint m_serialNo;
+ bool m_hasTuner;
+ bool m_hasPictureAttributes;
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+index 99d1a21c040..edb69f431b6 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.cpp
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.cpp
+@@ -31,7 +31,7 @@
+
+ using namespace std;
+
+-const QString VERSION = "0.5";
++const QString VERSION = "0.6";
+
+ #define LOC Desc()
+
+@@ -139,6 +139,7 @@ Q_SLOT void MythExternControl::ErrorMessage(const QString & msg)
+ Commands::Commands(MythExternControl * parent)
+ : m_thread()
+ , m_parent(parent)
++ , m_apiVersion(-1)
+ {
+ }
+
+@@ -279,7 +280,17 @@ bool Commands::ProcessCommand(const QString & cmd)
+ return true;
+ }
+
+- if (tokens[1].startsWith("Version?"))
++ if (tokens[1].startsWith("APIVersion"))
++ {
++ if (tokens.size() > 1)
++ {
++ m_apiVersion = tokens[2].toInt();
++ SendStatus(cmd, tokens[0], QString("OK:%1").arg(m_apiVersion));
++ }
++ else
++ SendStatus(cmd, tokens[0], "ERR:Missing API Version number");
++ }
++ else if (tokens[1].startsWith("Version?"))
+ {
+ if (m_parent->m_fatal)
+ SendStatus(cmd, tokens[0], "ERR:" + m_parent->ErrorString());
+diff --git a/mythtv/programs/mythexternrecorder/MythExternControl.h
b/mythtv/programs/mythexternrecorder/MythExternControl.h
+index 45b77244916..4619d6b486d 100644
+--- a/mythtv/programs/mythexternrecorder/MythExternControl.h
++++ b/mythtv/programs/mythexternrecorder/MythExternControl.h
+@@ -109,6 +109,7 @@ class Commands : public QObject
+ std::thread m_thread;
+
+ MythExternControl* m_parent;
++ int m_apiVersion;
+ };
+
+ class MythExternControl : public QObject
+
+From 1d1392ed9e5d3401c348576ceb514e2d58f4baf7 Mon Sep 17 00:00:00 2001
+From: Peter Bennett <pbennett(a)mythtv.org>
+Date: Fri, 19 Oct 2018 16:05:04 -0400
+Subject: [PATCH 48/52] mythexternrecorder: add .gitignore so that it does not
+ show in git status report
+
+(cherry picked from commit 402f35a8ef550ff9444d83a4d6c4b1186e2f7456)
+---
+ mythtv/programs/mythexternrecorder/.gitignore | 1 +
+ 1 file changed, 1 insertion(+)
+ create mode 100644 mythtv/programs/mythexternrecorder/.gitignore
+
+diff --git a/mythtv/programs/mythexternrecorder/.gitignore
b/mythtv/programs/mythexternrecorder/.gitignore
+new file mode 100644
+index 00000000000..7cbcce15f4f
+--- /dev/null
++++ b/mythtv/programs/mythexternrecorder/.gitignore
+@@ -0,0 +1 @@
++mythexternrecorder
+
+From 55a44b537a02206cfb99c3825e11c1f271444711 Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Sat, 27 Oct 2018 15:26:12 -0600
+Subject: [PATCH 49/52] ExternalRecorder: Adjust command timeouts to be more
+ appropriate.
+
+(cherry picked from commit 68f26642cc8768aa5c4bba99244c0312f76569b3)
+---
+ .../libmythtv/recorders/ExternalChannel.cpp | 2 +-
+ .../recorders/ExternalSignalMonitor.cpp | 6 ++---
+ .../recorders/ExternalStreamHandler.cpp | 22 +++++++++----------
+ .../recorders/ExternalStreamHandler.h | 2 +-
+ 4 files changed, 16 insertions(+), 16 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+index 5d44f8d41f6..03ffc938883 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalChannel.cpp
+@@ -84,7 +84,7 @@ bool ExternalChannel::Tune(const QString &channum)
+
+ LOG(VB_CHANNEL, LOG_INFO, LOC + "Tuning to " + channum);
+
+- if (!m_stream_handler->ProcessCommand("TuneChannel:" + channum, 5000,
++ if (!m_stream_handler->ProcessCommand("TuneChannel:" + channum, 10000,
+ result))
+ {
+ LOG(VB_CHANNEL, LOG_ERR, LOC + QString
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+index 8f406b7597d..aff1b6ec93c 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalSignalMonitor.cpp
+@@ -159,7 +159,7 @@ bool ExternalSignalMonitor::HasLock(void)
+ {
+ QString result;
+
+- m_stream_handler->ProcessCommand("HasLock?", 2500, result);
++ m_stream_handler->ProcessCommand("HasLock?", 500, result);
+ if (result.startsWith("OK:"))
+ {
+ return result.mid(3, 3) == "Yes";
+@@ -175,7 +175,7 @@ int ExternalSignalMonitor::GetSignalStrengthPercent(void)
+ {
+ QString result;
+
+- m_stream_handler->ProcessCommand("SignalStrengthPercent?", 2500,
result);
++ m_stream_handler->ProcessCommand("SignalStrengthPercent?", 500,
result);
+ if (result.startsWith("OK:"))
+ {
+ bool ok;
+@@ -201,7 +201,7 @@ int ExternalSignalMonitor::GetLockTimeout(void)
+ {
+ QString result;
+
+- m_stream_handler->ProcessCommand("LockTimeout?", 2500, result);
++ m_stream_handler->ProcessCommand("LockTimeout?", 500, result);
+ if (result.startsWith("OK:"))
+ {
+ bool ok;
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index edf826efa41..889fcf6da1d 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -809,7 +809,7 @@ bool ExternalStreamHandler::OpenApp(void)
+
+ QString result;
+
+- if (ProcessCommand("APIVersion?", 10000, result))
++ if (ProcessCommand("APIVersion?", 1000, result))
+ {
+ QStringList tokens = result.split(':', QString::SkipEmptyParts);
+
+@@ -824,7 +824,7 @@ bool ExternalStreamHandler::OpenApp(void)
+ m_apiVersion = 1;
+ }
+
+- ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), 2500,
result);
++ ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), 500,
result);
+ }
+
+ if (!IsAppOpen())
+@@ -835,7 +835,7 @@ bool ExternalStreamHandler::OpenApp(void)
+ }
+
+ // Gather capabilities
+- if (!ProcessCommand("HasTuner?", 10000, result))
++ if (!ProcessCommand("HasTuner?", 500, result))
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC +
+ QString("Bad response to 'HasTuner?' -
'%1'").arg(result));
+@@ -843,7 +843,7 @@ bool ExternalStreamHandler::OpenApp(void)
+ return false;
+ }
+ m_hasTuner = result.startsWith("OK:Yes");
+- if (!ProcessCommand("HasPictureAttributes?", 2500, result))
++ if (!ProcessCommand("HasPictureAttributes?", 500, result))
+ {
+ LOG(VB_RECORD, LOG_ERR, LOC +
+ QString("Bad response to 'HasPictureAttributes?' -
'%1'")
+@@ -854,7 +854,7 @@ bool ExternalStreamHandler::OpenApp(void)
+ m_hasPictureAttributes = result.startsWith("OK:Yes");
+
+ /* Operate in "poll" or "xon/xoff" mode */
+- m_poll_mode = ProcessCommand("FlowControl?", 2500, result) &&
++ m_poll_mode = ProcessCommand("FlowControl?", 500, result) &&
+ result.startsWith("OK:Poll");
+
+ LOG(VB_RECORD, LOG_INFO, LOC + "App opened successfully");
+@@ -868,7 +868,7 @@ bool ExternalStreamHandler::OpenApp(void)
+ );
+
+ /* Let the external app know how many bytes will read without blocking */
+- ProcessCommand(QString("BlockSize:%1").arg(PACKET_SIZE), 2500, result);
++ ProcessCommand(QString("BlockSize:%1").arg(PACKET_SIZE), 500, result);
+
+ return true;
+ }
+@@ -883,7 +883,7 @@ bool ExternalStreamHandler::IsAppOpen(void)
+ }
+
+ QString result;
+- return ProcessCommand("Version?", 10000, result);
++ return ProcessCommand("Version?", 500, result, 10, 10);
+ }
+
+ bool ExternalStreamHandler::IsTSOpen(void)
+@@ -893,7 +893,7 @@ bool ExternalStreamHandler::IsTSOpen(void)
+
+ QString result;
+
+- if (!ProcessCommand("IsOpen?", 2500, result))
++ if (!ProcessCommand("IsOpen?", 1000, result))
+ return false;
+
+ m_tsopen = true;
+@@ -909,7 +909,7 @@ void ExternalStreamHandler::CloseApp(void)
+
+ LOG(VB_RECORD, LOG_INFO, LOC + "CloseRecorder");
+ m_IO_lock.unlock();
+- ProcessCommand("CloseRecorder", 30000, result);
++ ProcessCommand("CloseRecorder", 1000, result, 10, 3);
+ m_IO_lock.lock();
+
+ if (!result.startsWith("OK"))
+@@ -1008,7 +1008,7 @@ bool ExternalStreamHandler::StartStreaming(void)
+
+ if (StreamingCount() == 0)
+ {
+- if (!ProcessCommand("StartStreaming", 15000, result))
++ if (!ProcessCommand("StartStreaming", 1000, result, 10, 5))
+ {
+ LogLevel_t level = LOG_ERR;
+ if (!result.toLower().startsWith("warn"))
+@@ -1069,7 +1069,7 @@ bool ExternalStreamHandler::StopStreaming(void)
+ return false;
+ }
+
+- if (!ProcessCommand("StopStreaming", 15000, result))
++ if (!ProcessCommand("StopStreaming", 1000, result, 10, 5))
+ {
+ LogLevel_t level = LOG_ERR;
+ if (!result.toLower().startsWith("warn"))
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+index cc5f875513d..951d40634fa 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.h
+@@ -94,7 +94,7 @@ class ExternalStreamHandler : public StreamHandler
+ void PurgeBuffer(void);
+
+ bool ProcessCommand(const QString & cmd, uint timeout, QString & result,
+- uint retry_cnt = 10, uint wait_cnt = 10);
++ uint retry_cnt = 10, uint wait_cnt = 2);
+ bool ProcessVer1(const QString & cmd, uint timeout, QString & result,
+ uint retry_cnt, uint wait_cnt);
+ bool ProcessVer2(const QString & cmd, uint timeout, QString & result,
+
+From b23ab22f46f0d6529d51ee506eb87ff1d941378f Mon Sep 17 00:00:00 2001
+From: John Poet <jpoet(a)mythtv.org>
+Date: Thu, 1 Nov 2018 08:53:05 -0600
+Subject: [PATCH 50/52] ExternalStreamHandler: fix an inverted logic bug
+
+When checking for errors, it was treating warnings as errors.
+
+(cherry picked from commit 710fdf1946a4b3c2bea14f3d609bd76e027dca5e)
+---
+ .../recorders/ExternalStreamHandler.cpp | 21 ++++++++++++++-----
+ 1 file changed, 16 insertions(+), 5 deletions(-)
+
+diff --git a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+index 889fcf6da1d..ad74bf94faf 100644
+--- a/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
++++ b/mythtv/libs/libmythtv/recorders/ExternalStreamHandler.cpp
+@@ -585,6 +585,12 @@ void ExternalStreamHandler::run(void)
+ uint restart_cnt = 0;
+ MythTimer status_timer;
+
++ if (!m_IO)
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC +
++ QString("%1 is not running.").arg(_device));
++ }
++
+ status_timer.start();
+
+ RunProlog();
+@@ -640,7 +646,7 @@ void ExternalStreamHandler::run(void)
+ if (empty_cnt % 5000)
+ {
+ if (restart_cnt++)
+- std::this_thread::sleep_for(std::chrono::seconds(5));
++ std::this_thread::sleep_for(std::chrono::seconds(20));
+ if (!RestartStream())
+ _error = true;
+ xon = false;
+@@ -664,7 +670,7 @@ void ExternalStreamHandler::run(void)
+ if (CheckForError())
+ {
+ if (restart_cnt++)
+- std::this_thread::sleep_for(std::chrono::seconds(5));
++ std::this_thread::sleep_for(std::chrono::seconds(20));
+ if (!RestartStream())
+ _error = true;
+ xon = false;
+@@ -753,7 +759,12 @@ void ExternalStreamHandler::run(void)
+ buffer.remove(0, len - remainder);
+ }
+
+- if (m_IO->Error())
++ if (m_IO == nullptr)
++ {
++ LOG(VB_GENERAL, LOG_ERR, LOC + "I/O thread has disappeared!");
++ _error = true;
++ }
++ else if (m_IO->Error())
+ {
+ LOG(VB_GENERAL, LOG_ERR, LOC +
+ QString("Fatal Error from External Recorder: %1")
+@@ -1011,7 +1022,7 @@ bool ExternalStreamHandler::StartStreaming(void)
+ if (!ProcessCommand("StartStreaming", 1000, result, 10, 5))
+ {
+ LogLevel_t level = LOG_ERR;
+- if (!result.toLower().startsWith("warn"))
++ if (result.toLower().startsWith("warn"))
+ level = LOG_WARNING;
+ else
+ _error = true;
+@@ -1072,7 +1083,7 @@ bool ExternalStreamHandler::StopStreaming(void)
+ if (!ProcessCommand("StopStreaming", 1000, result, 10, 5))
+ {
+ LogLevel_t level = LOG_ERR;
+- if (!result.toLower().startsWith("warn"))
++ if (result.toLower().startsWith("warn"))
+ level = LOG_WARNING;
+ else
+ _error = true;
+
+From 987159173d17ce4d7d33e7237564022735092887 Mon Sep 17 00:00:00 2001
+From: Jonatan Lindblad <jlindblad(a)mythtv.org>
+Date: Wed, 31 Oct 2018 23:36:22 +0100
+Subject: [PATCH 51/52] Settings: Check child settings in
+ StandardSetting::byName
+
+Fixes #13317
+
+(cherry picked from commit 658dd95fd04041f5e995c573b12579cd4def63fe)
+---
+ mythtv/libs/libmyth/standardsettings.cpp | 13 +++++-
+ .../test/test_settings/test_settings.cpp | 3 ++
+ .../test/test_settings/test_settings.h | 32 +++++++++++++++
+ .../test/test_settings/test_settings.pro | 41 +++++++++++++++++++
+ 4 files changed, 87 insertions(+), 2 deletions(-)
+ create mode 100644 mythtv/libs/libmyth/test/test_settings/test_settings.cpp
+ create mode 100644 mythtv/libs/libmyth/test/test_settings/test_settings.h
+ create mode 100644 mythtv/libs/libmyth/test/test_settings/test_settings.pro
+
+diff --git a/mythtv/libs/libmyth/standardsettings.cpp
b/mythtv/libs/libmyth/standardsettings.cpp
+index c8b0e544acd..0345af525e5 100644
+--- a/mythtv/libs/libmyth/standardsettings.cpp
++++ b/mythtv/libs/libmyth/standardsettings.cpp
+@@ -259,7 +259,16 @@ void StandardSetting::setName(const QString &name)
+
+ StandardSetting* StandardSetting::byName(const QString &name)
+ {
+- return (name == m_name) ? this : NULL;
++ if (name == m_name)
++ return this;
++
++ foreach (StandardSetting *setting, *getSubSettings())
++ {
++ StandardSetting *s = setting->byName(name);
++ if (s)
++ return s;
++ }
++ return nullptr;
+ }
+
+ void StandardSetting::MoveToThread(QThread *thread)
+@@ -1149,4 +1158,4 @@ void StandardSettingDialog::deleteEntryConfirmed(bool ok)
+ m_buttonList->RemoveItem(item);
+ }
+
+-}
+\ No newline at end of file
++}
+diff --git a/mythtv/libs/libmyth/test/test_settings/test_settings.cpp
b/mythtv/libs/libmyth/test/test_settings/test_settings.cpp
+new file mode 100644
+index 00000000000..db94acec9ea
+--- /dev/null
++++ b/mythtv/libs/libmyth/test/test_settings/test_settings.cpp
+@@ -0,0 +1,3 @@
++#include "test_settings.h"
++
++QTEST_APPLESS_MAIN(TestSettings)
+diff --git a/mythtv/libs/libmyth/test/test_settings/test_settings.h
b/mythtv/libs/libmyth/test/test_settings/test_settings.h
+new file mode 100644
+index 00000000000..19394ed0785
+--- /dev/null
++++ b/mythtv/libs/libmyth/test/test_settings/test_settings.h
+@@ -0,0 +1,32 @@
++#include <QtTest/QtTest>
++
++#include "standardsettings.h"
++
++class ComboBox : public MythUIComboBoxSetting {
++};
++
++class TestSettings: public QObject
++{
++ Q_OBJECT
++
++ private slots:
++
++ void ByName(void)
++ {
++ GroupSetting parent;
++ auto setting = new ButtonStandardSetting("setting");
++ setting->setName("setting");
++ parent.addChild(setting);
++
++ auto combobox = new ComboBox();
++ auto targetedSetting = new ButtonStandardSetting("targetedsetting");
++ targetedSetting->setName("targetedsetting");
++ combobox->addTargetedChild("target", targetedSetting);
++ parent.addChild(combobox);
++
++ QVERIFY(parent.byName("setting") == setting);
++ QVERIFY(parent.byName("targetedsetting") == nullptr);
++ combobox->setValue("target");
++ QVERIFY(parent.byName("targetedsetting") == targetedSetting);
++ }
++};
+diff --git a/mythtv/libs/libmyth/test/test_settings/test_settings.pro
b/mythtv/libs/libmyth/test/test_settings/test_settings.pro
+new file mode 100644
+index 00000000000..804958b639e
+--- /dev/null
++++ b/mythtv/libs/libmyth/test/test_settings/test_settings.pro
+@@ -0,0 +1,41 @@
++include ( ../../../../settings.pro )
++
++QT += sql testlib widgets
++
++TEMPLATE = app
++TARGET = test_settings
++INCLUDEPATH += ../..
++INCLUDEPATH += ../../../libmythbase
++INCLUDEPATH += ../../../libmythui
++
++LIBS += -L../.. -lmyth-$$LIBVERSION
++LIBS += -L../../../libmythbase -lmythbase-$$LIBVERSION
++LIBS += -L../../../libmythservicecontracts -lmythservicecontracts-$$LIBVERSION
++LIBS += -L../../../libmythui -lmythui-$$LIBVERSION
++LIBS += -L../../../libmythupnp -lmythupnp-$$LIBVERSION
++LIBS += -L../../../../external/FFmpeg/libavcodec -lmythavcodec
++LIBS += -L../../../../external/FFmpeg/libavformat -lmythavformat
++LIBS += -L../../../../external/FFmpeg/libavutil -lmythavutil
++LIBS += -L../../../../external/FFmpeg/libswresample -lmythswresample
++
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../..
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../libmythbase
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../libmythservicecontracts
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../libmythui
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../libmythupnp
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../../external/FFmpeg/libavcodec
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../../external/FFmpeg/libavformat
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../../external/FFmpeg/libavutil
++QMAKE_LFLAGS += -Wl,$$_RPATH_$(PWD)/../../../../external/FFmpeg/libswresample
++
++# Input
++HEADERS += test_settings.h
++SOURCES += test_settings.cpp
++
++QMAKE_CLEAN += $(TARGET) $(TARGETA) $(TARGETD) $(TARGET0) $(TARGET1) $(TARGET2)
++QMAKE_CLEAN += ; ( cd $(OBJECTS_DIR) && rm -f *.gcov *.gcda *.gcno )
++
++LIBS += $$EXTRA_LIBS $$LATE_LIBS
++
++# Fix runtime linking on Ubuntu 17.10.
++linux:QMAKE_LFLAGS += -Wl,--disable-new-dtags
+
+From 9f0acf372dec880800a57a77d67c19a68dedbd24 Mon Sep 17 00:00:00 2001
+From: Stuart Morgan <smorgan(a)mythtv.org>
+Date: Tue, 26 Jun 2018 16:45:00 +0100
+Subject: [PATCH 52/52] Probable fix for command line scanning not working on
+ headless box
+
+(cherry picked from commit 89a158e26ffb16ea14cbf9e87ab6aba39cee96ae)
+---
+ mythtv/programs/mythtv-setup/main.cpp | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/mythtv/programs/mythtv-setup/main.cpp
b/mythtv/programs/mythtv-setup/main.cpp
+index eb82858de94..4efaefed3d0 100644
+--- a/mythtv/programs/mythtv-setup/main.cpp
++++ b/mythtv/programs/mythtv-setup/main.cpp
+@@ -311,12 +311,19 @@ int main(int argc, char *argv[])
+
+ CleanupGuard callCleanup(cleanup);
+
++ if (use_display)
++ {
++
+ #ifdef Q_OS_MAC
+- // Without this, we can't set focus to any of the CheckBoxSetting, and most
+- // of the MythPushButton widgets, and they don't use the themed background.
+- QApplication::setDesktopSettingsAware(false);
++ // Without this, we can't set focus to any of the CheckBoxSetting, and most
++ // of the MythPushButton widgets, and they don't use the themed background.
++ QApplication::setDesktopSettingsAware(false);
+ #endif
+- new QApplication(argc, argv, use_display);
++ new QApplication(argc, argv);
++ }
++ else {
++ new QCoreApplication(argc, argv);
++ }
+ QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHTV_SETUP);
+
+ #ifndef _WIN32