From 7575235abdb732d1d234cdaa9887d41be39c2940 Mon Sep 17 00:00:00 2001 From: Warwick Allison Date: Wed, 29 Jul 2009 13:02:18 +1000 Subject: Recording options via menu. --- tools/qmlviewer/main.cpp | 15 +- tools/qmlviewer/qmlviewer.cpp | 217 ++++++++++++++++++++++++--- tools/qmlviewer/qmlviewer.h | 17 ++- tools/qmlviewer/qmlviewer.pro | 5 +- tools/qmlviewer/recopts.ui | 338 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 562 insertions(+), 30 deletions(-) create mode 100644 tools/qmlviewer/recopts.ui diff --git a/tools/qmlviewer/main.cpp b/tools/qmlviewer/main.cpp index 8784ae0..e5f5e05 100644 --- a/tools/qmlviewer/main.cpp +++ b/tools/qmlviewer/main.cpp @@ -32,7 +32,7 @@ void usage() qWarning(" - png file for raw frames"); qWarning(" - 'ffmpeg' for other formats"); qWarning(" -recorddither ordered|threshold|floyd .... set GIF dither recording mode"); - qWarning(" -recordperiod ............. set time between recording frames"); + qWarning(" -recordrate ........................ set recording frame rate"); qWarning(" -record arg .............................. add a recording process argument"); qWarning(" -autorecord [from-] ...... set recording to start and stop"); qWarning(" -devicekeys .............................. use numeric keys (see F1)"); @@ -63,7 +63,7 @@ int main(int argc, char ** argv) bool frameless = false; QString fileName; - int period = 0; + double fps = 0; int autorecord_from = 0; int autorecord_to = 0; QString dither = "none"; @@ -83,8 +83,8 @@ int main(int argc, char ** argv) skin = QString(argv[++i]); } else if (arg == "-netcache") { cache = QString(argv[++i]).toInt(); - } else if (arg == "-recordperiod") { - period = QString(argv[++i]).toInt(); + } else if (arg == "-recordrate") { + fps = QString(argv[++i]).toDouble(); } else if (arg == "-recordfile") { recordfile = QString(argv[++i]); } else if (arg == "-record") { @@ -127,8 +127,8 @@ int main(int argc, char ** argv) viewer.addLibraryPath(lib); viewer.setNetworkCacheSize(cache); viewer.setRecordFile(recordfile); - if (period>0) - viewer.setRecordPeriod(period); + if (fps>0) + viewer.setRecordRate(fps); if (autorecord_to) viewer.setAutoRecord(autorecord_from,autorecord_to); if (!skin.isEmpty() && QDir(skin).exists()) @@ -136,7 +136,8 @@ int main(int argc, char ** argv) if (devkeys) viewer.setDeviceKeys(true); viewer.setRecordDither(dither); - viewer.setRecordArgs(recordargs); + if (recordargs.count()) + viewer.setRecordArgs(recordargs); if (!fileName.isEmpty()) { viewer.openQml(fileName); viewer.show(); diff --git a/tools/qmlviewer/qmlviewer.cpp b/tools/qmlviewer/qmlviewer.cpp index 95cd24b..643760b 100644 --- a/tools/qmlviewer/qmlviewer.cpp +++ b/tools/qmlviewer/qmlviewer.cpp @@ -12,6 +12,7 @@ ****************************************************************************/ #include +#include "ui_recopts.h" #include "qmlviewer.h" #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +129,79 @@ void PreviewDeviceSkin::slotPopupMenu() menu->exec(QCursor::pos()); } +static struct { const char *name, *args; } ffmpegprofiles[] = { + {"Maximum Quality", "-sameq"}, + {"High Quality", "-qcomp 0.75"}, + {"Medium Quality", "-qcomp 0.5"}, + {"Low Quality", "-qcomp 0.2"}, + {"Custom ffmpeg arguments", ""}, + {0,0} +}; + +class RecordingDialog : public QDialog, public Ui::RecordingOptions { + Q_OBJECT + +public: + RecordingDialog(QWidget *parent) : QDialog(parent) + { + setupUi(this); + hz->setValidator(new QDoubleValidator(hz)); + for (int i=0; ffmpegprofiles[i].name; ++i) { + profile->addItem(ffmpegprofiles[i].name); + } + } + + void setArguments(QString a) + { + int i; + for (i=0; ffmpegprofiles[i].args[0]; ++i) { + if (ffmpegprofiles[i].args == a) { + profile->setCurrentIndex(i); + args->setText(QLatin1String(ffmpegprofiles[i].args)); + return; + } + } + customargs = a; + args->setText(a); + profile->setCurrentIndex(i); + } + + QString arguments() const + { + int i = profile->currentIndex(); + return ffmpegprofiles[i].args[0] ? QLatin1String(ffmpegprofiles[i].args) : customargs; + } + +private slots: + void pickProfile(int i) + { + if (ffmpegprofiles[i].args[0]) { + args->setText(QLatin1String(ffmpegprofiles[i].args)); + } else { + args->setText(customargs); + } + } + + void storeCustomArgs(QString s) + { + setArguments(s); + } + +private: + QString customargs; +}; + +QString QmlViewer::getVideoFileName() +{ + QString title = convertAvailable || ffmpegAvailable ? tr("Save Video File") : tr("Save PNG Frames"); + QStringList types; + if (ffmpegAvailable) types += tr("Common Video files")+QLatin1String(" (*.avi *.mpeg *.mov)"); + if (convertAvailable) types += tr("GIF Animation")+QLatin1String(" (*.gif)"); + types += tr("Individual PNG frames")+QLatin1String(" (*.png)"); + if (ffmpegAvailable) types += tr("All ffmpeg formats (*.*)"); + return QFileDialog::getSaveFileName(this, title, "", types.join(";; ")); +} + QmlViewer::QmlViewer(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), frame_stream(0), scaleSkin(true), mb(0) @@ -137,7 +212,28 @@ QmlViewer::QmlViewer(QWidget *parent, Qt::WindowFlags flags) palette = 0; disabledPalette = 0; record_autotime = 0; - record_period = 20; + record_rate = 50; + record_args += QLatin1String("-sameq"); + + recdlg = new RecordingDialog(this); + connect(recdlg->pickfile, SIGNAL(clicked()), this, SLOT(pickRecordingFile())); + senseFfmpeg(); + senseImageMagick(); + if (!ffmpegAvailable) + recdlg->ffmpegOptions->hide(); + if (!ffmpegAvailable && !convertAvailable) + recdlg->rateOptions->hide(); + QString warn; + if (!ffmpegAvailable) { + if (!convertAvailable) + warn = tr("ffmpeg and ImageMagick not available - no video output"); + else + warn = tr("ffmpeg not available - GIF and PNG outputs only"); + recdlg->warning->setText(warn); + } else { + recdlg->warning->hide(); + } + if (!(flags & Qt::FramelessWindowHint)) createMenu(menuBar(),0); @@ -205,6 +301,10 @@ void QmlViewer::createMenu(QMenuBar *menu, QMenu *flatmenu) connect(recordAction, SIGNAL(triggered()), this, SLOT(toggleRecordingWithSelection())); recordMenu->addAction(recordAction); + QAction *recordOptions = new QAction(tr("Recording &Options..."), parent); + connect(recordOptions, SIGNAL(triggered()), this, SLOT(chooseRecordingOptions())); + recordMenu->addAction(recordOptions); + if (flatmenu) flatmenu->addSeparator(); QMenu *skinMenu = flatmenu ? flatmenu->addMenu(tr("&Skin")) : menu->addMenu(tr("&Skin")); @@ -310,15 +410,57 @@ void QmlViewer::takeSnapShot() ++snapshotcount; } +void QmlViewer::pickRecordingFile() +{ + QString fileName = getVideoFileName(); + if (!fileName.isEmpty()) + recdlg->file->setText(fileName); +} + +void QmlViewer::chooseRecordingOptions() +{ + recdlg->file->setText(record_file); + if (record_rate == 24) + recdlg->hz24->setChecked(true); + else if (record_rate == 25) + recdlg->hz25->setChecked(true); + else if (record_rate == 50) + recdlg->hz50->setChecked(true); + else if (record_rate == 60) + recdlg->hz60->setChecked(true); + else { + recdlg->hzCustom->setChecked(true); + recdlg->hz->setText(QString::number(record_rate)); + } + recdlg->setArguments(record_args.join(" ")); + if (recdlg->exec()) { + record_file = recdlg->file->text(); + if (recdlg->hz24->isChecked()) + record_rate = 24; + else if (recdlg->hz25->isChecked()) + record_rate = 25; + else if (recdlg->hz50->isChecked()) + record_rate = 50; + else if (recdlg->hz60->isChecked()) + record_rate = 60; + else { + record_rate = recdlg->hz->text().toDouble(); + } + record_args = recdlg->arguments().split(" ",QString::SkipEmptyParts); + } +} + void QmlViewer::toggleRecordingWithSelection() { if (!recordTimer.isRunning()) { - QString fileName = QFileDialog::getSaveFileName(this, tr("Save Video File"), "", tr("Common Video files (*.avi *.mpeg *.mov);; GIF Animation (*.gif);; Individual PNG frames (*.png);; All ffmpeg formats (*.*)")); - if (fileName.isEmpty()) - return; - if (!fileName.contains(QRegExp(".[^\\/]*$"))) - fileName += ".avi"; - setRecordFile(fileName); + if (record_file.isEmpty()) { + QString fileName = getVideoFileName(); + if (fileName.isEmpty()) + return; + if (!fileName.contains(QRegExp(".[^\\/]*$"))) + fileName += ".avi"; + setRecordFile(fileName); + } } toggleRecording(); } @@ -503,9 +645,9 @@ void QmlViewer::setRecordFile(const QString& f) record_file = f; } -void QmlViewer::setRecordPeriod(int ms) +void QmlViewer::setRecordRate(int fps) { - record_period = ms; + record_rate = fps; } void QmlViewer::sceneResized(QSize size) @@ -545,32 +687,63 @@ void QmlViewer::keyPressEvent(QKeyEvent *event) QWidget::keyPressEvent(event); } +void QmlViewer::senseImageMagick() +{ + QProcess proc; + proc.start("convert", QStringList() << "-h"); + proc.waitForFinished(2000); + QString help = proc.readAllStandardOutput(); + convertAvailable = help.contains("ImageMagick"); +} + +void QmlViewer::senseFfmpeg() +{ + QProcess proc; + proc.start("ffmpeg", QStringList() << "-h"); + proc.waitForFinished(2000); + QString ffmpegHelp = proc.readAllStandardOutput(); + ffmpegAvailable = ffmpegHelp.contains("-s "); + ffmpegHelp = tr("Video recording use ffmpeg:")+"\n\n"+ffmpegHelp; + + QDialog *d = new QDialog(recdlg); + QVBoxLayout *l = new QVBoxLayout(d); + QTextBrowser *b = new QTextBrowser(d); + QFont f = b->font(); + f.setFamily("courier"); + b->setFont(f); + b->setText(ffmpegHelp); + l->addWidget(b); + d->setLayout(l); + ffmpegHelpWindow = d; + connect(recdlg->ffmpegHelp,SIGNAL(clicked()), ffmpegHelpWindow, SLOT(show())); +} + void QmlViewer::setRecording(bool on) { if (on == recordTimer.isRunning()) return; - QUnifiedTimer::instance()->setTimingInterval(on ? record_period:16); + int period = int(1000/record_rate+0.5); + QUnifiedTimer::instance()->setTimingInterval(on ? period:16); QUnifiedTimer::instance()->setConsistentTiming(on); if (on) { canvas->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); - recordTimer.setInterval(record_period); + recordTimer.setInterval(period); recordTimer.setRunning(true); - QString fmt = record_file.right(4).toLower(); - if (fmt != ".png" && fmt != ".gif") { + frame_fmt = record_file.right(4).toLower(); + frame = QImage(canvas->width(),canvas->height(),QImage::Format_RGB32); + if (frame_fmt != ".png" && (!convertAvailable || frame_fmt != ".gif")) { // Stream video to ffmpeg QProcess *proc = new QProcess(this); connect(proc, SIGNAL(finished(int)), this, SLOT(ffmpegFinished(int))); frame_stream = proc; - frame = QImage(canvas->width(),canvas->height(),QImage::Format_RGB32); QStringList args; - args << "-sameq"; // ie. high args << "-y"; - args << "-r" << QString::number(1000/record_period); + args << "-r" << QString::number(record_rate); args << "-f" << "rawvideo"; - args << "-pix_fmt" << "rgb32"; + args << "-pix_fmt" << (frame_fmt == ".gif" ? "rgb24" : "rgb32"); args << "-s" << QString("%1x%2").arg(canvas->width()).arg(canvas->height()); args << "-i" << "-"; args += record_args; @@ -635,7 +808,7 @@ void QmlViewer::setRecording(bool on) // ImageMagick and gifsicle for GIF encoding progress.setLabelText(tr("Converting frames to GIF file...")); QStringList args; - args << "-delay" << QString::number(record_period/10); + args << "-delay" << QString::number(period/10); args << inputs; args << record_file; qDebug() << "Converting..." << record_file << "(this may take a while)"; @@ -685,7 +858,13 @@ void QmlViewer::recordFrame() { canvas->QWidget::render(&frame); if (frame_stream) { - frame_stream->write((char*)frame.bits(),frame.numBytes()); + if (frame_fmt == ".gif") { + // ffmpeg can't do 32bpp with gif + QImage rgb24 = frame.convertToFormat(QImage::Format_RGB888); + frame_stream->write((char*)rgb24.bits(),rgb24.numBytes()); + } else { + frame_stream->write((char*)frame.bits(),frame.numBytes()); + } } else { frames.append(new QImage(frame)); } diff --git a/tools/qmlviewer/qmlviewer.h b/tools/qmlviewer/qmlviewer.h index e7177eb..15acf72 100644 --- a/tools/qmlviewer/qmlviewer.h +++ b/tools/qmlviewer/qmlviewer.h @@ -26,6 +26,7 @@ class PreviewDeviceSkin; class QFxTestEngine; class QmlPalette; class QProcess; +class RecordingDialog; class QmlViewer : public QWidget { @@ -34,10 +35,9 @@ public: QmlViewer(QWidget *parent=0, Qt::WindowFlags flags=0); void setRecordDither(const QString& s) { record_dither = s; } - void setRecordPeriod(int ms); + void setRecordRate(int fps); void setRecordFile(const QString&); void setRecordArgs(const QStringList&); - int recordPeriod() const { return record_period; } void setRecording(bool on); bool isRecording() const { return recordTimer.isRunning(); } void setAutoRecord(int from, int to); @@ -69,10 +69,13 @@ private slots: void autoStartRecording(); void autoStopRecording(); void recordFrame(); + void chooseRecordingOptions(); + void pickRecordingFile(); private: void setupProxy(); void setupPalettes(); + QString getVideoFileName(); QString currentFileName; PreviewDeviceSkin *skin; @@ -81,6 +84,7 @@ private: QmlPalette *palette; QmlPalette *disabledPalette; QmlTimer recordTimer; + QString frame_fmt; QImage frame; QList frames; QProcess* frame_stream; @@ -89,13 +93,20 @@ private: QString record_dither; QString record_file; QStringList record_args; - int record_period; + int record_rate; int record_autotime; bool devicemode; QAction *recordAction; QString currentSkin; bool scaleSkin; mutable QMenuBar *mb; + RecordingDialog *recdlg; + + void senseImageMagick(); + void senseFfmpeg(); + QWidget *ffmpegHelpWindow; + bool ffmpegAvailable; + bool convertAvailable; }; QT_END_NAMESPACE diff --git a/tools/qmlviewer/qmlviewer.pro b/tools/qmlviewer/qmlviewer.pro index 08d2d2b..a8ccd91 100644 --- a/tools/qmlviewer/qmlviewer.pro +++ b/tools/qmlviewer/qmlviewer.pro @@ -1,12 +1,15 @@ +TEMPLATE = app +CONFIG += qt uic DESTDIR = ../../bin QT += declarative script network sql # Input HEADERS += qmlviewer.h SOURCES += main.cpp qmlviewer.cpp +FORMS = recopts.ui + include($$QT_SOURCE_TREE/tools/shared/deviceskin/deviceskin.pri) target.path=$$[QT_INSTALL_BINS] INSTALLS += target -CONFIG += console diff --git a/tools/qmlviewer/recopts.ui b/tools/qmlviewer/recopts.ui new file mode 100644 index 0000000..041a4c4 --- /dev/null +++ b/tools/qmlviewer/recopts.ui @@ -0,0 +1,338 @@ + + + RecordingOptions + + + + 0 + 0 + 290 + 297 + + + + Recording options + + + + + + + 0 + 0 + + + + File: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + ... + + + + + + + + 0 + + + + + Profile: + + + + + + + + + + + + + Help + + + + + + + + + + + 0 + + + + + + 0 + 0 + + + + Rate: + + + + + + + 24Hz + + + + + + + 50Hz + + + + + + + 25Hz + + + + + + + + 0 + 0 + + + + 60Hz + + + true + + + + + + + 0 + + + + + + + + + + + + + 0 + 0 + + + + + 60 + 0 + + + + + 100 + 16777215 + + + + + + + + Hz + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + hzCustom + clicked() + hz + setFocus() + + + 54 + 104 + + + 85 + 107 + + + + + hz + textChanged(QString) + hzCustom + toggle() + + + 138 + 102 + + + 49 + 104 + + + + + hz + selectionChanged() + hzCustom + toggle() + + + 110 + 105 + + + 51 + 103 + + + + + buttonBox + accepted() + RecordingOptions + accept() + + + 280 + 287 + + + 60 + 219 + + + + + buttonBox + rejected() + RecordingOptions + reject() + + + 280 + 287 + + + 92 + 219 + + + + + profile + activated(int) + RecordingOptions + pickProfile(int) + + + 72 + 132 + + + 48 + 194 + + + + + args + textEdited(QString) + RecordingOptions + storeCustomArgs(QString) + + + 108 + 161 + + + 102 + 189 + + + + + + filePicked(QString) + argumentsPicked(QString) + pickFile() + pickProfile(int) + storeCustomArgs(QString) + + -- cgit v0.12