]> git.sesse.net Git - nageru/blob - futatabi/clip_list.cpp
Log a warning when we kill a client that is not keeping up.
[nageru] / futatabi / clip_list.cpp
1 #include "clip_list.h"
2
3 #include "mainwindow.h"
4 #include "shared/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_NON_CAMERA_COLUMNS) + num_cameras;
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         default:
109                 if (is_camera_column(column)) {
110                         unsigned stream_idx = column - int(Column::CAMERA_1);
111                         return QString::fromStdString(clips[row].descriptions[stream_idx]);
112                 } else {
113                         return "";
114                 }
115         }
116 }
117
118 QVariant PlayList::data(const QModelIndex &parent, int role) const
119 {
120         if (!parent.isValid())
121                 return QVariant();
122         const int row = parent.row(), column = parent.column();
123         if (size_t(row) >= clips.size())
124                 return QVariant();
125
126         if (role == Qt::TextAlignmentRole) {
127                 switch (Column(column)) {
128                 case Column::PLAYING:
129                         return Qt::AlignCenter;
130                 case Column::IN:
131                 case Column::OUT:
132                 case Column::DURATION:
133                 case Column::FADE_TIME:
134                 case Column::SPEED:
135                         return Qt::AlignRight + Qt::AlignVCenter;
136                 case Column::CAMERA:
137                         return Qt::AlignCenter;
138                 default:
139                         return Qt::AlignLeft + Qt::AlignVCenter;
140                 }
141         }
142         if (role == Qt::BackgroundRole) {
143                 if (Column(column) == Column::PLAYING) {
144                         auto it = current_progress.find(clips[row].id);
145                         if (it != current_progress.end()) {
146                                 double play_progress = it->second;
147
148                                 // This only really works well for the first column, for whatever odd Qt reason.
149                                 QLinearGradient grad(QPointF(0, 0), QPointF(1, 0));
150                                 grad.setCoordinateMode(grad.QGradient::ObjectBoundingMode);
151                                 grad.setColorAt(0.0f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
152                                 grad.setColorAt(play_progress, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.2f));
153                                 if (play_progress + 0.01f <= 1.0f) {
154                                         grad.setColorAt(play_progress + 0.01f, QColor::fromRgbF(0.0f, 0.0f, 1.0f, 0.0f));
155                                 }
156                                 return QBrush(grad);
157                         } else {
158                                 return QVariant();
159                         }
160                 } else {
161                         return QVariant();
162                 }
163         }
164
165         if (role != Qt::DisplayRole && role != Qt::EditRole)
166                 return QVariant();
167
168         switch (Column(column)) {
169         case Column::PLAYING:
170                 return current_progress.count(clips[row].id) ? "→" : "";
171         case Column::IN:
172                 return QString::fromStdString(pts_to_string(clips[row].clip.pts_in));
173         case Column::OUT:
174                 if (clips[row].clip.pts_out >= 0) {
175                         return QString::fromStdString(pts_to_string(clips[row].clip.pts_out));
176                 } else {
177                         return QVariant();
178                 }
179         case Column::DURATION:
180                 if (clips[row].clip.pts_out >= 0) {
181                         return QString::fromStdString(duration_to_string(clips[row].clip.pts_out - clips[row].clip.pts_in));
182                 } else {
183                         return QVariant();
184                 }
185         case Column::CAMERA:
186                 return qlonglong(clips[row].clip.stream_idx + 1);
187         case Column::DESCRIPTION:
188                 return QString::fromStdString(clips[row].clip.descriptions[clips[row].clip.stream_idx]);
189         case Column::FADE_TIME: {
190                 stringstream ss;
191                 ss.imbue(locale("C"));
192                 ss.precision(3);
193                 ss << fixed << clips[row].clip.fade_time_seconds;
194                 return QString::fromStdString(ss.str());
195         }
196         case Column::SPEED: {
197                 stringstream ss;
198                 ss.imbue(locale("C"));
199                 ss.precision(3);
200                 ss << fixed << clips[row].clip.speed;
201                 return QString::fromStdString(ss.str());
202         }
203         default:
204                 return "";
205         }
206 }
207
208 QVariant ClipList::headerData(int section, Qt::Orientation orientation, int role) const
209 {
210         if (role != Qt::DisplayRole)
211                 return QVariant();
212         if (orientation != Qt::Horizontal)
213                 return QVariant();
214
215         switch (Column(section)) {
216         case Column::IN:
217                 return "In";
218         case Column::OUT:
219                 return "Out";
220         case Column::DURATION:
221                 return "Duration";
222         default:
223                 if (is_camera_column(section)) {
224                         return QString::fromStdString("Camera " + to_string(section - int(Column::CAMERA_1) + 1));
225                 } else {
226                         return "";
227                 }
228         }
229 }
230
231 QVariant PlayList::headerData(int section, Qt::Orientation orientation, int role) const
232 {
233         if (role != Qt::DisplayRole)
234                 return QVariant();
235         if (orientation != Qt::Horizontal)
236                 return QVariant();
237
238         switch (Column(section)) {
239         case Column::PLAYING:
240                 return "";
241         case Column::IN:
242                 return "In";
243         case Column::OUT:
244                 return "Out";
245         case Column::DURATION:
246                 return "Duration";
247         case Column::CAMERA:
248                 return "Camera";
249         case Column::DESCRIPTION:
250                 return "Description";
251         case Column::FADE_TIME:
252                 return "Fade time";
253         case Column::SPEED:
254                 return "Speed";
255         default:
256                 return "";
257         }
258 }
259
260 Qt::ItemFlags ClipList::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         if (is_camera_column(column)) {
269                 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
270         } else {
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         case Column::SPEED:
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         if (is_camera_column(column)) {
305                 unsigned stream_idx = column - int(Column::CAMERA_1);
306                 clips[row].descriptions[stream_idx] = value.toString().toStdString();
307                 emit_data_changed(row);
308                 return true;
309         } else {
310                 return false;
311         }
312 }
313
314 bool PlayList::setData(const QModelIndex &index, const QVariant &value, int role)
315 {
316         if (!index.isValid() || role != Qt::EditRole) {
317                 return false;
318         }
319
320         const int row = index.row(), column = index.column();
321         if (size_t(row) >= clips.size())
322                 return false;
323
324         switch (Column(column)) {
325         case Column::DESCRIPTION:
326                 clips[row].clip.descriptions[clips[row].clip.stream_idx] = value.toString().toStdString();
327                 emit_data_changed(row);
328                 return true;
329         case Column::CAMERA: {
330                 bool ok;
331                 int camera_idx = value.toInt(&ok);
332                 if (!ok || camera_idx < 1 || camera_idx > int(num_cameras)) {
333                         return false;
334                 }
335                 clips[row].clip.stream_idx = camera_idx - 1;
336                 emit_data_changed(row);
337                 return true;
338         }
339         case Column::FADE_TIME: {
340                 bool ok;
341                 double val = value.toDouble(&ok);
342                 if (!ok || !(val >= 0.0)) {
343                         return false;
344                 }
345                 clips[row].clip.fade_time_seconds = val;
346                 emit_data_changed(row);
347                 return true;
348         }
349         case Column::SPEED: {
350                 bool ok;
351                 double val = value.toDouble(&ok);
352                 if (!ok || !(val >= 0.001)) {
353                         return false;
354                 }
355                 clips[row].clip.speed = val;
356                 emit_data_changed(row);
357                 return true;
358         }
359         default:
360                 return false;
361         }
362 }
363
364 void ClipList::add_clip(const Clip &clip)
365 {
366         beginInsertRows(QModelIndex(), clips.size(), clips.size());
367         clips.push_back(clip);
368         endInsertRows();
369         emit any_content_changed();
370 }
371
372 void PlayList::add_clip(const Clip &clip)
373 {
374         beginInsertRows(QModelIndex(), clips.size(), clips.size());
375         clips.emplace_back(ClipWithID{ clip, clip_counter++ });
376         endInsertRows();
377         emit any_content_changed();
378 }
379
380 void PlayList::duplicate_clips(size_t first, size_t last)
381 {
382         beginInsertRows(QModelIndex(), last + 1, last + 1 + (last - first));
383
384         vector<ClipWithID> new_clips;
385         for (auto it = clips.begin() + first; it <= clips.begin() + last; ++it) {
386                 new_clips.emplace_back(ClipWithID{ it->clip, clip_counter++ });  // Give them new IDs.
387         }
388         clips.insert(clips.begin() + last + 1, new_clips.begin(), new_clips.end());  // Note: The new elements are inserted after the old ones.
389         endInsertRows();
390         emit any_content_changed();
391 }
392
393 void PlayList::erase_clips(size_t first, size_t last)
394 {
395         beginRemoveRows(QModelIndex(), first, last);
396         clips.erase(clips.begin() + first, clips.begin() + last + 1);
397         endRemoveRows();
398         emit any_content_changed();
399 }
400
401 void PlayList::move_clips(size_t first, size_t last, int delta)
402 {
403         if (delta == -1) {
404                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
405                 rotate(clips.begin() + first - 1, clips.begin() + first, clips.begin() + last + 1);
406         } else {
407                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first + (last - first + 1) + 1);
408                 first = clips.size() - first - 1;
409                 last = clips.size() - last - 1;
410                 rotate(clips.rbegin() + last - 1, clips.rbegin() + last, clips.rbegin() + first + 1);
411         }
412         endMoveRows();
413         emit any_content_changed();
414 }
415
416 void ClipList::emit_data_changed(size_t row)
417 {
418         emit dataChanged(index(row, 0), index(row, int(Column::NUM_NON_CAMERA_COLUMNS) + num_cameras));
419         emit any_content_changed();
420 }
421
422 void PlayList::emit_data_changed(size_t row)
423 {
424         emit dataChanged(index(row, 0), index(row, int(Column::NUM_COLUMNS)));
425         emit any_content_changed();
426 }
427
428 void ClipList::change_num_cameras(size_t num_cameras)
429 {
430         assert(num_cameras >= this->num_cameras);
431         if (num_cameras == this->num_cameras) {
432                 return;
433         }
434
435         beginInsertColumns(QModelIndex(), int(Column::NUM_NON_CAMERA_COLUMNS) + this->num_cameras, int(Column::NUM_NON_CAMERA_COLUMNS) + num_cameras - 1);
436         this->num_cameras = num_cameras;
437         endInsertColumns();
438         emit any_content_changed();
439 }
440
441 void PlayList::set_progress(const map<uint64_t, double> &progress)
442 {
443         const int column = int(Column::PLAYING);
444         map<uint64_t, double> old_progress = move(this->current_progress);
445         this->current_progress = progress;
446
447         for (size_t row = 0; row < clips.size(); ++row) {
448                 uint64_t id = clips[row].id;
449                 if (current_progress.count(id) || old_progress.count(id)) {
450                         emit dataChanged(this->index(row, column), this->index(row, column));
451                 }
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(), MAX_STREAMS); ++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         if (clip_proto.speed() < 0.001) {
468                 clip.speed = 0.5;  // Default.
469         } else {
470                 clip.speed = clip_proto.speed();
471         }
472         return clip;
473 }
474
475 void serialize_clip(const Clip &clip, ClipProto *clip_proto)
476 {
477         clip_proto->set_pts_in(clip.pts_in);
478         clip_proto->set_pts_out(clip.pts_out);
479         for (int camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
480                 *clip_proto->add_description() = clip.descriptions[camera_idx];
481         }
482         clip_proto->set_stream_idx(clip.stream_idx);
483         clip_proto->set_fade_time_seconds(clip.fade_time_seconds);
484         clip_proto->set_speed(clip.speed);
485 }
486
487 }  // namespace
488
489 ClipList::ClipList(const ClipListProto &serialized)
490 {
491         for (const ClipProto &clip_proto : serialized.clip()) {
492                 clips.push_back(deserialize_clip(clip_proto));
493         }
494 }
495
496 ClipListProto ClipList::serialize() const
497 {
498         ClipListProto ret;
499         for (const Clip &clip : clips) {
500                 serialize_clip(clip, ret.add_clip());
501         }
502         return ret;
503 }
504
505 PlayList::PlayList(const ClipListProto &serialized)
506 {
507         for (const ClipProto &clip_proto : serialized.clip()) {
508                 clips.emplace_back(ClipWithID{ deserialize_clip(clip_proto), clip_counter++ });
509         }
510 }
511
512 ClipListProto PlayList::serialize() const
513 {
514         ClipListProto ret;
515         for (const ClipWithID &clip : clips) {
516                 serialize_clip(clip.clip, ret.add_clip());
517         }
518         return ret;
519 }