]> git.sesse.net Git - nageru/blob - futatabi/clip_list.cpp
c586b1a93dcd719db43e79221a24324d1fd74533
[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(row);
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(row) ? "→" : "";
171         case Column::IN:
172                 return QString::fromStdString(pts_to_string(clips[row].pts_in));
173         case Column::OUT:
174                 if (clips[row].pts_out >= 0) {
175                         return QString::fromStdString(pts_to_string(clips[row].pts_out));
176                 } else {
177                         return QVariant();
178                 }
179         case Column::DURATION:
180                 if (clips[row].pts_out >= 0) {
181                         return QString::fromStdString(duration_to_string(clips[row].pts_out - clips[row].pts_in));
182                 } else {
183                         return QVariant();
184                 }
185         case Column::CAMERA:
186                 return qlonglong(clips[row].stream_idx + 1);
187         case Column::DESCRIPTION:
188                 return QString::fromStdString(clips[row].descriptions[clips[row].stream_idx]);
189         case Column::FADE_TIME: {
190                 stringstream ss;
191                 ss.imbue(locale("C"));
192                 ss.precision(3);
193                 ss << fixed << clips[row].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].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].descriptions[clips[row].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].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].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].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.push_back(clip);
376         endInsertRows();
377         emit any_content_changed();
378 }
379
380 void PlayList::duplicate_clips(size_t first, size_t last)
381 {
382         beginInsertRows(QModelIndex(), first, last);
383         clips.insert(clips.begin() + first, clips.begin() + first, clips.begin() + last + 1);
384         endInsertRows();
385         emit any_content_changed();
386 }
387
388 void PlayList::erase_clips(size_t first, size_t last)
389 {
390         beginRemoveRows(QModelIndex(), first, last);
391         clips.erase(clips.begin() + first, clips.begin() + last + 1);
392         endRemoveRows();
393         emit any_content_changed();
394 }
395
396 void PlayList::move_clips(size_t first, size_t last, int delta)
397 {
398         if (delta == -1) {
399                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
400                 rotate(clips.begin() + first - 1, clips.begin() + first, clips.begin() + last + 1);
401         } else {
402                 beginMoveRows(QModelIndex(), first, last, QModelIndex(), first + (last - first + 1) + 1);
403                 first = clips.size() - first - 1;
404                 last = clips.size() - last - 1;
405                 rotate(clips.rbegin() + last - 1, clips.rbegin() + last, clips.rbegin() + first + 1);
406         }
407         endMoveRows();
408         emit any_content_changed();
409 }
410
411 void ClipList::emit_data_changed(size_t row)
412 {
413         emit dataChanged(index(row, 0), index(row, int(Column::NUM_NON_CAMERA_COLUMNS) + num_cameras));
414         emit any_content_changed();
415 }
416
417 void PlayList::emit_data_changed(size_t row)
418 {
419         emit dataChanged(index(row, 0), index(row, int(Column::NUM_COLUMNS)));
420         emit any_content_changed();
421 }
422
423 void ClipList::change_num_cameras(size_t num_cameras)
424 {
425         assert(num_cameras >= this->num_cameras);
426         if (num_cameras == this->num_cameras) {
427                 return;
428         }
429
430         beginInsertColumns(QModelIndex(), int(Column::NUM_NON_CAMERA_COLUMNS) + this->num_cameras, int(Column::NUM_NON_CAMERA_COLUMNS) + num_cameras - 1);
431         this->num_cameras = num_cameras;
432         endInsertColumns();
433         emit any_content_changed();
434 }
435
436 void PlayList::set_currently_playing(int index, double progress)
437 {
438         int old_index = currently_playing_index;
439         int column = int(Column::PLAYING);
440         if (index != old_index) {
441                 currently_playing_index = index;
442                 play_progress = progress;
443                 if (old_index != -1) {
444                         emit dataChanged(this->index(old_index, column), this->index(old_index, column));
445                 }
446                 if (index != -1) {
447                         emit dataChanged(this->index(index, column), this->index(index, column));
448                 }
449         } else if (index != -1 && fabs(progress - play_progress) > 1e-3) {
450                 play_progress = progress;
451                 emit dataChanged(this->index(index, column), this->index(index, column));
452         }
453 }
454
455 void PlayList::set_progress(const map<size_t, double> &progress)
456 {
457         const int column = int(Column::PLAYING);
458         map<size_t, double> old_progress = move(this->current_progress);
459         this->current_progress = progress;
460
461         for (auto it : old_progress) {
462                 size_t index = it.first;
463                 if (current_progress.count(index) == 0) {
464                         emit dataChanged(this->index(index, column), this->index(index, column));
465                 }
466         }
467         for (auto it : current_progress) {
468                 size_t index = it.first;
469                 emit dataChanged(this->index(index, column), this->index(index, column));
470         }
471 }
472
473 namespace {
474
475 Clip deserialize_clip(const ClipProto &clip_proto)
476 {
477         Clip clip;
478         clip.pts_in = clip_proto.pts_in();
479         clip.pts_out = clip_proto.pts_out();
480         for (int camera_idx = 0; camera_idx < min(clip_proto.description_size(), MAX_STREAMS); ++camera_idx) {
481                 clip.descriptions[camera_idx] = clip_proto.description(camera_idx);
482         }
483         clip.stream_idx = clip_proto.stream_idx();
484         clip.fade_time_seconds = clip_proto.fade_time_seconds();
485         if (clip_proto.speed() < 0.001) {
486                 clip.speed = 0.5;  // Default.
487         } else {
488                 clip.speed = clip_proto.speed();
489         }
490         return clip;
491 }
492
493 void serialize_clip(const Clip &clip, ClipProto *clip_proto)
494 {
495         clip_proto->set_pts_in(clip.pts_in);
496         clip_proto->set_pts_out(clip.pts_out);
497         for (int camera_idx = 0; camera_idx < MAX_STREAMS; ++camera_idx) {
498                 *clip_proto->add_description() = clip.descriptions[camera_idx];
499         }
500         clip_proto->set_stream_idx(clip.stream_idx);
501         clip_proto->set_fade_time_seconds(clip.fade_time_seconds);
502         clip_proto->set_speed(clip.speed);
503 }
504
505 }  // namespace
506
507 ClipList::ClipList(const ClipListProto &serialized)
508 {
509         for (const ClipProto &clip_proto : serialized.clip()) {
510                 clips.push_back(deserialize_clip(clip_proto));
511         }
512 }
513
514 ClipListProto ClipList::serialize() const
515 {
516         ClipListProto ret;
517         for (const Clip &clip : clips) {
518                 serialize_clip(clip, ret.add_clip());
519         }
520         return ret;
521 }
522
523 PlayList::PlayList(const ClipListProto &serialized)
524 {
525         for (const ClipProto &clip_proto : serialized.clip()) {
526                 clips.push_back(deserialize_clip(clip_proto));
527         }
528 }
529
530 ClipListProto PlayList::serialize() const
531 {
532         ClipListProto ret;
533         for (const Clip &clip : clips) {
534                 serialize_clip(clip, ret.add_clip());
535         }
536         return ret;
537 }