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