2014-08-26 11:46:47 -07:00
#!/usr/bin/env python3
2014-08-26 15:10:44 -07:00
from threading import Thread
2014-08-26 11:46:47 -07:00
import time
import datetime
from sched import scheduler
import cherrypy
import sys
import subprocess
import os
import os . path
2014-08-26 15:09:40 -07:00
class recordTick :
2014-08-26 11:46:47 -07:00
def __init__ ( self , database ) :
# sqlite3 reference
self . db = database
# list of downloader threads
self . threads = { }
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def tick ( self ) :
now = datetime . datetime . now ( )
#print("Tick start: %s" % now)
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
# Look for starting times set to now
days = [ " m " , " t " , " w " , " r " , " f " , " sa " , " su " ]
day = days [ datetime . datetime . now ( ) . weekday ( ) ]
2020-02-06 18:47:34 -08:00
2014-08-26 16:40:54 -07:00
startTimes = self . db . execute ( ' SELECT * FROM " times " JOIN " streams " ON " streams " . " id " = " times " . " streamid " where " starthour " =? AND " startmin " =? AND " ' + day + ' " =1 AND " status " =0 ' , ( now . hour , now . minute ) )
2014-08-26 11:46:47 -07:00
for startTime in startTimes :
# Start each downloader
self . startStream ( startTime [ " streamid " ] )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
# Look for end times set to now
endTimes = self . db . execute ( ' SELECT * FROM " times " where " endhour " =? AND " endmin " =? ' , ( now . hour , now . minute ) )
for endTime in endTimes :
# terminate each downloader
self . endStream ( endTime [ " streamid " ] )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
#print("Tick end: %s" % now)
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def startStream ( self , id ) :
# Find stream information
stream = self . db . execute ( ' SELECT * FROM " streams " WHERE " id " =? ; ' , ( id , ) ) [ 0 ]
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
# if the downloader isnt running already:
if not stream [ " id " ] in self . threads :
# Create the recording thread
self . threads [ stream [ " id " ] ] = recordThread ( stream [ " url " ] , stream [ " directory " ] )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def endStream ( self , id ) :
if id in self . threads :
# tell the downloader to finish
self . threads [ id ] . cancel ( )
del self . threads [ id ]
2020-02-06 18:47:34 -08:00
2014-08-26 13:45:05 -07:00
def streamStatus ( self , id ) :
if not id in self . threads :
2014-08-26 14:07:04 -07:00
return - 1
2020-02-06 18:47:34 -08:00
2014-08-26 13:45:05 -07:00
return self . threads [ id ] . status
2020-02-06 18:47:34 -08:00
2014-08-26 14:26:05 -07:00
def getSelf ( self ) :
return self
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def timeToNextMinute ( self ) :
# calculate time to the milliscond until the next minute rolls over
# Find the next minute
then = datetime . datetime . now ( ) + datetime . timedelta ( minutes = 1 )
# Drop the seconds
then = then - datetime . timedelta ( seconds = then . second , microseconds = then . microsecond )
# calculate difference
wait = then - datetime . datetime . now ( )
waitMillis = wait . seconds + int ( wait . microseconds / 1000 ) / 1000
return waitMillis
class recordThread ( Thread ) :
def __init__ ( self , url , directory ) :
Thread . __init__ ( self )
2014-08-26 13:45:05 -07:00
# Status
self . status = 1
2014-08-26 11:46:47 -07:00
# URL to download
self . url = url
# Directory name to use
self . directory = directory
# True means the downloader keeps alive on failure
self . running = True
# Start time of the recording
self . startdate = None
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
self . start ( )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def run ( self ) :
print ( " %s starting downloader for %s " % ( datetime . datetime . now ( ) , self . url ) )
# Download the stream to temp file(s)
2014-08-26 13:45:05 -07:00
self . status = 2
2020-02-06 18:47:34 -08:00
self . clearTempDir ( )
2014-08-26 11:46:47 -07:00
self . downloadStream ( )
# Combine files into 1 audio file
2014-08-26 13:45:05 -07:00
self . status = 3
2014-08-26 11:46:47 -07:00
self . mergeStream ( )
# Encode to mp3
2014-08-26 13:45:05 -07:00
self . status = 4
2014-08-26 11:46:47 -07:00
self . transcodeStream ( )
# Delete temp files, move recording to save directory
2014-08-26 13:45:05 -07:00
self . status = 5
2014-08-26 11:46:47 -07:00
self . cleanup ( )
print ( " %s finished downloader for %s " % ( datetime . datetime . now ( ) , self . url ) )
2014-08-26 13:45:05 -07:00
self . status = 0
2020-02-06 18:47:34 -08:00
def clearTempDir ( self ) :
# Delete temp files
files = os . listdir ( " files/temp/ %s " % self . directory )
for f in files :
os . unlink ( " files/temp/ %s / %s " % ( self . directory , f ) )
2014-08-26 11:46:47 -07:00
def downloadStream ( self ) :
self . startdate = datetime . datetime . now ( )
2020-02-06 18:47:34 -08:00
recdate = str ( int ( time . time ( ) ) )
2014-08-26 11:46:47 -07:00
# As long as we're supposed to keep retrying
while self . running :
# Create the temp dir for this stream
2020-02-06 18:47:34 -08:00
os . makedirs ( " files/temp/ " + self . directory , exist_ok = True )
2014-08-26 11:46:47 -07:00
# If there are already files, we're resuming. take the next available number
recNum = 0
2020-02-06 18:47:34 -08:00
while os . path . exists ( " files/temp/ %s / %s _ %s .mp3 " % ( self . directory , recdate , recNum ) ) :
2014-08-26 11:46:47 -07:00
recNum = recNum + 1
# Filename is something like files/temp/stream-name/rec-y-m-d_h-m-s.0.mp3
2020-02-06 18:47:34 -08:00
fileName = " files/temp/ %s / %s _ %s .mp3 " % ( self . directory , recdate , recNum )
2014-08-26 11:46:47 -07:00
args_libav = [
2020-02-06 18:47:34 -08:00
os . environ . get ( " CONVERT_PROGRAM " ) or " /usr/bin/ffmpeg " ,
2014-08-26 11:46:47 -07:00
' -loglevel ' ,
' error ' ,
' -i ' ,
self . url ,
' -ab ' ,
' 128k ' ,
fileName
]
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
self . proc = subprocess . Popen ( args_libav , stdin = subprocess . PIPE , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
output = self . proc . communicate ( )
print ( " LibAV output for %s : \n %s " % ( self . url , output ) )
self . proc = None
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def mergeStream ( self ) :
# Get an ordered list of the piece files
files = os . listdir ( " files/temp/ %s " % self . directory )
files . sort ( )
# merge audio tracks into a matroska audio file
command = [ ' /usr/bin/mkvmerge ' , ' -o ' , " files/temp/ %s /temp.mka " % self . directory , " files/temp/ %s / %s " % ( self . directory , files . pop ( 0 ) ) ]
for fname in files :
command . append ( " +files/temp/ %s / %s " % ( self . directory , fname ) )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
self . mergeproc = subprocess . Popen ( command , stdin = subprocess . PIPE , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
# Wait for the merge to finish
output = self . mergeproc . communicate ( )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def transcodeStream ( self ) :
# Delete the existing output file
if os . path . exists ( " files/temp/ %s /out.mp3 " % self . directory ) :
os . unlink ( " files/temp/ %s /out.mp3 " % self . directory )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
# Convert the matroska file to mp3
2020-02-06 18:47:34 -08:00
command = [
os . environ . get ( " CONVERT_PROGRAM " ) or ' /usr/bin/ffmpeg ' ,
' -i ' , " files/temp/ %s /temp.mka " % self . directory ,
' -q:a ' , ' 0 ' ,
' -ab ' , ' 128k ' ,
" files/temp/ %s /out.mp3 " % self . directory
]
2014-08-26 11:46:47 -07:00
self . transcodeproc = subprocess . Popen ( command , stdin = subprocess . PIPE , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
# wait for the trancode to finish
output = self . transcodeproc . communicate ( )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def cleanup ( self ) :
# create a dated name for the file
newname = self . startdate . strftime ( " % Y- % m- %d _ % H- % M- % S " ) + " .mp3 "
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
# make it's finished storage location
2020-02-06 18:47:34 -08:00
os . makedirs ( " files/output/ " + self . directory , exist_ok = True )
2014-08-26 11:46:47 -07:00
# copy final recording to output dir
os . rename ( " files/temp/ %s /out.mp3 " % ( self . directory ) , " files/output/ %s / %s " % ( self . directory , newname ) )
2020-02-06 18:47:34 -08:00
self . clearTempDir ( )
2014-08-26 11:46:47 -07:00
def cancel ( self ) :
2014-08-26 15:35:41 -07:00
print ( " Closing %s " % self . url )
2014-08-26 11:46:47 -07:00
# turn off keep-alive dow the downloader
self . running = False
# Kill the download process
self . proc . terminate ( )
Thread ( target = self . kill ) . start ( )
2020-02-06 18:47:34 -08:00
2014-08-26 11:46:47 -07:00
def kill ( self ) :
print ( " Starting kill thread for %s " % self . url )
time . sleep ( 3 )
# kill the thread
if not self . proc == None :
# One more chance to go quietly...
self . proc . terminate ( )
time . sleep ( 3 )
else :
print ( " Nothing to kill for %s " % self . url )
if not self . proc == None :
# Kill it
self . proc . kill ( )