mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Merge pull request #5083 from zhaowenlan1779/video-dumping-update
video_core, citra_qt: Video dumping updates
This commit is contained in:
		
						commit
						9c7da35382
					
				
					 35 changed files with 2085 additions and 309 deletions
				
			
		|  | @ -162,6 +162,20 @@ add_executable(citra-qt | |||
|     util/util.h | ||||
| ) | ||||
| 
 | ||||
| if (ENABLE_FFMPEG_VIDEO_DUMPER) | ||||
|     target_sources(citra-qt PRIVATE | ||||
|         dumping/dumping_dialog.cpp | ||||
|         dumping/dumping_dialog.h | ||||
|         dumping/dumping_dialog.ui | ||||
|         dumping/option_set_dialog.cpp | ||||
|         dumping/option_set_dialog.h | ||||
|         dumping/option_set_dialog.ui | ||||
|         dumping/options_dialog.cpp | ||||
|         dumping/options_dialog.h | ||||
|         dumping/options_dialog.ui | ||||
|     ) | ||||
| endif() | ||||
| 
 | ||||
| file(GLOB COMPAT_LIST | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
|  |  | |||
|  | @ -93,6 +93,7 @@ void Config::ReadValues() { | |||
|     ReadMiscellaneousValues(); | ||||
|     ReadDebuggingValues(); | ||||
|     ReadWebServiceValues(); | ||||
|     ReadVideoDumpingValues(); | ||||
|     ReadUIValues(); | ||||
|     ReadUtilityValues(); | ||||
| } | ||||
|  | @ -492,6 +493,49 @@ void Config::ReadSystemValues() { | |||
|     qt_config->endGroup(); | ||||
| } | ||||
| 
 | ||||
| // Options for variable bit rate live streaming taken from here:
 | ||||
| // https://developers.google.com/media/vp9/live-encoding
 | ||||
| const QString DEFAULT_VIDEO_ENCODER_OPTIONS = | ||||
|     QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1"); | ||||
| const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QString{}; | ||||
| 
 | ||||
| void Config::ReadVideoDumpingValues() { | ||||
|     qt_config->beginGroup(QStringLiteral("VideoDumping")); | ||||
| 
 | ||||
|     Settings::values.output_format = | ||||
|         ReadSetting(QStringLiteral("output_format"), QStringLiteral("webm")) | ||||
|             .toString() | ||||
|             .toStdString(); | ||||
|     Settings::values.format_options = | ||||
|         ReadSetting(QStringLiteral("format_options")).toString().toStdString(); | ||||
| 
 | ||||
|     Settings::values.video_encoder = | ||||
|         ReadSetting(QStringLiteral("video_encoder"), QStringLiteral("libvpx-vp9")) | ||||
|             .toString() | ||||
|             .toStdString(); | ||||
| 
 | ||||
|     Settings::values.video_encoder_options = | ||||
|         ReadSetting(QStringLiteral("video_encoder_options"), DEFAULT_VIDEO_ENCODER_OPTIONS) | ||||
|             .toString() | ||||
|             .toStdString(); | ||||
| 
 | ||||
|     Settings::values.video_bitrate = | ||||
|         ReadSetting(QStringLiteral("video_bitrate"), 2500000).toULongLong(); | ||||
| 
 | ||||
|     Settings::values.audio_encoder = | ||||
|         ReadSetting(QStringLiteral("audio_encoder"), QStringLiteral("libvorbis")) | ||||
|             .toString() | ||||
|             .toStdString(); | ||||
|     Settings::values.audio_encoder_options = | ||||
|         ReadSetting(QStringLiteral("audio_encoder_options"), DEFAULT_AUDIO_ENCODER_OPTIONS) | ||||
|             .toString() | ||||
|             .toStdString(); | ||||
|     Settings::values.audio_bitrate = | ||||
|         ReadSetting(QStringLiteral("audio_bitrate"), 64000).toULongLong(); | ||||
| 
 | ||||
|     qt_config->endGroup(); | ||||
| } | ||||
| 
 | ||||
| void Config::ReadUIValues() { | ||||
|     qt_config->beginGroup(QStringLiteral("UI")); | ||||
| 
 | ||||
|  | @ -624,6 +668,7 @@ void Config::SaveValues() { | |||
|     SaveMiscellaneousValues(); | ||||
|     SaveDebuggingValues(); | ||||
|     SaveWebServiceValues(); | ||||
|     SaveVideoDumpingValues(); | ||||
|     SaveUIValues(); | ||||
|     SaveUtilityValues(); | ||||
| } | ||||
|  | @ -928,6 +973,33 @@ void Config::SaveSystemValues() { | |||
|     qt_config->endGroup(); | ||||
| } | ||||
| 
 | ||||
| void Config::SaveVideoDumpingValues() { | ||||
|     qt_config->beginGroup(QStringLiteral("VideoDumping")); | ||||
| 
 | ||||
|     WriteSetting(QStringLiteral("output_format"), | ||||
|                  QString::fromStdString(Settings::values.output_format), QStringLiteral("webm")); | ||||
|     WriteSetting(QStringLiteral("format_options"), | ||||
|                  QString::fromStdString(Settings::values.format_options)); | ||||
|     WriteSetting(QStringLiteral("video_encoder"), | ||||
|                  QString::fromStdString(Settings::values.video_encoder), | ||||
|                  QStringLiteral("libvpx-vp9")); | ||||
|     WriteSetting(QStringLiteral("video_encoder_options"), | ||||
|                  QString::fromStdString(Settings::values.video_encoder_options), | ||||
|                  DEFAULT_VIDEO_ENCODER_OPTIONS); | ||||
|     WriteSetting(QStringLiteral("video_bitrate"), | ||||
|                  static_cast<unsigned long long>(Settings::values.video_bitrate), 2500000); | ||||
|     WriteSetting(QStringLiteral("audio_encoder"), | ||||
|                  QString::fromStdString(Settings::values.audio_encoder), | ||||
|                  QStringLiteral("libvorbis")); | ||||
|     WriteSetting(QStringLiteral("audio_encoder_options"), | ||||
|                  QString::fromStdString(Settings::values.audio_encoder_options), | ||||
|                  DEFAULT_AUDIO_ENCODER_OPTIONS); | ||||
|     WriteSetting(QStringLiteral("audio_bitrate"), | ||||
|                  static_cast<unsigned long long>(Settings::values.audio_bitrate), 64000); | ||||
| 
 | ||||
|     qt_config->endGroup(); | ||||
| } | ||||
| 
 | ||||
| void Config::SaveUIValues() { | ||||
|     qt_config->beginGroup(QStringLiteral("UI")); | ||||
| 
 | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ private: | |||
|     void ReadUpdaterValues(); | ||||
|     void ReadUtilityValues(); | ||||
|     void ReadWebServiceValues(); | ||||
|     void ReadVideoDumpingValues(); | ||||
| 
 | ||||
|     void SaveValues(); | ||||
|     void SaveAudioValues(); | ||||
|  | @ -65,6 +66,7 @@ private: | |||
|     void SaveUpdaterValues(); | ||||
|     void SaveUtilityValues(); | ||||
|     void SaveWebServiceValues(); | ||||
|     void SaveVideoDumpingValues(); | ||||
| 
 | ||||
|     QVariant ReadSetting(const QString& name) const; | ||||
|     QVariant ReadSetting(const QString& name, const QVariant& default_value) const; | ||||
|  |  | |||
							
								
								
									
										220
									
								
								src/citra_qt/dumping/dumping_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								src/citra_qt/dumping/dumping_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,220 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QFileDialog> | ||||
| #include <QMessageBox> | ||||
| #include "citra_qt/dumping/dumping_dialog.h" | ||||
| #include "citra_qt/dumping/options_dialog.h" | ||||
| #include "citra_qt/uisettings.h" | ||||
| #include "core/settings.h" | ||||
| #include "ui_dumping_dialog.h" | ||||
| 
 | ||||
| DumpingDialog::DumpingDialog(QWidget* parent) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::DumpingDialog>()) { | ||||
| 
 | ||||
|     ui->setupUi(this); | ||||
| 
 | ||||
|     format_generic_options = VideoDumper::GetFormatGenericOptions(); | ||||
|     encoder_generic_options = VideoDumper::GetEncoderGenericOptions(); | ||||
| 
 | ||||
|     connect(ui->pathExplore, &QToolButton::clicked, this, &DumpingDialog::OnToolButtonClicked); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { | ||||
|         if (ui->pathLineEdit->text().isEmpty()) { | ||||
|             QMessageBox::critical(this, tr("Citra"), tr("Please specify the output path.")); | ||||
|             return; | ||||
|         } | ||||
|         ApplyConfiguration(); | ||||
|         accept(); | ||||
|     }); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &DumpingDialog::reject); | ||||
|     connect(ui->formatOptionsButton, &QToolButton::clicked, [this] { | ||||
|         OpenOptionsDialog(formats.at(ui->formatComboBox->currentData().toUInt()).options, | ||||
|                           format_generic_options, ui->formatOptionsLineEdit); | ||||
|     }); | ||||
|     connect(ui->videoEncoderOptionsButton, &QToolButton::clicked, [this] { | ||||
|         OpenOptionsDialog( | ||||
|             video_encoders.at(ui->videoEncoderComboBox->currentData().toUInt()).options, | ||||
|             encoder_generic_options, ui->videoEncoderOptionsLineEdit); | ||||
|     }); | ||||
|     connect(ui->audioEncoderOptionsButton, &QToolButton::clicked, [this] { | ||||
|         OpenOptionsDialog( | ||||
|             audio_encoders.at(ui->audioEncoderComboBox->currentData().toUInt()).options, | ||||
|             encoder_generic_options, ui->audioEncoderOptionsLineEdit); | ||||
|     }); | ||||
| 
 | ||||
|     SetConfiguration(); | ||||
| 
 | ||||
|     connect(ui->formatComboBox, qOverload<int>(&QComboBox::currentIndexChanged), [this] { | ||||
|         ui->pathLineEdit->setText(QString{}); | ||||
|         ui->formatOptionsLineEdit->clear(); | ||||
|         PopulateEncoders(); | ||||
|     }); | ||||
| 
 | ||||
|     connect(ui->videoEncoderComboBox, qOverload<int>(&QComboBox::currentIndexChanged), | ||||
|             [this] { ui->videoEncoderOptionsLineEdit->clear(); }); | ||||
|     connect(ui->audioEncoderComboBox, qOverload<int>(&QComboBox::currentIndexChanged), | ||||
|             [this] { ui->audioEncoderOptionsLineEdit->clear(); }); | ||||
| } | ||||
| 
 | ||||
| DumpingDialog::~DumpingDialog() = default; | ||||
| 
 | ||||
| QString DumpingDialog::GetFilePath() const { | ||||
|     return ui->pathLineEdit->text(); | ||||
| } | ||||
| 
 | ||||
| void DumpingDialog::Populate() { | ||||
|     formats = VideoDumper::ListFormats(); | ||||
|     video_encoders = VideoDumper::ListEncoders(AVMEDIA_TYPE_VIDEO); | ||||
|     audio_encoders = VideoDumper::ListEncoders(AVMEDIA_TYPE_AUDIO); | ||||
| 
 | ||||
|     // Check that these are not empty
 | ||||
|     QString missing; | ||||
|     if (formats.empty()) { | ||||
|         missing = tr("output formats"); | ||||
|     } | ||||
|     if (video_encoders.empty()) { | ||||
|         missing = tr("video encoders"); | ||||
|     } | ||||
|     if (audio_encoders.empty()) { | ||||
|         missing = tr("audio encoders"); | ||||
|     } | ||||
| 
 | ||||
|     if (!missing.isEmpty()) { | ||||
|         QMessageBox::critical(this, tr("Citra"), | ||||
|                               tr("Could not find any available %1.\nPlease check your FFmpeg " | ||||
|                                  "installation used for compilation.") | ||||
|                                   .arg(missing)); | ||||
|         reject(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Populate formats
 | ||||
|     for (std::size_t i = 0; i < formats.size(); ++i) { | ||||
|         const auto& format = formats[i]; | ||||
| 
 | ||||
|         // Check format: only formats that have video encoders and audio encoders are displayed
 | ||||
|         bool has_video = false; | ||||
|         for (const auto& video_encoder : video_encoders) { | ||||
|             if (format.supported_video_codecs.count(video_encoder.codec)) { | ||||
|                 has_video = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         if (!has_video) | ||||
|             continue; | ||||
| 
 | ||||
|         bool has_audio = false; | ||||
|         for (const auto& audio_encoder : audio_encoders) { | ||||
|             if (format.supported_audio_codecs.count(audio_encoder.codec)) { | ||||
|                 has_audio = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         if (!has_audio) | ||||
|             continue; | ||||
| 
 | ||||
|         ui->formatComboBox->addItem(tr("%1 (%2)").arg(QString::fromStdString(format.long_name), | ||||
|                                                       QString::fromStdString(format.name)), | ||||
|                                     static_cast<unsigned long long>(i)); | ||||
|         if (format.name == Settings::values.output_format) { | ||||
|             ui->formatComboBox->setCurrentIndex(ui->formatComboBox->count() - 1); | ||||
|         } | ||||
|     } | ||||
|     PopulateEncoders(); | ||||
| } | ||||
| 
 | ||||
| void DumpingDialog::PopulateEncoders() { | ||||
|     const auto& format = formats.at(ui->formatComboBox->currentData().toUInt()); | ||||
| 
 | ||||
|     ui->videoEncoderComboBox->clear(); | ||||
|     for (std::size_t i = 0; i < video_encoders.size(); ++i) { | ||||
|         const auto& video_encoder = video_encoders[i]; | ||||
|         if (!format.supported_video_codecs.count(video_encoder.codec)) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         ui->videoEncoderComboBox->addItem( | ||||
|             tr("%1 (%2)").arg(QString::fromStdString(video_encoder.long_name), | ||||
|                               QString::fromStdString(video_encoder.name)), | ||||
|             static_cast<unsigned long long>(i)); | ||||
|         if (video_encoder.name == Settings::values.video_encoder) { | ||||
|             ui->videoEncoderComboBox->setCurrentIndex(ui->videoEncoderComboBox->count() - 1); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ui->audioEncoderComboBox->clear(); | ||||
|     for (std::size_t i = 0; i < audio_encoders.size(); ++i) { | ||||
|         const auto& audio_encoder = audio_encoders[i]; | ||||
|         if (!format.supported_audio_codecs.count(audio_encoder.codec)) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         ui->audioEncoderComboBox->addItem( | ||||
|             tr("%1 (%2)").arg(QString::fromStdString(audio_encoder.long_name), | ||||
|                               QString::fromStdString(audio_encoder.name)), | ||||
|             static_cast<unsigned long long>(i)); | ||||
|         if (audio_encoder.name == Settings::values.audio_encoder) { | ||||
|             ui->audioEncoderComboBox->setCurrentIndex(ui->audioEncoderComboBox->count() - 1); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DumpingDialog::OnToolButtonClicked() { | ||||
|     const auto& format = formats.at(ui->formatComboBox->currentData().toUInt()); | ||||
| 
 | ||||
|     QString extensions; | ||||
|     for (const auto& ext : format.extensions) { | ||||
|         if (!extensions.isEmpty()) { | ||||
|             extensions.append(QLatin1Char{' '}); | ||||
|         } | ||||
|         extensions.append(QStringLiteral("*.%1").arg(QString::fromStdString(ext))); | ||||
|     } | ||||
| 
 | ||||
|     const auto path = QFileDialog::getSaveFileName( | ||||
|         this, tr("Select Video Output Path"), last_path, | ||||
|         tr("%1 (%2)").arg(QString::fromStdString(format.long_name), extensions)); | ||||
|     if (!path.isEmpty()) { | ||||
|         last_path = QFileInfo(ui->pathLineEdit->text()).path(); | ||||
|         ui->pathLineEdit->setText(path); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DumpingDialog::OpenOptionsDialog(const std::vector<VideoDumper::OptionInfo>& specific_options, | ||||
|                                       const std::vector<VideoDumper::OptionInfo>& generic_options, | ||||
|                                       QLineEdit* line_edit) { | ||||
|     OptionsDialog dialog(this, specific_options, generic_options, line_edit->text().toStdString()); | ||||
|     if (dialog.exec() != QDialog::DialogCode::Accepted) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     line_edit->setText(QString::fromStdString(dialog.GetCurrentValue())); | ||||
| } | ||||
| 
 | ||||
| void DumpingDialog::SetConfiguration() { | ||||
|     Populate(); | ||||
| 
 | ||||
|     ui->formatOptionsLineEdit->setText(QString::fromStdString(Settings::values.format_options)); | ||||
|     ui->videoEncoderOptionsLineEdit->setText( | ||||
|         QString::fromStdString(Settings::values.video_encoder_options)); | ||||
|     ui->audioEncoderOptionsLineEdit->setText( | ||||
|         QString::fromStdString(Settings::values.audio_encoder_options)); | ||||
|     last_path = UISettings::values.video_dumping_path; | ||||
|     ui->videoBitrateSpinBox->setValue(static_cast<int>(Settings::values.video_bitrate)); | ||||
|     ui->audioBitrateSpinBox->setValue(static_cast<int>(Settings::values.audio_bitrate)); | ||||
| } | ||||
| 
 | ||||
| void DumpingDialog::ApplyConfiguration() { | ||||
|     Settings::values.output_format = formats.at(ui->formatComboBox->currentData().toUInt()).name; | ||||
|     Settings::values.format_options = ui->formatOptionsLineEdit->text().toStdString(); | ||||
|     Settings::values.video_encoder = | ||||
|         video_encoders.at(ui->videoEncoderComboBox->currentData().toUInt()).name; | ||||
|     Settings::values.video_encoder_options = ui->videoEncoderOptionsLineEdit->text().toStdString(); | ||||
|     Settings::values.video_bitrate = ui->videoBitrateSpinBox->value(); | ||||
|     Settings::values.audio_encoder = | ||||
|         audio_encoders.at(ui->audioEncoderComboBox->currentData().toUInt()).name; | ||||
|     Settings::values.audio_encoder_options = ui->audioEncoderOptionsLineEdit->text().toStdString(); | ||||
|     Settings::values.audio_bitrate = ui->audioBitrateSpinBox->value(); | ||||
|     UISettings::values.video_dumping_path = last_path; | ||||
|     Settings::Apply(); | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/citra_qt/dumping/dumping_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/citra_qt/dumping/dumping_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QDialog> | ||||
| #include "core/dumping/ffmpeg_backend.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
| class DumpingDialog; | ||||
| } | ||||
| 
 | ||||
| class QLineEdit; | ||||
| 
 | ||||
| class DumpingDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit DumpingDialog(QWidget* parent); | ||||
|     ~DumpingDialog() override; | ||||
| 
 | ||||
|     QString GetFilePath() const; | ||||
|     void ApplyConfiguration(); | ||||
| 
 | ||||
| private: | ||||
|     void Populate(); | ||||
|     void PopulateEncoders(); | ||||
|     void SetConfiguration(); | ||||
|     void OnToolButtonClicked(); | ||||
|     void OpenOptionsDialog(const std::vector<VideoDumper::OptionInfo>& specific_options, | ||||
|                            const std::vector<VideoDumper::OptionInfo>& generic_options, | ||||
|                            QLineEdit* line_edit); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::DumpingDialog> ui; | ||||
| 
 | ||||
|     QString last_path; | ||||
| 
 | ||||
|     std::vector<VideoDumper::FormatInfo> formats; | ||||
|     std::vector<VideoDumper::OptionInfo> format_generic_options; | ||||
|     std::vector<VideoDumper::EncoderInfo> video_encoders; | ||||
|     std::vector<VideoDumper::EncoderInfo> audio_encoders; | ||||
|     std::vector<VideoDumper::OptionInfo> encoder_generic_options; | ||||
| }; | ||||
							
								
								
									
										213
									
								
								src/citra_qt/dumping/dumping_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/citra_qt/dumping/dumping_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,213 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>DumpingDialog</class> | ||||
|  <widget class="QDialog" name="DumpingDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>600</width> | ||||
|     <height>420</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Dump Video</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <widget class="QGroupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Output</string> | ||||
|      </property> | ||||
|      <layout class="QGridLayout"> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Format:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QComboBox" name="formatComboBox"/> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Options:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="1"> | ||||
|        <widget class="QLineEdit" name="formatOptionsLineEdit"/> | ||||
|       </item> | ||||
|       <item row="1" column="2"> | ||||
|        <widget class="QToolButton" name="formatOptionsButton"> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Path:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QLineEdit" name="pathLineEdit"/> | ||||
|       </item> | ||||
|       <item row="2" column="2"> | ||||
|        <widget class="QToolButton" name="pathExplore"> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Video</string> | ||||
|      </property> | ||||
|      <layout class="QGridLayout"> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Encoder:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QComboBox" name="videoEncoderComboBox"> | ||||
|         <property name="sizePolicy"> | ||||
|          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> | ||||
|           <horstretch>0</horstretch> | ||||
|           <verstretch>0</verstretch> | ||||
|          </sizepolicy> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Options:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="1"> | ||||
|        <widget class="QLineEdit" name="videoEncoderOptionsLineEdit"/> | ||||
|       </item> | ||||
|       <item row="1" column="2"> | ||||
|        <widget class="QToolButton" name="videoEncoderOptionsButton"> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Bitrate:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QSpinBox" name="videoBitrateSpinBox"> | ||||
|         <property name="maximum"> | ||||
|          <number>10000000</number> | ||||
|         </property> | ||||
|         <property name="singleStep"> | ||||
|          <number>1000</number> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="2"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>bps</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Audio</string> | ||||
|      </property> | ||||
|      <layout class="QGridLayout"> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Encoder:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QComboBox" name="audioEncoderComboBox"> | ||||
|         <property name="sizePolicy"> | ||||
|          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> | ||||
|           <horstretch>0</horstretch> | ||||
|           <verstretch>0</verstretch> | ||||
|          </sizepolicy> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Options:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="1"> | ||||
|        <widget class="QLineEdit" name="audioEncoderOptionsLineEdit"/> | ||||
|       </item> | ||||
|       <item row="1" column="2"> | ||||
|        <widget class="QToolButton" name="audioEncoderOptionsButton"> | ||||
|         <property name="text"> | ||||
|          <string>...</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Bitrate:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QSpinBox" name="audioBitrateSpinBox"> | ||||
|         <property name="maximum"> | ||||
|          <number>1000000</number> | ||||
|         </property> | ||||
|         <property name="singleStep"> | ||||
|          <number>100</number> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="2"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>bps</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
							
								
								
									
										299
									
								
								src/citra_qt/dumping/option_set_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								src/citra_qt/dumping/option_set_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,299 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| #include <QCheckBox> | ||||
| #include <QStringList> | ||||
| #include "citra_qt/dumping/option_set_dialog.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "ui_option_set_dialog.h" | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <libavutil/pixdesc.h> | ||||
| } | ||||
| 
 | ||||
| static const std::unordered_map<AVOptionType, const char*> TypeNameMap{{ | ||||
|     {AV_OPT_TYPE_BOOL, QT_TR_NOOP("boolean")}, | ||||
|     {AV_OPT_TYPE_FLAGS, QT_TR_NOOP("flags")}, | ||||
|     {AV_OPT_TYPE_DURATION, QT_TR_NOOP("duration")}, | ||||
|     {AV_OPT_TYPE_INT, QT_TR_NOOP("int")}, | ||||
|     {AV_OPT_TYPE_UINT64, QT_TR_NOOP("uint64")}, | ||||
|     {AV_OPT_TYPE_INT64, QT_TR_NOOP("int64")}, | ||||
|     {AV_OPT_TYPE_DOUBLE, QT_TR_NOOP("double")}, | ||||
|     {AV_OPT_TYPE_FLOAT, QT_TR_NOOP("float")}, | ||||
|     {AV_OPT_TYPE_RATIONAL, QT_TR_NOOP("rational")}, | ||||
|     {AV_OPT_TYPE_PIXEL_FMT, QT_TR_NOOP("pixel format")}, | ||||
|     {AV_OPT_TYPE_SAMPLE_FMT, QT_TR_NOOP("sample format")}, | ||||
|     {AV_OPT_TYPE_COLOR, QT_TR_NOOP("color")}, | ||||
|     {AV_OPT_TYPE_IMAGE_SIZE, QT_TR_NOOP("image size")}, | ||||
|     {AV_OPT_TYPE_STRING, QT_TR_NOOP("string")}, | ||||
|     {AV_OPT_TYPE_DICT, QT_TR_NOOP("dictionary")}, | ||||
|     {AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("video rate")}, | ||||
|     {AV_OPT_TYPE_CHANNEL_LAYOUT, QT_TR_NOOP("channel layout")}, | ||||
| }}; | ||||
| 
 | ||||
| static const std::unordered_map<AVOptionType, const char*> TypeDescriptionMap{{ | ||||
|     {AV_OPT_TYPE_DURATION, QT_TR_NOOP("[<hours (integer)>:][<minutes (integer):]<seconds " | ||||
|                                       "(decimal)> e.g. 03:00.5 (3min 500ms)")}, | ||||
|     {AV_OPT_TYPE_RATIONAL, QT_TR_NOOP("<num>/<den>")}, | ||||
|     {AV_OPT_TYPE_COLOR, QT_TR_NOOP("0xRRGGBBAA")}, | ||||
|     {AV_OPT_TYPE_IMAGE_SIZE, QT_TR_NOOP("<width>x<height>, or preset values like 'vga'.")}, | ||||
|     {AV_OPT_TYPE_DICT, | ||||
|      QT_TR_NOOP("Comma-splitted list of <key>=<value>. Do not put spaces.")}, | ||||
|     {AV_OPT_TYPE_VIDEO_RATE, QT_TR_NOOP("<num>/<den>, or preset values like 'pal'.")}, | ||||
|     {AV_OPT_TYPE_CHANNEL_LAYOUT, QT_TR_NOOP("Hexadecimal channel layout mask starting with '0x'.")}, | ||||
| }}; | ||||
| 
 | ||||
| /// Get the preset values of an option. returns {display value, real value}
 | ||||
| std::vector<std::pair<QString, QString>> GetPresetValues(const VideoDumper::OptionInfo& option) { | ||||
|     switch (option.type) { | ||||
|     case AV_OPT_TYPE_BOOL: { | ||||
|         return {{QObject::tr("auto"), QStringLiteral("auto")}, | ||||
|                 {QObject::tr("true"), QStringLiteral("true")}, | ||||
|                 {QObject::tr("false"), QStringLiteral("false")}}; | ||||
|     } | ||||
|     case AV_OPT_TYPE_PIXEL_FMT: { | ||||
|         std::vector<std::pair<QString, QString>> out{{QObject::tr("none"), QStringLiteral("none")}}; | ||||
|         // List all pixel formats
 | ||||
|         const AVPixFmtDescriptor* current = nullptr; | ||||
|         while ((current = av_pix_fmt_desc_next(current))) { | ||||
|             out.emplace_back(QString::fromUtf8(current->name), QString::fromUtf8(current->name)); | ||||
|         } | ||||
|         return out; | ||||
|     } | ||||
|     case AV_OPT_TYPE_SAMPLE_FMT: { | ||||
|         std::vector<std::pair<QString, QString>> out{{QObject::tr("none"), QStringLiteral("none")}}; | ||||
|         // List all sample formats
 | ||||
|         int current = 0; | ||||
|         while (true) { | ||||
|             const char* name = av_get_sample_fmt_name(static_cast<AVSampleFormat>(current)); | ||||
|             if (name == nullptr) | ||||
|                 break; | ||||
|             out.emplace_back(QString::fromUtf8(name), QString::fromUtf8(name)); | ||||
|         } | ||||
|         return out; | ||||
|     } | ||||
|     case AV_OPT_TYPE_INT: | ||||
|     case AV_OPT_TYPE_INT64: | ||||
|     case AV_OPT_TYPE_UINT64: { | ||||
|         std::vector<std::pair<QString, QString>> out; | ||||
|         // Add in all named constants
 | ||||
|         for (const auto& constant : option.named_constants) { | ||||
|             out.emplace_back(QObject::tr("%1 (0x%2)") | ||||
|                                  .arg(QString::fromStdString(constant.name)) | ||||
|                                  .arg(constant.value, 0, 16), | ||||
|                              QString::fromStdString(constant.name)); | ||||
|         } | ||||
|         return out; | ||||
|     } | ||||
|     default: | ||||
|         return {}; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void OptionSetDialog::InitializeUI(const std::string& initial_value) { | ||||
|     const QString type_name = | ||||
|         TypeNameMap.count(option.type) ? tr(TypeNameMap.at(option.type)) : tr("unknown"); | ||||
|     ui->nameLabel->setText(tr("%1 <%2> %3") | ||||
|                                .arg(QString::fromStdString(option.name), type_name, | ||||
|                                     QString::fromStdString(option.description))); | ||||
|     if (TypeDescriptionMap.count(option.type)) { | ||||
|         ui->formatLabel->setVisible(true); | ||||
|         ui->formatLabel->setText(tr(TypeDescriptionMap.at(option.type))); | ||||
|     } | ||||
| 
 | ||||
|     if (option.type == AV_OPT_TYPE_INT || option.type == AV_OPT_TYPE_INT64 || | ||||
|         option.type == AV_OPT_TYPE_UINT64 || option.type == AV_OPT_TYPE_FLOAT || | ||||
|         option.type == AV_OPT_TYPE_DOUBLE || option.type == AV_OPT_TYPE_DURATION || | ||||
|         option.type == AV_OPT_TYPE_RATIONAL) { // scalar types
 | ||||
| 
 | ||||
|         ui->formatLabel->setVisible(true); | ||||
|         if (!ui->formatLabel->text().isEmpty()) { | ||||
|             ui->formatLabel->text().append(QStringLiteral("\n")); | ||||
|         } | ||||
|         ui->formatLabel->setText( | ||||
|             ui->formatLabel->text().append(tr("Range: %1 - %2").arg(option.min).arg(option.max))); | ||||
|     } | ||||
| 
 | ||||
|     // Decide and initialize layout
 | ||||
|     if (option.type == AV_OPT_TYPE_BOOL || option.type == AV_OPT_TYPE_PIXEL_FMT || | ||||
|         option.type == AV_OPT_TYPE_SAMPLE_FMT || | ||||
|         ((option.type == AV_OPT_TYPE_INT || option.type == AV_OPT_TYPE_INT64 || | ||||
|           option.type == AV_OPT_TYPE_UINT64) && | ||||
|          !option.named_constants.empty())) { // Use the combobox layout
 | ||||
| 
 | ||||
|         layout_type = 1; | ||||
|         ui->comboBox->setVisible(true); | ||||
|         ui->comboBoxHelpLabel->setVisible(true); | ||||
| 
 | ||||
|         QString real_initial_value = QString::fromStdString(initial_value); | ||||
|         if (option.type == AV_OPT_TYPE_INT || option.type == AV_OPT_TYPE_INT64 || | ||||
|             option.type == AV_OPT_TYPE_UINT64) { | ||||
| 
 | ||||
|             // Get the name of the initial value
 | ||||
|             try { | ||||
|                 s64 initial_value_integer = std::stoll(initial_value, nullptr, 0); | ||||
|                 for (const auto& constant : option.named_constants) { | ||||
|                     if (constant.value == initial_value_integer) { | ||||
|                         real_initial_value = QString::fromStdString(constant.name); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } catch (...) { | ||||
|                 // Not convertible to integer, ignore
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         bool found = false; | ||||
|         for (const auto& [display, value] : GetPresetValues(option)) { | ||||
|             ui->comboBox->addItem(display, value); | ||||
|             if (value == real_initial_value) { | ||||
|                 found = true; | ||||
|                 ui->comboBox->setCurrentIndex(ui->comboBox->count() - 1); | ||||
|             } | ||||
|         } | ||||
|         ui->comboBox->addItem(tr("custom")); | ||||
| 
 | ||||
|         if (!found) { | ||||
|             ui->comboBox->setCurrentIndex(ui->comboBox->count() - 1); | ||||
|             ui->lineEdit->setText(QString::fromStdString(initial_value)); | ||||
|         } | ||||
| 
 | ||||
|         UpdateUIDisplay(); | ||||
| 
 | ||||
|         connect(ui->comboBox, &QComboBox::currentTextChanged, this, | ||||
|                 &OptionSetDialog::UpdateUIDisplay); | ||||
|     } else if (option.type == AV_OPT_TYPE_FLAGS && | ||||
|                !option.named_constants.empty()) { // Use the check boxes layout
 | ||||
| 
 | ||||
|         layout_type = 2; | ||||
| 
 | ||||
|         for (const auto& constant : option.named_constants) { | ||||
|             auto* checkBox = new QCheckBox(tr("%1 (0x%2) %3") | ||||
|                                                .arg(QString::fromStdString(constant.name)) | ||||
|                                                .arg(constant.value, 0, 16) | ||||
|                                                .arg(QString::fromStdString(constant.description))); | ||||
|             checkBox->setProperty("value", static_cast<unsigned long long>(constant.value)); | ||||
|             checkBox->setProperty("name", QString::fromStdString(constant.name)); | ||||
|             ui->checkBoxLayout->addWidget(checkBox); | ||||
|         } | ||||
|         SetCheckBoxDefaults(initial_value); | ||||
|     } else { // Use the line edit layout
 | ||||
|         layout_type = 0; | ||||
|         ui->lineEdit->setVisible(true); | ||||
|         ui->lineEdit->setText(QString::fromStdString(initial_value)); | ||||
|     } | ||||
| 
 | ||||
|     adjustSize(); | ||||
| } | ||||
| 
 | ||||
| void OptionSetDialog::SetCheckBoxDefaults(const std::string& initial_value) { | ||||
|     if (initial_value.size() >= 2 && | ||||
|         (initial_value.substr(0, 2) == "0x" || initial_value.substr(0, 2) == "0X")) { | ||||
|         // This is a hex mask
 | ||||
|         try { | ||||
|             u64 value = std::stoull(initial_value, nullptr, 16); | ||||
|             for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { | ||||
|                 auto* checkBox = qobject_cast<QCheckBox*>(ui->checkBoxLayout->itemAt(i)->widget()); | ||||
|                 if (checkBox) { | ||||
|                     checkBox->setChecked(value & checkBox->property("value").toULongLong()); | ||||
|                 } | ||||
|             } | ||||
|         } catch (...) { | ||||
|             LOG_ERROR(Frontend, "Could not convert {} to number", initial_value); | ||||
|         } | ||||
|     } else { | ||||
|         // This is a combination of constants, splitted with + or |
 | ||||
|         std::vector<std::string> tmp; | ||||
|         Common::SplitString(initial_value, '+', tmp); | ||||
| 
 | ||||
|         std::vector<std::string> out; | ||||
|         std::vector<std::string> tmp2; | ||||
|         for (const auto& str : tmp) { | ||||
|             Common::SplitString(str, '|', tmp2); | ||||
|             out.insert(out.end(), tmp2.begin(), tmp2.end()); | ||||
|         } | ||||
|         for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { | ||||
|             auto* checkBox = qobject_cast<QCheckBox*>(ui->checkBoxLayout->itemAt(i)->widget()); | ||||
|             if (checkBox) { | ||||
|                 checkBox->setChecked( | ||||
|                     std::find(out.begin(), out.end(), | ||||
|                               checkBox->property("name").toString().toStdString()) != out.end()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void OptionSetDialog::UpdateUIDisplay() { | ||||
|     if (layout_type != 1) | ||||
|         return; | ||||
| 
 | ||||
|     if (ui->comboBox->currentIndex() == ui->comboBox->count() - 1) { // custom
 | ||||
|         ui->comboBoxHelpLabel->setVisible(false); | ||||
|         ui->lineEdit->setVisible(true); | ||||
|         adjustSize(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ui->lineEdit->setVisible(false); | ||||
|     for (const auto& constant : option.named_constants) { | ||||
|         if (constant.name == ui->comboBox->currentData().toString().toStdString()) { | ||||
|             ui->comboBoxHelpLabel->setVisible(true); | ||||
|             ui->comboBoxHelpLabel->setText(QString::fromStdString(constant.description)); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::pair<bool, std::string> OptionSetDialog::GetCurrentValue() { | ||||
|     if (!is_set) { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     switch (layout_type) { | ||||
|     case 0: // line edit layout
 | ||||
|         return {true, ui->lineEdit->text().toStdString()}; | ||||
|     case 1: // combo box layout
 | ||||
|         if (ui->comboBox->currentIndex() == ui->comboBox->count() - 1) { | ||||
|             return {true, ui->lineEdit->text().toStdString()}; // custom
 | ||||
|         } | ||||
|         return {true, ui->comboBox->currentData().toString().toStdString()}; | ||||
|     case 2: { // check boxes layout
 | ||||
|         std::string out; | ||||
|         for (int i = 0; i < ui->checkBoxLayout->count(); ++i) { | ||||
|             auto* checkBox = qobject_cast<QCheckBox*>(ui->checkBoxLayout->itemAt(i)->widget()); | ||||
|             if (checkBox && checkBox->isChecked()) { | ||||
|                 if (!out.empty()) { | ||||
|                     out.append("+"); | ||||
|                 } | ||||
|                 out.append(checkBox->property("name").toString().toStdString()); | ||||
|             } | ||||
|         } | ||||
|         if (out.empty()) { | ||||
|             out = "0x0"; | ||||
|         } | ||||
|         return {true, out}; | ||||
|     } | ||||
|     default: | ||||
|         return {}; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| OptionSetDialog::OptionSetDialog(QWidget* parent, VideoDumper::OptionInfo option_, | ||||
|                                  const std::string& initial_value) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::OptionSetDialog>()), option(std::move(option_)) { | ||||
| 
 | ||||
|     ui->setupUi(this); | ||||
|     InitializeUI(initial_value); | ||||
| 
 | ||||
|     connect(ui->unsetButton, &QPushButton::clicked, [this] { | ||||
|         is_set = false; | ||||
|         accept(); | ||||
|     }); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &OptionSetDialog::accept); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &OptionSetDialog::reject); | ||||
| } | ||||
| 
 | ||||
| OptionSetDialog::~OptionSetDialog() = default; | ||||
							
								
								
									
										33
									
								
								src/citra_qt/dumping/option_set_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/citra_qt/dumping/option_set_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QDialog> | ||||
| #include "core/dumping/ffmpeg_backend.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
| class OptionSetDialog; | ||||
| } | ||||
| 
 | ||||
| class OptionSetDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit OptionSetDialog(QWidget* parent, VideoDumper::OptionInfo option, | ||||
|                              const std::string& initial_value); | ||||
|     ~OptionSetDialog() override; | ||||
| 
 | ||||
|     // {is_set, value}
 | ||||
|     std::pair<bool, std::string> GetCurrentValue(); | ||||
| 
 | ||||
| private: | ||||
|     void InitializeUI(const std::string& initial_value); | ||||
|     void SetCheckBoxDefaults(const std::string& initial_value); | ||||
|     void UpdateUIDisplay(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::OptionSetDialog> ui; | ||||
|     VideoDumper::OptionInfo option; | ||||
|     bool is_set = true; | ||||
|     int layout_type = -1; // 0 - line edit, 1 - combo box, 2 - flags (check boxes)
 | ||||
| }; | ||||
							
								
								
									
										89
									
								
								src/citra_qt/dumping/option_set_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/citra_qt/dumping/option_set_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,89 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>OptionSetDialog</class> | ||||
|  <widget class="QDialog" name="OptionSetDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>600</width> | ||||
|     <height>150</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Options</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="nameLabel"/> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="formatLabel"> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <layout class="QVBoxLayout" name="comboBoxLayout"> | ||||
|      <item> | ||||
|       <widget class="QComboBox" name="comboBox"> | ||||
|        <property name="visible"> | ||||
|         <bool>false</bool> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="comboBoxHelpLabel"> | ||||
|        <property name="visible"> | ||||
|         <bool>false</bool> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLineEdit" name="lineEdit"> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <layout class="QVBoxLayout" name="checkBoxLayout"/> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Vertical</enum> | ||||
|      </property> | ||||
|     </spacer> | ||||
|    </item> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <widget class="QPushButton" name="unsetButton"> | ||||
|        <property name="text"> | ||||
|         <string>Unset</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <spacer> | ||||
|        <property name="orientation"> | ||||
|         <enum>Qt::Horizontal</enum> | ||||
|        </property> | ||||
|       </spacer> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|        <property name="standardButtons"> | ||||
|         <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
							
								
								
									
										68
									
								
								src/citra_qt/dumping/options_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/citra_qt/dumping/options_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QTreeWidgetItem> | ||||
| #include "citra_qt/dumping/option_set_dialog.h" | ||||
| #include "citra_qt/dumping/options_dialog.h" | ||||
| #include "ui_options_dialog.h" | ||||
| 
 | ||||
| constexpr char UNSET_TEXT[] = QT_TR_NOOP("[not set]"); | ||||
| 
 | ||||
| void OptionsDialog::PopulateOptions() { | ||||
|     const auto& options = ui->specificRadioButton->isChecked() ? specific_options : generic_options; | ||||
|     ui->main->clear(); | ||||
|     ui->main->setSortingEnabled(false); | ||||
|     for (std::size_t i = 0; i < options.size(); ++i) { | ||||
|         const auto& option = options.at(i); | ||||
|         auto* item = new QTreeWidgetItem( | ||||
|             {QString::fromStdString(option.name), QString::fromStdString(current_values.Get( | ||||
|                                                       option.name, tr(UNSET_TEXT).toStdString()))}); | ||||
|         item->setData(1, Qt::UserRole, static_cast<unsigned long long>(i)); // ID
 | ||||
|         ui->main->addTopLevelItem(item); | ||||
|     } | ||||
|     ui->main->setSortingEnabled(true); | ||||
|     ui->main->sortItems(0, Qt::AscendingOrder); | ||||
| } | ||||
| 
 | ||||
| void OptionsDialog::OnSetOptionValue(QTreeWidgetItem* item) { | ||||
|     const auto& options = ui->specificRadioButton->isChecked() ? specific_options : generic_options; | ||||
|     const int id = item->data(1, Qt::UserRole).toInt(); | ||||
|     OptionSetDialog dialog(this, options[id], | ||||
|                            current_values.Get(options[id].name, options[id].default_value)); | ||||
|     if (dialog.exec() != QDialog::DialogCode::Accepted) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto& [is_set, value] = dialog.GetCurrentValue(); | ||||
|     if (is_set) { | ||||
|         current_values.Set(options[id].name, value); | ||||
|     } else { | ||||
|         current_values.Erase(options[id].name); | ||||
|     } | ||||
|     item->setText(1, is_set ? QString::fromStdString(value) : tr(UNSET_TEXT)); | ||||
| } | ||||
| 
 | ||||
| std::string OptionsDialog::GetCurrentValue() const { | ||||
|     return current_values.Serialize(); | ||||
| } | ||||
| 
 | ||||
| OptionsDialog::OptionsDialog(QWidget* parent, | ||||
|                              std::vector<VideoDumper::OptionInfo> specific_options_, | ||||
|                              std::vector<VideoDumper::OptionInfo> generic_options_, | ||||
|                              const std::string& current_value) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::OptionsDialog>()), | ||||
|       specific_options(std::move(specific_options_)), generic_options(std::move(generic_options_)), | ||||
|       current_values(current_value) { | ||||
| 
 | ||||
|     ui->setupUi(this); | ||||
|     PopulateOptions(); | ||||
| 
 | ||||
|     connect(ui->main, &QTreeWidget::itemDoubleClicked, | ||||
|             [this](QTreeWidgetItem* item, int column) { OnSetOptionValue(item); }); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &OptionsDialog::accept); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &OptionsDialog::reject); | ||||
|     connect(ui->specificRadioButton, &QRadioButton::toggled, this, &OptionsDialog::PopulateOptions); | ||||
| } | ||||
| 
 | ||||
| OptionsDialog::~OptionsDialog() = default; | ||||
							
								
								
									
										36
									
								
								src/citra_qt/dumping/options_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/citra_qt/dumping/options_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <QDialog> | ||||
| #include "common/param_package.h" | ||||
| #include "core/dumping/ffmpeg_backend.h" | ||||
| 
 | ||||
| class QTreeWidgetItem; | ||||
| 
 | ||||
| namespace Ui { | ||||
| class OptionsDialog; | ||||
| } | ||||
| 
 | ||||
| class OptionsDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit OptionsDialog(QWidget* parent, std::vector<VideoDumper::OptionInfo> specific_options, | ||||
|                            std::vector<VideoDumper::OptionInfo> generic_options, | ||||
|                            const std::string& current_value); | ||||
|     ~OptionsDialog() override; | ||||
| 
 | ||||
|     std::string GetCurrentValue() const; | ||||
| 
 | ||||
| private: | ||||
|     void PopulateOptions(); | ||||
|     void OnSetOptionValue(QTreeWidgetItem* item); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::OptionsDialog> ui; | ||||
|     std::vector<VideoDumper::OptionInfo> specific_options; | ||||
|     std::vector<VideoDumper::OptionInfo> generic_options; | ||||
|     Common::ParamPackage current_values; | ||||
| }; | ||||
							
								
								
									
										71
									
								
								src/citra_qt/dumping/options_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/citra_qt/dumping/options_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>OptionsDialog</class> | ||||
|  <widget class="QDialog" name="OptionsDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>650</width> | ||||
|     <height>350</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Options</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <widget class="QLabel"> | ||||
|      <property name="wordWrap"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|      <property name="text"> | ||||
|       <string>Double click to see the description and change the values of the options.</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <widget class="QRadioButton" name="specificRadioButton"> | ||||
|        <property name="text"> | ||||
|         <string>Specific</string> | ||||
|        </property> | ||||
|        <property name="checked"> | ||||
|         <bool>true</bool> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QRadioButton" name="genericRadioButton"> | ||||
|        <property name="text"> | ||||
|         <string>Generic</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QTreeWidget" name="main"> | ||||
|      <column> | ||||
|       <property name="text"> | ||||
|        <string>Name</string> | ||||
|       </property> | ||||
|      </column> | ||||
|      <column> | ||||
|       <property name="text"> | ||||
|        <string>Value</string> | ||||
|       </property> | ||||
|      </column> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
|  | @ -87,6 +87,10 @@ | |||
| #include "citra_qt/discord_impl.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
| #include "citra_qt/dumping/dumping_dialog.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef QT_STATICPLUGIN | ||||
| Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); | ||||
| #endif | ||||
|  | @ -679,9 +683,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
|     connect(ui.action_Capture_Screenshot, &QAction::triggered, this, | ||||
|             &GMainWindow::OnCaptureScreenshot); | ||||
| 
 | ||||
| #ifndef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     ui.action_Dump_Video->setEnabled(false); | ||||
| #endif | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     connect(ui.action_Dump_Video, &QAction::triggered, [this] { | ||||
|         if (ui.action_Dump_Video->isChecked()) { | ||||
|             OnStartVideoDumping(); | ||||
|  | @ -689,6 +691,9 @@ void GMainWindow::ConnectMenuEvents() { | |||
|             OnStopVideoDumping(); | ||||
|         } | ||||
|     }); | ||||
| #else | ||||
|     ui.action_Dump_Video->setEnabled(false); | ||||
| #endif | ||||
| 
 | ||||
|     // Help
 | ||||
|     connect(ui.action_Open_Citra_Folder, &QAction::triggered, this, | ||||
|  | @ -975,8 +980,14 @@ void GMainWindow::BootGame(const QString& filename) { | |||
|     if (video_dumping_on_start) { | ||||
|         Layout::FramebufferLayout layout{ | ||||
|             Layout::FrameLayoutFromResolutionScale(VideoCore::GetResolutionScaleFactor())}; | ||||
|         Core::System::GetInstance().VideoDumper().StartDumping(video_dumping_path.toStdString(), | ||||
|                                                                "webm", layout); | ||||
|         if (!Core::System::GetInstance().VideoDumper().StartDumping( | ||||
|                 video_dumping_path.toStdString(), layout)) { | ||||
| 
 | ||||
|             QMessageBox::critical( | ||||
|                 this, tr("Citra"), | ||||
|                 tr("Could not start video dumping.<br>Refer to the log for details.")); | ||||
|             ui.action_Dump_Video->setChecked(false); | ||||
|         } | ||||
|         video_dumping_on_start = false; | ||||
|         video_dumping_path.clear(); | ||||
|     } | ||||
|  | @ -992,11 +1003,13 @@ void GMainWindow::ShutdownGame() { | |||
|         HideFullscreen(); | ||||
|     } | ||||
| 
 | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     if (Core::System::GetInstance().VideoDumper().IsDumping()) { | ||||
|         game_shutdown_delayed = true; | ||||
|         OnStopVideoDumping(); | ||||
|         return; | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     AllowOSSleep(); | ||||
| 
 | ||||
|  | @ -1804,18 +1817,23 @@ void GMainWindow::OnCaptureScreenshot() { | |||
|     OnStartGame(); | ||||
| } | ||||
| 
 | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
| void GMainWindow::OnStartVideoDumping() { | ||||
|     const QString path = QFileDialog::getSaveFileName( | ||||
|         this, tr("Save Video"), UISettings::values.video_dumping_path, tr("WebM Videos (*.webm)")); | ||||
|     if (path.isEmpty()) { | ||||
|     DumpingDialog dialog(this); | ||||
|     if (dialog.exec() != QDialog::DialogCode::Accepted) { | ||||
|         ui.action_Dump_Video->setChecked(false); | ||||
|         return; | ||||
|     } | ||||
|     UISettings::values.video_dumping_path = QFileInfo(path).path(); | ||||
|     const auto path = dialog.GetFilePath(); | ||||
|     if (emulation_running) { | ||||
|         Layout::FramebufferLayout layout{ | ||||
|             Layout::FrameLayoutFromResolutionScale(VideoCore::GetResolutionScaleFactor())}; | ||||
|         Core::System::GetInstance().VideoDumper().StartDumping(path.toStdString(), "webm", layout); | ||||
|         if (!Core::System::GetInstance().VideoDumper().StartDumping(path.toStdString(), layout)) { | ||||
|             QMessageBox::critical( | ||||
|                 this, tr("Citra"), | ||||
|                 tr("Could not start video dumping.<br>Refer to the log for details.")); | ||||
|             ui.action_Dump_Video->setChecked(false); | ||||
|         } | ||||
|     } else { | ||||
|         video_dumping_on_start = true; | ||||
|         video_dumping_path = path; | ||||
|  | @ -1832,6 +1850,8 @@ void GMainWindow::OnStopVideoDumping() { | |||
|         const bool was_dumping = Core::System::GetInstance().VideoDumper().IsDumping(); | ||||
|         if (!was_dumping) | ||||
|             return; | ||||
| 
 | ||||
|         game_paused_for_dumping = emu_thread->IsRunning(); | ||||
|         OnPauseGame(); | ||||
| 
 | ||||
|         auto future = | ||||
|  | @ -1841,13 +1861,15 @@ void GMainWindow::OnStopVideoDumping() { | |||
|             if (game_shutdown_delayed) { | ||||
|                 game_shutdown_delayed = false; | ||||
|                 ShutdownGame(); | ||||
|             } else { | ||||
|             } else if (game_paused_for_dumping) { | ||||
|                 game_paused_for_dumping = false; | ||||
|                 OnStartGame(); | ||||
|             } | ||||
|         }); | ||||
|         future_watcher->setFuture(future); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| void GMainWindow::UpdateStatusBar() { | ||||
|     if (emu_thread == nullptr) { | ||||
|  |  | |||
|  | @ -200,8 +200,10 @@ private slots: | |||
|     void OnPlayMovie(); | ||||
|     void OnStopRecordingPlayback(); | ||||
|     void OnCaptureScreenshot(); | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     void OnStartVideoDumping(); | ||||
|     void OnStopVideoDumping(); | ||||
| #endif | ||||
|     void OnCoreError(Core::System::ResultStatus, std::string); | ||||
|     /// Called whenever a user selects Help->About Citra
 | ||||
|     void OnMenuAboutCitra(); | ||||
|  | @ -256,6 +258,8 @@ private: | |||
|     QString video_dumping_path; | ||||
|     // Whether game shutdown is delayed due to video dumping
 | ||||
|     bool game_shutdown_delayed = false; | ||||
|     // Whether game was paused due to stopping video dumping
 | ||||
|     bool game_paused_for_dumping = false; | ||||
| 
 | ||||
|     // Debugger panes
 | ||||
|     ProfilerWidget* profilerWidget; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue