| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009 | 
							- /******************************************************************************
 
-     Copyright (C) 2014 by Ruwen Hahn <[email protected]>
 
-     This program is free software: you can redistribute it and/or modify
 
-     it under the terms of the GNU General Public License as published by
 
-     the Free Software Foundation, either version 2 of the License, or
 
-     (at your option) any later version.
 
-     This program is distributed in the hope that it will be useful,
 
-     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
-     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
-     GNU General Public License for more details.
 
-     You should have received a copy of the GNU General Public License
 
-     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
- ******************************************************************************/
 
- #include "window-remux.hpp"
 
- #include "obs-app.hpp"
 
- #include <QCloseEvent>
 
- #include <QDirIterator>
 
- #include <QItemDelegate>
 
- #include <QLineEdit>
 
- #include <QMessageBox>
 
- #include <QMimeData>
 
- #include <QPainter>
 
- #include <QPushButton>
 
- #include <QStandardItemModel>
 
- #include <QStyledItemDelegate>
 
- #include <QToolButton>
 
- #include <QTimer>
 
- #include "qt-wrappers.hpp"
 
- #include "window-basic-main.hpp"
 
- #include <memory>
 
- #include <cmath>
 
- using namespace std;
 
- enum RemuxEntryColumn {
 
- 	State,
 
- 	InputPath,
 
- 	OutputPath,
 
- 	Count
 
- };
 
- enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole };
 
- /**********************************************************
 
-   Delegate - Presents cells in the grid.
 
- **********************************************************/
 
- RemuxEntryPathItemDelegate::RemuxEntryPathItemDelegate(
 
- 	bool isOutput, const QString &defaultPath)
 
- 	: QStyledItemDelegate(),
 
- 	  isOutput(isOutput),
 
- 	  defaultPath(defaultPath)
 
- {
 
- }
 
- QWidget *RemuxEntryPathItemDelegate::createEditor(
 
- 	QWidget *parent, const QStyleOptionViewItem & /* option */,
 
- 	const QModelIndex &index) const
 
- {
 
- 	RemuxEntryState state =
 
- 		index.model()
 
- 			->index(index.row(), RemuxEntryColumn::State)
 
- 			.data(RemuxEntryRole::EntryStateRole)
 
- 			.value<RemuxEntryState>();
 
- 	if (state == RemuxEntryState::Pending ||
 
- 	    state == RemuxEntryState::InProgress) {
 
- 		// Never allow modification of rows that are
 
- 		// in progress.
 
- 		return Q_NULLPTR;
 
- 	} else if (isOutput && state != RemuxEntryState::Ready) {
 
- 		// Do not allow modification of output rows
 
- 		// that aren't associated with a valid input.
 
- 		return Q_NULLPTR;
 
- 	} else if (!isOutput && state == RemuxEntryState::Complete) {
 
- 		// Don't allow modification of rows that are
 
- 		// already complete.
 
- 		return Q_NULLPTR;
 
- 	} else {
 
- 		QSizePolicy buttonSizePolicy(
 
- 			QSizePolicy::Policy::Minimum,
 
- 			QSizePolicy::Policy::Expanding,
 
- 			QSizePolicy::ControlType::PushButton);
 
- 		QWidget *container = new QWidget(parent);
 
- 		auto browseCallback = [this, container]() {
 
- 			const_cast<RemuxEntryPathItemDelegate *>(this)
 
- 				->handleBrowse(container);
 
- 		};
 
- 		auto clearCallback = [this, container]() {
 
- 			const_cast<RemuxEntryPathItemDelegate *>(this)
 
- 				->handleClear(container);
 
- 		};
 
- 		QHBoxLayout *layout = new QHBoxLayout();
 
- 		layout->setContentsMargins(0, 0, 0, 0);
 
- 		layout->setSpacing(0);
 
- 		QLineEdit *text = new QLineEdit();
 
- 		text->setObjectName(QStringLiteral("text"));
 
- 		text->setSizePolicy(
 
- 			QSizePolicy(QSizePolicy::Policy::Expanding,
 
- 				    QSizePolicy::Policy::Expanding,
 
- 				    QSizePolicy::ControlType::LineEdit));
 
- 		layout->addWidget(text);
 
- 		QObject::connect(text, &QLineEdit::editingFinished, this,
 
- 				 &RemuxEntryPathItemDelegate::updateText);
 
- 		QToolButton *browseButton = new QToolButton();
 
- 		browseButton->setText("...");
 
- 		browseButton->setSizePolicy(buttonSizePolicy);
 
- 		layout->addWidget(browseButton);
 
- 		container->connect(browseButton, &QToolButton::clicked,
 
- 				   browseCallback);
 
- 		// The "clear" button is not shown in output cells
 
- 		// or the insertion point's input cell.
 
- 		if (!isOutput && state != RemuxEntryState::Empty) {
 
- 			QToolButton *clearButton = new QToolButton();
 
- 			clearButton->setText("X");
 
- 			clearButton->setSizePolicy(buttonSizePolicy);
 
- 			layout->addWidget(clearButton);
 
- 			container->connect(clearButton, &QToolButton::clicked,
 
- 					   clearCallback);
 
- 		}
 
- 		container->setLayout(layout);
 
- 		container->setFocusProxy(text);
 
- 		return container;
 
- 	}
 
- }
 
- void RemuxEntryPathItemDelegate::setEditorData(QWidget *editor,
 
- 					       const QModelIndex &index) const
 
- {
 
- 	QLineEdit *text = editor->findChild<QLineEdit *>();
 
- 	text->setText(index.data().toString());
 
- 	editor->setProperty(PATH_LIST_PROP, QVariant());
 
- }
 
- void RemuxEntryPathItemDelegate::setModelData(QWidget *editor,
 
- 					      QAbstractItemModel *model,
 
- 					      const QModelIndex &index) const
 
- {
 
- 	// We use the PATH_LIST_PROP property to pass a list of
 
- 	// path strings from the editor widget into the model's
 
- 	// NewPathsToProcessRole. This is only used when paths
 
- 	// are selected through the "browse" or "delete" buttons
 
- 	// in the editor. If the user enters new text in the
 
- 	// text box, we simply pass that text on to the model
 
- 	// as normal text data in the default role.
 
- 	QVariant pathListProp = editor->property(PATH_LIST_PROP);
 
- 	if (pathListProp.isValid()) {
 
- 		QStringList list =
 
- 			editor->property(PATH_LIST_PROP).toStringList();
 
- 		if (isOutput) {
 
- 			if (list.size() > 0)
 
- 				model->setData(index, list);
 
- 		} else
 
- 			model->setData(index, list,
 
- 				       RemuxEntryRole::NewPathsToProcessRole);
 
- 	} else {
 
- 		QLineEdit *lineEdit = editor->findChild<QLineEdit *>();
 
- 		model->setData(index, lineEdit->text());
 
- 	}
 
- }
 
- void RemuxEntryPathItemDelegate::paint(QPainter *painter,
 
- 				       const QStyleOptionViewItem &option,
 
- 				       const QModelIndex &index) const
 
- {
 
- 	RemuxEntryState state =
 
- 		index.model()
 
- 			->index(index.row(), RemuxEntryColumn::State)
 
- 			.data(RemuxEntryRole::EntryStateRole)
 
- 			.value<RemuxEntryState>();
 
- 	QStyleOptionViewItem localOption = option;
 
- 	initStyleOption(&localOption, index);
 
- 	if (isOutput) {
 
- 		if (state != Ready) {
 
- 			QColor background = localOption.palette.color(
 
- 				QPalette::ColorGroup::Disabled,
 
- 				QPalette::ColorRole::Window);
 
- 			localOption.backgroundBrush = QBrush(background);
 
- 		}
 
- 	}
 
- 	QApplication::style()->drawControl(QStyle::CE_ItemViewItem,
 
- 					   &localOption, painter);
 
- }
 
- void RemuxEntryPathItemDelegate::handleBrowse(QWidget *container)
 
- {
 
- 	QString ExtensionPattern = "(*.mp4 *.flv *.mov *.mkv *.ts *.m3u8)";
 
- 	QLineEdit *text = container->findChild<QLineEdit *>();
 
- 	QString currentPath = text->text();
 
- 	if (currentPath.isEmpty())
 
- 		currentPath = defaultPath;
 
- 	bool isSet = false;
 
- 	if (isOutput) {
 
- 		QString newPath = SaveFile(container,
 
- 					   QTStr("Remux.SelectTarget"),
 
- 					   currentPath, ExtensionPattern);
 
- 		if (!newPath.isEmpty()) {
 
- 			container->setProperty(PATH_LIST_PROP,
 
- 					       QStringList() << newPath);
 
- 			isSet = true;
 
- 		}
 
- 	} else {
 
- 		QStringList paths = OpenFiles(
 
- 			container, QTStr("Remux.SelectRecording"), currentPath,
 
- 			QTStr("Remux.OBSRecording") + QString(" ") +
 
- 				ExtensionPattern);
 
- 		if (!paths.empty()) {
 
- 			container->setProperty(PATH_LIST_PROP, paths);
 
- 			isSet = true;
 
- 		}
 
- #ifdef __APPLE__
 
- 		// TODO: Revisit when QTBUG-42661 is fixed
 
- 		container->window()->raise();
 
- #endif
 
- 	}
 
- 	if (isSet)
 
- 		emit commitData(container);
 
- }
 
- void RemuxEntryPathItemDelegate::handleClear(QWidget *container)
 
- {
 
- 	// An empty string list will indicate that the entry is being
 
- 	// blanked and should be deleted.
 
- 	container->setProperty(PATH_LIST_PROP, QStringList());
 
- 	emit commitData(container);
 
- }
 
- void RemuxEntryPathItemDelegate::updateText()
 
- {
 
- 	QLineEdit *lineEdit = dynamic_cast<QLineEdit *>(sender());
 
- 	QWidget *editor = lineEdit->parentWidget();
 
- 	emit commitData(editor);
 
- }
 
- /**********************************************************
 
-   Model - Manages the queue's data
 
- **********************************************************/
 
- int RemuxQueueModel::rowCount(const QModelIndex &) const
 
- {
 
- 	return queue.length() + (isProcessing ? 0 : 1);
 
- }
 
- int RemuxQueueModel::columnCount(const QModelIndex &) const
 
- {
 
- 	return RemuxEntryColumn::Count;
 
- }
 
- QVariant RemuxQueueModel::data(const QModelIndex &index, int role) const
 
- {
 
- 	QVariant result = QVariant();
 
- 	if (index.row() >= queue.length()) {
 
- 		return QVariant();
 
- 	} else if (role == Qt::DisplayRole) {
 
- 		switch (index.column()) {
 
- 		case RemuxEntryColumn::InputPath:
 
- 			result = queue[index.row()].sourcePath;
 
- 			break;
 
- 		case RemuxEntryColumn::OutputPath:
 
- 			result = queue[index.row()].targetPath;
 
- 			break;
 
- 		}
 
- 	} else if (role == Qt::DecorationRole &&
 
- 		   index.column() == RemuxEntryColumn::State) {
 
- 		result = getIcon(queue[index.row()].state);
 
- 	} else if (role == RemuxEntryRole::EntryStateRole) {
 
- 		result = queue[index.row()].state;
 
- 	}
 
- 	return result;
 
- }
 
- QVariant RemuxQueueModel::headerData(int section, Qt::Orientation orientation,
 
- 				     int role) const
 
- {
 
- 	QVariant result = QVariant();
 
- 	if (role == Qt::DisplayRole &&
 
- 	    orientation == Qt::Orientation::Horizontal) {
 
- 		switch (section) {
 
- 		case RemuxEntryColumn::State:
 
- 			result = QString();
 
- 			break;
 
- 		case RemuxEntryColumn::InputPath:
 
- 			result = QTStr("Remux.SourceFile");
 
- 			break;
 
- 		case RemuxEntryColumn::OutputPath:
 
- 			result = QTStr("Remux.TargetFile");
 
- 			break;
 
- 		}
 
- 	}
 
- 	return result;
 
- }
 
- Qt::ItemFlags RemuxQueueModel::flags(const QModelIndex &index) const
 
- {
 
- 	Qt::ItemFlags flags = QAbstractTableModel::flags(index);
 
- 	if (index.column() == RemuxEntryColumn::InputPath) {
 
- 		flags |= Qt::ItemIsEditable;
 
- 	} else if (index.column() == RemuxEntryColumn::OutputPath &&
 
- 		   index.row() != queue.length()) {
 
- 		flags |= Qt::ItemIsEditable;
 
- 	}
 
- 	return flags;
 
- }
 
- bool RemuxQueueModel::setData(const QModelIndex &index, const QVariant &value,
 
- 			      int role)
 
- {
 
- 	bool success = false;
 
- 	if (role == RemuxEntryRole::NewPathsToProcessRole) {
 
- 		QStringList pathList = value.toStringList();
 
- 		if (pathList.size() == 0) {
 
- 			if (index.row() < queue.size()) {
 
- 				beginRemoveRows(QModelIndex(), index.row(),
 
- 						index.row());
 
- 				queue.removeAt(index.row());
 
- 				endRemoveRows();
 
- 			}
 
- 		} else {
 
- 			if (pathList.size() >= 1 &&
 
- 			    index.row() < queue.length()) {
 
- 				queue[index.row()].sourcePath = pathList[0];
 
- 				checkInputPath(index.row());
 
- 				pathList.removeAt(0);
 
- 				success = true;
 
- 			}
 
- 			if (pathList.size() > 0) {
 
- 				int row = index.row();
 
- 				int lastRow = row + pathList.size() - 1;
 
- 				beginInsertRows(QModelIndex(), row, lastRow);
 
- 				for (QString path : pathList) {
 
- 					RemuxQueueEntry entry;
 
- 					entry.sourcePath = path;
 
- 					entry.state = RemuxEntryState::Empty;
 
- 					queue.insert(row, entry);
 
- 					row++;
 
- 				}
 
- 				endInsertRows();
 
- 				for (row = index.row(); row <= lastRow; row++) {
 
- 					checkInputPath(row);
 
- 				}
 
- 				success = true;
 
- 			}
 
- 		}
 
- 	} else if (index.row() == queue.length()) {
 
- 		QString path = value.toString();
 
- 		if (!path.isEmpty()) {
 
- 			RemuxQueueEntry entry;
 
- 			entry.sourcePath = path;
 
- 			entry.state = RemuxEntryState::Empty;
 
- 			beginInsertRows(QModelIndex(), queue.length() + 1,
 
- 					queue.length() + 1);
 
- 			queue.append(entry);
 
- 			endInsertRows();
 
- 			checkInputPath(index.row());
 
- 			success = true;
 
- 		}
 
- 	} else {
 
- 		QString path = value.toString();
 
- 		if (path.isEmpty()) {
 
- 			if (index.column() == RemuxEntryColumn::InputPath) {
 
- 				beginRemoveRows(QModelIndex(), index.row(),
 
- 						index.row());
 
- 				queue.removeAt(index.row());
 
- 				endRemoveRows();
 
- 			}
 
- 		} else {
 
- 			switch (index.column()) {
 
- 			case RemuxEntryColumn::InputPath:
 
- 				queue[index.row()].sourcePath =
 
- 					value.toString();
 
- 				checkInputPath(index.row());
 
- 				success = true;
 
- 				break;
 
- 			case RemuxEntryColumn::OutputPath:
 
- 				queue[index.row()].targetPath =
 
- 					value.toString();
 
- 				emit dataChanged(index, index);
 
- 				success = true;
 
- 				break;
 
- 			}
 
- 		}
 
- 	}
 
- 	return success;
 
- }
 
- QVariant RemuxQueueModel::getIcon(RemuxEntryState state)
 
- {
 
- 	QVariant icon;
 
- 	QStyle *style = QApplication::style();
 
- 	switch (state) {
 
- 	case RemuxEntryState::Complete:
 
- 		icon = style->standardIcon(QStyle::SP_DialogApplyButton);
 
- 		break;
 
- 	case RemuxEntryState::InProgress:
 
- 		icon = style->standardIcon(QStyle::SP_ArrowRight);
 
- 		break;
 
- 	case RemuxEntryState::Error:
 
- 		icon = style->standardIcon(QStyle::SP_DialogCancelButton);
 
- 		break;
 
- 	case RemuxEntryState::InvalidPath:
 
- 		icon = style->standardIcon(QStyle::SP_MessageBoxWarning);
 
- 		break;
 
- 	default:
 
- 		break;
 
- 	}
 
- 	return icon;
 
- }
 
- void RemuxQueueModel::checkInputPath(int row)
 
- {
 
- 	RemuxQueueEntry &entry = queue[row];
 
- 	if (entry.sourcePath.isEmpty()) {
 
- 		entry.state = RemuxEntryState::Empty;
 
- 	} else {
 
- 		entry.sourcePath = QDir::toNativeSeparators(entry.sourcePath);
 
- 		QFileInfo fileInfo(entry.sourcePath);
 
- 		if (fileInfo.exists())
 
- 			entry.state = RemuxEntryState::Ready;
 
- 		else
 
- 			entry.state = RemuxEntryState::InvalidPath;
 
- 		QString newExt = ".mp4";
 
- 		QString suffix = fileInfo.suffix();
 
- 		if (suffix.contains("mov", Qt::CaseInsensitive) ||
 
- 		    suffix.contains("mp4", Qt::CaseInsensitive)) {
 
- 			newExt = ".remuxed." + suffix;
 
- 		}
 
- 		if (entry.state == RemuxEntryState::Ready)
 
- 			entry.targetPath = QDir::toNativeSeparators(
 
- 				fileInfo.path() + QDir::separator() +
 
- 				fileInfo.completeBaseName() + newExt);
 
- 	}
 
- 	if (entry.state == RemuxEntryState::Ready && isProcessing)
 
- 		entry.state = RemuxEntryState::Pending;
 
- 	emit dataChanged(index(row, 0), index(row, RemuxEntryColumn::Count));
 
- }
 
- QFileInfoList RemuxQueueModel::checkForOverwrites() const
 
- {
 
- 	QFileInfoList list;
 
- 	for (const RemuxQueueEntry &entry : queue) {
 
- 		if (entry.state == RemuxEntryState::Ready) {
 
- 			QFileInfo fileInfo(entry.targetPath);
 
- 			if (fileInfo.exists()) {
 
- 				list.append(fileInfo);
 
- 			}
 
- 		}
 
- 	}
 
- 	return list;
 
- }
 
- bool RemuxQueueModel::checkForErrors() const
 
- {
 
- 	bool hasErrors = false;
 
- 	for (const RemuxQueueEntry &entry : queue) {
 
- 		if (entry.state == RemuxEntryState::Error) {
 
- 			hasErrors = true;
 
- 			break;
 
- 		}
 
- 	}
 
- 	return hasErrors;
 
- }
 
- void RemuxQueueModel::clearAll()
 
- {
 
- 	beginRemoveRows(QModelIndex(), 0, queue.size() - 1);
 
- 	queue.clear();
 
- 	endRemoveRows();
 
- }
 
- void RemuxQueueModel::clearFinished()
 
- {
 
- 	int index = 0;
 
- 	for (index = 0; index < queue.size(); index++) {
 
- 		const RemuxQueueEntry &entry = queue[index];
 
- 		if (entry.state == RemuxEntryState::Complete) {
 
- 			beginRemoveRows(QModelIndex(), index, index);
 
- 			queue.removeAt(index);
 
- 			endRemoveRows();
 
- 			index--;
 
- 		}
 
- 	}
 
- }
 
- bool RemuxQueueModel::canClearFinished() const
 
- {
 
- 	bool canClearFinished = false;
 
- 	for (const RemuxQueueEntry &entry : queue)
 
- 		if (entry.state == RemuxEntryState::Complete) {
 
- 			canClearFinished = true;
 
- 			break;
 
- 		}
 
- 	return canClearFinished;
 
- }
 
- void RemuxQueueModel::beginProcessing()
 
- {
 
- 	for (RemuxQueueEntry &entry : queue)
 
- 		if (entry.state == RemuxEntryState::Ready)
 
- 			entry.state = RemuxEntryState::Pending;
 
- 	// Signal that the insertion point no longer exists.
 
- 	beginRemoveRows(QModelIndex(), queue.length(), queue.length());
 
- 	endRemoveRows();
 
- 	isProcessing = true;
 
- 	emit dataChanged(index(0, RemuxEntryColumn::State),
 
- 			 index(queue.length(), RemuxEntryColumn::State));
 
- }
 
- void RemuxQueueModel::endProcessing()
 
- {
 
- 	for (RemuxQueueEntry &entry : queue) {
 
- 		if (entry.state == RemuxEntryState::Pending) {
 
- 			entry.state = RemuxEntryState::Ready;
 
- 		}
 
- 	}
 
- 	// Signal that the insertion point exists again.
 
- 	if (!autoRemux) {
 
- 		beginInsertRows(QModelIndex(), queue.length(), queue.length());
 
- 		endInsertRows();
 
- 	}
 
- 	isProcessing = false;
 
- 	emit dataChanged(index(0, RemuxEntryColumn::State),
 
- 			 index(queue.length(), RemuxEntryColumn::State));
 
- }
 
- bool RemuxQueueModel::beginNextEntry(QString &inputPath, QString &outputPath)
 
- {
 
- 	bool anyStarted = false;
 
- 	for (int row = 0; row < queue.length(); row++) {
 
- 		RemuxQueueEntry &entry = queue[row];
 
- 		if (entry.state == RemuxEntryState::Pending) {
 
- 			entry.state = RemuxEntryState::InProgress;
 
- 			inputPath = entry.sourcePath;
 
- 			outputPath = entry.targetPath;
 
- 			QModelIndex index =
 
- 				this->index(row, RemuxEntryColumn::State);
 
- 			emit dataChanged(index, index);
 
- 			anyStarted = true;
 
- 			break;
 
- 		}
 
- 	}
 
- 	return anyStarted;
 
- }
 
- void RemuxQueueModel::finishEntry(bool success)
 
- {
 
- 	for (int row = 0; row < queue.length(); row++) {
 
- 		RemuxQueueEntry &entry = queue[row];
 
- 		if (entry.state == RemuxEntryState::InProgress) {
 
- 			if (success)
 
- 				entry.state = RemuxEntryState::Complete;
 
- 			else
 
- 				entry.state = RemuxEntryState::Error;
 
- 			QModelIndex index =
 
- 				this->index(row, RemuxEntryColumn::State);
 
- 			emit dataChanged(index, index);
 
- 			break;
 
- 		}
 
- 	}
 
- }
 
- /**********************************************************
 
-   The actual remux window implementation
 
- **********************************************************/
 
- OBSRemux::OBSRemux(const char *path, QWidget *parent, bool autoRemux_)
 
- 	: QDialog(parent),
 
- 	  queueModel(new RemuxQueueModel),
 
- 	  worker(new RemuxWorker()),
 
- 	  ui(new Ui::OBSRemux),
 
- 	  recPath(path),
 
- 	  autoRemux(autoRemux_)
 
- {
 
- 	setAcceptDrops(true);
 
- 	setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
 
- 	ui->setupUi(this);
 
- 	ui->progressBar->setVisible(false);
 
- 	ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
 
- 	ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)
 
- 		->setEnabled(false);
 
- 	if (autoRemux) {
 
- 		resize(280, 40);
 
- 		ui->tableView->hide();
 
- 		ui->buttonBox->hide();
 
- 		ui->label->hide();
 
- 	}
 
- 	ui->progressBar->setMinimum(0);
 
- 	ui->progressBar->setMaximum(1000);
 
- 	ui->progressBar->setValue(0);
 
- 	ui->tableView->setModel(queueModel);
 
- 	ui->tableView->setItemDelegateForColumn(
 
- 		RemuxEntryColumn::InputPath,
 
- 		new RemuxEntryPathItemDelegate(false, recPath));
 
- 	ui->tableView->setItemDelegateForColumn(
 
- 		RemuxEntryColumn::OutputPath,
 
- 		new RemuxEntryPathItemDelegate(true, recPath));
 
- 	ui->tableView->horizontalHeader()->setSectionResizeMode(
 
- 		QHeaderView::ResizeMode::Stretch);
 
- 	ui->tableView->horizontalHeader()->setSectionResizeMode(
 
- 		RemuxEntryColumn::State, QHeaderView::ResizeMode::Fixed);
 
- 	ui->tableView->setEditTriggers(
 
- 		QAbstractItemView::EditTrigger::CurrentChanged);
 
- 	ui->tableView->setTextElideMode(Qt::ElideMiddle);
 
- 	ui->tableView->setWordWrap(false);
 
- 	installEventFilter(CreateShortcutFilter());
 
- 	ui->buttonBox->button(QDialogButtonBox::Ok)
 
- 		->setText(QTStr("Remux.Remux"));
 
- 	ui->buttonBox->button(QDialogButtonBox::Reset)
 
- 		->setText(QTStr("Remux.ClearFinished"));
 
- 	ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)
 
- 		->setText(QTStr("Remux.ClearAll"));
 
- 	ui->buttonBox->button(QDialogButtonBox::Reset)->setDisabled(true);
 
- 	connect(ui->buttonBox->button(QDialogButtonBox::Ok),
 
- 		&QPushButton::clicked, this, &OBSRemux::beginRemux);
 
- 	connect(ui->buttonBox->button(QDialogButtonBox::Reset),
 
- 		&QPushButton::clicked, this, &OBSRemux::clearFinished);
 
- 	connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults),
 
- 		&QPushButton::clicked, this, &OBSRemux::clearAll);
 
- 	connect(ui->buttonBox->button(QDialogButtonBox::Close),
 
- 		&QPushButton::clicked, this, &OBSRemux::close);
 
- 	worker->moveToThread(&remuxer);
 
- 	remuxer.start();
 
- 	connect(worker.data(), &RemuxWorker::updateProgress, this,
 
- 		&OBSRemux::updateProgress);
 
- 	connect(&remuxer, &QThread::finished, worker.data(),
 
- 		&QObject::deleteLater);
 
- 	connect(worker.data(), &RemuxWorker::remuxFinished, this,
 
- 		&OBSRemux::remuxFinished);
 
- 	connect(this, &OBSRemux::remux, worker.data(), &RemuxWorker::remux);
 
- 	connect(queueModel.data(), &RemuxQueueModel::rowsInserted, this,
 
- 		&OBSRemux::rowCountChanged);
 
- 	connect(queueModel.data(), &RemuxQueueModel::rowsRemoved, this,
 
- 		&OBSRemux::rowCountChanged);
 
- 	QModelIndex index = queueModel->createIndex(0, 1);
 
- 	QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex",
 
- 				  Qt::QueuedConnection,
 
- 				  Q_ARG(const QModelIndex &, index));
 
- }
 
- bool OBSRemux::stopRemux()
 
- {
 
- 	if (!worker->isWorking)
 
- 		return true;
 
- 	// By locking the worker thread's mutex, we ensure that its
 
- 	// update poll will be blocked as long as we're in here with
 
- 	// the popup open.
 
- 	QMutexLocker lock(&worker->updateMutex);
 
- 	bool exit = false;
 
- 	if (QMessageBox::critical(nullptr, QTStr("Remux.ExitUnfinishedTitle"),
 
- 				  QTStr("Remux.ExitUnfinished"),
 
- 				  QMessageBox::Yes | QMessageBox::No,
 
- 				  QMessageBox::No) == QMessageBox::Yes) {
 
- 		exit = true;
 
- 	}
 
- 	if (exit) {
 
- 		// Inform the worker it should no longer be
 
- 		// working. It will interrupt accordingly in
 
- 		// its next update callback.
 
- 		worker->isWorking = false;
 
- 	}
 
- 	return exit;
 
- }
 
- OBSRemux::~OBSRemux()
 
- {
 
- 	stopRemux();
 
- 	remuxer.quit();
 
- 	remuxer.wait();
 
- }
 
- void OBSRemux::rowCountChanged(const QModelIndex &, int, int)
 
- {
 
- 	// See if there are still any rows ready to remux. Change
 
- 	// the state of the "go" button accordingly.
 
- 	// There must be more than one row, since there will always be
 
- 	// at least one row for the empty insertion point.
 
- 	if (queueModel->rowCount() > 1) {
 
- 		ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
 
- 		ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)
 
- 			->setEnabled(true);
 
- 		ui->buttonBox->button(QDialogButtonBox::Reset)
 
- 			->setEnabled(queueModel->canClearFinished());
 
- 	} else {
 
- 		ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
 
- 		ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)
 
- 			->setEnabled(false);
 
- 		ui->buttonBox->button(QDialogButtonBox::Reset)
 
- 			->setEnabled(false);
 
- 	}
 
- }
 
- void OBSRemux::dropEvent(QDropEvent *ev)
 
- {
 
- 	QStringList urlList;
 
- 	for (QUrl url : ev->mimeData()->urls()) {
 
- 		QFileInfo fileInfo(url.toLocalFile());
 
- 		if (fileInfo.isDir()) {
 
- 			QStringList directoryFilter;
 
- 			directoryFilter << "*.flv"
 
- 					<< "*.mp4"
 
- 					<< "*.mov"
 
- 					<< "*.mkv"
 
- 					<< "*.ts"
 
- 					<< "*.m3u8";
 
- 			QDirIterator dirIter(fileInfo.absoluteFilePath(),
 
- 					     directoryFilter, QDir::Files,
 
- 					     QDirIterator::Subdirectories);
 
- 			while (dirIter.hasNext()) {
 
- 				urlList.append(dirIter.next());
 
- 			}
 
- 		} else {
 
- 			urlList.append(fileInfo.canonicalFilePath());
 
- 		}
 
- 	}
 
- 	if (urlList.empty()) {
 
- 		QMessageBox::information(nullptr,
 
- 					 QTStr("Remux.NoFilesAddedTitle"),
 
- 					 QTStr("Remux.NoFilesAdded"),
 
- 					 QMessageBox::Ok);
 
- 	} else if (!autoRemux) {
 
- 		QModelIndex insertIndex =
 
- 			queueModel->index(queueModel->rowCount() - 1,
 
- 					  RemuxEntryColumn::InputPath);
 
- 		queueModel->setData(insertIndex, urlList,
 
- 				    RemuxEntryRole::NewPathsToProcessRole);
 
- 	}
 
- }
 
- void OBSRemux::dragEnterEvent(QDragEnterEvent *ev)
 
- {
 
- 	if (ev->mimeData()->hasUrls() && !worker->isWorking)
 
- 		ev->accept();
 
- }
 
- void OBSRemux::beginRemux()
 
- {
 
- 	if (worker->isWorking) {
 
- 		stopRemux();
 
- 		return;
 
- 	}
 
- 	bool proceedWithRemux = true;
 
- 	QFileInfoList overwriteFiles = queueModel->checkForOverwrites();
 
- 	if (!overwriteFiles.empty()) {
 
- 		QString message = QTStr("Remux.FileExists");
 
- 		message += "\n\n";
 
- 		for (QFileInfo fileInfo : overwriteFiles)
 
- 			message += fileInfo.canonicalFilePath() + "\n";
 
- 		if (OBSMessageBox::question(this,
 
- 					    QTStr("Remux.FileExistsTitle"),
 
- 					    message) != QMessageBox::Yes)
 
- 			proceedWithRemux = false;
 
- 	}
 
- 	if (!proceedWithRemux)
 
- 		return;
 
- 	// Set all jobs to "pending" first.
 
- 	queueModel->beginProcessing();
 
- 	ui->progressBar->setVisible(true);
 
- 	ui->buttonBox->button(QDialogButtonBox::Ok)
 
- 		->setText(QTStr("Remux.Stop"));
 
- 	setAcceptDrops(false);
 
- 	remuxNextEntry();
 
- }
 
- void OBSRemux::AutoRemux(QString inFile, QString outFile)
 
- {
 
- 	if (inFile != "" && outFile != "" && autoRemux) {
 
- 		ui->progressBar->setVisible(true);
 
- 		emit remux(inFile, outFile);
 
- 		autoRemuxFile = outFile;
 
- 	}
 
- }
 
- void OBSRemux::remuxNextEntry()
 
- {
 
- 	worker->lastProgress = 0.f;
 
- 	QString inputPath, outputPath;
 
- 	if (queueModel->beginNextEntry(inputPath, outputPath)) {
 
- 		emit remux(inputPath, outputPath);
 
- 	} else {
 
- 		queueModel->autoRemux = autoRemux;
 
- 		queueModel->endProcessing();
 
- 		if (!autoRemux) {
 
- 			OBSMessageBox::information(
 
- 				this, QTStr("Remux.FinishedTitle"),
 
- 				queueModel->checkForErrors()
 
- 					? QTStr("Remux.FinishedError")
 
- 					: QTStr("Remux.Finished"));
 
- 		}
 
- 		ui->progressBar->setVisible(autoRemux);
 
- 		ui->buttonBox->button(QDialogButtonBox::Ok)
 
- 			->setText(QTStr("Remux.Remux"));
 
- 		ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)
 
- 			->setEnabled(true);
 
- 		ui->buttonBox->button(QDialogButtonBox::Reset)
 
- 			->setEnabled(queueModel->canClearFinished());
 
- 		setAcceptDrops(true);
 
- 	}
 
- }
 
- void OBSRemux::closeEvent(QCloseEvent *event)
 
- {
 
- 	if (!stopRemux())
 
- 		event->ignore();
 
- 	else
 
- 		QDialog::closeEvent(event);
 
- }
 
- void OBSRemux::reject()
 
- {
 
- 	if (!stopRemux())
 
- 		return;
 
- 	QDialog::reject();
 
- }
 
- void OBSRemux::updateProgress(float percent)
 
- {
 
- 	ui->progressBar->setValue(percent * 10);
 
- }
 
- void OBSRemux::remuxFinished(bool success)
 
- {
 
- 	ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
 
- 	queueModel->finishEntry(success);
 
- 	if (autoRemux && autoRemuxFile != "") {
 
- 		QTimer::singleShot(3000, this, &OBSRemux::close);
 
- 		OBSBasic *main = OBSBasic::Get();
 
- 		main->ShowStatusBarMessage(
 
- 			QTStr("Basic.StatusBar.AutoRemuxedTo")
 
- 				.arg(autoRemuxFile));
 
- 	}
 
- 	remuxNextEntry();
 
- }
 
- void OBSRemux::clearFinished()
 
- {
 
- 	queueModel->clearFinished();
 
- }
 
- void OBSRemux::clearAll()
 
- {
 
- 	queueModel->clearAll();
 
- }
 
- /**********************************************************
 
-   Worker thread - Executes the libobs remux operation as a
 
-                   background process.
 
- **********************************************************/
 
- void RemuxWorker::UpdateProgress(float percent)
 
- {
 
- 	if (abs(lastProgress - percent) < 0.1f)
 
- 		return;
 
- 	emit updateProgress(percent);
 
- 	lastProgress = percent;
 
- }
 
- void RemuxWorker::remux(const QString &source, const QString &target)
 
- {
 
- 	isWorking = true;
 
- 	auto callback = [](void *data, float percent) {
 
- 		RemuxWorker *rw = static_cast<RemuxWorker *>(data);
 
- 		QMutexLocker lock(&rw->updateMutex);
 
- 		rw->UpdateProgress(percent);
 
- 		return rw->isWorking;
 
- 	};
 
- 	bool stopped = false;
 
- 	bool success = false;
 
- 	media_remux_job_t mr_job = nullptr;
 
- 	if (media_remux_job_create(&mr_job, QT_TO_UTF8(source),
 
- 				   QT_TO_UTF8(target))) {
 
- 		success = media_remux_job_process(mr_job, callback, this);
 
- 		media_remux_job_destroy(mr_job);
 
- 		stopped = !isWorking;
 
- 	}
 
- 	isWorking = false;
 
- 	emit remuxFinished(!stopped && success);
 
- }
 
 
  |