better hourly
better hourly

file:a/db.sql -> file:b/db.sql
-- /usr/pgsql-9.1/bin/pg_dump --schema-only scannr -- /usr/pgsql-9.1/bin/pg_dump --schema-only scannr
-- --
-- PostgreSQL database dump -- PostgreSQL database dump
-- --
   
SET statement_timeout = 0; SET statement_timeout = 0;
SET client_encoding = 'UTF8'; SET client_encoding = 'UTF8';
SET standard_conforming_strings = on; SET standard_conforming_strings = on;
SET check_function_bodies = false; SET check_function_bodies = false;
SET client_min_messages = warning; SET client_min_messages = warning;
   
-- --
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
-- --
   
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
   
   
-- --
-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: -- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:
-- --
   
COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
   
   
SET search_path = public, pg_catalog; SET search_path = public, pg_catalog;
   
SET default_tablespace = ''; SET default_tablespace = '';
   
SET default_with_oids = false; SET default_with_oids = false;
   
-- --
-- Name: recordings; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- Name: recordings; Type: TABLE; Schema: public; Owner: postgres; Tablespace:
-- --
   
CREATE TABLE recordings ( CREATE TABLE recordings (
filename text NOT NULL, filename text NOT NULL,
tgid text, tgid text,
tgname text, tgname text,
sitename text, sitename text,
call_timestamp timestamp with time zone DEFAULT now(), call_timestamp timestamp with time zone DEFAULT now(),
length integer length integer
); );
   
   
ALTER TABLE public.recordings OWNER TO postgres; ALTER TABLE public.recordings OWNER TO postgres;
   
-- --
-- Name: tgids; Type: TABLE; Schema: public; Owner: postgres; Tablespace: -- Name: tgids; Type: TABLE; Schema: public; Owner: postgres; Tablespace:
-- --
   
CREATE TABLE tgids ( CREATE TABLE tgids (
tgid text NOT NULL, tgid text NOT NULL,
subfleet smallint, subfleet smallint,
alpha_tag text NOT NULL, alpha_tag text NOT NULL,
mode character(1) DEFAULT 'D'::bpchar NOT NULL, mode character(1) DEFAULT 'D'::bpchar NOT NULL,
description text, description text,
service_tag text, service_tag text,
category text category text
); );
   
   
ALTER TABLE public.tgids OWNER TO postgres; ALTER TABLE public.tgids OWNER TO postgres;
   
-- --
-- Name: recordings_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- Name: recordings_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace:
-- --
   
ALTER TABLE ONLY recordings ALTER TABLE ONLY recordings
ADD CONSTRAINT recordings_pkey PRIMARY KEY (filename); ADD CONSTRAINT recordings_pkey PRIMARY KEY (filename);
   
   
-- --
-- Name: tgids_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: -- Name: tgids_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace:
-- --
   
ALTER TABLE ONLY tgids ALTER TABLE ONLY tgids
ADD CONSTRAINT tgids_pkey PRIMARY KEY (tgid); ADD CONSTRAINT tgids_pkey PRIMARY KEY (tgid);
   
   
-- --
-- Name: public; Type: ACL; Schema: -; Owner: postgres -- Name: public; Type: ACL; Schema: -; Owner: postgres
-- --
   
REVOKE ALL ON SCHEMA public FROM PUBLIC; REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM postgres; REVOKE ALL ON SCHEMA public FROM postgres;
GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC; GRANT ALL ON SCHEMA public TO PUBLIC;
   
   
-- --
-- PostgreSQL database dump complete -- PostgreSQL database dump complete
-- --
   
CREATE TABLE "compilation" ( CREATE TABLE "compilations" (
"filename" text NOT NULL, "filename" text NOT NULL,
"files" text ARRAY NOT NULL, "files" text ARRAY NOT NULL,
"datetime" timestamp NOT NULL "datetime" timestamp NOT NULL
); );
   
CREATE TABLE "trunk_log" ( CREATE TABLE "trunk_logs" (
"id" text NOT NULL, "id" text NOT NULL,
"datetime" integer NOT NULL, "datetime" integer NOT NULL,
"site" integer NOT NULL, "site" integer NOT NULL,
"action" text NOT NULL, "action" text NOT NULL,
"sourcetype" character(1) NOT NULL, "sourcetype" character(1) NOT NULL,
"sourceid" smallint NOT NULL, "sourceid" smallint NOT NULL,
"targettype" character(1) NOT NULL, "targettype" character(1) NOT NULL,
"targetid" smallint NOT NULL, "targetid" smallint NOT NULL,
"channel" smallint NOT NULL, "channel" smallint NOT NULL,
"calltype" text NOT NULL "calltype" text NOT NULL
); );
   
<?php \<?php
include('common.inc.php'); include('common.inc.php');
function processHourly($hourly) { function processHourly($hourly) {
$filename = str_replace(' ',"_",$hourly['tgid']).'-'.str_replace(' 00:00:00+1','',$hourly['aday']).'-'.$hourly['ahour'].'.3gp'; global $conn;
  $hfilename = str_replace(' ','_',$hourly['tgid']) . '-' . str_replace(Array(' 00:00:00+10',' 00:00:00+11'), '', $hourly['aday']) . '-' . $hourly['ahour'] . '.3gp';
$hfilename = $hourly['tgid'] . '-' . str_replace(' 00:00:00+10', '', $hourly['aday']) . '-' . $hourly['ahour'] . '.3gp';  
   
if (!file_exists("hourly/" . $hfilename)) { if (!file_exists("hourly/" . $hfilename)) {
   
$filenames = explode(",", $hourly['filenames']); $filenames = explode(",", $hourly['filenames']);
$cmd = "/usr/local/bin/ffmpeg -filter_complex concat=n=" . count($filenames) . ":v=0:a=1 -i data/" . implode(" -i data/", $filenames) . " -ar 8000 -ab 4.75k -ac 1 hourly/" . $hfilename . ' 2>&1'; $cmd = "/usr/local/bin/ffmpeg";
  if (count($filenames) > 1) {
  $cmd .=" -filter_complex concat=n=" . count($filenames) . ":v=0:a=1"; // only concat when more than 1 file
  }
  $cmd .=" -i data/" . implode(" -i data/", $filenames) . " -ar 8000 -ab 4.75k -ac 1 hourly/" . $hfilename . ' 2>&1';
//print_r($hourly); //print_r($hourly);
exec($cmd, $output, $returncode); exec($cmd, $output, $returncode);
echo $cmd . "<br>\n"; echo $cmd . "<br>\n";
if ($returncode != 10) { if ($returncode != 0) {
  echo $returncode;
print_r($output); print_r($output);
//die(); unlink("hourly/" . $hfilename); // delete incomplete file
  // die();
} else { } else {
$q = " insert into compilations (filename files datetime) ('" . $hfilename . "',{'" . implode("', '", $filenames) . "'},'" . strtottime($hourly['aday'] . ' +' . $hourly['ahour'] . " hours')") . "' ";  
   
  $time = strtotime($hourly['aday'] . ' +' . $hourly['ahour'] . " hours");
  $q = " insert into compilations (filename,files,datetime) VALUES ('" . $hfilename . "', ARRAY['" . implode("', '", $filenames) . "'], to_timestamp('" . $time . "') );";
  $conn->query($q);
  //echo $q."\n";
foreach ($filenames as $filename) { foreach ($filenames as $filename) {
$q = "UPDATE recordings SET archived = '$hfilename' WHERE filename = '$filename' "; $q = "UPDATE recordings SET archived = '$hfilename' WHERE filename = '$filename' ;";
  $conn->query($q);
  //echo $q."\n";
} }
  //die();
} }
   
} }
} }
/*$sth = $conn->prepare("select tgid, extract(hour from call_timestamp) ahour, date_trunc('day', call_timestamp) aday, /*$sth = $conn->prepare("select tgid, extract(hour from call_timestamp) ahour, date_trunc('day', call_timestamp) aday,
count(filename), array_to_string(array_agg(filename order by call_timestamp), ',') filenames from recordings group by tgid, ahour, aday order by aday DESC, ahour, tgid;"); count(filename), array_to_string(array_agg(filename order by call_timestamp), ',') filenames from recordings group by tgid, ahour, aday order by aday DESC, ahour, tgid;");
   
$sth = $conn->prepare("SELECT tgid, EXTRACT(HOUR FROM call_timestamp) ahour, date_trunc('day', call_timestamp) aday, COUNT(filename), array_to_string(array_agg(filename ORDER BY call_timestamp), ',') filenames FROM recordings GROUP BY tgid, ahour, aday ORDER BY aday DESC, ahour, tgid;");  
// TODO use tgid categories instead, tgid too specific // TODO use tgid categories instead, tgid too specific
$sth->execute(); $sth->execute();
$hourlies = $sth->fetchAll(PDO::FETCH_ASSOC); $hourlies = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($hourlies as $hourly) { foreach ($hourlies as $hourly) {
processHourly($hourly); processHourly($hourly);
} }
$sth = $conn->prepare("select 'hour' as tgid, extract(hour from call_timestamp) ahour, date_trunc('day', call_timestamp) aday, $sth = $conn->prepare("select 'hour' as tgid, extract(hour from call_timestamp) ahour, date_trunc('day', call_timestamp) aday,
count(filename), array_to_string(array_agg(filename order by call_timestamp), ',') filenames from recordings group by ahour, aday order by aday DESC, ahour;"); count(filename), array_to_string(array_agg(filename order by call_timestamp), ',') filenames from recordings group by ahour, aday order by aday DESC, ahour;");
   
$sth->execute(); $sth->execute();
$hourlies = $sth->fetchAll(PDO::FETCH_ASSOC); $hourlies = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($hourlies as $hourly) { foreach ($hourlies as $hourly) {
processHourly($hourly); processHourly($hourly);
} }*/
$sth = $conn->prepare("select coalesce(category,'unknown') as tgid, extract(hour from call_timestamp) ahour, date_trunc('day', call_timestamp) aday, $sth = $conn->prepare("select coalesce(category,'unknown') as tgid, extract(hour from call_timestamp) ahour, date_trunc('day', call_timestamp) aday,
count(filename), array_to_string(array_agg(filename order by call_timestamp), ',') filenames count(filename), array_to_string(array_agg(filename order by call_timestamp), ',') filenames
from recordings inner join tgids on recordings.tgid = tgids.tgid group by category, ahour, aday order by aday DESC, ahour, category;"); from recordings inner join tgids on recordings.tgid = tgids.tgid
  group by category, ahour, aday
  having count(archived) != count(filename)
  order by aday DESC, ahour, category;");
   
$sth->execute(); $sth->execute();
$hourlies = $sth->fetchAll(PDO::FETCH_ASSOC); $hourlies = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach($hourlies as $hourly) { foreach($hourlies as $hourly) {
processHourly($hourly); processHourly($hourly);
} }
   
   
file:a/snd.py -> file:b/snd.py
""" Record a few seconds of audio and save to a WAVE file. """ Record a few seconds of audio and save to a WAVE file.
Based on http://stackoverflow.com/questions/892199/detect-record-audio-in-python/6743593#6743593 Based on http://stackoverflow.com/questions/892199/detect-record-audio-in-python/6743593#6743593
""" """
   
import pyaudio import pyaudio
import wave import wave
import sys import sys
from array import array from array import array
from struct import unpack, pack from struct import unpack, pack
from pydispatch import dispatcher from pydispatch import dispatcher
   
THRESHOLD = 500 THRESHOLD = 500
CHUNK_SIZE = 1024 CHUNK_SIZE = 1024
FORMAT = pyaudio.paInt16 FORMAT = pyaudio.paInt16
RATE = 44100 RATE = 44100
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
CHANNELS = 2 CHANNELS = 2
elif sys.platform.startswith('win32'): elif sys.platform.startswith('win32'):
CHANNELS = 1 CHANNELS = 1
   
MAX_SILENT = 80 MAX_SILENT = 80
   
def is_silent(L): def is_silent(L):
"Returns `True` if below the 'silent' threshold" "Returns `True` if below the 'silent' threshold"
"print max(L)" "print max(L)"
"print max(L) < THRESHOLD" "print max(L) < THRESHOLD"
return max(L) < THRESHOLD return max(L) < THRESHOLD
   
   
def normalize(L): def normalize(L):
"Average the volume out" "Average the volume out"
MAXIMUM = 16384 MAXIMUM = 16384
times = float(MAXIMUM) / max(abs(i) for i in L) times = float(MAXIMUM) / max(abs(i) for i in L)
   
LRtn = array('h') LRtn = array('h')
for i in L: for i in L:
LRtn.append(int(i * times)) LRtn.append(int(i * times))
return LRtn return LRtn
   
   
def trim(L): def trim(L):
"Trim the blank spots at the start and end" "Trim the blank spots at the start and end"
   
def _trim(L): def _trim(L):
snd_started = False snd_started = False
LRtn = array('h') LRtn = array('h')
   
for i in L: for i in L:
if not snd_started and abs(i) > THRESHOLD: if not snd_started and abs(i) > THRESHOLD:
snd_started = True snd_started = True
LRtn.append(i) LRtn.append(i)
   
elif snd_started: elif snd_started:
LRtn.append(i) LRtn.append(i)
return LRtn return LRtn
   
# Trim to the left # Trim to the left
L = _trim(L) L = _trim(L)
   
# Trim to the right # Trim to the right
L.reverse() L.reverse()
L = _trim(L) L = _trim(L)
L.reverse() L.reverse()
return L return L
   
   
def add_silence(L, seconds): def add_silence(L, seconds):
"Add silence to the start and end of `L` of length `seconds` (float)" "Add silence to the start and end of `L` of length `seconds` (float)"
LRtn = array('h', [0 for i in xrange(int(seconds * RATE))]) LRtn = array('h', [0 for i in xrange(int(seconds * RATE))])
LRtn.extend(L) LRtn.extend(L)
LRtn.extend([0 for i in xrange(int(seconds * RATE))]) LRtn.extend([0 for i in xrange(int(seconds * RATE))])
return LRtn return LRtn
   
   
def record(): def record():
""" """
Record a word or words from the microphone and Record a word or words from the microphone and
return the data as an array of signed shorts. return the data as an array of signed shorts.
   
Normalizes the audio, trims silence from the Normalizes the audio, trims silence from the
start and end, and pads with 0.5 seconds of start and end, and pads with 0.5 seconds of
blank sound to make sure VLC et al can play blank sound to make sure VLC et al can play
it without getting chopped off. it without getting chopped off.
""" """
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE,
input=True, input=True,
frames_per_buffer=CHUNK_SIZE) frames_per_buffer=CHUNK_SIZE)
   
num_silent = 0 num_silent = 0
snd_started = False snd_started = False
   
LRtn = array('h') LRtn = array('h')
   
while 1: while 1:
try: try:
data = stream.read(CHUNK_SIZE) data = stream.read(CHUNK_SIZE) # Fewer than n bytes may be returned if the operating system call returns fewer than n bytes.
  #if len(data) < 4096:
  #print "cropped packet detected! " + len(data)
  #data.extend('\x00' * CHUNK_SIZE - len(data))
  #print "fixed to " + len(data)
except IOError as ex: except IOError as ex:
if ex[1] != pyaudio.paInputOverflowed: if ex[1] != pyaudio.paInputOverflowed:
raise raise
data = '\x00' * CHUNK_SIZE data = '\x00' * CHUNK_SIZE
L = unpack('<' + ('h' * (len(data) / 2)), data) # little endian, signed short L = unpack('<' + ('h' * (len(data) / 2)), data) # little endian, signed short
L = array('h', L) L = array('h', L)
   
silent = is_silent(L) silent = is_silent(L)
#print silent, num_silent, L[:10] #print silent, num_silent, L[:10]
   
if silent and snd_started: if silent and snd_started:
num_silent += 1 num_silent += 1
#print num_silent #print num_silent
elif not silent and not snd_started: elif not silent and not snd_started:
dispatcher.send(signal='SND_STARTED') dispatcher.send(signal='SND_STARTED')
snd_started = True snd_started = True
print snd_started print snd_started
if snd_started and not silent: if snd_started and not silent:
num_silent = 0 num_silent = 0
if snd_started: if snd_started:
LRtn.extend(L) LRtn.extend(L)
if snd_started and num_silent > MAX_SILENT: if snd_started and num_silent > MAX_SILENT:
break break
   
sample_width = p.get_sample_size(FORMAT) sample_width = p.get_sample_size(FORMAT)
stream.stop_stream() stream.stop_stream()
stream.close() stream.close()
p.terminate() p.terminate()
   
LRtn = normalize(LRtn) LRtn = normalize(LRtn)
LRtn = trim(LRtn) LRtn = trim(LRtn)
LRtn = add_silence(LRtn, 0.5) LRtn = add_silence(LRtn, 0.5)
return sample_width, LRtn return sample_width, LRtn
   
   
def record_to_file(path): def record_to_file(path):
"Records from the microphone and outputs the resulting data to `path`" "Records from the microphone and outputs the resulting data to `path`"
sample_width, data = record() sample_width, data = record()
data = pack('<' + ('h' * len(data)), *data) data = pack('<' + ('h' * len(data)), *data)
   
wf = wave.open(path, 'wb') wf = wave.open(path, 'wb')
wf.setnchannels(CHANNELS) wf.setnchannels(CHANNELS)
wf.setsampwidth(sample_width) wf.setsampwidth(sample_width)
wf.setframerate(RATE) wf.setframerate(RATE)
wf.writeframes(data) wf.writeframes(data)
wf.close() wf.close()
print("done - result written to " + path) print("done - result written to " + path)
del data del data
   
   
if __name__ == '__main__': if __name__ == '__main__':
filename = 'demo.wav' filename = 'demo.wav'
record_to_file(filename) record_to_file(filename)
print("done - result written to " + filename) print("done - result written to " + filename)