Recorder: changed into a twistd plugin, add minimum recording time, allow specifying directory where to store recordings
This commit is contained in:
parent
82ebc68e02
commit
4468c05d9c
131
recorder.py
131
recorder.py
|
@ -1,131 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
|
|
||||||
# See LICENSE.txt for copyright info
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
from frn.protocol.client import FRNClient, FRNClientFactory
|
|
||||||
from frn.user import FRNUser
|
|
||||||
from twisted.internet import reactor, task
|
|
||||||
from twisted.internet.defer import DeferredList
|
|
||||||
from twisted.python import log
|
|
||||||
import os, string, time, struct
|
|
||||||
safe_chars = string.ascii_letters+string.digits+' :;.,+-=$@'
|
|
||||||
|
|
||||||
WAV_HEADER = "524946460c06000057415645666d74201400000031000100401f00005906000041000000020040016661637404000000000000006461746100000000".decode('hex')
|
|
||||||
AUDIO_TIMEOUT = 5.0
|
|
||||||
|
|
||||||
def sanitizeFilename(name):
|
|
||||||
r = ''
|
|
||||||
for c in name:
|
|
||||||
if c in safe_chars:
|
|
||||||
r += c
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
class FRNRecorder(FRNClient):
|
|
||||||
|
|
||||||
def getClientName(self, client_id):
|
|
||||||
if self.clientsById.has_key(client_id):
|
|
||||||
return self.clientsById[client_id]['on']
|
|
||||||
else:
|
|
||||||
return client_id
|
|
||||||
|
|
||||||
def buildRecordingName(self, client_id):
|
|
||||||
ts = time.localtime()
|
|
||||||
opname = sanitizeFilename(self.clients[client_id-1]['on'])
|
|
||||||
dirname = "/var/spool/grn/recordings/"+time.strftime("%Y%m/%d/%H", ts)
|
|
||||||
filename = time.strftime("%Y%m%d%H%M%S-", ts) + opname + '.wav'
|
|
||||||
try:
|
|
||||||
os.makedirs(dirname)
|
|
||||||
except OSError: pass
|
|
||||||
return dirname+'/'+filename
|
|
||||||
|
|
||||||
def textMessageReceived(self, client, message, target):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def closeFile(self):
|
|
||||||
size = self.recordingFile.tell()-len(WAV_HEADER)
|
|
||||||
self.recordingFile.seek(0x30) # fact chunk value
|
|
||||||
self.recordingFile.write(struct.pack("<L", int(size/32.5)))
|
|
||||||
self.recordingFile.seek(0x38) # data chunk size
|
|
||||||
self.recordingFile.write(struct.pack("<L", size))
|
|
||||||
self.recordingFile.close()
|
|
||||||
|
|
||||||
def archiveRecording(self, from_id):
|
|
||||||
log.msg("%s stopped talking: starting playback." %
|
|
||||||
self.clients[from_id-1]['on'])
|
|
||||||
self.closeFile()
|
|
||||||
self.recordingFile = None
|
|
||||||
self.recordingOperator = None
|
|
||||||
|
|
||||||
def audioFrameReceived(self, from_id, frames):
|
|
||||||
if self.recordingOperator != self.clients[from_id-1]['on']:
|
|
||||||
if self.recordingOperator is not None:
|
|
||||||
self.closeFile()
|
|
||||||
self.recordingOperator = self.clients[from_id-1]['on']
|
|
||||||
self.recordingFile = file(self.buildRecordingName(from_id), 'wb')
|
|
||||||
self.recordingFile.write(WAV_HEADER)
|
|
||||||
self.recordingFile.write(frames)
|
|
||||||
try:
|
|
||||||
self.parrot_timer.reset(AUDIO_TIMEOUT)
|
|
||||||
except:
|
|
||||||
log.msg("%s started talking" %
|
|
||||||
self.clients[from_id-1]['on'])
|
|
||||||
self.parrot_timer = self.factory.reactor.callLater(
|
|
||||||
AUDIO_TIMEOUT, self.archiveRecording, from_id)
|
|
||||||
self.pong()
|
|
||||||
|
|
||||||
def loginResponse(self, info):
|
|
||||||
log.msg("Login: %s" % info['al'])
|
|
||||||
self.setStatus(1)
|
|
||||||
self.recordingOperator = None
|
|
||||||
self.recordingFile = None
|
|
||||||
|
|
||||||
def clientsListUpdated(self, clients):
|
|
||||||
self.clients = clients
|
|
||||||
self.clientsById = dict([(i['id'], i) for i in clients])
|
|
||||||
|
|
||||||
|
|
||||||
class FRNRecorderFactory(FRNClientFactory):
|
|
||||||
protocol = FRNRecorder
|
|
||||||
reactor = reactor
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
from os.path import dirname, join as pjoin
|
|
||||||
from ConfigParser import ConfigParser
|
|
||||||
|
|
||||||
log.startLogging(sys.stderr)
|
|
||||||
|
|
||||||
basedir = dirname(__file__)
|
|
||||||
|
|
||||||
acfg = ConfigParser()
|
|
||||||
acfg.read(['/etc/grn/accounts.conf',
|
|
||||||
pjoin(basedir,'accounts.conf'), 'accounts.conf'])
|
|
||||||
|
|
||||||
scfg = ConfigParser()
|
|
||||||
scfg.read(['/etc/grn/servers.conf',
|
|
||||||
pjoin(basedir,'servers.conf'), 'servers.conf'])
|
|
||||||
|
|
||||||
argc = len(sys.argv)
|
|
||||||
if argc >= 3:
|
|
||||||
server_name, network_name = sys.argv[2].split(':',1)
|
|
||||||
account_cfg = acfg.items(sys.argv[1])+[('network', network_name)]
|
|
||||||
server_cfg = scfg.items(server_name)
|
|
||||||
server = scfg.get(server_name, 'server')
|
|
||||||
port = scfg.getint(server_name, 'port')
|
|
||||||
|
|
||||||
d = dict(account_cfg)
|
|
||||||
user = FRNUser(
|
|
||||||
EA=d['email'],
|
|
||||||
PW=d['password'], ON=d['operator'],
|
|
||||||
BC=d['transmission'], DS=d['description'],
|
|
||||||
NN=d['country'], CT=d['city'], NT=d['network'])
|
|
||||||
reactor.connectTCP(server, port, FRNRecorderFactory(user))
|
|
||||||
reactor.run()
|
|
||||||
|
|
||||||
|
|
||||||
# vim: set et ai sw=4 ts=4 sts=4:
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
|
||||||
|
# See LICENSE.txt for copyright info
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
from zope.interface import implements
|
||||||
|
from twisted.plugin import IPlugin
|
||||||
|
from twisted.application.service import IServiceMaker
|
||||||
|
from frn.protocol.client import FRNClient, FRNClientFactory
|
||||||
|
from frn.user import FRNUser
|
||||||
|
from twisted.application import internet
|
||||||
|
from twisted.internet import reactor, task
|
||||||
|
from twisted.internet.defer import DeferredList
|
||||||
|
from twisted.python import log, usage
|
||||||
|
import os, string, time, struct
|
||||||
|
from os.path import curdir, join as pjoin
|
||||||
|
from ConfigParser import ConfigParser
|
||||||
|
safe_chars = string.ascii_letters+string.digits+' :;.,+-=$@'
|
||||||
|
|
||||||
|
WAV_HEADER = "524946460c06000057415645666d74201400000031000100401f00005906000041000000020040016661637404000000000000006461746100000000".decode('hex')
|
||||||
|
AUDIO_TIMEOUT = 5.0
|
||||||
|
|
||||||
|
def sanitizeFilename(name):
|
||||||
|
r = ''
|
||||||
|
for c in name:
|
||||||
|
if c in safe_chars:
|
||||||
|
r += c
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class FRNRecorder(FRNClient):
|
||||||
|
|
||||||
|
def getClientName(self, client_id):
|
||||||
|
if self.clientsById.has_key(client_id):
|
||||||
|
return self.clientsById[client_id]['on']
|
||||||
|
else:
|
||||||
|
return client_id
|
||||||
|
|
||||||
|
def buildRecordingName(self, client_id):
|
||||||
|
ts = time.localtime()
|
||||||
|
opname = sanitizeFilename(self.clients[client_id-1]['on'])
|
||||||
|
dirname = self.factory.recdir+"/"+time.strftime("%Y%m/%d/%H", ts)
|
||||||
|
filename = time.strftime("%Y%m%d-%H%M%S-", ts) + opname + '.wav'
|
||||||
|
try:
|
||||||
|
os.makedirs(dirname)
|
||||||
|
except OSError: pass
|
||||||
|
return dirname+'/'+filename
|
||||||
|
|
||||||
|
def textMessageReceived(self, client, message, target):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def closeFile(self):
|
||||||
|
size = self.recordingFile.tell()-len(WAV_HEADER)
|
||||||
|
time = size / 1625.0
|
||||||
|
if time > self.factory.mintime:
|
||||||
|
self.recordingFile.seek(0x30) # fact chunk value
|
||||||
|
self.recordingFile.write(struct.pack("<L", int(size/32.5)))
|
||||||
|
self.recordingFile.seek(0x38) # data chunk size
|
||||||
|
self.recordingFile.write(struct.pack("<L", size))
|
||||||
|
self.recordingFile.close()
|
||||||
|
else:
|
||||||
|
name = self.recordingFile.name
|
||||||
|
log.msg("Deleting short recording: %s" % (name,))
|
||||||
|
self.recordingFile.close()
|
||||||
|
os.remove(name)
|
||||||
|
|
||||||
|
def archiveRecording(self, from_id):
|
||||||
|
log.msg("%s stopped talking" %
|
||||||
|
self.clients[from_id-1]['on'])
|
||||||
|
self.closeFile()
|
||||||
|
self.recordingFile = None
|
||||||
|
self.recordingOperator = None
|
||||||
|
|
||||||
|
def audioFrameReceived(self, from_id, frames):
|
||||||
|
if self.recordingOperator != self.clients[from_id-1]['on']:
|
||||||
|
if self.recordingOperator is not None:
|
||||||
|
self.closeFile()
|
||||||
|
self.recordingOperator = self.clients[from_id-1]['on']
|
||||||
|
self.recordingFile = file(self.buildRecordingName(from_id), 'wb')
|
||||||
|
self.recordingFile.write(WAV_HEADER)
|
||||||
|
self.recordingFile.write(frames)
|
||||||
|
try:
|
||||||
|
self.parrot_timer.reset(AUDIO_TIMEOUT)
|
||||||
|
except:
|
||||||
|
log.msg("%s started talking" %
|
||||||
|
self.clients[from_id-1]['on'])
|
||||||
|
self.parrot_timer = self.factory.reactor.callLater(
|
||||||
|
AUDIO_TIMEOUT, self.archiveRecording, from_id)
|
||||||
|
self.pong()
|
||||||
|
|
||||||
|
def loginResponse(self, info):
|
||||||
|
log.msg("Login: %s" % info['al'])
|
||||||
|
self.setStatus(1)
|
||||||
|
self.recordingOperator = None
|
||||||
|
self.recordingFile = None
|
||||||
|
|
||||||
|
def clientsListUpdated(self, clients):
|
||||||
|
self.clients = clients
|
||||||
|
self.clientsById = dict([(i['id'], i) for i in clients])
|
||||||
|
|
||||||
|
|
||||||
|
class FRNRecorderFactory(FRNClientFactory):
|
||||||
|
protocol = FRNRecorder
|
||||||
|
reactor = reactor
|
||||||
|
|
||||||
|
def __init__(self, user, recdir="/var/spool/grn/recordings", mintime=2.0):
|
||||||
|
self.recdir = recdir
|
||||||
|
self.mintime = mintime
|
||||||
|
FRNClientFactory.__init__(self, user)
|
||||||
|
|
||||||
|
|
||||||
|
class Options(usage.Options):
|
||||||
|
|
||||||
|
optParameters = [
|
||||||
|
["confdir", "c", curdir, "Directory containing config files"],
|
||||||
|
["recdir", "d", "/var/spool/grn/recordings", "Directory where to save recordings"],
|
||||||
|
["mintime", "m", 2.0, "Minimun recording duration"]
|
||||||
|
]
|
||||||
|
|
||||||
|
def parseArgs(self, serverdef):
|
||||||
|
basedir = self['confdir']
|
||||||
|
acfg = ConfigParser()
|
||||||
|
acfg.read(['/etc/grn/accounts.conf',
|
||||||
|
pjoin(basedir,'accounts.conf'), 'accounts.conf'])
|
||||||
|
|
||||||
|
scfg = ConfigParser()
|
||||||
|
scfg.read(['/etc/grn/servers.conf',
|
||||||
|
pjoin(basedir,'servers.conf'), 'servers.conf'])
|
||||||
|
|
||||||
|
account_name, server_name, network_name = serverdef.split(':', 2)
|
||||||
|
|
||||||
|
self['server'] = scfg.get(server_name, 'server')
|
||||||
|
self['port'] = scfg.getint(server_name, 'port')
|
||||||
|
self['backup_server'] = scfg.get(server_name, 'backup_server')
|
||||||
|
self['backup_port'] = scfg.getint(server_name, 'backup_port')
|
||||||
|
self['operator'] = acfg.get(account_name, 'operator')
|
||||||
|
self['email'] = acfg.get(account_name, 'email')
|
||||||
|
self['password'] = acfg.get(account_name, 'password')
|
||||||
|
self['description'] = acfg.get(account_name, 'description')
|
||||||
|
self['country'] = acfg.get(account_name, 'country')
|
||||||
|
self['city'] = acfg.get(account_name, 'city')
|
||||||
|
self['network'] = network_name
|
||||||
|
|
||||||
|
|
||||||
|
class FRNRecorderServiceMaker(object):
|
||||||
|
implements(IServiceMaker, IPlugin)
|
||||||
|
tapname = "frnrecorder"
|
||||||
|
description = "Freeradionetwork recorder"
|
||||||
|
options = Options
|
||||||
|
|
||||||
|
def makeService(self, options):
|
||||||
|
return internet.TCPClient(options['server'], options['port'],
|
||||||
|
FRNRecorderFactory(
|
||||||
|
FRNUser(
|
||||||
|
EA=options['email'],
|
||||||
|
PW=options['password'], ON=options['operator'],
|
||||||
|
BC="Crosslink", DS=options['description'],
|
||||||
|
NN=options['country'], CT=options['city'], NT=options['network']),
|
||||||
|
options['recdir'], options['mintime']))
|
||||||
|
|
||||||
|
serviceMaker = FRNRecorderServiceMaker()
|
||||||
|
|
||||||
|
|
||||||
|
# vim: set et ai sw=4 ts=4 sts=4:
|
Loading…
Reference in New Issue