#!/usr/bin/env python
# coding=utf-8
# aeneas is a Python/C library and a set of tools
# to automagically synchronize audio and text (aka forced alignment)
#
# Copyright (C) 2012-2013, Alberto Pettarin (www.albertopettarin.it)
# Copyright (C) 2013-2015, ReadBeyond Srl (www.readbeyond.it)
# Copyright (C) 2015-2017, Alberto Pettarin (www.albertopettarin.it)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
from __future__ import print_function
from aeneas.exacttiming import Decimal
from aeneas.exacttiming import TimeInterval
from aeneas.exacttiming import TimeValue
import aeneas.globalfunctions as gf
[docs]class SyncMapFragment(object):
"""
A sync map fragment, that is,
a text fragment and an associated time interval.
:param text_fragment: the text fragment
:type text_fragment: :class:`~aeneas.textfile.TextFragment`
:param begin: the begin time of the audio interval
:type begin: :class:`~aeneas.exacttiming.TimeValue`
:param end: the end time of the audio interval
:type end: :class:`~aeneas.exacttiming.TimeValue`
:param float confidence: the confidence of the audio timing
"""
TAG = u"SyncMapFragment"
REGULAR = 0
""" Regular fragment """
HEAD = 1
""" Head fragment """
TAIL = 2
""" Tail fragment """
NONSPEECH = 3
""" Nonspeech fragment (not head nor tail) """
NOT_REGULAR_TYPES = [HEAD, TAIL, NONSPEECH]
""" Types of fragment different than ``REGULAR`` """
def __init__(
self,
text_fragment=None,
interval=None,
begin=None,
end=None,
fragment_type=REGULAR,
confidence=1.0
):
self.text_fragment = text_fragment
if interval is not None:
self.interval = interval
elif (begin is not None) and (end is not None):
self.interval = TimeInterval(begin, end)
else:
self.interval = None
self.fragment_type = fragment_type
self.confidence = confidence
def __unicode__(self):
return u"%s %d %.3f %.3f" % (
self.text_fragment.identifier,
self.fragment_type,
self.begin,
self.end
)
def __str__(self):
return gf.safe_str(self.__unicode__())
def __eq__(self, other):
if not isinstance(other, SyncMapFragment):
return False
return self.interval == other.interval
def __ne__(self, other):
return not (self == other)
def __gt__(self, other):
if not isinstance(other, SyncMapFragment):
return False
return self.interval > other.interval
def __lt__(self, other):
if not isinstance(other, SyncMapFragment):
return False
return self.interval < other.interval
def __ge__(self, other):
return (self > other) or (self == other)
def __le__(self, other):
return (self < other) or (self == other)
@property
def text_fragment(self):
"""
The text fragment associated with this sync map fragment.
:rtype: :class:`~aeneas.textfile.TextFragment`
"""
return self.__text_fragment
@text_fragment.setter
def text_fragment(self, text_fragment):
self.__text_fragment = text_fragment
@property
def interval(self):
"""
The time interval corresponding to this fragment.
:rtype: :class:`~aeneas.exacttiming.TimeInterval`
"""
return self.__interval
@interval.setter
def interval(self, interval):
self.__interval = interval
@property
def fragment_type(self):
"""
The type of fragment.
Possible values are:
* :data:`~aeneas.syncmap.fragment.SyncMapFragment.REGULAR`
* :data:`~aeneas.syncmap.fragment.SyncMapFragment.HEAD`
* :data:`~aeneas.syncmap.fragment.SyncMapFragment.TAIL`
* :data:`~aeneas.syncmap.fragment.SyncMapFragment.NONSPEECH`
:rtype: int
"""
return self.__fragment_type
@fragment_type.setter
def fragment_type(self, fragment_type):
self.__fragment_type = fragment_type
@property
def is_head_or_tail(self):
"""
Return ``True`` if the fragment
is HEAD or TAIL.
:rtype: bool
.. versionadded:: 1.7.0
"""
return self.fragment_type in [self.HEAD, self.TAIL]
@property
def is_regular(self):
"""
Return ``True`` if the fragment
is REGULAR.
:rtype: bool
.. versionadded:: 1.7.0
"""
return self.fragment_type == self.REGULAR
@property
def confidence(self):
"""
The confidence of the audio timing, from ``0.0`` to ``1.0``.
Currently this value is not used, and it is always ``1.0``.
:rtype: float
"""
return self.__confidence
@confidence.setter
def confidence(self, confidence):
self.__confidence = confidence
@property
def pretty_print(self):
"""
Pretty print representation of this fragment,
as ``(identifier, begin, end, text)``.
:rtype: string
.. versionadded:: 1.7.0
"""
return u"%s\t%.3f\t%.3f\t%s" % (
(self.identifier or u""),
(self.begin if self.begin is not None else TimeValue("-2.000")),
(self.end if self.end is not None else TimeValue("-1.000")),
(self.text or u"")
)
@property
def identifier(self):
"""
The identifier of this sync map fragment.
:rtype: string
.. versionadded:: 1.7.0
"""
if self.text_fragment is None:
return None
return self.text_fragment.identifier
@property
def text(self):
"""
The text of this sync map fragment.
:rtype: string
.. versionadded:: 1.7.0
"""
if self.text_fragment is None:
return None
return self.text_fragment.text
@property
def begin(self):
"""
The begin time of this sync map fragment.
:rtype: :class:`~aeneas.exacttiming.TimeValue`
"""
if self.interval is None:
return None
return self.interval.begin
@begin.setter
def begin(self, begin):
if self.interval is None:
raise TypeError(u"Attempting to set begin when interval is None")
if not isinstance(begin, TimeValue):
raise TypeError(u"The given begin value is not an instance of TimeValue")
self.interval.begin = begin
@property
def end(self):
"""
The end time of this sync map fragment.
:rtype: :class:`~aeneas.exacttiming.TimeValue`
"""
if self.interval is None:
return None
return self.interval.end
@end.setter
def end(self, end):
if self.interval is None:
raise TypeError(u"Attempting to set end when interval is None")
if not isinstance(end, TimeValue):
raise TypeError(u"The given end value is not an instance of TimeValue")
self.interval.end = end
@property
def length(self):
"""
The audio duration of this sync map fragment,
as end time minus begin time.
:rtype: :class:`~aeneas.exacttiming.TimeValue`
"""
if self.interval is None:
return TimeValue("0.000")
return self.interval.length
@property
def has_zero_length(self):
"""
Returns ``True`` if this sync map fragment has zero length,
that is, if its begin and end values coincide.
:rtype: bool
.. versionadded:: 1.7.0
"""
return self.length == TimeValue("0.000")
@property
def chars(self):
"""
Return the number of characters of the text fragment,
not including the line separators.
:rtype: int
.. versionadded:: 1.2.0
"""
if self.text_fragment is None:
return 0
return self.text_fragment.chars
@property
def rate(self):
"""
The rate, in characters/second, of this fragment.
If the fragment is not ``REGULAR`` or its duration is zero,
return ``None``.
:rtype: ``None`` or :class:`~aeneas.exacttiming.Decimal`
.. versionadded:: 1.2.0
"""
if (
(self.fragment_type != self.REGULAR) or
(self.has_zero_length)
):
return None
return Decimal(self.chars / self.length)
[docs] def rate_lack(self, max_rate):
"""
The time interval that this fragment lacks
to respect the given max rate.
A positive value means that the current fragment
is faster than the max rate (bad).
A negative or zero value means that the current fragment
has rate slower or equal to the max rate (good).
Always return ``0.000`` for fragments that are not ``REGULAR``.
:param max_rate: the maximum rate (characters/second)
:type max_rate: :class:`~aeneas.exacttiming.Decimal`
:rtype: :class:`~aeneas.exacttiming.TimeValue`
.. versionadded:: 1.7.0
"""
if self.fragment_type == self.REGULAR:
return self.chars / max_rate - self.length
return TimeValue("0.000")
[docs] def rate_slack(self, max_rate):
"""
The maximum time interval that can be stolen to this fragment
while keeping it respecting the given max rate.
For ``REGULAR`` fragments this value is
the opposite of the ``rate_lack``.
For ``NONSPEECH`` fragments this value is equal to
the length of the fragment.
For ``HEAD`` and ``TAIL`` fragments this value is ``0.000``,
meaning that they cannot be stolen.
:param max_rate: the maximum rate (characters/second)
:type max_rate: :class:`~aeneas.exacttiming.Decimal`
:rtype: :class:`~aeneas.exacttiming.TimeValue`
.. versionadded:: 1.7.0
"""
if self.fragment_type == self.REGULAR:
return -self.rate_lack(max_rate)
elif self.fragment_type == self.NONSPEECH:
return self.length
else:
return TimeValue("0.000")