Created
July 18, 2014 12:26
-
-
Save oseiskar/66c3b4b0f3e3b3095085 to your computer and use it in GitHub Desktop.
Festival schedule generation script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# | |
# A script for generating a printable HTML festival schedule from CSV input. | |
# | |
# Usage: ./this-file.py < schedule.txt > schedule.html | |
# see the readSchedule function for input format description | |
# | |
import sys | |
def toFloatMinutes(s): | |
""" | |
Convert hh:mm representation to floating point 'minutes since midnight | |
format. If the time is before 06:00 AM, it is interpreted as a time on | |
the next day. | |
""" | |
hourmin = [float(f) for f in s.split(':')] | |
t = hourmin[0] + hourmin[1] / 60 | |
MORNING = 6 | |
if t < MORNING: t += 24 | |
return t | |
class Band: | |
def __init__(self,day,start,end,name,genre,stage): | |
self.day = day | |
self.start = start | |
self.end = end | |
self.stage = stage | |
self.genre = genre | |
self.name = name | |
# Return float or integer type time and stage notation | |
def startF(self): | |
return toFloatMinutes(self.start) | |
def endF(self): | |
return toFloatMinutes(self.end) | |
def readSchedule(inputFile): | |
""" | |
Each line in the input CSV (tab separated) represents a band gig and should | |
contain the following fields (see Band constructor above) | |
* date | |
* starting time (hh:mm) | |
* end time (hh:mm) | |
* band name | |
* comment/genre (currently ignored) | |
* stage | |
Example: | |
14.8. Wed 01:45 02:30 KADAVAR - PARTY | |
14.8. Wed 00:15 01:15 DESTRUCTION - PARTY | |
14.8. Wed 22:45 23:45 EXODUS - PARTY | |
14.8. Wed 21:15 22:15 VADER - MAIN | |
14.8. Wed 20:00 20:45 BURY TOMORROW - MAIN | |
""" | |
bands = [] | |
for line in inputFile: | |
line = line.strip() | |
if line != '': | |
vec = line.split('\t') | |
bands.append(Band(*vec)) | |
return bands | |
bands = readSchedule(sys.stdin) | |
outfile = sys.stdout | |
days = [] | |
stages = [] | |
bandsPerStagePerDay = {} | |
earliestBegin = 24 | |
latestEnd = 0 | |
for b in bands: | |
begin = b.startF() | |
end = b.endF() | |
if begin < earliestBegin: | |
earliestBegin = begin | |
if end > latestEnd: | |
latestEnd = end | |
day = b.day | |
if day not in days: | |
days.append(day) | |
bandsPerStagePerDay[day] = {} | |
if b.stage not in bandsPerStagePerDay[day]: | |
bandsPerStagePerDay[day][b.stage] = [] | |
if b.stage not in stages: | |
stages.append(b.stage) | |
bandsPerStagePerDay[day][b.stage].append(b) | |
# Tweakable layout settings | |
PAGE_TITLE = "Festival schedule" | |
scale = 70 # pixels per hour | |
dayBegin = earliestBegin | |
dayEnd = latestEnd | |
dayPaddingTopPx = 20 | |
dayPaddingBottomPx = 20 | |
dayHeightPx = int(scale*(dayEnd-dayBegin)+dayPaddingTopPx+dayPaddingBottomPx) | |
dayMarginBottomPx = 20 | |
dayHWMargin = dayHeightPx + dayMarginBottomPx | |
dayPaddingRightPx = 10 | |
columnWidthPx = 190 | |
SHORT_SHOW = 30/60.0 # 30 minutes | |
outfile.write("""<!DOCTYPE HTML> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>%s</title> | |
<style> | |
.boksi { | |
position:absolute; | |
border-top: 1px solid black; | |
border-bottom: 1px dotted black; | |
background: #ccffcc; | |
width: %dpx; | |
-webkit-print-color-adjust: economy; | |
}""" % (PAGE_TITLE, columnWidthPx)) | |
outfile.write(""" | |
.lava { | |
border-top: 1px solid black; | |
border-bottom: 2px solid black; | |
border-right: 1px solid black; | |
border-left: 1px solid black; | |
float: left; | |
position: relative; | |
width: %dpx; | |
height: %dpx; | |
background: #eeeeee; | |
-webkit-print-color-adjust: exact; | |
}""" % (columnWidthPx, dayHeightPx)) | |
outfile.write(""" | |
.paiva { | |
float: left; | |
width: %dpx; | |
height: %dpx; | |
page-break-after: always; | |
page-break-inside: avoid; | |
}""" % (columnWidthPx*len(stages)+dayPaddingRightPx, dayHWMargin)) | |
outfile.write(""" | |
.lavajatyhjaaalla { | |
float: left; | |
position: relative; | |
height: %dpx; | |
}""" % dayHWMargin) | |
outfile.write(""" | |
.sisus { | |
padding: 5px; | |
font-size: 11pt; | |
} | |
.tiivis { | |
font-size: 9pt; | |
line-height: 107%; | |
} | |
.paivanjalavanotsikko { | |
padding-left: 5px; | |
font-size: 10pt; | |
color: gray; | |
} | |
small { | |
color: gray; | |
font-size: 8pt; | |
} | |
</style> | |
</head> | |
<body>""") | |
for day in days: | |
bandsPerStage = bandsPerStagePerDay[day] | |
outfile.write(""" | |
<div class="paiva">""") | |
for stage in stages: | |
outfile.write(""" | |
<div class="lavajatyhjaaalla"> | |
<div class="lava"> | |
<div class="paivanjalavanotsikko">%s / %s</div>""" % (day,stage)) | |
if stage in bandsPerStage: | |
bands = bandsPerStage[stage] | |
for b in bands: | |
begin = b.startF() | |
duration = b.endF()-begin | |
style = "sisus" | |
#long_text = False | |
short_show = duration <= SHORT_SHOW | |
if short_show: | |
long_text = len(b.name) > 20 | |
else: | |
long_text = len(b.name) > 20 | |
if short_show or long_text: | |
style += " tiivis" | |
if short_show and not long_text: | |
br = " " | |
else: | |
br = "<br/>" | |
top_px = int((begin-dayBegin)*scale + dayPaddingTopPx) | |
height_px = int(duration*scale) | |
text = "<b>%s</b> %s %s - %s" % (b.name, br, b.start, b.end) | |
outfile.write(""" | |
<div class="boksi" style="top: %dpx; height: %dpx;"> | |
<div class="%s">%s</div> | |
</div>""" % (top_px, height_px, style, text)) | |
outfile.write(""" | |
</div> | |
</div>""") | |
outfile.write(""" | |
</div>""") | |
outfile.write(""" | |
</body> | |
</html> | |
""") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment