From f2b7ac1821344a9f284b26de1f73a48d9efa14a3 Mon Sep 17 00:00:00 2001 From: Gres Date: Sun, 1 Mar 2020 12:30:43 +0300 Subject: [PATCH] Add build and ci scripts --- .github/workflows/build.yml | 118 +++++++ screen-translator.pro | 35 +- share/ci/appimage.py | 54 +++ share/ci/build.py | 24 ++ share/ci/common.py | 213 ++++++++++++ share/ci/config.py | 23 ++ share/ci/get_leptonica.py | 71 ++++ share/ci/get_qt.py | 82 +++++ share/ci/get_tesseract.py | 74 ++++ share/ci/macdeploy.py | 18 + share/ci/windeploy.py | 35 ++ share/images/icon.icns | Bin 0 -> 38622 bytes share/images/screentranslator.png | Bin 0 -> 3985 bytes share/screentranslator.desktop | 9 + share/translations/screentranslator_ru.ts | 399 ++++++++++++++++++++++ 15 files changed, 1152 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 share/ci/appimage.py create mode 100644 share/ci/build.py create mode 100644 share/ci/common.py create mode 100644 share/ci/config.py create mode 100644 share/ci/get_leptonica.py create mode 100644 share/ci/get_qt.py create mode 100644 share/ci/get_tesseract.py create mode 100644 share/ci/macdeploy.py create mode 100644 share/ci/windeploy.py create mode 100644 share/images/icon.icns create mode 100644 share/images/screentranslator.png create mode 100644 share/screentranslator.desktop create mode 100644 share/translations/screentranslator_ru.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..600f0a0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,118 @@ +name: Build + +on: [push] + +jobs: + release: + name: Create release + if: contains(github.ref, '/tags/') + runs-on: ubuntu-16.04 + steps: + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false + prerelease: false + + - name: Store release url + run: echo "${{ steps.create_release.outputs.upload_url }}" > ./release_upload_url + + - name: Upload release url + uses: actions/upload-artifact@v1 + with: + path: ./release_upload_url + name: release_upload_url + + build: + name: Build ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + env: + OS: ${{ matrix.config.name }} + MSVC_VERSION: 2019/Enterprise + strategy: + matrix: + config: + - { name: "win64", os: windows-latest } + - { name: "win32", os: windows-latest } + - { name: "linux", os: ubuntu-16.04 } + - { name: "macos", os: macos-latest } + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 10 + + - name: Setup python + uses: actions/setup-python@v1 + with: + python-version: "3.x" + + - name: Install system libs + if: runner.os == 'Linux' + run: sudo apt install libgl1-mesa-dev libxkbcommon-x11-0 + + - name: Get Qt + run: python ./share/ci/get_qt.py + + - name: Get dependencies + run: | + python ./share/ci/get_leptonica.py + python ./share/ci/get_tesseract.py + + - name: Build + run: python ./share/ci/build.py + + - name: Create AppImage + if: runner.os == 'Linux' + shell: bash + run: | + python ./share/ci/appimage.py + echo ::set-env name=artifact::`python ./share/ci/appimage.py artifact_name` + + - name: Create win deploy + if: runner.os == 'Windows' + shell: bash + run: | + python ./share/ci/windeploy.py + echo ::set-env name=artifact::`python ./share/ci/windeploy.py artifact_name` + + - name: Create mac deploy + if: runner.os == 'macOS' + shell: bash + run: | + python ./share/ci/macdeploy.py + echo ::set-env name=artifact::`python ./share/ci/macdeploy.py artifact_name` + + - name: Upload build artifact + if: env.artifact != '' + uses: actions/upload-artifact@v1 + with: + name: ${{ env.artifact }} + path: ./${{ env.artifact }} + + - name: Download release url + if: contains(github.ref, '/tags/') + uses: actions/download-artifact@v1 + with: + name: release_upload_url + path: ./ + + - name: Set release env + if: contains(github.ref, '/tags/') + shell: bash + run: echo ::set-env name=upload_url::`cat ./release_upload_url` + + - name: Upload release artifacts + if: contains(github.ref, '/tags/') + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ env.upload_url }} + asset_path: ./${{ env.artifact }} + asset_name: ${{ env.artifact }} + asset_content_type: application/zip diff --git a/screen-translator.pro b/screen-translator.pro index 59cc9e1..96485b7 100644 --- a/screen-translator.pro +++ b/screen-translator.pro @@ -87,6 +87,35 @@ OTHER_FILES += \ translators/*.js \ version.json -#TRANSLATIONS += \ -# translations/translation_en.ts \ -# translations/translation_ru.ts +TRANSLATIONS += \ + share/translations/screentranslator_ru.ts + +translations.files = $$PWD/share/translations/screentranslator_ru.qm + +translators.files = $$PWD/translators/*.js + +linux { + PREFIX = /usr + + target.path = $$PREFIX/bin + + shortcuts.files = $$PWD/share/screentranslator.desktop + shortcuts.path = $$PREFIX/share/applications/ + pixmaps.files += $$PWD/share/images/screentranslator.png + pixmaps.path = $$PREFIX/share/icons/hicolor/128x128/apps/ + translations.path = $$PREFIX/translations + + INSTALLS += target shortcuts pixmaps translations +} +win32 { + RC_ICONS = $$PWD/share/images/icon.ico + translations.path = /translations + target.path = / + translators.path = /translators + INSTALLS += target translations translators +} +mac { + translations.path = Contents/Translations + QMAKE_BUNDLE_DATA += translations + ICON = $$PWD/share/images/icon.icns +} diff --git a/share/ci/appimage.py b/share/ci/appimage.py new file mode 100644 index 0000000..0373e9d --- /dev/null +++ b/share/ci/appimage.py @@ -0,0 +1,54 @@ +import common as c +from config import * +import os +import sys +import subprocess as sub + +if len(sys.argv) > 1 and sys.argv[1] == 'glibc_version': # subcommand + sub.run('ldd --version | head -n 1 | grep -Po "\\d\\.\\d\\d"', shell=True) + exit(0) + +artifact_name = '{}-{}.AppImage'.format(app_name, app_version) +if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand + c.print(artifact_name) + exit(0) +artifact_path = os.path.abspath(artifact_name) + +c.print('>> Making appimage') + +base_url = 'https://github.com/probonopd/linuxdeployqt/releases/download' +continuous_url = base_url + '/continuous/linuxdeployqt-continuous-x86_64.AppImage' +tagged_url = base_url + '/6/linuxdeployqt-6-x86_64.AppImage' +linuxdeployqt_url = tagged_url +linuxdeployqt_original = os.path.basename(linuxdeployqt_url) + +c.download(linuxdeployqt_url, linuxdeployqt_original) +c.run('chmod a+x {}'.format(linuxdeployqt_original)) + +linuxdeployqt_bin = os.path.abspath('linuxdeployqt') +c.symlink(linuxdeployqt_original, linuxdeployqt_bin) + +os.chdir(build_dir) + +install_dir = os.path.abspath('appdir') +c.recreate_dir(install_dir) +c.run('make INSTALL_ROOT={0} DESTDIR={0} install'.format(install_dir)) + +if c.is_inside_docker(): + c.run('{} --appimage-extract'.format(linuxdeployqt_bin)) + linuxdeployqt_bin = os.path.abspath('squashfs-root/AppRun') + +os.environ['LD_LIBRARY_PATH'] = dependencies_dir + '/lib' +os.environ['VERSION'] = app_version +# debug flags: -unsupported-bundle-everything -unsupported-allow-new-glibc +flags = '' if os.getenv("DEBUG") is None else '-unsupported-allow-new-glibc' + +# remove serial post dependency +os.rename(qt_dir + '/plugins/position', qt_dir + '/plugins/position1') + +c.run('{} {}/usr/share/applications/*.desktop {} -appimage -qmake={}/bin/qmake'.format( + linuxdeployqt_bin, install_dir, flags, qt_dir)) + +os.rename(qt_dir + '/plugins/position1', qt_dir + '/plugins/position') + +c.run('mv {}-{}*.AppImage "{}"'.format(app_name, app_version, artifact_path)) diff --git a/share/ci/build.py b/share/ci/build.py new file mode 100644 index 0000000..3c9939f --- /dev/null +++ b/share/ci/build.py @@ -0,0 +1,24 @@ +import common as c +from config import * +import os +import platform + +c.print('>> Building {} on {}'.format(app_name, os_name)) + +c.add_to_path(os.path.abspath(qt_dir + '/bin')) +os.environ['ST_DEPS_DIR'] = dependencies_dir + +if platform.system() == "Windows": + env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version) + c.apply_cmd_env(env_cmd) + +c.recreate_dir(build_dir) +os.chdir(build_dir) + +c.run('lupdate "{}"'.format(pro_file)) +c.run('lrelease "{}"'.format(pro_file)) + +c.set_make_threaded() +c.run('qmake "{}"'.format(pro_file)) +make_cmd = c.get_make_cmd() +c.run(make_cmd) diff --git a/share/ci/common.py b/share/ci/common.py new file mode 100644 index 0000000..47b76c2 --- /dev/null +++ b/share/ci/common.py @@ -0,0 +1,213 @@ +import os +import subprocess as sub +import urllib.request +from shutil import which +import zipfile +import tarfile +import functools +import shutil +import multiprocessing +import platform +import re + + +print = functools.partial(print, flush=True) + + +def run(cmd, capture_output=False, silent=False): + print('>> Running', cmd) + if capture_output: + result = sub.run(cmd, check=True, shell=True, universal_newlines=True, + stdout=sub.PIPE, stderr=sub.STDOUT) + if not silent: + print(result.stdout) + else: + if not silent: + result = sub.run(cmd, check=True, shell=True) + else: + result = sub.run(cmd, check=True, shell=True, + stdout=sub.DEVNULL, stderr=sub.DEVNULL) + return result + + +def download(url, out, force=False): + print('>> Downloading', url, 'as', out) + if not force and os.path.exists(out): + print('>>', out, 'already exists') + return + out_path = os.path.dirname(out) + if len(out_path) > 0: + os.makedirs(out_path, exist_ok=True) + urllib.request.urlretrieve(url, out) + + +def extract(src, dest): + abs_path = os.path.abspath(src) + print('>> Extracting', abs_path, 'to', dest) + if len(dest) > 0: + os.makedirs(dest, exist_ok=True) + + if which('cmake'): + out = run('cmake -E tar t "{}"'.format(abs_path), + capture_output=True, silent=True) + files = out.stdout.split('\n') + already_exist = True + for file in files: + if not os.path.exists(os.path.join(dest, file)): + already_exist = False + break + if already_exist: + print('>> All files already exist') + return + sub.run('cmake -E tar xvf "{}"'.format(abs_path), + check=True, shell=True, cwd=dest) + return + + is_tar_smth = src.endswith('.tar', 0, src.rfind('.')) + if which('7z'): + sub.run('7z x "{}" -o"{}"'.format(abs_path, dest), + check=True, shell=True, input=b'S\n') + + if is_tar_smth: + inner_name = abs_path[:abs_path.rfind('.')] + sub.run('7z x "{}" -o"{}"'.format(inner_name, dest), + check=True, shell=True, input=b'S\n') + return + + if src.endswith('.tar') or is_tar_smth: + path = abs_path if platform.system() != "Windows" else os.path.relpath(abs_path) + if which('tar'): + sub.run('tar xf "{}" --keep-newer-files -C "{}"'.format(path, dest), + check=True, shell=True) + return + + raise RuntimeError('No archiver to extract {} file'.format(src)) + + +def get_folder_files(path): + result = [] + for root, _, files in os.walk(path): + for file in files: + result.append(os.path.join(root, file)) + return result + + +def get_archive_top_dir(path): + """Return first top level folder name in given archive or raises RuntimeError""" + with tarfile.open(path) as tar: + first = tar.next() + if not first is None: + result = os.path.dirname(first.path) + if len(result) == 0: + result = first.path + return result + raise RuntimeError('Failed to open file or empty archive ' + path) + + +def archive(files, out): + print('>> Archiving', files, 'into', out) + if out.endswith('.zip'): + arc = zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) + for f in files: + arc.write(f) + arc.close() + return + + if out.endswith('.tar.gz'): + arc = tarfile.open(out, 'w|gz') + for f in files: + arc.add(f) + arc.close() + return + + raise RuntimeError('No archiver to create {} file'.format(out)) + + +def symlink(src, dest): + print('>> Creating symlink', src, '=>', dest) + norm_src = os.path.normcase(src) + norm_dest = os.path.normcase(dest) + if os.path.lexists(norm_dest): + os.remove(norm_dest) + os.symlink(norm_src, norm_dest, + target_is_directory=os.path.isdir(norm_src)) + + +def recreate_dir(path): + shutil.rmtree(path, ignore_errors=True) + os.mkdir(path) + + +def add_to_path(entry, prepend=True): + path_separator = ';' if platform.system() == "Windows" else ':' + os.environ['PATH'] = entry + path_separator + os.environ['PATH'] + + +def get_msvc_env_cmd(bitness='64', msvc_version=''): + """Return environment setup command for running msvc compiler for current platform""" + if platform.system() != "Windows": + return None + + msvc_path = 'C:/Program Files (x86)/Microsoft Visual Studio' + if len(msvc_version) == 0: + with os.scandir(msvc_path) as ver_it: + version = next(ver_it, '') + with os.scandir(msvc_path + '/' + version) as ed_it: + msvc_version = version + '/' + next(ed_it, '') + + env_script = msvc_path + '/{}/VC/Auxiliary/Build/vcvars{}.bat'.format( + msvc_version, bitness) + return '"' + env_script + '"' + + +def get_cmake_arch_args(bitness='64'): + if platform.system() != "Windows": + return '' + return '-A {}'.format('Win32' if bitness == '32' else 'x64') + + +def get_make_cmd(): + """Return `make` command for current platform""" + return 'nmake' if platform.system() == "Windows" else 'make' + + +def set_make_threaded(): + """Adjust environment to run threaded make command""" + if platform.system() == "Windows": + os.environ['CL'] = '/MP' + else: + os.environ['MAKEFLAGS'] = '-j{}'.format(multiprocessing.cpu_count()) + + +def is_inside_docker(): + """ Return True if running in a Docker container """ + with open('/proc/1/cgroup', 'rt') as f: + return 'docker' in f.read() + + +def ensure_got_path(path): + os.makedirs(path, exist_ok=True) + + +def apply_cmd_env(cmd): + """Run cmd and apply its modified environment""" + print('>> Applying env after', cmd) + env_cmd = 'env' if which('env') else 'set' + env = sub.run('{} && {}'.format(cmd, env_cmd), shell=True, universal_newlines=True, + stdout=sub.PIPE) + + is_mingw = 'MINGW_CHOST' in os.environ + + lines = env.stdout.split('\n') + for line in lines: + match = re.match(r"^([a-zA-Z0-9_-]+)=(.*)$", line) + if not match: + continue + key, value = match.groups() + if key in os.environ and os.environ[key] == value: + continue + if is_mingw and key.find('PATH') != -1 and value.find('/') != -1: + value = value.replace(':', ';') + value = re.sub(r'/(\w)/', r'\1:\\', value) + value = value.replace('/', '\\') + os.environ[key] = value diff --git a/share/ci/config.py b/share/ci/config.py new file mode 100644 index 0000000..d24a1fc --- /dev/null +++ b/share/ci/config.py @@ -0,0 +1,23 @@ +from os import getenv, path + +app_name = 'ScreenTranslator' +app_version = '3.0.0' + +target_name = app_name +qt_version = '5.14.0' +qt_modules = ['qtbase', 'qttools', 'icu', + 'qttranslations', 'qtx11extras', 'qtwebengine', 'qtwebchannel', + 'qtdeclarative', 'qtlocation', 'opengl32sw', 'd3dcompiler_47'] +qt_dir = path.abspath('qt') + +build_dir = path.abspath('build') +dependencies_dir = path.abspath('deps') +pro_file = path.abspath(path.dirname(__file__) + + '/../../screen-translator.pro') +ts_files_dir = path.abspath(path.dirname(__file__) + '/../../translations') + +os_name = getenv('OS', 'linux') +app_version += {'linux': '', 'macos': '-experimental', + 'win32': '', 'win64': ''}[os_name] +bitness = '32' if os_name == 'win32' else '64' +msvc_version = getenv('MSVC_VERSION', '2017/Community') diff --git a/share/ci/get_leptonica.py b/share/ci/get_leptonica.py new file mode 100644 index 0000000..e31fe82 --- /dev/null +++ b/share/ci/get_leptonica.py @@ -0,0 +1,71 @@ +import common as c +from config import bitness, msvc_version, build_dir, dependencies_dir +import os +import platform + +c.print('>> Installing leptonica') + +install_dir = dependencies_dir +url = 'http://www.leptonica.org/source/leptonica-1.78.0.tar.gz' +required_version = '1.78.0' + + +def check_existing(): + if platform.system() == "Windows": + dll = install_dir + '/bin/leptonica-1.78.0.dll' + lib = install_dir + '/lib/leptonica-1.78.0.lib' + if not os.path.exists(dll) or not os.path.exists(lib): + return False + c.symlink(dll, install_dir + '/bin/leptonica.dll') + c.symlink(lib, install_dir + '/lib/leptonica.lib') + elif platform.system() == "Darwin": + lib = install_dir + '/lib/libleptonica.1.78.0.dylib' + if not os.path.exists(lib): + return False + c.symlink(lib, install_dir + '/lib/libleptonica.dylib') + else: + if not os.path.exists(install_dir + '/lib/libleptonica.so'): + return False + + includes_path = install_dir + '/include/leptonica' + if len(c.get_folder_files(includes_path)) == 0: + return False + + version_file = install_dir + '/cmake/LeptonicaConfig-version.cmake' + if not os.path.exists(version_file): + return False + + with open(version_file, 'rt') as f: + existing_version = f.readline()[22:28] # set(Leptonica_VERSION 1.78.0) + if existing_version != required_version: + return False + return True + + +if check_existing(): + c.print('>> Using cached') + exit(0) + +archive = os.path.basename(url) +c.download(url, archive) + +src_dir = os.path.abspath('leptonica_src') +c.extract(archive, '.') +c.symlink(c.get_archive_top_dir(archive), src_dir) + +c.ensure_got_path(install_dir) + +c.recreate_dir(build_dir) +os.chdir(build_dir) + +cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}"'.format(src_dir, install_dir) + +if platform.system() == "Windows": + env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version) + c.apply_cmd_env(env_cmd) + cmake_args += ' ' + c.get_cmake_arch_args(bitness=bitness) + +c.set_make_threaded() +c.run('cmake {}'.format(cmake_args)) +c.run('cmake --build . --config Release') +c.run('cmake --build . --target install --config Release') diff --git a/share/ci/get_qt.py b/share/ci/get_qt.py new file mode 100644 index 0000000..430ce2f --- /dev/null +++ b/share/ci/get_qt.py @@ -0,0 +1,82 @@ +import common as c +from config import qt_modules, qt_version, qt_dir, os_name +import sys +import xml.etree.ElementTree as ET + +c.print('>> Downloading Qt {} ({}) for {}'.format( + qt_version, qt_modules, os_name)) + +if os_name == 'linux': + os_url = 'linux_x64' + kit_arch = 'gcc_64' + qt_dir_prefix = '{}/gcc_64'.format(qt_version) +elif os_name == 'win32': + os_url = 'windows_x86' + kit_arch = 'win32_msvc2017' + qt_dir_prefix = '{}/msvc2017'.format(qt_version) +elif os_name == 'win64': + os_url = 'windows_x86' + kit_arch = 'win64_msvc2017_64' + qt_dir_prefix = '{}/msvc2017_64'.format(qt_version) +elif os_name == 'macos': + os_url = 'mac_x64' + kit_arch = 'clang_64' + qt_dir_prefix = '{}/clang_64'.format(qt_version) + +qt_version_dotless = qt_version.replace('.', '') +base_url = 'https://download.qt.io/online/qtsdkrepository/{}/desktop/qt5_{}' \ + .format(os_url, qt_version_dotless) +updates_file = 'Updates-{}-{}.xml'.format(qt_version, os_name) +c.download(base_url + '/Updates.xml', updates_file) + +updates = ET.parse(updates_file) +updates_root = updates.getroot() +all_modules = {} +for i in updates_root.iter('PackageUpdate'): + name = i.find('Name').text + if 'debug' in name or not kit_arch in name: + continue + + archives = i.find('DownloadableArchives') + if archives.text is None: + continue + + archives_parts = archives.text.split(',') + version = i.find('Version').text + for archive in archives_parts: + archive = archive.strip() + parts = archive.split('-') + module_name = parts[0] + all_modules[module_name] = {'package': name, 'file': version + archive} + +if len(sys.argv) > 1: # handle subcommand + if sys.argv[1] == 'list': + c.print('Available modules:') + for k in iter(sorted(all_modules.keys())): + c.print(k, '---', all_modules[k]['file']) + exit(0) + +for module in qt_modules: + if module not in all_modules: + c.print('>> Required module {} not available'.format(module)) + continue + file_name = all_modules[module]['file'] + package = all_modules[module]['package'] + c.download(base_url + '/' + package + '/' + file_name, file_name) + c.extract(file_name, '.') + +c.symlink(qt_dir_prefix, qt_dir) + +c.print('>> Updating license') +config_name = qt_dir + '/mkspecs/qconfig.pri' +config = '' +with open(config_name, 'r') as f: + config = f.read() + +config = config.replace('Enterprise', 'OpenSource') +config = config.replace('licheck.exe', '') +config = config.replace('licheck64', '') +config = config.replace('licheck_mac', '') + +with open(config_name, 'w') as f: + f.write(config) diff --git a/share/ci/get_tesseract.py b/share/ci/get_tesseract.py new file mode 100644 index 0000000..0c89e5b --- /dev/null +++ b/share/ci/get_tesseract.py @@ -0,0 +1,74 @@ +import common as c +from config import bitness, msvc_version, build_dir, dependencies_dir +import os +import platform + +c.print('>> Installing tesseract') + +install_dir = dependencies_dir +url = 'https://github.com/tesseract-ocr/tesseract/archive/4.1.1.tar.gz' +required_version = '4.1.1' + + +def check_existing(): + if platform.system() == "Windows": + dll = install_dir + '/bin/tesseract41.dll' + lib = install_dir + '/lib/tesseract41.lib' + if not os.path.exists(dll) or not os.path.exists(lib): + return False + c.symlink(dll, install_dir + '/bin/tesseract.dll') + c.symlink(lib, install_dir + '/lib/tesseract.lib') + elif platform.system() == "Darwin": + lib = install_dir + '/lib/libtesseract.4.1.1.dylib' + if not os.path.exists(lib): + return False + c.symlink(lib, install_dir + '/lib/libtesseract.dylib') + else: + if not os.path.exists(install_dir + '/lib/libtesseract.so'): + return False + + includes_path = install_dir + '/include/tesseract' + if len(c.get_folder_files(includes_path)) == 0: + return False + + version_file = install_dir + '/cmake/TesseractConfig-version.cmake' + if not os.path.exists(version_file): + return False + + with open(version_file, 'rt') as f: + existing_version = f.readline()[22:27] # set(Tesseract_VERSION 1.78.0) + if existing_version != required_version: + return False + return True + + +if check_existing(): + c.print('>> Using cached') + exit(0) + +archive = 'tesseract-' + os.path.basename(url) +c.download(url, archive) + +src_dir = os.path.abspath('tesseract_src') +c.extract(archive, '.') +c.symlink(c.get_archive_top_dir(archive), src_dir) + +c.ensure_got_path(install_dir) + +c.recreate_dir(build_dir) +os.chdir(build_dir) + +os.environ['PKG_CONFIG_PATH'] = install_dir + '/lib/pkgconfig' + +cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}" -DBUILD_TRAINING_TOOLS=OFF \ + -DBUILD_TESTS=OFF'.format(src_dir, install_dir) + +if platform.system() == "Windows": + env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version) + c.apply_cmd_env(env_cmd) + cmake_args += ' ' + c.get_cmake_arch_args(bitness=bitness) + +c.set_make_threaded() +c.run('cmake {}'.format(cmake_args)) +c.run('cmake --build . --config Release') +c.run('cmake --build . --target install --config Release') diff --git a/share/ci/macdeploy.py b/share/ci/macdeploy.py new file mode 100644 index 0000000..9650f85 --- /dev/null +++ b/share/ci/macdeploy.py @@ -0,0 +1,18 @@ +import common as c +from config import * +import os +import sys + +artifact_name = '{}-{}.dmg'.format(app_name, app_version) +if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand + c.print(artifact_name) + exit(0) +artifact_path = os.path.abspath(artifact_name) + +c.print('>> Making mac deploy') + +os.chdir(build_dir) +build_target = build_dir + '/' + target_name + '.app' +built_dmg = build_dir + '/' + target_name + '.dmg' +c.run('{}/bin/macdeployqt "{}" -dmg'.format(qt_dir, build_target)) +os.rename(built_dmg, artifact_path) diff --git a/share/ci/windeploy.py b/share/ci/windeploy.py new file mode 100644 index 0000000..8e9def4 --- /dev/null +++ b/share/ci/windeploy.py @@ -0,0 +1,35 @@ +import common as c +from config import * +import os +import sys +import shutil + +artifact_name = '{}-{}-{}.zip'.format(app_name, app_version, os_name) +if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand + c.print(artifact_name) + exit(0) +artifact_path = os.path.abspath(artifact_name) + +c.print('>> Making win deploy') + +if os_name.startswith('win'): + env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version) + c.apply_cmd_env(env_cmd) + +pwd = os.getcwd() +os.chdir(build_dir) + +install_dir = os.path.abspath(app_name) +c.recreate_dir(install_dir) + +c.run('nmake INSTALL_ROOT="{0}" DESTDIR="{0}" install'.format(install_dir)) +c.run('{}/bin/windeployqt.exe "{}"'.format(qt_dir, install_dir)) + +libs_dir = os.path.join(dependencies_dir, 'bin') +for file in os.scandir(libs_dir): + if file.is_file(follow_symlinks=False) and file.name.endswith('.dll'): + full_name = os.path.join(libs_dir, file.name) + c.print('>> Copying {} to {}'.format(full_name, install_dir)) + shutil.copy(full_name, install_dir) + +c.archive(c.get_folder_files(os.path.relpath(install_dir)), artifact_path) diff --git a/share/images/icon.icns b/share/images/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..314215b714db470e3b4150efa04a406534da916f GIT binary patch literal 38622 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 literal 0 HcmV?d00001 diff --git a/share/images/screentranslator.png b/share/images/screentranslator.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca2c3ce53608988b24b234da0f0ac5ad991158f GIT binary patch literal 3985 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 h;reP){C~niFgI?I0#`1>(@>%C)-{y)uCYRdos literal 0 HcmV?d00001 diff --git a/share/screentranslator.desktop b/share/screentranslator.desktop new file mode 100644 index 0000000..5d0730f --- /dev/null +++ b/share/screentranslator.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Comment=OCR and translation tool +Exec=screen-translator +GenericName=Screen Translator +Name=ScreenTranslator +Icon=screentranslator +Terminal=false +Type=Application +Categories=Utility;Core;Qt; diff --git a/share/translations/screentranslator_ru.ts b/share/translations/screentranslator_ru.ts new file mode 100644 index 0000000..6d7c496 --- /dev/null +++ b/share/translations/screentranslator_ru.ts @@ -0,0 +1,399 @@ + + + + + QObject + + + OCR and translation tool + + + + + The last result was copied to the clipboard. + + + + + Optical character recognition (OCR) and translation tool +Author: Gres (translator@gres.biz) +Version: %1 + + + + + About + + + + + unknown recognition language: %1 + + + + + troubles with tessdata + + + + + Failed to recognize text + + + + + Another instance is running. Lock file is busy. + + + + + unknown translation language: %1 + + + + + RecognizeWorker + + + Failed to init OCR engine: %1 + + + + + Recognizer + + + Tessdata path is empty + + + + + SettingsEditor + + + Настройки + + + + + Shortcuts + + + + + <html><head/><body><p>Сочетание клавиш для перехода в режим захвата.</p></body></html> + + + + + Capture + + + + + <html><head/><body><p>Сочетание клавиш для перехода в режим захвата, но с использованием последнего использованного, а не текущего, изображения.</p></body></html> + + + + + Repeat capture + + + + + <html><head/><body><p>Сочетание клавиш для повторного отображения последнего результата.</p></body></html> + + + + + Show last result + + + + + <html><head/><body><p>Сочетание клавиш для копирования последнего результата в буфер обмена.</p></body></html> + + + + + Copy result to clipboard + + + + + Proxy + + + + + Type: + + + + + User: + + + + + Address: + + + + + Password: + + + + + Port: + + + + + save password (unsafe) + + + + + + <html><head/><body><p>Заполняется на основании содержания tessdata</p></body></html> + + + + + Language + + + + + ... + + + + + + Path to tessdata + + + + + Enter path + + + + + + Language: + + + + + <html><head/><body><p>Символы, регулярно распознаваемые с ошибками. При обнаружении будут заменены на указанные.</p></body></html> + + + + + Corrections + + + + + <html><head/><body><p>Отображает окно переводчика. Следует использовать только для разработки переводчиков.</p></body></html> + + + + + Debug mode + + + + + secs + + + + + Ignore SSL errors + + + + + Translators + + + + + <html><head/><body><p>Язык, на который осуществляется перевод.</p></body></html> + + + + + <html><head/><body><p>Отображены в порядке убывания приоритета.</p></body></html> + + + + + <html><head/><body><p>Необходимо ли переводить (вкл) распознанный текст.</p></body></html> + + + + + Translate text + + + + + <html><head/><body><p>Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &quot;зависшим&quot;.</p></body></html> + + + + + Single translator timeout: + + + + + Result type + + + + + Tray + + + + + Window + + + + + Check for updates: + + + + + Check now + + + + + General + + + + + Recognition + + + + + Correction + + + + + Translation + + + + + Representation + + + + + Update + + + + + Never + + + + + Daily + + + + + Weekly + + + + + Monthly + + + + + Translator + + + No translators loaded from %1 (named %2) + + + + + All translators failed + + + + + TrayIcon + + + Failed to register global shortcuts: +%1 + + + + + + Error + + + + + Capture + + + + + Repeat capture + + + + + Result + + + + + Show + + + + + To clipboard + + + + + Settings + + + + + About + + + + + Quit + + + +