]> git.sesse.net Git - kdenlive/blob - src/stopmotion/stopmotion.cpp
Preliminary support for blackmagic capture (only .raw format for now, cannot be read...
[kdenlive] / src / stopmotion / stopmotion.cpp
1 /***************************************************************************
2                           stopmotion.cpp  -  description
3                              -------------------
4     begin                : Feb 28 2008
5     copyright            : (C) 2010 by Jean-Baptiste Mardelle
6     email                : jb@kdenlive.org
7  ***************************************************************************/
8
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17
18 #include "stopmotion.h"
19 #include "../blackmagic/devices.h"
20 #include "../slideshowclip.h"
21 #include "kdenlivesettings.h"
22
23
24 #include <KDebug>
25 #include <KGlobalSettings>
26 #include <KFileDialog>
27 #include <KStandardDirs>
28 #include <KMessageBox>
29 #include <kdeversion.h>
30 #include <KNotification>
31
32 #include <QtConcurrentRun>
33 #include <QInputDialog>
34 #include <QComboBox>
35 #include <QVBoxLayout>
36 #include <QTimer>
37 #include <QPainter>
38 #include <QAction>
39 #include <QWheelEvent>
40
41 MyLabel::MyLabel(QWidget *parent) :
42         QLabel(parent)
43 {
44 }
45
46 void MyLabel::setImage(QImage img)
47 {
48     m_img = img;
49 }
50
51 //virtual
52 void MyLabel::wheelEvent(QWheelEvent * event)
53 {
54     if (event->delta() > 0) emit seek(true);
55     else emit seek(false);
56 }
57
58 //virtual
59 void MyLabel::paintEvent( QPaintEvent * event)
60 {
61     Q_UNUSED(event);
62
63     QRect r(0, 0, width(), height());
64     QPainter p(this);
65     p.fillRect(r, QColor(KdenliveSettings::window_background()));
66     double aspect_ratio = (double) m_img.width() / m_img.height();
67     int pictureHeight = height();
68     int pictureWidth = width();
69     int calculatedWidth = aspect_ratio * height();
70     if (calculatedWidth > width()) pictureHeight = width() / aspect_ratio;
71     else {
72         int calculatedHeight = width() / aspect_ratio;
73         if (calculatedHeight > height()) pictureWidth = height() * aspect_ratio;
74     }
75     p.drawImage(QRect((width() - pictureWidth) / 2, (height() - pictureHeight) / 2, pictureWidth, pictureHeight), m_img, QRect(0, 0, m_img.width(), m_img.height()));
76     p.end();
77 }
78
79
80 StopmotionWidget::StopmotionWidget(KUrl projectFolder, QWidget *parent) :
81         QDialog(parent)
82         , Ui::Stopmotion_UI()
83         , m_projectFolder(projectFolder)
84         , m_sequenceFrame(0)
85         , m_animatedIndex(-1)
86 {
87     setupUi(this);
88     setWindowTitle(i18n("Stop Motion Capture"));
89     setFont(KGlobalSettings::toolBarFont());
90
91     live_button->setIcon(KIcon("camera-photo"));
92     frameoverlay_button->setIcon(KIcon("edit-paste"));
93     m_captureAction = new QAction(KIcon("media-record"), i18n("Capture frame"), this);
94     m_captureAction->setShortcut(QKeySequence(Qt::Key_Space));
95     connect(m_captureAction, SIGNAL(triggered()), this, SLOT(slotCaptureFrame()));
96     capture_button->setDefaultAction(m_captureAction);
97     
98     preview_button->setIcon(KIcon("media-playback-start"));
99     removelast_button->setIcon(KIcon("edit-delete"));
100     frameoverlay_button->setEnabled(false);
101     removelast_button->setEnabled(false);
102     capture_button->setEnabled(false);
103
104     connect(sequence_name, SIGNAL(textChanged(const QString &)), this, SLOT(sequenceNameChanged(const QString &)));
105     BMInterface::getBlackMagicDeviceList(capture_device, NULL);
106     QVBoxLayout *lay = new QVBoxLayout;
107     m_bmCapture = new CaptureHandler(lay);
108     connect(m_bmCapture, SIGNAL(gotMessage(const QString &)), this, SLOT(slotGotHDMIMessage(const QString &)));
109     m_frame_preview = new MyLabel(this);
110     connect(m_frame_preview, SIGNAL(seek(bool)), this, SLOT(slotSeekFrame(bool)));
111     lay->addWidget(m_frame_preview);
112     m_frame_preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
113     video_preview->setLayout(lay);
114     live_button->setChecked(false);
115     frameoverlay_button->setChecked(false);
116     button_addsequence->setEnabled(false);
117     connect(live_button, SIGNAL(clicked(bool)), this, SLOT(slotLive(bool)));
118     connect(frameoverlay_button, SIGNAL(clicked(bool)), this, SLOT(slotShowOverlay(bool)));
119     connect(frame_number, SIGNAL(valueChanged(int)), this, SLOT(slotShowFrame(int)));
120     connect(button_addsequence, SIGNAL(clicked(bool)), this, SLOT(slotAddSequence()));
121     connect(preview_button, SIGNAL(clicked(bool)), this, SLOT(slotPlayPreview()));
122     connect(frame_list, SIGNAL(currentRowChanged(int)), this, SLOT(slotShowSelectedFrame()));
123     connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage,int)));
124     
125     parseExistingSequences();
126 }
127
128 StopmotionWidget::~StopmotionWidget()
129 {
130     m_bmCapture->stopPreview();
131 }
132
133 void StopmotionWidget::slotGotHDMIMessage(const QString &message)
134 {
135     log_box->insertItem(0, message);
136 }
137
138 void StopmotionWidget::parseExistingSequences()
139 {
140     sequence_name->clear();
141     sequence_name->addItem(QString());
142     QDir dir(m_projectFolder.path());
143     QStringList filters;
144     filters << "*_0000.png";
145     //dir.setNameFilters(filters);
146     QStringList sequences = dir.entryList(filters, QDir::Files, QDir::Name);
147     //kDebug()<<"PF: "<<<<", sm: "<<sequences;
148     foreach(QString sequencename, sequences) {
149         sequence_name->addItem(sequencename.section("_", 0, -2));
150     }
151 }
152
153 void StopmotionWidget::slotLive(bool isOn)
154 {
155     if (isOn) {
156         m_frame_preview->setImage(QImage());
157         m_frame_preview->setHidden(true);
158         m_bmCapture->startPreview(KdenliveSettings::hdmicapturedevice(), KdenliveSettings::hdmicapturemode());
159         capture_button->setEnabled(true);
160     }
161     else {
162         m_bmCapture->stopPreview();
163         capture_button->setEnabled(false);
164     }
165 }
166
167 void StopmotionWidget::slotShowOverlay(bool isOn)
168 {
169     if (isOn) {
170         if (live_button->isChecked() && m_sequenceFrame > 0) {
171             slotUpdateOverlay();
172         }
173     }
174     else {
175         m_bmCapture->hideOverlay();
176     }
177 }
178
179 void StopmotionWidget::slotUpdateOverlay()
180 {
181     QString path = getPathForFrame(m_sequenceFrame - 1);
182     if (!QFile::exists(path)) return;
183     QImage img(path);
184     if (img.isNull()) {
185         QTimer::singleShot(1000, this, SLOT(slotUpdateOverlay()));
186         return;
187     }
188     m_bmCapture->showOverlay(img);
189 }
190
191 void StopmotionWidget::sequenceNameChanged(const QString &name)
192 {
193     // Get rid of frames from previous sequence
194     disconnect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage,int)));
195     m_filesList.clear();
196     m_future.waitForFinished();
197     frame_list->clear();
198     if (name.isEmpty()) {
199         button_addsequence->setEnabled(false);
200         frame_number->blockSignals(true);
201         frame_number->setValue(m_sequenceFrame);
202         frame_number->blockSignals(false);
203         frameoverlay_button->setEnabled(false);
204         removelast_button->setEnabled(false);
205     }
206     else {
207         // Check if we are editing an existing sequence
208         int count = 0;
209         QString pattern = SlideshowClip::selectedPath(getPathForFrame(0, sequence_name->currentText()), false, QString(), &count);
210         m_sequenceFrame = count;
211         frame_number->blockSignals(true);
212         frame_number->setValue(0);
213         frame_number->blockSignals(false);
214         m_currentIndex = 0;
215         if (count > 0) {
216             m_sequenceName = sequence_name->currentText();
217             //TODO: Do the thumbnail stuff in a thread
218             for (int i = 0; i < count; i++) {
219                 //slotUpdateFrameList(i);
220                 m_filesList.append(getPathForFrame(i));
221             }
222             connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage,int)));
223             m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
224             button_addsequence->setEnabled(true);
225             frameoverlay_button->setEnabled(true);
226         }
227         else {
228             button_addsequence->setEnabled(false);
229             frameoverlay_button->setEnabled(false);
230         }
231         frame_number->setRange(0, m_sequenceFrame);
232         capture_button->setEnabled(true);
233     }
234 }
235
236 void StopmotionWidget::slotCaptureFrame()
237 {
238     if (sequence_name->currentText().isEmpty()) {
239         QString seqName = QInputDialog::getText(this, i18n("Create New Sequence"), i18n("Enter sequence name"));
240         if (seqName.isEmpty()) return;
241         sequence_name->blockSignals(true);
242         sequence_name->setItemText(sequence_name->currentIndex(), seqName);
243         sequence_name->blockSignals(false);
244     }
245     if (m_sequenceName != sequence_name->currentText()) {
246         m_sequenceName = sequence_name->currentText();
247         m_sequenceFrame = 0;
248     }
249     capture_button->setEnabled(false);
250     m_bmCapture->captureFrame(getPathForFrame(m_sequenceFrame));
251     KNotification::event("FrameCaptured");
252     frameoverlay_button->setEnabled(true);
253     m_sequenceFrame++;
254     frame_number->setRange(0, m_sequenceFrame);
255     frame_number->blockSignals(true);
256     frame_number->setValue(m_sequenceFrame);
257     frame_number->blockSignals(false);
258     button_addsequence->setEnabled(true);
259     //if (frameoverlay_button->isChecked()) QTimer::singleShot(1000, this, SLOT(slotUpdateOverlay()));
260     QTimer::singleShot(1000, this, SLOT(slotUpdateFrameList()));
261 }
262
263 void StopmotionWidget::slotPrepareThumbs()
264 {
265     if (m_filesList.isEmpty()) return;
266     QString path = m_filesList.takeFirst();
267     emit doCreateThumbs(QImage(path), m_currentIndex++);
268     
269 }
270
271 void StopmotionWidget::slotCreateThumbs(QImage img, int ix)
272 {
273     if (img.isNull()) {
274         m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
275         return;
276     }
277     int height = 90;
278     int width = height * img.width() / img.height();
279     frame_list->setIconSize(QSize(width, height));
280     QPixmap pix = QPixmap::fromImage(img).scaled(width, height);
281     QString nb = QString::number(ix);
282     QPainter p(&pix);
283     QFontInfo finfo(font());
284     p.fillRect(0, 0, finfo.pixelSize() * nb.count() + 6, finfo.pixelSize() + 6, QColor(0, 0, 0, 150));
285     p.setPen(Qt::white);
286     p.drawText(QPoint(3, finfo.pixelSize() + 3), nb);
287     p.end();
288     QIcon icon(pix);
289     QListWidgetItem *item = new QListWidgetItem(icon, QString(), frame_list);
290     item->setData(Qt::UserRole, ix);
291     m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs);
292 }
293
294 void StopmotionWidget::slotUpdateFrameList(int ix)
295 {
296     kDebug()<< "// GET FRAME: "<<ix;
297     if (ix == -1) ix = m_sequenceFrame - 1;
298     QString path = getPathForFrame(ix);
299     if (!QFile::exists(path)) {
300         capture_button->setEnabled(true);
301         return;
302     }
303     QImage img(path);
304     if (img.isNull()) {
305         if (ix == m_sequenceFrame - 1) QTimer::singleShot(1000, this, SLOT(slotUpdateFrameList()));
306         return;
307     }
308     int height = 90;
309     int width = height * img.width() / img.height();
310     frame_list->setIconSize(QSize(width, height));
311     QPixmap pix = QPixmap::fromImage(img).scaled(width, height);
312     QString nb = QString::number(ix);
313     QPainter p(&pix);
314     QFontInfo finfo(font());
315     p.fillRect(0, 0, finfo.pixelSize() * nb.count() + 6, finfo.pixelSize() + 6, QColor(0, 0, 0, 150));
316     p.setPen(Qt::white);
317     p.drawText(QPoint(3, finfo.pixelSize() + 3), nb);
318     p.end();
319     QIcon icon(pix);
320     QListWidgetItem *item = new QListWidgetItem(icon, QString(), frame_list);
321     item->setData(Qt::UserRole, ix);
322     capture_button->setEnabled(true);
323 }
324
325 QString StopmotionWidget::getPathForFrame(int ix, QString seqName)
326 {
327     if (seqName.isEmpty()) seqName = m_sequenceName;
328     return m_projectFolder.path(KUrl::AddTrailingSlash) + seqName + "_" + QString::number(ix).rightJustified(4, '0', false) + ".png";
329 }
330
331 void StopmotionWidget::slotShowFrame(int ix)
332 {
333     if (m_sequenceFrame == 0) {
334         //there are no images in sequence
335         return;
336     }
337     frameoverlay_button->blockSignals(true);
338     frameoverlay_button->setChecked(false);
339     frameoverlay_button->blockSignals(false);
340     if (ix < m_sequenceFrame) {
341         // Show previous frame
342         slotLive(false);
343         live_button->setChecked(false);
344         QImage img(getPathForFrame(ix));
345         capture_button->setEnabled(false);
346         if (!img.isNull()) {
347             //m_bmCapture->showOverlay(img, false);
348             m_bmCapture->hidePreview(true);
349             m_frame_preview->setImage(img);
350             m_frame_preview->setHidden(false);
351             m_frame_preview->update();
352             selectFrame(ix);
353         }
354     }
355     /*else {
356         slotLive(true);
357         m_frame_preview->setImage(QImage());
358         m_frame_preview->setHidden(true);
359         m_bmCapture->hideOverlay();
360         m_bmCapture->hidePreview(false);
361         capture_button->setEnabled(true);
362     }*/   
363 }
364
365 void StopmotionWidget::slotShowSelectedFrame()
366 {
367     QListWidgetItem *item = frame_list->currentItem();
368     if (item) {
369         int ix = item->data(Qt::UserRole).toInt();
370         //frame_number->blockSignals(true);
371         frame_number->setValue(ix);
372         //frame_number->blockSignals(false);
373         //slotShowFrame(ix);
374     }
375 }
376
377 void StopmotionWidget::slotAddSequence()
378 {
379     emit addOrUpdateSequence(getPathForFrame(0));
380 }
381
382 void StopmotionWidget::slotPlayPreview()
383 {
384     if (m_animatedIndex != -1) {
385         // stop animation
386         m_animatedIndex = -1;
387         return;
388     }
389     QListWidgetItem *item = frame_list->currentItem();
390     if (item) {
391         m_animatedIndex = item->data(Qt::UserRole).toInt();
392     }
393     QTimer::singleShot(200, this, SLOT(slotAnimate()));
394 }
395
396 void StopmotionWidget::slotAnimate()
397 {
398     slotShowFrame(m_animatedIndex);
399     m_animatedIndex++;
400     if (m_animatedIndex < m_sequenceFrame) QTimer::singleShot(200, this, SLOT(slotAnimate()));
401     else m_animatedIndex = -1;
402 }
403
404 void StopmotionWidget::selectFrame(int ix)
405 {
406     frame_list->blockSignals(true);
407     QListWidgetItem *item = frame_list->item(ix);
408     int current = item->data(Qt::UserRole).toInt();
409     if (current == ix) {
410         frame_list->setCurrentItem(item);
411     }
412     else if (current < ix) {
413         for (int i = ix; i < frame_list->count(); i++) {
414             item = frame_list->item(i);
415             current = item->data(Qt::UserRole).toInt();
416             if (current == ix) {
417                 frame_list->setCurrentItem(item);
418                 break;
419             }
420         }
421     }
422     else {
423         for (int i = ix; i >= 0; i--) {
424             item = frame_list->item(i);
425             current = item->data(Qt::UserRole).toInt();
426             if (current == ix) {
427                 frame_list->setCurrentItem(item);
428                 break;
429             }
430         }
431     }
432     frame_list->blockSignals(false);
433 }
434
435 void StopmotionWidget::slotSeekFrame(bool forward)
436 {
437     int ix = frame_list->currentRow();
438     if (forward) {
439         if (ix < frame_list->count() - 1) frame_list->setCurrentRow(ix + 1);
440     }
441     else if (ix > 0) frame_list->setCurrentRow(ix - 1);
442 }
443
444