tsMuxer/tsMuxerGUI/tsmuxerwindow.cpp

2831 lines
100 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 <unordered_set>
#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
{
using FileFilterVec = std::vector<std::pair<QString, std::vector<const char *>>>;
QString makeFileFilter(const FileFilterVec &filters)
{
QString rv;
QTextStream s(&rv);
for (auto &f : filters)
{
s << f.first << " (";
for (auto &e : f.second)
{
s << "*." << e;
if (&e != &f.second.back())
{
s << ' ';
}
}
s << ");;";
}
rv.append(QString("%1 (*.*)").arg(TsMuxerWindow::tr("All files")));
return rv;
}
QString fileDialogFilter()
{
FileFilterVec filters = {{TsMuxerWindow::tr("AC3/E-AC3"), {"ac3", "ddp", "ec3", "eac3"}},
{TsMuxerWindow::tr("AAC (advanced audio coding)"), {"aac"}},
{TsMuxerWindow::tr("AVC/MVC/H.264 elementary stream"), {"avc", "mvc", "264", "h264"}},
{TsMuxerWindow::tr("HEVC (High Efficiency Video Codec)"), {"hevc", "265", "h265"}},
{TsMuxerWindow::tr("Digital Theater System"), {"dts"}},
{TsMuxerWindow::tr("DTS-HD Master Audio"), {"dtshd", "dtsma"}},
{TsMuxerWindow::tr("TrueHD/AC3+TrueHD"), {"thd", "truehd", "ac3+thd", "thd+ac3"}},
{TsMuxerWindow::tr("Mpeg video elementary stream"), {"mpv", "m1v", "m2v"}},
{TsMuxerWindow::tr("Mpeg audio elementary stream"), {"mpa"}},
{TsMuxerWindow::tr("Transport Stream"), {"ts"}},
{TsMuxerWindow::tr("BDAV Transport Stream"), {"m2ts", "mts", "ssif"}},
{TsMuxerWindow::tr("Program Stream"), {"mpg", "mpeg", "vob", "evo"}},
{TsMuxerWindow::tr("Matroska audio/video files"), {"mkv", "mka", "mks"}},
{TsMuxerWindow::tr("MP4 audio/video files"), {"mp4", "m4a", "m4v"}},
{TsMuxerWindow::tr("QuickTime audio/video files"), {"mov"}},
{TsMuxerWindow::tr("Blu-ray play list"), {"mpls", "mpl"}},
{TsMuxerWindow::tr("Blu-ray PGS subtitles"), {"sup"}},
{TsMuxerWindow::tr("Text subtitles"), {"srt"}},
{TsMuxerWindow::tr("WAVE - Uncompressed PCM audio"), {"wav", "w64"}},
{TsMuxerWindow::tr("RAW LPCM Stream"), {"pcm"}}};
std::unordered_set<std::string> uniqueExts;
for (auto &f : filters)
{
uniqueExts.insert(std::begin(f.second), std::end(f.second));
}
std::vector<const char *> allSupportedExts;
for (auto &s : uniqueExts)
{
allSupportedExts.push_back(s.c_str());
}
filters.insert(std::begin(filters), {TsMuxerWindow::tr("All supported media files"), allSupportedExts});
return makeFileFilter(filters);
}
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 makeFileFilter({{TsMuxerWindow::tr("Transport Stream"), {"ts"}}}); }
QString M2TS_SAVE_DIALOG_FILTER()
{
return makeFileFilter({{TsMuxerWindow::tr("BDAV Transport Stream"), {"m2ts", "mts", "ssif"}}});
}
QString ISO_SAVE_DIALOG_FILTER() { return makeFileFilter({{TsMuxerWindow::tr("Disk image"), {"iso"}}}); }
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->addItem(QString::fromUtf8("Deutsch"), "de");
comboBox->addItem(QString::fromUtf8("עִברִית"), "he");
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);
}
langCodesModel = new LangCodesModel(this);
ui->langComboBox->setModel(langCodesModel);
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);
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());
// TODO: fix insertSEI option. Until then , the option is not default for H264/MVC
// 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 (TrackID %2) was not recognized and ignored. File name: \"%3\"").arg(i).arg(codecList[i].trackID).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();
langCodesModel->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();
}