diff options
author | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
---|---|---|
committer | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
commit | 14738704ede6dfa6ac79f362a9c1f7f40f470cdc (patch) | |
tree | 31c83bdd188ae7b64d7169974d6f066ccfe95367 /lvc/utils.py | |
parent | eb1896583afbbb622cadcde1a24e17173f61904f (diff) | |
download | librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.lz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.xz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.zip |
rename mvc at lvc
Diffstat (limited to 'lvc/utils.py')
-rw-r--r-- | lvc/utils.py | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/lvc/utils.py b/lvc/utils.py new file mode 100644 index 0000000..e0a64f3 --- /dev/null +++ b/lvc/utils.py @@ -0,0 +1,230 @@ +import ctypes +import itertools +import logging +import os +import sys + +def hms_to_seconds(hours, minutes, seconds): + return (hours * 3600 + + minutes * 60 + + seconds) + + +def round_even(num): + """This takes a number, converts it to an integer, then makes + sure it's even. + + Additional rules: this helper always rounds down to avoid stray black + pixels (see bz18122). + + This function makes sure that the value returned is always >= 0. + """ + num = int(num) + val = num - (num % 2) + return val if val > 0 else 0 + + +def rescale_video((source_width, source_height), + (target_width, target_height), + dont_upsize=True): + """ + Rescale a video given a (width, height) target. This returns the largest + (width, height) which maintains the original aspect ratio while fitting + within the target size. + + If dont_upsize is set, then don't resize it such that the rescaled size + will be larger than the original size. + """ + if source_width is None or source_height is None: + return (round_even(target_width), round_even(target_height)) + + if target_width is None or target_height is None: + return (round_even(source_width), round_even(source_height)) + + if (dont_upsize and + (source_width <= target_width or source_height <= target_height)): + return (round_even(source_width), round_even(source_height)) + + width_ratio = float(source_width) / float(target_width) + height_ratio = float(source_height) / float(target_height) + ratio = max(width_ratio, height_ratio) + return round_even(source_width / ratio), round_even(source_height / ratio) + +def line_reader(handle): + """Builds a line reading generator for the given handle. This + generator breaks on empty strings, \\r and \\n. + + This a little weird, but it makes it really easy to test error + checking and progress monitoring. + """ + def _readlines(): + chars = [] + c = handle.read(1) + while True: + if c in ["", "\r", "\n"]: + if chars: + yield "".join(chars) + if not c: + break + chars = [] + else: + chars.append(c) + c = handle.read(1) + return _readlines() + + +class Matrix(object): + """2 Dimensional matrix. + + Matrix objects are accessed like a list, except tuples are used as + indices, for example: + + >>> m = Matrix(5, 5) + >>> m[3, 4] = 'foo' + >>> m + None, None, None, None, None + None, None, None, None, None + None, None, None, None, None + None, None, None, None, None + None, None, None, 'foo', None + """ + + def __init__(self, columns, rows, initial_value=None): + self.columns = columns + self.rows = rows + self.data = [ initial_value ] * (columns * rows) + + def __getitem__(self, key): + return self.data[(key[0] * self.rows) + key[1]] + + def __setitem__(self, key, value): + self.data[(key[0] * self.rows) + key[1]] = value + + def __iter__(self): + return iter(self.data) + + def __repr__(self): + return "\n".join([", ".join([repr(r) + for r in list(self.row(i))]) + for i in xrange(self.rows)]) + + def remove(self, value): + """This sets the value to None--it does NOT remove the cell + from the Matrix because that doesn't make any sense. + """ + i = self.data.index(value) + self.data[i] = None + + def row(self, row): + """Iterator that yields all the objects in a row.""" + for i in xrange(self.columns): + yield self[i, row] + + def column(self, column): + """Iterator that yields all the objects in a column.""" + for i in xrange(self.rows): + yield self[column, i] + + +class Cache(object): + def __init__(self, size): + self.size = size + self.dict = {} + self.counter = itertools.count() + self.access_times = {} + self.invalidators = {} + + def get(self, key, invalidator=None): + if key in self.dict: + existing_invalidator = self.invalidators[key] + if (existing_invalidator is None or + not existing_invalidator(key)): + self.access_times[key] = self.counter.next() + return self.dict[key] + + value = self.create_new_value(key, invalidator=invalidator) + self.set(key, value, invalidator=invalidator) + return value + + def set(self, key, value, invalidator=None): + if len(self.dict) == self.size: + self.shrink_size() + self.access_times[key] = self.counter.next() + self.dict[key] = value + self.invalidators[key] = invalidator + + def remove(self, key): + if key in self.dict: + del self.dict[key] + del self.access_times[key] + if key in self.invalidators: + del self.invalidators[key] + + def keys(self): + return self.dict.iterkeys() + + def shrink_size(self): + # shrink by LRU + to_sort = self.access_times.items() + to_sort.sort(key=lambda m: m[1]) + new_dict = {} + new_access_times = {} + new_invalidators = {} + latest_times = to_sort[len(self.dict) // 2:] + for (key, time) in latest_times: + new_dict[key] = self.dict[key] + new_invalidators[key] = self.invalidators[key] + new_access_times[key] = time + self.dict = new_dict + self.access_times = new_access_times + + def create_new_value(self, val, invalidator=None): + raise NotImplementedError() + + +def size_string(nbytes): + # when switching from the enclosure reported size to the + # downloader reported size, it takes a while to get the new size + # and the downloader returns -1. the user sees the size go to -1B + # which is weird.... better to return an empty string. + if nbytes == -1 or nbytes == 0: + return "" + + # FIXME this is a repeat of util.format_size_for_user ... should + # probably ditch one of them. + if nbytes >= (1 << 30): + value = "%.1f" % (nbytes / float(1 << 30)) + return "%(size)s GB" % {"size": value} + elif nbytes >= (1 << 20): + value = "%.1f" % (nbytes / float(1 << 20)) + return "%(size)s MB" % {"size": value} + elif nbytes >= (1 << 10): + value = "%.1f" % (nbytes / float(1 << 10)) + return "%(size)s KB" % {"size": value} + else: + return "%(size)s B" % {"size": nbytes} + +def convert_path_for_subprocess(path): + """Convert a path to a form suitable for passing to a subprocess. + + This method converts unicode paths to bytestrings according to the system + fileencoding. On windows, it converts the path to a short filename for + maximum compatibility + + This method should only be called on a path that exists on the filesystem. + """ + if not os.path.exists(path): + raise ValueError("path %r doesn't exist" % path) + if not isinstance(path, unicode): + # path already is a bytestring, just return it + return path + if sys.platform != 'win32': + return path.encode(sys.getfilesystemencoding()) + else: + buf_size = 1024 + short_path_buf = ctypes.create_unicode_buffer(buf_size) + ctypes.windll.kernel32.GetShortPathNameW(path, + short_path_buf, buf_size) + logging.info("convert_path_for_subprocess: got short path %r", + short_path_buf.value) + return short_path_buf.value.encode('ascii') |