Browse Source

initial commit of main script

tags/v0.1
Ben Kurtovic 12 years ago
parent
commit
7e95cbc0d8
1 changed files with 410 additions and 0 deletions
  1. +410
    -0
      musicquizzer.py

+ 410
- 0
musicquizzer.py View File

@@ -0,0 +1,410 @@
#! /usr/bin/python
# -*- coding: utf-8 -*-

"""
MusicQuizzer is a Python program that can help you prepare for any test that
involves listening to excerpts of music pieces and answering multiple choice
questions about them. For more information, see the included README.md.
"""

from __future__ import division

import ConfigParser as configparser
import os
from pygame import mixer, error
import random
import thread
import time
from Tkinter import *
from tkFont import Font
from urllib import urlretrieve

__author__ = "Ben Kurtovic"
__copyright__ = "Copyright (c) 2011 by Ben Kurtovic"
__license__ = "MIT License"
__version__ = "0.1"
__email__ = "ben.kurtovic@verizon.net"

config_filename = "config.cfg"
config = None
piece_dir = None
download_complete = False

master_width = 500
master_height = 500
question_height = 100

class AnswerSheet(object):
def __init__(self, master):
self.master = master
self.order = generate_piece_order()
self.init_widgets()
self.generate_questions()
self.grid_questions()
def init_widgets(self):
self.scroll = Scrollbar(self.master)
self.scroll.grid(row=1, column=1, sticky=N+S)
self.canvas = Canvas(self.master, yscrollcommand=self.scroll.set,
width=master_width, height=master_height-30)
self.canvas.grid(row=1, column=0, sticky=N+S+E+W)
self.canvas.grid_propagate(0)
self.scroll.config(command=self.canvas.yview)
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
self.frame = Frame(self.canvas)
self.frame.rowconfigure(1, weight=1)
self.frame.columnconfigure(1, weight=1)
self.header = Frame(self.master, bd=2, relief="groove")
self.header.grid(row=0, column=0, columnspan=2)
self.header_buttons = Frame(self.header, width=250, height=30)
self.header_buttons.grid_propagate(0)
self.header_buttons.grid(row=0, column=0)
self.playing_container = Frame(self.header, width=master_width-240, height=30)
self.playing_container.grid_propagate(0)
self.playing_container.grid(row=0, column=1)
self.play_button = Button(self.header_buttons, text="Start Quiz",
command=self.play)
self.play_button.grid(row=0, column=0)
self.submit_button = Button(self.header_buttons, text="Submit Answers",
command=self.submit)
self.submit_button.grid(row=0, column=1)
self.playing = StringVar()
self.playing.set("Now Playing: None")
self.now_playing = Label(self.playing_container, textvar=self.playing)
self.now_playing.grid(row=0, column=0)
def generate_questions(self):
num_answers = config.getint("general", "answers")
answer_choices = {} # dict of {category1: {choice1, choice2...}, ...}
questions = {} # dict of {piece1: [question1, question2...], ...}
self.number_of_questions = 0
for piece in self.order:
for category, answer_choice in config.items(piece):
if category == "url":
continue
try:
answer_choices[category].add(answer_choice)
except KeyError:
answer_choices[category] = set([answer_choice])
for piece in self.order:
questions[piece] = dict()
for category in config.options(piece):
if category == "url":
continue
correct_choice = config.get(piece, category)
questions[piece][category] = [correct_choice]
all_choices = list(answer_choices[category])
all_choices.remove(correct_choice)
for x in range(num_answers - 1):
try:
choice = random.choice(all_choices)
except IndexError:
break # if there aren't enough choices in the choice
# bank, there will be fewer answer choices than
# we want, but what else can we do?
all_choices.remove(choice)
questions[piece][category].append(choice)
question_choices = questions[piece][category]
questions[piece][category] = randomize_list(question_choices)
self.number_of_questions += 1
self.questions = questions
def grid_questions(self):
self.answers = {}
self.stuff_to_disable = [] # what gets turned off when we press submit?
question_grid = Frame(self.frame)
this_row_number = 0
excerpt = "A"
for piece in self.order:
question_row_number = 1
piece_questions = self.questions[piece].keys()
piece_questions.reverse() # correct ordering issues
self.answers[piece] = {}
height = question_height * len(piece_questions) + 20
piece_grid = Frame(question_grid, width=master_width,
height=height)
title = Label(piece_grid, text="Excerpt {0}".format(excerpt),
font=Font(family="Verdana", size=10, weight="bold"))
excerpt = chr(ord(excerpt) + 1) # increment excerpt by 1 letter
title.grid(row=0, column=0, columnspan=3)
for question in piece_questions:
agrid = LabelFrame(piece_grid, text=question.capitalize(),
width=master_width, height=question_height)
a = StringVar()
self.answers[piece][question] = a
w = (master_width / 2) - 4
lhgrid = Frame(agrid, width=w, height=question_height)
rhgrid = Frame(agrid, width=w, height=question_height)
this_row = 0
left_side = True
for choice in self.questions[piece][question]:
if left_side:
r = Radiobutton(lhgrid, text=choice,
value=choice, variable=a)
left_side = False
else:
r = Radiobutton(rhgrid, text=choice,
value=choice, variable=a)
left_side = True
this_row += 1
r.grid(row=this_row, column=0, sticky=W)
self.stuff_to_disable.append(r)
lhgrid.grid_propagate(0)
lhgrid.grid(row=0, column=1)
rhgrid.grid_propagate(0)
rhgrid.grid(row=0, column=2)
agrid.grid_propagate(0)
agrid.grid(row=question_row_number, column=0)
question_row_number += 1
piece_grid.grid_propagate(0)
piece_grid.grid(row=this_row_number, column=0)
this_row_number += 1
question_grid.grid(row=0, column=0)

def play(self):
self.play_button.configure(state=DISABLED)
thread.start_new_thread(self.play_pieces, ())
def play_pieces(self):
self.excerpt_length = (config.getfloat("general", "excerpt_length") - 5) * 1000
break_length = config.getfloat("general", "break_length")
cur_excerpt = "A"
mixer.init()
for piece in self.order:
try:
self.playing.set("Now Playing: Excerpt {0}".format(cur_excerpt))
before = time.time()
self.play_piece(piece)
after = time.time()
retries = 1
while after - before < 3 or retries >= 100: # if the piece
before = time.time() # played for less than 3 seconds,
self.play_piece(piece) # assume something went wrong
after = time.time() # loading and try to replay it,
retries += 1 # but don't get stuck in a loop if
# we legitimately can't play it
self.playing.set("That was Excerpt {0}...".format(cur_excerpt))
cur_excerpt = chr(ord(cur_excerpt) + 1)
time.sleep(break_length)
except error: # Someone quit our mixer? STOP EVERYTHING.
break
self.playing.set("Finished playing.")

def play_piece(self, piece):
mixer.music.load(os.path.join(piece_dir, piece))
mixer.music.play()
fadeout_enabled = False
while mixer.music.get_busy():
if mixer.music.get_pos() >= self.excerpt_length:
if not fadeout_enabled:
mixer.music.fadeout(5000)
fadeout_enabled = True
time.sleep(1)

def submit(self):
self.submit_button.configure(state=DISABLED)
self.play_button.configure(state=DISABLED)
for item in self.stuff_to_disable:
item.configure(state=DISABLED)
try:
mixer.quit()
except error: # pygame.error
pass # music was never played, so we can't stop it
right = 0
wrong = []
excerpt = "A"
for piece in self.order:
questions = self.questions[piece].keys()
questions.reverse() # correct question ordering
for question in questions:
correct_answer = config.get(piece, question)
given_answer = self.answers[piece][question].get()
if given_answer == u"Der Erlk\xf6nig": # unicode bugfix
given_answer = "Der Erlk\xc3\xb6nig"
if correct_answer == given_answer:
right += 1
else:
wrong.append((excerpt, config.get(piece, "title"),
question, given_answer, correct_answer))
excerpt = chr(ord(excerpt) + 1)
results = Toplevel() # make a new window to display results
results.title("Results")
noq = self.number_of_questions
text = "{0} of {1} answered correctly ({2}%):".format(right, noq,
round((right / noq) * 100, 2))
if right == noq:
text += "\n\nCongratulations, you got everything right!"
else:
text += "\n"
for excerpt, title, question, given_answer, correct_answer in wrong:
if not given_answer:
if question == "title":
text += "\nYou left the title of Excerpt {0} blank; it's \"{1}\".".format(
excerpt, correct_answer)
else:
text += "\nYou left the {0} of \"{1}\" blank; it's {2}.".format(
question, title, correct_answer)
elif question == "title":
text += "\nExcerpt {0} was {1}, not {2}.".format(
excerpt, correct_answer, given_answer)
else:
text += "\nThe {0} of \"{1}\" is {2}, not {3}.".format(
question, title, correct_answer, given_answer)
label = Label(results, text=text, justify=LEFT, padx=15, pady=10,
font=Font(family="Verdana", size=8))
label.pack()
def randomize_list(old):
new = []
while old:
obj = random.choice(old)
new.append(obj)
old.remove(obj)
return new

def generate_piece_order():
pieces = config.sections()
pieces.remove("general") # remove config section that is not a piece
return randomize_list(pieces)

def load_config():
global config, piece_dir
config = configparser.SafeConfigParser()
config.read(config_filename)
if not config.has_section("general"):
exit("Your config file is missing or malformed.")
piece_dir = os.path.abspath(config.get("general", "piece_dir"))

def get_missing_pieces(root):
pieces = config.sections()
pieces.remove("general")
missing_pieces = []
for piece in pieces:
if not os.path.exists(os.path.join(piece_dir, piece)):
missing_pieces.append(piece)
if missing_pieces:
window = Toplevel()
window.title("PyQuizzer")
window.protocol("WM_DELETE_WINDOW", root.destroy)
status = StringVar()
status.set("I'm missing {0} music ".format(len(missing_pieces)) +
"pieces;\nwould you like me to download them for you now?")
head_label = Label(window, text="Download Music Pieces", font=Font(
family="Verdana", size=10, weight="bold"))
head_label.grid(row=0, column=0, columnspan=2)
status_label = Label(window, textvar=status, justify=LEFT, padx=15,
pady=10)
status_label.grid(row=1, column=0, columnspan=2)
quit_button = Button(window, text="Quit", command=lambda: exit())
quit_button.grid(row=2, column=0)
dl_button = Button(window, text="Download",
command=lambda: do_pieces_download(missing_pieces, status,
dl_button, status_label, window))
dl_button.grid(row=2, column=1)
window.mainloop()
else:
global download_complete
download_complete = True

def do_pieces_download(pieces, status, dl_button, status_label, window):
global download_complete
dl_button.configure(state=DISABLED)
counter = 1
for piece in pieces:
url = config.get("general", "base_url") + config.get(piece, "url")
name = "{0} of {1}: {2}".format(counter, len(pieces),
config.get(piece, "title"))
urlretrieve(url, os.path.join(piece_dir, piece),
lambda x, y, z: progress(x, y, z, name, status, status_label))
counter += 1
window.quit()
window.withdraw()
download_complete = True

def progress(count, block_size, total_size, name, status, label):
percent = int(count * block_size * 100 / total_size)
status.set("Downloading pieces...\n" + name + ": %2d%%" % percent)
label.update_idletasks()

def run():
root = Tk()
root.withdraw()
load_config()
get_missing_pieces(root)
while not download_complete:
time.sleep(0.5)
window = Toplevel()
window.title("MusicQuizzer")
answer_sheet = AnswerSheet(window)
answer_sheet.canvas.create_window(0, 0, anchor=NW,
window=answer_sheet.frame)
answer_sheet.frame.update_idletasks()
answer_sheet.canvas.config(scrollregion=answer_sheet.canvas.bbox("all"))

window.protocol("WM_DELETE_WINDOW", root.destroy) # make the 'x' in the
# corner quit the entire program, not just this window
window.mainloop()

if __name__ == "__main__":
run()

Loading…
Cancel
Save