Here's the preprocess script I'm using to start comskip when the recording starts. The timestamps are ready a minute or so after the recording has completed regardless of the recording's duration, as opposed to a delay of 30% of the recording's duration when comskip is started as a postprocess script.
I couldn't get comskip to write live edl files with ranges ending in '3', all ranges ended in '0' despite setting edl_mode
to 3
in comskip.ini
. It's possible the commercials weren't correctly detected on the few recordings I've tried. It needs more testing. The timestamps aren't available on a recording that's still in progress, but it's an improvement to have them available right after it finishes.
The script is adapted from the script tvhmeta
:
This complication could be omitted if tvheadend passed the recording's filename as %f
to the preprocessor script. The recording's filename could theoretically be determined before the file has been created.
tvhfilenamefetcher.py
:
#! /usr/bin/env python3
import sys
import json
import argparse
import base64
import logging
import time
import urllib
import urllib.parse
import urllib.request
urlencode = urllib.parse.urlencode
urlopen = urllib.request.urlopen
class FilenameFetcher():
def __init__(self, host, port, user, password, uuid):
self.host = host
self.port = port
self.user = user
self.password = password
self.uuid = uuid
def request(self, url, data):
"""Send a request for data to Tvheadend."""
ret = ''
full_url = f'http://{self.host}:{self.port}/{url}'
try:
req = urllib.request.Request(full_url, method="POST", data=bytearray(data, 'utf-8'))
except (urllib.error.HTTPError, urllib.error.URLError) as urllib_err:
logging.debug('Failed with %s', urllib_err)
return ret
if self.user is not None and self.password is not None:
base64string = base64.b64encode(bytes(f'{self.user}:{self.password}','ascii'))
req.add_header('Authorization', f'Basic {base64string.decode("utf-8")}')
logging.info("Sending %s to %s", data, full_url)
try:
with urllib.request.urlopen(req) as url_fp:
got = url_fp.read()
if got:
ret = got.decode('UTF-8', errors='replace')
logging.debug("Received: %s", ret)
except (urllib.error.HTTPError, urllib.error.URLError) as urllib_err:
pass
logging.debug('Failed with %s', urllib_err)
return ret
def fetch(self):
data = urlencode(
{"uuid": self.uuid,
"list": "uuid,filename,errorcode",
"grid": 1
})
okay = True
recjson = self.request("api/idnode/load", data)
try:
recall = json.loads(recjson)
recentries = recall["entries"]
if len(recentries) == 0:
okay = False
except json.JSONDecodeError :
okay = False
if not okay:
raise RuntimeError("No entries found for uuid " + self.uuid)
rec = recentries[0]
errorcode = rec["errorcode"]
filename = rec["filename"]
if errorcode != 0 or len(filename) == 0:
raise DVRNotReady()
else:
return filename
class DVRNotReady(Exception):
pass
if __name__ == '__main__':
optp = argparse.ArgumentParser(
description="Fetch filename from just-starting recording")
optp.add_argument('--host', default='localhost',
help='Specify HTSP server hostname')
optp.add_argument('--port', default=9981, type=int,
help='Specify HTTP server port')
optp.add_argument('--user', default=None,
help='Specify HTTP authentication username')
optp.add_argument('--password', default=None,
help='Specify HTTP authentication password')
optp.add_argument('--uuid', default=None,
help='Specify UUID on which to operate')
# optp.add_argument('--debug', default=None, action="store_true",
#help='Enable debug.')
(opts, remaining_args) = optp.parse_known_args(sys.argv[1:])
#if opts.debug:
logging.root.setLevel(logging.DEBUG)
#logging.debug("Got args %s and remaining_args %s", opts, remaining_args)
filenamefetcher = FilenameFetcher(opts.host, opts.port, opts.user, opts.password, opts.uuid)
if opts.uuid is None:
logging.error("Need --uuid")
sys.exit(1)
for i in range(12) :
time.sleep(5)
try:
print(filenamefetcher.fetch())
sys.exit(0)
except KeyboardInterrupt:
sys.exit(1)
except DVRNotReady:
logging.debug("Waiting for DVR event start")
pass
except (KeyError, RuntimeError) as err:
logging.error("Failed to process with error: %s", str(err))
sys.exit(1)
tvheadend-recording-preprocess.sh
:
#!/bin/bash
REC_FILENAME=$(tvhfilenamefetcher.py --user=user --password=pass --uuid=$1)
if [ $? -eq 0 ] && [ -n "$REC_FILENAME" ]; then
comskip -n --threads=4 --ini=/var/lib/tvheadend/comskip_pre.ini "$REC_FILENAME"
fi
comskip.ini
:
live_tv=1
live_tv_retries=16
play_nice_sleep=20
Set play_nice_sleep=
to a value that makes comskip run close to realtime (25fps). To test it, monitor tvheadend's console output.
In Config -> Recording -> (Default profile):
Set the preprocess script to:
/usr/local/bin/tvheadend-recording-preprocess.sh "%U"
In Config -> Base: "Authentication type" needs to be set to "Plain" or "Plain and digest" for this to work.