diff --git a/examples/transcoder/CMakeLists.txt b/examples/transcoder/CMakeLists.txt index c68266c..81ae559 100644 --- a/examples/transcoder/CMakeLists.txt +++ b/examples/transcoder/CMakeLists.txt @@ -18,6 +18,12 @@ set(PROJECT_SOURCES stautuswidget.hpp styleditemdelegate.cc styleditemdelegate.hpp + subtitleencodermodel.cc + subtitleencodermodel.hpp + subtitleencodertableview.cc + subtitleencodertableview.hpp + subtitleencoderwidget.cc + subtitleencoderwidget.hpp videoencoderwidget.cc videoencoderwidget.hpp) diff --git a/examples/transcoder/audioencodermodel.cc b/examples/transcoder/audioencodermodel.cc index a794d7f..1fa0a82 100644 --- a/examples/transcoder/audioencodermodel.cc +++ b/examples/transcoder/audioencodermodel.cc @@ -1,5 +1,9 @@ #include "audioencodermodel.hpp" +#include +#include +#include + class AudioEncoderModel::AudioEncoderModelPrivate { public: @@ -16,6 +20,8 @@ class AudioEncoderModel::AudioEncoderModelPrivate Ffmpeg::EncodeContexts encodeContexts; QStringList headers; + + QIcon removeIcon = qApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton); }; AudioEncoderModel::AudioEncoderModel(QObject *parent) @@ -54,10 +60,12 @@ auto AudioEncoderModel::data(const QModelIndex &index, int role) const -> QVaria case Qt::DisplayRole: case Qt::EditRole: { //双击为空需添加 switch (col) { - case Property::ID: return row; + case Property::ID: return data.streamIndex; + case Property::SourceInfo: return data.sourceInfo; case Property::Encoder: return data.codecInfo().displayName; case Property::ChannelLayout: return data.chLayout().channelName; case Property::Bitrate: return data.bitrate; + case Property::SampleRate: return data.sampleRate; case Property::Crf: return data.crf; case Property::Profile: { auto profile = data.profile(); @@ -69,6 +77,12 @@ auto AudioEncoderModel::data(const QModelIndex &index, int role) const -> QVaria default: break; } break; + case Qt::DecorationRole: + switch (col) { + case Property::Remove: return d_ptr->removeIcon; + default: break; + } + break; case Qt::UserRole: return QVariant::fromValue(data); } default: break; @@ -105,8 +119,16 @@ auto AudioEncoderModel::setData(const QModelIndex &index, const QVariant &value, } break; case Property::Bitrate: data.maxBitrate = data.minBitrate = data.bitrate = value.toInt(); + emit dataChanged(index, index); + break; + case Property::SampleRate: + data.sampleRate = value.toInt(); + emit dataChanged(index, index); + break; + case Property::Crf: + data.crf = value.toInt(); + emit dataChanged(index, index); break; - case Property::Crf: data.crf = value.toInt(); case Property::Profile: { auto profile = value.toInt(); if (data.profile().profile != profile) { @@ -129,8 +151,11 @@ auto AudioEncoderModel::flags(const QModelIndex &index) const -> Qt::ItemFlags return {}; } auto flags = QAbstractTableModel::flags(index); - if (index.column() != Property::ID) { - flags |= Qt::ItemIsEditable; + switch (index.column()) { + case Property::ID: + case Property::SourceInfo: + case Property::Remove: break; + default: flags |= Qt::ItemIsEditable; break; } return flags; } @@ -158,7 +183,14 @@ void AudioEncoderModel::setDatas(const Ffmpeg::EncodeContexts &encodeContexts) endResetModel(); } -Ffmpeg::EncodeContexts AudioEncoderModel::datas() const +auto AudioEncoderModel::datas() const -> Ffmpeg::EncodeContexts { return d_ptr->encodeContexts; } + +void AudioEncoderModel::remove(const QModelIndex &index) +{ + beginRemoveRows(index.parent(), index.row(), index.row()); + d_ptr->encodeContexts.removeAt(index.row()); + endRemoveRows(); +} diff --git a/examples/transcoder/audioencodermodel.hpp b/examples/transcoder/audioencodermodel.hpp index 67125bb..9c77548 100644 --- a/examples/transcoder/audioencodermodel.hpp +++ b/examples/transcoder/audioencodermodel.hpp @@ -9,7 +9,17 @@ class AudioEncoderModel : public QAbstractTableModel { Q_OBJECT public: - enum Property { ID, Encoder, ChannelLayout, Bitrate, Crf, Profile }; + enum Property { + ID, + SourceInfo, + Encoder, + ChannelLayout, + Bitrate, + SampleRate, + Crf, + Profile, + Remove + }; Q_ENUM(Property); explicit AudioEncoderModel(QObject *parent = nullptr); @@ -18,18 +28,21 @@ class AudioEncoderModel : public QAbstractTableModel [[nodiscard]] auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override; [[nodiscard]] auto columnCount(const QModelIndex &parent = QModelIndex()) const -> int override; - [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + [[nodiscard]] auto data(const QModelIndex &index, int role = Qt::DisplayRole) const + -> QVariant override; auto setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) -> bool override; - [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; + [[nodiscard]] auto flags(const QModelIndex &index) const -> Qt::ItemFlags override; - [[nodiscard]] QVariant headerData(int section, - Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; + [[nodiscard]] auto headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const -> QVariant override; void setDatas(const Ffmpeg::EncodeContexts &encodeContexts); - Ffmpeg::EncodeContexts datas() const; + [[nodiscard]] auto datas() const -> Ffmpeg::EncodeContexts; + + void remove(const QModelIndex &index); private: class AudioEncoderModelPrivate; diff --git a/examples/transcoder/audioencodertableview.cc b/examples/transcoder/audioencodertableview.cc index 9ce3521..e4ae36a 100644 --- a/examples/transcoder/audioencodertableview.cc +++ b/examples/transcoder/audioencodertableview.cc @@ -23,20 +23,53 @@ AudioEncoderTableView::AudioEncoderTableView(QWidget *parent) , d_ptr(new AudioEncoderTableViewPrivate(this)) { setupUI(); + buildConnect(); } -AudioEncoderTableView::~AudioEncoderTableView() {} +AudioEncoderTableView::~AudioEncoderTableView() = default; void AudioEncoderTableView::setDatas(const Ffmpeg::EncodeContexts &encodeContexts) { d_ptr->model->setDatas(encodeContexts); } -Ffmpeg::EncodeContexts AudioEncoderTableView::datas() const +auto AudioEncoderTableView::datas() const -> Ffmpeg::EncodeContexts { return d_ptr->model->datas(); } +void AudioEncoderTableView::onRemoved(const QModelIndex &index) +{ + d_ptr->model->remove(index); +} + +void AudioEncoderTableView::onResize() +{ + setColumnWidth(AudioEncoderModel::Property::ID, 50); + setColumnWidth(AudioEncoderModel::Property::SourceInfo, this->width() / 5 - 10); + setColumnWidth(AudioEncoderModel::Property::ChannelLayout, 100); + setColumnWidth(AudioEncoderModel::Property::Bitrate, 100); + setColumnWidth(AudioEncoderModel::Property::SampleRate, 100); + setColumnWidth(AudioEncoderModel::Property::Crf, 50); + setColumnWidth(AudioEncoderModel::Property::Profile, 100); + setColumnWidth(AudioEncoderModel::Property::Remove, 50); + + auto width = this->width() - columnWidth(AudioEncoderModel::Property::ID) + - columnWidth(AudioEncoderModel::Property::SourceInfo) + - columnWidth(AudioEncoderModel::Property::ChannelLayout) + - columnWidth(AudioEncoderModel::Property::Bitrate) + - columnWidth(AudioEncoderModel::Property::SampleRate) + - columnWidth(AudioEncoderModel::Property::Crf) + - columnWidth(AudioEncoderModel::Property::Profile) - 50 - 10; + setColumnWidth(AudioEncoderModel::Property::Encoder, width); +} + +void AudioEncoderTableView::resizeEvent(QResizeEvent *event) +{ + QTableView::resizeEvent(event); + QMetaObject::invokeMethod(this, &AudioEncoderTableView::onResize, Qt::QueuedConnection); +} + void AudioEncoderTableView::setupUI() { setModel(d_ptr->model); @@ -50,18 +83,24 @@ void AudioEncoderTableView::setupUI() horizontalHeader()->setDefaultSectionSize(120); horizontalHeader()->setMinimumSectionSize(60); horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); - horizontalHeader()->setSectionResizeMode(AudioEncoderModel::Property::Encoder, - QHeaderView::Stretch); setIconSize(QSize(20, 20)); setItemDelegateForColumn(AudioEncoderModel::Property::Encoder, new AudioEncoderDelegate(this)); setItemDelegateForColumn(AudioEncoderModel::Property::ChannelLayout, new ChannelLayoutDelegate(this)); + setItemDelegateForColumn(AudioEncoderModel::Property::SampleRate, new SampleRateDelegate(this)); setItemDelegateForColumn(AudioEncoderModel::Property::Profile, new ProfileDelegate(this)); + auto *removedDelegate = new RemovedDelegate(this); + connect(removedDelegate, &RemovedDelegate::removed, this, &AudioEncoderTableView::onRemoved); + setItemDelegateForColumn(AudioEncoderModel::Property::Remove, removedDelegate); +} - setColumnWidth(AudioEncoderModel::Property::ID, 50); - setColumnWidth(AudioEncoderModel::Property::ChannelLayout, 100); - setColumnWidth(AudioEncoderModel::Property::Bitrate, 100); - setColumnWidth(AudioEncoderModel::Property::Crf, 50); - setColumnWidth(AudioEncoderModel::Property::Profile, 100); +void AudioEncoderTableView::buildConnect() +{ + connect( + d_ptr->model, + &AudioEncoderModel::dataChanged, + this, + [this] { update(); }, + Qt::QueuedConnection); } diff --git a/examples/transcoder/audioencodertableview.hpp b/examples/transcoder/audioencodertableview.hpp index bddd2cb..10b4212 100644 --- a/examples/transcoder/audioencodertableview.hpp +++ b/examples/transcoder/audioencodertableview.hpp @@ -7,15 +7,24 @@ class AudioEncoderTableView : public QTableView { + Q_OBJECT public: explicit AudioEncoderTableView(QWidget *parent = nullptr); ~AudioEncoderTableView() override; void setDatas(const Ffmpeg::EncodeContexts &encodeContexts); - Ffmpeg::EncodeContexts datas() const; + [[nodiscard]] auto datas() const -> Ffmpeg::EncodeContexts; + +private slots: + void onRemoved(const QModelIndex &index); + void onResize(); + +protected: + void resizeEvent(QResizeEvent *event) override; private: void setupUI(); + void buildConnect(); class AudioEncoderTableViewPrivate; QScopedPointer d_ptr; diff --git a/examples/transcoder/audioencoderwidget.cc b/examples/transcoder/audioencoderwidget.cc index 6702810..833d567 100644 --- a/examples/transcoder/audioencoderwidget.cc +++ b/examples/transcoder/audioencoderwidget.cc @@ -1,9 +1,6 @@ #include "audioencoderwidget.hpp" #include "audioencodertableview.hpp" -#include -#include - #include class AudioEncoderWidget::AudioEncoderWidgetPrivate @@ -12,74 +9,13 @@ class AudioEncoderWidget::AudioEncoderWidgetPrivate explicit AudioEncoderWidgetPrivate(AudioEncoderWidget *q) : q_ptr(q) { - const auto *comboBoxStyleSheet = "QComboBox {combobox-popup:0;}"; - - audioEncoderCbx = new QComboBox(q_ptr); - audioEncoderCbx->setView(new QListView(audioEncoderCbx)); - audioEncoderCbx->setMaxVisibleItems(10); - audioEncoderCbx->setStyleSheet(comboBoxStyleSheet); - auto audioEncodercs = Ffmpeg::getCodecsInfo(AVMEDIA_TYPE_AUDIO, true); - for (const auto &codec : std::as_const(audioEncodercs)) { - audioEncoderCbx->addItem(codec.displayName, QVariant::fromValue(codec)); - if (codec.codecId == AV_CODEC_ID_AAC) { - audioEncoderCbx->setCurrentText(codec.displayName); - } - } - audioEncoderCbx->model()->sort(0); - - chLayoutCbx = new QComboBox(q_ptr); - chLayoutCbx->setView(new QListView(chLayoutCbx)); - chLayoutCbx->setMaxVisibleItems(10); - chLayoutCbx->setStyleSheet(comboBoxStyleSheet); - - crfSbx = new QSpinBox(q_ptr); - crfSbx->setToolTip( - QCoreApplication::translate("VideoEncoderWidgetPrivate", "smaller -> better")); - - profileCbx = new QComboBox(q_ptr); - profileCbx->setView(new QListView(profileCbx)); - profileCbx->setMaxVisibleItems(10); - profileCbx->setStyleSheet(comboBoxStyleSheet); - - const int defaultBitrate = 512 * 1000; - minBitrateSbx = new QSpinBox(q_ptr); - minBitrateSbx->setRange(0, INT_MAX); - minBitrateSbx->setValue(defaultBitrate); - maxBitrateSbx = new QSpinBox(q_ptr); - maxBitrateSbx->setRange(0, INT_MAX); - maxBitrateSbx->setValue(defaultBitrate); - audioEncoderTable = new AudioEncoderTableView(q_ptr); - - init(); - } - - void init() const - { - Ffmpeg::EncodeContext encodeParam; - - profileCbx->clear(); - - crfSbx->setRange(Ffmpeg::EncodeLimit::crf_min, Ffmpeg::EncodeLimit::crf_max); - crfSbx->setValue(encodeParam.crf); - } - - [[nodiscard]] auto currentCodecName() const -> QString - { - return audioEncoderCbx->currentData().value().name; } AudioEncoderWidget *q_ptr; - QComboBox *audioEncoderCbx; - QComboBox *chLayoutCbx; - QSpinBox *crfSbx; - QComboBox *profileCbx; - - QSpinBox *minBitrateSbx; - QSpinBox *maxBitrateSbx; - AudioEncoderTableView *audioEncoderTable; + Ffmpeg::EncodeContexts encodeContexts; }; AudioEncoderWidget::AudioEncoderWidget(QWidget *parent) @@ -87,14 +23,13 @@ AudioEncoderWidget::AudioEncoderWidget(QWidget *parent) , d_ptr(new AudioEncoderWidgetPrivate(this)) { setupUI(); - buildConnect(); - onEncoderChanged(); } AudioEncoderWidget::~AudioEncoderWidget() = default; void AudioEncoderWidget::setDecodeContext(const Ffmpeg::EncodeContexts &decodeContexts) { + d_ptr->encodeContexts = decodeContexts; d_ptr->audioEncoderTable->setDatas(decodeContexts); } @@ -103,54 +38,18 @@ auto AudioEncoderWidget::encodeContexts() const -> Ffmpeg::EncodeContexts return d_ptr->audioEncoderTable->datas(); } -void AudioEncoderWidget::onEncoderChanged() +void AudioEncoderWidget::onReset() { - d_ptr->profileCbx->clear(); - d_ptr->chLayoutCbx->clear(); - - QScopedPointer contextInfoPtr(new Ffmpeg::AVContextInfo); - if (!contextInfoPtr->initEncoder(d_ptr->currentCodecName())) { - return; - } - auto profiles = contextInfoPtr->profiles(); - for (const auto &profile : std::as_const(profiles)) { - d_ptr->profileCbx->addItem(profile.name, profile.profile); - } - auto chLayouts = Ffmpeg::getChLayouts(contextInfoPtr->chLayouts()); - for (const auto &chLayout : std::as_const(chLayouts)) { - d_ptr->chLayoutCbx->addItem(chLayout.channelName, chLayout.channel); - } - auto index = d_ptr->chLayoutCbx->findData(AV_CH_LAYOUT_STEREO); - d_ptr->chLayoutCbx->setCurrentIndex(index >= 0 ? index : 0); + d_ptr->audioEncoderTable->setDatas(d_ptr->encodeContexts); } void AudioEncoderWidget::setupUI() { - auto *invailedGroupBox = new QGroupBox(tr("Invalid setting"), this); - auto *invailedLayout = new QFormLayout(invailedGroupBox); - invailedLayout->addRow(tr("Crf:"), d_ptr->crfSbx); - invailedLayout->addRow(tr("Profile:"), d_ptr->profileCbx); - - auto *bitrateGroupBox = new QGroupBox(tr("Bitrate"), this); - auto *bitrateLayout = new QFormLayout(bitrateGroupBox); - bitrateLayout->addRow(tr("Min Bitrate:"), d_ptr->minBitrateSbx); - bitrateLayout->addRow(tr("Max Bitrate:"), d_ptr->maxBitrateSbx); - - auto *hLayout = new QHBoxLayout; - hLayout->addWidget(invailedGroupBox); - hLayout->addWidget(bitrateGroupBox); + auto *button = new QToolButton(this); + button->setText(tr("Reset")); + connect(button, &QToolButton::clicked, this, &AudioEncoderWidget::onReset); - auto *layout = new QFormLayout(this); - layout->addRow(tr("Encoder:"), d_ptr->audioEncoderCbx); - layout->addRow(tr("Channel Layout:"), d_ptr->chLayoutCbx); - layout->addRow(hLayout); - layout->addRow(d_ptr->audioEncoderTable); -} - -void AudioEncoderWidget::buildConnect() -{ - connect(d_ptr->audioEncoderCbx, - &QComboBox::currentIndexChanged, - this, - &AudioEncoderWidget::onEncoderChanged); + auto *layout = new QVBoxLayout(this); + layout->addWidget(button); + layout->addWidget(d_ptr->audioEncoderTable); } diff --git a/examples/transcoder/audioencoderwidget.hpp b/examples/transcoder/audioencoderwidget.hpp index 1c9ec2c..efa16e9 100644 --- a/examples/transcoder/audioencoderwidget.hpp +++ b/examples/transcoder/audioencoderwidget.hpp @@ -5,10 +5,6 @@ #include -extern "C" { -#include -} - class AudioEncoderWidget : public QWidget { Q_OBJECT @@ -20,11 +16,10 @@ class AudioEncoderWidget : public QWidget [[nodiscard]] auto encodeContexts() const -> Ffmpeg::EncodeContexts; private slots: - void onEncoderChanged(); + void onReset(); private: void setupUI(); - void buildConnect(); class AudioEncoderWidgetPrivate; QScopedPointer d_ptr; diff --git a/examples/transcoder/mainwindow.cc b/examples/transcoder/mainwindow.cc index 10e7c89..a9c1d64 100644 --- a/examples/transcoder/mainwindow.cc +++ b/examples/transcoder/mainwindow.cc @@ -4,6 +4,7 @@ #include "previewwidget.hpp" #include "sourcewidget.hpp" #include "stautuswidget.hpp" +#include "subtitleencoderwidget.hpp" #include "videoencoderwidget.hpp" #include @@ -34,12 +35,15 @@ class MainWindow::MainWindowPrivate previewWidget = new PreviewWidget(q_ptr); videoEncoderWidget = new VideoEncoderWidget(q_ptr); audioEncoderWidget = new AudioEncoderWidget(q_ptr); + subtitleEncoderWidget = new SubtitleEncoderWidget(q_ptr); tabWidget->addTab(previewWidget, QCoreApplication::translate("MainWindowPrivate", "Preview")); tabWidget->addTab(videoEncoderWidget, QCoreApplication::translate("MainWindowPrivate", "Video")); tabWidget->addTab(audioEncoderWidget, QCoreApplication::translate("MainWindowPrivate", "Audio")); + tabWidget->addTab(subtitleEncoderWidget, + QCoreApplication::translate("MainWindowPrivate", "Subtitle")); outPutWidget = new OutPutWidget(q_ptr); statusWidget = new StautusWidget(q_ptr); @@ -81,10 +85,11 @@ class MainWindow::MainWindowPrivate } } + auto fileName = QFileInfo(mediaInfo.url).fileName(); auto info = QCoreApplication::translate( "MainWindowPrivate", - "%1, Duration: %2, %3, %4x%5, %6FPS, %7 video, %8 audio, %9 subtitle") - .arg(QFileInfo(mediaInfo.url).fileName(), + "%1\nDuration: %2, %3, %4x%5, %6FPS, %7 video, %8 audio, %9 subtitle") + .arg(fileName, mediaInfo.durationText, format, QString::number(size.width()), @@ -95,6 +100,8 @@ class MainWindow::MainWindowPrivate QString::number(subtitleCount)); sourceWidget->setFileInfo(info); sourceWidget->setDuration(mediaInfo.duration / 1000); + + q_ptr->setWindowTitle(fileName); } MainWindow *q_ptr; @@ -106,6 +113,7 @@ class MainWindow::MainWindowPrivate PreviewWidget *previewWidget; VideoEncoderWidget *videoEncoderWidget; AudioEncoderWidget *audioEncoderWidget; + SubtitleEncoderWidget *subtitleEncoderWidget; OutPutWidget *outPutWidget; StautusWidget *statusWidget; @@ -236,17 +244,19 @@ void MainWindow::onProcessEvents() d_ptr->initUI(); auto decodeContexts = d_ptr->transcoder->decodeContexts(); - qDebug() << "decodeContexts:" << decodeContexts.size(); Ffmpeg::EncodeContexts audioDecodeContexts; Ffmpeg::EncodeContexts videoDecodeContexts; + Ffmpeg::EncodeContexts subtitleDecodeContexts; for (const auto &decodeContext : std::as_const(decodeContexts)) { switch (decodeContext.mediaType) { case AVMEDIA_TYPE_AUDIO: audioDecodeContexts.append(decodeContext); break; case AVMEDIA_TYPE_VIDEO: videoDecodeContexts.append(decodeContext); break; + case AVMEDIA_TYPE_SUBTITLE: subtitleDecodeContexts.append(decodeContext); break; default: break; } } d_ptr->audioEncoderWidget->setDecodeContext(audioDecodeContexts); + d_ptr->subtitleEncoderWidget->setDecodeContext(subtitleDecodeContexts); if (!videoDecodeContexts.isEmpty()) { d_ptr->videoEncoderWidget->setDecodeContext(videoDecodeContexts.first()); } diff --git a/examples/transcoder/outputwidget.hpp b/examples/transcoder/outputwidget.hpp index cd86133..e58ded3 100644 --- a/examples/transcoder/outputwidget.hpp +++ b/examples/transcoder/outputwidget.hpp @@ -11,7 +11,7 @@ class OutPutWidget : public QWidget ~OutPutWidget() override; void setOutputFileName(const QString &fileName); - auto outputFilePath() const -> QString; + [[nodiscard]] auto outputFilePath() const -> QString; private slots: void onBrowse(); diff --git a/examples/transcoder/sourcewidget.hpp b/examples/transcoder/sourcewidget.hpp index 388a297..1952264 100644 --- a/examples/transcoder/sourcewidget.hpp +++ b/examples/transcoder/sourcewidget.hpp @@ -17,7 +17,7 @@ class SourceWidget : public QWidget void setFileInfo(const QString &info); - auto range() const -> QPair; + [[nodiscard]] auto range() const -> QPair; private slots: void onRangeChanged(); diff --git a/examples/transcoder/stautuswidget.hpp b/examples/transcoder/stautuswidget.hpp index ade890f..2db1d6e 100644 --- a/examples/transcoder/stautuswidget.hpp +++ b/examples/transcoder/stautuswidget.hpp @@ -11,7 +11,7 @@ class StautusWidget : public QWidget ~StautusWidget() override; void setStatus(const QString &status); - auto status() const -> QString; + [[nodiscard]] auto status() const -> QString; void setProgress(int progress); diff --git a/examples/transcoder/styleditemdelegate.cc b/examples/transcoder/styleditemdelegate.cc index 494a64e..ab84a03 100644 --- a/examples/transcoder/styleditemdelegate.cc +++ b/examples/transcoder/styleditemdelegate.cc @@ -5,7 +5,7 @@ #include -QComboBox *createComboBox(QWidget *parent) +auto createComboBox(QWidget *parent) -> QComboBox * { const auto *comboBoxStyleSheet = "QComboBox {combobox-popup:0;}"; auto *comboBox = new QComboBox(parent); @@ -105,3 +105,52 @@ void ProfileDelegate::setModelData(QWidget *editor, auto *comboBox = qobject_cast(editor); model->setData(index, comboBox->currentData(), Qt::EditRole); } + +SampleRateDelegate::SampleRateDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{} + +auto SampleRateDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &, + const QModelIndex &index) const -> QWidget * +{ + auto *comboBox = createComboBox(parent); + auto data = index.data(Qt::UserRole).value(); + for (const auto &sampleRate : std::as_const(data.sampleRates)) { + comboBox->addItem(QString::number(sampleRate), sampleRate); + } + return comboBox; +} + +void SampleRateDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + auto *comboBox = qobject_cast(editor); + comboBox->setCurrentText(index.data(Qt::EditRole).toString()); +} + +void SampleRateDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ + auto *comboBox = qobject_cast(editor); + model->setData(index, comboBox->currentData(), Qt::EditRole); +} + +RemovedDelegate::RemovedDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{} + +bool RemovedDelegate::editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + if (event->type() == QEvent::MouseButtonRelease) { + auto e = static_cast(event); + if (e) { + emit removed(index); + qDebug() << "remove"; + } + } + return QStyledItemDelegate::editorEvent(event, model, option, index); +} diff --git a/examples/transcoder/styleditemdelegate.hpp b/examples/transcoder/styleditemdelegate.hpp index 2bf9270..c1037e9 100644 --- a/examples/transcoder/styleditemdelegate.hpp +++ b/examples/transcoder/styleditemdelegate.hpp @@ -45,4 +45,32 @@ class ProfileDelegate : public QStyledItemDelegate const QModelIndex &index) const Q_DECL_OVERRIDE; }; +class SampleRateDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit SampleRateDelegate(QObject *parent = nullptr); + + auto createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const + -> QWidget *Q_DECL_OVERRIDE; + void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE; + void setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const Q_DECL_OVERRIDE; +}; + +class RemovedDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit RemovedDelegate(QObject *parent = nullptr); + + auto editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) -> bool override; +signals: + void removed(const QModelIndex &index); +}; + #endif // STYLEDITEMDELEGATE_HPP diff --git a/examples/transcoder/subtitleencodermodel.cc b/examples/transcoder/subtitleencodermodel.cc new file mode 100644 index 0000000..7fdf38e --- /dev/null +++ b/examples/transcoder/subtitleencodermodel.cc @@ -0,0 +1,180 @@ +#include "subtitleencodermodel.hpp" + +#include +#include +#include + +class SubtitleEncoderModel::SubtitleEncoderModelPrivate +{ +public: + explicit SubtitleEncoderModelPrivate(SubtitleEncoderModel *q) + : q_ptr(q) + { + auto metaEnums = QMetaEnum::fromType(); + for (int i = 0; i < metaEnums.keyCount(); i++) { + headers.append(metaEnums.key(i)); + } + } + + SubtitleEncoderModel *q_ptr; + + Ffmpeg::EncodeContexts encodeContexts; + QStringList headers; + + QIcon removeIcon = qApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton); +}; + +SubtitleEncoderModel::SubtitleEncoderModel(QObject *parent) + : QAbstractTableModel{parent} + , d_ptr(new SubtitleEncoderModelPrivate(this)) +{} + +SubtitleEncoderModel::~SubtitleEncoderModel() = default; + +auto SubtitleEncoderModel::rowCount(const QModelIndex &parent) const -> int +{ + Q_UNUSED(parent); + return d_ptr->encodeContexts.size(); +} + +auto SubtitleEncoderModel::columnCount(const QModelIndex &parent) const -> int +{ + Q_UNUSED(parent); + return d_ptr->headers.size(); +} + +auto SubtitleEncoderModel::data(const QModelIndex &index, int role) const -> QVariant +{ + if (!index.isValid()) { + return {}; + } + + auto row = index.row(); + auto col = index.column(); + + const auto &data = d_ptr->encodeContexts.at(row); + switch (role) { + case Qt::TextAlignmentRole: return Qt::AlignCenter; + case Qt::WhatsThisRole: + case Qt::ToolTipRole: + case Qt::DisplayRole: + case Qt::EditRole: { //双击为空需添加 + switch (col) { + case Property::ID: return data.streamIndex; + case Property::SourceInfo: return data.sourceInfo; + default: break; + } + break; + case Qt::CheckStateRole: + switch (col) { + case Property::Burn: return data.burn ? Qt::Checked : Qt::Unchecked; + default: break; + } + break; + case Qt::DecorationRole: + switch (col) { + case Property::Remove: return d_ptr->removeIcon; + default: break; + } + break; + case Qt::UserRole: return QVariant::fromValue(data); + } + default: break; + } + return {}; +} + +auto SubtitleEncoderModel::setData(const QModelIndex &index, const QVariant &value, int role) + -> bool +{ + if (!index.isValid()) { + return false; + } + + auto row = index.row(); + auto col = index.column(); + + auto &data = d_ptr->encodeContexts[row]; + switch (role) { + case Qt::CheckStateRole: { + switch (col) { + case Property::Burn: + data.burn = value.toBool(); + emit dataChanged(index, index); + break; + default: break; + } + } break; + default: break; + } + + return false; +} + +auto SubtitleEncoderModel::flags(const QModelIndex &index) const -> Qt::ItemFlags +{ + if (!index.isValid()) { + return {}; + } + auto flags = QAbstractTableModel::flags(index); + switch (index.column()) { + case Property::Burn: flags |= Qt::ItemIsUserCheckable; break; + default: break; + } + return flags; +} + +auto SubtitleEncoderModel::headerData(int section, Qt::Orientation orientation, int role) const + -> QVariant +{ + if (section < 0 || section >= d_ptr->headers.size() || orientation != Qt::Horizontal) { + return {}; + } + switch (role) { + case Qt::TextAlignmentRole: return Qt::AlignCenter; + case Qt::WhatsThisRole: + case Qt::ToolTipRole: + case Qt::DisplayRole: return d_ptr->headers.at(section); + default: break; + } + return {}; +} + +void SubtitleEncoderModel::setDatas(const Ffmpeg::EncodeContexts &encodeContexts) +{ + beginResetModel(); + d_ptr->encodeContexts = encodeContexts; + endResetModel(); +} + +void SubtitleEncoderModel::append(const Ffmpeg::EncodeContexts &encodeContexts) +{ + beginInsertRows({}, + d_ptr->encodeContexts.size(), + d_ptr->encodeContexts.size() + encodeContexts.size() - 1); + d_ptr->encodeContexts.append(encodeContexts); + endInsertRows(); +} + +auto SubtitleEncoderModel::datas() const -> Ffmpeg::EncodeContexts +{ + return d_ptr->encodeContexts; +} + +void SubtitleEncoderModel::remove(const QModelIndex &index) +{ + beginRemoveRows(index.parent(), index.row(), index.row()); + d_ptr->encodeContexts.removeAt(index.row()); + endRemoveRows(); +} + +void SubtitleEncoderModel::setExclusive(int row) +{ + beginResetModel(); + for (int i = 0; i < d_ptr->encodeContexts.size(); i++) { + if (i != row) { + d_ptr->encodeContexts[i].burn = false; + } + } + endResetModel(); +} diff --git a/examples/transcoder/subtitleencodermodel.hpp b/examples/transcoder/subtitleencodermodel.hpp new file mode 100644 index 0000000..1f0391b --- /dev/null +++ b/examples/transcoder/subtitleencodermodel.hpp @@ -0,0 +1,45 @@ +#ifndef SUBTITLEENCODERMODEL_HPP +#define SUBTITLEENCODERMODEL_HPP + +#include + +#include + +class SubtitleEncoderModel : public QAbstractTableModel +{ + Q_OBJECT +public: + enum Property { ID, SourceInfo, Burn, Remove }; + Q_ENUM(Property); + + explicit SubtitleEncoderModel(QObject *parent = nullptr); + ~SubtitleEncoderModel() override; + + [[nodiscard]] auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override; + [[nodiscard]] auto columnCount(const QModelIndex &parent = QModelIndex()) const -> int override; + + [[nodiscard]] auto data(const QModelIndex &index, int role = Qt::DisplayRole) const + -> QVariant override; + auto setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) + -> bool override; + + [[nodiscard]] auto flags(const QModelIndex &index) const -> Qt::ItemFlags override; + + [[nodiscard]] auto headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const -> QVariant override; + + void setDatas(const Ffmpeg::EncodeContexts &encodeContexts); + void append(const Ffmpeg::EncodeContexts &encodeContexts); + [[nodiscard]] auto datas() const -> Ffmpeg::EncodeContexts; + + void remove(const QModelIndex &index); + + void setExclusive(int row); + +private: + class SubtitleEncoderModelPrivate; + QScopedPointer d_ptr; +}; + +#endif // SUBTITLEENCODERMODEL_HPP diff --git a/examples/transcoder/subtitleencodertableview.cc b/examples/transcoder/subtitleencodertableview.cc new file mode 100644 index 0000000..d6d79df --- /dev/null +++ b/examples/transcoder/subtitleencodertableview.cc @@ -0,0 +1,105 @@ +#include "subtitleencodertableview.hpp" +#include "styleditemdelegate.hpp" +#include "subtitleencodermodel.hpp" + +#include + +class SubtitleEncoderTableView::SubtitleEncoderTableViewPrivate +{ +public: + explicit SubtitleEncoderTableViewPrivate(SubtitleEncoderTableView *q) + : q_ptr(q) + { + model = new SubtitleEncoderModel(q_ptr); + } + + SubtitleEncoderTableView *q_ptr; + + SubtitleEncoderModel *model; +}; + +SubtitleEncoderTableView::SubtitleEncoderTableView(QWidget *parent) + : QTableView{parent} + , d_ptr(new SubtitleEncoderTableViewPrivate(this)) +{ + setupUI(); + buildConnect(); +} + +SubtitleEncoderTableView::~SubtitleEncoderTableView() = default; + +void SubtitleEncoderTableView::setDatas(const Ffmpeg::EncodeContexts &encodeContexts) +{ + d_ptr->model->setDatas(encodeContexts); +} + +void SubtitleEncoderTableView::append(const Ffmpeg::EncodeContexts &encodeContexts) +{ + d_ptr->model->append(encodeContexts); +} + +auto SubtitleEncoderTableView::datas() const -> Ffmpeg::EncodeContexts +{ + return d_ptr->model->datas(); +} + +void SubtitleEncoderTableView::onRemoved(const QModelIndex &index) +{ + d_ptr->model->remove(index); +} + +void SubtitleEncoderTableView::onDataChnged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QList &roles) +{ + if (topLeft.column() == SubtitleEncoderModel::Property::Burn) { + d_ptr->model->setExclusive(topLeft.row()); + } + update(); +} + +void SubtitleEncoderTableView::onResize() +{ + setColumnWidth(SubtitleEncoderModel::Property::ID, 50); + setColumnWidth(SubtitleEncoderModel::Property::Burn, 50); + setColumnWidth(SubtitleEncoderModel::Property::Remove, 50); + + auto width = this->width() - columnWidth(SubtitleEncoderModel::Property::ID) + - columnWidth(SubtitleEncoderModel::Property::Burn) - 50 - 10; + setColumnWidth(SubtitleEncoderModel::Property::SourceInfo, width); +} + +void SubtitleEncoderTableView::resizeEvent(QResizeEvent *event) +{ + QTableView::resizeEvent(event); + QMetaObject::invokeMethod(this, &SubtitleEncoderTableView::onResize, Qt::QueuedConnection); +} + +void SubtitleEncoderTableView::setupUI() +{ + setModel(d_ptr->model); + + setShowGrid(true); + setWordWrap(false); + setAlternatingRowColors(true); + verticalHeader()->setVisible(false); + verticalHeader()->setDefaultSectionSize(35); + horizontalHeader()->setStretchLastSection(true); + horizontalHeader()->setDefaultSectionSize(120); + horizontalHeader()->setMinimumSectionSize(60); + horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + setIconSize(QSize(20, 20)); + + auto *removedDelegate = new RemovedDelegate(this); + connect(removedDelegate, &RemovedDelegate::removed, this, &SubtitleEncoderTableView::onRemoved); + setItemDelegateForColumn(SubtitleEncoderModel::Property::Remove, removedDelegate); +} + +void SubtitleEncoderTableView::buildConnect() +{ + connect(d_ptr->model, + &SubtitleEncoderModel::dataChanged, + this, + &SubtitleEncoderTableView::onDataChnged, + Qt::QueuedConnection); +} diff --git a/examples/transcoder/subtitleencodertableview.hpp b/examples/transcoder/subtitleencodertableview.hpp new file mode 100644 index 0000000..6f5e7ed --- /dev/null +++ b/examples/transcoder/subtitleencodertableview.hpp @@ -0,0 +1,37 @@ +#ifndef SUBTITLEENCODERTABLEVIEW_HPP +#define SUBTITLEENCODERTABLEVIEW_HPP + +#include + +#include + +class SubtitleEncoderTableView : public QTableView +{ + Q_OBJECT +public: + explicit SubtitleEncoderTableView(QWidget *parent = nullptr); + ~SubtitleEncoderTableView() override; + + void setDatas(const Ffmpeg::EncodeContexts &encodeContexts); + void append(const Ffmpeg::EncodeContexts &encodeContexts); + [[nodiscard]] auto datas() const -> Ffmpeg::EncodeContexts; + +private slots: + void onDataChnged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QList &roles); + void onRemoved(const QModelIndex &index); + void onResize(); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private: + void setupUI(); + void buildConnect(); + + class SubtitleEncoderTableViewPrivate; + QScopedPointer d_ptr; +}; + +#endif // SUBTITLEENCODERTABLEVIEW_HPP diff --git a/examples/transcoder/subtitleencoderwidget.cc b/examples/transcoder/subtitleencoderwidget.cc new file mode 100644 index 0000000..578246e --- /dev/null +++ b/examples/transcoder/subtitleencoderwidget.cc @@ -0,0 +1,82 @@ +#include "subtitleencoderwidget.hpp" +#include "subtitleencodertableview.hpp" + +#include + +class SubtitleEncoderWidget::SubtitleEncoderWidgetPrivate +{ +public: + explicit SubtitleEncoderWidgetPrivate(SubtitleEncoderWidget *q) + : q_ptr(q) + { + subtitleEncoderTable = new SubtitleEncoderTableView(q_ptr); + } + + SubtitleEncoderWidget *q_ptr; + + SubtitleEncoderTableView *subtitleEncoderTable; + Ffmpeg::EncodeContexts encodeContexts; +}; + +SubtitleEncoderWidget::SubtitleEncoderWidget(QWidget *parent) + : QWidget{parent} + , d_ptr(new SubtitleEncoderWidgetPrivate(this)) +{ + setupUI(); +} + +SubtitleEncoderWidget::~SubtitleEncoderWidget() = default; + +void SubtitleEncoderWidget::setDecodeContext(const Ffmpeg::EncodeContexts &decodeContexts) +{ + d_ptr->encodeContexts = decodeContexts; + d_ptr->subtitleEncoderTable->setDatas(decodeContexts); +} + +auto SubtitleEncoderWidget::encodeContexts() const -> Ffmpeg::EncodeContexts +{ + return d_ptr->subtitleEncoderTable->datas(); +} + +void SubtitleEncoderWidget::onReset() +{ + d_ptr->subtitleEncoderTable->setDatas(d_ptr->encodeContexts); +} + +void SubtitleEncoderWidget::onLoad() +{ + const auto path = QStandardPaths::standardLocations(QStandardPaths::MoviesLocation) + .value(0, QDir::homePath()); + const auto filePath = QFileDialog::getOpenFileName(this, + tr("Open File"), + path, + tr("Subtitle (*.srt *.ass *.txt)")); + if (filePath.isEmpty()) { + return; + } + + Ffmpeg::EncodeContext encodeContext; + encodeContext.sourceInfo = filePath; + encodeContext.external = true; + d_ptr->subtitleEncoderTable->append({encodeContext}); +} + +void SubtitleEncoderWidget::setupUI() +{ + auto *resetButton = new QToolButton(this); + resetButton->setText(tr("Reset")); + connect(resetButton, &QToolButton::clicked, this, &SubtitleEncoderWidget::onReset); + + auto *loadButton = new QToolButton(this); + loadButton->setText(tr("Load Subtitle")); + connect(loadButton, &QToolButton::clicked, this, &SubtitleEncoderWidget::onLoad); + + auto *buttonLayout = new QHBoxLayout; + buttonLayout->addWidget(resetButton); + buttonLayout->addWidget(loadButton); + buttonLayout->addStretch(); + + auto *layout = new QVBoxLayout(this); + layout->addLayout(buttonLayout); + layout->addWidget(d_ptr->subtitleEncoderTable); +} diff --git a/examples/transcoder/subtitleencoderwidget.hpp b/examples/transcoder/subtitleencoderwidget.hpp new file mode 100644 index 0000000..67a764b --- /dev/null +++ b/examples/transcoder/subtitleencoderwidget.hpp @@ -0,0 +1,29 @@ +#ifndef SUBTITLEENCODERWIDGET_HPP +#define SUBTITLEENCODERWIDGET_HPP + +#include + +#include + +class SubtitleEncoderWidget : public QWidget +{ + Q_OBJECT +public: + explicit SubtitleEncoderWidget(QWidget *parent = nullptr); + ~SubtitleEncoderWidget() override; + + void setDecodeContext(const Ffmpeg::EncodeContexts &decodeContexts); + [[nodiscard]] auto encodeContexts() const -> Ffmpeg::EncodeContexts; + +private slots: + void onReset(); + void onLoad(); + +private: + void setupUI(); + + class SubtitleEncoderWidgetPrivate; + QScopedPointer d_ptr; +}; + +#endif // SUBTITLEENCODERWIDGET_HPP diff --git a/examples/transcoder/transcoder.pro b/examples/transcoder/transcoder.pro index 7ea79ed..7e87cf4 100644 --- a/examples/transcoder/transcoder.pro +++ b/examples/transcoder/transcoder.pro @@ -25,6 +25,9 @@ SOURCES += \ sourcewidget.cc \ stautuswidget.cc \ styleditemdelegate.cc \ + subtitleencodermodel.cc \ + subtitleencodertableview.cc \ + subtitleencoderwidget.cc \ videoencoderwidget.cc HEADERS += \ @@ -37,6 +40,9 @@ HEADERS += \ sourcewidget.hpp \ stautuswidget.hpp \ styleditemdelegate.hpp \ + subtitleencodermodel.hpp \ + subtitleencodertableview.hpp \ + subtitleencoderwidget.hpp \ videoencoderwidget.hpp DESTDIR = $$APP_OUTPUT_PATH diff --git a/examples/transcoder/videoencoderwidget.cc b/examples/transcoder/videoencoderwidget.cc index 11cfe26..7fc77f4 100644 --- a/examples/transcoder/videoencoderwidget.cc +++ b/examples/transcoder/videoencoderwidget.cc @@ -1,6 +1,7 @@ #include "videoencoderwidget.hpp" #include +#include #include #include @@ -176,7 +177,7 @@ void VideoEncoderWidget::onEncoderChanged() if (!contextInfoPtr->initEncoder(d_ptr->currentCodecName())) { return; } - auto profiles = contextInfoPtr->profiles(); + auto profiles = contextInfoPtr->codecCtx()->supportedProfiles(); for (const auto &profile : std::as_const(profiles)) { d_ptr->profileCbx->addItem(profile.name, profile.profile); } diff --git a/ffmpeg/avcontextinfo.cpp b/ffmpeg/avcontextinfo.cpp index 996b8f2..b16e130 100644 --- a/ffmpeg/avcontextinfo.cpp +++ b/ffmpeg/avcontextinfo.cpp @@ -255,19 +255,4 @@ auto AVContextInfo::pixfmt() const -> AVPixelFormat return d_ptr->codecCtx->avCodecCtx()->pix_fmt; } -auto AVContextInfo::quantizer() -> QPair -{ - return d_ptr->codecCtx->quantizer(); -} - -auto AVContextInfo::profiles() -> QVector -{ - return d_ptr->codecCtx->supportedProfiles(); -} - -auto AVContextInfo::chLayouts() const -> QVector -{ - return d_ptr->codecCtx->supportedChLayouts(); -} - } // namespace Ffmpeg diff --git a/ffmpeg/avcontextinfo.h b/ffmpeg/avcontextinfo.h index 08635ef..f83966b 100644 --- a/ffmpeg/avcontextinfo.h +++ b/ffmpeg/avcontextinfo.h @@ -58,10 +58,6 @@ class FFMPEG_EXPORT AVContextInfo : public QObject [[nodiscard]] auto gpuType() const -> GpuType; [[nodiscard]] auto pixfmt() const -> AVPixelFormat; - [[nodiscard]] auto quantizer() -> QPair; - [[nodiscard]] auto profiles() -> QVector; - [[nodiscard]] auto chLayouts() const -> QVector; - auto codecCtx() -> CodecContext *; private: diff --git a/ffmpeg/codeccontext.cpp b/ffmpeg/codeccontext.cpp index 2f77b4a..c46d5f6 100644 --- a/ffmpeg/codeccontext.cpp +++ b/ffmpeg/codeccontext.cpp @@ -64,7 +64,6 @@ class CodecContext::CodecContextPrivate const auto *profile = codec->profiles; while (profile != nullptr && profile->profile != AV_PROFILE_UNKNOWN) { supported_profiles.append(*profile); - qDebug() << profile->name << profile->profile; profile++; } } @@ -265,7 +264,7 @@ auto CodecContext::setParameters(const AVCodecParameters *par) -> bool ERROR_RETURN(ret) } -auto CodecContext::supportFrameRates() const -> QVector +auto CodecContext::supportedFrameRates() const -> QVector { return d_ptr->supported_framerates; } @@ -301,7 +300,7 @@ void CodecContext::setPixfmt(AVPixelFormat pixfmt) d_ptr->codecCtx->pix_fmt = d_ptr->supported_pix_fmts.first(); } -auto CodecContext::supportSampleRates() const -> QVector +auto CodecContext::supportedSampleRates() const -> QVector { return d_ptr->supported_samplerates; } @@ -369,6 +368,7 @@ void CodecContext::setEncodeParameters(const EncodeContext &encodeContext) av_channel_layout_from_mask(&chLayout, static_cast(encodeContext.chLayout().channel)); setChLayout(chLayout); + setSampleRate(encodeContext.sampleRate); d_ptr->initAudioEncoderOptions(encodeContext); } break; default: break; @@ -423,12 +423,12 @@ auto CodecContext::quantizer() const -> QPair return {d_ptr->codecCtx->qmin, d_ptr->codecCtx->qmax}; } -auto CodecContext::supportPixFmts() const -> QVector +auto CodecContext::supportedPixFmts() const -> QVector { return d_ptr->supported_pix_fmts; } -auto CodecContext::supportSampleFmts() const -> QVector +auto CodecContext::supportedSampleFmts() const -> QVector { return d_ptr->supported_sample_fmts; } diff --git a/ffmpeg/codeccontext.h b/ffmpeg/codeccontext.h index d7664f8..a888a62 100644 --- a/ffmpeg/codeccontext.h +++ b/ffmpeg/codeccontext.h @@ -1,6 +1,8 @@ #ifndef CODECCONTEXT_H #define CODECCONTEXT_H +#include "ffmepg_global.h" + #include extern "C" { @@ -16,7 +18,7 @@ struct EncodeContext; class Subtitle; class Packet; class Frame; -class CodecContext : public QObject +class FFMPEG_EXPORT CodecContext : public QObject { public: explicit CodecContext(const AVCodec *codec, QObject *parent = nullptr); @@ -26,16 +28,16 @@ class CodecContext : public QObject auto setParameters(const AVCodecParameters *par) -> bool; - [[nodiscard]] auto supportFrameRates() const -> QVector; + [[nodiscard]] auto supportedFrameRates() const -> QVector; void setFrameRate(const AVRational &frameRate); - [[nodiscard]] auto supportPixFmts() const -> QVector; + [[nodiscard]] auto supportedPixFmts() const -> QVector; void setPixfmt(AVPixelFormat pixfmt); - [[nodiscard]] auto supportSampleRates() const -> QVector; + [[nodiscard]] auto supportedSampleRates() const -> QVector; void setSampleRate(int sampleRate); - [[nodiscard]] auto supportSampleFmts() const -> QVector; + [[nodiscard]] auto supportedSampleFmts() const -> QVector; void setSampleFmt(AVSampleFormat sampleFmt); [[nodiscard]] auto supportedProfiles() const -> QVector; diff --git a/ffmpeg/encodecontext.cc b/ffmpeg/encodecontext.cc index 5060530..75bc660 100644 --- a/ffmpeg/encodecontext.cc +++ b/ffmpeg/encodecontext.cc @@ -1,21 +1,21 @@ #include "encodecontext.hpp" +#include "audioframeconverter.h" #include "avcontextinfo.h" #include "codeccontext.h" +#include "mediainfo.hpp" extern "C" { #include +#include } namespace Ffmpeg { -EncodeContext::EncodeContext(int streamIndex, AVContextInfo *info) +EncodeContext::EncodeContext(AVStream *stream, AVContextInfo *info) { auto *avCodecContext = info->codecCtx()->avCodecCtx(); const auto *codec = avCodecContext->codec; - setEncoderName(QString::fromUtf8(codec->name)); - setChannel(static_cast(avCodecContext->ch_layout.u.mask)); - setProfile(avCodecContext->profile); - this->streamIndex = streamIndex; + this->streamIndex = stream->index; mediaType = avCodecContext->codec_type; minBitrate = avCodecContext->rc_min_rate; maxBitrate = avCodecContext->rc_max_rate; @@ -24,33 +24,35 @@ EncodeContext::EncodeContext(int streamIndex, AVContextInfo *info) bitrate = 512000; } size = {avCodecContext->width, avCodecContext->height}; -} + sampleRate = avCodecContext->sample_rate; -void EncodeContext::setEncoderName(const QString &name) -{ - QScopedPointer contextInfoPtr(new AVContextInfo); - if (!contextInfoPtr->initEncoder(name)) { - return; + if (!setEncoderName(QString::fromUtf8(codec->name))) { + setEncoderName(codec->id); } + setChannel(static_cast(avCodecContext->ch_layout.u.mask)); + setProfile(avCodecContext->profile); - const auto *codec = contextInfoPtr->codecCtx()->avCodecCtx()->codec; - m_codecInfo.codecId = codec->id; - m_codecInfo.name = QString::fromUtf8(codec->name); - m_codecInfo.longName = QString::fromUtf8(codec->long_name); - m_codecInfo.displayName = QString("%1 (%2)").arg(m_codecInfo.longName, m_codecInfo.name); + sourceInfo = StreamInfo(stream).info(); +} - profiles = contextInfoPtr->profiles(); - if (!profiles.isEmpty()) { - m_profile = profiles.first(); +auto EncodeContext::setEncoderName(AVCodecID codecId) -> bool +{ + QScopedPointer contextInfoPtr(new AVContextInfo); + if (!contextInfoPtr->initEncoder(codecId)) { + return false; } + init(contextInfoPtr.data()); + return true; +} - chLayouts = Ffmpeg::getChLayouts(contextInfoPtr->chLayouts()); - auto index = chLayouts.indexOf({static_cast(AV_CH_LAYOUT_STEREO), {}}); - if (index >= 0) { - m_chLayout = chLayouts[index]; - } else if (!chLayouts.isEmpty()) { - m_chLayout = chLayouts.first(); +auto EncodeContext::setEncoderName(const QString &name) -> bool +{ + QScopedPointer contextInfoPtr(new AVContextInfo); + if (!contextInfoPtr->initEncoder(name)) { + return false; } + init(contextInfoPtr.data()); + return true; } void EncodeContext::setChannel(AVChannel channel) @@ -71,4 +73,39 @@ void EncodeContext::setProfile(int profile) } } +void EncodeContext::init(AVContextInfo *info) +{ + auto *codecContext = info->codecCtx(); + const auto *codec = codecContext->avCodecCtx()->codec; + m_codecInfo.codecId = codec->id; + m_codecInfo.name = QString::fromUtf8(codec->name); + m_codecInfo.longName = QString::fromUtf8(codec->long_name); + m_codecInfo.displayName = QString("%1 (%2)").arg(m_codecInfo.longName, m_codecInfo.name); + qInfo() << "init EncodeContext: " << m_codecInfo.displayName; + profiles = codecContext->supportedProfiles(); + if (!profiles.isEmpty()) { + m_profile = profiles.first(); + } + + chLayouts = Ffmpeg::getChLayouts(codecContext->supportedChLayouts()); + auto index = chLayouts.indexOf({static_cast(AV_CH_LAYOUT_STEREO), {}}); + if (index >= 0) { + m_chLayout = chLayouts[index]; + } else if (!chLayouts.isEmpty()) { + m_chLayout = chLayouts.first(); + } + + sampleRates = codecContext->supportedSampleRates(); + if (sampleRates.isEmpty()) { + sampleRates = {96000, 48000, 44100, 32000, 22050, 16000, 11025, 8000}; + } + if (!sampleRates.contains(sampleRate)) { + sampleRate = sampleRates.first(); + } + + if (codec->type == AVMEDIA_TYPE_AUDIO) { + qDebug() << "supported sample rates: " << sampleRates; + } +} + } // namespace Ffmpeg diff --git a/ffmpeg/encodecontext.hpp b/ffmpeg/encodecontext.hpp index a535e09..21e943d 100644 --- a/ffmpeg/encodecontext.hpp +++ b/ffmpeg/encodecontext.hpp @@ -10,6 +10,8 @@ extern "C" { #include } +struct AVStream; + namespace Ffmpeg { namespace EncodeLimit { @@ -40,9 +42,10 @@ static const QStringList tunes = QStringList{"film", struct FFMPEG_EXPORT EncodeContext { EncodeContext() = default; - explicit EncodeContext(int streamIndex, AVContextInfo *info); + explicit EncodeContext(AVStream *stream, AVContextInfo *info); - void setEncoderName(const QString &name); + auto setEncoderName(AVCodecID codecId) -> bool; + auto setEncoderName(const QString &name) -> bool; [[nodiscard]] auto codecInfo() const -> CodecInfo { return m_codecInfo; } void setChannel(AVChannel channel); @@ -51,6 +54,8 @@ struct FFMPEG_EXPORT EncodeContext void setProfile(int profile); [[nodiscard]] auto profile() const -> AVProfile { return m_profile; } + QString sourceInfo; + int streamIndex = -1; AVMediaType mediaType; @@ -59,20 +64,28 @@ struct FFMPEG_EXPORT EncodeContext qint64 bitrate = -1; int threadCount = -1; - - bool gpuDecode = true; - int crf = 18; + // video + bool gpuDecode = true; + QSize size = {-1, -1}; QString preset = "slow"; QString tune = "film"; - QSize size = {-1, -1}; + // audio + int sampleRate = -1; + + // subtitle + bool burn = false; + bool external = false; - ChLayouts chLayouts; QVector profiles; + ChLayouts chLayouts; + QVector sampleRates; private: + void init(AVContextInfo *info); + CodecInfo m_codecInfo; ChLayout m_chLayout; AVProfile m_profile; diff --git a/ffmpeg/transcoder.cc b/ffmpeg/transcoder.cc index 02656c7..82b7050 100644 --- a/ffmpeg/transcoder.cc +++ b/ffmpeg/transcoder.cc @@ -93,7 +93,7 @@ class Transcoder::TranscoderPrivate } } transContext->decContextInfoPtr = contextInfoPtr; - decodeContexts.append(EncodeContext{i, contextInfoPtr.data()}); + decodeContexts.append(EncodeContext{stream, contextInfoPtr.data()}); } inFormatContext->dumpFormat();