c2de3db345
Make it possible to build GUI with Qt 6 while keeping compatibility with 5.6.3.
2797 lines
98 KiB
C++
2797 lines
98 KiB
C++
#include "tsmuxerwindow.h"
|
|
|
|
#include <QColorDialog>
|
|
#include <QDebug>
|
|
#include <QDesktopServices>
|
|
#include <QDir>
|
|
#include <QDropEvent>
|
|
#include <QFileDialog>
|
|
#include <QFontDialog>
|
|
#include <QLibraryInfo>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QSettings>
|
|
#include <QStandardPaths>
|
|
#include <QTemporaryFile>
|
|
#include <QTime>
|
|
|
|
#include "checkboxedheaderview.h"
|
|
#include "codecinfo.h"
|
|
#include "fontsettingstablemodel.h"
|
|
#include "lang_codes.h"
|
|
#include "muxForm.h"
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
#define setTabStopDistance setTabStopWidth
|
|
#endif
|
|
|
|
#include "ui_tsmuxerwindow.h"
|
|
|
|
namespace
|
|
{
|
|
QString fileDialogFilter()
|
|
{
|
|
return TsMuxerWindow::tr(
|
|
"All supported media files (*.aac *.mpv *.mpa *.avc *.mvc *.264 *.h264 *.ac3 *.dts *.dtshd *.ts *.m2ts *.mts *.ssif *.mpg *.mpeg *.vob *.evo *.mkv *.mka *.mks *.mp4 *.m4a *.m4v *.mov *.sup *.wav *.w64 *.pcm *.m1v *.m2v *.vc1 *.hevc *.hvc *.265 *.h265 *.mpls *.mpl *.srt);;\
|
|
AC3/E-AC3 (*.ac3 *.ddp);;\
|
|
AAC (advanced audio coding) (*.aac);;\
|
|
AVC/MVC/H.264 elementary stream (*.avc *.mvc *.264 *.h264);;\
|
|
HEVC (High Efficiency Video Codec) (*.hevc *.hvc *.265 *.h265);;\
|
|
Digital Theater System (*.dts);;\
|
|
DTS-HD Master Audio (*.dtshd);;\
|
|
Mpeg video elementary stream (*.mpv *.m1v *.m2v);;\
|
|
Mpeg audio elementary stream (*.mpa);;\
|
|
Transport Stream (*.ts);;\
|
|
BDAV Transport Stream (*.m2ts *.mts *.ssif);;\
|
|
Program Stream (*.mpg *.mpeg *.vob *.evo);;\
|
|
Matroska audio/video files (*.mkv *.mka *.mks);;\
|
|
MP4 audio/video files (*.mp4 *.m4a *.m4v);;\
|
|
Quick time audio/video files (*.mov);;\
|
|
Blu-ray play list (*.mpls *.mpl);;\
|
|
Blu-ray PGS subtitles (*.sup);;\
|
|
Text subtitles (*.srt);;\
|
|
WAVE - Uncompressed PCM audio (*.wav *.w64);;\
|
|
RAW LPCM Stream (*.pcm);;\
|
|
All files (*.*)");
|
|
}
|
|
|
|
QString TI_DEFAULT_TAB_NAME() { return TsMuxerWindow::tr("General track options"); }
|
|
|
|
QString TI_DEMUX_TAB_NAME() { return TsMuxerWindow::tr("Demux options"); }
|
|
|
|
QString TS_SAVE_DIALOG_FILTER() { return TsMuxerWindow::tr("Transport stream (*.ts);;all files (*.*)"); }
|
|
|
|
QString M2TS_SAVE_DIALOG_FILTER() { return TsMuxerWindow::tr("BDAV Transport Stream (*.m2ts);;all files (*.*)"); }
|
|
|
|
QString ISO_SAVE_DIALOG_FILTER() { return TsMuxerWindow::tr("Disk image (*.iso);;all files (*.*)"); }
|
|
|
|
QSettings *settings = nullptr;
|
|
|
|
enum FileCustomData
|
|
{
|
|
MplsItemRole = Qt::UserRole,
|
|
FileNameRole,
|
|
ChaptersRole,
|
|
FileDurationRole
|
|
};
|
|
|
|
static const QString FILE_JOIN_PREFIX(" ++ ");
|
|
|
|
bool doubleCompare(double a, double b) { return qAbs(a - b) < 1e-6; }
|
|
|
|
QString closeDirPath(const QString &src)
|
|
{
|
|
if (src.isEmpty())
|
|
return src;
|
|
if (src[src.length() - 1] == '/' || src[src.length() - 1] == '\\')
|
|
return src;
|
|
return src + QDir::separator();
|
|
}
|
|
|
|
QString unquoteStr(QString val)
|
|
{
|
|
val = val.trimmed();
|
|
if (val.isEmpty())
|
|
return val;
|
|
if (val.at(0) == '\"')
|
|
{
|
|
if (val.right(1) == "\"")
|
|
return val.mid(1, val.length() - 2);
|
|
else
|
|
return val.mid(1, val.length() - 1);
|
|
}
|
|
else
|
|
{
|
|
if (val.right(1) == "\"")
|
|
return val.mid(0, val.length() - 1);
|
|
else
|
|
return val;
|
|
}
|
|
}
|
|
|
|
bool isVideoCodec(const QString &displayName)
|
|
{
|
|
return displayName == "H.264" || displayName == "MVC" || displayName == "VC-1" || displayName == "MPEG-2" ||
|
|
displayName == "HEVC";
|
|
}
|
|
|
|
QString floatToTime(double time, char msSeparator = '.')
|
|
{
|
|
int iTime = (int)time;
|
|
int hour = iTime / 3600;
|
|
iTime -= hour * 3600;
|
|
int min = iTime / 60;
|
|
iTime -= min * 60;
|
|
int sec = iTime;
|
|
int msec = (int)((time - (int)time) * 1000.0);
|
|
QString str;
|
|
str += QString::number(hour).rightJustified(2, '0');
|
|
str += ':';
|
|
str += QString::number(min).rightJustified(2, '0');
|
|
str += ':';
|
|
str += QString::number(sec).rightJustified(2, '0');
|
|
str += msSeparator;
|
|
str += QString::number(msec).rightJustified(3, '0');
|
|
return str;
|
|
}
|
|
|
|
QTime qTimeFromFloat(double secondsF)
|
|
{
|
|
int seconds = (int)secondsF;
|
|
int ms = (secondsF - seconds) * 1000.0;
|
|
int hours = seconds / 3600;
|
|
seconds -= hours * 3600;
|
|
int minute = seconds / 60;
|
|
seconds -= minute * 60;
|
|
return QTime(hours, minute, seconds, ms);
|
|
}
|
|
|
|
int qTimeToMsec(const QTime &time)
|
|
{
|
|
return (time.hour() * 3600 + time.minute() * 60 + time.second()) * 1000 + time.msec();
|
|
}
|
|
|
|
double timeToFloat(const QString &chapterStr)
|
|
{
|
|
if (chapterStr.size() == 0)
|
|
return 0;
|
|
QStringList timeParts = chapterStr.split(':');
|
|
double sec = 0;
|
|
if (timeParts.size() > 0)
|
|
sec = timeParts[timeParts.size() - 1].toDouble();
|
|
int min = 0;
|
|
if (timeParts.size() > 1)
|
|
min = timeParts[timeParts.size() - 2].toInt();
|
|
int hour = 0;
|
|
if (timeParts.size() > 2)
|
|
hour = timeParts[timeParts.size() - 3].toInt();
|
|
return hour * 3600 + min * 60 + sec;
|
|
}
|
|
|
|
QString changeFileExt(const QString &value, const QString &newExt)
|
|
{
|
|
QFileInfo fi(unquoteStr(value));
|
|
if (fi.suffix().length() > 0 || (!value.isEmpty() && value.right(1) == "."))
|
|
return unquoteStr(value.left(value.length() - fi.suffix().length()) + newExt);
|
|
else
|
|
return unquoteStr(value) + "." + newExt;
|
|
}
|
|
|
|
QString fpsTextToFpsStr(const QString &fpsText)
|
|
{
|
|
int p = fpsText.indexOf('/');
|
|
if (p >= 0)
|
|
{
|
|
auto left = fpsText.mid(0, p).toFloat();
|
|
auto right = fpsText.mid(p + 1).toFloat();
|
|
return QString::number(left / right, 'f', 3);
|
|
}
|
|
else
|
|
return fpsText;
|
|
}
|
|
|
|
float extractFloatFromDescr(const QString &str, const QString &pattern)
|
|
{
|
|
try
|
|
{
|
|
int p = 0;
|
|
if (!pattern.isEmpty())
|
|
p = str.indexOf(pattern);
|
|
if (p >= 0)
|
|
{
|
|
p += pattern.length();
|
|
int p2 = p;
|
|
while (p2 < str.length() &&
|
|
((str.at(p2) >= '0' && str.at(p2) <= '9') || str.at(p2) == '.' || str.at(p2) == '-'))
|
|
p2++;
|
|
return str.mid(p, p2 - p).toFloat();
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
QString quoteStr(const QString &val)
|
|
{
|
|
QString rez;
|
|
if (val.isEmpty())
|
|
return "";
|
|
if (val.at(0) != '\"')
|
|
rez = '\"' + val;
|
|
else
|
|
rez = val;
|
|
if (val.at(val.length() - 1) != '\"')
|
|
rez += '\"';
|
|
return rez;
|
|
}
|
|
|
|
QString myUnquoteStr(const QString &val) { return unquoteStr(val); }
|
|
|
|
QString getComboBoxTrackText(int idx, const QtvCodecInfo &codecInfo)
|
|
{
|
|
auto text = QString("[%1] %2").arg(idx + 1).arg(codecInfo.displayName);
|
|
if (!codecInfo.lang.isEmpty())
|
|
{
|
|
text.append(", lang : ");
|
|
text.append(codecInfo.lang);
|
|
}
|
|
text.append(", ");
|
|
text.append(codecInfo.descr);
|
|
return text;
|
|
}
|
|
|
|
void initLanguageComboBox(QComboBox *comboBox)
|
|
{
|
|
comboBox->addItem("English", "en"); // 0th index is also used as default if the language isn't set in the settings.
|
|
comboBox->addItem(QString::fromUtf8("Русский"), "ru");
|
|
comboBox->addItem(QString::fromUtf8("Français"), "fr");
|
|
comboBox->addItem(QString::fromUtf8("简体中文"), "zh");
|
|
comboBox->setCurrentIndex(-1); // makes sure currentIndexChanged() is emitted when reading settings.
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// ----------------------- TsMuxerWindow -------------------------------------
|
|
|
|
QString TsMuxerWindow::getOutputDir() const
|
|
{
|
|
QString result = ui->radioButtonOutoutInInput->isChecked() ? lastSourceDir : lastOutputDir;
|
|
if (!result.isEmpty())
|
|
result = QDir::toNativeSeparators(closeDirPath(result));
|
|
return result;
|
|
}
|
|
|
|
QString TsMuxerWindow::getDefaultOutputFileName() const
|
|
{
|
|
QString prefix = getOutputDir();
|
|
|
|
if (ui->radioButtonTS->isChecked())
|
|
return prefix + QString("default.ts");
|
|
else if (ui->radioButtonM2TS->isChecked())
|
|
return prefix + QString("default.m2ts");
|
|
else if (ui->radioButtonBluRayISO->isChecked())
|
|
return prefix + QString("default.iso");
|
|
else
|
|
return prefix;
|
|
}
|
|
|
|
TsMuxerWindow::TsMuxerWindow()
|
|
: ui(new Ui::TsMuxerWindow),
|
|
disableUpdatesCnt(0),
|
|
outFileNameModified(false),
|
|
outFileNameDisableChange(false),
|
|
muxForm(new MuxForm(this)),
|
|
m_updateMeta(true),
|
|
m_3dMode(false)
|
|
{
|
|
ui->setupUi(this);
|
|
setUiMetaItemsData();
|
|
qApp->installTranslator(&qtCoreTranslator);
|
|
qApp->installTranslator(&tsMuxerTranslator);
|
|
initLanguageComboBox(ui->languageSelectComboBox);
|
|
setWindowTitle("tsMuxeR GUI " TSMUXER_VERSION);
|
|
lastInputDir = QDir::homePath();
|
|
lastOutputDir = QDir::homePath();
|
|
|
|
void (QComboBox::*comboBoxIndexChanged)(int) = &QComboBox::currentIndexChanged;
|
|
connect(ui->languageSelectComboBox, comboBoxIndexChanged, this, &TsMuxerWindow::onLanguageComboBoxIndexChanged);
|
|
|
|
QString path = QFileInfo(QApplication::arguments()[0]).absolutePath();
|
|
QString iniName = QDir::toNativeSeparators(path) + QDir::separator() + QString("tsMuxerGUI.ini");
|
|
|
|
settings = new QSettings();
|
|
readSettings();
|
|
|
|
if (QFile::exists(iniName))
|
|
{
|
|
delete settings;
|
|
settings = new QSettings(iniName, QSettings::IniFormat);
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
settings->setIniCodec("UTF-8");
|
|
#endif
|
|
if (!readSettings())
|
|
writeSettings(); // copy current registry settings to the ini file
|
|
}
|
|
|
|
ui->outFileName->setText(getDefaultOutputFileName());
|
|
|
|
m_header = new QnCheckBoxedHeaderView(this);
|
|
ui->trackLV->setHorizontalHeader(m_header);
|
|
ui->trackLV->horizontalHeader()->setStretchLastSection(true);
|
|
// ui->trackLV->model()->setHeaderData(0, Qt::Vertical, "#");
|
|
m_header->setVisible(true);
|
|
m_header->setSectionsClickable(true);
|
|
|
|
connect(m_header, &QnCheckBoxedHeaderView::checkStateChanged, this, &TsMuxerWindow::at_sectionCheckstateChanged);
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
for (int i = 0; i <= 3600; i += 5 * 60) ui->memoChapters->insertPlainText(floatToTime(i, '.') + '\n');
|
|
|
|
mSaveDialogFilter = TS_SAVE_DIALOG_FILTER();
|
|
const static int colWidths[] = {28, 200, 62, 38, 10};
|
|
for (unsigned i = 0u; i < sizeof(colWidths) / sizeof(int); ++i)
|
|
ui->trackLV->horizontalHeader()->resizeSection(i, colWidths[i]);
|
|
ui->trackLV->setWordWrap(false);
|
|
|
|
fontSettingsModel = new FontSettingsTableModel(this);
|
|
ui->fontSettingsTableView->setModel(fontSettingsModel);
|
|
ui->fontSettingsTableView->horizontalHeader()->resizeSection(0, 65);
|
|
ui->fontSettingsTableView->horizontalHeader()->resizeSection(1, 185);
|
|
for (int i = 0; i < fontSettingsModel->rowCount(QModelIndex()); ++i)
|
|
{
|
|
ui->fontSettingsTableView->setRowHeight(i, 20);
|
|
}
|
|
|
|
void (QSpinBox::*spinBoxValueChanged)(int) = &QSpinBox::valueChanged;
|
|
void (QDoubleSpinBox::*doubleSpinBoxValueChanged)(double) = &QDoubleSpinBox::valueChanged;
|
|
connect(&opacityTimer, &QTimer::timeout, this, &TsMuxerWindow::onOpacityTimer);
|
|
connect(ui->trackLV, &QTableWidget::itemSelectionChanged, this, &TsMuxerWindow::trackLVItemSelectionChanged);
|
|
connect(ui->trackLV, &QTableWidget::itemChanged, this, &TsMuxerWindow::trackLVItemChanged);
|
|
connect(ui->inputFilesLV, &QListWidget::currentRowChanged, this, &TsMuxerWindow::inputFilesLVChanged);
|
|
connect(ui->addBtn, &QPushButton::clicked, this, &TsMuxerWindow::onAddBtnClick);
|
|
connect(ui->btnAppend, &QPushButton::clicked, this, &TsMuxerWindow::onAppendButtonClick);
|
|
connect(ui->removeFile, &QPushButton::clicked, this, &TsMuxerWindow::onRemoveBtnClick);
|
|
connect(ui->removeTrackBtn, &QPushButton::clicked, this, &TsMuxerWindow::onRemoveTrackButtonClick);
|
|
connect(ui->moveupBtn, &QPushButton::clicked, this, &TsMuxerWindow::onMoveUpButtonCLick);
|
|
connect(ui->movedownBtn, &QPushButton::clicked, this, &TsMuxerWindow::onMoveDownButtonCLick);
|
|
connect(ui->checkFPS, &QCheckBox::stateChanged, this, &TsMuxerWindow::onVideoCheckBoxChanged);
|
|
connect(ui->checkBoxLevel, &QCheckBox::stateChanged, this, &TsMuxerWindow::onVideoCheckBoxChanged);
|
|
connect(ui->comboBoxSEI, comboBoxIndexChanged, this, &TsMuxerWindow::onVideoCheckBoxChanged);
|
|
connect(ui->checkBoxSecondaryVideo, &QCheckBox::stateChanged, this, &TsMuxerWindow::onVideoCheckBoxChanged);
|
|
connect(ui->checkBoxSPS, &QCheckBox::stateChanged, this, &TsMuxerWindow::onVideoCheckBoxChanged);
|
|
connect(ui->checkBoxRemovePulldown, &QCheckBox::stateChanged, this, &TsMuxerWindow::onPulldownCheckBoxChanged);
|
|
connect(ui->comboBoxFPS, comboBoxIndexChanged, this, &TsMuxerWindow::onVideoComboBoxChanged);
|
|
connect(ui->comboBoxLevel, comboBoxIndexChanged, this, &TsMuxerWindow::onVideoComboBoxChanged);
|
|
connect(ui->comboBoxAR, comboBoxIndexChanged, this, &TsMuxerWindow::onVideoComboBoxChanged);
|
|
connect(ui->checkBoxKeepFps, &QCheckBox::stateChanged, this, &TsMuxerWindow::onAudioSubtitlesParamsChanged);
|
|
connect(ui->dtsDwnConvert, &QCheckBox::stateChanged, this, &TsMuxerWindow::onAudioSubtitlesParamsChanged);
|
|
connect(ui->secondaryCheckBox, &QCheckBox::stateChanged, this, &TsMuxerWindow::onAudioSubtitlesParamsChanged);
|
|
connect(ui->langComboBox, comboBoxIndexChanged, this, &TsMuxerWindow::onAudioSubtitlesParamsChanged);
|
|
connect(ui->offsetsComboBox, comboBoxIndexChanged, this, &TsMuxerWindow::onAudioSubtitlesParamsChanged);
|
|
connect(ui->comboBoxPipCorner, comboBoxIndexChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->comboBoxPipSize, comboBoxIndexChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->spinBoxPipOffsetH, spinBoxValueChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->spinBoxPipOffsetV, spinBoxValueChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->checkBoxSound, &QCheckBox::stateChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->editDelay, spinBoxValueChanged, this, &TsMuxerWindow::onEditDelayChanged);
|
|
connect(ui->muxTimeEdit, &QTimeEdit::timeChanged, this, &TsMuxerWindow::updateMuxTime1);
|
|
connect(ui->muxTimeClock, spinBoxValueChanged, this, &TsMuxerWindow::updateMuxTime2);
|
|
connect(ui->fontButton, &QPushButton::clicked, this, &TsMuxerWindow::onFontBtnClicked);
|
|
connect(ui->colorButton, &QPushButton::clicked, this, &TsMuxerWindow::onColorBtnClicked);
|
|
connect(ui->checkBoxVBR, &QPushButton::clicked, this, &TsMuxerWindow::onGeneralCheckboxClicked);
|
|
connect(ui->spinBoxMplsNum, spinBoxValueChanged, this, &TsMuxerWindow::onGeneralCheckboxClicked);
|
|
connect(ui->spinBoxM2tsNum, spinBoxValueChanged, this, &TsMuxerWindow::onGeneralCheckboxClicked);
|
|
connect(ui->checkBoxBlankPL, &QPushButton::clicked, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->checkBoxV3, &QCheckBox::stateChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
connect(ui->BlackplaylistCombo, spinBoxValueChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->checkBoxNewAudioPes, &QAbstractButton::clicked, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->checkBoxCrop, &QCheckBox::stateChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->checkBoxRVBR, &QAbstractButton::clicked, this, &TsMuxerWindow::onGeneralCheckboxClicked);
|
|
connect(ui->checkBoxCBR, &QAbstractButton::clicked, this, &TsMuxerWindow::onGeneralCheckboxClicked);
|
|
connect(ui->radioButtonStoreOutput, &QAbstractButton::clicked, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->radioButtonOutoutInInput, &QAbstractButton::clicked, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->editVBVLen, spinBoxValueChanged, this, &TsMuxerWindow::onGeneralSpinboxValueChanged);
|
|
connect(ui->editMaxBitrate, doubleSpinBoxValueChanged, this, &TsMuxerWindow::onGeneralSpinboxValueChanged);
|
|
connect(ui->editMinBitrate, doubleSpinBoxValueChanged, this, &TsMuxerWindow::onGeneralSpinboxValueChanged);
|
|
connect(ui->editCBRBitrate, doubleSpinBoxValueChanged, this, &TsMuxerWindow::onGeneralSpinboxValueChanged);
|
|
connect(ui->rightEyeCheckBox, &QCheckBox::stateChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
connect(ui->radioButtonAutoChapter, &QAbstractButton::clicked, this, &TsMuxerWindow::onChapterParamsChanged);
|
|
connect(ui->radioButtonNoChapters, &QAbstractButton::clicked, this, &TsMuxerWindow::onChapterParamsChanged);
|
|
connect(ui->radioButtonCustomChapters, &QAbstractButton::clicked, this, &TsMuxerWindow::onChapterParamsChanged);
|
|
connect(ui->spinEditChapterLen, spinBoxValueChanged, this, &TsMuxerWindow::onChapterParamsChanged);
|
|
connect(ui->memoChapters, &QPlainTextEdit::textChanged, this, &TsMuxerWindow::onChapterParamsChanged);
|
|
connect(ui->noSplit, &QAbstractButton::clicked, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->splitByDuration, &QAbstractButton::clicked, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->splitBySize, &QAbstractButton::clicked, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->spinEditSplitDuration, spinBoxValueChanged, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->editSplitSize, doubleSpinBoxValueChanged, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->comboBoxMeasure, comboBoxIndexChanged, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->checkBoxCut, &QCheckBox::stateChanged, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->cutStartTimeEdit, &QTimeEdit::timeChanged, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->cutEndTimeEdit, &QTimeEdit::timeChanged, this, &TsMuxerWindow::onSplitCutParamsChanged);
|
|
connect(ui->spinEditBorder, spinBoxValueChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->comboBoxAnimation, comboBoxIndexChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->lineSpacing, doubleSpinBoxValueChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->spinEditOffset, spinBoxValueChanged, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->radioButtonTS, &QAbstractButton::clicked, this, &TsMuxerWindow::RadioButtonMuxClick);
|
|
connect(ui->radioButtonM2TS, &QAbstractButton::clicked, this, &TsMuxerWindow::RadioButtonMuxClick);
|
|
connect(ui->radioButtonBluRay, &QAbstractButton::clicked, this, &TsMuxerWindow::RadioButtonMuxClick);
|
|
connect(ui->radioButtonBluRayISO, &QAbstractButton::clicked, this, &TsMuxerWindow::RadioButtonMuxClick);
|
|
connect(ui->radioButtonAVCHD, &QAbstractButton::clicked, this, &TsMuxerWindow::RadioButtonMuxClick);
|
|
connect(ui->radioButtonDemux, &QAbstractButton::clicked, this, &TsMuxerWindow::RadioButtonMuxClick);
|
|
connect(ui->outFileName, &QLineEdit::textChanged, this, &TsMuxerWindow::outFileNameChanged);
|
|
connect(ui->DiskLabelEdit, &QLineEdit::textChanged, this, &TsMuxerWindow::onGeneralCheckboxClicked);
|
|
connect(ui->btnBrowse, &QAbstractButton::clicked, this, &TsMuxerWindow::saveFileDialog);
|
|
connect(ui->buttonMux, &QAbstractButton::clicked, this, &TsMuxerWindow::startMuxing);
|
|
connect(ui->buttonSaveMeta, &QAbstractButton::clicked, this, &TsMuxerWindow::saveMetaFileBtnClick);
|
|
connect(ui->radioButtonOutoutInInput, &QAbstractButton::clicked, this, &TsMuxerWindow::onSavedParamChanged);
|
|
connect(ui->defaultAudioTrackComboBox, comboBoxIndexChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
connect(ui->defaultSubTrackComboBox, comboBoxIndexChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
connect(ui->defaultAudioTrackCheckBox, &QCheckBox::stateChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
connect(ui->defaultSubTrackCheckBox, &QCheckBox::stateChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
connect(ui->defaultSubTrackForcedOnlyCheckBox, &QCheckBox::stateChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
connect(ui->pipTransparencySpinBox, spinBoxValueChanged, this, &TsMuxerWindow::updateMetaLines);
|
|
|
|
connect(&proc, &QProcess::readyReadStandardOutput, this, &TsMuxerWindow::readFromStdout);
|
|
connect(&proc, &QProcess::readyReadStandardError, this, &TsMuxerWindow::readFromStderr);
|
|
void (QProcess::*processFinished)(int, QProcess::ExitStatus) = &QProcess::finished;
|
|
connect(&proc, processFinished, this, &TsMuxerWindow::onProcessFinished);
|
|
void (QProcess::*processError)(QProcess::ProcessError) = &QProcess::errorOccurred;
|
|
connect(&proc, processError, this, &TsMuxerWindow::onProcessError);
|
|
|
|
ui->DiskLabel->setVisible(false);
|
|
ui->DiskLabelEdit->setVisible(false);
|
|
|
|
ui->label_Donate->installEventFilter(this);
|
|
|
|
ui->langComboBox->addItem("und (Undetermined)");
|
|
ui->langComboBox->addItem("--------- common ---------");
|
|
for (auto &&lang : shortLangList)
|
|
{
|
|
ui->langComboBox->addItem(QString("%1 (%2)").arg(lang.code).arg(lang.lang), QString::fromUtf8(lang.code));
|
|
}
|
|
ui->langComboBox->addItem("---------- all ----------");
|
|
for (auto &&lang : fullLangList)
|
|
{
|
|
ui->langComboBox->addItem(QString("%1 (%2)").arg(lang.code).arg(lang.lang), QString::fromUtf8(lang.code));
|
|
}
|
|
trackLVItemSelectionChanged();
|
|
|
|
ui->trackSplitter->setStretchFactor(0, 10);
|
|
ui->trackSplitter->setStretchFactor(1, 100);
|
|
|
|
writeSettings();
|
|
}
|
|
|
|
TsMuxerWindow::~TsMuxerWindow()
|
|
{
|
|
disableUpdatesCnt = 0;
|
|
writeSettings();
|
|
delete settings;
|
|
}
|
|
|
|
void TsMuxerWindow::onTsMuxerCodecInfoReceived()
|
|
{
|
|
m_updateMeta = false;
|
|
codecList.clear();
|
|
int p;
|
|
QtvCodecInfo *codecInfo = 0;
|
|
int lastTrackID = 0;
|
|
QString tmpStr;
|
|
bool firstMark = true;
|
|
codecList.clear();
|
|
mplsFileList.clear();
|
|
chapters.clear();
|
|
fileDuration = 0;
|
|
for (int i = 0; i < procStdOutput.size(); ++i)
|
|
{
|
|
p = procStdOutput[i].indexOf("Track ID: ");
|
|
if (p >= 0)
|
|
{
|
|
lastTrackID = procStdOutput[i].mid(QString("Track ID: ").length()).toInt();
|
|
codecList << QtvCodecInfo();
|
|
codecInfo = &(codecList.back());
|
|
codecInfo->trackID = lastTrackID;
|
|
}
|
|
|
|
p = procStdOutput[i].indexOf("Stream type: ");
|
|
if (p >= 0)
|
|
{
|
|
if (lastTrackID == 0)
|
|
{
|
|
codecList << QtvCodecInfo();
|
|
codecInfo = &(codecList.back());
|
|
}
|
|
codecInfo->descr = "Can't detect codec";
|
|
codecInfo->displayName = procStdOutput[i].mid(QString("Stream type: ").length());
|
|
if (codecInfo->displayName != "H.264" && codecInfo->displayName != "MVC")
|
|
{
|
|
codecInfo->addSEIMethod = 0;
|
|
codecInfo->addSPS = false;
|
|
}
|
|
if (codecInfo->displayName == "HEVC" && !ui->checkBoxV3->isChecked())
|
|
ui->checkBoxV3->setChecked(true);
|
|
lastTrackID = 0;
|
|
}
|
|
p = procStdOutput[i].indexOf("Stream ID: ");
|
|
if (p >= 0)
|
|
codecInfo->programName = procStdOutput[i].mid(QString("Stream ID: ").length());
|
|
p = procStdOutput[i].indexOf("Stream info: ");
|
|
if (p >= 0)
|
|
codecInfo->descr = procStdOutput[i].mid(QString("Stream info: ").length());
|
|
p = procStdOutput[i].indexOf("Stream lang: ");
|
|
if (p >= 0)
|
|
codecInfo->lang = procStdOutput[i].mid(QString("Stream lang: ").length());
|
|
|
|
p = procStdOutput[i].indexOf("Stream delay: ");
|
|
if (p >= 0)
|
|
{
|
|
tmpStr = procStdOutput[i].mid(QString("Stream delay: ").length());
|
|
codecInfo->delay = tmpStr.toInt();
|
|
}
|
|
|
|
p = procStdOutput[i].indexOf("subTrack: ");
|
|
if (p >= 0)
|
|
{
|
|
tmpStr = procStdOutput[i].mid(QString("subTrack: ").length());
|
|
codecInfo->subTrack = tmpStr.toInt();
|
|
}
|
|
|
|
p = procStdOutput[i].indexOf("Secondary: 1");
|
|
if (p == 0)
|
|
{
|
|
codecInfo->isSecondary = true;
|
|
}
|
|
p = procStdOutput[i].indexOf("Unselected: 1");
|
|
if (p == 0)
|
|
{
|
|
codecInfo->enabledByDefault = false;
|
|
}
|
|
|
|
if (procStdOutput[i].startsWith("Error: "))
|
|
{
|
|
tmpStr = procStdOutput[i].mid(QString("Error: ").length());
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Not supported"));
|
|
msgBox.setText(tmpStr);
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
}
|
|
else if (procStdOutput[i].startsWith("File #"))
|
|
{
|
|
tmpStr = procStdOutput[i].mid(17); // todo: check here
|
|
mplsFileList << MPLSFileInfo(tmpStr, 0.0);
|
|
}
|
|
else if (procStdOutput[i].startsWith("Duration:"))
|
|
{
|
|
tmpStr = procStdOutput[i].mid(10); // todo: check here
|
|
if (!mplsFileList.empty())
|
|
{
|
|
mplsFileList.last().duration = timeToFloat(tmpStr);
|
|
}
|
|
else
|
|
{
|
|
fileDuration = timeToFloat(tmpStr);
|
|
}
|
|
}
|
|
else if (procStdOutput[i].startsWith("Base view: "))
|
|
{
|
|
tmpStr = procStdOutput[i].mid(11); // todo: check here
|
|
ui->rightEyeCheckBox->setChecked(tmpStr.trimmed() == "right-eye");
|
|
}
|
|
else if (procStdOutput[i].startsWith("start-time: "))
|
|
{
|
|
tmpStr = procStdOutput[i].mid(12);
|
|
if (tmpStr.contains(':'))
|
|
{
|
|
double secondsF = timeToFloat(tmpStr);
|
|
ui->muxTimeEdit->setTime(qTimeFromFloat(secondsF));
|
|
}
|
|
else
|
|
{
|
|
ui->muxTimeClock->setValue(tmpStr.toInt());
|
|
}
|
|
}
|
|
|
|
p = procStdOutput[i].indexOf("Marks: ");
|
|
if (p == 0)
|
|
{
|
|
if (firstMark)
|
|
{
|
|
firstMark = false;
|
|
ui->radioButtonCustomChapters->setChecked(true);
|
|
}
|
|
QStringList stringList = procStdOutput[i].mid(7).split(' ');
|
|
for (int k = 0; k < stringList.size(); ++k)
|
|
if (!stringList[k].isEmpty())
|
|
chapters << timeToFloat(stringList[k]);
|
|
}
|
|
}
|
|
if (fileDuration == 0 && !mplsFileList.isEmpty())
|
|
{
|
|
foreach (const MPLSFileInfo &mplsFile, mplsFileList)
|
|
fileDuration += mplsFile.duration;
|
|
}
|
|
|
|
m_updateMeta = true;
|
|
if (codecList.isEmpty())
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Unsupported format"));
|
|
msgBox.setText(tr("Can't detect stream type. File name: \"%1\"").arg(newFileName));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
updateMetaLines();
|
|
emit codecListReady();
|
|
}
|
|
|
|
int TsMuxerWindow::findLangByCode(const QString &code)
|
|
{
|
|
QString addr;
|
|
for (int i = 0; i < ui->langComboBox->count(); ++i)
|
|
{
|
|
addr = ui->langComboBox->itemData(i).toString();
|
|
if (!addr.isEmpty() && code == addr)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
QtvCodecInfo *TsMuxerWindow::getCodecInfo(int idx)
|
|
{
|
|
auto iCodec = ui->trackLV->item(idx, 0)->data(Qt::UserRole).toLongLong();
|
|
return reinterpret_cast<QtvCodecInfo *>(iCodec);
|
|
}
|
|
|
|
QtvCodecInfo *TsMuxerWindow::getCurrentCodec()
|
|
{
|
|
auto row = ui->trackLV->currentRow();
|
|
if (row == -1)
|
|
return nullptr;
|
|
return getCodecInfo(row);
|
|
}
|
|
|
|
void TsMuxerWindow::onVideoComboBoxChanged(int)
|
|
{
|
|
if (disableUpdatesCnt)
|
|
return;
|
|
QtvCodecInfo *codecInfo = getCurrentCodec();
|
|
if (!codecInfo)
|
|
return;
|
|
codecInfo->fpsText = ui->comboBoxFPS->itemText(ui->comboBoxFPS->currentIndex());
|
|
codecInfo->levelText = ui->comboBoxLevel->itemText(ui->comboBoxLevel->currentIndex());
|
|
codecInfo->arText = ui->comboBoxAR->currentData().toString();
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onVideoCheckBoxChanged(int state)
|
|
{
|
|
Q_UNUSED(state);
|
|
if (disableUpdatesCnt)
|
|
return;
|
|
QtvCodecInfo *codecInfo = getCurrentCodec();
|
|
if (codecInfo == nullptr)
|
|
return;
|
|
ui->comboBoxFPS->setEnabled(ui->checkFPS->isChecked());
|
|
codecInfo->checkFPS = ui->checkFPS->isChecked();
|
|
ui->comboBoxLevel->setEnabled(ui->checkBoxLevel->isChecked());
|
|
codecInfo->checkLevel = ui->checkBoxLevel->isChecked();
|
|
codecInfo->addSEIMethod = ui->comboBoxSEI->currentIndex();
|
|
codecInfo->addSPS = ui->checkBoxSPS->isChecked();
|
|
codecInfo->isSecondary = ui->checkBoxSecondaryVideo->isChecked();
|
|
colorizeCurrentRow(codecInfo);
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::updateCurrentColor(int dr, int dg, int db, int row)
|
|
{
|
|
if (row == -1)
|
|
row = ui->trackLV->currentRow();
|
|
if (row == -1)
|
|
return;
|
|
QColor color = ui->trackLV->palette().color(QPalette::Base);
|
|
color.setRed(qBound(0, color.red() + dr, 255));
|
|
color.setGreen(qBound(0, color.green() + dg, 255));
|
|
color.setBlue(qBound(0, color.blue() + db, 255));
|
|
for (int i = 0; i < 5; ++i)
|
|
{
|
|
QModelIndex index = ui->trackLV->model()->index(row, i);
|
|
ui->trackLV->model()->setData(index, QBrush(color), Qt::BackgroundRole);
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::colorizeCurrentRow(const QtvCodecInfo *codecInfo, int rowIndex)
|
|
{
|
|
if (codecInfo->isSecondary)
|
|
updateCurrentColor(-40, 0, 0, rowIndex);
|
|
else
|
|
updateCurrentColor(0, 0, 0, rowIndex);
|
|
}
|
|
|
|
void TsMuxerWindow::addTrackToDefaultComboBox(int trackRowIdx)
|
|
{
|
|
auto codecInfo = getCodecInfo(trackRowIdx);
|
|
auto text = getComboBoxTrackText(trackRowIdx, *codecInfo);
|
|
if (codecInfo->programName.startsWith('A'))
|
|
{
|
|
ui->defaultAudioTrackComboBox->addItem(text, trackRowIdx);
|
|
}
|
|
else if (codecInfo->programName.startsWith('S'))
|
|
{
|
|
ui->defaultSubTrackComboBox->addItem(text, trackRowIdx);
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::removeTrackFromDefaultComboBox(QComboBox *targetComboBox, QCheckBox *targetCheckBox,
|
|
int comboBoxIdx, int trackRowIdx)
|
|
{
|
|
if (targetComboBox->currentData().toInt() == trackRowIdx)
|
|
{
|
|
targetCheckBox->setChecked(false);
|
|
}
|
|
targetComboBox->removeItem(comboBoxIdx);
|
|
}
|
|
|
|
static void fixupIndices(QComboBox *comboBox, int removedTrackIdx)
|
|
{
|
|
for (int i = 0; i < comboBox->count(); ++i)
|
|
{
|
|
auto trackIdx = comboBox->itemData(i).toInt();
|
|
if (trackIdx > removedTrackIdx)
|
|
{
|
|
comboBox->setItemData(i, trackIdx - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::removeTrackFromDefaultComboBox(int trackRowIdx)
|
|
{
|
|
auto comboBoxIdx = ui->defaultAudioTrackComboBox->findData(trackRowIdx);
|
|
if (comboBoxIdx != -1)
|
|
{
|
|
removeTrackFromDefaultComboBox(ui->defaultAudioTrackComboBox, ui->defaultAudioTrackCheckBox, comboBoxIdx,
|
|
trackRowIdx);
|
|
}
|
|
comboBoxIdx = ui->defaultSubTrackComboBox->findData(trackRowIdx);
|
|
if (comboBoxIdx != -1)
|
|
{
|
|
removeTrackFromDefaultComboBox(ui->defaultSubTrackComboBox, ui->defaultSubTrackCheckBox, comboBoxIdx,
|
|
trackRowIdx);
|
|
}
|
|
fixupIndices(ui->defaultAudioTrackComboBox, trackRowIdx);
|
|
fixupIndices(ui->defaultSubTrackComboBox, trackRowIdx);
|
|
}
|
|
|
|
void TsMuxerWindow::updateTracksComboBox(QComboBox *comboBox)
|
|
{
|
|
for (int i = 0; i < comboBox->count(); ++i)
|
|
{
|
|
auto trackRowIdx = comboBox->itemData(i).toInt();
|
|
auto codecInfo = getCodecInfo(trackRowIdx);
|
|
comboBox->setItemText(i, getComboBoxTrackText(trackRowIdx, *codecInfo));
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::moveTrackInDefaultComboBox(int oldTrackRowIdx, int newTrackRowIdx)
|
|
{
|
|
auto currentSubTrack = ui->defaultSubTrackComboBox->currentData();
|
|
auto currentAudioTrack = ui->defaultAudioTrackComboBox->currentData();
|
|
ui->defaultSubTrackComboBox->clear();
|
|
ui->defaultAudioTrackComboBox->clear();
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
addTrackToDefaultComboBox(i);
|
|
}
|
|
postMoveComboBoxUpdate(ui->defaultAudioTrackComboBox, currentAudioTrack, oldTrackRowIdx, newTrackRowIdx);
|
|
postMoveComboBoxUpdate(ui->defaultSubTrackComboBox, currentSubTrack, oldTrackRowIdx, newTrackRowIdx);
|
|
}
|
|
|
|
void TsMuxerWindow::postMoveComboBoxUpdate(QComboBox *comboBox, const QVariant &preMoveIndex, int oldIndex,
|
|
int newIndex)
|
|
{
|
|
if (!preMoveIndex.isValid())
|
|
{
|
|
return;
|
|
}
|
|
auto curTrackIdx = preMoveIndex.toInt();
|
|
if (curTrackIdx == oldIndex)
|
|
{
|
|
curTrackIdx = newIndex;
|
|
}
|
|
else if (curTrackIdx == newIndex)
|
|
{
|
|
curTrackIdx = oldIndex;
|
|
}
|
|
auto idx = comboBox->findData(curTrackIdx);
|
|
Q_ASSERT(idx != -1);
|
|
comboBox->setCurrentIndex(idx);
|
|
}
|
|
|
|
void TsMuxerWindow::setUiMetaItemsData()
|
|
{
|
|
// unfortunately, the .ui files don't allow the user to specify "item data" for combo boxes, which is the most
|
|
// convenient way to associate some extra data that's not displayed in the UI in a combo box item without having
|
|
// to resort to some kind of external containers which need to be kept synchronised.
|
|
// as some of the combo boxes are taken as input for the meta file, it would end up having translated strings in it
|
|
// if a non-English translation is active, and thus being invalid. item data for these UI items should always
|
|
// contain the valid metafile tokens, as they are the actual things incorporated into the metafile content.
|
|
ui->comboBoxAR->setItemData(0, QString());
|
|
ui->comboBoxAR->setItemData(1, "1:1");
|
|
ui->comboBoxAR->setItemData(2, "4:3");
|
|
ui->comboBoxAR->setItemData(3, "16:9");
|
|
ui->comboBoxAR->setItemData(4, "2.21:1");
|
|
ui->comboBoxMeasure->setItemData(0, "KB");
|
|
ui->comboBoxMeasure->setItemData(1, "KiB");
|
|
ui->comboBoxMeasure->setItemData(2, "MB");
|
|
ui->comboBoxMeasure->setItemData(3, "MiB");
|
|
ui->comboBoxMeasure->setItemData(4, "GB");
|
|
ui->comboBoxMeasure->setItemData(5, "GiB");
|
|
}
|
|
|
|
void TsMuxerWindow::onAudioSubtitlesParamsChanged()
|
|
{
|
|
if (disableUpdatesCnt)
|
|
return;
|
|
QtvCodecInfo *codecInfo = getCurrentCodec();
|
|
if (!codecInfo)
|
|
return;
|
|
codecInfo->bindFps = ui->checkBoxKeepFps->isChecked();
|
|
codecInfo->dtsDownconvert = ui->dtsDwnConvert->isChecked();
|
|
codecInfo->isSecondary = ui->secondaryCheckBox->isChecked();
|
|
codecInfo->offsetId = ui->offsetsComboBox->currentIndex() - 1;
|
|
QString addr = ui->langComboBox->itemData(ui->langComboBox->currentIndex()).toString();
|
|
if (!addr.isEmpty())
|
|
{
|
|
codecInfo->lang = addr;
|
|
}
|
|
else
|
|
{
|
|
codecInfo->lang.clear();
|
|
}
|
|
ui->trackLV->item(ui->trackLV->currentRow(), 3)->setText(codecInfo->lang);
|
|
colorizeCurrentRow(codecInfo);
|
|
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onEditDelayChanged(int)
|
|
{
|
|
if (disableUpdatesCnt)
|
|
return;
|
|
QtvCodecInfo *codecInfo = getCurrentCodec();
|
|
if (!codecInfo)
|
|
return;
|
|
codecInfo->delay = ui->editDelay->value();
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onPulldownCheckBoxChanged(int state)
|
|
{
|
|
Q_UNUSED(state);
|
|
if (disableUpdatesCnt)
|
|
return;
|
|
QtvCodecInfo *codecInfo = getCurrentCodec();
|
|
if (!codecInfo)
|
|
return;
|
|
if (ui->checkBoxRemovePulldown->isEnabled())
|
|
{
|
|
if (ui->checkBoxRemovePulldown->isChecked())
|
|
{
|
|
codecInfo->delPulldown = 1;
|
|
if (codecInfo->fpsTextOrig == "29.97")
|
|
{
|
|
ui->checkFPS->setChecked(true);
|
|
ui->checkFPS->setEnabled(true);
|
|
codecInfo->checkFPS = true;
|
|
ui->comboBoxFPS->setEnabled(true);
|
|
ui->comboBoxFPS->setCurrentIndex(3);
|
|
codecInfo->fpsText = "24000/1001";
|
|
setComboBoxText(ui->comboBoxFPS, "24000/1001");
|
|
}
|
|
}
|
|
else
|
|
codecInfo->delPulldown = 0;
|
|
}
|
|
else
|
|
codecInfo->delPulldown = -1;
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::addFiles(const QList<QUrl> &files)
|
|
{
|
|
addFileList.clear();
|
|
addFileList = files;
|
|
addFile();
|
|
}
|
|
|
|
void TsMuxerWindow::onAddBtnClick()
|
|
{
|
|
showAddFilesDialog(tr("Add media files"), [this]() { addFile(); });
|
|
}
|
|
|
|
void TsMuxerWindow::addFile()
|
|
{
|
|
processAddFileList(&TsMuxerWindow::continueAddFile, &TsMuxerWindow::fileAdded, &TsMuxerWindow::addFile);
|
|
}
|
|
|
|
bool TsMuxerWindow::checkFileDuplicate(const QString &fileName)
|
|
{
|
|
QString t = myUnquoteStr(fileName).trimmed();
|
|
for (int i = 0; i < ui->inputFilesLV->count(); ++i)
|
|
if (myUnquoteStr(ui->inputFilesLV->item(i)->data(FileNameRole).toString()) == t)
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("File already exists"));
|
|
msgBox.setText(tr("File \"%1\" already exists").arg(fileName));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TsMuxerWindow::setComboBoxText(QComboBox *comboBox, const QString &text)
|
|
{
|
|
for (int k = 0; k < comboBox->count(); ++k)
|
|
{
|
|
if (comboBox->itemText(k) == text)
|
|
{
|
|
comboBox->setCurrentIndex(k);
|
|
return;
|
|
}
|
|
}
|
|
comboBox->addItem(text);
|
|
comboBox->setCurrentIndex(comboBox->count() - 1);
|
|
}
|
|
|
|
void TsMuxerWindow::trackLVItemSelectionChanged()
|
|
{
|
|
if (disableUpdatesCnt)
|
|
return;
|
|
while (ui->tabWidgetTracks->count()) ui->tabWidgetTracks->removeTab(0);
|
|
if (ui->trackLV->currentRow() == -1)
|
|
{
|
|
ui->tabWidgetTracks->addTab(ui->tabSheetFake, TI_DEFAULT_TAB_NAME());
|
|
return;
|
|
}
|
|
QtvCodecInfo *codecInfo = getCurrentCodec();
|
|
if (!codecInfo)
|
|
return;
|
|
disableUpdatesCnt++;
|
|
if (ui->trackLV->rowCount() >= 1)
|
|
{
|
|
if (isVideoCodec(codecInfo->displayName))
|
|
{
|
|
ui->tabWidgetTracks->addTab(ui->tabSheetVideo, TI_DEFAULT_TAB_NAME());
|
|
|
|
ui->checkFPS->setChecked(codecInfo->checkFPS);
|
|
ui->checkBoxLevel->setChecked(codecInfo->checkLevel);
|
|
ui->comboBoxFPS->setEnabled(ui->checkFPS->isChecked());
|
|
ui->comboBoxLevel->setEnabled(ui->checkBoxLevel->isChecked());
|
|
ui->comboBoxSEI->setCurrentIndex(codecInfo->addSEIMethod);
|
|
ui->checkBoxSecondaryVideo->setChecked(codecInfo->isSecondary);
|
|
ui->checkBoxSPS->setChecked(codecInfo->addSPS);
|
|
ui->checkBoxRemovePulldown->setChecked(codecInfo->delPulldown == 1);
|
|
ui->checkBoxRemovePulldown->setEnabled(codecInfo->delPulldown >= 0);
|
|
|
|
setComboBoxText(ui->comboBoxFPS, codecInfo->fpsText);
|
|
if (!codecInfo->arText.isEmpty())
|
|
setComboBoxText(ui->comboBoxAR, codecInfo->arText);
|
|
else
|
|
ui->comboBoxAR->setCurrentIndex(0);
|
|
ui->checkBoxLevel->setEnabled(codecInfo->displayName == "H.264" || codecInfo->displayName == "MVC");
|
|
if (ui->checkBoxLevel->isEnabled())
|
|
setComboBoxText(ui->comboBoxLevel, codecInfo->levelText);
|
|
else
|
|
ui->comboBoxLevel->setCurrentIndex(0);
|
|
ui->comboBoxSEI->setEnabled(ui->checkBoxLevel->isEnabled());
|
|
ui->checkBoxSPS->setEnabled(ui->checkBoxLevel->isEnabled());
|
|
ui->comboBoxAR->setEnabled(codecInfo->displayName == "MPEG-2");
|
|
ui->comboBoxAR->setEnabled(true);
|
|
ui->labelAR->setEnabled(ui->comboBoxAR->isEnabled());
|
|
ui->checkBoxSecondaryVideo->setEnabled(codecInfo->displayName != "MVC");
|
|
}
|
|
else
|
|
{
|
|
ui->tabWidgetTracks->addTab(ui->tabSheetAudio, TI_DEFAULT_TAB_NAME());
|
|
if (codecInfo->displayName == "LPCM")
|
|
ui->tabWidgetTracks->addTab(ui->demuxLpcmOptions, TI_DEMUX_TAB_NAME());
|
|
|
|
if (codecInfo->displayName == "DTS-HD")
|
|
ui->dtsDwnConvert->setText(tr("Downconvert DTS-HD to DTS"));
|
|
else if (codecInfo->displayName == "TRUE-HD")
|
|
ui->dtsDwnConvert->setText(tr("Downconvert TRUE-HD to AC3"));
|
|
else if (codecInfo->displayName == "E-AC3 (DD+)")
|
|
ui->dtsDwnConvert->setText(tr("Downconvert E-AC3 to AC3"));
|
|
else
|
|
ui->dtsDwnConvert->setText(tr("Downconvert HD audio"));
|
|
ui->dtsDwnConvert->setEnabled(codecInfo->displayName == "DTS-HD" || codecInfo->displayName == "TRUE-HD" ||
|
|
codecInfo->displayName == "E-AC3 (DD+)");
|
|
ui->secondaryCheckBox->setEnabled(codecInfo->descr.contains("(DTS Express)") ||
|
|
codecInfo->descr.contains("(DTS Express 24bit)") ||
|
|
codecInfo->displayName == "E-AC3 (DD+)");
|
|
|
|
if (!ui->secondaryCheckBox->isEnabled())
|
|
ui->secondaryCheckBox->setChecked(false);
|
|
|
|
ui->langComboBox->setCurrentIndex(findLangByCode(codecInfo->lang));
|
|
ui->offsetsComboBox->setCurrentIndex(codecInfo->offsetId + 1);
|
|
ui->dtsDwnConvert->setVisible(codecInfo->displayName != "PGS" && codecInfo->displayName != "SRT");
|
|
ui->secondaryCheckBox->setVisible(ui->dtsDwnConvert->isVisible());
|
|
|
|
bool isPGS = codecInfo->displayName == "PGS";
|
|
ui->checkBoxKeepFps->setVisible(isPGS);
|
|
ui->offsetsLabel->setVisible(isPGS);
|
|
ui->offsetsComboBox->setVisible(isPGS);
|
|
|
|
ui->imageSubtitles->setVisible(!ui->dtsDwnConvert->isVisible());
|
|
ui->imageAudio->setVisible(ui->dtsDwnConvert->isVisible());
|
|
|
|
ui->editDelay->setValue(codecInfo->delay);
|
|
ui->dtsDwnConvert->setChecked(codecInfo->dtsDownconvert);
|
|
ui->secondaryCheckBox->setChecked(codecInfo->isSecondary);
|
|
ui->checkBoxKeepFps->setChecked(codecInfo->bindFps);
|
|
ui->editDelay->setEnabled(!ui->radioButtonDemux->isChecked());
|
|
}
|
|
}
|
|
|
|
disableUpdatesCnt--;
|
|
trackLVItemChanged(0);
|
|
}
|
|
|
|
void TsMuxerWindow::trackLVItemChanged(QTableWidgetItem *item)
|
|
{
|
|
if (disableUpdatesCnt > 0)
|
|
return;
|
|
|
|
Q_UNUSED(item);
|
|
updateMetaLines();
|
|
ui->moveupBtn->setEnabled(ui->trackLV->currentItem() != 0);
|
|
ui->movedownBtn->setEnabled(ui->trackLV->currentItem() != 0);
|
|
ui->removeTrackBtn->setEnabled(ui->trackLV->currentItem() != 0);
|
|
if (ui->trackLV->rowCount() == 0)
|
|
oldFileName.clear();
|
|
|
|
disableUpdatesCnt++;
|
|
bool checkedExist = false;
|
|
bool uncheckedExist = false;
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
if (ui->trackLV->item(i, 0)->checkState() == Qt::Checked)
|
|
checkedExist = true;
|
|
else
|
|
uncheckedExist = true;
|
|
}
|
|
if (checkedExist && uncheckedExist)
|
|
m_header->setCheckState(Qt::PartiallyChecked);
|
|
else if (checkedExist)
|
|
m_header->setCheckState(Qt::Checked);
|
|
else
|
|
m_header->setCheckState(Qt::Unchecked);
|
|
update();
|
|
disableUpdatesCnt--;
|
|
}
|
|
|
|
void TsMuxerWindow::inputFilesLVChanged()
|
|
{
|
|
QListWidgetItem *itm = ui->inputFilesLV->currentItem();
|
|
if (!itm)
|
|
{
|
|
ui->btnAppend->setEnabled(false);
|
|
ui->removeFile->setEnabled(false);
|
|
return;
|
|
}
|
|
ui->btnAppend->setEnabled(itm->data(MplsItemRole).toInt() != MPLS_M2TS && ui->buttonMux->isEnabled());
|
|
ui->removeFile->setEnabled(itm->data(MplsItemRole).toInt() != MPLS_M2TS);
|
|
}
|
|
|
|
void TsMuxerWindow::modifyOutFileName(const QString fileName)
|
|
{
|
|
QFileInfo fi(unquoteStr(fileName));
|
|
QString name = fi.completeBaseName();
|
|
|
|
QString existingName = QDir::toNativeSeparators(ui->outFileName->text());
|
|
QFileInfo fiDst(existingName);
|
|
if (fiDst.completeBaseName() == "default")
|
|
{
|
|
QString dstPath;
|
|
if (existingName.contains(QDir::separator()))
|
|
dstPath = QDir::toNativeSeparators(fiDst.absolutePath());
|
|
else
|
|
dstPath = getOutputDir();
|
|
|
|
if (ui->radioButtonTS->isChecked())
|
|
ui->outFileName->setText(dstPath + QDir::separator() + name + ".ts");
|
|
else if (ui->radioButtonM2TS->isChecked())
|
|
ui->outFileName->setText(dstPath + QDir::separator() + name + ".m2ts");
|
|
else if (ui->radioButtonBluRayISO->isChecked())
|
|
ui->outFileName->setText(dstPath + QDir::separator() + name + ".iso");
|
|
else
|
|
ui->outFileName->setText(dstPath);
|
|
if (ui->outFileName->text() == fileName)
|
|
ui->outFileName->setText(dstPath + QDir::separator() + name + "_new." + fi.suffix());
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::continueAddFile()
|
|
{
|
|
double fps;
|
|
double level;
|
|
disableUpdatesCnt++;
|
|
bool firstWarn = true;
|
|
int firstAddedIndex = -1;
|
|
for (int i = 0; i < codecList.size(); ++i)
|
|
{
|
|
if (codecList[i].displayName == "PGS (depended view)")
|
|
continue;
|
|
|
|
QtvCodecInfo info = codecList[i];
|
|
if (info.displayName.isEmpty())
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Unsupported format"));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
if (codecList.size() == 0)
|
|
{
|
|
msgBox.setText(
|
|
tr("Unsupported format or all tracks are not recognized. File name: \"%1\"").arg(newFileName));
|
|
msgBox.exec();
|
|
disableUpdatesCnt--;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (firstWarn)
|
|
{
|
|
msgBox.setText(
|
|
tr("Track %1 was not recognized and ignored. File name: \"%2\"").arg(i).arg(newFileName));
|
|
msgBox.exec();
|
|
firstWarn = false;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
if (mplsFileList.isEmpty())
|
|
info.fileList << newFileName;
|
|
else
|
|
{
|
|
info.fileList << mplsFileList[0].name;
|
|
QFileInfo fileInfo(unquoteStr(newFileName));
|
|
// info.mplsFile = fileInfo.baseName();
|
|
info.mplsFiles << unquoteStr(newFileName);
|
|
}
|
|
if (info.descr.indexOf("not found") >= 0)
|
|
{
|
|
fps = 23.976;
|
|
info.checkFPS = true;
|
|
}
|
|
else
|
|
fps = extractFloatFromDescr(info.descr, "Frame rate: ");
|
|
info.width = extractFloatFromDescr(info.descr, "Resolution: ") + 0.5;
|
|
info.height = extractFloatFromDescr(info.descr, QString::number(info.width) + ":") + 0.5;
|
|
if (doubleCompare(fps, 23.976))
|
|
info.fpsText = "24000/1001";
|
|
else if (doubleCompare(fps, 29.97))
|
|
info.fpsText = "30000/1001";
|
|
else if (doubleCompare(fps, 59.94))
|
|
info.fpsText = "60000/1001";
|
|
else
|
|
info.fpsText = QString::number(fps);
|
|
info.fpsTextOrig = QString::number(fps);
|
|
level = extractFloatFromDescr(info.descr, "@");
|
|
info.levelText = QString::number(level);
|
|
if (info.descr.indexOf("pulldown") >= 0)
|
|
info.delPulldown = 0;
|
|
|
|
info.maxPgOffsets = extractFloatFromDescr(info.descr, "3d-pg-planes: ");
|
|
if (info.maxPgOffsets > 0)
|
|
m_3dMode = true;
|
|
|
|
if (info.descr.contains("3d-plane: zero"))
|
|
info.offsetId = -1;
|
|
else if (info.descr.contains("3d-plane: "))
|
|
info.offsetId = extractFloatFromDescr(info.descr, "3d-plane: ");
|
|
else if (m_3dMode)
|
|
info.offsetId = 0;
|
|
|
|
auto newTrackRowIdx = ui->trackLV->rowCount();
|
|
ui->trackLV->setRowCount(newTrackRowIdx + 1);
|
|
ui->trackLV->setRowHeight(newTrackRowIdx, 18);
|
|
QTableWidgetItem *item = new QTableWidgetItem("");
|
|
item->setCheckState(info.enabledByDefault ? Qt::Checked : Qt::Unchecked);
|
|
item->setData(Qt::UserRole, reinterpret_cast<qlonglong>(new QtvCodecInfo(info)));
|
|
ui->trackLV->setCurrentItem(item);
|
|
|
|
ui->trackLV->setItem(newTrackRowIdx, 0, item);
|
|
item = new QTableWidgetItem(newFileName);
|
|
item->setFlags(item->flags() & (~Qt::ItemIsEditable));
|
|
ui->trackLV->setItem(newTrackRowIdx, 1, item);
|
|
item = new QTableWidgetItem(info.displayName);
|
|
item->setFlags(item->flags() & (~Qt::ItemIsEditable));
|
|
ui->trackLV->setItem(newTrackRowIdx, 2, item);
|
|
item = new QTableWidgetItem(info.lang);
|
|
item->setFlags(item->flags() & (~Qt::ItemIsEditable));
|
|
ui->trackLV->setItem(newTrackRowIdx, 3, item);
|
|
item = new QTableWidgetItem(info.descr);
|
|
item->setFlags(item->flags() & (~Qt::ItemIsEditable));
|
|
ui->trackLV->setItem(newTrackRowIdx, 4, item);
|
|
if (firstAddedIndex == -1)
|
|
firstAddedIndex = newTrackRowIdx;
|
|
colorizeCurrentRow(&info, newTrackRowIdx);
|
|
addTrackToDefaultComboBox(newTrackRowIdx);
|
|
}
|
|
if (firstAddedIndex >= 0)
|
|
{
|
|
ui->trackLV->setRangeSelected(QTableWidgetSelectionRange(firstAddedIndex, 0, ui->trackLV->rowCount() - 1, 4),
|
|
true);
|
|
ui->trackLV->setCurrentCell(firstAddedIndex, 0);
|
|
}
|
|
|
|
QString displayName = newFileName;
|
|
if (fileDuration > 0)
|
|
displayName += QString(" (%1)").arg(floatToTime(fileDuration));
|
|
ui->inputFilesLV->addItem(displayName);
|
|
|
|
int index = ui->inputFilesLV->count() - 1;
|
|
QListWidgetItem *fileItem = ui->inputFilesLV->item(index);
|
|
if (!mplsFileList.empty())
|
|
fileItem->setData(MplsItemRole, MPLS_PRIMARY);
|
|
fileItem->setData(FileNameRole, newFileName);
|
|
fileItem->setData(ChaptersRole, QVariant::fromValue(chapters));
|
|
fileItem->setData(FileDurationRole, fileDuration);
|
|
chapters.clear();
|
|
fileDuration = 0.0;
|
|
|
|
ui->inputFilesLV->setCurrentItem(fileItem);
|
|
if (mplsFileList.size() > 0)
|
|
doAppendInt(mplsFileList[0].name, newFileName, mplsFileList[0].duration, false, MPLS_M2TS);
|
|
for (int mplsCnt = 1; mplsCnt < mplsFileList.size(); ++mplsCnt)
|
|
doAppendInt(mplsFileList[mplsCnt].name, mplsFileList[mplsCnt - 1].name, mplsFileList[mplsCnt].duration, false,
|
|
MPLS_M2TS);
|
|
ui->inputFilesLV->setCurrentItem(ui->inputFilesLV->item(index));
|
|
updateMetaLines();
|
|
ui->moveupBtn->setEnabled(ui->trackLV->currentRow() >= 0);
|
|
ui->movedownBtn->setEnabled(ui->trackLV->currentRow() >= 0);
|
|
ui->removeTrackBtn->setEnabled(ui->trackLV->currentRow() >= 0);
|
|
if (!outFileNameModified)
|
|
{
|
|
modifyOutFileName(newFileName);
|
|
outFileNameModified = true;
|
|
}
|
|
updateMaxOffsets();
|
|
updateCustomChapters();
|
|
disableUpdatesCnt--;
|
|
trackLVItemSelectionChanged();
|
|
emit fileAdded();
|
|
}
|
|
|
|
void TsMuxerWindow::updateCustomChapters()
|
|
{
|
|
QSet<qint64> chaptersSet;
|
|
double prevDuration = 0.0;
|
|
double offset = 0.0;
|
|
for (int i = 0; i < ui->inputFilesLV->count(); ++i)
|
|
{
|
|
QListWidgetItem *item = ui->inputFilesLV->item(i);
|
|
if (item->data(MplsItemRole).toInt() == MPLS_M2TS)
|
|
continue;
|
|
if (item->text().left(4) == FILE_JOIN_PREFIX)
|
|
offset += prevDuration;
|
|
else
|
|
offset = 0;
|
|
|
|
ChapterList chapters = item->data(ChaptersRole).value<ChapterList>();
|
|
foreach (double chapter, chapters)
|
|
chaptersSet << qint64((chapter + offset) * 1000000);
|
|
prevDuration = item->data(FileDurationRole).toDouble();
|
|
}
|
|
|
|
ui->memoChapters->clear();
|
|
QList<qint64> mergedChapterList = chaptersSet.values();
|
|
std::sort(std::begin(mergedChapterList), std::end(mergedChapterList));
|
|
for (auto chapter : mergedChapterList)
|
|
ui->memoChapters->insertPlainText(floatToTime(chapter / 1000000.0) + QString('\n'));
|
|
}
|
|
|
|
void splitLines(const QString &str, QList<QString> &strList)
|
|
{
|
|
strList = str.split('\n');
|
|
for (int i = 0; i < strList.size(); ++i)
|
|
{
|
|
if (!strList[i].isEmpty() && strList[i].at(0) == '\r')
|
|
strList[i] = strList[i].mid(1);
|
|
else if (!strList[i].isEmpty() && strList[i].at(strList[i].length() - 1) == '\r')
|
|
strList[i] = strList[i].mid(0, strList[i].length() - 1);
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::addLines(const QByteArray &arr, QList<QString> &outList, bool isError)
|
|
{
|
|
QString str = QString::fromUtf8(arr);
|
|
QList<QString> strList;
|
|
splitLines(str, strList);
|
|
QString text;
|
|
for (int i = 0; i < strList.size(); ++i)
|
|
{
|
|
if (strList[i].trimmed().isEmpty())
|
|
continue;
|
|
int p = strList[i].indexOf("% complete");
|
|
if (p >= 0)
|
|
{
|
|
int numStartPos = 0;
|
|
for (int j = 0; j < strList[i].length(); ++j)
|
|
{
|
|
if (strList[i].at(j) >= '0' && strList[i].at(j) <= '9')
|
|
{
|
|
numStartPos = j;
|
|
break;
|
|
}
|
|
}
|
|
QString progress = strList[i].mid(numStartPos, p - numStartPos);
|
|
float tmpVal = progress.toFloat();
|
|
if (qAbs(tmpVal) > 0 && runInMuxMode)
|
|
muxForm->setProgress(int(double(tmpVal * 10) + 0.5)); // todo: uncomment here
|
|
}
|
|
else
|
|
{
|
|
if (runInMuxMode)
|
|
{
|
|
if (!text.isEmpty())
|
|
text += '\n';
|
|
text += strList[i];
|
|
}
|
|
else
|
|
outList << strList[i];
|
|
}
|
|
}
|
|
if (runInMuxMode && !text.isEmpty())
|
|
{
|
|
if (isError)
|
|
muxForm->addStdErrLine(text);
|
|
else
|
|
muxForm->addStdOutLine(text);
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::readFromStdout() { addLines(proc.readAllStandardOutput(), procStdOutput, false); }
|
|
|
|
void TsMuxerWindow::readFromStderr()
|
|
{
|
|
QByteArray data(proc.readAllStandardError());
|
|
addLines(data, procErrOutput, true);
|
|
}
|
|
|
|
void TsMuxerWindow::myPlaySound(const QString &fileName)
|
|
{
|
|
#if QT_MULTIMEDIA_LIB
|
|
sound.setSource(QUrl(QString("qrc%1").arg(fileName)));
|
|
sound.play();
|
|
#else
|
|
QApplication::beep();
|
|
#endif
|
|
}
|
|
|
|
void TsMuxerWindow::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|
{
|
|
processFinished = true;
|
|
if (!metaName.isEmpty())
|
|
{
|
|
QFile::remove(metaName);
|
|
metaName.clear();
|
|
if (ui->checkBoxSound->isChecked())
|
|
{
|
|
if (exitCode == 0)
|
|
myPlaySound(":/sounds/success.wav");
|
|
else
|
|
myPlaySound(":/sounds/fail.wav");
|
|
}
|
|
}
|
|
processExitCode = exitCode;
|
|
processExitStatus = exitStatus;
|
|
muxForm->muxFinished(processExitCode, ui->radioButtonDemux->isChecked() ? tr("Demux") : tr("Mux"));
|
|
ui->buttonMux->setEnabled(true);
|
|
ui->addBtn->setEnabled(true);
|
|
inputFilesLVChanged();
|
|
if (processExitCode == 0)
|
|
emit tsMuxerSuccessFinished();
|
|
}
|
|
|
|
void TsMuxerWindow::onProcessError(QProcess::ProcessError error)
|
|
{
|
|
processExitCode = -1;
|
|
if (!metaName.isEmpty())
|
|
{
|
|
QFile::remove(metaName);
|
|
metaName.clear();
|
|
}
|
|
processFinished = true;
|
|
QMessageBox msgBox(this);
|
|
QString text;
|
|
msgBox.setWindowTitle(tr("tsMuxeR error"));
|
|
switch (error)
|
|
{
|
|
case QProcess::FailedToStart:
|
|
msgBox.setText(tr("tsMuxeR not found!"));
|
|
ui->buttonMux->setEnabled(true);
|
|
ui->addBtn->setEnabled(true);
|
|
inputFilesLVChanged();
|
|
break;
|
|
case QProcess::Crashed:
|
|
// process killed
|
|
if (runInMuxMode)
|
|
return;
|
|
for (int i = 0; i < procErrOutput.size(); ++i)
|
|
{
|
|
if (i > 0)
|
|
text += '\n';
|
|
text += procErrOutput[i];
|
|
}
|
|
msgBox.setText(text);
|
|
break;
|
|
default:
|
|
msgBox.setText(tr("Can't execute tsMuxeR!"));
|
|
break;
|
|
}
|
|
msgBox.setIcon(QMessageBox::Critical);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
}
|
|
|
|
static QString getTsMuxerBinaryPath()
|
|
{
|
|
const auto applicationDirPath =
|
|
QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + QDir::separator();
|
|
for (auto binaryName : {"tsmuxer", "tsMuxeR"})
|
|
{
|
|
auto binaryPath = QStandardPaths::findExecutable(binaryName, {applicationDirPath});
|
|
if (!binaryPath.isEmpty())
|
|
{
|
|
return binaryPath;
|
|
}
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
void TsMuxerWindow::tsMuxerExecute(const QStringList &args)
|
|
{
|
|
const auto exePath = getTsMuxerBinaryPath();
|
|
ui->buttonMux->setEnabled(false);
|
|
procStdOutput.clear();
|
|
procErrOutput.clear();
|
|
processFinished = false;
|
|
processExitCode = -1;
|
|
proc.start(exePath, args);
|
|
if (muxForm->isVisible())
|
|
muxForm->setProcess(&proc);
|
|
}
|
|
|
|
void TsMuxerWindow::doAppendInt(const QString &fileName, const QString &parentFileName, double duration,
|
|
bool doublePrefix, MplsType mplsRole)
|
|
{
|
|
QString itemName = FILE_JOIN_PREFIX;
|
|
if (doublePrefix)
|
|
itemName += FILE_JOIN_PREFIX;
|
|
itemName += fileName;
|
|
if (duration > 0)
|
|
itemName += QString(" ( %1)").arg(floatToTime(duration));
|
|
QListWidgetItem *item = new QListWidgetItem(itemName);
|
|
ui->inputFilesLV->insertItem(ui->inputFilesLV->currentRow() + 1, item);
|
|
item->setData(MplsItemRole, mplsRole);
|
|
item->setData(FileNameRole, fileName);
|
|
if (duration > 0)
|
|
item->setData(FileDurationRole, duration);
|
|
item->setData(ChaptersRole, QVariant::fromValue(chapters));
|
|
|
|
ui->inputFilesLV->setCurrentItem(item);
|
|
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
auto info = getCodecInfo(i);
|
|
if (!info)
|
|
continue;
|
|
if (mplsRole == MPLS_PRIMARY)
|
|
{
|
|
for (int j = 0; j < info->mplsFiles.size(); ++j)
|
|
{
|
|
if (info->mplsFiles[j] == parentFileName)
|
|
{
|
|
info->mplsFiles.insert(j + 1, fileName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int j = 0; j < info->fileList.size(); ++j)
|
|
{
|
|
if (info->fileList[j] == parentFileName)
|
|
{
|
|
info->fileList.insert(j + 1, fileName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TsMuxerWindow::isVideoCropped()
|
|
{
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
auto codecInfo = getCodecInfo(i);
|
|
if (!codecInfo)
|
|
continue;
|
|
|
|
if (isVideoCodec(codecInfo->displayName))
|
|
{
|
|
if (codecInfo->height < 1080 && codecInfo->height != 720 && codecInfo->height != 576 &&
|
|
codecInfo->height != 480)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TsMuxerWindow::isDiskOutput() const
|
|
{
|
|
return ui->radioButtonAVCHD->isChecked() || ui->radioButtonBluRay->isChecked() ||
|
|
ui->radioButtonBluRayISO->isChecked();
|
|
}
|
|
|
|
QString TsMuxerWindow::getMuxOpts()
|
|
{
|
|
QString rez = "MUXOPT --no-pcr-on-video-pid ";
|
|
if (ui->checkBoxNewAudioPes->isChecked())
|
|
rez += "--new-audio-pes --hdmv-descriptors ";
|
|
if (ui->radioButtonBluRay->isChecked())
|
|
rez += (ui->checkBoxV3->isChecked() ? "--blu-ray-v3 " : "--blu-ray ");
|
|
else if (ui->radioButtonBluRayISO->isChecked())
|
|
{
|
|
rez += (ui->checkBoxV3->isChecked() ? "--blu-ray-v3 " : "--blu-ray ");
|
|
if (!ui->DiskLabelEdit->text().isEmpty())
|
|
rez += QString("--label=\"%1\" ").arg(ui->DiskLabelEdit->text());
|
|
}
|
|
else if (ui->radioButtonAVCHD->isChecked())
|
|
rez += "--avchd ";
|
|
else if (ui->radioButtonDemux->isChecked())
|
|
rez += "--demux ";
|
|
if (ui->checkBoxCBR->isChecked())
|
|
rez += "--cbr --bitrate=" + QString::number(ui->editCBRBitrate->value(), 'f', 3);
|
|
else
|
|
{
|
|
rez += "--vbr ";
|
|
if (ui->checkBoxRVBR->isChecked())
|
|
{
|
|
rez += QString("--minbitrate=") + QString::number(ui->editMinBitrate->value(), 'f', 3);
|
|
rez += QString(" --maxbitrate=") + QString::number(ui->editMaxBitrate->value(), 'f', 3);
|
|
}
|
|
}
|
|
// if (!ui->checkBoxuseAsynIO->isChecked())
|
|
// rez += " --no-asyncio";
|
|
if (isDiskOutput())
|
|
{
|
|
if (ui->checkBoxBlankPL->isChecked() && isVideoCropped())
|
|
{
|
|
rez += " --insertBlankPL";
|
|
if (ui->BlackplaylistCombo->value() != 1900)
|
|
rez += QString(" --blankOffset=") + QString::number(ui->BlackplaylistCombo->value());
|
|
}
|
|
if (ui->spinBoxMplsNum->value() > 0)
|
|
rez += " --mplsOffset=" + QString::number(ui->spinBoxMplsNum->value());
|
|
if (ui->spinBoxM2tsNum->value() > 0)
|
|
rez += " --m2tsOffset=" + QString::number(ui->spinBoxM2tsNum->value());
|
|
}
|
|
if (isDiskOutput())
|
|
{
|
|
if (ui->radioButtonAutoChapter->isChecked())
|
|
rez += " --auto-chapters=" + QString::number(ui->spinEditChapterLen->value());
|
|
if (ui->radioButtonCustomChapters->isChecked())
|
|
{
|
|
QString custChapStr;
|
|
QList<QString> lines;
|
|
splitLines(ui->memoChapters->toPlainText(), lines);
|
|
for (int i = 0; i < lines.size(); ++i)
|
|
{
|
|
QString tmpStr = lines[i].trimmed();
|
|
if (!tmpStr.isEmpty())
|
|
{
|
|
if (!custChapStr.isEmpty())
|
|
custChapStr += ';';
|
|
custChapStr += tmpStr;
|
|
}
|
|
}
|
|
rez += QString(" --custom-chapters=") + custChapStr;
|
|
}
|
|
}
|
|
if (ui->splitByDuration->isChecked())
|
|
rez += QString(" --split-duration=") + ui->spinEditSplitDuration->text();
|
|
if (ui->splitBySize->isChecked())
|
|
rez += QString(" --split-size=") + QString::number(ui->editSplitSize->value(), 'f', 3) +
|
|
ui->comboBoxMeasure->currentData().toString();
|
|
|
|
int startCut = qTimeToMsec(ui->cutStartTimeEdit->time());
|
|
int endCut = qTimeToMsec(ui->cutEndTimeEdit->time());
|
|
if (startCut > 0 && ui->checkBoxCut->isChecked())
|
|
rez += QString(" --cut-start=%1ms").arg(startCut);
|
|
if (endCut > 0 && ui->checkBoxCut->isChecked())
|
|
rez += QString(" --cut-end=%1ms").arg(endCut);
|
|
|
|
int vbvLen = ui->editVBVLen->value();
|
|
if (vbvLen > 0)
|
|
rez += " --vbv-len=" + QString::number(vbvLen);
|
|
|
|
if (isDiskOutput() && ui->rightEyeCheckBox->isChecked())
|
|
rez += " --right-eye";
|
|
/*
|
|
QString muxTimeStr = ui->muxTimeEdit->time().toString("hh:mm:ss.zzz");
|
|
if (muxTimeStr != "00:00:00.000")
|
|
rez += " --start-time=" + muxTimeStr;
|
|
*/
|
|
if (ui->muxTimeClock->value())
|
|
rez += QString(" --start-time=%1").arg(ui->muxTimeClock->value());
|
|
return rez;
|
|
}
|
|
|
|
double TsMuxerWindow::getRendererAnimationTime() const
|
|
{
|
|
switch (ui->comboBoxAnimation->currentIndex())
|
|
{
|
|
case 1:
|
|
return 0.1; // fast
|
|
break;
|
|
case 2:
|
|
return 0.25; // medium
|
|
break;
|
|
case 3:
|
|
return 0.5; // slow
|
|
break;
|
|
case 4:
|
|
return 1.0; // very slow
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
void TsMuxerWindow::setRendererAnimationTime(double value)
|
|
{
|
|
int index = 0;
|
|
if (doubleCompare(value, 0.1))
|
|
index = 1;
|
|
else if (doubleCompare(value, 0.25))
|
|
index = 2;
|
|
else if (doubleCompare(value, 0.5))
|
|
index = 3;
|
|
else if (doubleCompare(value, 1.0))
|
|
index = 4;
|
|
|
|
ui->comboBoxAnimation->setCurrentIndex(index);
|
|
}
|
|
|
|
QString TsMuxerWindow::getSrtParams()
|
|
{
|
|
auto &font = fontSettingsModel->font();
|
|
auto rez = QString(",font-name=\"%1\",font-size=%2,font-color=0x%3")
|
|
.arg(font.family())
|
|
.arg(font.pointSize())
|
|
.arg(fontSettingsModel->color(), 8, 16, QLatin1Char('0'));
|
|
|
|
if (ui->lineSpacing->value() != 1.0)
|
|
rez += ",line-spacing=" + QString::number(ui->lineSpacing->value());
|
|
|
|
if (font.italic())
|
|
rez += ",font-italic";
|
|
if (font.bold())
|
|
rez += ",font-bold";
|
|
if (font.underline())
|
|
rez += ",font-underline";
|
|
if (font.strikeOut())
|
|
rez += ",font-strikeout";
|
|
|
|
rez += QString(",bottom-offset=") + QString::number(ui->spinEditOffset->value()) +
|
|
",font-border=" + QString::number(ui->spinEditBorder->value());
|
|
if (ui->rbhLeft->isChecked())
|
|
rez += ",text-align=left";
|
|
else if (ui->rbhRight->isChecked())
|
|
rez += ",text-align=right";
|
|
else
|
|
rez += ",text-align=center";
|
|
|
|
double animationTime = getRendererAnimationTime();
|
|
if (animationTime > 0.0)
|
|
rez += QString(",fadein-time=%1,fadeout-time=%1").arg(animationTime);
|
|
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
if (ui->trackLV->item(i, 0)->checkState() == Qt::Checked)
|
|
{
|
|
auto codecInfo = getCodecInfo(i);
|
|
if (!codecInfo)
|
|
continue;
|
|
|
|
if (isVideoCodec(codecInfo->displayName))
|
|
{
|
|
rez += QString(",video-width=") + QString::number(codecInfo->width);
|
|
rez += QString(",video-height=") + QString::number(codecInfo->height);
|
|
rez += QString(",fps=") + fpsTextToFpsStr(codecInfo->fpsText);
|
|
return rez;
|
|
}
|
|
}
|
|
}
|
|
rez += ",video-width=1920,video-height=1080,fps=23.976";
|
|
return rez;
|
|
}
|
|
|
|
QString TsMuxerWindow::getFileList(QtvCodecInfo *codecInfo)
|
|
{
|
|
QString rezStr;
|
|
|
|
if (codecInfo->mplsFiles.isEmpty())
|
|
{
|
|
for (int i = 0; i < codecInfo->fileList.size(); ++i)
|
|
{
|
|
if (i > 0)
|
|
rezStr += '+';
|
|
rezStr += QString("\"") + codecInfo->fileList[i] + "\"";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < codecInfo->mplsFiles.size(); ++i)
|
|
{
|
|
if (i > 0)
|
|
rezStr += '+';
|
|
rezStr += QString("\"") + codecInfo->mplsFiles[i] + "\"";
|
|
}
|
|
}
|
|
|
|
return rezStr;
|
|
}
|
|
|
|
QString cornerToStr(int corner)
|
|
{
|
|
if (corner == 0)
|
|
return "topLeft";
|
|
else if (corner == 1)
|
|
return "topRight";
|
|
else if (corner == 2)
|
|
return "bottomRight";
|
|
else
|
|
return "bottomLeft";
|
|
}
|
|
|
|
QString toPipScaleStr(int scaleIdx)
|
|
{
|
|
if (scaleIdx == 0)
|
|
return "1";
|
|
if (scaleIdx == 1)
|
|
return "1/2";
|
|
else if (scaleIdx == 2)
|
|
return "1/4";
|
|
else if (scaleIdx == 3)
|
|
return "1.5";
|
|
else
|
|
return "fullScreen";
|
|
}
|
|
|
|
QString TsMuxerWindow::getVideoMetaInfo(QtvCodecInfo *codecInfo)
|
|
{
|
|
QString fpsStr;
|
|
QString levelStr;
|
|
QString rezStr = codecInfo->programName + ", ";
|
|
|
|
rezStr += getFileList(codecInfo);
|
|
|
|
if (codecInfo->checkFPS)
|
|
fpsStr = fpsTextToFpsStr(codecInfo->fpsText);
|
|
if (codecInfo->checkLevel)
|
|
levelStr = QString::number(codecInfo->levelText.toDouble(), 'f', 1);
|
|
|
|
if (ui->checkBoxCrop->isChecked() && ui->checkBoxCrop->isEnabled())
|
|
rezStr += QString(", ") + "restoreCrop";
|
|
if (!fpsStr.isEmpty())
|
|
rezStr += QString(", ") + "fps=" + fpsStr;
|
|
if (!levelStr.isEmpty())
|
|
rezStr += QString(", ") + "level=" + levelStr;
|
|
if (codecInfo->addSEIMethod == 1)
|
|
rezStr += QString(", ") + "insertSEI";
|
|
else if (codecInfo->addSEIMethod == 2)
|
|
rezStr += QString(", ") + "forceSEI";
|
|
if (codecInfo->addSPS)
|
|
rezStr += QString(", ") + "contSPS";
|
|
if (codecInfo->delPulldown == 1)
|
|
rezStr += QString(", ") + "delPulldown";
|
|
if (!codecInfo->arText.isEmpty())
|
|
rezStr += QString(", ") + "ar=" + codecInfo->arText;
|
|
|
|
if (codecInfo->isSecondary)
|
|
{
|
|
rezStr += QString(", secondary");
|
|
rezStr += QString(", pipCorner=%1").arg(cornerToStr(ui->comboBoxPipCorner->currentIndex()));
|
|
rezStr += QString(" ,pipHOffset=%1").arg(ui->spinBoxPipOffsetH->value());
|
|
rezStr += QString(" ,pipVOffset=%1").arg(ui->spinBoxPipOffsetV->value());
|
|
rezStr += QString(", pipScale=%1").arg(toPipScaleStr(ui->comboBoxPipSize->currentIndex()));
|
|
rezStr += QString(", pipLumma=%1").arg(ui->pipTransparencySpinBox->value());
|
|
}
|
|
|
|
return rezStr;
|
|
}
|
|
|
|
QString TsMuxerWindow::getAudioMetaInfo(QtvCodecInfo *codecInfo)
|
|
{
|
|
QString rezStr = codecInfo->programName + ", ";
|
|
rezStr += getFileList(codecInfo);
|
|
if (codecInfo->delay != 0)
|
|
rezStr += QString(", timeshift=") + QString::number(codecInfo->delay) + "ms";
|
|
if (codecInfo->dtsDownconvert && codecInfo->programName == "A_DTS")
|
|
rezStr += ", down-to-dts";
|
|
else if (codecInfo->dtsDownconvert && codecInfo->programName == "A_AC3")
|
|
rezStr += ", down-to-ac3";
|
|
if (codecInfo->isSecondary)
|
|
rezStr += ", secondary";
|
|
return rezStr;
|
|
}
|
|
|
|
void TsMuxerWindow::updateMuxTime1()
|
|
{
|
|
QTime t = ui->muxTimeEdit->time();
|
|
int clock = (t.hour() * 3600 + t.minute() * 60 + t.second()) * 45000ll + t.msec() * 45ll;
|
|
ui->muxTimeClock->blockSignals(true);
|
|
ui->muxTimeClock->setValue(clock);
|
|
ui->muxTimeClock->blockSignals(false);
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::updateMuxTime2()
|
|
{
|
|
double timeF = ui->muxTimeClock->value() / 45000.0;
|
|
ui->muxTimeEdit->blockSignals(true);
|
|
ui->muxTimeEdit->setTime(qTimeFromFloat(timeF));
|
|
ui->muxTimeEdit->blockSignals(false);
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onLanguageComboBoxIndexChanged(int idx)
|
|
{
|
|
auto lang = ui->languageSelectComboBox->itemData(idx).toString();
|
|
qtCoreTranslator.load(QString("qtbase_%1").arg(lang), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
|
tsMuxerTranslator.load(QString("tsmuxergui_%1").arg(lang), ":/i18n");
|
|
QFile aboutContent(QString(":/about_%1.html").arg(lang));
|
|
if (aboutContent.open(QIODevice::ReadOnly))
|
|
{
|
|
ui->textEdit->setHtml(QString::fromUtf8(aboutContent.readAll()));
|
|
}
|
|
else
|
|
{
|
|
qWarning() << "Failed to open about.html for language" << lang << aboutContent.errorString();
|
|
ui->textEdit->clear();
|
|
}
|
|
writeSettings();
|
|
}
|
|
|
|
void TsMuxerWindow::updateMetaLines()
|
|
{
|
|
if (!m_updateMeta || disableUpdatesCnt > 0)
|
|
return;
|
|
|
|
ui->memoMeta->clear();
|
|
QString metaContent;
|
|
metaContent.append(getMuxOpts() + '\n');
|
|
QString tmpFps;
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
auto codecInfo = getCodecInfo(i);
|
|
if (!codecInfo)
|
|
continue;
|
|
|
|
if (isVideoCodec(codecInfo->displayName))
|
|
{
|
|
if (codecInfo->checkFPS)
|
|
tmpFps = fpsTextToFpsStr(codecInfo->fpsText);
|
|
else
|
|
tmpFps = codecInfo->fpsTextOrig;
|
|
break;
|
|
}
|
|
}
|
|
|
|
QString prefix;
|
|
QString postfix;
|
|
|
|
bool bluray3D = isDiskOutput() && m_3dMode;
|
|
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
if (ui->trackLV->item(i, 0)->checkState() == Qt::Checked)
|
|
prefix = "";
|
|
else
|
|
prefix = "#";
|
|
auto codecInfo = getCodecInfo(i);
|
|
if (!codecInfo)
|
|
continue;
|
|
|
|
postfix.clear();
|
|
if (codecInfo->programName.startsWith('S'))
|
|
{
|
|
if (isDiskOutput() && ui->defaultSubTrackCheckBox->isChecked() &&
|
|
ui->defaultSubTrackComboBox->currentData().toInt() == i)
|
|
{
|
|
postfix +=
|
|
QString(", default=") + (ui->defaultSubTrackForcedOnlyCheckBox->isChecked() ? "forced" : "all");
|
|
}
|
|
}
|
|
if (codecInfo->displayName == "PGS")
|
|
{
|
|
if (codecInfo->bindFps && !tmpFps.isEmpty())
|
|
postfix += QString(", fps=") + tmpFps;
|
|
if (bluray3D && codecInfo->offsetId >= 0)
|
|
postfix += QString(", 3d-plane=%1").arg(codecInfo->offsetId);
|
|
}
|
|
if (codecInfo->displayName == "SRT")
|
|
{
|
|
postfix += getSrtParams();
|
|
if (bluray3D && codecInfo->offsetId >= 0)
|
|
postfix += QString(", offset=%1").arg(codecInfo->offsetId);
|
|
}
|
|
if (codecInfo->trackID != 0)
|
|
postfix += QString(", track=") + QString::number(codecInfo->trackID);
|
|
if (!codecInfo->lang.isEmpty())
|
|
postfix += QString(", lang=") + codecInfo->lang;
|
|
// if (!codecInfo->mplsFile.isEmpty())
|
|
// postfix += QString(", mplsFile=") + codecInfo->mplsFile;
|
|
if (codecInfo->subTrack != 0)
|
|
postfix += QString(", subTrack=") + QString::number(codecInfo->subTrack);
|
|
if (isVideoCodec(codecInfo->displayName))
|
|
metaContent.append(prefix + getVideoMetaInfo(codecInfo) + postfix + '\n');
|
|
else
|
|
{
|
|
if (isDiskOutput() && ui->defaultAudioTrackCheckBox->isChecked() &&
|
|
ui->defaultAudioTrackComboBox->currentData().toInt() == i && codecInfo->programName.startsWith('A'))
|
|
{
|
|
postfix += QString(", default");
|
|
}
|
|
metaContent.append(prefix + getAudioMetaInfo(codecInfo) + postfix + '\n');
|
|
}
|
|
}
|
|
ui->memoMeta->setPlainText(metaContent);
|
|
}
|
|
|
|
void TsMuxerWindow::onFontBtnClicked()
|
|
{
|
|
bool ok;
|
|
auto font = QFontDialog::getFont(&ok, fontSettingsModel->font(), this);
|
|
if (ok)
|
|
{
|
|
fontSettingsModel->setFont(font);
|
|
writeSettings();
|
|
updateMetaLines();
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::onColorBtnClicked()
|
|
{
|
|
QColor color = fontSettingsModel->color();
|
|
color = QColorDialog::getColor(color, this);
|
|
fontSettingsModel->setColor(color.rgba());
|
|
|
|
writeSettings();
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onGeneralCheckboxClicked()
|
|
{
|
|
ui->editMaxBitrate->setEnabled(ui->checkBoxRVBR->isChecked());
|
|
ui->editMinBitrate->setEnabled(ui->checkBoxRVBR->isChecked());
|
|
ui->editCBRBitrate->setEnabled(ui->checkBoxCBR->isChecked());
|
|
ui->BlackplaylistCombo->setEnabled(ui->checkBoxBlankPL->isChecked());
|
|
ui->BlackplaylistLabel->setEnabled(ui->checkBoxBlankPL->isChecked());
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onGeneralSpinboxValueChanged() { updateMetaLines(); }
|
|
|
|
void TsMuxerWindow::onChapterParamsChanged()
|
|
{
|
|
ui->memoChapters->setEnabled(ui->radioButtonCustomChapters->isChecked());
|
|
ui->spinEditChapterLen->setEnabled(ui->radioButtonAutoChapter->isChecked());
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onSplitCutParamsChanged()
|
|
{
|
|
ui->spinEditSplitDuration->setEnabled(ui->splitByDuration->isChecked());
|
|
ui->labelSplitByDur->setEnabled(ui->splitByDuration->isChecked());
|
|
ui->editSplitSize->setEnabled(ui->splitBySize->isChecked());
|
|
ui->comboBoxMeasure->setEnabled(ui->splitBySize->isChecked());
|
|
|
|
ui->cutStartTimeEdit->setEnabled(ui->checkBoxCut->isChecked());
|
|
ui->cutEndTimeEdit->setEnabled(ui->checkBoxCut->isChecked());
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onSavedParamChanged()
|
|
{
|
|
writeSettings();
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::onFontParamsChanged() { updateMetaLines(); }
|
|
|
|
void TsMuxerWindow::onRemoveBtnClick()
|
|
{
|
|
if (!ui->inputFilesLV->currentItem())
|
|
return;
|
|
int idx = ui->inputFilesLV->currentRow();
|
|
bool delMplsM2ts = false;
|
|
|
|
if (idx < ui->inputFilesLV->count() - 1)
|
|
{
|
|
if (ui->inputFilesLV->currentItem()->data(MplsItemRole).toInt() == MPLS_PRIMARY)
|
|
delMplsM2ts = true;
|
|
else if (ui->inputFilesLV->currentItem()->text().left(4) != FILE_JOIN_PREFIX)
|
|
{
|
|
QString text = ui->inputFilesLV->item(idx + 1)->text();
|
|
if (text.length() >= 4 && text.left(4) == FILE_JOIN_PREFIX)
|
|
ui->inputFilesLV->item(idx + 1)->setText(text.mid(4));
|
|
}
|
|
}
|
|
|
|
delTracksByFileName(myUnquoteStr(ui->inputFilesLV->currentItem()->data(FileNameRole).toString()));
|
|
ui->inputFilesLV->takeItem(idx);
|
|
if (idx >= ui->inputFilesLV->count())
|
|
idx--;
|
|
if (delMplsM2ts)
|
|
{
|
|
while (idx < ui->inputFilesLV->count())
|
|
{
|
|
QString text = ui->inputFilesLV->item(idx)->text();
|
|
if (text.length() >= 4 && text.left(4) == FILE_JOIN_PREFIX)
|
|
{
|
|
delTracksByFileName(myUnquoteStr(ui->inputFilesLV->item(idx)->data(FileNameRole).toString()));
|
|
ui->inputFilesLV->takeItem(idx);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
if (ui->inputFilesLV->count() > 0)
|
|
ui->inputFilesLV->setCurrentRow(idx);
|
|
updateCustomChapters();
|
|
}
|
|
|
|
void TsMuxerWindow::delTracksByFileName(const QString &fileName)
|
|
{
|
|
for (int i = ui->trackLV->rowCount() - 1; i >= 0; --i)
|
|
{
|
|
if (auto info = getCodecInfo(i))
|
|
{
|
|
for (int j = info->fileList.count() - 1; j >= 0; --j)
|
|
{
|
|
if (info->fileList[j] == fileName)
|
|
{
|
|
info->fileList.removeAt(j);
|
|
break;
|
|
}
|
|
}
|
|
for (int j = info->mplsFiles.count() - 1; j >= 0; --j)
|
|
{
|
|
if (info->mplsFiles[j] == fileName)
|
|
{
|
|
info->mplsFiles.removeAt(j);
|
|
break;
|
|
}
|
|
}
|
|
if (info->fileList.count() == 0)
|
|
deleteTrack(i);
|
|
}
|
|
}
|
|
updateMaxOffsets();
|
|
updateMetaLines();
|
|
}
|
|
|
|
void TsMuxerWindow::deleteTrack(int idx)
|
|
{
|
|
disableUpdatesCnt++;
|
|
removeTrackFromDefaultComboBox(idx);
|
|
delete getCodecInfo(idx);
|
|
int lastItemIndex = idx; // trackLV.items[idx].index;
|
|
ui->trackLV->removeRow(idx);
|
|
if (ui->trackLV->rowCount() == 0)
|
|
{
|
|
lastSourceDir.clear();
|
|
while (ui->tabWidgetTracks->count()) ui->tabWidgetTracks->removeTab(0);
|
|
ui->tabWidgetTracks->addTab(ui->tabSheetFake, TI_DEFAULT_TAB_NAME());
|
|
ui->outFileName->setText(getDefaultOutputFileName());
|
|
outFileNameModified = false;
|
|
}
|
|
else
|
|
{
|
|
if (lastItemIndex > ui->trackLV->rowCount() - 1)
|
|
--lastItemIndex;
|
|
if (lastItemIndex >= 0)
|
|
{
|
|
ui->trackLV->setCurrentCell(lastItemIndex, 0);
|
|
ui->trackLV->setRangeSelected(QTableWidgetSelectionRange(lastItemIndex, 0, lastItemIndex, 4), true);
|
|
}
|
|
updateNum();
|
|
}
|
|
updateMaxOffsets();
|
|
updateMetaLines();
|
|
ui->moveupBtn->setEnabled(ui->trackLV->currentItem() != 0);
|
|
ui->movedownBtn->setEnabled(ui->trackLV->currentItem() != 0);
|
|
ui->removeTrackBtn->setEnabled(ui->trackLV->currentItem() != 0);
|
|
disableUpdatesCnt--;
|
|
trackLVItemSelectionChanged();
|
|
updateTracksComboBox(ui->defaultAudioTrackComboBox);
|
|
updateTracksComboBox(ui->defaultSubTrackComboBox);
|
|
}
|
|
|
|
void TsMuxerWindow::updateNum()
|
|
{
|
|
// for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
// ui->trackLV->item(i,0)->setText(QString::number(i+1));
|
|
}
|
|
|
|
void TsMuxerWindow::onAppendButtonClick()
|
|
{
|
|
QList<QtvCodecInfo> codecList;
|
|
if (ui->inputFilesLV->currentItem() == 0)
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("No track selected"));
|
|
msgBox.setText(tr("No track selected"));
|
|
msgBox.setIcon(QMessageBox::Information);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
showAddFilesDialog(tr("Append media files"), [this]() { appendFile(); });
|
|
}
|
|
|
|
void TsMuxerWindow::appendFile()
|
|
{
|
|
processAddFileList(&TsMuxerWindow::continueAppendFile, &TsMuxerWindow::fileAppended, &TsMuxerWindow::appendFile);
|
|
}
|
|
|
|
void TsMuxerWindow::continueAppendFile()
|
|
{
|
|
QString parentFileName = myUnquoteStr((ui->inputFilesLV->currentItem()->data(FileNameRole).toString()));
|
|
QFileInfo newFi(unquoteStr(newFileName));
|
|
QFileInfo oldFi(unquoteStr(parentFileName));
|
|
if (newFi.suffix().toUpper() != oldFi.suffix().toUpper())
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Invalid file extension"));
|
|
msgBox.setText(tr("Appended file must have same file extension."));
|
|
msgBox.setIcon(QMessageBox::Information);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
|
|
disableUpdatesCnt++;
|
|
|
|
int idx = ui->inputFilesLV->currentRow();
|
|
bool firstStep = true;
|
|
bool doublePrefix = false;
|
|
for (; idx < ui->inputFilesLV->count(); ++idx)
|
|
{
|
|
int mplsRole = ui->inputFilesLV->item(idx)->data(MplsItemRole).toInt();
|
|
if ((mplsRole == MPLS_PRIMARY && firstStep) || mplsRole == MPLS_M2TS)
|
|
{
|
|
ui->inputFilesLV->setCurrentRow(idx);
|
|
doublePrefix = true;
|
|
}
|
|
else
|
|
break;
|
|
firstStep = false;
|
|
}
|
|
|
|
doAppendInt(newFileName, parentFileName, fileDuration, false, doublePrefix ? MPLS_PRIMARY : MPLS_NONE);
|
|
|
|
if (mplsFileList.size() > 0)
|
|
doAppendInt(mplsFileList[0].name, newFileName, mplsFileList[0].duration, doublePrefix, MPLS_M2TS);
|
|
for (int mplsCnt = 1; mplsCnt < mplsFileList.size(); ++mplsCnt)
|
|
doAppendInt(mplsFileList[mplsCnt].name, mplsFileList[mplsCnt - 1].name, mplsFileList[mplsCnt].duration,
|
|
doublePrefix, MPLS_M2TS);
|
|
|
|
updateMaxOffsets();
|
|
if (!outFileNameModified)
|
|
{
|
|
modifyOutFileName(newFileName);
|
|
outFileNameModified = true;
|
|
}
|
|
disableUpdatesCnt--;
|
|
updateMetaLines();
|
|
updateCustomChapters();
|
|
emit fileAppended();
|
|
}
|
|
|
|
void TsMuxerWindow::onRemoveTrackButtonClick()
|
|
{
|
|
if (ui->trackLV->currentItem())
|
|
deleteTrack(ui->trackLV->currentRow());
|
|
}
|
|
|
|
void TsMuxerWindow::onMoveUpButtonCLick()
|
|
{
|
|
if (ui->trackLV->currentItem() == 0 || ui->trackLV->currentRow() < 1)
|
|
return;
|
|
disableUpdatesCnt++;
|
|
auto preMoveRow = ui->trackLV->currentRow();
|
|
moveRow(preMoveRow, preMoveRow - 1);
|
|
moveTrackInDefaultComboBox(preMoveRow, preMoveRow - 1);
|
|
disableUpdatesCnt--;
|
|
updateMetaLines();
|
|
updateNum();
|
|
}
|
|
|
|
void TsMuxerWindow::onMoveDownButtonCLick()
|
|
{
|
|
if (ui->trackLV->currentItem() == 0 || ui->trackLV->rowCount() == 0 ||
|
|
ui->trackLV->currentRow() == ui->trackLV->rowCount() - 1)
|
|
return;
|
|
disableUpdatesCnt++;
|
|
auto preMoveRow = ui->trackLV->currentRow();
|
|
moveRow(preMoveRow, preMoveRow + 2);
|
|
moveTrackInDefaultComboBox(preMoveRow, preMoveRow + 1);
|
|
disableUpdatesCnt--;
|
|
updateMetaLines();
|
|
updateNum();
|
|
}
|
|
|
|
void TsMuxerWindow::moveRow(int index, int index2)
|
|
{
|
|
ui->trackLV->insertRow(index2);
|
|
ui->trackLV->setRowHeight(index2, 18);
|
|
if (index2 < index)
|
|
index++;
|
|
for (int i = 0; i < ui->trackLV->columnCount(); ++i)
|
|
ui->trackLV->setItem(index2, i, ui->trackLV->item(index, i)->clone());
|
|
ui->trackLV->removeRow(index);
|
|
if (index2 > index)
|
|
index2--;
|
|
ui->trackLV->setRangeSelected(QTableWidgetSelectionRange(index2, 0, index2, 4), true);
|
|
ui->trackLV->setCurrentCell(index2, 0);
|
|
}
|
|
|
|
void TsMuxerWindow::RadioButtonMuxClick()
|
|
{
|
|
if (outFileNameDisableChange)
|
|
return;
|
|
if (ui->radioButtonDemux->isChecked())
|
|
ui->buttonMux->setText(tr("Sta&rt demuxing"));
|
|
else
|
|
ui->buttonMux->setText(tr("Sta&rt muxing"));
|
|
ui->checkBoxNewAudioPes->setChecked(!ui->radioButtonTS->isChecked());
|
|
ui->checkBoxNewAudioPes->setEnabled(ui->radioButtonTS->isChecked() || ui->radioButtonM2TS->isChecked());
|
|
outFileNameDisableChange = true;
|
|
if (ui->radioButtonBluRay->isChecked() || ui->radioButtonDemux->isChecked() || ui->radioButtonAVCHD->isChecked())
|
|
{
|
|
QFileInfo fi(unquoteStr(ui->outFileName->text()));
|
|
if (!fi.suffix().isEmpty())
|
|
{
|
|
oldFileName = fi.fileName();
|
|
ui->outFileName->setText(QDir::toNativeSeparators(fi.absolutePath()) + QDir::separator());
|
|
}
|
|
ui->FilenameLabel->setText(tr("Folder"));
|
|
}
|
|
else
|
|
{
|
|
ui->FilenameLabel->setText(tr("File name"));
|
|
if (!oldFileName.isEmpty())
|
|
{
|
|
ui->outFileName->setText(QDir::toNativeSeparators(ui->outFileName->text()));
|
|
if (!ui->outFileName->text().isEmpty() && ui->outFileName->text().right(1) != QDir::separator())
|
|
ui->outFileName->setText(ui->outFileName->text() + QDir::separator());
|
|
ui->outFileName->setText(ui->outFileName->text() + oldFileName);
|
|
oldFileName.clear();
|
|
}
|
|
if (ui->radioButtonTS->isChecked())
|
|
{
|
|
ui->outFileName->setText(changeFileExt(ui->outFileName->text(), "ts"));
|
|
mSaveDialogFilter = TS_SAVE_DIALOG_FILTER();
|
|
}
|
|
else if (ui->radioButtonBluRayISO->isChecked())
|
|
{
|
|
ui->outFileName->setText(changeFileExt(ui->outFileName->text(), "iso"));
|
|
mSaveDialogFilter = ISO_SAVE_DIALOG_FILTER();
|
|
}
|
|
else
|
|
{
|
|
ui->outFileName->setText(changeFileExt(ui->outFileName->text(), "m2ts"));
|
|
mSaveDialogFilter = M2TS_SAVE_DIALOG_FILTER();
|
|
}
|
|
}
|
|
ui->DiskLabel->setVisible(ui->radioButtonBluRayISO->isChecked());
|
|
ui->DiskLabelEdit->setVisible(ui->radioButtonBluRayISO->isChecked());
|
|
ui->editDelay->setEnabled(!ui->radioButtonDemux->isChecked());
|
|
updateMetaLines();
|
|
outFileNameDisableChange = false;
|
|
}
|
|
|
|
void TsMuxerWindow::outFileNameChanged()
|
|
{
|
|
outFileNameModified = true;
|
|
if (outFileNameDisableChange)
|
|
return;
|
|
if (ui->radioButtonDemux->isChecked() || ui->radioButtonBluRay->isChecked() || ui->radioButtonAVCHD->isChecked())
|
|
return;
|
|
|
|
outFileNameDisableChange = true;
|
|
QFileInfo fi(unquoteStr(ui->outFileName->text().trimmed()));
|
|
QString ext = fi.suffix().toUpper();
|
|
|
|
bool isISOMode = ui->radioButtonBluRayISO->isChecked();
|
|
|
|
if (ext == "M2TS" || ext == "M2TS\"")
|
|
ui->radioButtonM2TS->setChecked(true);
|
|
else if (ext == "ISO" || ext == "ISO\"")
|
|
{
|
|
ui->radioButtonBluRayISO->setChecked(true);
|
|
}
|
|
else
|
|
ui->radioButtonTS->setChecked(true);
|
|
|
|
bool isISOModeNew = ui->radioButtonBluRayISO->isChecked();
|
|
|
|
ui->DiskLabel->setVisible(ui->radioButtonBluRayISO->isChecked());
|
|
ui->DiskLabelEdit->setVisible(ui->radioButtonBluRayISO->isChecked());
|
|
if (isISOMode != isISOModeNew)
|
|
updateMetaLines();
|
|
outFileNameDisableChange = false;
|
|
}
|
|
|
|
void TsMuxerWindow::saveFileDialog()
|
|
{
|
|
if (ui->radioButtonDemux->isChecked() || ui->radioButtonBluRay->isChecked() || ui->radioButtonAVCHD->isChecked())
|
|
{
|
|
QString folder = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, getOutputDir()));
|
|
if (!folder.isEmpty())
|
|
{
|
|
ui->outFileName->setText(folder + QDir::separator());
|
|
outFileNameModified = true;
|
|
lastOutputDir = folder;
|
|
writeSettings();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto fileName = unquoteStr(ui->outFileName->text());
|
|
auto path = fileName.isEmpty() ? getOutputDir() : QFileInfo(fileName).absoluteFilePath();
|
|
fileName = QDir::toNativeSeparators(
|
|
QFileDialog::getSaveFileName(this, tr("Select file for muxing"), path, mSaveDialogFilter));
|
|
if (!fileName.isEmpty())
|
|
{
|
|
ui->outFileName->setText(fileName);
|
|
lastOutputDir = QFileInfo(fileName).absolutePath();
|
|
writeSettings();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::startMuxing()
|
|
{
|
|
QString outputName = unquoteStr(ui->outFileName->text().trimmed());
|
|
ui->outFileName->setText(outputName);
|
|
lastOutputDir = QFileInfo(outputName).absolutePath();
|
|
writeSettings();
|
|
if (ui->radioButtonM2TS->isChecked())
|
|
{
|
|
QFileInfo fi(ui->outFileName->text());
|
|
if (fi.suffix().toUpper() != "M2TS")
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Invalid file name"));
|
|
msgBox.setText(tr("The output file \"%1\" has invalid extension. Please, change file extension "
|
|
"to \".m2ts\"")
|
|
.arg(ui->outFileName->text()));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
}
|
|
else if (ui->radioButtonBluRayISO->isChecked())
|
|
{
|
|
QFileInfo fi(ui->outFileName->text());
|
|
if (fi.suffix().toUpper() != "ISO")
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Invalid file name"));
|
|
msgBox.setText(tr("The output file \"%1\" has invalid extension. Please, change file extension to \".iso\"")
|
|
.arg(ui->outFileName->text()));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool isFile =
|
|
ui->radioButtonM2TS->isChecked() || ui->radioButtonTS->isChecked() || ui->radioButtonBluRayISO->isChecked();
|
|
if (isFile && QFile::exists(ui->outFileName->text()))
|
|
{
|
|
//: Used in expressions "Overwrite existing %1" and "The output %1 already exists".
|
|
auto fileOrDir = isFile ? tr("file") : tr("directory");
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Overwrite existing %1?").arg(fileOrDir));
|
|
msgBox.setText(tr("The output %1 \"%2\" already exists. Do you want to overwrite it?")
|
|
.arg(fileOrDir)
|
|
.arg(ui->outFileName->text()));
|
|
msgBox.setIcon(QMessageBox::Question);
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
if (msgBox.exec() != QMessageBox::Yes)
|
|
return;
|
|
}
|
|
|
|
QFileInfo fi(ui->outFileName->text());
|
|
metaName =
|
|
QDir::toNativeSeparators(QDir::tempPath()) + QDir::separator() + "tsMuxeR_" + fi.completeBaseName() + ".meta";
|
|
if (!saveMetaFile(metaName))
|
|
{
|
|
metaName.clear();
|
|
return;
|
|
}
|
|
muxForm->prepare(!ui->radioButtonDemux->isChecked() ? tr("Muxing in progress") : tr("Demuxing in progress"));
|
|
ui->buttonMux->setEnabled(false);
|
|
ui->addBtn->setEnabled(false);
|
|
ui->btnAppend->setEnabled(false);
|
|
muxForm->show();
|
|
disconnect();
|
|
// QCoreApplication::dir
|
|
runInMuxMode = true;
|
|
tsMuxerExecute(QStringList() << metaName << quoteStr(ui->outFileName->text()));
|
|
}
|
|
|
|
void TsMuxerWindow::saveMetaFileBtnClick()
|
|
{
|
|
QString metaName = QFileDialog::getSaveFileName(this, "", changeFileExt(ui->outFileName->text(), "meta"),
|
|
tr("tsMuxeR project file (*.meta);;All files (*.*)"));
|
|
if (metaName.isEmpty())
|
|
return;
|
|
QFileInfo fi(metaName);
|
|
QDir dir;
|
|
dir.mkpath(fi.absolutePath());
|
|
saveMetaFile(metaName);
|
|
}
|
|
|
|
bool TsMuxerWindow::saveMetaFile(const QString &metaName)
|
|
{
|
|
QFile file(metaName);
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
{
|
|
QMessageBox msgBox(this);
|
|
msgBox.setWindowTitle(tr("Can't create temporary meta file"));
|
|
msgBox.setText(tr("Can't create temporary meta file \"%1\"").arg(metaName));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return false;
|
|
}
|
|
QByteArray metaText = ui->memoMeta->toPlainText().toUtf8();
|
|
file.write(metaText);
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
void TsMuxerWindow::closeEvent(QCloseEvent *event)
|
|
{
|
|
if (!metaName.isEmpty())
|
|
{
|
|
QFile::remove(metaName);
|
|
metaName.clear();
|
|
}
|
|
muxForm->close();
|
|
event->accept();
|
|
}
|
|
|
|
void TsMuxerWindow::changeEvent(QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::LanguageChange)
|
|
{
|
|
ui->retranslateUi(this);
|
|
fontSettingsModel->onLanguageChanged();
|
|
}
|
|
QWidget::changeEvent(event);
|
|
}
|
|
|
|
void TsMuxerWindow::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
if (event->mimeData()->hasFormat("text/plain") || event->mimeData()->hasFormat("text/uri-list"))
|
|
{
|
|
if (ui->addBtn->isEnabled())
|
|
{
|
|
opacityTimer.stop();
|
|
setWindowOpacity(0.9);
|
|
event->acceptProposedAction();
|
|
QWidget *w = childAt(event->pos());
|
|
updateBtns(w);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::dropEvent(QDropEvent *event)
|
|
{
|
|
setWindowOpacity(1.0);
|
|
updateBtns(0);
|
|
if (event->mimeData()->hasFormat("text/uri-list"))
|
|
{
|
|
addFileList = event->mimeData()->urls();
|
|
event->acceptProposedAction();
|
|
}
|
|
else if (event->mimeData()->hasFormat("text/plain"))
|
|
{
|
|
QList<QString> strList;
|
|
addFileList.clear();
|
|
splitLines(event->mimeData()->text(), strList);
|
|
QList<QUrl> urls = event->mimeData()->urls();
|
|
for (int i = 0; i < strList.size(); ++i) addFileList << QUrl::fromLocalFile(strList[i]);
|
|
event->acceptProposedAction();
|
|
}
|
|
if (addFileList.isEmpty())
|
|
return;
|
|
auto w = childAt(event->pos());
|
|
if (w && w == ui->btnAppend && w->isEnabled())
|
|
appendFile();
|
|
else if (ui->addBtn->isEnabled())
|
|
addFile();
|
|
}
|
|
|
|
void TsMuxerWindow::dragMoveEvent(QDragMoveEvent *event)
|
|
{
|
|
event->acceptProposedAction();
|
|
QWidget *w = childAt(event->pos());
|
|
updateBtns(w);
|
|
}
|
|
|
|
void TsMuxerWindow::updateBtns(QWidget *w)
|
|
{
|
|
if (w)
|
|
{
|
|
ui->btnAppend->setDefault(w == ui->btnAppend && w->isEnabled());
|
|
ui->addBtn->setDefault(w == ui->addBtn && w->isEnabled());
|
|
}
|
|
else
|
|
{
|
|
ui->btnAppend->setDefault(false);
|
|
ui->addBtn->setDefault(false);
|
|
}
|
|
QFont font = ui->removeFile->font();
|
|
QFont bFont(font);
|
|
bFont.setBold(true);
|
|
if (ui->btnAppend->isDefault())
|
|
ui->btnAppend->setFont(bFont);
|
|
else
|
|
ui->btnAppend->setFont(font);
|
|
if (ui->addBtn->isDefault())
|
|
ui->addBtn->setFont(bFont);
|
|
else
|
|
ui->addBtn->setFont(font);
|
|
}
|
|
|
|
void TsMuxerWindow::dragLeaveEvent(QDragLeaveEvent *) { opacityTimer.start(100); }
|
|
|
|
void TsMuxerWindow::onOpacityTimer()
|
|
{
|
|
opacityTimer.stop();
|
|
setWindowOpacity(1.0);
|
|
updateBtns(0);
|
|
}
|
|
|
|
void TsMuxerWindow::updateMaxOffsets()
|
|
{
|
|
int maxPGOffsets = 0;
|
|
m_3dMode = false;
|
|
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i)
|
|
{
|
|
auto codecInfo = getCodecInfo(i);
|
|
if (!codecInfo)
|
|
continue;
|
|
|
|
if (codecInfo->displayName == "MVC")
|
|
{
|
|
m_3dMode = true;
|
|
maxPGOffsets = qMax(maxPGOffsets, codecInfo->maxPgOffsets);
|
|
}
|
|
}
|
|
|
|
disableUpdatesCnt++;
|
|
|
|
int oldIndex = ui->offsetsComboBox->currentIndex();
|
|
ui->offsetsComboBox->clear();
|
|
ui->offsetsComboBox->addItem(QString("zero"));
|
|
for (int i = 0; i < maxPGOffsets; ++i) ui->offsetsComboBox->addItem(QString("plane #%1").arg(i));
|
|
if (oldIndex >= 0 && oldIndex < ui->offsetsComboBox->count())
|
|
ui->offsetsComboBox->setCurrentIndex(oldIndex);
|
|
|
|
disableUpdatesCnt--;
|
|
}
|
|
|
|
bool TsMuxerWindow::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if (obj == ui->label_Donate && event->type() == QEvent::MouseButtonPress)
|
|
{
|
|
QDesktopServices::openUrl(QUrl("https://github.com/justdan96/tsMuxer"));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return QWidget::eventFilter(obj, event);
|
|
}
|
|
}
|
|
|
|
void TsMuxerWindow::at_sectionCheckstateChanged(Qt::CheckState state)
|
|
{
|
|
if (disableUpdatesCnt > 0)
|
|
return;
|
|
|
|
disableUpdatesCnt++;
|
|
for (int i = 0; i < ui->trackLV->rowCount(); ++i) ui->trackLV->item(i, 0)->setCheckState(state);
|
|
disableUpdatesCnt--;
|
|
trackLVItemSelectionChanged();
|
|
}
|
|
|
|
void TsMuxerWindow::writeSettings()
|
|
{
|
|
if (disableUpdatesCnt > 0)
|
|
return;
|
|
|
|
disableUpdatesCnt++;
|
|
|
|
settings->beginGroup("main");
|
|
// settings->setValue("asyncIO", ui->checkBoxuseAsynIO->isChecked());
|
|
settings->setValue("soundEnabled", ui->checkBoxSound->isChecked());
|
|
settings->setValue("hdmvPES", ui->checkBoxNewAudioPes->isChecked());
|
|
if (ui->checkBoxCrop->isEnabled())
|
|
settings->setValue("restoreCropEnabled", ui->checkBoxCrop->isChecked());
|
|
settings->setValue("inputDir", lastInputDir);
|
|
settings->setValue("outputDir", lastOutputDir);
|
|
settings->setValue("useBlankPL", ui->checkBoxBlankPL->isChecked());
|
|
settings->setValue("blankPLNum", ui->BlackplaylistCombo->value());
|
|
|
|
settings->setValue("outputToInputFolder", ui->radioButtonOutoutInInput->isChecked());
|
|
settings->setValue("language", ui->languageSelectComboBox->currentText());
|
|
settings->setValue("windowSize", size());
|
|
|
|
settings->endGroup();
|
|
|
|
settings->beginGroup("subtitles");
|
|
settings->setValue("fontBorder", ui->spinEditBorder->value());
|
|
settings->setValue("fontLineSpacing", ui->lineSpacing->value());
|
|
settings->setValue("offset", ui->spinEditOffset->value());
|
|
settings->setValue("fadeTime", getRendererAnimationTime());
|
|
settings->endGroup();
|
|
|
|
settings->beginGroup("pip");
|
|
settings->setValue("corner", ui->comboBoxPipCorner->currentIndex());
|
|
settings->setValue("h_offset", ui->spinBoxPipOffsetH->value());
|
|
settings->setValue("v_offset", ui->spinBoxPipOffsetV->value());
|
|
settings->setValue("size", ui->comboBoxPipSize->currentIndex());
|
|
settings->endGroup();
|
|
|
|
disableUpdatesCnt--;
|
|
}
|
|
|
|
bool TsMuxerWindow::readSettings()
|
|
{
|
|
// due to QTBUG-28893, the settings saved under "general" are not accessible
|
|
// when using .ini files for storage - those are used on Linux by default.
|
|
// newer GUI versions will save the settings under the "main" group to avoid
|
|
// that. the "general" group is still read in order to import the old settings
|
|
// on non-Linux systems where the bug doesn't occur.
|
|
if (!readGeneralSettings("main") && !readGeneralSettings("general"))
|
|
{
|
|
return false; // no settings still written
|
|
}
|
|
// checkBoxVBR checkBoxRVBR editMaxBitrate editMinBitrate checkBoxCBR
|
|
// editCBRBitrate editVBVLen
|
|
|
|
settings->beginGroup("subtitles");
|
|
ui->spinEditBorder->setValue(settings->value("fontBorder").toInt());
|
|
ui->lineSpacing->setValue(settings->value("fontLineSpacing").toDouble());
|
|
setRendererAnimationTime(settings->value("fadeTime").toDouble());
|
|
ui->spinEditOffset->setValue(settings->value("offset").toInt());
|
|
settings->endGroup();
|
|
|
|
settings->beginGroup("pip");
|
|
ui->comboBoxPipCorner->setCurrentIndex(settings->value("corner").toInt());
|
|
ui->spinBoxPipOffsetH->setValue(settings->value("h_offset").toInt());
|
|
ui->spinBoxPipOffsetV->setValue(settings->value("v_offset").toInt());
|
|
ui->comboBoxPipSize->setCurrentIndex(settings->value("size").toInt());
|
|
settings->endGroup();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TsMuxerWindow::readGeneralSettings(const QString &prefix)
|
|
{
|
|
settings->beginGroup(prefix);
|
|
|
|
auto size = settings->value("windowSize");
|
|
if (size.isValid() && size.canConvert<QSize>())
|
|
{
|
|
resize(size.toSize());
|
|
}
|
|
|
|
auto lang = settings->value("language");
|
|
if (lang.isValid())
|
|
{
|
|
ui->languageSelectComboBox->setCurrentText(lang.toString());
|
|
}
|
|
else
|
|
{
|
|
ui->languageSelectComboBox->setCurrentIndex(0);
|
|
}
|
|
|
|
if (!settings->contains("outputDir"))
|
|
{
|
|
settings->endGroup();
|
|
return false;
|
|
}
|
|
|
|
lastInputDir = settings->value("inputDir").toString();
|
|
lastOutputDir = settings->value("outputDir").toString();
|
|
|
|
// ui->checkBoxuseAsynIO->setChecked(settings->value("asyncIO").toBool());
|
|
ui->checkBoxSound->setChecked(settings->value("soundEnabled").toBool());
|
|
ui->checkBoxNewAudioPes->setChecked(settings->value("hdmvPES").toBool());
|
|
ui->checkBoxCrop->setChecked(settings->value("restoreCropEnabled").toBool());
|
|
ui->checkBoxBlankPL->setChecked(settings->value("useBlankPL").toBool());
|
|
int plNum = settings->value("blankPLNum").toInt();
|
|
if (plNum)
|
|
ui->BlackplaylistCombo->setValue(plNum);
|
|
|
|
ui->radioButtonOutoutInInput->setChecked(settings->value("outputToInputFolder").toBool());
|
|
ui->radioButtonStoreOutput->setChecked(!ui->radioButtonOutoutInInput->isChecked());
|
|
|
|
settings->endGroup();
|
|
return true;
|
|
}
|
|
|
|
template <typename OnCodecListReadyFn, typename PostActionSignal, typename PostActionFn>
|
|
void TsMuxerWindow::processAddFileList(OnCodecListReadyFn onCodecListReady, PostActionSignal postActionSignal,
|
|
PostActionFn postActionFn)
|
|
{
|
|
if (addFileList.isEmpty())
|
|
return;
|
|
newFileName = QDir::toNativeSeparators(addFileList[0].toLocalFile());
|
|
if (lastSourceDir.isEmpty())
|
|
lastSourceDir = QFileInfo(newFileName).absolutePath();
|
|
addFileList.removeAt(0);
|
|
if (!checkFileDuplicate(newFileName))
|
|
return;
|
|
// disconnect(this, SIGNAL(tsMuxerSuccessFinished()));
|
|
// disconnect(this, SIGNAL(codecListReady()));
|
|
disconnect();
|
|
connect(this, &TsMuxerWindow::tsMuxerSuccessFinished, this, &TsMuxerWindow::onTsMuxerCodecInfoReceived);
|
|
connect(this, &TsMuxerWindow::codecListReady, this, onCodecListReady);
|
|
connect(this, postActionSignal, this, postActionFn);
|
|
runInMuxMode = false;
|
|
tsMuxerExecute(QStringList() << newFileName);
|
|
}
|
|
|
|
template <typename F>
|
|
void TsMuxerWindow::showAddFilesDialog(QString &&windowTitle, F &&windowOkFn)
|
|
{
|
|
const auto files = QFileDialog::getOpenFileNames(this, windowTitle, lastInputDir, fileDialogFilter());
|
|
if (files.isEmpty())
|
|
return;
|
|
lastInputDir = QDir::toNativeSeparators(files.back());
|
|
addFileList.clear();
|
|
for (auto f : files)
|
|
{
|
|
addFileList << QUrl::fromLocalFile(QDir::toNativeSeparators(f));
|
|
}
|
|
windowOkFn();
|
|
}
|