File speechnote.spec of Package speechnote

#
# spec file for package speechnote
#
# Copyright (c) 2025 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# Please submit bugfixes or comments via http://bugs.opensuse.org/
#

%define  aname  dsnote
%define __provides_exclude ^(libRHVoice.*\\.so.*|libaprilasr\\.so.*|libbergamot_api.*\\.so.*|libclblast\\.so.*|libespeak-ng\\.so.*|libkenlm\\.so.*|libonnxruntime\\.so.*|libstt\\.so.*|libtensorflowlite\\.so.*|libtflitedelegates\\.so.*|libvosk\\.so.*|libwhisper-.*\\.so.*|mimehandler\\(.*|perl\\(.*)$
%define __requires_exclude ^(libRHVoice.*\\.so.*|libaprilasr\\.so.*|libbergamot_api.*\\.so.*|libclblast\\.so.*|libespeak-ng\\.so.*|libkenlm\\.so.*|libonnxruntime\\.so.*|libstt\\.so.*|libtensorflowlite\\.so.*|libtflitedelegates\\.so.*|libvosk\\.so.*|libwhisper-.*\\.so.*|libamdhip64\\.so.*|libhipblas\\.so.*|librocblas\\.so.*)$

%if 0%{?suse_version} < 1600
%define isLeap15 %nil
%else
%undefine isLeap15
%endif
%if 0%{?suse_version} == 1600
%define isLeap16 %nil
%else
%undefine isLeap16
%endif

%if %{defined isLeap16}
%define pythons python313
%bcond_with python_modules
%else
%define pythons python312
%bcond_without python_modules
%endif

%bcond_with mkl
%bcond_with download
%if %{defined isLeap15}
%bcond_with system_pybind11
%else
%bcond_without system_pybind11
%endif

Name:           speechnote
Version:        4.8.2
Release:        0
Summary:        App for note taking, reading and translating
URL:            https://github.com/mkiol/dsnote
License:        MPL-2.0
Group:          Productivity/Office/Other
Source:         %aname-%version.tar.gz
Source1:        sources.tar.zst
Source2:        pythonbasic.tar
Source3:        CTranslate2-v4.3.1.tar.zst
Source4:        rhvoice-846293.tar.zst
Source5:        bergamot-ada8c3.tar.zst
Source6:        pythonmodules1.tar.zst
Source7:        pythonmodules2.tar.zst
Source8:        %name.rpmlintrc
Source9:        README.SUSE
BuildRequires:  cmake
BuildRequires:  gcc-c++
BuildRequires:  cmake(Qt5LinguistTools)
BuildRequires:  cmake(Qt5Multimedia)
BuildRequires:  cmake(Qt5Xml)
BuildRequires:  cmake(Qt5Sql)
BuildRequires:  cmake(Qt5QuickControls2)
BuildRequires:  cmake(Qt5X11Extras)
BuildRequires:  libQt5Gui-private-headers-devel
BuildRequires:  extra-cmake-modules
%if %{defined isLeap15}
BuildRequires:  meson
BuildRequires:  bison
BuildRequires:  pkgconfig(libxml-2.0)
%else
BuildRequires:  pkgconfig(xkbcommon-x11) >= 1.6
%endif
BuildRequires:  cmake(KF5DBusAddons)
BuildRequires:  git-core
BuildRequires:  %{python_module devel}
BuildRequires:  AppStream
BuildRequires:  libXtst-devel
BuildRequires:  libXinerama-devel

BuildRequires:  libopenblas_pthreads-devel
BuildRequires:  libarchive-devel
BuildRequires:  fmt-devel
BuildRequires:  xz-devel
%if %{with system_pybind11}
BuildRequires:  %{python_module pybind11-devel}
%endif
BuildRequires:  librubberband-devel
BuildRequires:  libtag-devel
BuildRequires:  libnumbertext-devel
BuildRequires:  libtool
BuildRequires:  ocl-icd-devel
%if %{defined isLeap15}
BuildRequires:  libboost_headers1_75_0-devel
%else
%if %{defined isLeap16}
BuildRequires:  libboost_headers1_86_0-devel
%else
BuildRequires:  libboost_headers-devel
%endif
%endif
BuildRequires:  chrpath
BuildRequires:  dos2unix
BuildRequires:  fdupes
BuildRequires:  site-config
BuildRequires:  yq
BuildRequires:  wget
%if %{with python_modules}
BuildRequires:  %{python_module pip}
BuildRequires:  %{python_module wheel}
BuildRequires:  %{python_module setuptools}
%if %{with system_pybind11}
BuildRequires:  %{python_module Cython}
BuildRequires:  %{python_module six}
BuildRequires:  %{python_module Markdown}
BuildRequires:  %{python_module Pygments}
%endif
%endif
BuildRequires:  zstd
BuildRequires:  zip
BuildRequires:  unzip
BuildRequires:  patchelf
BuildRequires:  onednn-devel
%if %{with mkl}
BuildRequires:  intel-oneapi-mkl-devel
%endif

%if %{with python_modules}
Recommends:     %{name}-python-modules = %{version}
%endif
ExclusiveArch:  x86_64

%description
Speech Note let you take, read and translate notes in multiple languages.
It uses Speech to Text, Text to Speech and Machine Translation to do so.
Text and voice processing take place entirely offline, locally on your
computer, without using a network connection.

%package python-modules
Summary:        Extra python modules
Requires:       %{name} = %{version}
Requires:       libdnnl3
Requires:       %pythons
Requires:       %pythons-setuptools
%if %{with system_pybind11}
Requires:       %pythons-six
Requires:       %pythons-Pygments
%endif
AutoReqProv:    no

%description python-modules
Python modules for %name


%prep
%setup -q -n %{aname}-%{version}
%if %{without download}
#do not download libsst
sed -i '/DOWNLOAD/d' cmake/libstt.cmake
%endif

mkdir build build/aux external external/pythonsrc

%if %{without download}
#sources
tar fx %SOURCE1 -C build

#rhvoice
tar fx %SOURCE4 -C external
sed -i '/GIT_REPOSITORY/d;/GIT_TAG/d;/GIT_SHALLOW/d' cmake/rhvoice.cmake

#bergamot
tar fx %SOURCE5 -C external
mv external/bergamot external/bergamotfallback
tar fx %SOURCE5 -C external
sed -i '/GIT_REPOSITORY/d;/GIT_TAG/d;/GIT_SHALLOW/d' cmake/bergamot.cmake
%endif
#endif without download

srcdownload () {
    local n i URL DEST SHA256
    ! yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | grep -qF 'sources:' && return
    n=$(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources' | grep -c -e ^-)
    if ((n)); then
        for ((i=0;i<n;i++)) ; do
            case $(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources['$i'].type') in
                git)
                    git -C $1 clone --recursive $(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources['$i'].url')
                    pushd $1/* >/dev/null
                    git checkout $(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources['$i'].commit')
                    popd >/dev/null
                    ;;
                file|archive)
                    URL=$(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources['$i'].url')
                    DEST=$(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources['$i'].dest-filename')
                    [[ $DEST = null ]] && DEST=${URL##*/}
                    SHA256=$(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources['$i'].sha256')
                    [[ ! -f $1/$DEST ]] && wget -q -O $1/$DEST $URL
                    [[ $(sha256sum $1/$DEST | cut -f1 -d" ") = $SHA256 ]]
                    ;;&
                archive)
                    tar fx $1/$DEST -C $1 && mv $1/$DEST $1/..
                    ;;
                shell)
                    pushd $1 >/dev/null
                    eval $(yq '.modules[] | explode(.) | select(.name == "'$2'" )' $3 | yq '.sources['$i'].commands' | grep -v '^- |' | sed 's@$@;@')
                    rm -rf tmp #fix for 1.8.0
                    popd >/dev/null
                    ;;
                patch|file) : ;;
                *) echo "yq: unknown type" ; exit 1 ;;
            esac
        done
    fi
}

#ctranslate2
%if %{with download}
srcdownload build/aux ctranslate2 $PWD/flatpak/net.mkiol.SpeechNote.yaml
%else
tar fx %SOURCE3 -C build/aux
%endif


#pythonmodules
%if %{with download}
for m in $(yq '.modules[].name | explode(.)' flatpak/python3-modules-x86-64.yaml) ; do
    srcdownload external/pythonsrc $m $PWD/flatpak/python3-modules-x86-64.yaml
done
%else
%if %{without system_pybind11}
tar fx %SOURCE2 -C external/pythonsrc
%endif
tar fx %SOURCE6 -C external/pythonsrc
tar fx %SOURCE7 -C external/pythonsrc
%endif

cp %SOURCE9 .

#rpath /usr/share/dsnote/lib
sed -i '/-rpath/s@/lib@/share/%{aname}&@' CMakeLists.txt


%build
%if %{with system_pybind11}
export PKG_CONFIG_PATH=%{python_sitelib}/pybind11/share/pkgconfig
%endif

%cmake \
  -DCMAKE_BUILD_TYPE=Release \
  -DWITH_DESKTOP=ON \
%if %{defined isLeap15}
  -DBUILD_XKBCOMMON=ON \
%endif
  \
  -DBUILD_WHISPERCPP_CLBLAST=ON \
  -DBUILD_WHISPERCPP_VULKAN=ON \
  -DBUILD_OPENBLAS=OFF \
  -DBUILD_LIBARCHIVE=OFF \
  -DBUILD_XZ=OFF \
  -DBUILD_FMT=OFF \
  -DBUILD_RUBBERBAND=OFF \
  -DBUILD_LIBNUMBERTEXT=OFF \
%if %{with system_pybind11}
  -DBUILD_PYBIND11=OFF \
%else
  -DBUILD_PYBIND11=ON \
%endif
  -DBUILD_FFMPEG=ON \
  -DBUILD_TAGLIB=OFF \
  -DBUILD_ESPEAK=ON \
  -DCMAKE_VERBOSE_MAKEFILE=OFF \
  -Wno-dev

%cmake_build


%if %{with python_modules}
cd aux/CTranslate2*
%cmake \
  -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_CLI=OFF \
  -DCUDA_DYNAMIC_LOADING=ON \
  -DOPENMP_RUNTIME=COMP \
%if %{with mkl}
  -DWITH_MKL=ON \
%else
  -DWITH_MKL=OFF \
%endif
  -DWITH_DNNL=ON \
  -DWITH_RUY=ON \
  -DCMAKE_CXX_FLAGS=-msse4.1 \
  -DCMAKE_INSTALL_PREFIX=%{_datadir}/%{aname}/python/extensions/cpu \
  %{nil}

%cmake_build
DESTDIR=$PWD/../tmpinst make install
cd ../python
sed -i '/^ldflags/s@=.*@= ["-Wl,-rpath,%_datadir/%{aname}/python/extensions/cpu/%{_lib}"]@' setup.py 
%if %{with system_pybind11}
CTRANSLATE2_ROOT=$PWD/../tmpinst/%{_datadir}/%{aname}/python/extensions/cpu python%{python_version} setup.py bdist_wheel
%endif
cd ../../..
%endif


%install
%cmake_install

%if %{with python_modules}
FLATPAK_DEST=%buildroot%{_datadir}/%{aname}/python
export PYTHONPATH=$FLATPAK_DEST/extensions/cpu/%{_lib}/python%{python_version}/site-packages:$FLATPAK_DEST/lib/python%{python_version}/site-packages:$FLATPAK_DEST/%{_lib}/python%{python_version}/site-packages

YAMLSRC=$PWD/flatpak/python3-modules-x86-64.yaml
[[ -f $YAMLSRC ]]
#make python3-unidic-fix work
sed -i '/ln -s/{s@/app/lib/python%{python_version}/site-packages@..@;s@/app/@$FLATPAK_DEST/@}' $YAMLSRC
MODSRCDIR=$PWD/external/pythonsrc

%if %{without system_pybind11}
pushd $MODSRCDIR >/dev/null
for a in pygments six markdown google_api_core cython ; do
   pip3 install --verbose --exists-action=i --no-index --find-links=file://$PWD --prefix=${FLATPAK_DEST} --no-build-isolation $a
done
popd >/dev/null
%endif

modules=$(yq '.modules[].name' $YAMLSRC)

for m in $modules ; do
    [[ $m = python3-pygments ]] && continue
    DSBUILDFLAGS=
    BUILDMOD=$PWD/python-modules
    mkdir $BUILDMOD
    pushd $MODSRCDIR >/dev/null
    modsrc=$(yq '.modules[] | explode(.) | select(.name == "'$m'" )' $YAMLSRC | yq '.sources[].url' | sed 's@.*/@@;s@%2B@+@g')
    for a in $modsrc ; do
        [[ -f $a ]] && ln $a $BUILDMOD
    done
    popd >/dev/null
    pushd $BUILDMOD >/dev/null
    #build
%if ! ( %{defined isLeap15} || %{defined isLeap16} )
    [[ $m = python3-mycroft_mimic3_tts ]] && DSBUILDFLAGS="CFLAGS=-Wno-error=incompatible-pointer-types"
%endif
    eval $(yq '.modules[] | explode(.) | select(.name == "'$m'" )' $YAMLSRC | yq '.build-commands[]' | sed "s@python @python%{python_version} @;s@^pip3 @$DSBUILDFLAGS pip3 @;s@\$@;@")
    #cleanup
    eval $(yq '.modules[] | explode(.) | select(.name == "'$m'" )' $YAMLSRC | yq '.cleanup[]' | sed 's@^@rm -rf $FLATPAK_DEST@;s@/lib/@/lib*/@g;s@$@;@')
    popd >/dev/null
    rm -r $BUILDMOD
done

%if %{without system_pybind11}
cd external/pybind11
pip3 wheel --verbose --exists-action=i --no-index --no-build-isolation .
pip3 install --verbose --exists-action=i --no-index --find-links=file://$PWD --prefix=${FLATPAK_DEST} --no-build-isolation *.whl
cd ../..
%endif

YAMLSRC=$(dirname $YAMLSRC)/net.mkiol.SpeechNote.yaml
cd build/aux/CTranslate2*
%cmake_install
%if %{without system_pybind11}
cd python
CTRANSLATE2_ROOT=$PWD/../tmpinst/%{_datadir}/%{aname}/python/extensions/cpu python%{python_version} setup.py bdist_wheel
cd ..
%endif
( export PYTHONPATH=${FLATPAK_DEST}/extensions/cpu/%{_lib}/python%{python_version}/site-packages:$FLATPAK_DEST/lib/python%{python_version}/site-packages:$FLATPAK_DEST/%{_lib}/python%{python_version}/site-packages
  rm -rf ${FLATPAK_DEST}/%{_lib}/python%{python_version}/site-packages/ctranslate2*
  m=ctranslate2
  #install
  eval $(yq '.modules[] | explode(.) | select(.name == "'$m'" )' $YAMLSRC | yq '.post-install[]' | grep -A4 ^pip3 | sed 's@/lib/@/lib*/@g;s@$@;@')
  #cleanup
  eval $(yq '.modules[] | explode(.) | select(.name == "'$m'" )' $YAMLSRC | yq '.cleanup[]' | sed 's@^@rm -rf $FLATPAK_DEST@;s@/lib/@/lib*/@g;s@$@;@')
)
cd ../../..

rm -r $FLATPAK_DEST/lib{,64}/python%{python_version}/site-packages/__pycache__
rm -r $FLATPAK_DEST/lib/python%{python_version}/site-packages/pypinyin/__pycache__
rm -r $FLATPAK_DEST/%{_lib}/python%{python_version}/site-packages/pandas/tests
rm -r $FLATPAK_DEST/extensions/cpu/%{_lib}/python%{python_version}/site-packages/torch/{test,include}
rm $FLATPAK_DEST/%{_lib}/python%{python_version}/site-packages/numpy/*/lib/lib*.a
rm -f $FLATPAK_DEST/lib/python%{python_version}/site-packages/*.{txt,md}

find $FLATPAK_DEST -name ".gitignore" -exec rm {} \;
find $FLATPAK_DEST -name "*.so" -exec strip --strip-unneeded {} \;
find $FLATPAK_DEST -name "*.so.*" -exec strip --strip-unneeded {} \;
find $FLATPAK_DEST/extensions/cpu/%{_lib}/python%{python_version}/site-packages/torch -type f -perm -a+x -a ! -name "*.so.*" -exec strip --strip-unneeded {} \;
chmod a-x $FLATPAK_DEST/lib/python%{python_version}/site-packages/tensorboard/webfiles.zip
rm -r $FLATPAK_DEST/share

%fdupes %buildroot%_datadir/%{aname}/python
%endif
mv %buildroot%{_bindir}/%{aname} %buildroot%{_bindir}/%{aname}.exe
#main program script
SHPRE=%_datadir/%{aname}/python/extensions
SHPOST=%{_lib}/python%{python_version}/site-packages
echo -e "#!/bin/bash\n" >%buildroot%{_bindir}/%{aname}
echo -e "export ESPEAK_DATA_PATH=%{_datadir}/%{aname}/share/espeak-ng-data" >>%buildroot%{_bindir}/%{aname}
%if %{with python_modules}
echo -e "export LD_LIBRARY_PATH=\${LD_LIBRARY_PATH:+\$LD_LIBRARY_PATH:}$SHPRE/cpu/%{_lib}\nexport PYTHONPATH=\${PYTHONPATH:+\$PYTHONPATH:}$SHPRE/cpu/$SHPOST" >>%buildroot%{_bindir}/%{aname}
echo -e 'export PYTHONPATH=$PYTHONPATH:%_datadir/%{aname}/python/lib/python%{python_version}/site-packages:%_datadir/%{aname}/python/'$SHPOST'' >>%buildroot%{_bindir}/%{aname}
%endif
echo -e 'exec %{aname}.exe "$@"' >>%buildroot%{_bindir}/%{aname}
chmod a+x %buildroot%{_bindir}/%{aname}

ln -s %aname %buildroot%{_bindir}/%{name}

export NO_BRP_CHECK_RPATH=true

%files
%doc README.md README.SUSE
%license LICENSE
%{_bindir}/%{name}
%{_bindir}/%{aname}
%{_bindir}/%{aname}.exe
%{_datadir}/applications/%{aname}.desktop
%{_datadir}/dbus-1/services/net.mkiol.%{aname}.service
%{_datadir}/icons/hicolor/*
%{_datadir}/metainfo/%{aname}.metainfo.xml
%{_datadir}/%{aname}
%exclude %{_datadir}/%{aname}/python

%if %{with python_modules}
%files python-modules
%{_datadir}/%{aname}/python
%endif

%changelog