An advanced GUI torrent file creator with batch functionality, powered by PyQt and torf
from setuptools import find_packages, setup

with open("torf_gui/version.py") as f:
    exec(f.read())

setup(
    name="torf-gui",
    version=__version__,  # noqa: F821
    packages=find_packages(),
    entry_points={"gui_scripts": ["torf-gui = torf_gui.gui:main"]},
    install_requires=[
        "torf>=4.2.2",
        "humanfriendly>=10.0",
        "PyQt5>=5.15.9",
        "pyqtdarktheme>=2.1.0",
    ],
    # metadata for upload to PyPI
    author="Oliver Sayers",
    author_email="talk@savagecore.uk",
    description=(
        "An advanced GUI torrent file creator with batch functionality, "
        "powered by PyQt and torf."
    ),
    long_description=open("README.md").read(),
    keywords="bittorrent torrent",
    url="https://github.com/SavageCore/torf-gui",
    classifiers=[
        "Development Status :: 5 - Production/Stable",
        "Intended Audience :: End Users/Desktop",
        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Programming Language :: Python :: 3.12",
        "Programming Language :: Python :: 3.13",
    ],
)

setup.py
SavageCore/torf-gui
from .ui_about import Ui_AboutDialog
from .ui_mainwindow import Ui_MainWindow
from .version import __version__

torf_gui/__init__.py
SavageCore/torf-gui
#!/usr/bin/env python3

import json
import os
import stat
import sys
from datetime import datetime
from fnmatch import fnmatch

import humanfriendly
import qdarktheme
import torf
from PyQt5 import QtCore, QtWidgets

from torf_gui import Ui_AboutDialog, Ui_MainWindow, __version__

PROGRAM_NAME = "torf-gui"
PROGRAM_NAME_VERSION = f"{PROGRAM_NAME} {__version__}"
CREATOR = f"torf-gui/{__version__} (https://github.com/SavageCore/torf-gui)"

PIECE_SIZES = [None] + [2**i for i in range(14, 25)]

if getattr(sys, "frozen", False):
    _basedir = sys._MEIPASS
else:
    _basedir = os.path.dirname(__file__)


class CreateTorrentQThread(QtCore.QThread):
    progress_update = QtCore.pyqtSignal(str, int, int)
    onError = QtCore.pyqtSignal(str)

    def __init__(self, torrent, save_path):
        super().__init__()
        self.torrent = torrent
        self.save_path = save_path
        self.success = False

    def run(self):
        def progress_callback(*args):
            # Args: torrent, filepath, piece_count, piece_total
            # Emit: filename, piece_count, piece_total
            filename = os.path.split(args[1])[1]
            self.progress_update.emit(filename, args[2], args[3])
            return None

        self.torrent.creation_date = datetime.now()
        self.torrent.created_by = CREATOR
        try:
            self.success = self.torrent.generate(callback=progress_callback)
        except Exception as exc:
            self.onError.emit(str(exc))
            return
        if self.success:
            self.torrent.write(self.save_path, overwrite=True)


class CreateTorrentBatchQThread(QtCore.QThread):
    progress_update = QtCore.pyqtSignal(str, int, int)
    onError = QtCore.pyqtSignal(str)

    def __init__(
        self,
        path,
        exclude,
        save_dir,
        trackers,
        web_seeds,
        private,
        source,
        randomize_infohash,
        comment,
        include_md5,
    ):
        super().__init__()
        self.path = path
        self.exclude = exclude
        self.save_dir = save_dir
        self.trackers = trackers
        self.web_seeds = web_seeds
        self.private = private
        self.source = source
        self.randomize_infohash = randomize_infohash
        self.comment = comment
        self.include_md5 = include_md5

    def has_hidden_attribute(self, filepath):
        return bool(
            os.stat(filepath).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN
        )

    def is_hidden_file(self, path):
        name = os.path.basename(os.path.abspath(path))
        return name.startswith(".") or self.has_hidden_attribute(path)

    def run(self):
        def callback(*args):
            return None

        entries = os.listdir(self.path)
        for i, p in enumerate(entries):
            if any(fnmatch(p, ex) for ex in self.exclude):
                continue
            p = os.path.join(self.path, p)

            # Check if the file (p) is hidden

            if os.path.isfile(p) and not self.is_hidden_file(p):
                sfn = os.path.split(p)[1] + ".torrent"
                self.progress_update.emit(sfn, i, len(entries))
                t = torf.Torrent(
                    path=p,
                    exclude_globs=self.exclude,
                    trackers=self.trackers,
                    webseeds=self.web_seeds,
                    private=self.private,
                    source=self.source,
                    randomize_infohash=self.randomize_infohash,
                    comment=self.comment,
                    creation_date=datetime.now(),
                    created_by=CREATOR,
                )
                try:
                    self.success = t.generate(callback=callback)
                except Exception as exc:
                    self.onError.emit(str(exc))
                    return
                if self.isInterruptionRequested():
                    return
                if self.success:
                    t.write(os.path.join(self.save_dir, sfn), overwrite=True)


class TorfGUI(Ui_MainWindow):
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)

        self.torrent = None
        self.MainWindow = MainWindow

        self.actionImportProfile.triggered.connect(self.import_profile)
        self.actionExportProfile.triggered.connect(self.export_profile)
        self.actionAbout.triggered.connect(self.showAboutDialog)
        self.actionQuit.triggered.connect(self.MainWindow.close)

        self.fileRadioButton.toggled.connect(self.inputModeToggle)
        self.fileRadioButton.setChecked(True)
        self.directoryRadioButton.toggled.connect(self.inputModeToggle)

        self.browseButton.clicked.connect(self.browseInput)
        self.batchModeCheckBox.stateChanged.connect(self.batchModeChanged)

        self.inputEdit.dragEnterEvent = self.inputDragEnterEvent
        self.inputEdit.dropEvent = self.inputDropEvent
        self.pasteButton.clicked.connect(self.pasteInput)

        self.pieceCountLabel.hide()
        self.pieceSizeComboBox.addItem("Auto")
        for x in PIECE_SIZES[1:]:
            self.pieceSizeComboBox.addItem(
                humanfriendly.format_size(x, binary=True)
            )

        self.pieceSizeComboBox.currentIndexChanged.connect(
            self.pieceSizeChanged
        )

        self.privateTorrentCheckBox.stateChanged.connect(
            self.privateTorrentChanged
        )

        self.commentEdit.textEdited.connect(self.commentEdited)

        self.sourceEdit.textEdited.connect(self.sourceEdited)

        self.randomizeInfoHashCheckBox.stateChanged.connect(
            self.randomizeInfohashChanged
        )

        self.md5CheckBox.stateChanged.connect(self.md5Changed)

        self.progressBar.hide()
        self.createButton.setEnabled(False)
        self.createButton.clicked.connect(self.createButtonClicked)
        self.cancelButton.hide()
        self.cancelButton.clicked.connect(self.cancel_creation)
        self.resetButton.clicked.connect(self.reset)

        self._statusBarMsg("Ready")

    def getSettings(self):
        portable_fn = PROGRAM_NAME + ".ini"
        portable_fn = os.path.join(_basedir, portable_fn)
        if os.path.exists(portable_fn):
            return QtCore.QSettings(portable_fn, QtCore.QSettings.IniFormat)
        return QtCore.QSettings(
            QtCore.QSettings.IniFormat,
            QtCore.QSettings.UserScope,
            PROGRAM_NAME,
            PROGRAM_NAME,
        )

    def loadSettings(self):
        settings = self.getSettings()
        if settings.value("input/mode") == "directory":
            self.directoryRadioButton.setChecked(True)
        batch_mode = bool(int(settings.value("input/batch_mode") or 0))
        self.batchModeCheckBox.setChecked(batch_mode)
        exclude = settings.value("input/exclude")
        if exclude:
            self.excludeEdit.setPlainText(exclude)
        trackers = settings.value("seeding/trackers")
        if trackers:
            self.trackerEdit.setPlainText(trackers)
        web_seeds = settings.value("seeding/web_seeds")
        if web_seeds:
            self.webSeedEdit.setPlainText(web_seeds)
        private = bool(int(settings.value("options/private") or 0))
        self.privateTorrentCheckBox.setChecked(private)
        source = settings.value("options/source")
        if source:
            self.sourceEdit.setText(source)
        randomize_infohash = bool(
            int(settings.value("options/randomize_infohash") or 0)
        )
        self.randomizeInfoHashCheckBox.setChecked(randomize_infohash)
        compute_md5 = bool(int(settings.value("options/compute_md5") or 0))
        if compute_md5:
            self.md5CheckBox.setChecked(compute_md5)
        mainwindow_size = settings.value("geometry/size")
        if mainwindow_size:
            self.MainWindow.resize(mainwindow_size)
        mainwindow_position = settings.value("geometry/position")
        if mainwindow_position:
            self.MainWindow.move(mainwindow_position)
        self.last_input_dir = settings.value("history/last_input_dir") or None
        self.last_output_dir = (
            settings.value("history/last_output_dir") or None
        )

    def saveSettings(self):
        settings = self.getSettings()
        settings.setValue("input/mode", self.inputMode)
        settings.setValue(
            "input/batch_mode", int(self.batchModeCheckBox.isChecked())
        )
        settings.setValue("input/exclude", self.excludeEdit.toPlainText())
        settings.setValue("seeding/trackers", self.trackerEdit.toPlainText())
        settings.setValue("seeding/web_seeds", self.webSeedEdit.toPlainText())
        settings.setValue(
            "options/private", int(self.privateTorrentCheckBox.isChecked())
        )
        settings.setValue("options/source", self.sourceEdit.text())
        settings.setValue(
            "options/randomize_infohash",
            int(self.randomizeInfoHashCheckBox.isChecked()),
        )
        settings.setValue(
            "options/compute_md5", int(self.md5CheckBox.isChecked())
        )
        settings.setValue("geometry/size", self.MainWindow.size())
        settings.setValue("geometry/position", self.MainWindow.pos())
        if self.last_input_dir:
            settings.setValue("history/last_input_dir", self.last_input_dir)
        if self.last_output_dir:
            settings.setValue("history/last_output_dir", self.last_output_dir)

    def _statusBarMsg(self, msg):
        self.MainWindow.statusBar().showMessage(msg)

    def _showError(self, msg):
        errdlg = QtWidgets.QErrorMessage()
        errdlg.setWindowTitle("Error")
        errdlg.showMessage(msg)
        errdlg.exec_()

    def showAboutDialog(self):
        qdlg = QtWidgets.QDialog()
        ad = Ui_AboutDialog()
        ad.setupUi(qdlg)
        ad.programVersionLabel.setText(f"version {__version__}")
        ad.dtVersionLabel.setText(f"(torf {torf.__version__})")
        qdlg.exec_()

    def inputModeToggle(self):
        if self.fileRadioButton.isChecked():
            self.inputMode = "file"
            self.batchModeCheckBox.setEnabled(False)
            self.batchModeCheckBox.hide()
        else:
            self.inputMode = "directory"
            self.batchModeCheckBox.setEnabled(True)
            self.batchModeCheckBox.show()
        self.inputEdit.setText("")

    def browseInput(self):
        qfd = QtWidgets.QFileDialog(self.MainWindow)
        if self.last_input_dir and os.path.exists(self.last_input_dir):
            qfd.setDirectory(self.last_input_dir)
        if self.inputMode == "file":
            qfd.setWindowTitle("Select file")
            qfd.setFileMode(QtWidgets.QFileDialog.ExistingFile)
        else:
            qfd.setWindowTitle("Select directory")
            qfd.setFileMode(QtWidgets.QFileDialog.Directory)
        if qfd.exec_():
            fn = qfd.selectedFiles()[0]
            self.inputEdit.setText(fn)
            self.last_input_dir = os.path.split(fn)[0]
            self.initializeTorrent()

    def injectInputPath(self, path):
        if os.path.exists(path):
            if os.path.isfile(path):
                self.fileRadioButton.setChecked(True)
                self.inputMode = "file"
                self.batchModeCheckBox.setCheckState(QtCore.Qt.Unchecked)
                self.batchModeCheckBox.setEnabled(False)
                self.batchModeCheckBox.hide()
            else:
                self.directoryRadioButton.setChecked(True)
                self.inputMode = "directory"
                self.batchModeCheckBox.setEnabled(True)
                self.batchModeCheckBox.show()
            self.inputEdit.setText(path)
            self.last_input_dir = os.path.split(path)[0]
            self.initializeTorrent()

    def inputDragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            if len(urls) == 1 and urls[0].isLocalFile():
                event.accept()
                return
        event.ignore()

    def inputDropEvent(self, event):
        path = event.mimeData().urls()[0].toLocalFile()
        self.injectInputPath(path)

    def pasteInput(self):
        mimeData = self.clipboard().mimeData()
        if mimeData.hasText():
            path = mimeData.text().strip("'\"")
            self.injectInputPath(path)

    def batchModeChanged(self, state):
        if state == QtCore.Qt.Checked:
            self.pieceSizeComboBox.setCurrentIndex(0)
            self.pieceSizeComboBox.setEnabled(False)
            self.pieceCountLabel.hide()
        else:
            self.pieceSizeComboBox.setEnabled(True)
            if self.torrent:
                self.pieceCountLabel.show()

    def initializeTorrent(self):
        self.torrent = torf.Torrent(self.inputEdit.text())
        try:
            t_info = self.get_info(self.torrent)
        except Exception as e:
            self.torrent = None
            self._showError(str(e))
            return
        ptail = os.path.split(self.torrent.path)[1]
        if self.inputMode == "file":
            self._statusBarMsg(
                f"{ptail}: {humanfriendly.format_size(t_info[0], binary=True)}"
            )
        else:
            self._statusBarMsg(
                "{}: {} files, {}".format(
                    ptail,
                    t_info[1],
                    humanfriendly.format_size(t_info[0], binary=True),
                )
            )
        self.pieceSizeComboBox.setCurrentIndex(0)
        self.updatePieceCountLabel(t_info[2], t_info[3])
        self.pieceCountLabel.show()
        self.createButton.setEnabled(True)

    def commentEdited(self, comment):
        if getattr(self, "torrent", None):
            self.torrent.comment = comment

    def sourceEdited(self, source):
        if getattr(self, "torrent", None):
            self.torrent.source = source

    def pieceSizeChanged(self, index):
        if getattr(self, "torrent", None):
            self.torrent.piece_size = PIECE_SIZES[index]
            t_info = self.get_info(self.torrent)
            self.updatePieceCountLabel(t_info[2], t_info[3])

    def updatePieceCountLabel(self, ps, pc):
        ps = humanfriendly.format_size(ps, binary=True)
        self.pieceCountLabel.setText(f"{pc} pieces @ {ps} each")

    def privateTorrentChanged(self, state):
        if getattr(self, "torrent", None):
            self.torrent.private = state == QtCore.Qt.Checked

    def randomizeInfohashChanged(self, state):
        if getattr(self, "torrent", None):
            self.torrent.randomize_infohash = state == QtCore.Qt.Checked

    def md5Changed(self, state):
        if getattr(self, "torrent", None):
            self.torrent.include_md5 = state == QtCore.Qt.Checked

    def createButtonClicked(self):
        self.torrent.exclude_globs = (
            self.excludeEdit.toPlainText().strip().splitlines()
        )
        # Validate trackers and web seed URLs
        trackers = self.trackerEdit.toPlainText().strip().split()
        web_seeds = self.webSeedEdit.toPlainText().strip().split()
        try:
            self.torrent.trackers = trackers
            self.torrent.webseeds = web_seeds
        except Exception as e:
            self._showError(str(e))
            return
        self.torrent.private = self.privateTorrentCheckBox.isChecked()
        self.torrent.randomize_infohash = (
            self.randomizeInfoHashCheckBox.isChecked()
        )
        self.torrent.comment = self.commentEdit.text() or None
        self.torrent.source = self.sourceEdit.text() or None
        self.torrent.include_md5 = self.md5CheckBox.isChecked()
        if (
            self.inputMode == "directory"
            and self.batchModeCheckBox.isChecked()
        ):
            self.createTorrentBatch()
        else:
            self.createTorrent()

    def createTorrent(self):
        if os.path.isfile(self.inputEdit.text()):
            save_fn = (
                os.path.splitext(os.path.split(self.inputEdit.text())[1])[0]
                + ".torrent"
            )
        else:
            save_fn = self.inputEdit.text().split(os.sep)[-1] + ".torrent"
        if self.last_output_dir and os.path.exists(self.last_output_dir):
            save_fn = os.path.join(self.last_output_dir, save_fn)
        fn = QtWidgets.QFileDialog.getSaveFileName(
            self.MainWindow,
            "Save torrent",
            save_fn,
            filter=("Torrent file (*.torrent)"),
        )[0]
        if fn:
            self.last_output_dir = os.path.split(fn)[0]
            self.creation_thread = CreateTorrentQThread(self.torrent, fn)
            self.creation_thread.started.connect(self.creation_started)
            self.creation_thread.progress_update.connect(self._progress_update)
            self.creation_thread.finished.connect(self.creation_finished)
            self.creation_thread.onError.connect(self._showError)
            self.creation_thread.start()

    def createTorrentBatch(self):
        save_dir = QtWidgets.QFileDialog.getExistingDirectory(
            self.MainWindow, "Select output directory", self.last_output_dir
        )
        if save_dir:
            self.last_output_dir = save_dir
            trackers = self.trackerEdit.toPlainText().strip().split()
            web_seeds = self.webSeedEdit.toPlainText().strip().split()
            self.creation_thread = CreateTorrentBatchQThread(
                path=self.inputEdit.text(),
                exclude=self.excludeEdit.toPlainText().strip().splitlines(),
                save_dir=save_dir,
                trackers=trackers,
                web_seeds=web_seeds,
                private=self.privateTorrentCheckBox.isChecked(),
                source=self.sourceEdit.text(),
                comment=self.commentEdit.text(),
                randomize_infohash=self.randomizeInfoHashCheckBox.isChecked(),
                include_md5=self.md5CheckBox.isChecked(),
            )
            self.creation_thread.started.connect(self.creation_started)
            self.creation_thread.progress_update.connect(
                self._progress_update_batch
            )
            self.creation_thread.finished.connect(self.creation_finished)
            self.creation_thread.onError.connect(self._showError)
            self.creation_thread.start()

    def cancel_creation(self):
        self.creation_thread.terminate()

    def _progress_update(self, fn, pc, pt):
        fn = os.path.split(fn)[1]
        msg = f"{fn} ({pc}/{pt})"
        self.updateProgress(msg, int(round(100 * pc / pt)))

    def _progress_update_batch(self, fn, tc, tt):
        msg = f"({tc}/{tt}) {fn}"
        self.updateProgress(msg, int(round(100 * tc / tt)))

    def updateProgress(self, statusMsg, pv):
        self._statusBarMsg(statusMsg)
        self.progressBar.setValue(pv)

    def creation_started(self):
        self.inputGroupBox.setEnabled(False)
        self.seedingGroupBox.setEnabled(False)
        self.optionGroupBox.setEnabled(False)
        self.progressBar.show()
        self.createButton.hide()
        self.cancelButton.show()
        self.resetButton.setEnabled(False)

    def creation_finished(self):
        self.inputGroupBox.setEnabled(True)
        self.seedingGroupBox.setEnabled(True)
        self.optionGroupBox.setEnabled(True)
        self.progressBar.hide()
        self.createButton.show()
        self.cancelButton.hide()
        self.resetButton.setEnabled(True)
        if self.creation_thread.success:
            self._statusBarMsg("Finished")
        else:
            self._statusBarMsg("Canceled")
        self.creation_thread = None

    def export_profile(self):
        fn = QtWidgets.QFileDialog.getSaveFileName(
            self.MainWindow,
            "Save profile",
            self.last_output_dir,
            filter=("JSON configuration file (*.json)"),
        )[0]
        if fn:
            exclude = self.excludeEdit.toPlainText().strip().splitlines()
            trackers = self.trackerEdit.toPlainText().strip().split()
            web_seeds = self.webSeedEdit.toPlainText().strip().split()
            private = self.privateTorrentCheckBox.isChecked()
            randomize_infohash = self.randomizeInfoHashCheckBox.isChecked()
            compute_md5 = self.md5CheckBox.isChecked()
            source = self.sourceEdit.text()
            data = {
                "exclude": exclude,
                "trackers": trackers,
                "web_seeds": web_seeds,
                "private": private,
                "compute_md5": compute_md5,
                "randomize_infohash": randomize_infohash,
                "source": source,
            }
            with open(fn, "w") as f:
                json.dump(data, f, indent=4, sort_keys=True)
            self._statusBarMsg("Profile saved to " + fn)

    def import_profile(self):
        fn = QtWidgets.QFileDialog.getOpenFileName(
            self.MainWindow,
            "Open profile",
            self.last_input_dir,
            filter=("JSON configuration file (*.json)"),
        )[0]
        if fn:
            with open(fn) as f:
                data = json.load(f)
            exclude = data.get("exclude", [])
            trackers = data.get("trackers", [])
            web_seeds = data.get("web_seeds", [])
            private = data.get("private", False)
            randomize_infohash = data.get("randomize_infohash", False)
            compute_md5 = data.get("compute_md5", False)
            source = data.get("source", "")
            try:
                self.excludeEdit.setPlainText(os.linesep.join(exclude))
                self.trackerEdit.setPlainText(os.linesep.join(trackers))
                self.webSeedEdit.setPlainText(os.linesep.join(web_seeds))
                self.privateTorrentCheckBox.setChecked(private)
                self.randomizeInfoHashCheckBox.setChecked(randomize_infohash)
                self.md5CheckBox.setChecked(compute_md5)
                self.sourceEdit.setText(source)
            except Exception as e:
                self._showError(str(e))
                return
            self._statusBarMsg(f"Profile {os.path.split(fn)[1]} loaded")

    def reset(self):
        self._statusBarMsg("")
        self.createButton.setEnabled(False)
        self.fileRadioButton.setChecked(True)
        self.batchModeCheckBox.setChecked(False)
        self.inputEdit.setText(None)
        self.excludeEdit.setPlainText(None)
        self.trackerEdit.setPlainText(None)
        self.webSeedEdit.setPlainText(None)
        self.pieceSizeComboBox.setCurrentIndex(0)
        self.pieceCountLabel.hide()
        self.commentEdit.setText(None)
        self.privateTorrentCheckBox.setChecked(False)
        self.randomizeInfoHashCheckBox.setChecked(False)
        self.md5CheckBox.setChecked(False)
        self.sourceEdit.setText(None)
        self.torrent = None
        self._statusBarMsg("Ready")

    def get_info(self, torrent):
        t_info = []
        t_info.append(torrent.size)
        t_info.append(len(torrent.files))
        t_info.append(torrent.pieces)
        t_info.append(torrent.piece_size)
        return t_info


def main():
    try:
        qdarktheme.enable_hi_dpi()

        app = QtWidgets.QApplication(sys.argv)

        qdarktheme.setup_theme("auto")

        MainWindow = QtWidgets.QMainWindow()
        ui = TorfGUI()
        ui.setupUi(MainWindow)

        MainWindow.setWindowTitle(PROGRAM_NAME_VERSION)

        ui.loadSettings()
        ui.clipboard = app.clipboard
        app.aboutToQuit.connect(lambda: ui.saveSettings())
        MainWindow.show()
        sys.exit(app.exec_())
    except Exception as e:
        print("An error occurred:", str(e))


if __name__ == "__main__":
    main()

torf_gui/gui.py
SavageCore/torf-gui
# Form implementation generated from reading ui file 'torf_gui/about.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_AboutDialog:
    def setupUi(self, AboutDialog):
        AboutDialog.setObjectName("AboutDialog")
        AboutDialog.setWindowModality(QtCore.Qt.WindowModal)
        AboutDialog.resize(605, 253)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            AboutDialog.sizePolicy().hasHeightForWidth()
        )
        AboutDialog.setSizePolicy(sizePolicy)
        self.verticalLayout = QtWidgets.QVBoxLayout(AboutDialog)
        self.verticalLayout.setObjectName("verticalLayout")
        self.programNameLabel = QtWidgets.QLabel(AboutDialog)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.programNameLabel.sizePolicy().hasHeightForWidth()
        )
        self.programNameLabel.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setPointSize(16)
        font.setBold(True)
        font.setWeight(75)
        self.programNameLabel.setFont(font)
        self.programNameLabel.setObjectName("programNameLabel")
        self.verticalLayout.addWidget(self.programNameLabel)
        self.programVersionLabel = QtWidgets.QLabel(AboutDialog)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.programVersionLabel.sizePolicy().hasHeightForWidth()
        )
        self.programVersionLabel.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.programVersionLabel.setFont(font)
        self.programVersionLabel.setObjectName("programVersionLabel")
        self.verticalLayout.addWidget(self.programVersionLabel)
        self.dtVersionLabel = QtWidgets.QLabel(AboutDialog)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.dtVersionLabel.sizePolicy().hasHeightForWidth()
        )
        self.dtVersionLabel.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setPointSize(11)
        font.setBold(False)
        font.setWeight(50)
        self.dtVersionLabel.setFont(font)
        self.dtVersionLabel.setObjectName("dtVersionLabel")
        self.verticalLayout.addWidget(self.dtVersionLabel)
        self.infoLabel = QtWidgets.QLabel(AboutDialog)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.infoLabel.sizePolicy().hasHeightForWidth()
        )
        self.infoLabel.setSizePolicy(sizePolicy)
        self.infoLabel.setWordWrap(True)
        self.infoLabel.setOpenExternalLinks(True)
        self.infoLabel.setObjectName("infoLabel")
        self.verticalLayout.addWidget(self.infoLabel)
        self.buttonBox = QtWidgets.QDialogButtonBox(AboutDialog)
        self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
        self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
        self.buttonBox.setObjectName("buttonBox")
        self.verticalLayout.addWidget(self.buttonBox)

        self.retranslateUi(AboutDialog)
        self.buttonBox.accepted.connect(AboutDialog.accept)  # type: ignore
        self.buttonBox.rejected.connect(AboutDialog.reject)  # type: ignore
        QtCore.QMetaObject.connectSlotsByName(AboutDialog)

    def retranslateUi(self, AboutDialog):
        _translate = QtCore.QCoreApplication.translate
        AboutDialog.setWindowTitle(_translate("AboutDialog", "About"))
        self.programNameLabel.setText(_translate("AboutDialog", "torf-gui"))
        self.programVersionLabel.setText(
            _translate("AboutDialog", "PROGRAM_VERSION")
        )
        self.dtVersionLabel.setText(_translate("AboutDialog", "TORF_VERSION"))
        self.infoLabel.setText(
            _translate(
                "AboutDialog",
                '<html><head/><body><p><span style=" font-size:10pt;">© 2023 Oliver Sayers</span></p><p><span style=" font-size:10pt;">torf-gui is made available under the terms of the </span><a href="http://choosealicense.com/licenses/gpl-3.0/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">GNU General Public License, version 3</span></a><span style=" font-size:10pt;">.</span></p><p><a href="https://github.com/SavageCore/dottorrent-gui"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">https://github.com/SavageCore/dottorrent-gui</span></a></p></body></html>',
            )
        )

torf_gui/ui_about.py
SavageCore/torf-gui
# Form implementation generated from reading ui file 'torf_gui/mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtWidgets


class Ui_MainWindow:
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(500, 1029)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            MainWindow.sizePolicy().hasHeightForWidth()
        )
        MainWindow.setSizePolicy(sizePolicy)
        MainWindow.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.centralwidget.sizePolicy().hasHeightForWidth()
        )
        self.centralwidget.setSizePolicy(sizePolicy)
        self.centralwidget.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
        self.scrollArea.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.AdjustToContents
        )
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 480, 910))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.verticalLayoutMain = QtWidgets.QVBoxLayout(
            self.scrollAreaWidgetContents
        )
        self.verticalLayoutMain.setContentsMargins(12, 12, 12, 12)
        self.verticalLayoutMain.setObjectName("verticalLayoutMain")
        self.inputGroupBox = QtWidgets.QGroupBox(self.scrollAreaWidgetContents)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred,
            QtWidgets.QSizePolicy.MinimumExpanding,
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.inputGroupBox.sizePolicy().hasHeightForWidth()
        )
        self.inputGroupBox.setSizePolicy(sizePolicy)
        self.inputGroupBox.setObjectName("inputGroupBox")
        self.gridLayout = QtWidgets.QGridLayout(self.inputGroupBox)
        self.gridLayout.setObjectName("gridLayout")
        self.fileRadioButton = QtWidgets.QRadioButton(self.inputGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.fileRadioButton.sizePolicy().hasHeightForWidth()
        )
        self.fileRadioButton.setSizePolicy(sizePolicy)
        self.fileRadioButton.setObjectName("fileRadioButton")
        self.gridLayout.addWidget(self.fileRadioButton, 0, 0, 1, 1)
        self.batchModeCheckBox = QtWidgets.QCheckBox(self.inputGroupBox)
        self.batchModeCheckBox.setObjectName("batchModeCheckBox")
        self.gridLayout.addWidget(self.batchModeCheckBox, 4, 0, 1, 1)
        self.excludeLabel = QtWidgets.QLabel(self.inputGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.excludeLabel.sizePolicy().hasHeightForWidth()
        )
        self.excludeLabel.setSizePolicy(sizePolicy)
        self.excludeLabel.setObjectName("excludeLabel")
        self.gridLayout.addWidget(self.excludeLabel, 5, 0, 1, 4)
        self.excludeEdit = QtWidgets.QPlainTextEdit(self.inputGroupBox)
        self.excludeEdit.setEnabled(True)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.MinimumExpanding,
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.excludeEdit.sizePolicy().hasHeightForWidth()
        )
        self.excludeEdit.setSizePolicy(sizePolicy)
        self.excludeEdit.setMinimumSize(QtCore.QSize(0, 90))
        self.excludeEdit.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self.excludeEdit.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.AdjustToContents
        )
        self.excludeEdit.setObjectName("excludeEdit")
        self.gridLayout.addWidget(self.excludeEdit, 6, 0, 1, 4)
        self.inputEdit = QtWidgets.QLineEdit(self.inputGroupBox)
        self.inputEdit.setText("")
        self.inputEdit.setDragEnabled(True)
        self.inputEdit.setReadOnly(True)
        self.inputEdit.setObjectName("inputEdit")
        self.gridLayout.addWidget(self.inputEdit, 1, 0, 1, 4)
        self.horizontalLayout_inputButtons = QtWidgets.QHBoxLayout()
        self.horizontalLayout_inputButtons.setSizeConstraint(
            QtWidgets.QLayout.SetDefaultConstraint
        )
        self.horizontalLayout_inputButtons.setObjectName(
            "horizontalLayout_inputButtons"
        )
        self.browseButton = QtWidgets.QPushButton(self.inputGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.browseButton.sizePolicy().hasHeightForWidth()
        )
        self.browseButton.setSizePolicy(sizePolicy)
        self.browseButton.setObjectName("browseButton")
        self.horizontalLayout_inputButtons.addWidget(self.browseButton)
        self.pasteButton = QtWidgets.QPushButton(self.inputGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.pasteButton.sizePolicy().hasHeightForWidth()
        )
        self.pasteButton.setSizePolicy(sizePolicy)
        self.pasteButton.setObjectName("pasteButton")
        self.horizontalLayout_inputButtons.addWidget(self.pasteButton)
        self.gridLayout.addLayout(
            self.horizontalLayout_inputButtons, 2, 0, 1, 4
        )
        self.directoryRadioButton = QtWidgets.QRadioButton(self.inputGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.directoryRadioButton.sizePolicy().hasHeightForWidth()
        )
        self.directoryRadioButton.setSizePolicy(sizePolicy)
        self.directoryRadioButton.setObjectName("directoryRadioButton")
        self.gridLayout.addWidget(self.directoryRadioButton, 0, 2, 1, 1)
        self.verticalLayoutMain.addWidget(self.inputGroupBox)
        self.seedingGroupBox = QtWidgets.QGroupBox(
            self.scrollAreaWidgetContents
        )
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred,
            QtWidgets.QSizePolicy.MinimumExpanding,
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(2)
        sizePolicy.setHeightForWidth(
            self.seedingGroupBox.sizePolicy().hasHeightForWidth()
        )
        self.seedingGroupBox.setSizePolicy(sizePolicy)
        self.seedingGroupBox.setObjectName("seedingGroupBox")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.seedingGroupBox)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.trackerLabel = QtWidgets.QLabel(self.seedingGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.trackerLabel.sizePolicy().hasHeightForWidth()
        )
        self.trackerLabel.setSizePolicy(sizePolicy)
        self.trackerLabel.setObjectName("trackerLabel")
        self.verticalLayout_2.addWidget(self.trackerLabel)
        self.trackerEdit = QtWidgets.QPlainTextEdit(self.seedingGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.MinimumExpanding,
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.trackerEdit.sizePolicy().hasHeightForWidth()
        )
        self.trackerEdit.setSizePolicy(sizePolicy)
        self.trackerEdit.setMinimumSize(QtCore.QSize(0, 90))
        self.trackerEdit.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self.trackerEdit.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.AdjustToContents
        )
        self.trackerEdit.setObjectName("trackerEdit")
        self.verticalLayout_2.addWidget(self.trackerEdit)
        self.webSeedLabel = QtWidgets.QLabel(self.seedingGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.webSeedLabel.sizePolicy().hasHeightForWidth()
        )
        self.webSeedLabel.setSizePolicy(sizePolicy)
        self.webSeedLabel.setObjectName("webSeedLabel")
        self.verticalLayout_2.addWidget(self.webSeedLabel)
        self.webSeedEdit = QtWidgets.QPlainTextEdit(self.seedingGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.MinimumExpanding,
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(
            self.webSeedEdit.sizePolicy().hasHeightForWidth()
        )
        self.webSeedEdit.setSizePolicy(sizePolicy)
        self.webSeedEdit.setMinimumSize(QtCore.QSize(0, 90))
        self.webSeedEdit.setMaximumSize(QtCore.QSize(16777215, 16777215))
        self.webSeedEdit.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.AdjustToContents
        )
        self.webSeedEdit.setObjectName("webSeedEdit")
        self.verticalLayout_2.addWidget(self.webSeedEdit)
        self.verticalLayoutMain.addWidget(self.seedingGroupBox)
        self.optionGroupBox = QtWidgets.QGroupBox(
            self.scrollAreaWidgetContents
        )
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.optionGroupBox.sizePolicy().hasHeightForWidth()
        )
        self.optionGroupBox.setSizePolicy(sizePolicy)
        self.optionGroupBox.setObjectName("optionGroupBox")
        self.gridLayout_2 = QtWidgets.QGridLayout(self.optionGroupBox)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.pieceSizeComboBox = QtWidgets.QComboBox(self.optionGroupBox)
        self.pieceSizeComboBox.setMaximumSize(QtCore.QSize(100, 16777215))
        self.pieceSizeComboBox.setObjectName("pieceSizeComboBox")
        self.gridLayout_2.addWidget(self.pieceSizeComboBox, 3, 0, 1, 1)
        self.pieceCountLabel = QtWidgets.QLabel(self.optionGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.pieceCountLabel.sizePolicy().hasHeightForWidth()
        )
        self.pieceCountLabel.setSizePolicy(sizePolicy)
        self.pieceCountLabel.setObjectName("pieceCountLabel")
        self.gridLayout_2.addWidget(self.pieceCountLabel, 3, 1, 1, 1)
        self.md5CheckBox = QtWidgets.QCheckBox(self.optionGroupBox)
        self.md5CheckBox.setObjectName("md5CheckBox")
        self.gridLayout_2.addWidget(self.md5CheckBox, 10, 0, 1, 2)
        self.privateTorrentCheckBox = QtWidgets.QCheckBox(self.optionGroupBox)
        self.privateTorrentCheckBox.setObjectName("privateTorrentCheckBox")
        self.gridLayout_2.addWidget(self.privateTorrentCheckBox, 8, 0, 1, 2)
        self.pieceSizeLabel = QtWidgets.QLabel(self.optionGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.pieceSizeLabel.sizePolicy().hasHeightForWidth()
        )
        self.pieceSizeLabel.setSizePolicy(sizePolicy)
        self.pieceSizeLabel.setObjectName("pieceSizeLabel")
        self.gridLayout_2.addWidget(self.pieceSizeLabel, 2, 0, 1, 1)
        self.commentLabel = QtWidgets.QLabel(self.optionGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.commentLabel.sizePolicy().hasHeightForWidth()
        )
        self.commentLabel.setSizePolicy(sizePolicy)
        self.commentLabel.setObjectName("commentLabel")
        self.gridLayout_2.addWidget(self.commentLabel, 4, 0, 1, 1)
        self.commentEdit = QtWidgets.QLineEdit(self.optionGroupBox)
        self.commentEdit.setObjectName("commentEdit")
        self.gridLayout_2.addWidget(self.commentEdit, 5, 0, 1, 2)
        self.sourceEdit = QtWidgets.QLineEdit(self.optionGroupBox)
        self.sourceEdit.setObjectName("sourceEdit")
        self.gridLayout_2.addWidget(self.sourceEdit, 7, 0, 1, 2)
        self.sourceLabel = QtWidgets.QLabel(self.optionGroupBox)
        sizePolicy = QtWidgets.QSizePolicy(
            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
        )
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.sourceLabel.sizePolicy().hasHeightForWidth()
        )
        self.sourceLabel.setSizePolicy(sizePolicy)
        self.sourceLabel.setObjectName("sourceLabel")
        self.gridLayout_2.addWidget(self.sourceLabel, 6, 0, 1, 1)
        self.randomizeInfoHashCheckBox = QtWidgets.QCheckBox(
            self.optionGroupBox
        )
        self.randomizeInfoHashCheckBox.setObjectName(
            "randomizeInfoHashCheckBox"
        )
        self.gridLayout_2.addWidget(self.randomizeInfoHashCheckBox, 9, 0, 1, 1)
        self.verticalLayoutMain.addWidget(self.optionGroupBox)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.verticalLayout.addWidget(self.scrollArea)
        self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        self.verticalLayout.addWidget(self.progressBar)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.createButton = QtWidgets.QPushButton(self.centralwidget)
        self.createButton.setObjectName("createButton")
        self.horizontalLayout.addWidget(self.createButton)
        self.cancelButton = QtWidgets.QPushButton(self.centralwidget)
        self.cancelButton.setObjectName("cancelButton")
        self.horizontalLayout.addWidget(self.cancelButton)
        self.resetButton = QtWidgets.QPushButton(self.centralwidget)
        self.resetButton.setObjectName("resetButton")
        self.horizontalLayout.addWidget(self.resetButton)
        self.verticalLayout.addLayout(self.horizontalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 500, 21))
        self.menubar.setObjectName("menubar")
        self.menuFile = QtWidgets.QMenu(self.menubar)
        self.menuFile.setObjectName("menuFile")
        self.menuHelp = QtWidgets.QMenu(self.menubar)
        self.menuHelp.setObjectName("menuHelp")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionImportProfile = QtWidgets.QAction(MainWindow)
        self.actionImportProfile.setObjectName("actionImportProfile")
        self.actionExportProfile = QtWidgets.QAction(MainWindow)
        self.actionExportProfile.setObjectName("actionExportProfile")
        self.actionAbout = QtWidgets.QAction(MainWindow)
        self.actionAbout.setObjectName("actionAbout")
        self.actionQuit = QtWidgets.QAction(MainWindow)
        self.actionQuit.setObjectName("actionQuit")
        self.menuFile.addAction(self.actionImportProfile)
        self.menuFile.addAction(self.actionExportProfile)
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.actionQuit)
        self.menuHelp.addAction(self.actionAbout)
        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuHelp.menuAction())

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.inputGroupBox.setTitle(_translate("MainWindow", "Input path"))
        self.fileRadioButton.setText(_translate("MainWindow", "File"))
        self.batchModeCheckBox.setToolTip(
            _translate(
                "MainWindow",
                "<html><head/><body><p>If enabled, a torrent will be created for each directory and file one level below the input directory. The piece size for each torrent will be determined automatically.</p></body></html>",
            )
        )
        self.batchModeCheckBox.setText(_translate("MainWindow", "Batch mode"))
        self.excludeLabel.setText(
            _translate(
                "MainWindow",
                "Filename exclusion patterns (globs, one per line)",
            )
        )
        self.browseButton.setText(_translate("MainWindow", "Browse..."))
        self.pasteButton.setText(
            _translate("MainWindow", "Paste from clipboard")
        )
        self.directoryRadioButton.setText(
            _translate("MainWindow", "Directory")
        )
        self.seedingGroupBox.setTitle(_translate("MainWindow", "Seeding"))
        self.trackerLabel.setText(
            _translate("MainWindow", "Tracker URLs (one per line)")
        )
        self.webSeedLabel.setText(
            _translate("MainWindow", "HTTP/FTP seeds (one per line)")
        )
        self.optionGroupBox.setTitle(
            _translate("MainWindow", "Torrent options")
        )
        self.pieceCountLabel.setText(_translate("MainWindow", "# pieces"))
        self.md5CheckBox.setToolTip(
            _translate(
                "MainWindow",
                "<html><head/><body><p>This is not used by BitTorrent at all, but it is included by some programs for greater compatibility.</p></body></html>",
            )
        )
        self.md5CheckBox.setText(
            _translate("MainWindow", "Compute MD5 hashes")
        )
        self.privateTorrentCheckBox.setToolTip(
            _translate(
                "MainWindow",
                "<html><head/><body><p>If enabled, clients will not use DHT or Peer Exchange to discover peers or transfer data. Make sure to enable this option for private trackers.</p></body></html>",
            )
        )
        self.privateTorrentCheckBox.setText(
            _translate("MainWindow", "Private torrent")
        )
        self.pieceSizeLabel.setText(_translate("MainWindow", "Piece size"))
        self.commentLabel.setText(_translate("MainWindow", "Comment"))
        self.sourceEdit.setToolTip(
            _translate(
                "MainWindow",
                "<html><head/><body><p>An optional source string that wil be added to the info dictionary. Useful for creating torrents with unique info hashes for private trackers.</p></body></html>",
            )
        )
        self.sourceLabel.setText(_translate("MainWindow", "Source"))
        self.randomizeInfoHashCheckBox.setToolTip(
            _translate(
                "MainWindow",
                "<html><head/><body><p>If enabled, this adds an entropy field to the info section of the metainfo and sets it to a random integer, to help with cross-seeding.</p></body></html>",
            )
        )
        self.randomizeInfoHashCheckBox.setText(
            _translate("MainWindow", "Randomise info hash")
        )
        self.createButton.setText(_translate("MainWindow", "Create..."))
        self.cancelButton.setText(_translate("MainWindow", "Cancel"))
        self.resetButton.setText(_translate("MainWindow", "Reset"))
        self.menuFile.setTitle(_translate("MainWindow", "File"))
        self.menuHelp.setTitle(_translate("MainWindow", "Help"))
        self.actionImportProfile.setText(
            _translate("MainWindow", "Import profile...")
        )
        self.actionImportProfile.setShortcut(
            _translate("MainWindow", "Ctrl+O")
        )
        self.actionExportProfile.setText(
            _translate("MainWindow", "Export profile...")
        )
        self.actionExportProfile.setShortcut(
            _translate("MainWindow", "Ctrl+S")
        )
        self.actionAbout.setText(_translate("MainWindow", "About"))
        self.actionAbout.setShortcut(_translate("MainWindow", "F1"))
        self.actionQuit.setText(_translate("MainWindow", "Quit"))
        self.actionQuit.setShortcut(_translate("MainWindow", "Ctrl+Q"))

torf_gui/ui_mainwindow.py
SavageCore/torf-gui