├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── pyproject.toml ├── requirements-dev.in ├── requirements-dev.txt └── whisper_cpp ├── __init__.py └── __main__.py /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | release: 10 | types: 11 | - published 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build_wheels: 19 | name: Wheels (${{ matrix.os }}) 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | os: [ macos-13, macos-14, windows-2022 ] 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Setup uv 30 | uses: yezz123/setup-uv@v4 31 | 32 | - name: Build wheels 33 | uses: pypa/cibuildwheel@v2.19 34 | env: 35 | CIBW_BUILD: "cp312-*" 36 | 37 | - uses: actions/upload-artifact@v4 38 | with: 39 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 40 | path: ./wheelhouse/*.whl 41 | 42 | build_linux_wheels: 43 | runs-on: ubuntu-latest 44 | name: Linux (${{ matrix.arch }}, ${{ matrix.tag }}) 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | arch: [ x86_64, i686, aarch64, ppc64le ] 49 | tag: [ manylinux, musllinux ] 50 | # TODO(charlie): Fails to compile, root cause is unexplored. 51 | exclude: 52 | - arch: aarch64 53 | tag: musllinux 54 | 55 | steps: 56 | - uses: actions/checkout@v4 57 | 58 | - name: Set up QEMU 59 | if: ${{ matrix.arch }} != 'x86_64' 60 | uses: docker/setup-qemu-action@v3 61 | with: 62 | platforms: all 63 | 64 | - name: Setup uv 65 | uses: yezz123/setup-uv@v4 66 | 67 | - name: Build wheels 68 | uses: pypa/cibuildwheel@v2.19 69 | env: 70 | CIBW_ARCHS_LINUX: ${{ matrix.arch }} 71 | CIBW_BUILD: "cp312-${{ matrix.tag }}_*" 72 | 73 | - uses: actions/upload-artifact@v4 74 | with: 75 | name: cibw-wheels-linux-${{ matrix.arch }}-${{ matrix.tag }}-${{ strategy.job-index }} 76 | path: ./wheelhouse/*.whl 77 | 78 | build_sdist: 79 | name: Build source distribution 80 | runs-on: ubuntu-latest 81 | steps: 82 | - uses: actions/checkout@v4 83 | 84 | - name: Build sdist 85 | run: pipx run build --sdist 86 | 87 | - uses: actions/upload-artifact@v4 88 | with: 89 | name: cibw-sdist 90 | path: dist/*.tar.gz 91 | 92 | # Verify that the x86_64 wheels work on macOS 11. 93 | test_macos_11: 94 | name: Test macOS 11 95 | runs-on: macos-11 96 | needs: build_wheels 97 | steps: 98 | - uses: actions/checkout@v4 99 | 100 | - uses: actions/download-artifact@v4 101 | with: 102 | name: cibw-wheels-macos-13-0 103 | path: wheelhouse 104 | 105 | - name: Install wheel 106 | run: pip install wheelhouse/*.whl 107 | 108 | - name: Test wheel 109 | run: python -m whisper_cpp -h 110 | 111 | upload_pypi: 112 | needs: [ build_wheels, build_linux_wheels, build_sdist ] 113 | runs-on: ubuntu-latest 114 | environment: pypi 115 | permissions: 116 | id-token: write 117 | if: github.event_name == 'release' && github.event.action == 'published' 118 | steps: 119 | - uses: actions/download-artifact@v4 120 | with: 121 | # unpacks all CIBW artifacts into dist/ 122 | pattern: cibw-* 123 | path: dist 124 | merge-multiple: true 125 | 126 | - name: Publish package distributions to PyPI 127 | uses: pypa/gh-action-pypi-publish@release/v1 128 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the `whisper.cpp` source 2 | whisper.cpp-* 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ 164 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28...3.29) 2 | project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX) 3 | 4 | set(WHISPER_BUILD_EXAMPLES 5 | ON 6 | CACHE BOOL "" FORCE) 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | whisper-cpp 11 | URL https://github.com/ggerganov/whisper.cpp/archive/refs/tags/v1.5.5.zip 12 | URL_HASH MD5=7262e27c619a0e51e5e2892e429d7481 13 | EXCLUDE_FROM_ALL) 14 | FetchContent_MakeAvailable(whisper-cpp) 15 | 16 | set_target_properties(main PROPERTIES OUTPUT_NAME whisper-cpp EXCLUDE_FROM_ALL 17 | FALSE) 18 | 19 | install(TARGETS main RUNTIME DESTINATION "${SKBUILD_SCRIPTS_DIR}") 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Charles Marsh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # whisper.cpp-cli 2 | 3 | A Python wrapper around the [`whisper.cpp`](https://github.com/ggerganov/whisper.cpp) CLI. 4 | 5 | Packages [`whisper.cpp`](https://github.com/ggerganov/whisper.cpp) into pre-built, `pip`-installable 6 | wheels, for macOS and Linux. 7 | 8 | ## Installation 9 | 10 | Available on PyPI, with pre-built wheels for macOS and Linux: 11 | 12 | ```bash 13 | pip install whisper.cpp-cli 14 | ``` 15 | 16 | Once installed, `whisper-cpp` will be exposed as a command-line tool: 17 | 18 | ```bash 19 | whisper-cpp --help 20 | ``` 21 | 22 | ## Usage 23 | 24 | Following Simon Willison's [_Transcribing MP3s with whisper-cpp on macOS_](https://til.simonwillison.net/macos/whisper-cpp), 25 | once installed, you can download a Whisper model file: 26 | 27 | ```bash 28 | curl -o ggml-large-v3-q5_0.bin -L 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-q5_0.bin?download=true' 29 | ``` 30 | 31 | Convert from MP3 to 16kHz WAV, if necessary: 32 | 33 | ```bash 34 | ffmpeg -i input.mp3 -ar 16000 input.wav 35 | ``` 36 | 37 | And transcribe audio, as follows: 38 | 39 | ```bash 40 | whisper-cpp -m ggml-large-v3-q5_0.bin input.wav --output-txt 41 | ``` 42 | 43 | ## Development 44 | 45 | [`whisper.cpp`](https://github.com/ggerganov/whisper.cpp) is compiled without any CPU or GPU 46 | acceleration. 47 | 48 | In the future, I'd like to distribute builds with 49 | [Core ML support](https://github.com/ggerganov/whisper.cpp?tab=readme-ov-file#core-ml-support), 50 | [CUDA support](https://github.com/ggerganov/whisper.cpp?tab=readme-ov-file#nvidia-gpu-support), and 51 | more, given [`whisper.cpp`](https://github.com/ggerganov/whisper.cpp)'s own support for these 52 | features. 53 | 54 | The latest release compiles against [`v1.5.5`](https://github.com/ggerganov/whisper.cpp/releases/tag/v1.5.5). 55 | 56 | ## License 57 | 58 | MIT 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "whisper.cpp-cli" 7 | version = "0.0.3" 8 | description = "A Python package for the whisper.cpp CLI." 9 | authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }] 10 | readme = "README.md" 11 | requires-python = ">=3.7" 12 | license = { file = "LICENSE" } 13 | dependencies = [] 14 | 15 | [tool.scikit-build] 16 | minimum-version = "0.9" 17 | cmake.define.BUILD_SHARED_LIBS = false 18 | cmake.version = ">=3.28" 19 | wheel.packages = ["whisper_cpp"] 20 | wheel.py-api = "py3" 21 | 22 | [tool.ruff] 23 | line-length = 100 24 | 25 | [tool.ruff.lint] 26 | extend-select = ["I", "B", "UP"] 27 | 28 | [tool.cibuildwheel] 29 | build-frontend = "build[uv]" 30 | test-command = [ 31 | "whisper-cpp -h", 32 | "python -m whisper_cpp -h", 33 | ] 34 | environment.MACOSX_DEPLOYMENT_TARGET = "10.12" 35 | 36 | -------------------------------------------------------------------------------- /requirements-dev.in: -------------------------------------------------------------------------------- 1 | ruff 2 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile requirements-dev.in -o requirements-dev.txt 3 | ruff==0.4.2 4 | -------------------------------------------------------------------------------- /whisper_cpp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charliermarsh/whisper.cpp-cli/1aa1c6c7ce01a059c3858b4113cca45bcd6b3eba/whisper_cpp/__init__.py -------------------------------------------------------------------------------- /whisper_cpp/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import sysconfig 4 | 5 | 6 | def find_whisper_bin() -> str: 7 | """Return the whisper binary path.""" 8 | 9 | whisper_exe = "whisper-cpp" + sysconfig.get_config_var("EXE") 10 | 11 | path = os.path.join(sysconfig.get_path("scripts"), whisper_exe) 12 | if os.path.isfile(path): 13 | return path 14 | 15 | if sys.version_info >= (3, 10): 16 | user_scheme = sysconfig.get_preferred_scheme("user") 17 | elif os.name == "nt": 18 | user_scheme = "nt_user" 19 | elif sys.platform == "darwin" and sys._framework: 20 | user_scheme = "osx_framework_user" 21 | else: 22 | user_scheme = "posix_user" 23 | 24 | path = os.path.join(sysconfig.get_path("scripts", scheme=user_scheme), whisper_exe) 25 | if os.path.isfile(path): 26 | return path 27 | 28 | raise FileNotFoundError(path) 29 | 30 | 31 | if __name__ == "__main__": 32 | whisper = os.fsdecode(find_whisper_bin()) 33 | if sys.platform == "win32": 34 | import subprocess 35 | 36 | completed_process = subprocess.run([whisper, *sys.argv[1:]]) 37 | sys.exit(completed_process.returncode) 38 | else: 39 | os.execvp(whisper, [whisper, *sys.argv[1:]]) 40 | --------------------------------------------------------------------------------