segmentation_information()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 10
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
1 1
import re
2 1
import time
3
4 1
import attr
5 1
6
from music_album_creation.tracks_parsing import StringParser
7
8
9 1
#####################################
10
def segmentation_information(tracks_timestamps_info):
11
    """
12
    Converts input Nx2 (timestamps) list of lists to Nx3 (timestamps) list of lists. The exception being the last list that has 2 elements\n
13
    The input list's inner lists' elements are 'track_name' and 'starting_timestamp' in hhmmss format.\n
14
    :param list of lists tracks_timestamps_info: each inner list should contain track title (no need for number and without extension)
15
    and starting time stamp in hh:mm:ss format
16
    :return: each iner list contains track path and timestamp in seconds
17
    :rtype: list of lists
18 1
    """
19
    return [list(_) for _ in _generate_segmentation_spans(tracks_timestamps_info)]
20
21 1
22
def _generate_segmentation_spans(tracks_info):
23
    """
24
    Given a data list, with each element representing an album's track (as an inner 2-element list with 1st element the 'track_name' and 2nd a timetamp in hh:mm:ss format (the track starts it's playback at that timestamp in relation with the total album playtime), the path to the alum file at hand and the desired output directory or potentially storing the track files,
25
    generates 3-length tuples with the track_file_path, starting timestamp and ending timestamp. Purpose is for the yielded tripplets to be digested for audio segmentation. The exception being the last tuple yielded that has 2 elements; it naturally misses the ending timestamp.\n
26
    :param list tracks_info:
27
    :returns: 3-element tuples with track_file_path, starting_timestamp, ending_timestamp
28
    :rtype: tuple
29 1
    """
30 1
    track_index_generator = iter(
31 1
        (lambda x: str(x) if 9 < x else '0' + str(x))(_)
32 1
        for _ in range(1, len(tracks_info) + 1)
33
    )
34
    for i in range(len(tracks_info) - 1):
35
        if Timestamp(tracks_info[i + 1][1]) <= Timestamp(tracks_info[i][1]):
36 1
            raise TrackTimestampsSequenceError(
37
                "Track '{} - {}' starting timestamp '{}' should be 'bigger' than track's '{} - {}'; '{}'".format(
38
                    i + 2,
39
                    tracks_info[i + 1][0],
40
                    tracks_info[i + 1][1],
41 1
                    i + 1,
42
                    tracks_info[i][0],
43
                    tracks_info[i][1],
44
                )
45
            )
46 1
        yield (
47
            '{} - {}'.format(next(track_index_generator), tracks_info[i][0]),
48
            str(int(Timestamp(tracks_info[i][1]))),
49
            str(int(Timestamp(tracks_info[i + 1][1]))),
50
        )
51
    yield (
52 1
        '{} - {}'.format(next(track_index_generator), tracks_info[-1][0]),
53
        str(int(Timestamp(tracks_info[-1][1]))),
54
    )
55 1
56
57
def to_timestamps_info(tracks_durations_info):
58
    """Call this method to transform a list of 2-legnth lists of track_name - duration_hhmmss pairs to the equivalent list of lists but with starting timestamps in hhmmss format inplace of the durations.\n
59
    :param list tracks_durations_info: eg: [['Know your enemy', '3:45'], ['Wake up', '4:53'], ['Testify', '4:32']]
60
    :return: eg: [['Know your enemy', '0:00'], ['Wake up', '3:45'], ['Testify', '8:38']]
61 1
    :rtype: list
62 1
    """
63 1
    return [list(_) for _ in _gen_timestamp_data(tracks_durations_info)]
64 1
65 1
66 1
def _gen_timestamp_data(tracks_duration_info):
67
    """
68
    :param list of lists tracks_duration_info: each inner list has as 1st element a track name and as 2nd the track duration in hh:mm:s format
69 1
    :return: list of lists with timestamps instead of durations ready to feed for segmentation
70 1
    :rtype: list
71
    """
72
    i = 1
73 1
    p = Timestamp('0:00')
74 1
    yield tracks_duration_info[0][0], str(p)
75
    while i < len(tracks_duration_info):
76 1
        try:
77
            yield tracks_duration_info[i][0], str(
78 1
                p + Timestamp(tracks_duration_info[i - 1][1])
79
            )
80 1
        except WrongTimestampFormat as e:
81 1
            raise e
82
        p += Timestamp(tracks_duration_info[i - 1][1])
83
        i += 1
84 1
85
86 1
##############################################
87
@attr.s
88 1
class SegmentationInformation(object):
89 1
    """Encapsulates per track: ['track-name', 'start-timestamp', 'end-timestamp']. Last entry does not have 'end-timestamp' because the end of the album is implied."""
90
91 1
    tracks_info = attr.ib(init=True)
92 1
93
    @classmethod
94 1
    def from_tracks_information(cls, tracks_information, hhmmss_type):
95 1
        if hhmmss_type.lower().startswith('timestamp'):
96
            return SegmentationInformation(segmentation_information(list(tracks_information)))
97 1
        return SegmentationInformation(
98 1
            segmentation_information(to_timestamps_info(list(tracks_information)))
99 1
        )
100
101 1
    @classmethod
102
    def from_multiline(cls, string, hhmmss_type):
103 1
        return cls.from_tracks_information(
104
            TracksInformation.from_multiline(string), hhmmss_type
105 1
        )
106
107
    def __len__(self):
108
        return len(self.tracks_info)
109 1
110 1
    def __getitem__(self, item):
111
        return self.tracks_info[item]
112 1
113 1
114
@attr.s
115
class TracksInformation(object):
116 1
    """Encapsulates per track: ['track-name', 'hhmmss']"""
117 1
118
    tracks_data = attr.ib(init=True)
119 1
    track_names = attr.ib(
120
        init=False,
121 1
        default=attr.Factory(lambda self: [x[0] for x in self.tracks_data], takes_self=True),
122 1
    )
123 1
    hhmmss_list = attr.ib(
124
        init=False,
125 1
        default=attr.Factory(lambda self: [x[1] for x in self.tracks_data], takes_self=True),
126
    )
127 1
128 1
    @classmethod
129
    def from_multiline(cls, string):
130 1
        return TracksInformation(StringParser().parse_hhmmss_string(string))
131
132 1
    @classmethod
133 1
    def from_multiline_interactive(cls, interactive_dialog):
134 1
        return TracksInformation(StringParser().parse_hhmmss_string(interactive_dialog()))
135 1
136 1
    def __len__(self):
137 1
        return len(self.tracks_data)
138 1
139 1
    def __getitem__(self, item):
140
        return self.tracks_data[item]
141 1
142 1
143 1
##########################################################
144 1
class Timestamp(object):
145 1
    instances = {}
146
147 1
    @classmethod
148 1
    def __str(cls, element):
149
        if len(element) == 1:
150 1
            return '0{}'.format(int(element))
151 1
        return element
152 1
153 1
    @classmethod
154 1
    def __pos(cls, array):
155 1
        i = 0
156 1
        while i < len(array) and array[i] == 0:
157 1
            i += 1
158 1
        return i
159
160 1
    def __new__(cls, *args, **kwargs):
161
        hhmmss = args[0]
162
        m = re.compile(r'^(?:(\d?\d):){0,2}(\d?\d)$').search(hhmmss)
163 1
        if not m:
164
            raise WrongTimestampFormat(
165 1
                "Timestamp given: '{}'. Please use the 'hh:mm:ss' format.".format(hhmmss)
166
            )
167 1
        groups = hhmmss.split(':')
168 1
        if not all([0 <= int(_) <= 60 for _ in groups]):
169
            raise WrongTimestampFormat(
170 1
                "Timestamp given: '{}'. Please use the 'hh:mm:ss' format.".format(hhmmss)
171
            )
172
173 1
        ind = cls.__pos(groups)
174 1
        if len(groups) == 1:
175
            minlength_string = '{}:{}'.format(0, cls.__str(groups[0]))
176 1
        elif len(groups) - ind - 1 < 2:
177 1
            minlength_string = '{}:{}'.format(int(groups[-2]), cls.__str(groups[-1]))
178
        else:
179 1
            minlength_string = ':'.join(
180 1
                [str(int(groups[ind]))] + [y for y in groups[ind + 1 :]]
181
            )
182 1
        stripped_string = ':'.join((str(int(_)) for _ in minlength_string.split(':')))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable _ does not seem to be defined.
Loading history...
183 1
184
        if stripped_string in cls.instances:
185 1
            return cls.instances[stripped_string]
186 1
        x = super(Timestamp, cls).__new__(cls)
187
        x.__minlength_string = minlength_string
188 1
        x._a = 'gg'
189 1
        x.__stripped_string = stripped_string
190
        x.__s = sum([60**i * int(gr) for i, gr in enumerate(reversed(groups))])
191 1
        cls.instances[x.__stripped_string] = x
192 1
        return x
193
194 1
    def __init__(self, hhmmss):
195 1
        pass
196
197 1
    @staticmethod
198 1
    def from_duration(seconds):
199
        return Timestamp(time.strftime('%H:%M:%S', time.gmtime(seconds)))
200
201
    def __int__(self):
202
        return self.__s
203
204
    def __repr__(self):
205
        return self.__minlength_string
206
207
    def __str__(self):
208
        return self.__minlength_string
209
210
    def __hash__(self):
211
        return self.__s
212
213
    def __eq__(self, other):
214
        return hash(self) == hash(other)
215
216
    def __lt__(self, other):
217
        return int(self) < int(other)
218
219
    def __le__(self, other):
220
        return int(self) <= int(other)
221
222
    def __gt__(self, other):
223
        return int(other) < int(self)
224
225
    def __ge__(self, other):
226
        return int(other) <= int(self)
227
228
    def __add__(self, other):
229
        return Timestamp.from_duration(int(self) + int(other))
230
231
    def __sub__(self, other):
232
        return Timestamp.from_duration(int(self) - int(other))
233
234
235
class WrongTimestampFormat(Exception):
236
    pass
237
238
239
class TrackTimestampsSequenceError(Exception):
240
    pass
241