diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..0c4b9767 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.21) + +set(QNAPI_VERSION "0.4.0") +if(EXISTS "${CMAKE_SOURCE_DIR}/debian/control") + file(STRINGS "${CMAKE_SOURCE_DIR}/debian/control" _debian_version_line + REGEX "^Version:[ \t]*") + if(_debian_version_line) + string(REGEX REPLACE "^Version:[ \t]*([0-9]+\\.[0-9]+\\.[0-9]+).*$" "\\1" + QNAPI_VERSION "${_debian_version_line}") + endif() +endif() + +project(qnapi VERSION ${QNAPI_VERSION} LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +option(QNAPI_BUILD_GUI "Build GUI app" ON) +option(QNAPI_BUILD_CLI "Build CLI app" ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Network Xml) + +add_subdirectory(libqnapi) + +if(QNAPI_BUILD_CLI) + add_subdirectory(cli) +endif() + +if(QNAPI_BUILD_GUI) + add_subdirectory(gui) +endif() diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt new file mode 100644 index 00000000..a10dfe77 --- /dev/null +++ b/cli/CMakeLists.txt @@ -0,0 +1,18 @@ +find_package(Qt6 REQUIRED COMPONENTS Core Network Xml) + +set(CLI_SOURCES + src/main.cpp + src/clisubtitlesdownloader.cpp + src/climain.cpp + res/resources.qrc) + +add_executable(qnapic ${CLI_SOURCES}) + +target_include_directories(qnapic PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src") + +target_link_libraries(qnapic PRIVATE + qnapi::libqnapi + Qt6::Core + Qt6::Network + Qt6::Xml) diff --git a/debian/changelog b/debian/changelog index ed42b0ef..e8caedb3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +qnapi (0.4.0-1) unstable; urgency=medium + + * Przejście projektu na Qt6. + * Dodanie systemu budowania CMake (obok dotychczasowego układu źródeł). + * Migracja kodu pod nowsze API Qt (QRegularExpression, QRandomGenerator, + QStringConverter, itp.). + + -- mskpluk Sun, 19 Apr 2026 12:00:00 +0000 + qnapi (0.3.2-1) unstable; urgency=medium * Merge desktop notifications improvements: @@ -37,4 +46,3 @@ qnapi (0.2.2-1) xenial; urgency=medium * Initial release -- Piotr Krzemiński Wed, 05 Nov 2016 16:56:00 +0100 - diff --git a/debian/control b/debian/control index 7894c54d..c7cd23ac 100644 --- a/debian/control +++ b/debian/control @@ -1,10 +1,10 @@ Package: qnapi -Version: 0.3.2 +Version: 0.4.0 Section: video Priority: optional Architecture: amd64 Installed-Size: 1300 -Depends: libc6 (>= 2.17), libgcc1 (>= 1:3.0), libqt5core5a (>= 5.5.0), libqt5gui5 (>= 5.0.2), libqt5network5 (>= 5.0.2), libqt5widgets5 (>= 5.0.2), libqt5xml5 (>= 5.0.2), libstdc++6 (>= 5.2), libmediainfo0v5 (>= 0.7.52), libzen0v5 (>= 0.4.31), p7zip-full, xdg-utils +Depends: libc6 (>= 2.17), libgcc1 (>= 1:3.0), libqt6core6 (>= 6.2.0), libqt6gui6 (>= 6.2.0), libqt6network6 (>= 6.2.0), libqt6widgets6 (>= 6.2.0), libqt6xml6 (>= 6.2.0), libstdc++6 (>= 5.2), libmediainfo0v5 (>= 0.7.52), libzen0v5 (>= 0.4.31), p7zip-full, xdg-utils Maintainer: mskpluk mskpluk Homepage: https://github.com/mskpluk/qnapi Description: Automatyczne pobieranie napisów do filmów @@ -18,6 +18,7 @@ Description: Automatyczne pobieranie napisów do filmów . * Poprawione przetwarzanie napisów w formacie UTF-16 LE * Dodana obsługa linków napiprojekt: (URI scheme handler) + * Przejście na Qt6 i migracja builda na CMake . Powyższe zmiany zostały zaimplementowane z wykorzystaniem AI (Claude Sonnet 4.5 by Anthropic). diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt new file mode 100644 index 00000000..865bd4df --- /dev/null +++ b/gui/CMakeLists.txt @@ -0,0 +1,56 @@ +find_package(Qt6 REQUIRED COMPONENTS Core Network Xml Gui Widgets) + +if(UNIX AND NOT APPLE) + find_package(Qt6 REQUIRED COMPONENTS DBus) +endif() + +file(GLOB_RECURSE GUI_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +set(GUI_FORMS + ui/frmprogress.ui + ui/frmlistsubtitles.ui + ui/frmsummary.ui + ui/frmscan.ui + ui/frmoptions.ui + ui/frmabout.ui + ui/frmconvert.ui + ui/subDataWidget.ui + ui/napiprojekt/frmnapiprojektconfig.ui + ui/opensubtitles/frmopensubtitlesconfig.ui + ui/napisy24/frmnapisy24config.ui) + +set(GUI_RESOURCES res/resources.qrc) + +if(WIN32) + list(REMOVE_ITEM GUI_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/src/qcumber/qinterprocesschannel.cpp") +else() + list(REMOVE_ITEM GUI_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/src/qcumber/qinterprocesschannel_win32.cpp") +endif() + +if(APPLE) + list(APPEND GUI_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/infoplistdockicon.cpp") +endif() + +add_executable(qnapi + ${GUI_SOURCES} + ${GUI_FORMS} + ${GUI_RESOURCES}) + +target_include_directories(qnapi PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/src") + +target_link_libraries(qnapi PRIVATE + qnapi::libqnapi + Qt6::Core + Qt6::Network + Qt6::Xml + Qt6::Gui + Qt6::Widgets) + +if(UNIX AND NOT APPLE) + target_link_libraries(qnapi PRIVATE Qt6::DBus) +endif() diff --git a/gui/src/forms/frmabout.cpp b/gui/src/forms/frmabout.cpp index 95de9eb6..ac0e4be7 100644 --- a/gui/src/forms/frmabout.cpp +++ b/gui/src/forms/frmabout.cpp @@ -14,6 +14,8 @@ #include "frmabout.h" #include "libqnapi.h" +#include +#include frmAbout::frmAbout(QWidget* parent, Qt::WindowFlags f) : QDialog(parent, f) { ui.setupUi(this); @@ -23,7 +25,7 @@ frmAbout::frmAbout(QWidget* parent, Qt::WindowFlags f) : QDialog(parent, f) { ui.lbQtVersion->setText(QString("Qt version: ") + qVersion()); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); ui.lbQNapiIcon->setPixmap(QIcon(":/icon/qnapi.png").pixmap(64)); diff --git a/gui/src/forms/frmabout.h b/gui/src/forms/frmabout.h index 3722a2a2..5e318047 100644 --- a/gui/src/forms/frmabout.h +++ b/gui/src/forms/frmabout.h @@ -16,7 +16,6 @@ #define __FRMABOUT__H__ #include -#include #include #include "ui_frmabout.h" diff --git a/gui/src/forms/frmconvert.cpp b/gui/src/forms/frmconvert.cpp index 690892ce..792aca2a 100644 --- a/gui/src/forms/frmconvert.cpp +++ b/gui/src/forms/frmconvert.cpp @@ -18,7 +18,8 @@ #include "qnapiopendialog.h" #include -#include +#include +#include #include #include #include @@ -38,7 +39,7 @@ frmConvert::frmConvert(QWidget *parent, Qt::WindowFlags f) setAttribute(Qt::WA_QuitOnClose, false); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); ui.lbDetectedFormatValue->setText(""); diff --git a/gui/src/forms/frmlistsubtitles.cpp b/gui/src/forms/frmlistsubtitles.cpp index 244b85d3..ebbb15c6 100644 --- a/gui/src/forms/frmlistsubtitles.cpp +++ b/gui/src/forms/frmlistsubtitles.cpp @@ -17,7 +17,8 @@ #include "frmlistsubtitles.h" #include "subdatawidget.h" -#include +#include +#include #include #include @@ -27,7 +28,7 @@ frmListSubtitles::frmListSubtitles(QWidget *parent, Qt::WindowFlags f) ui.setupUi(this); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } diff --git a/gui/src/forms/frmnapiprojektconfig.cpp b/gui/src/forms/frmnapiprojektconfig.cpp index 5112a5c2..b4590517 100644 --- a/gui/src/forms/frmnapiprojektconfig.cpp +++ b/gui/src/forms/frmnapiprojektconfig.cpp @@ -13,7 +13,8 @@ *****************************************************************************/ #include -#include +#include +#include #include "engines/napiprojektdownloadengine.h" #include "frmnapiprojektconfig.h" @@ -32,7 +33,7 @@ frmNapiProjektConfig::frmNapiProjektConfig(const EngineConfig &config, connect(ui.pbRegister, SIGNAL(clicked()), this, SLOT(pbRegisterClicked())); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } diff --git a/gui/src/forms/frmnapisy24config.cpp b/gui/src/forms/frmnapisy24config.cpp index f7bdc1c5..ae802a06 100644 --- a/gui/src/forms/frmnapisy24config.cpp +++ b/gui/src/forms/frmnapisy24config.cpp @@ -13,7 +13,8 @@ *****************************************************************************/ #include -#include +#include +#include #include "engines/napisy24downloadengine.h" #include "frmnapisy24config.h" @@ -32,7 +33,7 @@ frmNapisy24Config::frmNapisy24Config(const EngineConfig &config, connect(ui.pbRegister, SIGNAL(clicked()), this, SLOT(pbRegisterClicked())); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } diff --git a/gui/src/forms/frmopensubtitlesconfig.cpp b/gui/src/forms/frmopensubtitlesconfig.cpp index 7b2f2939..68f5b74b 100644 --- a/gui/src/forms/frmopensubtitlesconfig.cpp +++ b/gui/src/forms/frmopensubtitlesconfig.cpp @@ -13,7 +13,8 @@ *****************************************************************************/ #include -#include +#include +#include #include "engines/opensubtitlesdownloadengine.h" #include "frmopensubtitlesconfig.h" @@ -34,7 +35,7 @@ frmOpenSubtitlesConfig::frmOpenSubtitlesConfig(const EngineConfig &config, connect(ui.pbRegister, SIGNAL(clicked()), this, SLOT(pbRegisterClicked())); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } diff --git a/gui/src/forms/frmoptions.cpp b/gui/src/forms/frmoptions.cpp index 6cf08b3d..325a5c37 100644 --- a/gui/src/forms/frmoptions.cpp +++ b/gui/src/forms/frmoptions.cpp @@ -25,8 +25,12 @@ #include "forms/frmopensubtitlesconfig.h" #include "libqnapi.h" +#include "utils/encodingutils.h" #include +#include +#include +#include #ifdef Q_OS_MAC #include "utils/infoplistdockicon.h" @@ -112,7 +116,7 @@ frmOptions::frmOptions(QWidget *parent, Qt::WindowFlags f) } QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } @@ -306,17 +310,30 @@ void frmOptions::showAllEncodingsClicked() { ui.cbEncTo->addItems(codecs); } - ui.cbEncFrom->setCurrentIndex(ui.cbEncFrom->findText(encFrom)); - ui.cbEncTo->setCurrentIndex(ui.cbEncTo->findText(encTo)); + int fromIdx = ui.cbEncFrom->findText(encFrom); + if (fromIdx == -1 && !encFrom.isEmpty()) { + ui.cbEncFrom->addItem(encFrom); + fromIdx = ui.cbEncFrom->findText(encFrom); + } + + int toIdx = ui.cbEncTo->findText(encTo); + if (toIdx == -1 && !encTo.isEmpty()) { + ui.cbEncTo->addItem(encTo); + toIdx = ui.cbEncTo->findText(encTo); + } + + ui.cbEncFrom->setCurrentIndex(fromIdx); + ui.cbEncTo->setCurrentIndex(toIdx); } void frmOptions::showAllEncodings() { ui.cbEncFrom->clear(); ui.cbEncTo->clear(); - QList codecs = QTextCodec::availableCodecs(); - qSort(codecs.begin(), codecs.end()); - for (QList::iterator i = codecs.begin(); i != codecs.end(); i++) { + EncodingUtils encodingUtils; + QStringList codecs = encodingUtils.availableEncodings(); + std::sort(codecs.begin(), codecs.end()); + for (QStringList::const_iterator i = codecs.begin(); i != codecs.end(); i++) { ui.cbEncFrom->addItem(*i); ui.cbEncTo->addItem(*i); } diff --git a/gui/src/forms/frmoptions.h b/gui/src/forms/frmoptions.h index 199bdb62..f2543ecf 100644 --- a/gui/src/forms/frmoptions.h +++ b/gui/src/forms/frmoptions.h @@ -22,14 +22,12 @@ #include "engines/subtitledownloadenginesregistry.h" #include "subconvert/subtitleformatsregistry.h" -#include #include #include #include #include #include #include -#include class frmOptions : public QDialog { Q_OBJECT diff --git a/gui/src/forms/frmprogress.cpp b/gui/src/forms/frmprogress.cpp index e5e24532..6c9c808d 100644 --- a/gui/src/forms/frmprogress.cpp +++ b/gui/src/forms/frmprogress.cpp @@ -14,7 +14,8 @@ #include "frmprogress.h" #include -#include +#include +#include #include #include #include @@ -47,7 +48,7 @@ frmProgress::frmProgress(QWidget *parent, Qt::WindowFlags f) connect(&getThread, SIGNAL(finished()), this, SLOT(downloadFinished())); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } @@ -104,7 +105,7 @@ bool frmProgress::download(const QNapiConfig &config) { if (!isVisible()) { QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); show(); } diff --git a/gui/src/forms/frmscan.cpp b/gui/src/forms/frmscan.cpp index f349b9df..6c5fed5f 100644 --- a/gui/src/forms/frmscan.cpp +++ b/gui/src/forms/frmscan.cpp @@ -14,6 +14,8 @@ #include "frmscan.h" #include "libqnapi.h" +#include +#include frmScan::frmScan(QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f), scanConfig(LibQNapi::loadConfig().scanConfig()) { @@ -57,7 +59,7 @@ frmScan::frmScan(QWidget *parent, Qt::WindowFlags f) iconFilm = QIcon(":/ui/film.png"); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } diff --git a/gui/src/forms/frmsummary.cpp b/gui/src/forms/frmsummary.cpp index d58c6211..b5d6108c 100644 --- a/gui/src/forms/frmsummary.cpp +++ b/gui/src/forms/frmsummary.cpp @@ -17,7 +17,8 @@ #include "subdatawidget.h" -#include +#include +#include #include frmSummary::frmSummary(QWidget *parent, Qt::WindowFlags f) @@ -27,7 +28,7 @@ frmSummary::frmSummary(QWidget *parent, Qt::WindowFlags f) setAttribute(Qt::WA_QuitOnClose, false); QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); } diff --git a/gui/src/guimain.cpp b/gui/src/guimain.cpp index 968dce8e..f35e2039 100644 --- a/gui/src/guimain.cpp +++ b/gui/src/guimain.cpp @@ -187,11 +187,7 @@ Maybe cliExecutablePath() { QStringList searchPaths = {LibQNapi::appExecutableDir}; #ifndef Q_OS_WIN - searchPaths << QProcess::systemEnvironment() - .filter(QRegExp("^PATH=(.*)$")) - .value(0) - .mid(5) - .split(":") + searchPaths << qEnvironmentVariable("PATH").split(":") << "/bin" << "/usr/bin" << "/usr/local/bin"; diff --git a/gui/src/qcumber/qmanagedrequest.cpp b/gui/src/qcumber/qmanagedrequest.cpp index 25b09532..509e5634 100644 --- a/gui/src/qcumber/qmanagedrequest.cpp +++ b/gui/src/qcumber/qmanagedrequest.cpp @@ -14,6 +14,7 @@ ****************************************************************************/ #include "qmanagedrequest.h" +#include /*! \file qmanagedrequest.cpp @@ -62,7 +63,8 @@ QManagedRequest QManagedRequest::fromString(const QString& s) { QStringList QManagedRequest::splitArguments(const QString& s) { int i = -1; - QStringList d, l = s.split(QRegExp("\\s")); + QStringList d, l = s.split(QRegularExpression("\\s+"), + Qt::SkipEmptyParts); while (++i < l.count()) { if (l.at(i).startsWith("\"")) { @@ -95,7 +97,7 @@ QByteArray QManagedRequest::joinArguments(const QString& cmd, foreach (QString a, l) { if (a.isEmpty()) continue; - if (a.contains(QRegExp("\\s"))) a = "\"" + a + "\""; + if (a.contains(QRegularExpression("\\s"))) a = "\"" + a + "\""; msg += " "; msg += a.toUtf8(); diff --git a/gui/src/qnapiopendialog.cpp b/gui/src/qnapiopendialog.cpp index 3d0e482b..9842149d 100644 --- a/gui/src/qnapiopendialog.cpp +++ b/gui/src/qnapiopendialog.cpp @@ -14,6 +14,8 @@ #include "qnapiopendialog.h" #include "libqnapi.h" +#include +#include QNapiOpenDialog::QNapiOpenDialog(QWidget* parent, const QString& caption, const QString& init_path, @@ -118,7 +120,7 @@ bool QNapiOpenDialog::placeWindow() { } QRect position = frameGeometry(); - position.moveCenter(QDesktopWidget().availableGeometry().center()); + position.moveCenter(QGuiApplication::primaryScreen()->availableGeometry().center()); move(position.topLeft()); return true; diff --git a/gui/src/qnapiopendialog.h b/gui/src/qnapiopendialog.h index ad24aae2..f29b9081 100644 --- a/gui/src/qnapiopendialog.h +++ b/gui/src/qnapiopendialog.h @@ -17,7 +17,6 @@ #include "config/staticconfig.h" -#include #include #include #include diff --git a/libqnapi/CMakeLists.txt b/libqnapi/CMakeLists.txt new file mode 100644 index 00000000..de0d23a3 --- /dev/null +++ b/libqnapi/CMakeLists.txt @@ -0,0 +1,31 @@ +find_package(Qt6 REQUIRED COMPONENTS Core Network Xml) + +file(GLOB_RECURSE LIBQNAPI_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +add_library(libqnapi_static STATIC ${LIBQNAPI_SOURCES}) +add_library(qnapi::libqnapi ALIAS libqnapi_static) + +target_include_directories(libqnapi_static + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_SOURCE_DIR}/deps/libmaia" + "${CMAKE_SOURCE_DIR}/deps/qt-maybe") + +target_link_libraries(libqnapi_static + PUBLIC + Qt6::Core + Qt6::Network + Qt6::Xml) + +if(UNIX AND NOT APPLE) + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(MEDIAINFO QUIET libmediainfo) + if(MEDIAINFO_FOUND) + target_include_directories(libqnapi_static PUBLIC ${MEDIAINFO_INCLUDE_DIRS}) + target_link_libraries(libqnapi_static PUBLIC ${MEDIAINFO_LIBRARIES}) + target_compile_options(libqnapi_static PUBLIC ${MEDIAINFO_CFLAGS_OTHER}) + endif() + endif() +endif() diff --git a/libqnapi/src/config/configreader.cpp b/libqnapi/src/config/configreader.cpp index aa72d535..07e21b95 100644 --- a/libqnapi/src/config/configreader.cpp +++ b/libqnapi/src/config/configreader.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include "subtitlelanguage.h" ConfigReader::ConfigReader( @@ -105,8 +104,7 @@ const QList> ConfigReader::readEnabledEngines( } else { QList> enabledEngines; foreach (QString engineEnableStr, enabledEnginesStr) { - QStringList engineParts = - engineEnableStr.split(":", QString::SkipEmptyParts); + QStringList engineParts = engineEnableStr.split(":", Qt::SkipEmptyParts); if (engineParts.size() != 2) { return defaultEnabledEngines; } @@ -172,9 +170,7 @@ const GeneralConfig ConfigReader::resolveP7zipPath( p7zipPath = QFileInfo(appExecutableDir + "/7za.exe").absoluteFilePath(); #else - QString pathEnv = - QProcess::systemEnvironment().filter(QRegExp("^PATH=(.*)$")).value(0); - QStringList sysPaths = pathEnv.mid(5).split(":"); + QStringList sysPaths = qEnvironmentVariable("PATH").split(":"); sysPaths.removeAll(""); sysPaths.append("."); diff --git a/libqnapi/src/engines/napiprojektdownloadengine.cpp b/libqnapi/src/engines/napiprojektdownloadengine.cpp index 1c1cebc8..880235f0 100644 --- a/libqnapi/src/engines/napiprojektdownloadengine.cpp +++ b/libqnapi/src/engines/napiprojektdownloadengine.cpp @@ -15,6 +15,7 @@ #include "napiprojektdownloadengine.h" #include "subtitlelanguage.h" #include "utils/synchttp.h" +#include "utils/encodingutils.h" #include @@ -25,7 +26,6 @@ #include #include #include -#include // Dodane dla obsługi różnych kodowań #ifdef Q_OS_WIN // for SetFileAttributes #include @@ -358,19 +358,9 @@ QString NapiProjektDownloadEngine::extractSubtitlesFromXml(const QString& xmlCon QByteArray rawData = file.readAll(); file.close(); - // --- POPRAWKA: wykrywanie kodowania napisów --- - // Próba wykrycia UTF (UTF-8, UTF-16, UTF-32) - QTextCodec *codec = QTextCodec::codecForUtfText(rawData, nullptr); - if (!codec) { - // Jeśli nie UTF, to najprawdopodobniej Windows-1250 (CP1250) - standard dla polskich napisów - codec = QTextCodec::codecForName("Windows-1250"); - if (!codec) { - // Ostateczny fallback na kodowanie systemowe - codec = QTextCodec::codecForLocale(); - } - } - result = codec->toUnicode(rawData); - // --- KONIEC POPRAWKI --- + EncodingUtils encodingUtils; + const QString detectedEncoding = encodingUtils.detectBufferEncoding(rawData); + result = encodingUtils.decodeToUnicode(rawData, detectedEncoding); } QFile::remove(subtitleFile); } diff --git a/libqnapi/src/engines/opensubtitlesdownloadengine.cpp b/libqnapi/src/engines/opensubtitlesdownloadengine.cpp index 8f6fc1e9..da8f20f9 100644 --- a/libqnapi/src/engines/opensubtitlesdownloadengine.cpp +++ b/libqnapi/src/engines/opensubtitlesdownloadengine.cpp @@ -16,6 +16,7 @@ #include "subtitlelanguage.h" #include +#include namespace OpenSubtitlesDownloadEngineConst { const QString openSubtitlesXmlRpcUrl = "http://api.opensubtitles.org/xml-rpc"; @@ -117,10 +118,14 @@ bool OpenSubtitlesDownloadEngine::lookForSubtitles(QString lang) { QFileInfo(responseMap["SubFileName"].toString()) .completeBaseName()) { r = SUBTITLE_GOOD; - } else if (QRegExp(QString("^%1").arg( - responseMap["MovieReleaseName"].toString()), - Qt::CaseInsensitive) - .exactMatch(QFileInfo(movie).completeBaseName())) { + } else if ( + QRegularExpression( + QString("^%1") + .arg(QRegularExpression::escape( + responseMap["MovieReleaseName"].toString())), + QRegularExpression::CaseInsensitiveOption) + .match(QFileInfo(movie).completeBaseName()) + .hasMatch()) { r = SUBTITLE_GOOD; } diff --git a/libqnapi/src/engines/subtitledownloadengine.cpp b/libqnapi/src/engines/subtitledownloadengine.cpp index c9d98d6a..5924559f 100644 --- a/libqnapi/src/engines/subtitledownloadengine.cpp +++ b/libqnapi/src/engines/subtitledownloadengine.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "subconvert/subtitleformatsregistry.h" SubtitleDownloadEngine::SubtitleDownloadEngine(const QString& tmpPath) @@ -51,12 +52,8 @@ void SubtitleDownloadEngine::updateSubtitleInfo(const SubtitleInfo& si) { } QString SubtitleDownloadEngine::generateTmpFileName() const { - static bool gen_inited; - if (!gen_inited) { - qsrand(time(0)); - gen_inited = true; - } - return QString("QNapi.%1.tmp").arg(qrand()); + return QString("QNapi.%1.tmp") + .arg(QRandomGenerator::global()->generate()); } QString SubtitleDownloadEngine::generateTmpPath() const { diff --git a/libqnapi/src/movieinfo/libmediainfomovieinfoprovider.cpp b/libqnapi/src/movieinfo/libmediainfomovieinfoprovider.cpp index 2fec5835..83f8aa63 100644 --- a/libqnapi/src/movieinfo/libmediainfomovieinfoprovider.cpp +++ b/libqnapi/src/movieinfo/libmediainfomovieinfoprovider.cpp @@ -13,7 +13,7 @@ *****************************************************************************/ #include "libmediainfomovieinfoprovider.h" -#include +#include #include #ifndef UNICODE @@ -51,23 +51,23 @@ const Maybe LibmediainfoMovieInfoProvider::getMovieInfo( mi.Close(); - QRegExp rWidth("(\\d+)"); - if (rWidth.indexIn(widthS) == -1) return nothing(); + auto widthMatch = QRegularExpression("(\\d+)").match(widthS); + if (!widthMatch.hasMatch()) return nothing(); - QRegExp rHeight("(\\d+)"); - if (rHeight.indexIn(heightS) == -1) return nothing(); + auto heightMatch = QRegularExpression("(\\d+)").match(heightS); + if (!heightMatch.hasMatch()) return nothing(); - QRegExp rFrameRate("(\\d+).(\\d+)"); - if (rFrameRate.indexIn(frameRateS) == -1) return nothing(); - long frFloor = rFrameRate.cap(1).toLong(); - long frFrac = rFrameRate.cap(2).toLong(); + auto frameRateMatch = QRegularExpression("(\\d+)[\\.,](\\d+)").match(frameRateS); + if (!frameRateMatch.hasMatch()) return nothing(); + long frFloor = frameRateMatch.captured(1).toLong(); + long frFrac = frameRateMatch.captured(2).toLong(); - QRegExp rDuration("(\\d+)"); - if (rDuration.indexIn(durationS) == -1) return nothing(); + auto durationMatch = QRegularExpression("(\\d+)").match(durationS); + if (!durationMatch.hasMatch()) return nothing(); - MovieInfo info(rWidth.cap(1).toInt(), rHeight.cap(1).toInt(), + MovieInfo info(widthMatch.captured(1).toInt(), heightMatch.captured(1).toInt(), (double)frFloor + (double)frFrac / 1000.0, - rDuration.cap(1).toDouble() / 1000.0); + durationMatch.captured(1).toDouble() / 1000.0); return just(info); } diff --git a/libqnapi/src/parser/extensionargparser.cpp b/libqnapi/src/parser/extensionargparser.cpp index 4e9b5ec9..9e98927c 100644 --- a/libqnapi/src/parser/extensionargparser.cpp +++ b/libqnapi/src/parser/extensionargparser.cpp @@ -13,7 +13,7 @@ *****************************************************************************/ #include "extensionargparser.h" -#include +#include ExtensionArgParser::ExtensionArgParser() {} @@ -35,7 +35,7 @@ QVariant ExtensionArgParser::parse(const QStringList& args, QString ext = args[idx + 1]; - if (!QRegExp("^[A-Za-z0-9]+$").exactMatch(ext)) { + if (!QRegularExpression("^[A-Za-z0-9]+$").match(ext).hasMatch()) { return QVariant::fromValue(ParseError{ tr("Invalid target subtitles extension passed: %1").arg(ext)}); } else { diff --git a/libqnapi/src/subconvert/formats/microdvd.cpp b/libqnapi/src/subconvert/formats/microdvd.cpp index daa686a8..2eb545ae 100644 --- a/libqnapi/src/subconvert/formats/microdvd.cpp +++ b/libqnapi/src/subconvert/formats/microdvd.cpp @@ -13,13 +13,13 @@ *****************************************************************************/ #include "microdvd.h" +#include bool MicroDVDSubtitleFormat::detect(const QStringList &lines) const { + static const QRegularExpression linePattern("^\\{(\\d+)\\}\\{(\\d+)\\}(.*)"); foreach (QString line, lines) { if (!line.trimmed().isEmpty()) { - QRegExp r("^\\{(\\d+)\\}\\{(\\d+)\\}(.*)"); - r.setPatternSyntax(QRegExp::RegExp2); - return r.exactMatch(line); + return linePattern.match(line).hasMatch(); } } @@ -28,16 +28,16 @@ bool MicroDVDSubtitleFormat::detect(const QStringList &lines) const { SubFile MicroDVDSubtitleFormat::decode(const QStringList &lines) const { SubFile sf; + static const QRegularExpression linePattern("^\\{(\\d+)\\}\\{(\\d+)\\}(.*)"); foreach (QString line, lines) { if (!line.trimmed().isEmpty()) { - QRegExp r("^\\{(\\d+)\\}\\{(\\d+)\\}(.*)"); - r.setPatternSyntax(QRegExp::RegExp2); - if (r.exactMatch(line)) { + auto match = linePattern.match(line); + if (match.hasMatch()) { SubEntry se; - se.frameStart = r.cap(1).toLong(); - se.frameStop = r.cap(2).toLong(); - QString tokenStream = r.cap(3); + se.frameStart = match.captured(1).toLong(); + se.frameStop = match.captured(2).toLong(); + QString tokenStream = match.captured(3); se.tokens = decodeTokenStream(tokenStream); diff --git a/libqnapi/src/subconvert/formats/mpl2.cpp b/libqnapi/src/subconvert/formats/mpl2.cpp index 070d5574..d282412f 100644 --- a/libqnapi/src/subconvert/formats/mpl2.cpp +++ b/libqnapi/src/subconvert/formats/mpl2.cpp @@ -13,13 +13,13 @@ *****************************************************************************/ #include "mpl2.h" +#include bool MPL2SubtitleFormat::detect(const QStringList &lines) const { + static const QRegularExpression linePattern("^\\[(\\d+)\\]\\[(\\d+)\\](.*)"); foreach (QString line, lines) { if (!line.trimmed().isEmpty()) { - QRegExp r("^\\[(\\d+)\\]\\[(\\d+)\\](.*)"); - r.setPatternSyntax(QRegExp::RegExp2); - return r.exactMatch(line); + return linePattern.match(line).hasMatch(); } } @@ -28,16 +28,16 @@ bool MPL2SubtitleFormat::detect(const QStringList &lines) const { SubFile MPL2SubtitleFormat::decode(const QStringList &lines) const { SubFile sf; + static const QRegularExpression linePattern("^\\[(\\d+)\\]\\[(\\d+)\\](.*)"); foreach (QString line, lines) { if (!line.trimmed().isEmpty()) { - QRegExp r("^\\[(\\d+)\\]\\[(\\d+)\\](.*)"); - r.setPatternSyntax(QRegExp::RegExp2); - if (r.exactMatch(line)) { + auto match = linePattern.match(line); + if (match.hasMatch()) { SubEntry se; - se.frameStart = 100L * r.cap(1).toLong(); - se.frameStop = 100L * r.cap(2).toLong(); - QString tokenStream = r.cap(3); + se.frameStart = 100L * match.captured(1).toLong(); + se.frameStop = 100L * match.captured(2).toLong(); + QString tokenStream = match.captured(3); se.tokens = decodeTokenStream(tokenStream); diff --git a/libqnapi/src/subconvert/formats/subrip.cpp b/libqnapi/src/subconvert/formats/subrip.cpp index f3cb2de0..0f9ac9b1 100644 --- a/libqnapi/src/subconvert/formats/subrip.cpp +++ b/libqnapi/src/subconvert/formats/subrip.cpp @@ -13,6 +13,7 @@ *****************************************************************************/ #include "subrip.h" +#include bool SubRipSubtitleFormat::detect(const QStringList &lines) const { QStringList lines2 = lines; @@ -27,51 +28,54 @@ bool SubRipSubtitleFormat::detect(const QStringList &lines) const { firstEntryBuff += line + "\n"; } - QRegExp r( + static const QRegularExpression firstEntryPattern( "^(\\d+)(\\n|\\r\\n|\\s+)(\\d{2}):(\\d{2}):(\\d{2})\\,(\\d{3})\\s+\\-\\->" "\\s+(\\d{2}):(\\d{2}):(\\d{2})\\,(\\d{3})(.*)"); - return r.exactMatch(firstEntryBuff); + return firstEntryPattern.match(firstEntryBuff).hasMatch(); } SubFile SubRipSubtitleFormat::decode(const QStringList &lines) const { SubFile sf; - QRegExp r1( + static const QRegularExpression r1( "^(\\d+)\\s+(\\d{2}):(\\d{2}):(\\d{2})\\,(\\d{3})\\s+\\-\\->\\s+(\\d{2}):" "(\\d{2}):(\\d{2})\\,(\\d{3})(.*)"); - QRegExp r2( + static const QRegularExpression r2( "^(\\d{2}):(\\d{2}):(\\d{2})\\,(\\d{3})\\s+\\-\\->\\s+(\\d{2}):(\\d{2}):(" "\\d{2})\\,(\\d{3})(.*)"); - QRegExp rNumLine("^\\d+"); + static const QRegularExpression rNumLine("^\\d+$"); QString tokensBuff = "", numsBuff; SrtTimestamps tss; foreach (QString line, lines) { - if (r1.exactMatch(line)) { + auto m1 = r1.match(line); + auto m2 = r2.match(line); + auto mNum = rNumLine.match(line); + if (m1.hasMatch()) { if (!tokensBuff.isEmpty()) addEntry(sf.entries, tokensBuff, tss); - tss.h1 = r1.cap(2).toInt(); - tss.m1 = r1.cap(3).toInt(); - tss.s1 = r1.cap(4).toInt(); - tss.ms1 = r1.cap(5).toInt(); - tss.h2 = r1.cap(6).toInt(); - tss.m2 = r1.cap(7).toInt(); - tss.s2 = r1.cap(8).toInt(); - tss.ms2 = r1.cap(9).toInt(); + tss.h1 = m1.captured(2).toInt(); + tss.m1 = m1.captured(3).toInt(); + tss.s1 = m1.captured(4).toInt(); + tss.ms1 = m1.captured(5).toInt(); + tss.h2 = m1.captured(6).toInt(); + tss.m2 = m1.captured(7).toInt(); + tss.s2 = m1.captured(8).toInt(); + tss.ms2 = m1.captured(9).toInt(); numsBuff.clear(); - } else if (r2.exactMatch(line)) { + } else if (m2.hasMatch()) { if (!tokensBuff.isEmpty()) addEntry(sf.entries, tokensBuff, tss); - tss.h1 = r2.cap(1).toInt(); - tss.m1 = r2.cap(2).toInt(); - tss.s1 = r2.cap(3).toInt(); - tss.ms1 = r2.cap(4).toInt(); - tss.h2 = r2.cap(5).toInt(); - tss.m2 = r2.cap(6).toInt(); - tss.s2 = r2.cap(7).toInt(); - tss.ms2 = r2.cap(8).toInt(); + tss.h1 = m2.captured(1).toInt(); + tss.m1 = m2.captured(2).toInt(); + tss.s1 = m2.captured(3).toInt(); + tss.ms1 = m2.captured(4).toInt(); + tss.h2 = m2.captured(5).toInt(); + tss.m2 = m2.captured(6).toInt(); + tss.s2 = m2.captured(7).toInt(); + tss.ms2 = m2.captured(8).toInt(); numsBuff.clear(); - } else if (rNumLine.exactMatch(line)) { + } else if (mNum.hasMatch()) { numsBuff += line + "\n"; } else if (!line.trimmed().isEmpty()) { if (!numsBuff.isEmpty()) { diff --git a/libqnapi/src/subconvert/formats/tmplayer.cpp b/libqnapi/src/subconvert/formats/tmplayer.cpp index 0cc8e535..008d96fc 100644 --- a/libqnapi/src/subconvert/formats/tmplayer.cpp +++ b/libqnapi/src/subconvert/formats/tmplayer.cpp @@ -13,13 +13,14 @@ *****************************************************************************/ #include "tmplayer.h" +#include bool TMPlayerSubtitleFormat::detect(const QStringList &lines) const { + static const QRegularExpression linePattern( + "^(0\\d|\\d\\d):(0\\d|\\d\\d):(0\\d|\\d\\d):(.*)"); foreach (QString line, lines) { if (!line.trimmed().isEmpty()) { - QRegExp r("^(0\\d|\\d\\d):(0\\d|\\d\\d):(0\\d|\\d\\d):(.*)"); - r.setPatternSyntax(QRegExp::RegExp2); - return r.exactMatch(line); + return linePattern.match(line).hasMatch(); } } @@ -28,16 +29,17 @@ bool TMPlayerSubtitleFormat::detect(const QStringList &lines) const { SubFile TMPlayerSubtitleFormat::decode(const QStringList &lines) const { SubFile sf; + static const QRegularExpression linePattern( + "^(0\\d|\\d\\d):(0\\d|\\d\\d):(0\\d|\\d\\d):(.*)"); foreach (QString line, lines) { if (!line.trimmed().isEmpty()) { - QRegExp r("^(0\\d|\\d\\d):(0\\d|\\d\\d):(0\\d|\\d\\d):(.*)"); - r.setPatternSyntax(QRegExp::RegExp2); - if (r.exactMatch(line)) { - int h = r.cap(1).toInt(); - int m = r.cap(2).toInt(); - int s = r.cap(3).toInt(); - QString tokenStream = r.cap(4); + auto match = linePattern.match(line); + if (match.hasMatch()) { + int h = match.captured(1).toInt(); + int m = match.captured(2).toInt(); + int s = match.captured(3).toInt(); + QString tokenStream = match.captured(4); SubEntry se; se.frameStart = 1000L * (3600L * h + 60L * m + s); diff --git a/libqnapi/src/subconvert/subtitleconverter.cpp b/libqnapi/src/subconvert/subtitleconverter.cpp index 928f1a09..3e850cc5 100644 --- a/libqnapi/src/subconvert/subtitleconverter.cpp +++ b/libqnapi/src/subconvert/subtitleconverter.cpp @@ -14,7 +14,6 @@ #include "subtitleconverter.h" #include -#include #include #include "libqnapi.h" #include "movieinfo/movieinfoprovider.h" @@ -180,31 +179,31 @@ long SubtitleConverter::frame2ts(long frame, double frameRate) const { QStringList SubtitleConverter::readFile(const QString &filename, QString encoding, long atMostLines) const { - QStringList buff; - long current = 0; QFile inputFile(filename); - if (inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - QTextStream in(&inputFile); - in.setCodec(qPrintable(encoding)); - while (!in.atEnd() && (atMostLines == 0 || current < atMostLines)) { - buff += in.readLine(); - ++current; - } - inputFile.close(); + if (!inputFile.open(QIODevice::ReadOnly)) return QStringList(); + EncodingUtils encodingUtils; + QStringList buff = + encodingUtils.decodeToUnicode(inputFile.readAll(), encoding).split("\n"); + inputFile.close(); + + if (atMostLines > 0 && buff.size() > atMostLines) { + buff = buff.mid(0, atMostLines); } + return buff; } bool SubtitleConverter::writeFile(const QString &filename, QString encoding, const QStringList &lines) const { QFile outputFile(filename); - if (outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) { - QTextStream out(&outputFile); - out.setCodec(qPrintable(encoding)); - foreach (QString line, lines) { out << line << "\n"; } - outputFile.close(); - return true; - } else { + if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) return false; - } + + QString text = lines.join("\n"); + if (!lines.isEmpty()) text += "\n"; + + EncodingUtils encodingUtils; + outputFile.write(encodingUtils.encodeFromUnicode(text, encoding)); + outputFile.close(); + return true; } diff --git a/libqnapi/src/subconvert/subtitleformat.cpp b/libqnapi/src/subconvert/subtitleformat.cpp index 23c9f31d..078d390b 100644 --- a/libqnapi/src/subconvert/subtitleformat.cpp +++ b/libqnapi/src/subconvert/subtitleformat.cpp @@ -14,6 +14,7 @@ #include "subtitleformat.h" #include +#include QVector SubtitleFormat::decodeTokenStream(QString tokenStream) const { QVector tokens; @@ -50,23 +51,22 @@ QVector SubtitleFormat::decodeTokenStream(QString tokenStream) const { } if (!matched) { - QRegExp colorR1("^\\{c:(.*)\\}"); - colorR1.setPatternSyntax(QRegExp::RegExp2); - colorR1.setCaseSensitivity(Qt::CaseInsensitive); + static const QRegularExpression colorR1( + "^\\{c:(.*)\\}", QRegularExpression::CaseInsensitiveOption); + static const QRegularExpression colorR2( + "^", QRegularExpression::CaseInsensitiveOption); + auto color1Match = colorR1.match(tokenStream); + auto color2Match = colorR2.match(tokenStream); - QRegExp colorR2("^"); - colorR1.setPatternSyntax(QRegExp::RegExp2); - colorR1.setCaseSensitivity(Qt::CaseInsensitive); - - if (colorR1.indexIn(tokenStream) == 0) { + if (color1Match.hasMatch()) { tok.type = STT_FONTCOLOR; - tok.payload = parseColor(colorR1.cap(1)); - tokenStream.remove(0, colorR1.cap(0).size()); + tok.payload = parseColor(color1Match.captured(1)); + tokenStream.remove(0, color1Match.capturedLength(0)); matched = true; - } else if (colorR2.indexIn(tokenStream) == 0) { + } else if (color2Match.hasMatch()) { tok.type = STT_FONTCOLOR; - tok.payload = parseColor(colorR2.cap(1)); - tokenStream.remove(0, colorR2.cap(0).size()); + tok.payload = parseColor(color2Match.captured(1)); + tokenStream.remove(0, color2Match.capturedLength(0)); matched = true; } else if (tokenStream[0] == '/' && wordBuff.isEmpty()) { tok.type = STT_ITALIC; diff --git a/libqnapi/src/subtitlepostprocessor.cpp b/libqnapi/src/subtitlepostprocessor.cpp index e204ad1f..d084b81e 100644 --- a/libqnapi/src/subtitlepostprocessor.cpp +++ b/libqnapi/src/subtitlepostprocessor.cpp @@ -16,8 +16,7 @@ #include #include -#include -#include +#include SubtitlePostProcessor::SubtitlePostProcessor( const PostProcessingConfig& ppConfig, @@ -67,17 +66,7 @@ bool SubtitlePostProcessor::ppReplaceDiacriticsWithASCII( QByteArray fileContent = f.readAll(); f.close(); - // Fix: Proper UTF-16 handling - QTextCodec* fromCodec; - if (from.startsWith("UTF-16", Qt::CaseInsensitive)) { - fromCodec = QTextCodec::codecForName("UTF-16"); - } else { - fromCodec = QTextCodec::codecForName(qPrintable(from)); - } - - if (!fromCodec) return false; - - QString contentStr = fromCodec->toUnicode(fileContent); + QString contentStr = encodingUtils.decodeToUnicode(fileContent, from); fileContent.clear(); fileContent.append(encodingUtils.replaceDiacriticsWithASCII(contentStr)); @@ -99,25 +88,8 @@ bool SubtitlePostProcessor::ppChangeSubtitlesEncoding( QByteArray fileContent = f.readAll(); f.close(); - // Fix: Proper UTF-16 handling - QTextCodec* fromCodec; - if (from.startsWith("UTF-16", Qt::CaseInsensitive)) { - fromCodec = QTextCodec::codecForName("UTF-16"); - } else { - fromCodec = QTextCodec::codecForName(qPrintable(from)); - } - - if (!fromCodec) return false; - - QString contentStr = fromCodec->toUnicode(fileContent); - - if (to.compare("UTF-8", Qt::CaseInsensitive) != 0) { - fileContent = QTextCodec::codecForName(qPrintable(to)) - ->fromUnicode(contentStr.constData(), contentStr.size()); - } else { - fileContent.clear(); - fileContent.append(contentStr.toUtf8()); - } + QString contentStr = encodingUtils.decodeToUnicode(fileContent, from); + fileContent = encodingUtils.encodeFromUnicode(contentStr, to); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) return false; @@ -142,28 +114,16 @@ bool SubtitlePostProcessor::ppRemoveLinesContainingWords( const QString& subtitleFilePath, QStringList wordList) const { if (!QFileInfo(subtitleFilePath).exists()) return false; - wordList = wordList.filter(QRegExp("^(.+)$")); + wordList = wordList.filter(QRegularExpression("^(.+)$")); QString fromCodec = encodingUtils.detectFileEncoding(subtitleFilePath); if (fromCodec.isEmpty()) fromCodec = ppConfig.encodingFrom(); - // Fix: Read as QString instead of QByteArray to preserve encoding QFile f(subtitleFilePath); - if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false; - - QTextStream in(&f); - if (fromCodec.startsWith("UTF-16", Qt::CaseInsensitive)) { - in.setCodec("UTF-16"); - in.setAutoDetectUnicode(true); - } else { - in.setCodec(qPrintable(fromCodec)); - } + if (!f.open(QIODevice::ReadOnly)) return false; - QStringList lines; - while (!in.atEnd()) { - lines << in.readLine(); - } + QStringList lines = encodingUtils.decodeToUnicode(f.readAll(), fromCodec).split("\n"); f.close(); // Filter lines @@ -181,21 +141,12 @@ bool SubtitlePostProcessor::ppRemoveLinesContainingWords( } } - // Write back with same encoding - if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) return false; - QTextStream out(&f); - if (fromCodec.startsWith("UTF-16", Qt::CaseInsensitive)) { - out.setCodec("UTF-16"); - out.setGenerateByteOrderMark(true); - } else { - out.setCodec(qPrintable(fromCodec)); - } - - foreach (QString line, filtered) { - out << line << "\n"; - } + QString outText = filtered.join("\n"); + if (!filtered.isEmpty()) outText += "\n"; + f.write(encodingUtils.encodeFromUnicode(outText, fromCodec)); f.close(); return true; diff --git a/libqnapi/src/utils/encodingutils.cpp b/libqnapi/src/utils/encodingutils.cpp index 91b10ae6..750f4c0e 100644 --- a/libqnapi/src/utils/encodingutils.cpp +++ b/libqnapi/src/utils/encodingutils.cpp @@ -15,8 +15,7 @@ #include "encodingutils.h" #include -#include -#include +#include EncodingUtils::EncodingUtils() { diacritics = QString::fromUtf8( @@ -108,12 +107,37 @@ EncodingUtils::EncodingUtils() { << "y" << "y"; - codecs << "windows-1257" - << "ISO-8859-13" - << "ISO-8859-16" - << "ISO-8859-2" - << "windows-1250" - << "UTF-8"; + heuristicCodecs << "windows-1257" + << "ISO-8859-13" + << "ISO-8859-16" + << "ISO-8859-2" + << "windows-1250" + << "UTF-8"; + + allCodecs << "UTF-8" + << "UTF-16" + << "UTF-16LE" + << "UTF-16BE" + << "windows-1250" + << "windows-1251" + << "windows-1252" + << "windows-1257" + << "ISO-8859-1" + << "ISO-8859-2" + << "ISO-8859-5" + << "ISO-8859-7" + << "ISO-8859-9" + << "ISO-8859-13" + << "ISO-8859-15" + << "ISO-8859-16" + << "KOI8-R" + << "KOI8-U" + << "IBM866" + << "Shift_JIS" + << "EUC-JP" + << "GB18030" + << "Big5" + << "EUC-KR"; } QString EncodingUtils::replaceDiacriticsWithASCII(const QString &str) const { @@ -132,19 +156,19 @@ QString EncodingUtils::replaceDiacriticsWithASCII(const QString &str) const { } QString EncodingUtils::detectBufferEncoding(const QByteArray &buffer) const { - QTextCodec::ConverterState state; - QTextCodec::codecForName("UTF-8")->toUnicode(buffer.constData(), - buffer.length(), &state); + if (buffer.startsWith("\xEF\xBB\xBF")) return "UTF-8"; + if (buffer.startsWith("\xFF\xFE")) return "UTF-16LE"; + if (buffer.startsWith("\xFE\xFF")) return "UTF-16BE"; - if (state.invalidChars == 0) return "UTF-8"; + QStringDecoder utf8Decoder(QStringConverter::Utf8); + utf8Decoder.decode(buffer); + if (!utf8Decoder.hasError()) return "UTF-8"; int bestMatch = 0; QString from; - foreach (QString codec, codecs) { - QTextCodec *tc = QTextCodec::codecForName(qPrintable(codec)); - - const QString text = tc->toUnicode(buffer.constData(), buffer.size()); + foreach (QString codec, heuristicCodecs) { + const QString text = decodeToUnicode(buffer, codec); QStringList chars = QString::fromUtf8("ą/ś/ż/ć/ń/ł/ó/ę").split("/"); @@ -167,10 +191,43 @@ QString EncodingUtils::detectBufferEncoding(const QByteArray &buffer) const { QString EncodingUtils::detectFileEncoding(const QString &filename) const { QFile f(filename); - if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { + if (f.open(QIODevice::ReadOnly)) { QString enc = detectBufferEncoding(f.readAll()); f.close(); return enc; } return ""; } + +QString EncodingUtils::decodeToUnicode(const QByteArray& buffer, + const QString& encoding) const { + auto maybeEncoding = QStringConverter::encodingForName(encoding.toUtf8()); + if (!maybeEncoding) { + QStringDecoder fallback(QStringConverter::Utf8); + return fallback.decode(buffer); + } + + QStringDecoder decoder(*maybeEncoding); + return decoder.decode(buffer); +} + +QByteArray EncodingUtils::encodeFromUnicode(const QString& text, + const QString& encoding) const { + auto maybeEncoding = QStringConverter::encodingForName(encoding.toUtf8()); + if (!maybeEncoding) { + QStringEncoder fallback(QStringConverter::Utf8); + return fallback.encode(text); + } + + auto flags = QStringConverter::Flags{}; + if (encoding.compare("UTF-16", Qt::CaseInsensitive) == 0 || + encoding.compare("UTF-16LE", Qt::CaseInsensitive) == 0 || + encoding.compare("UTF-16BE", Qt::CaseInsensitive) == 0) { + flags |= QStringConverter::Flag::WriteBom; + } + + QStringEncoder encoder(*maybeEncoding, flags); + return encoder.encode(text); +} + +QStringList EncodingUtils::availableEncodings() const { return allCodecs; } diff --git a/libqnapi/src/utils/encodingutils.h b/libqnapi/src/utils/encodingutils.h index 71c37dd2..dc696aa0 100644 --- a/libqnapi/src/utils/encodingutils.h +++ b/libqnapi/src/utils/encodingutils.h @@ -26,11 +26,17 @@ class EncodingUtils { QString replaceDiacriticsWithASCII(const QString& str) const; QString detectBufferEncoding(const QByteArray& buffer) const; QString detectFileEncoding(const QString& filename) const; + QString decodeToUnicode(const QByteArray& buffer, + const QString& encoding) const; + QByteArray encodeFromUnicode(const QString& text, + const QString& encoding) const; + QStringList availableEncodings() const; private: QString diacritics; QStringList replacements; - QStringList codecs; + QStringList heuristicCodecs; + QStringList allCodecs; }; #endif // ENCODINGUTILS_H diff --git a/libqnapi/src/utils/p7zipdecoder.cpp b/libqnapi/src/utils/p7zipdecoder.cpp index 8a8f2870..ca81324d 100644 --- a/libqnapi/src/utils/p7zipdecoder.cpp +++ b/libqnapi/src/utils/p7zipdecoder.cpp @@ -15,6 +15,7 @@ #include "p7zipdecoder.h" #include +#include P7ZipDecoder::P7ZipDecoder(const QString& p7zipPath, int operationTimeoutMsecs) : p7zipPath(p7zipPath), operationTimeoutMsecs(operationTimeoutMsecs) {} @@ -22,19 +23,13 @@ P7ZipDecoder::P7ZipDecoder(const QString& p7zipPath, int operationTimeoutMsecs) QStringList P7ZipDecoder::listArchiveFiles(const QString& archivePath) const { QString stdoutBuff = readP7ZipOutput({"l", "-slt", archivePath}); -#ifdef Q_OS_WIN - QRegExp r("\r\nPath = ([^\r\n]*)\r\n"); -#else - QRegExp r("\nPath = ([^\n]*)\n"); -#endif - r.setPatternSyntax(QRegExp::RegExp2); + static const QRegularExpression pathPattern( + "^Path = ([^\r\n]*)$", QRegularExpression::MultilineOption); QStringList files; - int offset = 0; - - while ((offset = r.indexIn(stdoutBuff, offset)) != -1) { - files << r.cap(1); - offset += r.matchedLength(); + auto matches = pathPattern.globalMatch(stdoutBuff); + while (matches.hasNext()) { + files << matches.next().captured(1); } return files;