|
|
#include "nmWxPebiResultAnimationExportDlg.h"
|
|
|
|
|
|
#include "nmFFmpegVideoWriter.h"
|
|
|
#include "ZxBaseUtil.h"
|
|
|
|
|
|
#include <QButtonGroup>
|
|
|
#include <QComboBox>
|
|
|
#include <QCoreApplication>
|
|
|
#include <QDialogButtonBox>
|
|
|
#include <QDir>
|
|
|
#include <QFileDialog>
|
|
|
#include <QFileInfo>
|
|
|
#include <QGridLayout>
|
|
|
#include <QGroupBox>
|
|
|
#include <QIcon>
|
|
|
#include <QLabel>
|
|
|
#include <QLineEdit>
|
|
|
#include <QMessageBox>
|
|
|
#include <QPushButton>
|
|
|
#include <QRadioButton>
|
|
|
#include <QSizePolicy>
|
|
|
#include <QSpinBox>
|
|
|
#include <QVBoxLayout>
|
|
|
#include <QVariant>
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
QString exportDialogIconPath(const QString& sFileName)
|
|
|
{
|
|
|
const QString sApplicationDir = QCoreApplication::applicationDirPath();
|
|
|
return sApplicationDir.section('/', 0, -2) +
|
|
|
"/Res/Icon/" + sFileName;
|
|
|
}
|
|
|
|
|
|
QLabel* createRightAlignedLabel(const QString& sText, QWidget* pParent)
|
|
|
{
|
|
|
QLabel* pLabel = new QLabel(sText, pParent);
|
|
|
pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
return pLabel;
|
|
|
}
|
|
|
|
|
|
QString formatPixelSize(int nWidth, int nHeight)
|
|
|
{
|
|
|
QString sText = QString::number(nWidth);
|
|
|
sText.append(' ');
|
|
|
sText.append(QChar(0x00D7));
|
|
|
sText.append(QString::fromLatin1(" %1 px").arg(nHeight));
|
|
|
return sText;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
nmWxPebiResultAnimationExportDlg::nmWxPebiResultAnimationExportDlg(
|
|
|
const QVector<double>& vecFrameTimesHours,
|
|
|
const QSize& oCurrentViewportSize,
|
|
|
QWidget* pParent)
|
|
|
: iDlgBase(pParent),
|
|
|
m_vecFrameTimesHours(vecFrameTimesHours),
|
|
|
m_pFpsSpinBox(nullptr),
|
|
|
m_pStartFrameCombo(nullptr),
|
|
|
m_pEndFrameCombo(nullptr),
|
|
|
m_pSamplingIntervalSpinBox(nullptr),
|
|
|
m_pResolutionButtonGroup(nullptr),
|
|
|
m_pCurrentSizeRadioButton(nullptr),
|
|
|
m_p720pRadioButton(nullptr),
|
|
|
m_p1080pRadioButton(nullptr),
|
|
|
m_pCustomSizeRadioButton(nullptr),
|
|
|
m_pWidthSpinBox(nullptr),
|
|
|
m_pHeightSpinBox(nullptr),
|
|
|
m_pVideoFormatCombo(nullptr),
|
|
|
m_pTargetPathEdit(nullptr),
|
|
|
m_pOutputFrameCountLabel(nullptr),
|
|
|
m_pEstimatedDurationLabel(nullptr),
|
|
|
m_pErrorLabel(nullptr)
|
|
|
{
|
|
|
int nCurrentWidth = oCurrentViewportSize.width();
|
|
|
int nCurrentHeight = oCurrentViewportSize.height();
|
|
|
if(nCurrentWidth <= 0 || nCurrentHeight <= 0) {
|
|
|
nCurrentWidth = 1280;
|
|
|
nCurrentHeight = 720;
|
|
|
}
|
|
|
m_oEffectiveCurrentViewportSize = QSize(
|
|
|
normalizeEvenDimension(nCurrentWidth),
|
|
|
normalizeEvenDimension(nCurrentHeight));
|
|
|
|
|
|
createUi();
|
|
|
populateFrameItems();
|
|
|
populateVideoFormats();
|
|
|
setTargetFilePath(getSuggestedTargetFilePath());
|
|
|
onResolutionPresetChanged(Resolution_CurrentView);
|
|
|
onFrameRangeChanged();
|
|
|
}
|
|
|
|
|
|
nmPebiResultAnimationExportSettings
|
|
|
nmWxPebiResultAnimationExportDlg::getSettings() const
|
|
|
{
|
|
|
return m_oAcceptedSettings;
|
|
|
}
|
|
|
|
|
|
QString nmWxPebiResultAnimationExportDlg::getTargetFilePath() const
|
|
|
{
|
|
|
return m_sAcceptedTargetFilePath;
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::onFrameRangeChanged()
|
|
|
{
|
|
|
const int nStartIndex = m_pStartFrameCombo->currentIndex();
|
|
|
if(nStartIndex >= 0 &&
|
|
|
m_pEndFrameCombo->currentIndex() < nStartIndex) {
|
|
|
const bool bBlocked = m_pEndFrameCombo->blockSignals(true);
|
|
|
m_pEndFrameCombo->setCurrentIndex(nStartIndex);
|
|
|
m_pEndFrameCombo->blockSignals(bBlocked);
|
|
|
}
|
|
|
|
|
|
const int nRangeLength = qMax(1,
|
|
|
m_pEndFrameCombo->currentIndex() -
|
|
|
m_pStartFrameCombo->currentIndex());
|
|
|
m_pSamplingIntervalSpinBox->setMaximum(nRangeLength);
|
|
|
updateSummary();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::onResolutionPresetChanged(
|
|
|
int nPresetId)
|
|
|
{
|
|
|
const bool bCustom = nPresetId == Resolution_Custom;
|
|
|
m_pWidthSpinBox->setEnabled(bCustom);
|
|
|
m_pHeightSpinBox->setEnabled(bCustom);
|
|
|
updateSummary();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::normalizeCustomDimensions()
|
|
|
{
|
|
|
// editingFinished后再回写,避免valueChanged期间干扰用户键盘输入。
|
|
|
m_pWidthSpinBox->setValue(
|
|
|
normalizeEvenDimension(m_pWidthSpinBox->value()));
|
|
|
m_pHeightSpinBox->setValue(
|
|
|
normalizeEvenDimension(m_pHeightSpinBox->value()));
|
|
|
updateSummary();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::updateSummary()
|
|
|
{
|
|
|
const nmPebiResultAnimationExportSettings oSettings =
|
|
|
collectSettings();
|
|
|
const int nOutputFrameCount = oSettings.getOutputFrameCount();
|
|
|
m_pOutputFrameCountLabel->setText(
|
|
|
tr("%1 frames").arg(nOutputFrameCount));
|
|
|
const double dDuration = oSettings.m_nFramesPerSecond > 0
|
|
|
? static_cast<double>(nOutputFrameCount) /
|
|
|
static_cast<double>(oSettings.m_nFramesPerSecond)
|
|
|
: 0.0;
|
|
|
m_pEstimatedDurationLabel->setText(
|
|
|
tr("%1 s").arg(QString::number(dDuration, 'g', 4)));
|
|
|
clearError();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::onVideoFormatChanged(int nIndex)
|
|
|
{
|
|
|
Q_UNUSED(nIndex);
|
|
|
if(m_pTargetPathEdit != nullptr &&
|
|
|
!m_pTargetPathEdit->text().isEmpty()) {
|
|
|
setTargetFilePath(normalizeTargetFilePath(
|
|
|
m_pTargetPathEdit->text()));
|
|
|
}
|
|
|
clearError();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::browseTargetFile()
|
|
|
{
|
|
|
const QString sExtension = getSelectedVideoExtension();
|
|
|
if(sExtension.isEmpty()) {
|
|
|
showError(tr("The selected video format is unsupported."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const QString sInitialPath = m_pTargetPathEdit->text().isEmpty()
|
|
|
? getSuggestedTargetFilePath() : m_pTargetPathEdit->text();
|
|
|
const QFileInfo oInitialFileInfo(sInitialPath);
|
|
|
|
|
|
QFileDialog oFileDialog(this, tr("Save Result Animation"));
|
|
|
oFileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
|
oFileDialog.setFileMode(QFileDialog::AnyFile);
|
|
|
oFileDialog.setNameFilter(getSelectedVideoFilter());
|
|
|
oFileDialog.setDefaultSuffix(sExtension);
|
|
|
oFileDialog.setOption(QFileDialog::DontConfirmOverwrite, true);
|
|
|
oFileDialog.setDirectory(oInitialFileInfo.absolutePath());
|
|
|
oFileDialog.selectFile(oInitialFileInfo.fileName());
|
|
|
if(oFileDialog.exec() != QDialog::Accepted ||
|
|
|
oFileDialog.selectedFiles().isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const QString sTargetPath = normalizeTargetFilePath(
|
|
|
oFileDialog.selectedFiles().first());
|
|
|
const QFileInfo oTargetFileInfo(sTargetPath);
|
|
|
setTargetFilePath(oTargetFileInfo.absoluteFilePath());
|
|
|
ZxBaseUtil::setLastDir(oTargetFileInfo.absolutePath());
|
|
|
clearError();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::acceptSettings()
|
|
|
{
|
|
|
// 只有自定义模式需要把编辑值回写为编码器最终使用的偶数尺寸。
|
|
|
if(m_pCustomSizeRadioButton->isChecked()) {
|
|
|
normalizeCustomDimensions();
|
|
|
}
|
|
|
const nmPebiResultAnimationExportSettings oSettings =
|
|
|
collectSettings();
|
|
|
if(!oSettings.isValid(m_vecFrameTimesHours.size())) {
|
|
|
showError(tr("The animation export settings are invalid."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(m_pTargetPathEdit->text().isEmpty()) {
|
|
|
showError(tr("Please select an output file."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const QString sExtension = getSelectedVideoExtension();
|
|
|
if(sExtension.isEmpty()) {
|
|
|
showError(tr("The selected video format is unsupported."));
|
|
|
return;
|
|
|
}
|
|
|
const QString sTargetPath = QFileInfo(normalizeTargetFilePath(
|
|
|
m_pTargetPathEdit->text())).absoluteFilePath();
|
|
|
const QFileInfo oTargetFileInfo(sTargetPath);
|
|
|
if(oTargetFileInfo.suffix().compare(
|
|
|
sExtension, Qt::CaseInsensitive) != 0) {
|
|
|
showError(tr("The selected video file extension does not match "
|
|
|
"the selected format."));
|
|
|
return;
|
|
|
}
|
|
|
if(oTargetFileInfo.isDir()) {
|
|
|
showError(tr("The selected output path is a directory."));
|
|
|
return;
|
|
|
}
|
|
|
if(!QDir(oTargetFileInfo.absolutePath()).exists()) {
|
|
|
showError(tr("The selected output directory does not exist."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
setTargetFilePath(sTargetPath);
|
|
|
if(oTargetFileInfo.exists() &&
|
|
|
QMessageBox::question(
|
|
|
this, tr("Export Animation"),
|
|
|
tr("The selected file already exists. Replace it?"),
|
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
|
QMessageBox::No) != QMessageBox::Yes) {
|
|
|
return;
|
|
|
}
|
|
|
m_oAcceptedSettings = oSettings;
|
|
|
m_sAcceptedTargetFilePath = sTargetPath;
|
|
|
accept();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::createUi()
|
|
|
{
|
|
|
setWindowTitle(tr("Export Animation"));
|
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
setMinimumWidth(460);
|
|
|
|
|
|
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
|
|
|
pMainLayout->setContentsMargins(12, 10, 12, 10);
|
|
|
pMainLayout->setSpacing(8);
|
|
|
|
|
|
QGroupBox* pFrameGroup = new QGroupBox(tr("Frame Settings"), this);
|
|
|
QGridLayout* pFrameGrid = new QGridLayout(pFrameGroup);
|
|
|
pFrameGrid->setHorizontalSpacing(8);
|
|
|
pFrameGrid->setVerticalSpacing(6);
|
|
|
pFrameGrid->setColumnStretch(1, 1);
|
|
|
const QSizePolicy oFieldSizePolicy(
|
|
|
QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
|
|
|
|
m_pFpsSpinBox = new QSpinBox(pFrameGroup);
|
|
|
m_pFpsSpinBox->setRange(1, 5000);
|
|
|
m_pFpsSpinBox->setValue(15);
|
|
|
m_pFpsSpinBox->setSuffix(tr(" FPS"));
|
|
|
m_pFpsSpinBox->setSizePolicy(oFieldSizePolicy);
|
|
|
pFrameGrid->addWidget(
|
|
|
createRightAlignedLabel(tr("Frame rate"), pFrameGroup), 0, 0);
|
|
|
pFrameGrid->addWidget(m_pFpsSpinBox, 0, 1);
|
|
|
|
|
|
m_pStartFrameCombo = new QComboBox(pFrameGroup);
|
|
|
m_pStartFrameCombo->setSizePolicy(oFieldSizePolicy);
|
|
|
pFrameGrid->addWidget(
|
|
|
createRightAlignedLabel(tr("Start time"), pFrameGroup), 1, 0);
|
|
|
pFrameGrid->addWidget(m_pStartFrameCombo, 1, 1);
|
|
|
|
|
|
m_pEndFrameCombo = new QComboBox(pFrameGroup);
|
|
|
m_pEndFrameCombo->setSizePolicy(oFieldSizePolicy);
|
|
|
pFrameGrid->addWidget(
|
|
|
createRightAlignedLabel(tr("End time"), pFrameGroup), 2, 0);
|
|
|
pFrameGrid->addWidget(m_pEndFrameCombo, 2, 1);
|
|
|
|
|
|
m_pSamplingIntervalSpinBox = new QSpinBox(pFrameGroup);
|
|
|
m_pSamplingIntervalSpinBox->setRange(1, 1);
|
|
|
m_pSamplingIntervalSpinBox->setSizePolicy(oFieldSizePolicy);
|
|
|
pFrameGrid->addWidget(
|
|
|
createRightAlignedLabel(tr("Frame step"), pFrameGroup), 3, 0);
|
|
|
pFrameGrid->addWidget(m_pSamplingIntervalSpinBox, 3, 1);
|
|
|
pMainLayout->addWidget(pFrameGroup);
|
|
|
|
|
|
QGroupBox* pOutputSizeGroup = new QGroupBox(tr("Output Size"), this);
|
|
|
QGridLayout* pOutputSizeGrid = new QGridLayout(pOutputSizeGroup);
|
|
|
pOutputSizeGrid->setHorizontalSpacing(6);
|
|
|
pOutputSizeGrid->setVerticalSpacing(6);
|
|
|
|
|
|
m_pResolutionButtonGroup = new QButtonGroup(this);
|
|
|
m_pCurrentSizeRadioButton = new QRadioButton(
|
|
|
tr("Current image size") + QString::fromLatin1(" ") +
|
|
|
formatPixelSize(m_oEffectiveCurrentViewportSize.width(),
|
|
|
m_oEffectiveCurrentViewportSize.height()),
|
|
|
pOutputSizeGroup);
|
|
|
m_p720pRadioButton = new QRadioButton(
|
|
|
formatPixelSize(1280, 720), pOutputSizeGroup);
|
|
|
m_p1080pRadioButton = new QRadioButton(
|
|
|
formatPixelSize(1920, 1080), pOutputSizeGroup);
|
|
|
m_pCustomSizeRadioButton = new QRadioButton(
|
|
|
tr("Custom size"), pOutputSizeGroup);
|
|
|
m_pResolutionButtonGroup->addButton(
|
|
|
m_pCurrentSizeRadioButton, Resolution_CurrentView);
|
|
|
m_pResolutionButtonGroup->addButton(
|
|
|
m_p720pRadioButton, Resolution_720p);
|
|
|
m_pResolutionButtonGroup->addButton(
|
|
|
m_p1080pRadioButton, Resolution_1080p);
|
|
|
m_pResolutionButtonGroup->addButton(
|
|
|
m_pCustomSizeRadioButton, Resolution_Custom);
|
|
|
m_pCurrentSizeRadioButton->setChecked(true);
|
|
|
|
|
|
pOutputSizeGrid->addWidget(
|
|
|
m_pCurrentSizeRadioButton, 0, 1, 1, 5, Qt::AlignLeft);
|
|
|
pOutputSizeGrid->addWidget(
|
|
|
m_p720pRadioButton, 1, 1, 1, 5, Qt::AlignLeft);
|
|
|
pOutputSizeGrid->addWidget(
|
|
|
m_p1080pRadioButton, 2, 1, 1, 5, Qt::AlignLeft);
|
|
|
pOutputSizeGrid->addWidget(m_pCustomSizeRadioButton, 3, 1, Qt::AlignLeft);
|
|
|
|
|
|
m_pWidthSpinBox = new QSpinBox(pOutputSizeGroup);
|
|
|
m_pHeightSpinBox = new QSpinBox(pOutputSizeGroup);
|
|
|
m_pWidthSpinBox->setRange(2, 8192);
|
|
|
m_pHeightSpinBox->setRange(2, 8192);
|
|
|
m_pWidthSpinBox->setSingleStep(2);
|
|
|
m_pHeightSpinBox->setSingleStep(2);
|
|
|
m_pWidthSpinBox->setMinimumWidth(90);
|
|
|
m_pWidthSpinBox->setMaximumWidth(110);
|
|
|
m_pHeightSpinBox->setMinimumWidth(90);
|
|
|
m_pHeightSpinBox->setMaximumWidth(110);
|
|
|
m_pWidthSpinBox->setValue(m_oEffectiveCurrentViewportSize.width());
|
|
|
m_pHeightSpinBox->setValue(m_oEffectiveCurrentViewportSize.height());
|
|
|
QLabel* pMultiplyLabel = new QLabel(
|
|
|
QString(QChar(0x00D7)), pOutputSizeGroup);
|
|
|
pMultiplyLabel->setAlignment(Qt::AlignCenter);
|
|
|
QLabel* pPixelLabel = new QLabel(
|
|
|
QString::fromLatin1("px"), pOutputSizeGroup);
|
|
|
pOutputSizeGrid->addWidget(m_pWidthSpinBox, 3, 2);
|
|
|
pOutputSizeGrid->addWidget(pMultiplyLabel, 3, 3, Qt::AlignCenter);
|
|
|
pOutputSizeGrid->addWidget(m_pHeightSpinBox, 3, 4);
|
|
|
pOutputSizeGrid->addWidget(pPixelLabel, 3, 5, Qt::AlignLeft);
|
|
|
// 两侧等权留白使完整尺寸选项块居中,块内控件仍保持左对齐。
|
|
|
pOutputSizeGrid->setColumnStretch(0, 1);
|
|
|
pOutputSizeGrid->setColumnStretch(6, 1);
|
|
|
pMainLayout->addWidget(pOutputSizeGroup);
|
|
|
|
|
|
QGroupBox* pOutputFileGroup = new QGroupBox(
|
|
|
tr("Output File"), this);
|
|
|
QGridLayout* pOutputFileGrid = new QGridLayout(pOutputFileGroup);
|
|
|
pOutputFileGrid->setHorizontalSpacing(8);
|
|
|
pOutputFileGrid->setVerticalSpacing(6);
|
|
|
pOutputFileGrid->setColumnStretch(1, 1);
|
|
|
|
|
|
m_pVideoFormatCombo = new QComboBox(pOutputFileGroup);
|
|
|
m_pVideoFormatCombo->setSizePolicy(oFieldSizePolicy);
|
|
|
pOutputFileGrid->addWidget(
|
|
|
createRightAlignedLabel(
|
|
|
tr("File format"), pOutputFileGroup), 0, 0);
|
|
|
pOutputFileGrid->addWidget(m_pVideoFormatCombo, 0, 1, 1, 2);
|
|
|
|
|
|
m_pTargetPathEdit = new QLineEdit(pOutputFileGroup);
|
|
|
m_pTargetPathEdit->setReadOnly(true);
|
|
|
m_pTargetPathEdit->setSizePolicy(oFieldSizePolicy);
|
|
|
QPushButton* pBrowseButton = new QPushButton(
|
|
|
tr("Browse..."), pOutputFileGroup);
|
|
|
pBrowseButton->setMinimumWidth(72);
|
|
|
pOutputFileGrid->addWidget(
|
|
|
createRightAlignedLabel(
|
|
|
tr("Save location"), pOutputFileGroup), 1, 0);
|
|
|
pOutputFileGrid->addWidget(m_pTargetPathEdit, 1, 1);
|
|
|
pOutputFileGrid->addWidget(pBrowseButton, 1, 2);
|
|
|
pMainLayout->addWidget(pOutputFileGroup);
|
|
|
|
|
|
QGroupBox* pInformationGroup = new QGroupBox(
|
|
|
tr("Export Information"), this);
|
|
|
QGridLayout* pInformationGrid = new QGridLayout(pInformationGroup);
|
|
|
pInformationGrid->setHorizontalSpacing(8);
|
|
|
pInformationGrid->setVerticalSpacing(6);
|
|
|
m_pOutputFrameCountLabel = new QLabel(pInformationGroup);
|
|
|
m_pEstimatedDurationLabel = new QLabel(pInformationGroup);
|
|
|
m_pOutputFrameCountLabel->setTextInteractionFlags(
|
|
|
Qt::TextSelectableByMouse);
|
|
|
m_pEstimatedDurationLabel->setTextInteractionFlags(
|
|
|
Qt::TextSelectableByMouse);
|
|
|
pInformationGrid->addWidget(
|
|
|
createRightAlignedLabel(
|
|
|
tr("Output frames"), pInformationGroup), 0, 1);
|
|
|
pInformationGrid->addWidget(
|
|
|
m_pOutputFrameCountLabel, 0, 2, Qt::AlignLeft);
|
|
|
pInformationGrid->addWidget(
|
|
|
createRightAlignedLabel(
|
|
|
tr("Estimated duration"), pInformationGroup), 1, 1);
|
|
|
pInformationGrid->addWidget(
|
|
|
m_pEstimatedDurationLabel, 1, 2, Qt::AlignLeft);
|
|
|
// 标签列和数值列作为一个整体居中,保留内部左右对齐关系。
|
|
|
pInformationGrid->setColumnStretch(0, 1);
|
|
|
pInformationGrid->setColumnStretch(3, 1);
|
|
|
pMainLayout->addWidget(pInformationGroup);
|
|
|
|
|
|
m_pErrorLabel = new QLabel(this);
|
|
|
m_pErrorLabel->setStyleSheet("QLabel { color: #a32626; }");
|
|
|
m_pErrorLabel->setWordWrap(true);
|
|
|
m_pErrorLabel->hide();
|
|
|
pMainLayout->addWidget(m_pErrorLabel);
|
|
|
|
|
|
QDialogButtonBox* pButtonBox = new QDialogButtonBox(
|
|
|
QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
|
|
Qt::Horizontal, this);
|
|
|
QPushButton* pOkButton = pButtonBox->button(QDialogButtonBox::Ok);
|
|
|
QPushButton* pCancelButton = pButtonBox->button(QDialogButtonBox::Cancel);
|
|
|
pOkButton->setIcon(QIcon(exportDialogIconPath("OK.png")));
|
|
|
pCancelButton->setIcon(QIcon(exportDialogIconPath("Cancel.png")));
|
|
|
pOkButton->setIconSize(QSize(16, 16));
|
|
|
pCancelButton->setIconSize(QSize(16, 16));
|
|
|
pOkButton->setMinimumWidth(72);
|
|
|
pCancelButton->setMinimumWidth(72);
|
|
|
pMainLayout->addWidget(pButtonBox, 0, Qt::AlignRight);
|
|
|
|
|
|
connect(m_pStartFrameCombo, SIGNAL(currentIndexChanged(int)),
|
|
|
this, SLOT(onFrameRangeChanged()));
|
|
|
connect(m_pEndFrameCombo, SIGNAL(currentIndexChanged(int)),
|
|
|
this, SLOT(onFrameRangeChanged()));
|
|
|
connect(m_pSamplingIntervalSpinBox, SIGNAL(valueChanged(int)),
|
|
|
this, SLOT(updateSummary()));
|
|
|
connect(m_pFpsSpinBox, SIGNAL(valueChanged(int)),
|
|
|
this, SLOT(updateSummary()));
|
|
|
connect(m_pResolutionButtonGroup, SIGNAL(buttonClicked(int)),
|
|
|
this, SLOT(onResolutionPresetChanged(int)));
|
|
|
connect(m_pVideoFormatCombo, SIGNAL(currentIndexChanged(int)),
|
|
|
this, SLOT(onVideoFormatChanged(int)));
|
|
|
connect(pBrowseButton, SIGNAL(clicked()),
|
|
|
this, SLOT(browseTargetFile()));
|
|
|
connect(m_pWidthSpinBox, SIGNAL(valueChanged(int)),
|
|
|
this, SLOT(updateSummary()));
|
|
|
connect(m_pHeightSpinBox, SIGNAL(valueChanged(int)),
|
|
|
this, SLOT(updateSummary()));
|
|
|
connect(m_pWidthSpinBox, SIGNAL(editingFinished()),
|
|
|
this, SLOT(normalizeCustomDimensions()));
|
|
|
connect(m_pHeightSpinBox, SIGNAL(editingFinished()),
|
|
|
this, SLOT(normalizeCustomDimensions()));
|
|
|
connect(pButtonBox, SIGNAL(accepted()),
|
|
|
this, SLOT(acceptSettings()));
|
|
|
connect(pButtonBox, SIGNAL(rejected()),
|
|
|
this, SLOT(reject()));
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::populateFrameItems()
|
|
|
{
|
|
|
for(int nFrameIndex = 0;
|
|
|
nFrameIndex < m_vecFrameTimesHours.size(); ++nFrameIndex) {
|
|
|
const QString sText = tr("%1 h").arg(QString::number(
|
|
|
m_vecFrameTimesHours[nFrameIndex], 'g', 8));
|
|
|
m_pStartFrameCombo->addItem(sText, nFrameIndex);
|
|
|
m_pEndFrameCombo->addItem(sText, nFrameIndex);
|
|
|
}
|
|
|
if(!m_vecFrameTimesHours.isEmpty()) {
|
|
|
m_pStartFrameCombo->setCurrentIndex(0);
|
|
|
m_pEndFrameCombo->setCurrentIndex(
|
|
|
m_vecFrameTimesHours.size() - 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::populateVideoFormats()
|
|
|
{
|
|
|
// itemData只保存稳定扩展名,界面翻译不会参与格式识别。
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("MP4 Video (*.mp4)"), QString::fromLatin1("mp4"));
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("AVI Video (*.avi)"), QString::fromLatin1("avi"));
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("QuickTime Video (*.mov)"), QString::fromLatin1("mov"));
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("MPEG Video (*.mpg)"), QString::fromLatin1("mpg"));
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("WebM Video (*.webm)"), QString::fromLatin1("webm"));
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("Matroska Video (*.mkv)"), QString::fromLatin1("mkv"));
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("Windows Media Video (*.wmv)"), QString::fromLatin1("wmv"));
|
|
|
m_pVideoFormatCombo->addItem(
|
|
|
tr("Flash Video (*.flv)"), QString::fromLatin1("flv"));
|
|
|
m_pVideoFormatCombo->setCurrentIndex(0);
|
|
|
}
|
|
|
|
|
|
nmPebiResultAnimationExportSettings
|
|
|
nmWxPebiResultAnimationExportDlg::collectSettings() const
|
|
|
{
|
|
|
nmPebiResultAnimationExportSettings oSettings;
|
|
|
oSettings.m_nFramesPerSecond = m_pFpsSpinBox->value();
|
|
|
oSettings.m_nStartFrameIndex =
|
|
|
m_pStartFrameCombo->itemData(
|
|
|
m_pStartFrameCombo->currentIndex()).toInt();
|
|
|
oSettings.m_nEndFrameIndex =
|
|
|
m_pEndFrameCombo->itemData(
|
|
|
m_pEndFrameCombo->currentIndex()).toInt();
|
|
|
oSettings.m_nSamplingInterval =
|
|
|
m_pSamplingIntervalSpinBox->value();
|
|
|
const QSize oOutputSize = getSelectedOutputSize();
|
|
|
oSettings.m_nOutputWidth = oOutputSize.width();
|
|
|
oSettings.m_nOutputHeight = oOutputSize.height();
|
|
|
return oSettings;
|
|
|
}
|
|
|
|
|
|
QSize nmWxPebiResultAnimationExportDlg::getSelectedOutputSize() const
|
|
|
{
|
|
|
const int nPresetId = m_pResolutionButtonGroup == nullptr
|
|
|
? Resolution_CurrentView :
|
|
|
m_pResolutionButtonGroup->checkedId();
|
|
|
if(nPresetId == Resolution_720p) {
|
|
|
return QSize(1280, 720);
|
|
|
}
|
|
|
if(nPresetId == Resolution_1080p) {
|
|
|
return QSize(1920, 1080);
|
|
|
}
|
|
|
if(nPresetId == Resolution_Custom) {
|
|
|
return QSize(
|
|
|
normalizeEvenDimension(m_pWidthSpinBox->value()),
|
|
|
normalizeEvenDimension(m_pHeightSpinBox->value()));
|
|
|
}
|
|
|
return m_oEffectiveCurrentViewportSize;
|
|
|
}
|
|
|
|
|
|
QString nmWxPebiResultAnimationExportDlg::getSelectedVideoExtension() const
|
|
|
{
|
|
|
if(m_pVideoFormatCombo == nullptr ||
|
|
|
m_pVideoFormatCombo->currentIndex() < 0) {
|
|
|
return QString();
|
|
|
}
|
|
|
const QString sExtension = m_pVideoFormatCombo->itemData(
|
|
|
m_pVideoFormatCombo->currentIndex()).toString().toLower();
|
|
|
return nmFFmpegVideoWriter::isSupportedExtension(sExtension)
|
|
|
? sExtension : QString();
|
|
|
}
|
|
|
|
|
|
QString nmWxPebiResultAnimationExportDlg::getSelectedVideoFilter() const
|
|
|
{
|
|
|
return m_pVideoFormatCombo == nullptr
|
|
|
? QString() : m_pVideoFormatCombo->currentText();
|
|
|
}
|
|
|
|
|
|
QString nmWxPebiResultAnimationExportDlg::normalizeTargetFilePath(
|
|
|
const QString& sPath) const
|
|
|
{
|
|
|
const QString sExtension = getSelectedVideoExtension();
|
|
|
if(sPath.isEmpty() || sExtension.isEmpty()) {
|
|
|
return QString();
|
|
|
}
|
|
|
|
|
|
QString sNormalizedPath = QDir::fromNativeSeparators(sPath);
|
|
|
const QString sExistingExtension =
|
|
|
QFileInfo(sNormalizedPath).suffix().toLower();
|
|
|
if(sExistingExtension.isEmpty()) {
|
|
|
if(!sNormalizedPath.endsWith('.')) {
|
|
|
sNormalizedPath.append('.');
|
|
|
}
|
|
|
sNormalizedPath.append(sExtension);
|
|
|
} else if(nmFFmpegVideoWriter::isSupportedExtension(
|
|
|
sExistingExtension)) {
|
|
|
sNormalizedPath.chop(sExistingExtension.length());
|
|
|
sNormalizedPath.append(sExtension);
|
|
|
} else {
|
|
|
sNormalizedPath.append(
|
|
|
QString::fromLatin1(".%1").arg(sExtension));
|
|
|
}
|
|
|
return QDir::cleanPath(sNormalizedPath);
|
|
|
}
|
|
|
|
|
|
QString nmWxPebiResultAnimationExportDlg::getSuggestedTargetFilePath() const
|
|
|
{
|
|
|
QString sDirectory = ZxBaseUtil::getLastDir();
|
|
|
if(sDirectory.isEmpty() || !QDir(sDirectory).exists()) {
|
|
|
sDirectory = QDir::homePath();
|
|
|
}
|
|
|
return QDir(sDirectory).absoluteFilePath(
|
|
|
QString::fromLatin1("PEBI-result.%1").arg(
|
|
|
getSelectedVideoExtension()));
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::setTargetFilePath(
|
|
|
const QString& sPath)
|
|
|
{
|
|
|
const QString sDisplayPath = QDir::toNativeSeparators(sPath);
|
|
|
m_pTargetPathEdit->setText(sDisplayPath);
|
|
|
m_pTargetPathEdit->setToolTip(sDisplayPath);
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::showError(const QString& sError)
|
|
|
{
|
|
|
m_pErrorLabel->setText(sError);
|
|
|
m_pErrorLabel->show();
|
|
|
}
|
|
|
|
|
|
void nmWxPebiResultAnimationExportDlg::clearError()
|
|
|
{
|
|
|
m_pErrorLabel->clear();
|
|
|
m_pErrorLabel->hide();
|
|
|
}
|
|
|
|
|
|
int nmWxPebiResultAnimationExportDlg::normalizeEvenDimension(int nValue)
|
|
|
{
|
|
|
nValue = qBound(2, nValue, 8192);
|
|
|
if((nValue % 2) != 0) {
|
|
|
++nValue;
|
|
|
}
|
|
|
return qMin(nValue, 8192);
|
|
|
}
|