aboutsummaryrefslogtreecommitdiffstats
path: root/lvc/utils.py
diff options
context:
space:
mode:
authorJesús Eduardo <heckyel@hyperbola.info>2017-09-11 17:47:17 -0500
committerJesús Eduardo <heckyel@hyperbola.info>2017-09-11 17:47:17 -0500
commit14738704ede6dfa6ac79f362a9c1f7f40f470cdc (patch)
tree31c83bdd188ae7b64d7169974d6f066ccfe95367 /lvc/utils.py
parenteb1896583afbbb622cadcde1a24e17173f61904f (diff)
downloadlibrevideoconverter-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.py230
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')