Skip to content

Commit

Permalink
added GUI and logging
Browse files Browse the repository at this point in the history
  • Loading branch information
Vhati committed Oct 3, 2012
1 parent 8267496 commit 16c0680
Show file tree
Hide file tree
Showing 25 changed files with 9,624 additions and 203 deletions.
10 changes: 9 additions & 1 deletion README.md
Expand Up @@ -23,7 +23,15 @@ More parser and exporter modules can be added fairly easily.

Requirements
------------
Windows, Linux, or OSX.
The commandline interface only needs Python, and should run well
on any os. The GUI is more demanding... it will run on Windows
and Linux; no idea about OSX.

* Python 2.6 or higher, but not 3.x.
* http://www.python.org/getit/

* VLC 2.x.x
* http://www.videolan.org/vlc/

* wxPython 2.8
* http://www.wxpython.org/download.php
195 changes: 18 additions & 177 deletions compilesubs.py
Expand Up @@ -4,49 +4,43 @@
import locale
import logging
import os
import random
import platform
import re
import sys
import urllib2

from lib import common
from lib import global_config
from lib import snarkutils


VERSION = "2.51"

random.seed()


def main():
global VERSION
def main_cli():
config = None
snarks = []

try:
logging.info("CompileSubs %s" % VERSION)

# If common's backend prompt funcs were to be replaced
# (i.e., for GUI popups), that'd happen here.

# Import the config module as an object that can
# be passed around, in case future fanciness replaces it.
# Also suppress creation of pyc clutter.
# be passed around, and suppress creation of pyc clutter.
#
sys.dont_write_bytecode = True
config = __import__("config")
sys.dont_write_bytecode = False
config = common.Changeling(config) # A copy w/o module baggage.

logging.info("Calling %s parser..." % config.parser_name)
raw_snarks = parse_snarks(config)
if (len(raw_snarks) == 0):
logging.warning("No messages were parsed.")
sys.exit(1)
snarks = snarkutils.parse_snarks(config)
if (len(snarks) == 0):
raise common.CompileSubsException("No messages were parsed.")

snarks = process_snarks(config, raw_snarks)
snarkutils.process_snarks(config, snarks)
if (len(snarks) == 0):
logging.warning("After processing, no messages were left.")
sys.exit(1)
raise common.CompileSubsException("After processing, no messages were left.")

logging.info("Calling %s exporter..." % config.exporter_name)
export_snarks(config, snarks)
snarkutils.export_snarks(config, snarks)

logging.info("Done.")

Expand All @@ -60,162 +54,9 @@ def main():
sys.exit(1)


def parse_snarks(config):
"""Returns a list of raw snark dicts{user,msg,date} from a parser.
:raises: ParserError
"""
parsers_pkg = __import__("lib.parsers", globals(), locals(), [config.parser_name])
parser_mod = getattr(parsers_pkg, config.parser_name)
parse_func = getattr(parser_mod, "fetch_snarks")
snarks = parse_func(config.src_path, config.first_msg, config.parser_options)

return snarks


def process_snarks(config, snarks):
"""Adds info to, and temporally fudges, a list of raw snarks.
While a snark's "date" is its original real-world datestamp,
"time" is relative to the first snark's "date" and is subject
to fudging.
If enabled in config, "color" is an RGB float tuple (0.0-1.0),
assigned randomly.
:return: A list of processed snark dicts{user,msg,date,time,color}.
"""

# Drop ignored users.
snarks[:] = [s for s in snarks if s["user"] not in config.ignore_users]

# Sort the msgs by their real-world date.
snarks[:] = sorted(snarks, key=lambda k: k["date"])

# Add in-movie time info to them.
for snark in snarks:
snark["time"] = snark["date"] - snarks[0]["date"] + config.fudge_time

# Search backward through a user's delays for one in the recent past.
if (snark["user"] in config.fudge_users):
for (bookmark, fudge_value) in reversed(config.fudge_users[snark["user"]]):
if (snark["time"] > bookmark):
snark["time"] += fudge_value
break

# Omit snarks that got shifted into negative times.
snarks[:] = [x for x in snarks if (abs(x["time"]) == x["time"])]

# Omit snarks beyond the end time, if set.
if (config.end_time is not None):
snarks[:] = [x for x in snarks if (x["time"] <= config.end_time)]

# Sort the msgs by their in-movie time.
snarks[:] = sorted(snarks, key=lambda k: k["time"])

# Assign unique colors, and paint each snark.
if (config.color_enabled == "random"):
unique_users = set(x["user"] for x in snarks)
unique_colors = get_random_colors(len(unique_users))
#write_palette_preview("./preview.html", unique_colors)

color_users = dict(zip(unique_users, unique_colors))
for snark in snarks:
snark["color"] = color_users[snark["user"]]
elif (config.color_enabled == "no"):
for snark in snarks:
if ("color" in snark): del snark["color"]

return snarks


def export_snarks(config, snarks):
"""Sends a list of processed snark dicts to an exporter.
:raises: ExporterError
"""
exporters_pkg = __import__("lib.exporters", globals(), locals(), [config.exporter_name])
exporter_mod = getattr(exporters_pkg, config.exporter_name)
write_func = getattr(exporter_mod, "write_snarks")
needs_file = getattr(exporter_mod, "needs_file")

if (config.dest_path and needs_file):
# Give the exporter a file-like object.
with open(config.dest_path, 'wb') as dest_file:
write_func(dest_file, snarks, config.show_time, config.exporter_options)
else:
# An empty string or None!? Maybe the exporter doesn't write.
write_func(None, snarks, config.show_time, config.exporter_options)


def get_random_colors(count):
"""Gets a list of arbitrary colors.
I had a fancy HSV randomizer with the colorsys module,
but it was hit and miss. So now there's a prefab list.
http://jsfiddle.net/k8NC2/1/
http://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors
:param count: The number of colors needed (too many, and you'll get white).
:return: A list of RGB float tuples (0.0-1.0).
"""
color_library = [("white", "FFFFFF"),
("kelly-vivid-yellow", "FFB300"),
("kelly-strong-purple", "803E75"),
#("kelly-vivid-orange", "FF6800"),
("kelly-very-light-blue", "A6BDD7"),
##("kelly-vivid-red", "C10020"),
("kelly-grayish-yellow", "CEA262"),
#("kelly-medium-gray", "817066"),
("kelly-vivid-green", "007D34"),
#("kelly-strong-purplish-pink", "F6768E"),
("kelly-strong-blue", "00538A"),
#("kelly-strong-yellowish-pink", "FF7A5C"),
##("kelly-strong-violet", "53377A"),
#("kelly-vivid-orange-yellow", "FF8E00"),
#("kelly-strong-purplish-red", "B32851"),
#("kelly-vivid-greenish-yellow", "F4C800"),
##("kelly-strong-reddish-brown", "7F180D"),
#("kelly-vivid-yellowish-green", "93AA00"),
##("kelly-deep-yellowish-brown", "593315"),
#("kelly-reddish-orange", "F13A13"),
##("kelly-dark-olive-green", "232C16"),
#("boynton-blue", "0000FF"),
#("boynton-red", "FF0000"),
##("boynton-green", "00FF00"),
##("boynton-yellow", "FFFF00"),
##("boynton-magenta", "FF00FF"),
##("boynton-pink", "FF8080"),
("boynton-gray", "808080"),
#("boynton-brown", "800000"),
##("boynton-orange", "FF8000"),
("softened-pink", "CC8080"),
("softened-yellow", "FECC5A"),
##("softened-red", "C71E1E"),
("softened-magenta", "CC00CC"),
("softened-orange", "CC8000"),
("softened-green", "00CC00"),
("lighter-yellowish-brown", "795335"),
("lighter-strong-violet", "73579A")
]
random.shuffle(color_library)
color_library *= (count // len(color_library)) + 1

result = [common.hex_to_rgb(y) for (x,y) in color_library[:count]]

return result


def write_palette_preview(path, unique_colors):
"""Dumps colors to a sanity-preserving html file for eyeballing.
A relic from debugging random palettes.
:param path: A file path to write to.
:param unique_colors: A list of RGB float tuples.
"""
out_file = open(path, 'w')
out_file.write("<html><body>\n");
for color in unique_colors:
out_file.write("<font color=\"#%s\"><b>#########</b></font><br />\n" % (common.rgb_to_hex(color)));
out_file.write("</body></html>");
out_file.close()
def main():
logging.info("CompileSubs %s (on %s)" % (global_config.VERSION, platform.platform(aliased=True, terse=False)))
main_cli()



Expand All @@ -234,7 +75,7 @@ def write_palette_preview(path, unique_colors):
logstream_handler.setFormatter(logstream_formatter)
logstream_handler.setLevel(logging.INFO)

logfile_handler = logging.FileHandler("log.txt", mode="w")
logfile_handler = logging.FileHandler("./log.txt", mode="w")
logger.addHandler(logfile_handler)
logfile_formatter = logging.Formatter("%(asctime)s %(levelname)s (%(module)s): %(message)s", "%Y-%m-%d %H:%M:%S")
logfile_handler.setFormatter(logfile_formatter)
Expand Down
130 changes: 130 additions & 0 deletions compilesubs_gui.py
@@ -0,0 +1,130 @@
#!/usr/bin/env python

from datetime import datetime, timedelta
import locale
import logging
import os
import platform
import re
import sys
import urllib2
import wx

from lib import cleanup
from lib import common
from lib import global_config
from lib import snarkutils
from lib.gui import csgui


def main_gui():
config = None
snarks = []

logging.info("Registering ctrl-c handler.")
cleanup_handler = cleanup.CustomCleanupHandler()
cleanup_handler.register() # Must be called from main thread.
# Warning: If the main thread gets totally blocked, it'll never notice sigint.
sys.excepthook = create_exception_handler(logger, cleanup_handler)

try:
# If common's backend prompt funcs were to be replaced
# (i.e., for GUI popups), that'd happen here.

# Import the config module as an object that can
# be passed around, and suppress creation of pyc clutter.
#
sys.dont_write_bytecode = True
config = __import__("config")
sys.dont_write_bytecode = False
config = common.Changeling(config) # A copy w/o module baggage.

logging.info("Calling %s parser..." % config.parser_name)
snarks = snarkutils.parse_snarks(config)
if (len(snarks) == 0):
raise common.CompileSubsException("No messages were parsed.")

snarkutils.gui_preprocess_snarks(config, snarks)
snarkutils.gui_fudge_users(config, snarks)

if (len(snarks) == 0):
raise common.CompileSubsException("After preprocessing, no messages were left.")

snarks_wrapper = common.SnarksWrapper(config, snarks)

fudge_saver = common.Bunch()
def on_snarks_changed(e):
if (common.SnarksEvent.FLAG_CONFIG_FUDGES not in e.get_flags()):
return

repr_str = snarkutils.config_repr(e.get_source().clone_config())
with open("./config_gui_backup.py", "w") as fudge_file:
fudge_file.write("# These settings were auto-saved when the GUI made changes.\n")
fudge_file.write("# To reuse them next time, rename this file to config.py.\n")
fudge_file.write("# Otherwise this file will be overwritten.\n\n")
fudge_file.write(repr_str)
fudge_file.write("\n")
fudge_saver.on_snarks_changed = on_snarks_changed
snarks_wrapper.add_snarks_listener(fudge_saver)

mygui = csgui.GuiApp(snarks_wrapper=snarks_wrapper, redirect=False, clearSigInt=False)
cleanup_handler.add_gui(mygui)

try:
mygui.MainLoop()
finally:
mygui.done = True

except (common.CompileSubsException) as err:
# Parser or Exporter failed in an uninteresting way.
logging.error(str(err))

except (Exception) as err:
logging.exception(err)

finally:
cleanup_handler.cleanup()


def main():
logging.info("CompileSubs %s (on %s)" % (global_config.VERSION, platform.platform(aliased=True, terse=False)))
main_gui()


def create_exception_handler(logger, cleanup_handler):
# The logger is protected from garbage collection by closure magic.
# Local vars referenced by inner funcs live as long as those funcs.
def handleException(type, value, tb):
logger.error("Uncaught exception", exc_info=(type, value, tb))
if (cleanup_handler is not None):
cleanup_handler.cleanup()
else:
sys.exit(1)
return handleException



if __name__ == "__main__":
locale.setlocale(locale.LC_ALL, "")

# Go to the script dir (primary module search path; blank if cwd).
if (sys.path[0]): os.chdir(sys.path[0])

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

logstream_handler = logging.StreamHandler()
logger.addHandler(logstream_handler)
logstream_formatter = logging.Formatter("%(levelname)s (%(module)s): %(message)s")
logstream_handler.setFormatter(logstream_formatter)
logstream_handler.setLevel(logging.INFO)

logfile_handler = logging.FileHandler("./log.txt", mode="w")
logger.addHandler(logfile_handler)
logfile_formatter = logging.Formatter("%(asctime)s %(levelname)s (%(module)s): %(message)s", "%Y-%m-%d %H:%M:%S")
logfile_handler.setFormatter(logfile_formatter)

# wx doesn't have a better exception mechanism.
sys.excepthook = create_exception_handler(logger, None)

main()

0 comments on commit 16c0680

Please sign in to comment.