]> git.sesse.net Git - nageru/blob - clip_list.cpp
Implement fades.
[nageru] / clip_list.cpp
1 #include "mainwindow.h"
2
3 #include <math.h>
4 #include <string>
5 #include <vector>
6
7 #include "clip_list.h"
8 #include "timebase.h"
9 #include "ui_mainwindow.h"
10
11 using namespace std;
12
13 string pts_to_string(int64_t pts)
14 {
15         int64_t t = lrint((pts / double(TIMEBASE)) * 1e3);  // In milliseconds.
16         int ms = t % 1000;
17         t /= 1000;
18         int sec = t % 60;
19         t /= 60;
20         int min = t % 60;
21         t /= 60;
22         int hour = t;
23
24         char buf[256];
25         snprintf(buf, sizeof(buf), "%d:%02d:%02d.%03d", hour, min, sec, ms);
26         return buf;
27 }
28
29 string duration_to_string(int64_t pts_diff)
30 {
31         int64_t t = lrint((pts_diff / double(TIMEBASE)) * 1e3);  // In milliseconds.
32         int ms = t % 1000;
33         t /= 1000;
34         int sec = t % 60;
35         t /= 60;
36         int min = t;
37
38         char buf[256];
39         snprintf(buf, sizeof(buf), "%d:%02d.%03d", min, sec, ms);
40         return buf;
41 }
42
43 int ClipList::rowCount(const QModelIndex &parent) const {
44         if (parent.isValid()) return 0;
45         return clips.size();
46 }
47
48 int PlayList::rowCount(const QModelIndex &parent) const {
49         if (parent.isValid()) return 0;
50         return clips.size();
51 }
52
53 int ClipList::columnCount(const QModelIndex &parent) const {
54         if (parent.isValid()) return 0;
55         return int(Column::NUM_COLUMNS);
56 }
57
58 int PlayList::columnCount(const QModelIndex &parent) const {
59         if (parent.isValid()) return 0;
60         return int(Column::NUM_COLUMNS);
61 }
62
63 QVariant ClipList::data(const QModelIndex &parent, int role) const {
64         if (!parent.isValid())
65                 return QVariant();
66         const int row = parent.row(), column = parent.column();
67         if (size_t(row) >= clips.size())
68                 return QVariant();
69
70         if (role == Qt::TextAlignmentRole) {
71                 switch (Column(column)) {
72                 case Column::IN:
73                 case Column::OUT:
74                 case Column::DURATION:
75                         return Qt::AlignRight + Qt::AlignVCenter;
76                 default:
77                         return Qt::AlignLeft + Qt::AlignVCenter;
78                 }
79         }
80
81         if (role != Qt::DisplayRole && role != Qt::EditRole)
82                 return QVariant();
83
84         switch (Column(column)) {
85         case Column::IN:
86                 return QString::fromStdString(pts_to_string(clips[row].pts_in));
87         case Column::OUT:
88                 if (clips[row].pts_out >= 0) {
89                         return QString::fromStdString(pts_to_string(clips[row].pts_out));
90                 } else {
91                         return QVariant();
92                 }
93         case Column::DURATION:
94                 if (clips[row].pts_out >= 0) {
95                         return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
96                 } else {
97                         return QVariant();
98                 }
99         case Column::CAMERA_1:
100         case Column::CAMERA_2:
101         case Column::CAMERA_3:
102         case Column::CAMERA_4: {
103                 unsigned stream_idx = column - int(Column::CAMERA_1);
104                 return QString::fromStdString(clips[row].descriptions[stream_idx]);
105         }
106         default:
107                 return "";
108         }
109 }
110
111 QVariant PlayList::data(const QModelIndex &parent, int role) const {
112         if (!parent.isValid())
113                 return QVariant();
114         const int row = parent.row(), column = parent.column();
115         if (size_t(row) >= clips.size())
116                 return QVariant();
117
118         if (role == Qt::TextAlignmentRole) {
119                 switch (Column(column)) {
120                 case Column::PLAYING:
121                         return Qt::AlignCenter;
122                 case Column::IN:
123                 case Column::OUT:
124                 case Column::DURATION:
125                 case Column::FADE_TIME:
126                         return Qt::AlignRight + Qt::AlignVCenter;
127                 case Column::CAMERA:
128                         return Qt::AlignCenter;
129                 default:
130                         return Qt::AlignLeft + Qt::AlignVCenter;
131                 }
132         }
133         if (role == Qt::BackgroundRole) {
134                 if (Column(column) == Column::PLAYING) {
135                         if (row == currently_playing_index) {
136                                 // This only really works well for the first column, for whatever odd Qt reason.
137                                 QLinearGradient grad(QPointF(0, 0), QPointF(1, 0));
138                                 grad.setCoordinateMode(grad.QGradient::ObjectBoundingMode);
139                                 grad.setColorAt(0.0f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
140                                 grad.setColorAt(play_progress, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
141                                 if (play_progress + 0.01f <= 1.0f) {
142                                         grad.setColorAt(play_progress + 0.01f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.0f));
143                                 }
144                                 return QBrush(grad);
145                         } else {
146                                 return QVariant();
147                         }
148                 } else {
149                         return QVariant();
150                 }
151         }
152
153         if (role != Qt::DisplayRole && role != Qt::EditRole)
154                 return QVariant();
155
156         switch (Column(column)) {
157         case Column::PLAYING:
158                 return (row == currently_playing_index) ? "→" : "";
159         case Column::IN:
160                 return QString::fromStdString(pts_to_string(clips[row].pts_in));
161         case Column::OUT:
162                 if (clips[row].pts_out >= 0) {
163                         return QString::fromStdString(pts_to_string(clips[row].pts_out));
164                 } else {
165                         return QVariant();
166                 }
167         case Column::DURATION:
168                 if (clips[row].pts_out >= 0) {
169                         return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
170                 } else {
171                         return QVariant();
172                 }
173         case Column::CAMERA:
174                 return qlonglong(clips[row].stream_idx + 1);
175         case Column::DESCRIPTION:
176                 return QString::fromStdString(clips[row].descriptions[clips[row].stream_idx]);
177         case Column::FADE_TIME: {
178                 stringstream ss;
179                 ss.imbue(locale("C"));
180                 ss.precision(3);
181                 ss << fixed << clips[row].fade_time_seconds;
182                 return QString::fromStdString(ss.str());
183         }
184         default:
185                 return "";
186         }
187 }
188
189 QVariant ClipList::headerData(int section, Qt::Orientation orientation, int role) const {
190         if (role != Qt::DisplayRole)
191                 return QVariant();
192         if (orientation != Qt::Horizontal)
193                 return QVariant();
194
195         switch (Column(section)) {
196         case Column::IN:
197                 return "In";
198         case Column::OUT:
199                 return "Out";
200         case Column::DURATION:
201                 return "Duration";
202         case Column::CAMERA_1:
203                 return "Camera 1";
204         case Column::CAMERA_2:
205                 return "Camera 2";
206         case Column::CAMERA_3:
207                 return "Camera 3";
208         case Column::CAMERA_4:
209                 return "Camera 4";
210         default:
211                 return "";
212         }
213 }
214
215 QVariant PlayList::headerData(int section, Qt::Orientation orientation, int role) const {
216         if (role != Qt::DisplayRole)
217                 return QVariant();
218         if (orientation != Qt::Horizontal)
219                 return QVariant();
220
221         switch (Column(section)) {
222         case Column::PLAYING:
223                 return "";
224         case Column::IN:
225                 return "In";
226         case Column::OUT:
227                 return "Out";
228         case Column::DURATION:
229                 return "Duration";
230         case Column::CAMERA:
231                 return "Camera";
232         case Column::DESCRIPTION:
233                 return "Description";
234         case Column::FADE_TIME:
235                 return "Fade time";
236         default:
237                 return "";
238         }
239 }
240
241 Qt::ItemFlags ClipList::flags(const QModelIndex &index) const
242 {
243         if (!index.isValid())
244                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
245         const int row = index.row(), column = index.column();
246         if (size_t(row) >= clips.size())
247                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
248
249         switch (Column(column)) {
250         case Column::CAMERA_1:
251         case Column::CAMERA_2:
252         case Column::CAMERA_3:
253         case Column::CAMERA_4:
254                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
255         default:
256                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
257         }
258 }
259
260 Qt::ItemFlags PlayList::flags(const QModelIndex &index) const
261 {
262         if (!index.isValid())
263                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
264         const int row = index.row(), column = index.column();
265         if (size_t(row) >= clips.size())
266                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
267
268         switch (Column(column)) {
269         case Column::DESCRIPTION:
270         case Column::CAMERA:
271         case Column::FADE_TIME:
272                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
273                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
274         default:
275                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
276         }
277 }
278
279 bool ClipList::setData(const QModelIndex &index, const QVariant &value, int role)
280 {
281         if (!index.isValid() || role != Qt::EditRole) {
282                 return false;
283         }
284
285         const int row = index.row(), column = index.column();
286         if (size_t(row) >= clips.size())
287                 return false;
288
289         switch (Column(column)) {
290         case Column::CAMERA_1:
291         case Column::CAMERA_2:
292         case Column::CAMERA_3:
293         case Column::CAMERA_4: {
294                 unsigned stream_idx = column - int(Column::CAMERA_1);
295                 clips[row].descriptions[stream_idx] = value.toString().toStdString();
296                 emit_data_changed(row);
297                 return true;
298         }
299         default:
300                 return false;
301         }
302 }
303
304 bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role)
305 {
306         if (!index.isValid() || role != Qt::EditRole) {
307                 return false;
308         }
309
310         const int row = index.row(), column = index.column();
311         if (size_t(row) >= clips.size())
312                 return false;
313
314         switch (Column(column)) {
315         case Column::DESCRIPTION:
316                 clips[row].descriptions[clips[row].stream_idx] = value.toString().toStdString();
317                 emit_data_changed(row);
318                 return true;
319         case Column::CAMERA: {
320                 bool ok;
321                 int camera_idx = value.toInt(&ok);
322                 if (!ok || camera_idx < 1 || camera_idx > NUM_CAMERAS) {
323                         return false;
324                 }
325                 clips[row].stream_idx = camera_idx - 1;
326                 emit_data_changed(row);
327                 return true;
328         }
329         case Column::FADE_TIME: {
330                 bool ok;
331                 double val = value.toDouble(&ok);
332                 if (!ok || !(val >= 0.0)) {
333                         return false;
334                 }
335                 clips[row].fade_time_seconds = val;
336                 emit_data_changed(row);
337                 return true;
338         }
339         default:
340                 return false;
341         }
342 }
343
344 void ClipList::add_clip(const Clip &clip)
345 {
346         beginInsertRows(QModelIndex(), clips.size(), clips.size());
347         clips.push_back(clip);
348         endInsertRows();
349         emit any_content_changed();
350 }
351
352 void PlayList::add_clip(const Clip &clip)
353 {
354         beginInsertRows(QModelIndex(), clips.size(), clips.size());
355         clips.push_back(clip);
356         endInsertRows();
357         emit any_content_changed();
358 }
359
360 void PlayList::duplicate_clips(size_t first, size_t last)
361 {
362         beginInsertRows(QModelIndex(), first, last);
363         clips.insert(clips.begin() + first, clips.begin() + first, clips.begin() + last + 1);
364         endInsertRows();
365         emit any_content_changed();
366 }
367
368 void PlayList::erase_clips(size_t first, size_t last)
369 {
370         beginRemoveRows(QModelIndex(), first, last);
371         clips.erase(clips.begin() + first, clips.begin() + last + 1);
372         endRemoveRows();
373         emit any_content_changed();
374 }
375
376 void PlayList::move_clips(size_t first, size_t last, int delta)
377 {
378         if (delta == -1) {
379                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
380                 rotate(clips.begin() + first - 1, clips.begin() + first, clips.begin() + last + 1);
381         } else {
382                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first + (last-first+1) + 1);
383                 first = clips.size() - first - 1;
384                 last = clips.size() - last - 1;
385                 rotate(clips.rbegin() + last - 1, clips.rbegin() + last, clips.rbegin() + first + 1);
386         }
387         endMoveRows();
388         emit any_content_changed();
389 }
390
391 void ClipList::emit_data_changed(size_t row)
392 {
393         emit dataChanged(index(row, 0), index(row, int(Column::NUM_COLUMNS)));
394         emit any_content_changed();
395 }
396
397 void PlayList::emit_data_changed(size_t row)
398 {
399         emit dataChanged(index(row, 0), index(row, int(Column::NUM_COLUMNS)));
400         emit any_content_changed();
401 }
402
403 void PlayList::set_currently_playing(int index, double progress)
404 {
405         int old_index = currently_playing_index;
406         int column = int(Column::PLAYING);
407         if (index != old_index) {
408                 currently_playing_index = index;
409                 play_progress = progress;
410                 if (old_index != -1) {
411                         emit dataChanged(this->index(old_index, column), this->index(old_index, column));
412                 }
413                 if (index != -1) {
414                         emit dataChanged(this->index(index, column), this->index(index, column));
415                 }
416         } else if (index != -1 && fabs(progress - play_progress) > 1e-3) {
417                 play_progress = progress;
418                 emit dataChanged(this->index(index, column), this->index(index, column));
419         }
420 }
421
422 namespace {
423
424 Clip deserialize_clip(const ClipProto &clip_proto)
425 {
426         Clip clip;
427         clip.pts_in = clip_proto.pts_in();
428         clip.pts_out = clip_proto.pts_out();
429         for (int camera_idx = 0; camera_idx < min(clip_proto.description_size(), NUM_CAMERAS); ++camera_idx) {
430                 clip.descriptions[camera_idx] = clip_proto.description(camera_idx);
431         }
432         clip.stream_idx = clip_proto.stream_idx();
433         clip.fade_time_seconds = clip_proto.fade_time_seconds();
434         return clip;
435 }
436
437 void serialize_clip(const Clip &clip, ClipProto *clip_proto)
438 {
439         clip_proto->set_pts_in(clip.pts_in);
440         clip_proto->set_pts_out(clip.pts_out);
441         for (int camera_idx = 0; camera_idx < NUM_CAMERAS; ++camera_idx) {
442                 *clip_proto->add_description() = clip.descriptions[camera_idx];
443         }
444         clip_proto->set_stream_idx(clip.stream_idx);
445         clip_proto->set_fade_time_seconds(clip.fade_time_seconds);
446 }
447
448 }  // namespace
449
450 ClipList::ClipList(const ClipListProto &serialized)
451 {
452         for (const ClipProto &clip_proto : serialized.clip()) {
453                 clips.push_back(deserialize_clip(clip_proto));
454         }
455 }
456
457 ClipListProto ClipList::serialize() const
458 {
459         ClipListProto ret;
460         for (const Clip &clip : clips) {
461                 serialize_clip(clip, ret.add_clip());
462         }
463         return ret;
464 }
465
466 PlayList::PlayList(const ClipListProto &serialized)
467 {
468         for (const ClipProto &clip_proto : serialized.clip()) {
469                 clips.push_back(deserialize_clip(clip_proto));
470         }
471 }
472
473 ClipListProto PlayList::serialize() const
474 {
475         ClipListProto ret;
476         for (const Clip &clip : clips) {
477                 serialize_clip(clip, ret.add_clip());
478         }
479         return ret;
480 }