Commit of initial code

This commit is contained in:
dave 2014-08-26 11:46:47 -07:00
commit e33022dab6
41 changed files with 14231 additions and 0 deletions

302
app.py Normal file
View File

@ -0,0 +1,302 @@
#!/usr/bin/env python3
# Clear out old session lockfiles BEFORE importing cherry
#import os
#for f in os.listdir("sessions/"):
# if "lock" in f:
# os.remove("sessions/%s" % f)
# CREATE TABLE 'streams' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'user' INTEGER, 'name' TEXT, 'url' TEXT, 'directory' TEXT, 'status' INTEGER, 'message' TEXT);
# CREATE TABLE 'times' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'streamid' INTEGER, 'su' BOOLEAN, 'm' BOOLEAN, 't' BOOLEAN, 'w' BOOLEAN, 'r' BOOLEAN, 'f' BOOLEAN, 'sa' BOOLEAN, 'starthour' INTEGER, 'startmin' INTEGER, 'endhour' INTEGER, 'endmin' INTEGER)
# INSERT INTO "streams" ("id","user","name","url","directory","status","message") VALUES (NULL,NULL,'WCMF Breakroom','http://1681.live.streamtheworld.com/WCMFFMAAC','wcmf-breakroom','0','')
# INSERT INTO "times" ("id","streamid","su","m","t","w","r","f","sa","starthour","startmin","endhour","endmin") VALUES (NULL,'1','0','1','1','1','1','1','0','2','0','7','15')
STREAM_STATUS_ACTIVE = 0
STREAM_STATUS_PAUSED = 1
STREAM_STATUS_ERROR = 2
import sys
import os
import os.path
import cherrypy
import json
import signal
from jinja2 import Environment, FileSystemLoader
from libs import database
from libs import recordTick
from feedgen.feed import FeedGenerator
from datetime import datetime
if __name__ == '__main__' or 'uwsgi' in __name__:
appdir = "/home/streamrecord/app"
appconf = {
'/': {
#'tools.proxy.on':True,
#'tools.proxy.base': conf["base"]["url"],
'tools.sessions.on':True,
'tools.sessions.storage_type':'file',
'tools.sessions.storage_path':appdir+'/sessions/',
'tools.sessions.timeout':525600,
'request.show_tracebacks': True
},
'/media': {
'tools.staticdir.on': True,
'tools.staticdir.dir': appdir+"/static/"
}
}
cherrypy.config.update({
'server.socket_port':3000,
'server.thread_pool':1,
'server.socket_host': '0.0.0.0',
'sessionFilter.on':True,
'server.show.tracebacks': True
})
cherrypy.server.socket_timeout = 5
# env - jinja2 template renderer
env = Environment(loader=FileSystemLoader("/home/streamrecord/app/templates"))
# db - slightly custom sqlite3 object. rows = db.execute(query, args)
db = database()
# REC - recorder thread - see recordTick.py
REC = recordTick(db)
def render(template, args):
templatesCache = pysite.cacheTemplates()
defaults = {"templates":templatesCache}
for item in args:
defaults[item] = args[item]
return quickRender(template, defaults)
def quickRender(template, args):
template = env.get_template(template)
return template.render(args)
class siteRoot(object):
def __init__(self):
print("Siteroot init !")
self.templateCache = self.cacheTemplates()
def cacheTemplates(self):
templateFiles = os.listdir("jstemplates/")
templateList = []
nameList = []
for item in templateFiles:
name = item.split(".")
templateList.append({"name":name[0],"content":open("jstemplates/"+item,"r").read().replace("\t", "").replace("\n","")})
nameList.append(name[0])
return quickRender("templates.html", {"names":json.dumps(nameList), "templates":templateList})
@cherrypy.expose
def index(self):
return render("html.html", {})
@cherrypy.expose
def htmltest(self):
return render("html.tpl", {})
#index.exposed = True
@cherrypy.expose
def templates(self):
return self.templateCache
class api(object):
def __init__(self):
pass
@cherrypy.expose
def getStreams(self):
streamList = db.execute('SELECT * FROM "streams"')
for stream in streamList:
stream["time"] = db.execute('SELECT * FROM "times" WHERE streamid=?', [stream["id"]])[0]
stream["files"] = self._getFiles(stream["id"])
return json.dumps(streamList)
def _getStream(self,id):
streamList = db.execute('SELECT * FROM "streams" WHERE "id"=?', [int(id)])
for stream in streamList:
stream["time"] = db.execute('SELECT * FROM "times" WHERE streamid=?', [stream["id"]])[0]
stream["files"]=self._getFiles(id)
return streamList[0]
@cherrypy.expose
def getStream(self, id):
return json.dumps(self._getStream(id))
@cherrypy.expose
def changeTimeDay(self, streamid, day, value):
streamid = int(streamid)
value = value == "true"
col = ""
if day == "daysu":
col="su"
elif day == "daym":
col="m"
elif day == "dayt":
col="t"
elif day == "dayw":
col="w"
elif day == "dayr":
col="r"
elif day == "dayf":
col="f"
elif day == "daysa":
col="sa"
else:
raise cherrypy.HTTPError(500, message="Day not found")
db.execute('UPDATE "times" SET "'+col+'"=? WHERE "streamid"=? ;', [1 if value else 0,streamid])
return json.dumps({"result":True})
@cherrypy.expose
def changeName(self, streamid, value):
streamid = int(streamid)
db.execute('UPDATE "streams" SET "name"=? WHERE "id"=?', [value,streamid])
return json.dumps({"result":True})
@cherrypy.expose
def changeUrl(self, streamid, value):
streamid = int(streamid)
db.execute('UPDATE "streams" SET "url"=? WHERE "id"=?', [value,streamid])
return json.dumps({"result":True})
@cherrypy.expose
def changeTime(self, streamid, startHour, startMin, endHour, endMin):
startHour=int(startHour)
assert startHour>=0 and startHour<=23
startMin=int(startMin)
assert startMin>=0 and startMin<=59
endHour=int(endHour)
assert endHour>=0 and endHour<=23
endMin=int(endMin)
assert endMin>=0 and endMin<=59
db.execute('UPDATE "times" SET "starthour"=?, "startmin"=?, "endhour"=?, "endmin"=? WHERE "streamid"=? ;', [startHour, startMin, endHour, endMin, streamid])
return json.dumps({"result":True})
def _filterName(self, input):
allowed="abcdefghijklmnopqrstuvwxyz123456789-"
input = input.replace(" ", "-").lower()
output=[]
for i in range(0, len(allowed)):
if input[i:i+1] in allowed:
output.append(input[i:i+1])
return ''.join(output)
@cherrypy.expose
def createStream(self, data):
data = json.loads(data)
assert not data["name"] == ""
assert not data["url"] == ""
assert data["time"]["su"] or data["time"]["m"] or data["time"]["t"] or data["time"]["w"] or data["time"]["r"] or data["time"]["f"] or data["time"]["sa"]
dirName = self._filterName(data["name"])
rowid = db.execute('INSERT INTO "streams" ("user", "name", "url", "directory", "status", "message") VALUES (?, ?, ?, ?, ?, ?);', [0, data["name"], data["url"], dirName, data["status"], ""])
db.execute('INSERT INTO "times" ("streamid", "su", "m", "t", "w", "r", "f", "sa", "starthour", "startmin", "endhour", "endmin") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);', [
rowid,
data["time"]["su"],
data["time"]["m"],
data["time"]["t"],
data["time"]["w"],
data["time"]["r"],
data["time"]["f"],
data["time"]["sa"],
data["time"]["startHour"],
data["time"]["startMin"],
data["time"]["endHour"],
data["time"]["endMin"]
])
return json.dumps({"result":rowid})
def _getFiles(self, id):
stream = db.execute('SELECT * FROM "streams" WHERE "id"=?', [int(id)])[0]
recordingsDir = "files/output/"+stream["directory"]+"/"
files = []
if os.path.exists(recordingsDir):
files = os.listdir(recordingsDir)
files.sort()
allFiles = []
for i in range(0, len(files)):
item = files[i]
size = os.path.getsize(recordingsDir+item)
allFiles.append({
"filename":item,
"directory":recordingsDir,
"streamdir":stream["directory"],
"filenum":i,
"bytes":size,
"mbytes":round(size/1024.0/1024.0, 2),
"date":os.path.getmtime(recordingsDir+item)
})
return allFiles
@cherrypy.expose
def getFiles(self, id):
files = self._getFiles(id)
return json.dumps({"data":files})
@cherrypy.expose
def download(self, id, fn):
files = self._getFiles(id)
item = files[int(fn)]
raise cherrypy.HTTPRedirect("/static/output/"+item["streamdir"]+"/"+item["filename"], 302)
@cherrypy.expose
def getUrl(self, id, fn):
files = self._getFiles(id)
item = files[int(fn)]
return json.dumps({"result":"/static/output/"+item["streamdir"]+"/"+item["filename"]})
@cherrypy.expose
@cherrypy.tools.response_headers(headers=[('Content-Type', 'application/rss+xml')])
def getPodcast(self, id):
"""fg = FeedGenerator()
fg.load_extension('podcast')
stream = self._getStream(id)
fg.title("Radio Feed - %s" % stream["name"])
fg.subtitle("Stream ID: %s"%stream["id"])
fg.language('en')
fg.link( href="http://192.168.1.200:3000/api/getPodcast?id=%s"%stream["id"], rel='self' )
for item in stream["files"]:
fe = fg.add_entry()
fe.id("http://192.168.1.200:3000/api/download?id=%s&fn=%s" % (stream["id"], item["filenum"]))
fe.link(href="http://192.168.1.200:3000/api/download?id=%s&fn=%s" % (stream["id"], item["filenum"]), rel="alternate")
fe.title(item["filename"])
cherrypy.response.headers['Content-Type']= 'application/rss+xml'
xml = fg.rss_str(pretty=True)
#return xml
#return str.encode(open("extra.txt", "r").read())
"""
stream = self._getStream(id)
# Thu, 31 Jul 2014 07:13:48 +0000
for f in stream["files"]:
f["date"]=datetime.fromtimestamp(f["date"]).strftime("%a, %m %b %Y %H:%M:%S +%z")
return str.encode(render("podcast.html", {
"stream":stream,
"builddate": datetime.now().strftime("%a, %m %b %Y %H:%M:%S +0100")#Thu, 31 Jul 2014 07:13:48 +0000
}))
pysite = siteRoot()
pysite.api = api()
print( "Ready to start application" )
if(len(sys.argv)>1 and sys.argv[1]=="test"):
print("test!")
application = cherrypy.quickstart(pysite, '/', appconf)
else:
sys.stdout = sys.stderr
cherrypy.config.update({'environment': 'embedded'})
application = cherrypy.tree.mount(pysite, "/", appconf)

23
jstemplates/add.html Normal file
View File

@ -0,0 +1,23 @@
<form class="add-schedule" data-validation="addschedule">
{{>stream}}
<input type="submit" class="btn btn-success pull-right" value="Add" />
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">Errors</h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Back</button>
</div>
</div>
</div>
</div>
</form>

View File

@ -0,0 +1,22 @@
<div id="downloads">
{{#each streams}}
<div class="panel panel-default" data-streamid="{{ id }}">
<div class="panel-heading">
<h3 class="panel-title"><a href="#view/{{ id }}">{{ name }}</a></h3>
</div>
<div class="panel-body">
{{>stream_downloads}}
</div>
</div>
{{/each}}
</div>
<div id="playerdrop">
<div class="controls">
</div>
<div class="time">
<input id="player-slider" type="text" data-slider-min="0" data-slider-max="20" data-slider-step="1" data-slider-value="0"/>
</div>
<div id="player"></div>
</div>

12
jstemplates/error.html Normal file
View File

@ -0,0 +1,12 @@
<div class="row-fluid clearfix errorpanel">
<div class="col-sm 12">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">Error</h3>
</div>
<div class="panel-body">
{{ message }}
</div>
</div>
</div>
</div>

5
jstemplates/list.html Normal file
View File

@ -0,0 +1,5 @@
<ul>
{{#each messages}}
<li>{{ this }}</li>
{{/each}}
</ul>

93
jstemplates/stream.html Normal file
View File

@ -0,0 +1,93 @@
<div class="panel panel-default" data-streamid="{{ id }}">
<div class="panel-heading">
<h3 class="panel-title"><a href="#view/{{ id }}">{{ name }}</a></h3>
</div>
<div class="panel-body">
<div class="row-fluid clearfix">
<div class="col-sm-2">
<strong>Name</strong>
</div>
<div class="col-sm-4">
<input type="text" class="form-control" name="stream-name" value="{{ name }}" />
</div>
<div class="col-sm-1">
<strong>URL</strong>
</div>
<div class="col-sm-5">
<input type="text" class="form-control" name="stream-url" value="{{ url }}" />
</div>
</div>
<div class="row-fluid clearfix">
<div class="col-sm-12"><br /></div>
</div>
<div class="row-fluid clearfix schedule-row">
<div class="col-sm-2">
<strong>Time</strong>
</div>
<div class="col-sm-6 day-input clearfix">
<div class="dropdown pull-left">
<a class="dropdown-toggle btn btn-default" data-toggle="dropdown" href="#">
Days
<b class="caret"></b>
</a>
<ul class="dropdown-menu dropdown-menu-form" role="menu">
<li><label class="checkbox"><input type="checkbox" name="daysu"{{#if time.su}} checked="checked"{{/if}}>S</label></li>
<li><label class="checkbox"><input type="checkbox" name="daym"{{#if time.m}} checked="checked"{{/if}}>M</label></li>
<li><label class="checkbox"><input type="checkbox" name="dayt"{{#if time.t}} checked="checked"{{/if}}>T</label></li>
<li><label class="checkbox"><input type="checkbox" name="dayw"{{#if time.w}} checked="checked"{{/if}}>W</label></li>
<li><label class="checkbox"><input type="checkbox" name="dayr"{{#if time.r}} checked="checked"{{/if}}>R</label></li>
<li><label class="checkbox"><input type="checkbox" name="dayf"{{#if time.f}} checked="checked"{{/if}}>F</label></li>
<li><label class="checkbox"><input type="checkbox" name="daysa"{{#if time.sa}} checked="checked"{{/if}}>S</label></li>
</ul>
</div>
<div class="pull-left time-input">
<input type="text" class="form-control hours" name="stream-starthour" value="{{ time.starthour }}"/>
<span>&nbsp;:&nbsp;</span>
<input type="text" class="form-control minutes" name="stream-startmin" value="{{ time.startmin }}"/>
<span>&nbsp;-&nbsp;</span>
<input type="text" class="form-control hours" name="stream-endhour" value="{{ time.endhour }}"/>
<span>&nbsp;:&nbsp;</span>
<input type="text" class="form-control minutes" name="stream-endmin" value="{{ time.endmin }}"/>
</div>
</div>
<div class="col-sm-1">
<strong>Status</strong>
</div>
<div class="col-sm-3">
<div class="btn-group btn-group-status">
<button type="button" class="btn btn-default btn-status-ok{{#is status 0 }} active{{/is}}"><span class="glyphicon glyphicon-ok"></span></button>
<button type="button" class="btn btn-default btn-status-stop{{#is status 1 }} active{{/is}}"><span class="glyphicon glyphicon-stop"></span></button>
<button type="button" class="btn btn-default btn-status-alarm{{#is status 2 }} active{{/is}}"><span class="glyphicon glyphicon-bell"></span></button>
</div>
</div>
</div>
{{#if message }}
<div class="row-fluid clearfix errorpanel">
<div class="col-sm 12">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">Error</h3>
</div>
<div class="panel-body">
{{ message }}
</div>
</div>
</div>
</div>
{{/if}}
{{#if files }}
<div class="row-fluid clearfix ">
<div class="col-sm 12">
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">Files</h3>
</div>
<div class="panel-body">
{{>stream_downloads}}
</div>
</div>
</div>
</div>
{{/if}}
</div>
</div>

View File

@ -0,0 +1,14 @@
{{#each files}}
<div class="row-fluid clearfix">
<div class="col-sm-6">
<strong>{{filename}}</strong>
</div>
<div class="col-sm-4">
{{mbytes}} mb
</div>
<div class="col-sm-2">
<a href="javascript:void(0);" class="playlink" data-url="/api/download?id={{ ../id }}&fn={{ filenum }}">Play</a> &middot;
<a href="/api/download?id={{ ../id }}&fn={{ filenum }}">Download</a>
</div>
</div>
{{/each}}

7
jstemplates/streams.html Normal file
View File

@ -0,0 +1,7 @@
<h1>{{#is single true}}Viewing: {{streams.[0].name}} {{else}}Scheduled Recordings{{/is}}</h1>
<hr />
<div class="streams">
{{#each streams}}
{{>stream}}
{{/each}}
</div>

4
libs/__init__.py Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env python3
from libs.recordTick import recordTick
from libs.database import database

13
libs/config/app.ini Normal file
View File

@ -0,0 +1,13 @@
[uwsgi]
uid = streamrecord
pid = streamrecord
plugins = python3
touch-reload = /home/streamrecord/app/app.py
chdir = /home/streamrecord/app/
wsgi-file = /home/streamrecord/app/app.py
callable = application
master = true
processes = 1
socket = 0.0.0.0:3330
enable-threads = true
no-threads-wait = true

36
libs/database.py Normal file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
import sqlite3
import threading
class database(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.db = None
self.start()
def run(self):
self.db = self.openDB()
def openDB(self):
db = sqlite3.connect("db.sqlite", check_same_thread=False, cached_statements=0, isolation_level=None)
db.row_factory = self.dict_factory
return db
def dict_factory(self, cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def execute(self, sql, params=None):
db = self.db
cursor = db.cursor()
if params:
cursor.execute(sql, params)
else:
cursor.execute(sql)
data = cursor.fetchall()
if not cursor.lastrowid==None:
return cursor.lastrowid
cursor.close()
return data

215
libs/recordTick.py Normal file
View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
from threading import Thread
import time
import datetime
from sched import scheduler
import cherrypy
import sys
import subprocess
import os
import os.path
class recordTick(Thread):
def __init__(self, database):
Thread.__init__(self)
# sqlite3 reference
self.db = database
# list of downloader threads
self.threads = {}
# tick timer
self.timer = scheduler(time.time, time.sleep)
self.start()
def run(self):
time.sleep(3)
## TESTING CODE
#now=datetime.datetime.now()
#self.db.execute('UPDATE "times" SET "starthour"=?, "startmin"=?, "endhour"=?, "endmin"=? WHERE "streamid"=? ;', (now.hour, now.minute, now.hour, now.minute+1, 1))
#self.tick()
## END TESTING CODE
self.scheduleTick()
def tick(self):
now=datetime.datetime.now()
#print("Tick start: %s" % now)
# Look for starting times set to now
days = ["m", "t", "w", "r", "f", "sa", "su"]
day = days[datetime.datetime.now().weekday()]
startTimes = self.db.execute('SELECT * FROM "times" where "starthour"=? AND "startmin"=? AND "'+day+'"=1', (now.hour, now.minute))
for startTime in startTimes:
# Start each downloader
self.startStream(startTime["streamid"])
# 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"])
#print("Tick end: %s" % now)
def startStream(self, id):
# Find stream information
stream = self.db.execute('SELECT * FROM "streams" WHERE "id"=? ;', (id,))[0]
# 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"])
def endStream(self, id):
if id in self.threads:
# tell the downloader to finish
self.threads[id].cancel()
del self.threads[id]
def scheduleTick(self):
# schedule tick in the next minute
self.timer.enter(self.timeToNextMinute(), 1, self.tick)
self.timer.run()
# Schedule the next tick
Thread(target=self.scheduleTick).start()
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)
# 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
self.start()
def run(self):
print("%s starting downloader for %s" % (datetime.datetime.now(), self.url))
# Download the stream to temp file(s)
self.downloadStream()
# Combine files into 1 audio file
self.mergeStream()
# Encode to mp3
self.transcodeStream()
# Delete temp files, move recording to save directory
self.cleanup()
print("%s finished downloader for %s" % (datetime.datetime.now(), self.url))
def downloadStream(self):
self.startdate = datetime.datetime.now()
# As long as we're supposed to keep retrying
while self.running:
# Create the temp dir for this stream
if not os.path.exists("files/temp/"+self.directory):
os.mkdir("files/temp/"+self.directory)
# If there are already files, we're resuming. take the next available number
recNum = 0
while os.path.exists("files/temp/%s/recdate%s.mp3" % (self.directory, ".%s"%recNum)):
recNum = recNum + 1
# Filename is something like files/temp/stream-name/rec-y-m-d_h-m-s.0.mp3
fileName = "files/temp/%s/recdate%s.mp3" % (self.directory, "" if recNum == None else ".%s"%recNum)
# Args if we download with curl (bad)
args_curl = [
'/usr/bin/curl',
#'-s',
'-A',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36',
'--output', fileName, self.url]
# args if we download/transcode with avconv (HERO!)
args_libav = [
'/usr/bin/avconv',
'-loglevel',
'error',
'-i',
self.url,
'-ab',
'128k',
fileName
]
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
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))
self.mergeproc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Wait for the merge to finish
output = self.mergeproc.communicate()
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)
# Convert the matroska file to mp3
command = ['/usr/bin/avconv', '-i', "files/temp/%s/temp.mka"%self.directory, '-q:a', '0', '-ab', '128k', "files/temp/%s/out.mp3"%self.directory]
self.transcodeproc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# wait for the trancode to finish
output = self.transcodeproc.communicate()
def cleanup(self):
# create a dated name for the file
newname = self.startdate.strftime("%Y-%m-%d_%H-%M-%S")+".mp3"
# make it's finished storage location
if not os.path.exists("files/output/"+self.directory):
os.mkdir("files/output/"+self.directory)
# copy final recording to output dir
os.rename("files/temp/%s/out.mp3"%(self.directory), "files/output/%s/%s"%(self.directory,newname))
# Delete temp files
files = os.listdir("files/temp/%s"%self.directory)
for f in files:
os.unlink("files/temp/%s/%s"%(self.directory,f))
def cancel(self):
# turn off keep-alive dow the downloader
self.running = False
# Kill the download process
self.proc.terminate()
Thread(target=self.kill).start()
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()

View File

@ -0,0 +1 @@
.img-thumbnail{border-radius:0}code{border-radius:0}pre{border-radius:0}.form-control{border-radius:0}.form-control:focus{border-color:#66afe9;outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;-webkit-box-shadow:none;box-shadow:none}.input-sm{border-radius:0}.input-lg{border-radius:0}.btn{border-radius:0}.btn-lg{border-radius:0}.btn-sm,.btn-xs{border-radius:0}.dropdown-menu{border-radius:0;-webkit-box-shadow:none;box-shadow:none}.btn-group-xs>.btn{border-radius:0}.btn-group-sm>.btn{border-radius:0}.btn-group-lg>.btn{border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{border-radius:0}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-lg>.input-group-btn>.btn{border-radius:0}.input-group-addon{border-radius:0}.input-group-addon.input-sm{border-radius:0}.input-group-addon.input-lg{border-radius:0}.nav-tabs>li>a{border-radius:0}.nav-pills>li>a{border-radius:0}@media(min-width:768px){.navbar{border-radius:0}}.navbar-toggle{border-radius:0}.navbar-toggle .icon-bar{border-radius:0}.breadcrumb{border-radius:0}.pagination{border-radius:0}.pagination>li:first-child>a,.pagination>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:0;border-bottom-right-radius:0}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:0;border-bottom-right-radius:0}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:0;border-bottom-right-radius:0}.pager li>a,.pager li>span{border-radius:0}.label{border-radius:0}.badge{border-radius:0}.container .jumbotron{border-radius:0}.thumbnail{border-radius:0}.alert{border-radius:0}.progress{border-radius:0;-webkit-box-shadow:none;box-shadow:none}.progress-bar{-webkit-box-shadow:none;box-shadow:none}.list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.list-group-item:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.panel{border-radius:0;-webkit-box-shadow:none;box-shadow:none}.panel-heading{border-top-right-radius:0;border-top-left-radius:0}.panel-footer{border-bottom-right-radius:0;border-bottom-left-radius:0}.panel-group .panel{border-radius:0}.well{border-radius:0;-webkit-box-shadow:none;box-shadow:none}.well-lg{border-radius:0}.well-sm{border-radius:0}.close{text-shadow:none}.modal-content{border-radius:0;-webkit-box-shadow:none;box-shadow:none}@media screen and (min-width:768px){.modal-content{-webkit-box-shadow:none;box-shadow:none}}.tooltip-inner{border-radius:0}.popover{border-radius:0;-webkit-box-shadow:none;box-shadow:none}.popover-title{border-radius:0}.carousel-control{text-shadow:none}.carousel-indicators li{border-radius:0}.carousel-caption{text-shadow:none}

442
static/bootstrap/css/bootstrap-theme.css vendored Normal file
View File

@ -0,0 +1,442 @@
/*!
* Bootstrap v3.2.0 (http://getbootstrap.com)
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default:disabled,
.btn-default[disabled] {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#2d6ca2));
background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #2b669a;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #2d6ca2;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #2d6ca2;
border-color: #2b669a;
}
.btn-primary:disabled,
.btn-primary[disabled] {
background-color: #2d6ca2;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success:disabled,
.btn-success[disabled] {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info:disabled,
.btn-info[disabled] {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning:disabled,
.btn-warning[disabled] {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger:disabled,
.btn-danger[disabled] {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #357ebd;
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
}
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f3f3f3));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
}
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%);
background-image: -o-linear-gradient(top, #222 0%, #282828 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#222), to(#282828));
background-image: linear-gradient(to bottom, #222 0%, #282828 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3071a9));
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #3071a9;
background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #3278b3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3278b3));
background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
background-repeat: repeat-x;
border-color: #3278b3;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
}
/*# sourceMappingURL=bootstrap-theme.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6203
static/bootstrap/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,229 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
<font-face units-per-em="1200" ascent="960" descent="-240" />
<missing-glyph horiz-adv-x="500" />
<glyph />
<glyph />
<glyph unicode="&#xd;" />
<glyph unicode=" " />
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
<glyph unicode="&#xa0;" />
<glyph unicode="&#x2000;" horiz-adv-x="652" />
<glyph unicode="&#x2001;" horiz-adv-x="1304" />
<glyph unicode="&#x2002;" horiz-adv-x="652" />
<glyph unicode="&#x2003;" horiz-adv-x="1304" />
<glyph unicode="&#x2004;" horiz-adv-x="434" />
<glyph unicode="&#x2005;" horiz-adv-x="326" />
<glyph unicode="&#x2006;" horiz-adv-x="217" />
<glyph unicode="&#x2007;" horiz-adv-x="217" />
<glyph unicode="&#x2008;" horiz-adv-x="163" />
<glyph unicode="&#x2009;" horiz-adv-x="260" />
<glyph unicode="&#x200a;" horiz-adv-x="72" />
<glyph unicode="&#x202f;" horiz-adv-x="260" />
<glyph unicode="&#x205f;" horiz-adv-x="326" />
<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />