def search_gen(self, query, audio_basename=None, case_sensitive=False,
                   subsequence=False, supersequence=False, timing_error=0.0,
                   anagram=False, missing_word_tolerance=0):
        """
        A generator that searches for the `query` within the audiofiles of the
        src_dir.

        Parameters
        ----------
        query : str
            A string that'll be searched. It'll be splitted on spaces and then
            each word gets sequentially searched.
        audio_basename : str, optional
            Search only within the given audio_basename.

            Default is `None`
        case_sensitive : bool, optional
            Default is `False`
        subsequence : bool, optional
            `True` if it's not needed for the exact word be detected and larger
            strings that contain the given one are fine.

            If the query is a sentences with multiple words, it'll be
            considered for each word, not the whole sentence.

            Default is `False`.
        supersequence : bool, optional
            `True` if it's not needed for the exact word be detected and
            smaller strings that are contained within the given one are fine.

            If the query is a sentences with multiple words, it'll be
            considered for each word, not the whole sentence.

            Default is `False`.
        anagram : bool, optional
            `True` if it's acceptable for a complete permutation of the word to
            be found. e.g. "abcde" would be acceptable for "edbac".

            If the query is a sentences with multiple words, it'll be
            considered for each word, not the whole sentence.

            Default is `False`.
        timing_error : None or float, optional
            Sometimes other words (almost always very small) would be detected
            between the words of the `query`. This parameter defines the
            timing difference/tolerance of the search.

            Default is 0.0 i.e. No timing error is tolerated.
        missing_word_tolerance : int, optional
            The number of words that can be missed within the result.
            For example, if the query is "Some random text" and the tolerance
            value is `1`, then "Some text" would be a valid response.
            Note that the first and last words cannot be missed. Also,
            there'll be an error if the value is more than the number of
            available words. For the example above, any value more than 1
            would have given an error (since there's only one word i.e.
            "random" that can be missed)

            Default is 0.

        Yields
        ------
        {"File Name": str, "Query": `query`, "Result": (float, float)}
            The result of the search is returned as a tuple which is the value
            of the "Result" key. The first element of the tuple is the
            starting second of `query` and the last element is the ending
            second of `query`

        Raises
        ------
        AssertionError
            If `missing_word_tolerance` value is more than the total number of
            words in the query minus 2 (since the first and the last word
            cannot be removed)
        """
        def case_sensitivity_handler(case_sensitive=case_sensitive):

            def get_query_words(query, case_sensitive=case_sensitive):
                query_words = list(
                    filter(None, ''.join(
                        filter(lambda char: char in (ascii_letters + " "),
                               list(query))).split(" ")))
                if case_sensitive:
                    return query_words
                return [q.lower() for q in query_words]

            def get_timestamps(case_sensitive=case_sensitive):
                timestamps = self.get_timestamps().copy()
                if not case_sensitive:
                    return {
                        audio_basename: [
                            _WordBlock(word=word_block.word.lower(),
                                       start=word_block.start,
                                       end=word_block.end)
                            for word_block in timestamps[audio_basename]]
                        for audio_basename in timestamps}
                return timestamps

            return locals()

        query_words = case_sensitivity_handler()["get_query_words"](query)
        timestamps = case_sensitivity_handler()["get_timestamps"]()

        assert abs(missing_word_tolerance -
                   (len(query_words) - 2)) >= 0, (
            "The number of words that can be missing must be less than "
            "the total number of words within the query minus the first and "
            "the last word."
        )

        for audio_filename in (
                (lambda: (timestamps.keys() if audio_basename is None else
                          [audio_basename]))()):
            result = list()
            missed_words_so_far = 0
            query_cursor = 0
            try:
                for word_block in timestamps[audio_filename]:
                    if (
                            # When the query is identical
                            (word_block.word == query_words[query_cursor]) or
                            # When the query is a subsequence of what's
                            # available
                            (subsequence and
                             self._is_subsequence_of(query_words[query_cursor],
                                                     word_block.word)) or
                            # When the query is a supersequence of what's
                            # available
                            (supersequence and self._is_supersequence_of(
                                query_words[query_cursor], word_block.word)) or
                            # When query is a permutation of what's available.
                            (anagram and self._is_anagram_of(
                                query_words[query_cursor], word_block.word))
                    ):
                        result.append(word_block)

                        if timing_error is not None:
                            try:
                                if round(result[-1].start -
                                         result[-2].end, 4) > timing_error:
                                    result = list()
                                    query_cursor = 0
                            except IndexError:
                                pass

                        if self._partial_search_validator(
                                query_words, [x.word for x in result],
                                anagram=anagram,
                                subsequence=subsequence,
                                supersequence=supersequence):
                            yield {
                                "File Name": audio_filename,
                                "Query": query,
                                "Result": tuple([result[0].start,
                                                 result[-1].end])}
                            result = list()
                            query_cursor = 0

                        else:
                            query_cursor += 1

                    elif missed_words_so_far > missing_word_tolerance:
                        result = list()
                        query_cursor = 0

                    elif (missing_word_tolerance > 0) and (len(result) > 0):
                        result.append(word_block)
                        missed_words_so_far += 1

            except KeyError:
                # This is needed for the case where no timestamp is present.
                pass

            except IndexError:
                # This is needed when multiple timestamps are present, and
                # advanced control structures like `missed_word_tolerance` are
                # non-zero. In that case, it can search to the end of the first
                # timestamp looking to complete its partial result and since
                # there are no more `word_block`s left, it returns an error.
                # `continue` should be used to reset the partial result and
                # move to the next timestamp.
                continue
def search_gen ( self , query , audio_basename = None , case_sensitive = False , subsequence = False , supersequence = False , timing_error = 0.0 , anagram = False , missing_word_tolerance = 0 ) : def case_sensitivity_handler ( case_sensitive = case_sensitive ) : def get_query_words ( query , case_sensitive = case_sensitive ) : query_words = list ( filter ( None , '' . join ( filter ( lambda char : char in ( ascii_letters + " " ) , list ( query ) ) ) . split ( " " ) ) ) if case_sensitive : return query_words return [ q . lower ( ) for q in query_words ] def get_timestamps ( case_sensitive = case_sensitive ) : timestamps = self . get_timestamps ( ) . copy ( ) if not case_sensitive : return { audio_basename : [ _WordBlock ( word = word_block . word . lower ( ) , start = word_block . start , end = word_block . end ) for word_block in timestamps [ audio_basename ] ] for audio_basename in timestamps } return timestamps return locals ( ) query_words = case_sensitivity_handler ( ) [ "get_query_words" ] ( query ) timestamps = case_sensitivity_handler ( ) [ "get_timestamps" ] ( ) assert abs ( missing_word_tolerance - ( len ( query_words ) - 2 ) ) >= 0 , ( "The number of words that can be missing must be less than " "the total number of words within the query minus the first and " "the last word." ) for audio_filename in ( ( lambda : ( timestamps . keys ( ) if audio_basename is None else [ audio_basename ] ) ) ( ) ) : result = list ( ) missed_words_so_far = 0 query_cursor = 0 try : for word_block in timestamps [ audio_filename ] : if ( ( word_block . word == query_words [ query_cursor ] ) or ( subsequence and self . _is_subsequence_of ( query_words [ query_cursor ] , word_block . word ) ) or ( supersequence and self . _is_supersequence_of ( query_words [ query_cursor ] , word_block . word ) ) or ( anagram and self . _is_anagram_of ( query_words [ query_cursor ] , word_block . word ) ) ) : result . append ( word_block ) if timing_error is not None : try : if round ( result [ - 1 ] . start - result [ - 2 ] . end , 4 ) > timing_error : result = list ( ) query_cursor = 0 except IndexError : pass if self . _partial_search_validator ( query_words , [ x . word for x in result ] , anagram = anagram , subsequence = subsequence , supersequence = supersequence ) : yield { "File Name" : audio_filename , "Query" : query , "Result" : tuple ( [ result [ 0 ] . start , result [ - 1 ] . end ] ) } result = list ( ) query_cursor = 0 else : query_cursor += 1 elif missed_words_so_far > missing_word_tolerance : result = list ( ) query_cursor = 0 elif ( missing_word_tolerance > 0 ) and ( len ( result ) > 0 ) : result . append ( word_block ) missed_words_so_far += 1 except KeyError : pass except IndexError : continue
A generator that searches for the `query` within the audiofiles of the
        src_dir.

        Parameters
        ----------
        query : str
            A string that'll be searched. It'll be splitted on spaces and then
            each word gets sequentially searched.
        audio_basename : str, optional
            Search only within the given audio_basename.

            Default is `None`
        case_sensitive : bool, optional
            Default is `False`
        subsequence : bool, optional
            `True` if it's not needed for the exact word be detected and larger
            strings that contain the given one are fine.

            If the query is a sentences with multiple words, it'll be
            considered for each word, not the whole sentence.

            Default is `False`.
        supersequence : bool, optional
            `True` if it's not needed for the exact word be detected and
            smaller strings that are contained within the given one are fine.

            If the query is a sentences with multiple words, it'll be
            considered for each word, not the whole sentence.

            Default is `False`.
        anagram : bool, optional
            `True` if it's acceptable for a complete permutation of the word to
            be found. e.g. "abcde" would be acceptable for "edbac".

            If the query is a sentences with multiple words, it'll be
            considered for each word, not the whole sentence.

            Default is `False`.
        timing_error : None or float, optional
            Sometimes other words (almost always very small) would be detected
            between the words of the `query`. This parameter defines the
            timing difference/tolerance of the search.

            Default is 0.0 i.e. No timing error is tolerated.
        missing_word_tolerance : int, optional
            The number of words that can be missed within the result.
            For example, if the query is "Some random text" and the tolerance
            value is `1`, then "Some text" would be a valid response.
            Note that the first and last words cannot be missed. Also,
            there'll be an error if the value is more than the number of
            available words. For the example above, any value more than 1
            would have given an error (since there's only one word i.e.
            "random" that can be missed)

            Default is 0.

        Yields
        ------
        {"File Name": str, "Query": `query`, "Result": (float, float)}
            The result of the search is returned as a tuple which is the value
            of the "Result" key. The first element of the tuple is the
            starting second of `query` and the last element is the ending
            second of `query`

        Raises
        ------
        AssertionError
            If `missing_word_tolerance` value is more than the total number of
            words in the query minus 2 (since the first and the last word
            cannot be removed)