Coverage for skcvideo/timeline.py: 0%
118 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-02 14:10 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-02 14:10 +0200
1import cv2
2import numpy as np
4from skcvideo.colors import RED, WHITE
5from skcvideo.core import Button
6from skcvideo.utils import put_text
9class Timeline:
10 def __init__(self, box=None, timeline_width=20, margin=5, pixel_per_frame=1):
11 if box is None:
12 box = [955, 110, 1035, 1910]
13 self.name = "Timeline"
14 self.box = box
15 self.pixel_per_frame = pixel_per_frame
17 self.timeline_width = timeline_width
18 self.margin = margin
19 self.gap = self.timeline_width + 2 * self.margin
21 self.timeline_length = self.box[3] - self.box[1]
23 self.hitbox = (self.box[1], self.box[0], self.box[3], self.box[2])
24 self.buttons = [Button(self.hitbox, self.jump_event)]
26 @property
27 def min_frame(self):
28 return getattr(self.parent, "min_frame", 0)
30 @property
31 def max_frame(self):
32 return getattr(self.parent, "max_frame", 9000)
34 @property
35 def n_timelines(self):
36 return ((self.max_frame - self.min_frame) * self.pixel_per_frame) // self.timeline_length + 1
38 def jump_event(self, x, y, *kwargs):
39 frame = self.get_frame(x, y)
40 self.parent.jump(frame)
42 def timeline_color(self, frame):
43 """
44 Here you define the color of the timeline with repect to the frame.
45 """
46 return (0, 0, 0)
48 def build(self, image):
49 """
50 Draws the timeline's background composed of several timeline lines
51 with box and graduations.
52 """
53 # Puts time labels label
54 put_text(
55 img=image,
56 text="min",
57 org=(self.box[1] - 90, self.box[0] - self.margin - self.timeline_width // 2),
58 fontScale=0.6,
59 align_x="left",
60 )
62 # Draws graduations labels
63 for frame in range(0, self.box[3] - self.box[1], 100):
64 if (frame + 1) % 600 == 0:
65 put_text(
66 img=image,
67 text=f"{(frame + 1) // 600}min",
68 org=(self.box[1] + frame, self.box[0] - self.margin - self.timeline_width // 2 - 2),
69 fontScale=0.6,
70 align_x="center",
71 )
72 else:
73 put_text(
74 img=image,
75 text=f"{(frame + 1) % 600 // 10}s",
76 org=(self.box[1] + frame, self.box[0] - self.margin - self.timeline_width // 2 + 4),
77 fontScale=0.4,
78 align_x="center",
79 )
81 # Draws each timeline's line
82 for i in range(self.n_timelines):
83 self.draw_timeline_box(image, i)
85 # Draws graduations
86 for frame in range(self.max_frame - self.min_frame):
87 x = frame % self.timeline_length
88 y = frame // self.timeline_length
90 # A small mark every 5 seconds
91 if ((frame + 1) % 50) == 0:
92 cv2.line(
93 image,
94 (self.box[1] + x, self.box[0] + y * self.gap + self.margin - 1),
95 (self.box[1] + x, self.box[0] + (y + 1) * self.gap - self.margin + 1),
96 color=WHITE,
97 thickness=1,
98 )
100 # A big mark every minute
101 if ((frame + 1) % 600) == 0:
102 cv2.line(
103 image,
104 (self.box[1] + x, self.box[0] + y * self.gap + self.margin - 3),
105 (self.box[1] + x, self.box[0] + (y + 1) * self.gap - self.margin + 3),
106 color=WHITE,
107 thickness=2,
108 )
110 self.draw_timeline_data(image)
112 def refresh(self, image, frame):
113 self.draw_timer(image, frame)
115 def draw_timeline_box(self, image, i):
116 """
117 Draws one line of the timeline's background, which consists in a
118 simple white box.
119 """
120 # Manage the offset
121 y_min = self.box[0] + self.margin + i * self.gap
122 y_max = y_min + self.timeline_width
124 # The last box may not go up to the end.
125 if i == self.n_timelines - 1:
126 frame_max = (self.max_frame - self.min_frame) * self.pixel_per_frame % self.timeline_length
127 timeline_max = self.box[1] + frame_max
128 else:
129 timeline_max = self.box[3]
131 # Draws the box
132 cv2.rectangle(
133 image,
134 (self.box[1], y_min),
135 (timeline_max, y_max),
136 color=WHITE,
137 thickness=1,
138 )
140 # Adds a time label before the line
141 put_text(
142 img=image,
143 text=f"{3 * i}-{3 * (i + 1)}",
144 org=(self.box[1] - 90, (y_min + y_max) // 2),
145 fontScale=0.6,
146 align_x="left",
147 )
149 def draw_timeline_data(self, im):
150 """
151 Draws information on the timeline. Useful to have a global view of
152 your data or to have a reference for jumping in the video.
153 """
154 for frame in range(self.min_frame, self.max_frame):
155 self.draw_one_timeline_data(im, frame)
157 def draw_one_timeline_data(self, im, frame):
158 """
159 Colors the given frame on the timeline according to the timeline_color
160 function
161 """
162 color = self.timeline_color(frame)
163 if color != (0, 0, 0):
164 timer = frame - self.min_frame
165 timer_x = timer % self.timeline_length
166 timer_y = timer // self.timeline_length
168 x1 = self.box[1] + timer_x * self.pixel_per_frame
169 x2 = x1 + self.pixel_per_frame
170 y1 = timer_y * self.gap + self.box[0] + self.margin + 1
171 y2 = y1 + self.timeline_width - 2
172 im[y1:y2, x1:x2] = color
174 def draw_timer(self, image, frame):
175 """
176 Draws a timer on the timeline on the given frame. To be used in the
177 refresh method of the parent Reader class.
178 """
179 timer = (frame - self.min_frame) * self.pixel_per_frame
180 timer_x = timer % self.timeline_length
181 timer_y = timer // self.timeline_length
182 cv2.line(
183 image,
184 (self.box[1] + timer_x, timer_y * self.gap + self.box[0]),
185 (self.box[1] + timer_x, (timer_y + 1) * self.gap + self.box[0]),
186 color=WHITE,
187 thickness=3,
188 )
189 cv2.line(
190 image,
191 (self.box[1] + timer_x, timer_y * self.gap + self.box[0]),
192 (self.box[1] + timer_x, (timer_y + 1) * self.gap + self.box[0]),
193 color=RED,
194 thickness=2,
195 )
197 def get_frame(self, x, y):
198 """
199 Returns the frame corresponding to a given pixel of the timeline.
200 It is used to be able to click one the timeline to jump to a
201 particular frame.
202 """
203 x = x - self.box[1]
204 y = (y - self.box[0]) // self.gap
205 frame = (x + y * self.timeline_length) // self.pixel_per_frame + self.min_frame
206 return frame
209GRAY_BAR = np.array(
210 [
211 [189, 189, 190],
212 [193, 193, 194],
213 [196, 197, 198],
214 [200, 201, 202],
215 [204, 205, 206],
216 [208, 208, 209],
217 [212, 212, 213],
218 [215, 216, 217],
219 [219, 220, 221],
220 [235, 235, 236],
221 ],
222)
224BLUE_BAR = np.array(
225 [
226 [224, 137, 44],
227 [218, 134, 43],
228 [213, 130, 42],
229 [207, 127, 41],
230 [204, 125, 40],
231 [204, 125, 40],
232 [204, 125, 40],
233 [204, 125, 40],
234 ],
235)
238class VlcTimeline:
239 def __init__(self, box=None):
240 """
241 Args:
242 box: list [x1, y1, x2, y2]
243 """
244 if box is None:
245 box = [79, 966, 1771, 976]
246 self.box = box
247 self.buttons = [Button(self.box, self.jump_event)]
248 self.timeline_length = float(self.box[2] - self.box[0])
250 @property
251 def min_frame(self):
252 return getattr(self.parent, "min_frame", 0)
254 @property
255 def max_frame(self):
256 return getattr(self.parent, "max_frame", 9000)
258 @property
259 def frames_length(self):
260 return float(self.max_frame - self.min_frame)
262 def jump_event(self, x, y, *kwargs):
263 frame = self.get_frame(x, y)
264 self.parent.jump(frame)
266 def build(self, image):
267 image[self.box[1] : self.box[1] + 18] = np.array([240, 241, 242])[np.newaxis, np.newaxis, :]
268 image[self.box[1] + 4 : self.box[1] + 14, self.box[0] : self.box[2]] = GRAY_BAR[:, np.newaxis, :]
270 def refresh(self, image, frame):
271 self.draw_timer(image, frame)
273 def draw_timer(self, image, frame):
274 frame = float(frame - self.min_frame)
275 i = int(np.round(frame / self.frames_length * self.timeline_length))
276 image[self.box[1] + 5 : self.box[1] + 13, self.box[0] : self.box[0] + i] = BLUE_BAR[:, np.newaxis, :]
278 def get_frame(self, x, y):
279 x = float(x - self.box[0])
280 frame = int(np.round(x / self.timeline_length * self.frames_length)) + self.min_frame
281 return frame