#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" || displayName == "VVC";
}

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->addItem(QString::fromUtf8("Español"), "es");
    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[] = {31, 200, 62, 62, 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());
            /* Add SEI and SPS only with AVC and MVC (currently disabled)
            if (codecInfo->displayName != "H.264" && codecInfo->displayName != "MVC")
            {
                codecInfo->addSEIMethod = 0;
                codecInfo->addSPS = false;
            }
            */
            if (codecInfo->displayName == "HEVC")
                ui->checkBoxV3->setChecked(true);
            else if (codecInfo->displayName == "H.264" || codecInfo->displayName == "MVC" ||
                     codecInfo->displayName == "MPEG-2" || codecInfo->displayName == "VC-1")
                ui->checkBoxV3->setChecked(false);
            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))
        {
            codecInfo->addSEIMethod = ui->comboBoxSEI->currentIndex();
            codecInfo->addSPS = ui->checkBoxSPS->isChecked();

            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->descr.contains("(core 0Kbps)") &&
                                          (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";
    else
        rez += " --no-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)
    {
        auto codecInfo = getCodecInfo(i);
        if (!codecInfo)
            continue;

        if (isVideoCodec(codecInfo->displayName))
        {
            rez += QString(",video-width=") + QString::number(std::min(codecInfo->width, 1920));
            rez += QString(",video-height=") + QString::number(std::min(codecInfo->height, 1080));
            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("Start demuxing"));
    else
        ui->buttonMux->setText(tr("Start 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->setValue("addSEIMethod", ui->comboBoxSEI->currentIndex());
    settings->setValue("addSPS", ui->checkBoxSPS->isChecked());

    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());

    ui->comboBoxSEI->setCurrentIndex(settings->value("addSEIMethod").toInt());
    ui->checkBoxSPS->setChecked(settings->value("addSPS").toBool());

    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();
}
