Qt: Add video recording presets

This commit is contained in:
Jeffrey Pfau 2014-11-12 00:55:37 -08:00
parent e51285a0db
commit 046a1b71ed
3 changed files with 664 additions and 201 deletions

View File

@ -11,6 +11,31 @@ QMap<QString, QString> VideoView::s_acodecMap;
QMap<QString, QString> VideoView::s_vcodecMap;
QMap<QString, QString> VideoView::s_containerMap;
bool VideoView::Preset::compatible(const Preset& other) const {
if (!other.container.isNull() && !container.isNull() && other.container != container) {
return false;
}
if (!other.acodec.isNull() && !acodec.isNull() && other.acodec != acodec) {
return false;
}
if (!other.vcodec.isNull() && !vcodec.isNull() && other.vcodec != vcodec) {
return false;
}
if (other.abr && abr && other.abr != abr) {
return false;
}
if (other.vbr && vbr && other.vbr != vbr) {
return false;
}
if (other.width && width && other.width != width) {
return false;
}
if (other.height && height && other.height != height) {
return false;
}
return true;
}
VideoView::VideoView(QWidget* parent)
: QWidget(parent)
, m_audioCodecCstr(nullptr)
@ -54,13 +79,73 @@ VideoView::VideoView(QWidget* parent)
connect(m_ui.abr, SIGNAL(valueChanged(int)), this, SLOT(setAudioBitrate(int)));
connect(m_ui.vbr, SIGNAL(valueChanged(int)), this, SLOT(setVideoBitrate(int)));
connect(m_ui.showAdvanced, SIGNAL(clicked(bool)), this, SLOT(showAdvanced(bool)));
FFmpegEncoderInit(&m_encoder);
addPreset(m_ui.preset1080, (Preset) {
.width = 1620,
.height = 1080
});
addPreset(m_ui.preset720, (Preset) {
.width = 1080,
.height = 720
});
addPreset(m_ui.preset480, (Preset) {
.width = 720,
.height = 480
});
addPreset(m_ui.preset160, (Preset) {
.width = 240,
.height = 160
});
addPreset(m_ui.presetHQ, (Preset) {
.container = "MP4",
.vcodec = "h.264",
.acodec = "AAC",
.vbr = 6000,
.abr = 384,
.width = 1620,
.height = 1080
});
addPreset(m_ui.presetYoutube, (Preset) {
.container = "MP4",
.vcodec = "h.264",
.acodec = "AAC",
.vbr = 3000,
.abr = 256,
.width = 1080,
.height = 720
});
addPreset(m_ui.presetWebM, (Preset) {
.container = "WebM",
.vcodec = "VP8",
.acodec = "Vorbis",
.vbr = 800,
.abr = 128
});
addPreset(m_ui.presetLossless, (Preset) {
.container = "MKV",
.vcodec = "PNG",
.acodec = "FLAC",
.width = 240,
.height = 160,
});
setAudioCodec(m_ui.audio->currentText());
setVideoCodec(m_ui.video->currentText());
setAudioBitrate(m_ui.abr->value());
setVideoBitrate(m_ui.vbr->value());
setContainer(m_ui.container->currentText());
showAdvanced(false);
}
VideoView::~VideoView() {
@ -101,58 +186,68 @@ void VideoView::setFilename(const QString& fname) {
validateSettings();
}
void VideoView::setAudioCodec(const QString& codec) {
void VideoView::setAudioCodec(const QString& codec, bool manual) {
free(m_audioCodecCstr);
m_audioCodec = sanitizeCodec(codec);
if (s_acodecMap.contains(m_audioCodec)) {
m_audioCodec = s_acodecMap[m_audioCodec];
}
m_audioCodec = sanitizeCodec(codec, s_acodecMap);
m_audioCodecCstr = strdup(m_audioCodec.toLocal8Bit().constData());
if (!FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr)) {
free(m_audioCodecCstr);
m_audioCodecCstr = nullptr;
}
validateSettings();
if (manual) {
uncheckIncompatible();
}
}
void VideoView::setVideoCodec(const QString& codec) {
void VideoView::setVideoCodec(const QString& codec, bool manual) {
free(m_videoCodecCstr);
m_videoCodec = sanitizeCodec(codec);
if (s_vcodecMap.contains(m_videoCodec)) {
m_videoCodec = s_vcodecMap[m_videoCodec];
}
m_videoCodec = sanitizeCodec(codec, s_vcodecMap);
m_videoCodecCstr = strdup(m_videoCodec.toLocal8Bit().constData());
if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) {
free(m_videoCodecCstr);
m_videoCodecCstr = nullptr;
}
validateSettings();
if (manual) {
uncheckIncompatible();
}
}
void VideoView::setContainer(const QString& container) {
void VideoView::setContainer(const QString& container, bool manual) {
free(m_containerCstr);
m_container = sanitizeCodec(container);
if (s_containerMap.contains(m_container)) {
m_container = s_containerMap[m_container];
}
m_container = sanitizeCodec(container, s_containerMap);
m_containerCstr = strdup(m_container.toLocal8Bit().constData());
if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) {
free(m_containerCstr);
m_containerCstr = nullptr;
}
validateSettings();
if (manual) {
uncheckIncompatible();
}
}
void VideoView::setAudioBitrate(int br) {
void VideoView::setAudioBitrate(int br, bool manual) {
m_abr = br * 1000;
FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr);
validateSettings();
if (manual) {
uncheckIncompatible();
}
}
void VideoView::setVideoBitrate(int br) {
void VideoView::setVideoBitrate(int br, bool manual) {
m_vbr = br * 1000;
FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr);
validateSettings();
if (manual) {
uncheckIncompatible();
}
}
void VideoView::showAdvanced(bool show) {
m_ui.advancedBox->setVisible(show);
}
bool VideoView::validateSettings() {
@ -185,12 +280,100 @@ bool VideoView::validateSettings() {
}
m_ui.start->setEnabled(valid && !m_filename.isNull());
return valid;
}
QString VideoView::sanitizeCodec(const QString& codec) {
void VideoView::uncheckIncompatible() {
Preset current = {
.container = m_container,
.acodec = m_audioCodec,
.vcodec = m_videoCodec,
.abr = m_abr / 1000,
.vbr = m_vbr / 1000
};
for (auto iterator = m_presets.constBegin(); iterator != m_presets.constEnd(); ++iterator) {
Preset next = *iterator;
next.container = sanitizeCodec(next.container, s_containerMap);
next.acodec = sanitizeCodec(next.acodec, s_acodecMap);
next.vcodec = sanitizeCodec(next.vcodec, s_vcodecMap);
if (!current.compatible(next)) {
safelyCheck(iterator.key(), false);
}
}
if (current.compatible(m_presets[m_ui.preset160])) {
safelyCheck(m_ui.preset160);
}
if (current.compatible(m_presets[m_ui.preset480])) {
safelyCheck(m_ui.preset480);
}
if (current.compatible(m_presets[m_ui.preset720])) {
safelyCheck(m_ui.preset720);
}
if (current.compatible(m_presets[m_ui.preset1080])) {
safelyCheck(m_ui.preset1080);
}
}
QString VideoView::sanitizeCodec(const QString& codec, const QMap<QString, QString>& mapping) {
QString sanitized = codec.toLower();
return sanitized.remove(QChar('.'));
sanitized = sanitized.remove(QChar('.'));
if (mapping.contains(sanitized)) {
sanitized = mapping[sanitized];
}
return sanitized;
}
void VideoView::safelyCheck(QAbstractButton* button, bool set) {
bool signalsBlocked = button->blockSignals(true);
button->setChecked(set);
button->blockSignals(signalsBlocked);
}
void VideoView::safelySet(QSpinBox* box, int value) {
bool signalsBlocked = box->blockSignals(true);
box->setValue(value);
box->blockSignals(signalsBlocked);
}
void VideoView::safelySet(QComboBox* box, const QString& value) {
bool signalsBlocked = box->blockSignals(true);
box->lineEdit()->setText(value);
box->blockSignals(signalsBlocked);
}
void VideoView::addPreset(QAbstractButton* button, const Preset& preset) {
m_presets[button] = preset;
connect(button, &QAbstractButton::pressed, [this, preset]() {
setPreset(preset);
});
}
void VideoView::setPreset(const Preset& preset) {
if (!preset.container.isNull()) {
setContainer(preset.container, false);
safelySet(m_ui.container, preset.container);
}
if (!preset.acodec.isNull()) {
setAudioCodec(preset.acodec, false);
safelySet(m_ui.audio, preset.acodec);
}
if (!preset.vcodec.isNull()) {
setVideoCodec(preset.vcodec, false);
safelySet(m_ui.video, preset.vcodec);
}
if (preset.abr) {
setAudioBitrate(preset.abr, false);
safelySet(m_ui.abr, preset.abr);
}
if (preset.vbr) {
setVideoBitrate(preset.vbr, false);
safelySet(m_ui.vbr, preset.vbr);
}
uncheckIncompatible();
validateSettings();
}
#endif

View File

@ -17,6 +17,18 @@ class VideoView : public QWidget {
Q_OBJECT
public:
struct Preset {
QString container;
QString vcodec;
QString acodec;
int vbr;
int abr;
int width;
int height;
bool compatible(const Preset&) const;
};
VideoView(QWidget* parent = nullptr);
virtual ~VideoView();
@ -33,16 +45,26 @@ signals:
private slots:
void selectFile();
void setFilename(const QString&);
void setAudioCodec(const QString&);
void setVideoCodec(const QString&);
void setContainer(const QString&);
void setAudioCodec(const QString&, bool manual = true);
void setVideoCodec(const QString&, bool manual = true);
void setContainer(const QString&, bool manual = true);
void setAudioBitrate(int);
void setVideoBitrate(int);
void setAudioBitrate(int, bool manual = true);
void setVideoBitrate(int, bool manual = true);
void showAdvanced(bool);
void uncheckIncompatible();
private:
bool validateSettings();
static QString sanitizeCodec(const QString&);
static QString sanitizeCodec(const QString&, const QMap<QString, QString>& mapping);
static void safelyCheck(QAbstractButton*, bool set = true);
static void safelySet(QSpinBox*, int value);
static void safelySet(QComboBox*, const QString& value);
void addPreset(QAbstractButton*, const Preset&);
void setPreset(const Preset&);
Ui::VideoView m_ui;
@ -59,6 +81,8 @@ private:
int m_abr;
int m_vbr;
QMap<QAbstractButton*, Preset> m_presets;
static QMap<QString, QString> s_acodecMap;
static QMap<QString, QString> s_vcodecMap;
static QMap<QString, QString> s_containerMap;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>462</width>
<height>194</height>
<width>351</width>
<height>510</height>
</rect>
</property>
<property name="sizePolicy">
@ -19,175 +19,11 @@
<property name="windowTitle">
<string>Record Video</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item row="0" column="0" rowspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Format</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QComboBox" name="container">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>MKV</string>
</property>
</item>
<item>
<property name="text">
<string>WebM</string>
</property>
</item>
<item>
<property name="text">
<string>AVI</string>
</property>
</item>
<item>
<property name="text">
<string>MP4</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="video">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>PNG</string>
</property>
</item>
<item>
<property name="text">
<string>h.264</string>
</property>
</item>
<item>
<property name="text">
<string>VP8</string>
</property>
</item>
<item>
<property name="text">
<string>Xvid</string>
</property>
</item>
<item>
<property name="text">
<string>FFV1</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="audio">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>FLAC</string>
</property>
</item>
<item>
<property name="text">
<string>Vorbis</string>
</property>
</item>
<item>
<property name="text">
<string>MP3</string>
</property>
</item>
<item>
<property name="text">
<string>AAC</string>
</property>
</item>
<item>
<property name="text">
<string>Uncompressed</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string> Bitrate</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Video</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>vbr</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="vbr">
<property name="suffix">
<string/>
</property>
<property name="minimum">
<number>200</number>
</property>
<property name="maximum">
<number>2000</number>
</property>
<property name="value">
<number>400</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Audio</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>abr</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="abr">
<property name="minimum">
<number>16</number>
</property>
<property name="maximum">
<number>320</number>
</property>
<property name="value">
<number>192</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="start">
@ -221,7 +57,7 @@
</property>
</widget>
</item>
<item row="1" column="2">
<item row="1" column="3">
<widget class="QPushButton" name="selectFile">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -234,7 +70,7 @@
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<item row="0" column="0" colspan="4">
<widget class="QLineEdit" name="filename">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -244,15 +80,433 @@
</property>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Presets</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="presetHQ">
<property name="text">
<string>High Quality</string>
</property>
<attribute name="buttonGroup">
<string notr="true">presets</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="presetYoutube">
<property name="text">
<string>YouTube</string>
</property>
<attribute name="buttonGroup">
<string notr="true">presets</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="presetWebM">
<property name="text">
<string>WebM</string>
</property>
<attribute name="buttonGroup">
<string notr="true">presets</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="presetLossless">
<property name="text">
<string>Lossless</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">presets</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="preset1080">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>1080p</string>
</property>
<attribute name="buttonGroup">
<string notr="true">resolutions</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="preset720">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>720p</string>
</property>
<attribute name="buttonGroup">
<string notr="true">resolutions</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="preset480">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>480p</string>
</property>
<attribute name="buttonGroup">
<string notr="true">resolutions</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="preset160">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>GBA (240x160)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">resolutions</string>
</attribute>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="advancedBox" native="true">
<layout class="QGridLayout" name="gridLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QGroupBox" name="formatBox">
<property name="title">
<string>Format</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QComboBox" name="container">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>MKV</string>
</property>
</item>
<item>
<property name="text">
<string>WebM</string>
</property>
</item>
<item>
<property name="text">
<string>AVI</string>
</property>
</item>
<item>
<property name="text">
<string>MP4</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="video">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>PNG</string>
</property>
</item>
<item>
<property name="text">
<string>h.264</string>
</property>
</item>
<item>
<property name="text">
<string>VP8</string>
</property>
</item>
<item>
<property name="text">
<string>Xvid</string>
</property>
</item>
<item>
<property name="text">
<string>FFV1</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="audio">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>FLAC</string>
</property>
</item>
<item>
<property name="text">
<string>Vorbis</string>
</property>
</item>
<item>
<property name="text">
<string>MP3</string>
</property>
</item>
<item>
<property name="text">
<string>AAC</string>
</property>
</item>
<item>
<property name="text">
<string>Uncompressed</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="bitrateBox">
<property name="title">
<string> Bitrate (kbps)</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>VBR </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>vbr</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="vbr">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string/>
</property>
<property name="minimum">
<number>200</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>400</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="abr">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="minimum">
<number>16</number>
</property>
<property name="maximum">
<number>320</number>
</property>
<property name="value">
<number>128</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>ABR</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>abr</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="dimensionsBox">
<property name="title">
<string>Dimensions</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>×</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QSpinBox" name="height">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>160</number>
</property>
<property name="maximum">
<number>3160</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="width">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>240</number>
</property>
<property name="maximum">
<number>3840</number>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QSpinBox" name="hratio">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="wratio">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QCheckBox" name="lockRatio">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Lock aspect ratio</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="showAdvanced">
<property name="text">
<string>Show advanced</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
@ -263,9 +517,11 @@
<tabstop>container</tabstop>
<tabstop>video</tabstop>
<tabstop>audio</tabstop>
<tabstop>vbr</tabstop>
<tabstop>abr</tabstop>
</tabstops>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="presets"/>
<buttongroup name="resolutions"/>
</buttongroups>
</ui>