Skip to content

Instantly share code, notes, and snippets.

@tdack
Last active December 31, 2015 12:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tdack/7984364 to your computer and use it in GitHub Desktop.
Save tdack/7984364 to your computer and use it in GitHub Desktop.
Commercial detection with silence for UK freeviewHD and Australian Freeview SD/HDSee http://www.mythtv.org/wiki/Commercial_detection_with_silence_for_UK_freeviewHD for detailsRunning:Assuming you use the same locations, your 'Advert-detection command' (mythtv-setup/General/Page 8) should be: /usr/local/bin/silence.py %JOBID% %VERBOSEMODE% --logl…
CC = g++
CFLAGS = -c -Wall -std=c++0x
LIBPATH = -L/usr/lib
TARGETDIR = /usr/local/bin
.PHONY: clean install
all: silence
silence: silence.o
$(CC) silence.o -o $@ $(LIBPATH) -lsndfile
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
install: silence silence.py
install -p -t $(TARGETDIR) $^
clean:
-rm -f silence *.o
// Based on mausc.c by Tinsel Phipps.
// v1.0 Roger Siddons
// v2.0 Roger Siddons: Flag clusters asap, fix segfaults, optional headers
// v3.0 Roger Siddons: Remove lib dependencies & commfree
// v4.0 Kill process argv[1] when idle for 30 seconds.
// v4.1 Fix averaging overflow
// v4.2 Unblock the alarm signal so the job actually finishes.
// Public domain. Requires libsndfile
// Detects commercial breaks using clusters of audio silences
#include <cstdlib>
#include <cmath>
#include <cerrno>
#include <climits>
#include <deque>
#include <sndfile.h>
#include <unistd.h>
#include <signal.h>
typedef unsigned frameNumber_t;
typedef unsigned frameCount_t;
// Output to python wrapper requires prefix to indicate level
#define DELIMITER "@" // must correlate with python wrapper
char prefixdebug[7] = "debug" DELIMITER;
char prefixinfo[6] = "info" DELIMITER;
char prefixerr[5] = "err" DELIMITER;
char prefixcut[5] = "cut" DELIMITER;
void error(const char* mesg, bool die = true)
{
printf("%s%s\n", prefixerr, mesg);
if (die)
exit(1);
}
pid_t tail_pid = 0;
void watchdog(int sig)
{
if (0 != tail_pid)
kill(tail_pid, SIGTERM);
}
namespace Arg
// Program argument management
{
const float kvideoRate = 25.0; // sample rate in fps (maps time to frame count)
const frameCount_t krateInMins = kvideoRate * 60; // frames per min
unsigned useThreshold; // Audio level of silence
frameCount_t useMinQuiet; // Minimum length of a silence to register
unsigned useMinDetect; // Minimum number of silences that constitute an advert
frameCount_t useMinLength; // adverts must be at least this long
frameCount_t useMaxSep; // silences must be closer than this to be in the same cluster
frameCount_t usePad; // padding for each cut
void usage()
{
error("Usage: silence <tail_pid> <threshold> <minquiet> <mindetect> <minlength> <maxsep> <pad>", false);
error("<tail_pid> : (int) Process ID to be killed after idle timeout.", false);
error("<threshold>: (float) silence threshold in dB.", false);
error("<minquiet> : (float) minimum time for silence detection in seconds.", false);
error("<mindetect>: (float) minimum number of silences to constitute an advert.", false);
error("<minlength>: (float) minimum length of advert break in seconds.", false);
error("<maxsep> : (float) maximum time between silences in an advert break in seconds.", false);
error("<pad> : (float) padding for each cut point in seconds.", false);
error("AU format audio is expected on stdin.", false);
error("Example: silence 4567 -75 0.1 5 60 90 1 < audio.au");
}
void parse(int argc, char **argv)
// Parse args and convert to useable values (frames)
{
if (8 != argc)
usage();
float argThreshold; // db
float argMinQuiet; // secs
float argMinDetect;
float argMinLength; // secs
float argMaxSep; // secs
float argPad; // secs
/* Load options. */
if (1 != sscanf(argv[1], "%d", &tail_pid))
error("Could not parse tail_pid option into a number");
if (1 != sscanf(argv[2], "%f", &argThreshold))
error("Could not parse threshold option into a number");
if (1 != sscanf(argv[3], "%f", &argMinQuiet))
error("Could not parse minquiet option into a number");
if (1 != sscanf(argv[4], "%f", &argMinDetect))
error("Could not parse mindetect option into a number");
if (1 != sscanf(argv[5], "%f", &argMinLength))
error("Could not parse minlength option into a number");
if (1 != sscanf(argv[6], "%f", &argMaxSep))
error("Could not parse maxsep option into a number");
if (1 != sscanf(argv[7], "%f", &argPad))
error("Could not parse pad option into a number");
/* Scale threshold to integer range that libsndfile will use. */
useThreshold = rint(INT_MAX * pow(10, argThreshold / 20));
/* Scale times to frames. */
useMinQuiet = ceil(argMinQuiet * kvideoRate);
useMinDetect = (int)argMinDetect;
useMinLength = ceil(argMinLength * kvideoRate);
useMaxSep = rint(argMaxSep * kvideoRate + 0.5);
usePad = rint(argPad * kvideoRate + 0.5);
printf("%sThreshold=%.1f, MinQuiet=%.2f, MinDetect=%.1f, MinLength=%.1f, MaxSep=%.1f, Pad=%.2f\n",
prefixdebug, argThreshold, argMinQuiet, argMinDetect, argMinLength, argMaxSep, argPad);
printf("%sFrame rate is %.2f, Detecting silences below %d that last for at least %d frames\n",
prefixdebug, kvideoRate, useThreshold, useMinQuiet);
printf("%sClusters are composed of a minimum of %d silences closer than %d frames and must be\n",
prefixdebug, useMinDetect, useMaxSep);
printf("%slonger than %d frames in total. Cuts will be padded by %d frames\n",
prefixdebug, useMinLength, usePad);
printf("%s< preroll, > postroll, - advert, ? too few silences, # too short, = comm flagged\n", prefixdebug);
printf("%s Start - End Start - End Duration Interval Level/Count\n", prefixinfo);
printf("%s frame - frame (mmm:ss-mmm:ss) frame (mm:ss.s) frame (mmm:ss)\n", prefixinfo);
}
}
class Silence
// Defines a silence
{
public:
enum state_t {progStart, detection, progEnd};
static const char state_log[3];
const state_t state; // type of silence
const frameNumber_t start; // frame of start
frameNumber_t end; // frame of end
frameCount_t length; // number of frames
frameCount_t interval; // frames between end of last silence & start of this one
double power; // average power level
Silence(frameNumber_t _start, double _power = 0, state_t _state = detection)
: state(_state), start(_start), end(_start), length(1), interval(0), power(_power) {}
void extend(frameNumber_t frame, double _power)
// Define end of the silence
{
end = frame;
length = frame - start + 1;
// maintain running average power: = (oldpower * (newlength - 1) + newpower)/ newlength
power += (_power - power)/length;
}
};
// c++0x doesn't allow initialisation within class
const char Silence::state_log[3] = {'<', ' ', '>'};
class Cluster
// A cluster of silences
{
private:
void setState()
{
if (this->start->start == 1)
state = preroll;
else if (this->end->state == Silence::progEnd)
state = postroll;
else if (length < Arg::useMinLength)
state = tooshort;
else if (silenceCount < Arg::useMinDetect)
state = toofew;
else
state = advert;
}
public:
// tooshort..unset are transient states - they may be updated, preroll..postroll are final
enum state_t {tooshort, toofew, unset, preroll, advert, postroll};
static const char state_log[6];
static frameNumber_t completesAt; // frame where the most recent cluster will complete
state_t state; // type of cluster
const Silence* start; // first silence
Silence* end; // last silence
frameNumber_t padStart, padEnd; // padded cluster start/end frames
unsigned silenceCount; // number of silences
frameCount_t length; // number of frames
frameCount_t interval; // frames between end of last cluster and start of this one
Cluster(Silence* s) : state(unset), start(s), end(s), silenceCount(1), length(s->length), interval(0)
{
completesAt = end->end + Arg::useMaxSep; // finish cluster <maxsep> beyond silence end
setState();
// pad everything except pre-rolls
padStart = (state == preroll ? 1 : start->start + Arg::usePad);
}
void extend(Silence* _end)
// Define end of a cluster
{
end = _end;
silenceCount++;
length = end->end - start->start + 1;
completesAt = end->end + Arg::useMaxSep; // finish cluster <maxsep> beyond silence end
setState();
// pad everything except post-rolls
padEnd = end->end - (state == postroll ? 0 : Arg::usePad);
}
};
// c++0x doesn't allow initialisation within class
const char Cluster::state_log[6] = {'#', '?', '.', '<', '-', '>'};
frameNumber_t Cluster::completesAt = 0;
class ClusterList
// Manages a list of detected silences and a list of assigned clusters
{
protected:
// list of detected silences
std::deque<Silence*> silence;
// list of deduced clusters of the silences
std::deque<Cluster*> cluster;
public:
Silence* insertStartSilence()
// Inserts a fake silence at the front of the silence list
{
// create a single frame silence at frame 1 and insert it at front
Silence* ref = new Silence(1, 0, Silence::progStart);
silence.push_front(ref);
return ref;
}
void addSilence(Silence* newSilence)
// Adds a silence detection to the end of the silence list
{
// set interval between this & previous silence/prog start
newSilence->interval = newSilence->start
- (silence.empty() ? 1 : silence.back()->end - 1);
// store silence
silence.push_back(newSilence);
}
void addCluster(Cluster* newCluster)
// Adds a cluster to end of the cluster list
{
// set interval between new cluster & previous one/prog start
newCluster->interval = newCluster->start->start
- (cluster.empty() ? 1 : cluster.back()->end->end - 1);
// store cluster
cluster.push_back(newCluster);
}
};
Silence* currentSilence; // the silence currently being detected/built
Cluster* currentCluster; // the cluster currently being built
ClusterList* clist; // List of completed silences & clusters
void report(const char* err,
const char type,
const char* msg1,
const frameNumber_t start,
const frameNumber_t end,
const frameNumber_t interval,
const int power)
// Logs silences/clusters/cuts in a standard format
{
frameCount_t duration = end - start + 1;
printf("%s%c %7s %6d-%6d (%3d:%02ld-%3d:%02ld), %4d (%2d:%04.1f), %5d (%3d:%02ld), [%7d]\n",
err, type, msg1, start, end,
(start+13) / Arg::krateInMins, lrint(start / Arg::kvideoRate) % 60,
(end+13) / Arg::krateInMins, lrint(end / Arg::kvideoRate) % 60,
duration, (duration+1) / Arg::krateInMins, fmod(duration / Arg::kvideoRate, 60),
interval, (interval+13) / Arg::krateInMins, lrint(interval / Arg::kvideoRate) % 60, power);
}
void processSilence()
// Process a silence detection
{
// ignore detections that are too short
if (currentSilence->state == Silence::detection && currentSilence->length < Arg::useMinQuiet)
{
// throw it away
delete currentSilence;
currentSilence = NULL;
}
else
{
// record new silence
clist->addSilence(currentSilence);
// assign it to a cluster
if (currentCluster)
{
// add to existing cluster
currentCluster->extend(currentSilence);
}
else if (currentSilence->interval <= Arg::useMaxSep) // only possible for very first silence
{
// First silence is close to prog start so extend cluster to the start
// by inserting a fake silence at prog start and starting the cluster there
currentCluster = new Cluster(clist->insertStartSilence());
currentCluster->extend(currentSilence);
}
else
{
// this silence is the start of a new cluster
currentCluster = new Cluster(currentSilence);
}
report(prefixdebug, currentSilence->state_log[currentSilence->state], "Silence",
currentSilence->start, currentSilence->end,
currentSilence->interval, currentSilence->power);
// silence is now owned by the list, start looking for next
currentSilence = NULL;
}
}
void processCluster()
// Process a completed cluster
{
// record new cluster
clist->addCluster(currentCluster);
report(prefixinfo, currentCluster->state_log[currentCluster->state], "Cluster",
currentCluster->start->start, currentCluster->end->end,
currentCluster->interval, currentCluster->silenceCount);
// only flag clusters at final state
if (currentCluster->state > Cluster::unset)
report(prefixcut, '=', "Cut", currentCluster->padStart, currentCluster->padEnd, 0, 0);
// cluster is now owned by the list, start looking for next
currentCluster = NULL;
}
int main(int argc, char **argv)
// Detect silences and allocate to clusters
{
// Remove logging prefixes if writing to terminal
if (isatty(1))
prefixcut[0] = prefixinfo[0] = prefixdebug[0] = prefixerr[0] = '\0';
// flush output buffer after every line
setvbuf(stdout, NULL, _IOLBF, 0);
Arg::parse(argc, argv);
/* Check the input is an audiofile. */
SF_INFO metadata;
SNDFILE* input = sf_open_fd(STDIN_FILENO, SFM_READ, &metadata, SF_FALSE);
if (NULL == input) {
error("libsndfile error:", false);
error(sf_strerror(NULL));
}
/* Allocate data buffer to contain audio data from one video frame. */
const size_t frameSamples = metadata.channels * metadata.samplerate / Arg::kvideoRate;
int* samples = (int*)malloc(frameSamples * sizeof(int));
if (NULL == samples)
error("Couldn't allocate memory");
// create silence/cluster list
clist = new ClusterList();
// Kill head of pipeline if timeout happens.
signal(SIGALRM, watchdog);
sigset_t intmask;
sigemptyset(&intmask);
sigaddset(&intmask, SIGALRM);
sigprocmask(SIG_UNBLOCK, &intmask, NULL);
alarm(30);
// Process the input one frame at a time and process cuts along the way.
frameNumber_t frames = 0;
while (frameSamples == static_cast<size_t>(sf_read_int(input, samples, frameSamples)))
{
alarm(30);
frames++;
// determine average audio level in this frame
unsigned long long avgabs = 0;
for (unsigned i = 0; i < frameSamples; i++)
avgabs += abs(samples[i]);
avgabs = avgabs / frameSamples;
// check for a silence
if (avgabs < Arg::useThreshold)
{
if (currentSilence)
{
// extend current silence
currentSilence->extend(frames, avgabs);
}
else // transition to silence
{
// start a new silence
currentSilence = new Silence(frames, avgabs);
}
}
else if (currentSilence) // transition out of silence
{
processSilence();
}
// in noise: check for cluster completion
else if (currentCluster && frames > currentCluster->completesAt)
{
processCluster();
}
}
// Complete any current silence (prog may have finished in silence)
if (currentSilence)
{
processSilence();
}
// extend any cluster close to prog end
if (currentCluster && frames <= currentCluster->completesAt)
{
// generate a silence at prog end and extend cluster to it
currentSilence = new Silence(frames, 0, Silence::progEnd);
processSilence();
}
// Complete any final cluster
if (currentCluster)
{
processCluster();
}
}
#!/usr/bin/env python
# Build a skiplist from silence in the audio track.
# v1.0 Roger Siddons
# v2.0 Fix progid for job/player messages
# v3.0 Send player messages via Python
# v3.1 Fix commflag status, pad preset. Improve style & make Python 3 compatible
# v4.0 silence.cpp will kill the head of the pipeline (tail) when recording finished
# v4.1 Use unicode for foreign chars
# v4.2 Prevent BE writeStringList errors
# v5.0 Improve exception handling/logging. Fix player messages (0.26+ only)
import MythTV
import os
import subprocess
import argparse
import collections
import re
import sys
kExe_Silence = '/usr/local/bin/silence'
kUpmix_Channels = '6' # Change this to 2 if you never have surround sound in your recordings.
class MYLOG(MythTV.MythLog):
"A specialised logger"
def __init__(self, db):
"Initialise logging"
MythTV.MythLog.__init__(self, '', db)
def log(self, msg, level = MythTV.MythLog.INFO):
"Log message"
# prepend string to msg so that rsyslog routes it to mythcommflag.log logfile
MythTV.MythLog.log(self, MythTV.MythLog.COMMFLAG, level, 'mythcommflag: ' + msg.rstrip('\n'))
class PRESET:
"Manages the presets (parameters passed to the detection algorithm)"
# define arg ordering and default values
argname = ['thresh', 'minquiet', 'mindetect', 'minbreak', 'maxsep', 'pad']
argval = [ -75, 0.16, 6, 120, 120, 0.48]
# dictionary holds value for each arg
argdict = collections.OrderedDict(list(zip(argname, argval)))
def _validate(self, k, v):
"Converts arg input from string to float or None if invalid/not supplied"
if v is None or v == '':
return k, None
try:
return k, float(v)
except ValueError:
self.logger.log('Preset ' + k + ' (' + str(v) + ') is invalid - will use default',
MYLOG.ERR)
return k, None
def __init__(self, _logger):
"Initialise preset manager"
self.logger = _logger
def getFromArg(self, line):
"Parses preset values from command-line string"
self.logger.log('Parsing presets from "' + line + '"', MYLOG.DEBUG)
if line: # ignore empty string
vals = [i.strip() for i in line.split(',')] # split individual params
# convert supplied values to float & match to appropriate arg name
validargs = list(map(self._validate, self.argname, vals[0:len(self.argname)]))
# remove missing/invalid values from list & replace default values with the rest
self.argdict.update(v for v in validargs if v[1] is not None)
def getFromFile(self, filename, title, callsign):
"Gets preset values from a file"
self.logger.log('Using preset file "' + filename + '"', MYLOG.DEBUG)
try:
with open(filename) as presets:
for rawline in presets:
line = rawline.strip()
if line and (not line.startswith('#')): # ignore empty & comment lines
vals = [i.strip() for i in line.split(',')] # split individual params
# match preset name to recording title or channel
pattern = re.compile(vals[0], re.IGNORECASE)
if pattern.match(title) or pattern.match(callsign):
self.logger.log('Using preset "' + line.strip() + '"')
# convert supplied values to float & match to appropriate arg name
validargs = list(map(self._validate, self.argname,
vals[1:1 + len(self.argname)]))
# remove missing/invalid values from list &
# replace default values with the rest
self.argdict.update(v for v in validargs if v[1] is not None)
break
else:
self.logger.log('No preset found for "' + title.encode('utf-8') + '" or "' + callsign.encode('utf-8') + '"')
except IOError:
self.logger.log('Presets file "' + filename + '" not found', MYLOG.ERR)
return self.argdict
def getValues(self):
"Returns params as a list of strings"
return [str(i) for i in list(self.argdict.values())]
def main():
"Commflag a recording"
try:
# define options
parser = argparse.ArgumentParser(description='Commflagger')
parser.add_argument('--preset', help='Specify values as "Threshold, MinQuiet, MinDetect, MinLength, MaxSep, Pad"')
parser.add_argument('--presetfile', help='Specify file containing preset values')
parser.add_argument('--chanid', type=int, help='Use chanid for manual operation')
parser.add_argument('--starttime', help='Use starttime for manual operation')
parser.add_argument('--dump', action="store_true", help='Generate stack trace of exception')
parser.add_argument('jobid', nargs='?', help='Myth job id')
# must set up log attributes before Db locks them
MYLOG.loadArgParse(parser)
MYLOG._setmask(MYLOG.COMMFLAG)
# parse options
args = parser.parse_args()
# connect to backend
db = MythTV.MythDB()
logger = MYLOG(db)
be = MythTV.BECache(db=db)
logger.log('') # separate jobs in logfile
if args.jobid:
logger.log('Starting job %s'%args.jobid, MYLOG.INFO)
job = MythTV.Job(args.jobid, db)
chanid = job.chanid
starttime = job.starttime
elif args.chanid and args.starttime:
job = None
chanid = args.chanid
try:
# only 0.26+
starttime = MythTV.datetime.duck(args.starttime)
except AttributeError:
starttime = args.starttimeaction="store_true"
else:
logger.log('Both --chanid and -starttime must be specified', MYLOG.ERR)
sys.exit(1)
# mythplayer update message uses a 'chanid_utcTimeAsISODate' format to identify recording
try:
# only 0.26+
utc = starttime.asnaiveutc()
except AttributeError:
utc = starttime
progId = '%d_%s'%(chanid, str(utc).replace(' ', 'T'))
# get recording
logger.log('Seeking chanid %s, starttime %s' %(chanid, starttime), MYLOG.INFO)
rec = MythTV.Recorded((chanid, starttime), db)
channel = MythTV.Channel(chanid, db)
logger.log('Processing: ' + channel.callsign.encode('utf-8') + ', ' + str(rec.starttime)
+ ', "' + rec.title.encode('utf-8') + ' - ' + rec.subtitle.encode('utf-8')+ '"')
sg = MythTV.findfile(rec.basename, rec.storagegroup, db)
if sg is None:
logger.log("Can't access file %s from %s"%(rec.basename, rec.storagegroup), MYLOG.ERR)
try:
job.update({'status': job.ERRORED, 'comment': "Couldn't access file"})
except AttributeError : pass
sys.exit(1)
# create params with default values
param = PRESET(logger)
# read any supplied presets
if args.preset:
param.getFromArg(args.preset)
elif args.presetfile: # use preset file
param.getFromFile(args.presetfile, rec.title, channel.callsign)
# Pipe file through ffmpeg to extract uncompressed audio stream. Keep going till recording is finished.
infile = os.path.join(sg.dirname, rec.basename)
p1 = subprocess.Popen(["tail", "--follow", "--bytes=+1", infile], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["mythffmpeg", "-loglevel", "quiet", "-i", "pipe:0",
"-f", "au", "-ac", kUpmix_Channels, "-"],
stdin=p1.stdout, stdout=subprocess.PIPE)
# Pipe audio stream to C++ silence which will spit out formatted log lines
p3 = subprocess.Popen([kExe_Silence, "%d" % p1.pid] + param.getValues(), stdin=p2.stdout,
stdout=subprocess.PIPE)
# Purge any existing skip list and flag as in-progress
rec.commflagged = 2
rec.markup.clean()
rec.update()
# Process log output from C++ silence
breaks = 0
level = {'info': MYLOG.INFO, 'debug': MYLOG.DEBUG, 'err': MYLOG.ERR}
while True:
line = p3.stdout.readline()
if line:
flag, info = line.split('@', 1)
if flag == 'cut':
# extract numbers from log line
numbers = re.findall('\d+', info)
logger.log(info)
# mark advert in database
rec.markup.append(int(numbers[0]), rec.markup.MARK_COMM_START, None)
rec.markup.append(int(numbers[1]), rec.markup.MARK_COMM_END, None)
rec.update()
breaks += 1
# send new advert skiplist to MythPlayers
skiplist = ['%d:%d,%d:%d'%(x, rec.markup.MARK_COMM_START, y, rec.markup.MARK_COMM_END)
for x, y in rec.markup.getskiplist()]
mesg = 'COMMFLAG_UPDATE %s %s'%(progId, ','.join(skiplist))
# logger.log(' Sending %s'%mesg, MYLOG.DEBUG)
result = be.backendCommand("MESSAGE[]:[]" + mesg)
if result != 'OK':
logger.log('Sending update message to backend failed, response = %s, message = %s'% (result, mesg), MYLOG.ERR)
elif flag in level:
logger.log(info, level.get(flag))
else: # unexpected prefix
# use warning for unexpected log levels
logger.log(flag, MYLOG.WARNING)
else:
break
# Signal comflagging has finished
rec.commflagged = 1
rec.update()
logger.log('Detected %s adverts.' % breaks)
try:
job.update({'status': 272, 'comment': 'Detected %s adverts.' % breaks})
except AttributeError : pass
# Finishing too quickly can cause writeStringList/socket errors in the BE. (pre-0.28 only?)
# A short delay prevents this
import time
time.sleep(1)
except Exception as e:
# get exception before we generate another
import traceback
exc_type, exc_value, frame = sys.exc_info()
# get stacktrace as a list
stack = traceback.format_exception(exc_type, exc_value, frame)
# set status
status = 'Failed due to: "%s"'%e
try:
logger.log(status, MYLOG.ERR)
except : pass
try:
job.update({'status': job.ERRORED, 'comment': 'Failed.'})
except : pass
# populate stack trace with vars
try:
if args.dump:
# insert the frame local vars after each frame trace
# i is the line index following the frame trace; 0 is the trace mesg, 1 is the first code line
i = 2
while frame is not None:
# format local vars
vars = []
for name, var in frame.tb_frame.f_locals.iteritems():
try:
text = '%s' % var
# truncate vars that are too long
if len(text) > 1000:
text = text[:1000] + '...'
except Exception as e: # some var defs may be incomplete
text = '<Unknown due to: %s>'%e
vars.append('@ %s = %s'%(name, text))
# insert local stack contents after code trace line
stack.insert(i, '\n'.join(['@-------------'] + vars + ['@-------------\n']))
# advance over our insertion & the next code trace line
i += 2
frame = frame.tb_next
logger.log('\n'.join(stack), MYLOG.ERR)
except : pass
sys.exit(1)
if __name__ == '__main__':
main()
# presets for silence.py
# use comma separated values: defaults are used for absent values
# For titles/callsign the name is a python regular expressions, case is ignored.
# Re Metachars are # . ^ $ * + ? { } [ ] \ | ( )
# If a title contains one of these, then escape it (using \) or replace it with full stop
# Names are matched to the START of a title/callsign so "e4" also matches "e4+1"
# First name match is used so put specific presets (ie. programmes) before general ones (channels)
#
# title/callsign, threshold, minquiet, mindetect, minbreak, maxsep, padding
# defaults -75, 0.16, 6, 120, 120, 0.48,
#
# Defaults for Australian Freeview channels.
NINE DIGITAL, -73, 0.16, 6, 150, 60, 0.48,
GEM, -73, 0.16, 5, 120, 60, 0.48,
GO!, -73, 0.16, 5, 120, 60, 0.48,
7 Digital, -75, 0.16, 5, 150, 60, 0.48,
#ABC1 - No ads, have not bothered attempting to configure for preroll or postroll.
#ABC News 24 - No ads
#ABC2 - ABC4 - No ads, have not bothered attempting to configure for preroll or postroll.
#ABC3 - No ads, have not bothered attempting to configure for preroll or postroll.
#7mate – defaults working okay with limited testing
#7TWO – defaults working okay so far
#TEN Digital – defaults working okay so far
#ELEVEN – defaults working okay so far
#ONE – defaults working okay so far
#SBS ONE – defaults working okay with limited testing
#SBS TWO – defaults working okay with limited testing
#SBS HD – defaults working okay with limited testing
# presets for silence.py
# use comma separated values: defaults are used for absent values
# For titles/callsign the name is a python regular expressions, case is ignored.
# Re Metachars are # . ^ $ * + ? { } [ ] \ | ( )
# If a title contains one of these, then escape it (using \) or replace it with full stop
# Names are matched to the START of a title/callsign so "e4" also matches "e4+1"
# First name match is used so put specific presets (ie. programmes) before general ones (channels)
#
# title/callsign, threshold, minquiet, mindetect, minbreak, maxsep, padding
# defaults -75, 0.16, 6, 120, 120, 0.48,
#
frasier, , 0.28, , , 91, , long pauses in prog
channel 4 news, , 1.00, 1, 55, , 0, short advert, many silences
milkshake, , 0.48, 8, 60, 61, , ignore short silences in animation/links
rude tube, , 0.32, , 180, 61, , ignore short silences in links
channel 4, , 0.24,
more 4, , 0.24,
dave, -71, , , , , , loud silences
quest, , , , , 55, , short silences, long breaks, short ads
channel 5, , 0.24, 2, , 300, , cut news out of films
itv, , , , , , 1.0, long pad for films
film 4, , , , , , 1.0, long pad for films
bbc, , 0.48, 1, 20, 360, 0, pre/post-roll
cbeebies, , 0.48, 1, 20, 360, 0, pre/post-roll
cbbc, , 0.48, 1, 20, 360, 0, pre/post-roll
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment