Format track duration as H:MM instead of seconds

* Modify library.Item in order to have length formatted as H:MM instead of the
raw number of seconds by using a types.Float subclass (DurationType).
* Add library.DurationType, with custom format() and parse() methods that
handle the conversion.
* Add dbcore.query.DurationQuery as a NumericQuery subclass that _convert()s
the ranges specified by the user to floats, delegating the rest of the
functionality in the parent NumericQuery class.
* Add ui.raw_seconds_short() as the reverse of human_seconds_short(). This
function uses a regular expression in order to allow any number of minutes, and
always required SS to have two digits.
This commit is contained in:
Diego Moreda 2015-12-04 20:51:25 +01:00
parent 5c8acc9a49
commit 67eb0ed54c
3 changed files with 62 additions and 1 deletions

View file

@ -653,6 +653,35 @@ class DateQuery(FieldQuery):
return clause, subvals
class DurationQuery(NumericQuery):
"""NumericQuery that allow human-friendly (M:SS) time interval formats.
Converts the range(s) to a float value, and delegates on NumericQuery.
Raises InvalidQueryError when the pattern does not represent an int, float
or M:SS time interval.
"""
def _convert(self, s):
"""Convert a M:SS or numeric string to a float.
Return None if `s` is empty.
Raise an InvalidQueryError if the string cannot be converted.
"""
if not s:
return None
try:
# TODO: tidy up circular import
from beets.ui import raw_seconds_short
return raw_seconds_short(s)
except ValueError:
try:
return float(s)
except ValueError:
raise InvalidQueryArgumentTypeError(
s,
"a M:SS string or a float")
# Sorting.
class Sort(object):

View file

@ -195,6 +195,25 @@ class MusicalKey(types.String):
return self.parse(key)
class DurationType(types.Float):
"""Human-friendly (M:SS) representation of a time interval."""
query = dbcore.query.DurationQuery
def format(self, value):
return beets.ui.human_seconds_short(value or 0.0)
def parse(self, string):
try:
# Try to format back hh:ss to seconds.
return beets.ui.raw_seconds_short(value)
except ValueError:
# Fall back to a plain float..
try:
return float(string)
except ValueError:
return self.null
# Library-specific sort types.
class SmartArtistSort(dbcore.query.Sort):
@ -426,7 +445,7 @@ class Item(LibModel):
'original_day': types.PaddedInt(2),
'initial_key': MusicalKey(),
'length': types.FLOAT,
'length': DurationType(),
'bitrate': types.ScaledInt(1000, u'kbps'),
'format': types.STRING,
'samplerate': types.ScaledInt(1000, u'kHz'),

View file

@ -416,6 +416,19 @@ def human_seconds_short(interval):
return u'%i:%02i' % (interval // 60, interval % 60)
def raw_seconds_short(string):
"""Formats a human-readable M:SS string as a float (number of seconds).
Raises ValueError if the conversion cannot take place due to `string` not
being in the right format.
"""
match = re.match('^(\d+):([0-5]\d)$', string)
if not match:
raise ValueError('String not in M:SS format')
minutes, seconds = map(int, match.groups())
return float(minutes*60 + seconds)
# Colorization.
# ANSI terminal colorization code heavily inspired by pygments: