mirror of
https://github.com/ggerganov/whisper.cpp.git
synced 2025-07-04 08:20:57 +02:00
Compare commits
459 Commits
fix_vs_sdl
...
sync-ggml-
Author | SHA1 | Date | |
---|---|---|---|
bff8dc248a | |||
69753804ed | |||
89970b9aaa | |||
79fb43e252 | |||
926e06dbfd | |||
43a59eccf6 | |||
fe0d52b9a2 | |||
cb90cb0992 | |||
8264872b5d | |||
882d975729 | |||
c426829771 | |||
0b1962a181 | |||
86dece9c7c | |||
04445664b4 | |||
22f4997dd8 | |||
b493e03b90 | |||
aef59f4851 | |||
f8c75dc43e | |||
00c8056715 | |||
19d8d9a928 | |||
0c4a229154 | |||
b2513a6208 | |||
587ea01f55 | |||
e41bc5c61a | |||
e39ba750cd | |||
db0fc9edc6 | |||
186855e38b | |||
a513146102 | |||
4730950492 | |||
9dd9685c79 | |||
2e310b841e | |||
5d4390d281 | |||
9791647653 | |||
288304ee64 | |||
b6f3fa4059 | |||
cb2bd11ee8 | |||
09e6b66025 | |||
d41cf26a0f | |||
3c67195be9 | |||
f9f78a773f | |||
be55e25cac | |||
2ffdda99e8 | |||
9bbedc51cc | |||
1e1fa27add | |||
e1bdd148c5 | |||
7fa8bb303f | |||
7564f5e6f1 | |||
22ba2e27ce | |||
0676b2dab2 | |||
4a512cb153 | |||
76171ce199 | |||
5eac2a3fbb | |||
42938398f9 | |||
a8fe90ae15 | |||
c5a5a2da5b | |||
8316bfd82b | |||
fd1cb9fc12 | |||
17f6b8225e | |||
6374ea32ca | |||
3a66f9f248 | |||
9b584b0cc0 | |||
09846f4e12 | |||
bcf1ed0163 | |||
934d4b3083 | |||
988dcd4b5b | |||
9f540ad8cb | |||
1fa17bc752 | |||
366082d072 | |||
0778b6ff5f | |||
5cd59c9396 | |||
d052e64d42 | |||
780750a108 | |||
919c78e618 | |||
dc288f84cd | |||
1543a3600c | |||
4872355f6e | |||
1a76e97c28 | |||
7017c1d37d | |||
670bf02662 | |||
9fff2f751c | |||
46392f733f | |||
eeb259909e | |||
fe21ddf0dc | |||
33bdbfbb33 | |||
0f49edf0f3 | |||
25efcfe3ed | |||
edbd4cb7f5 | |||
3ae9b8416a | |||
55d73a13f5 | |||
2e30e6df59 | |||
f0171f0616 | |||
b7db9e7aac | |||
f3c42399a3 | |||
28dcdff4c5 | |||
50218b935d | |||
f9b2dfdd8c | |||
50fda73f4c | |||
1c20f46887 | |||
adaea088bc | |||
6c0d843f9d | |||
efb800557f | |||
337becefb9 | |||
11ae30c19e | |||
88c3cecd43 | |||
fe4acb33e3 | |||
fd5a3e1bc6 | |||
01e1600edd | |||
cf3eb291ab | |||
3d54b68ea7 | |||
11218294db | |||
33c89ade7d | |||
27a56e7243 | |||
f4ca3e2f9c | |||
0287a5c51b | |||
24d29c55df | |||
36019c35a3 | |||
4e936e2afa | |||
314ce5981e | |||
cb7642b0f5 | |||
7db8f278f0 | |||
be42a19eab | |||
b8755670ca | |||
483eecae62 | |||
43e3d25d93 | |||
e1dbf9a42e | |||
ee0013865d | |||
32a407166b | |||
622f981853 | |||
d049d67065 | |||
877308838e | |||
d87dfcf7c0 | |||
915c14ef10 | |||
5d33d3c929 | |||
751e42b21e | |||
e8ee32d12d | |||
e9ce285135 | |||
b942f451b6 | |||
e6410faf99 | |||
182df69384 | |||
3bf9691dfd | |||
ba444e9c23 | |||
c6caf8eef2 | |||
6cae79a1d7 | |||
b9bfe0c693 | |||
1d50c6ac22 | |||
79f23d9132 | |||
ee2cbeeb74 | |||
868a5ce310 | |||
b9c71fae5a | |||
6d67c6d93d | |||
12cade118e | |||
fd1c725e65 | |||
d33fd00cfe | |||
3e0d89782a | |||
7074b622eb | |||
b8d3e45342 | |||
1901505138 | |||
3c26dd3353 | |||
d792d2a2dc | |||
8add58aa5e | |||
8f8ede1b12 | |||
3a6fe8d767 | |||
76231bda56 | |||
785437c253 | |||
2f0612cb1c | |||
e944065d5b | |||
ccc7b5df0b | |||
fbed36851e | |||
d1d847f184 | |||
337f91d4a6 | |||
317a0031f9 | |||
b243416918 | |||
6e532c7187 | |||
2105b110d3 | |||
f82622180f | |||
a71c64512a | |||
1e9c2f87f1 | |||
06ce8f83e6 | |||
8b92060a10 | |||
7858eddd10 | |||
3a88f1e504 | |||
f0d2bfbfb7 | |||
170b2faf75 | |||
f8a3509b6d | |||
2a2d21c75d | |||
9cfcd6cc45 | |||
e853620270 | |||
549db9376f | |||
33a25e4dda | |||
43f5030aeb | |||
cf794133de | |||
ef6cf357e7 | |||
b1f5c11b32 | |||
ada745f4a5 | |||
01985c22c0 | |||
448f3d3b93 | |||
e6234cd435 | |||
2b6d0d2200 | |||
0b17d4507e | |||
77e0c86ab6 | |||
eac1bc9c47 | |||
cbde66d913 | |||
513ecf8dc0 | |||
cce5daf17b | |||
2c502b3c00 | |||
51c6961c7b | |||
503a786c9a | |||
ad4e350933 | |||
d7a9346ab1 | |||
b63d23f728 | |||
f6ce10e4a1 | |||
6cb2b86581 | |||
801d6bd809 | |||
ddf7e6a15d | |||
0d42097fd3 | |||
842b9c984c | |||
0810f02547 | |||
8c13c78f9d | |||
f31b404fcb | |||
854c0518bc | |||
c8e3968edd | |||
b358de2458 | |||
11688b262f | |||
04b9508fb3 | |||
4200430e75 | |||
e153b8eaa2 | |||
83af237f0b | |||
7a2e39750a | |||
0a40ae9728 | |||
32cfdcbf42 | |||
cfa42aca09 | |||
2e2f0f954b | |||
93631b2be6 | |||
f9015b585b | |||
1880ffd7ff | |||
9173932c78 | |||
94c3f3877f | |||
00086469fb | |||
2d8e40e2a0 | |||
e17af6524f | |||
88d13a17a7 | |||
f92bd59951 | |||
6e7629b146 | |||
27533e7f63 | |||
1b81415963 | |||
0001ec075f | |||
5bad2e5099 | |||
6fc0ae2f5a | |||
de6b38c6d9 | |||
46d6e0abc1 | |||
1279f0d0bc | |||
f28bf5d186 | |||
1fbdfb1d36 | |||
ee5581633b | |||
8ca67df291 | |||
fc6d343e76 | |||
3199356d3a | |||
e0c43b0bbf | |||
f4f619ea8e | |||
3c4d363872 | |||
15aa189329 | |||
c53d5c9e85 | |||
ba6f584f30 | |||
a219941812 | |||
a2cc8c2666 | |||
388ed98220 | |||
d487a28ae1 | |||
cbb88c4050 | |||
13455c0b5f | |||
2f77a9e9bd | |||
fa2b5249ff | |||
5b854ebba5 | |||
8058f19d0b | |||
ae6a9bb9a5 | |||
24faba9e9b | |||
c722ff84d3 | |||
102af79f63 | |||
03c364557d | |||
31b62276cf | |||
97b5a3055d | |||
9993c3f703 | |||
fa72479cfb | |||
6c15539c54 | |||
52c4c03b0a | |||
cfc2560e41 | |||
db6e8056b5 | |||
b3f3779c1b | |||
13eeebb1b2 | |||
905b834af1 | |||
2cd3061a23 | |||
88d59e21b2 | |||
4917f122d4 | |||
16a1b77249 | |||
51d1398a0a | |||
3499dd83c0 | |||
7b7d9ae35e | |||
2dcb7181ff | |||
96ab3b2465 | |||
08f32992d0 | |||
394fae57c3 | |||
0708835301 | |||
774c519433 | |||
776cdceb9e | |||
03d050481e | |||
3d60219622 | |||
521d72d76e | |||
9fb9025a40 | |||
3c2abb01e8 | |||
efd9407e22 | |||
3684af2594 | |||
206459a804 | |||
21d890d534 | |||
0b43a02be8 | |||
2699e1485a | |||
594a121f3e | |||
996581c5e2 | |||
226d344f56 | |||
bb9f68129f | |||
30cf30ca82 | |||
ee6286c35d | |||
c7941d5ccc | |||
b82ac32a6c | |||
edf1ee1ef8 | |||
cf5ddb8c21 | |||
7fe4979f25 | |||
9bc0dc7235 | |||
3fc6ad97a3 | |||
663cafc1e8 | |||
be9de81171 | |||
21fb513ef1 | |||
4e56747944 | |||
ca75449a92 | |||
80dad86b2c | |||
485ece6725 | |||
e7d9d8687a | |||
6e8242f7fe | |||
e27fd6f0c0 | |||
96db0c5a9c | |||
d2aaffd5d9 | |||
215990abde | |||
7e23d8c64a | |||
740bf7f6a1 | |||
c8e12f59dd | |||
83b14c357c | |||
60b481d881 | |||
4854789751 | |||
e0f3c9d4dd | |||
1f4886b40d | |||
f11de0e73c | |||
d5cc27ee4d | |||
5bb1d58c6a | |||
7d14005717 | |||
4ffb8e3e4d | |||
1d8d8ae55e | |||
eebf6bc0bd | |||
dc8f423b40 | |||
548e7052f1 | |||
a34cb73dc2 | |||
82f9496657 | |||
e3c85e75bd | |||
b9eab73fa2 | |||
76385c8311 | |||
442cd1d2e7 | |||
bc8cb97e02 | |||
8dcadf736b | |||
93986b61e0 | |||
bd1a9e34c9 | |||
cc03608e78 | |||
54a54faee4 | |||
96a92ecc4c | |||
edd1d8686a | |||
dc6f4e7c05 | |||
74c85d154e | |||
eb2d8b6ffd | |||
b442dcd598 | |||
c98681e6d5 | |||
3bab804981 | |||
c927830a70 | |||
992b51b3d5 | |||
2c882cbe4c | |||
1fbb119b1e | |||
40dea850fd | |||
8255a830a8 | |||
a0f76b2da7 | |||
394768c48b | |||
846e01b2c0 | |||
6ac8e6b2ce | |||
60d2ddebdf | |||
2e180184a8 | |||
ef40950c4a | |||
c774eec709 | |||
5b481a27a6 | |||
fc7b1ee521 | |||
c42f67e2d2 | |||
339a1cba5d | |||
c64f3e8ada | |||
9f83f67221 | |||
7d3da68f79 | |||
b5d21359c1 | |||
17addf7104 | |||
cdaee8b4bd | |||
4b60ff4f92 | |||
b43b9d928c | |||
e3cb412a59 | |||
ac301a7d9b | |||
82e04e7670 | |||
38ac47cd4d | |||
2d70cd36d7 | |||
98dab49b9a | |||
b1385e9aa9 | |||
48f5e893f5 | |||
dc21871fcb | |||
64a430bc81 | |||
51a3580c79 | |||
37a21dd43d | |||
8a22a8b17f | |||
fcbcad0c90 | |||
4444db7360 | |||
a7fc1038ca | |||
1689aaf854 | |||
4b48fe449a | |||
47cc043e69 | |||
e3d9ffb98b | |||
e22d69839d | |||
defe731263 | |||
4e07957bf9 | |||
d2c5154bb5 | |||
4fac43fe00 | |||
3be9670f17 | |||
86729fcd6d | |||
7fbca6304e | |||
d597f83e1a | |||
e5edcc6259 | |||
556f773d53 | |||
91d02de332 | |||
1b67d72f87 | |||
14d7c0368d | |||
db6e19188a | |||
b4b063a5c9 | |||
930b739e7a | |||
5981352bb5 | |||
7561da244e | |||
be83f342fb | |||
fd369871f7 | |||
bbd8364f5e | |||
e4102440ef | |||
f8242ec483 | |||
ef51b4cba4 | |||
6f08b24146 | |||
7c165d7fa8 | |||
2f0cf44915 | |||
b9c972fd0d | |||
01c9aafbfd | |||
bae6bbf487 | |||
c310272fa0 | |||
bd0b55dbe0 | |||
ba4645db2c | |||
dfc6ca62f3 | |||
47e14c0529 |
@ -13,8 +13,6 @@ WORKDIR /app
|
|||||||
ARG CUDA_DOCKER_ARCH=all
|
ARG CUDA_DOCKER_ARCH=all
|
||||||
# Set nvcc architecture
|
# Set nvcc architecture
|
||||||
ENV CUDA_DOCKER_ARCH=${CUDA_DOCKER_ARCH}
|
ENV CUDA_DOCKER_ARCH=${CUDA_DOCKER_ARCH}
|
||||||
# Enable cuBLAS
|
|
||||||
ENV GGML_CUDA=1
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y build-essential libsdl2-dev wget cmake git \
|
apt-get install -y build-essential libsdl2-dev wget cmake git \
|
||||||
@ -25,7 +23,8 @@ ENV CUDA_MAIN_VERSION=12.3
|
|||||||
ENV LD_LIBRARY_PATH /usr/local/cuda-${CUDA_MAIN_VERSION}/compat:$LD_LIBRARY_PATH
|
ENV LD_LIBRARY_PATH /usr/local/cuda-${CUDA_MAIN_VERSION}/compat:$LD_LIBRARY_PATH
|
||||||
|
|
||||||
COPY .. .
|
COPY .. .
|
||||||
RUN make base.en
|
# Enable cuBLAS
|
||||||
|
RUN make base.en CMAKE_ARGS="-DGGML_CUDA=1"
|
||||||
|
|
||||||
FROM ${BASE_CUDA_RUN_CONTAINER} AS runtime
|
FROM ${BASE_CUDA_RUN_CONTAINER} AS runtime
|
||||||
ENV CUDA_MAIN_VERSION=12.3
|
ENV CUDA_MAIN_VERSION=12.3
|
||||||
@ -37,4 +36,5 @@ RUN apt-get update && \
|
|||||||
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
COPY --from=build /app /app
|
COPY --from=build /app /app
|
||||||
|
ENV PATH=/app/build/bin:$PATH
|
||||||
ENTRYPOINT [ "bash", "-c" ]
|
ENTRYPOINT [ "bash", "-c" ]
|
||||||
|
29
.devops/main-musa.Dockerfile
Normal file
29
.devops/main-musa.Dockerfile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
ARG UBUNTU_VERSION=22.04
|
||||||
|
# This needs to generally match the container host's environment.
|
||||||
|
ARG MUSA_VERSION=rc3.1.1
|
||||||
|
# Target the MUSA build image
|
||||||
|
ARG BASE_MUSA_DEV_CONTAINER=mthreads/musa:${MUSA_VERSION}-devel-ubuntu${UBUNTU_VERSION}
|
||||||
|
# Target the MUSA runtime image
|
||||||
|
ARG BASE_MUSA_RUN_CONTAINER=mthreads/musa:${MUSA_VERSION}-runtime-ubuntu${UBUNTU_VERSION}
|
||||||
|
|
||||||
|
FROM ${BASE_MUSA_DEV_CONTAINER} AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y build-essential libsdl2-dev wget cmake git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
|
COPY .. .
|
||||||
|
# Enable muBLAS
|
||||||
|
RUN make base.en CMAKE_ARGS="-DGGML_MUSA=1"
|
||||||
|
|
||||||
|
FROM ${BASE_MUSA_RUN_CONTAINER} AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y curl ffmpeg wget cmake git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
|
COPY --from=build /app /app
|
||||||
|
ENV PATH=/app/build/bin:$PATH
|
||||||
|
ENTRYPOINT [ "bash", "-c" ]
|
@ -16,4 +16,5 @@ RUN apt-get update && \
|
|||||||
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
COPY --from=build /app /app
|
COPY --from=build /app /app
|
||||||
|
ENV PATH=/app/build/bin:$PATH
|
||||||
ENTRYPOINT [ "bash", "-c" ]
|
ENTRYPOINT [ "bash", "-c" ]
|
||||||
|
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
build*/
|
||||||
|
.github/
|
||||||
|
.devops/
|
44
.github/workflows/bindings-ruby.yml
vendored
44
.github/workflows/bindings-ruby.yml
vendored
@ -1,45 +1,11 @@
|
|||||||
name: Bindings Tests (Ruby)
|
name: Bindings Tests (Ruby)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
branches:
|
||||||
- bindings/ruby/**
|
- master
|
||||||
- src/**/*.c
|
|
||||||
- src/**/*.cpp
|
|
||||||
- src/**/*.h
|
|
||||||
- src/**/*.m
|
|
||||||
- src/**/*.metal
|
|
||||||
- include/**/*.c
|
|
||||||
- include/**/*.cpp
|
|
||||||
- include/**/*.h
|
|
||||||
- include/**/*.m
|
|
||||||
- include/**/*.metal
|
|
||||||
- ggml/**/*.c
|
|
||||||
- ggml/**/*.cpp
|
|
||||||
- ggml/**/*.h
|
|
||||||
- ggml/**/*.m
|
|
||||||
- ggml/**/*.metal
|
|
||||||
- scripts/get-flags.mk
|
|
||||||
- examples/dr_wav.h
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
types: [opened, synchronize, reopened]
|
||||||
- bindings/ruby/**
|
|
||||||
- src/**/*.c
|
|
||||||
- src/**/*.cpp
|
|
||||||
- src/**/*.h
|
|
||||||
- src/**/*.m
|
|
||||||
- src/**/*.metal
|
|
||||||
- include/**/*.c
|
|
||||||
- include/**/*.cpp
|
|
||||||
- include/**/*.h
|
|
||||||
- include/**/*.m
|
|
||||||
- include/**/*.metal
|
|
||||||
- ggml/**/*.c
|
|
||||||
- ggml/**/*.cpp
|
|
||||||
- ggml/**/*.h
|
|
||||||
- ggml/**/*.m
|
|
||||||
- ggml/**/*.metal
|
|
||||||
- scripts/get-flags.mk
|
|
||||||
- examples/dr_wav.h
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ubuntu-22:
|
ubuntu-22:
|
||||||
@ -50,6 +16,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: ruby/setup-ruby@v1
|
- uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.1'
|
ruby-version: '3.2'
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: rake test
|
- run: rake test
|
||||||
|
677
.github/workflows/build.yml
vendored
677
.github/workflows/build.yml
vendored
@ -6,17 +6,81 @@ on:
|
|||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
create_release:
|
||||||
|
description: 'Create new release'
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
pre_release_tag:
|
||||||
|
description: 'Pre-release tag name'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
run_type:
|
||||||
|
description: 'Workflow type to run'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- full-ci
|
||||||
|
- release-only
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # for creating release
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||||
ubuntu_image: "ubuntu:22.04"
|
ubuntu_image: "ubuntu:22.04"
|
||||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
determine-tag:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tag_name: ${{ steps.tag.outputs.name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout with full history
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Determine tag name
|
||||||
|
id: tag
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
BUILD_NUMBER=$(git rev-list --count HEAD)
|
||||||
|
SHORT_HASH=$(git rev-parse --short=7 HEAD)
|
||||||
|
CUSTOM_TAG="${{ github.event.inputs.pre_release_tag }}"
|
||||||
|
|
||||||
|
echo "Raw values:"
|
||||||
|
echo "BUILD_NUMBER: $BUILD_NUMBER"
|
||||||
|
echo "SHORT_HASH: $SHORT_HASH"
|
||||||
|
echo "BRANCH_NAME: ${{ env.BRANCH_NAME }}"
|
||||||
|
echo "CUSTOM_TAG: $CUSTOM_TAG"
|
||||||
|
|
||||||
|
# Use custom tag if provided
|
||||||
|
if [[ -n "$CUSTOM_TAG" ]]; then
|
||||||
|
echo "Using custom tag"
|
||||||
|
TAG_NAME="${CUSTOM_TAG}"
|
||||||
|
elif [[ "${{ env.BRANCH_NAME }}" == "master" ]]; then
|
||||||
|
echo "Using master branch format"
|
||||||
|
TAG_NAME="b${BUILD_NUMBER}"
|
||||||
|
else
|
||||||
|
echo "Using non-master branch format"
|
||||||
|
SAFE_NAME=$(echo "${{ env.BRANCH_NAME }}" | tr '/' '-')
|
||||||
|
TAG_NAME="${SAFE_NAME}-b${BUILD_NUMBER}-${SHORT_HASH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Final tag name: $TAG_NAME"
|
||||||
|
echo "name=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
ubuntu-22:
|
ubuntu-22:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -43,6 +107,8 @@ jobs:
|
|||||||
cmake --build build --config Release -j $(nproc)'
|
cmake --build build --config Release -j $(nproc)'
|
||||||
|
|
||||||
ubuntu-22-arm64:
|
ubuntu-22-arm64:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -69,6 +135,8 @@ jobs:
|
|||||||
cmake --build build --config Release -j $(nproc)'
|
cmake --build build --config Release -j $(nproc)'
|
||||||
|
|
||||||
ubuntu-22-arm-v7:
|
ubuntu-22-arm-v7:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -95,12 +163,25 @@ jobs:
|
|||||||
cmake --build build --config Release -j $(nproc)'
|
cmake --build build --config Release -j $(nproc)'
|
||||||
|
|
||||||
macOS-latest:
|
macOS-latest:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: macOS-latest
|
runs-on: macOS-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
destination: ['generic/platform=macOS', 'generic/platform=iOS', 'generic/platform=tvOS']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
|
id: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: ccache
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2.16
|
||||||
|
with:
|
||||||
|
key: macOS-latest-swift
|
||||||
|
evict-old-files: 1d
|
||||||
|
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
run: |
|
run: |
|
||||||
brew update
|
brew update
|
||||||
@ -108,28 +189,38 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cmake -B build
|
sysctl -a
|
||||||
cmake --build build --config Release
|
cmake -B build -G Xcode \
|
||||||
|
-DGGML_METAL_USE_BF16=ON \
|
||||||
|
-DGGML_METAL_EMBED_LIBRARY=ON \
|
||||||
|
-DWHISPER_BUILD_EXAMPLES=OFF \
|
||||||
|
-DWHISPER_BUILD_TESTS=OFF \
|
||||||
|
-DWHISPER_BUILD_SERVER=OFF \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
|
||||||
|
cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
|
||||||
|
|
||||||
|
|
||||||
# freeBSD-latest:
|
# freeBSD-latest:
|
||||||
# runs-on: macos-12
|
# runs-on: macos-13
|
||||||
#
|
#
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v4
|
# uses: actions/checkout@v4
|
||||||
#
|
#
|
||||||
# - name: Build
|
# - name: Build
|
||||||
# uses: cross-platform-actions/action@v0.24.0
|
# uses: cross-platform-actions/action@v0.27.0
|
||||||
# with:
|
# with:
|
||||||
# operating_system: freebsd
|
# operating_system: freebsd
|
||||||
# version: '13.3'
|
# version: '14.2'
|
||||||
# run: |
|
# run: |
|
||||||
# sudo pkg update
|
# sudo pkg update
|
||||||
# sudo pkg install -y gmake sdl2 cmake
|
# sudo pkg install -y gmake sdl2 cmake git
|
||||||
# cmake -B build
|
# cmake -B build
|
||||||
# cmake --build build --config Release
|
# cmake --build build --config Release
|
||||||
|
|
||||||
ubuntu-22-gcc:
|
ubuntu-22-gcc:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -158,6 +249,8 @@ jobs:
|
|||||||
ctest -L gh --output-on-failure'
|
ctest -L gh --output-on-failure'
|
||||||
|
|
||||||
ubuntu-22-gcc-arm64:
|
ubuntu-22-gcc-arm64:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -186,6 +279,8 @@ jobs:
|
|||||||
ctest -L gh --output-on-failure'
|
ctest -L gh --output-on-failure'
|
||||||
|
|
||||||
ubuntu-22-gcc-arm-v7:
|
ubuntu-22-gcc-arm-v7:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -214,6 +309,8 @@ jobs:
|
|||||||
ctest -L gh --output-on-failure'
|
ctest -L gh --output-on-failure'
|
||||||
|
|
||||||
ubuntu-22-clang:
|
ubuntu-22-clang:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -245,6 +342,8 @@ jobs:
|
|||||||
ctest -L gh --output-on-failure'
|
ctest -L gh --output-on-failure'
|
||||||
|
|
||||||
ubuntu-22-gcc-sanitized:
|
ubuntu-22-gcc-sanitized:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -268,11 +367,15 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
apt update
|
apt update
|
||||||
apt install -y build-essential cmake git
|
apt install -y build-essential cmake git
|
||||||
cmake . -DCMAKE_BUILD_TYPE=Debug -DWHISPER_SANITIZE_${{ matrix.sanitizer }}=ON
|
cmake . -DCMAKE_BUILD_TYPE=Debug \
|
||||||
|
-DWHISPER_SANITIZE_${{ matrix.sanitizer }}=ON \
|
||||||
|
-DGGML_OPENMP=OFF
|
||||||
make
|
make
|
||||||
ctest -L gh --output-on-failure'
|
ctest -L gh --output-on-failure'
|
||||||
|
|
||||||
ubuntu-22-cmake-sycl:
|
ubuntu-22-cmake-sycl:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -323,6 +426,8 @@ jobs:
|
|||||||
cmake --build . --config Release -j $(nproc)
|
cmake --build . --config Release -j $(nproc)
|
||||||
|
|
||||||
ubuntu-22-cmake-sycl-fp16:
|
ubuntu-22-cmake-sycl-fp16:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -373,6 +478,8 @@ jobs:
|
|||||||
cmake --build . --config Release -j $(nproc)
|
cmake --build . --config Release -j $(nproc)
|
||||||
|
|
||||||
windows-msys2:
|
windows-msys2:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -417,6 +524,8 @@ jobs:
|
|||||||
cmake --build build --config ${{ matrix.build }} -j $(nproc)
|
cmake --build build --config ${{ matrix.build }} -j $(nproc)
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -452,6 +561,7 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
cmake -S . -B ./build -A ${{ matrix.arch }}
|
cmake -S . -B ./build -A ${{ matrix.arch }}
|
||||||
-DCMAKE_BUILD_TYPE=${{ matrix.build }}
|
-DCMAKE_BUILD_TYPE=${{ matrix.build }}
|
||||||
|
-DBUILD_SHARED_LIBS=ON
|
||||||
-DWHISPER_SDL2=${{ matrix.sdl2 }}
|
-DWHISPER_SDL2=${{ matrix.sdl2 }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
@ -463,20 +573,54 @@ jobs:
|
|||||||
if: matrix.sdl2 == 'ON'
|
if: matrix.sdl2 == 'ON'
|
||||||
run: copy "$env:SDL2_DIR/../lib/${{ matrix.s2arc }}/SDL2.dll" build/bin/${{ matrix.build }}
|
run: copy "$env:SDL2_DIR/../lib/${{ matrix.s2arc }}/SDL2.dll" build/bin/${{ matrix.build }}
|
||||||
|
|
||||||
- name: Upload dll
|
- name: Upload SDL2.dll
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.jnaPath }}_whisper.dll
|
|
||||||
path: build/bin/${{ matrix.build }}/whisper.dll
|
|
||||||
|
|
||||||
- name: Upload binaries
|
|
||||||
if: matrix.sdl2 == 'ON'
|
if: matrix.sdl2 == 'ON'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: whisper-bin-${{ matrix.arch }}
|
name: ${{ matrix.s2arc }}_SDL2.dll
|
||||||
path: build/bin/${{ matrix.build }}
|
path: build/bin/${{ matrix.build }}/SDL2.dll
|
||||||
|
|
||||||
|
- name: Upload whisper dll
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: whisper_${{ matrix.arch }}.dll
|
||||||
|
path: build/bin/${{ matrix.build }}/whisper.dll
|
||||||
|
|
||||||
|
- name: Upload ggml dll
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ggml_${{ matrix.arch }}.dll
|
||||||
|
path: build/bin/${{ matrix.build }}/ggml.dll
|
||||||
|
|
||||||
|
- name: Upload ggml base dll
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ggml_base_${{ matrix.arch }}.dll
|
||||||
|
path: build/bin/${{ matrix.build }}/ggml-base.dll
|
||||||
|
|
||||||
|
- name: Upload ggml cpu dll
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ggml_cpu_${{ matrix.arch }}.dll
|
||||||
|
path: build/bin/${{ matrix.build }}/ggml-cpu.dll
|
||||||
|
|
||||||
|
- name: Pack bin artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path "build/bin/${{ matrix.build }}" -DestinationPath "whisper-bin-${{ matrix.arch }}.zip"
|
||||||
|
|
||||||
|
- name: Upload binaries
|
||||||
|
if: matrix.sdl2 == 'ON' && ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') ||
|
||||||
|
github.event.inputs.create_release == 'true' ||
|
||||||
|
github.event.inputs.pre_release_tag != '' }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: whisper-bin-${{ matrix.arch }}.zip
|
||||||
|
path: whisper-bin-${{ matrix.arch }}.zip
|
||||||
|
|
||||||
windows-blas:
|
windows-blas:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -542,14 +686,23 @@ jobs:
|
|||||||
if: matrix.sdl2 == 'ON'
|
if: matrix.sdl2 == 'ON'
|
||||||
run: copy "$env:SDL2_DIR/../lib/${{ matrix.s2arc }}/SDL2.dll" build/bin/${{ matrix.build }}
|
run: copy "$env:SDL2_DIR/../lib/${{ matrix.s2arc }}/SDL2.dll" build/bin/${{ matrix.build }}
|
||||||
|
|
||||||
|
- name: Pack bin artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path "build/bin/${{ matrix.build }}" -DestinationPath "whisper-blas-bin-${{ matrix.arch }}.zip"
|
||||||
|
|
||||||
- name: Upload binaries
|
- name: Upload binaries
|
||||||
if: matrix.blas == 'ON' && matrix.sdl2 == 'ON'
|
if: matrix.blas == 'ON' && matrix.sdl2 == 'ON' && ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') ||
|
||||||
|
github.event.inputs.create_release == 'true' ||
|
||||||
|
github.event.inputs.pre_release_tag != '' }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: whisper-blas-bin-${{ matrix.arch }}
|
name: whisper-blas-bin-${{ matrix.arch }}.zip
|
||||||
path: build/bin/${{ matrix.build }}
|
path: whisper-blas-bin-${{ matrix.arch }}.zip
|
||||||
|
|
||||||
windows-cublas:
|
windows-cublas:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -566,15 +719,134 @@ jobs:
|
|||||||
- name: Clone repository
|
- name: Clone repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Ninja
|
||||||
|
id: install_ninja
|
||||||
|
run: |
|
||||||
|
choco install ninja
|
||||||
|
|
||||||
|
- name: Install ccache
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2.16
|
||||||
|
with:
|
||||||
|
key: ${{ github.job }}-${{ matrix.cuda-toolkit }}-${{ matrix.build }}
|
||||||
|
variant: sccache
|
||||||
|
evict-old-files: 5d
|
||||||
|
|
||||||
|
- name: Install Cuda Toolkit 11.8.0
|
||||||
|
if: ${{ matrix.cuda-toolkit == '11.8.0' }}
|
||||||
|
run: |
|
||||||
|
$CUDA_VERSION = ${{ matrix.cuda-toolkit }}
|
||||||
|
$CUDA_TOOLKIT_DIR = "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v$CUDA_VERSION"
|
||||||
|
$CUDA_DOWNLOAD = "https://developer.download.nvidia.com/compute/cuda/redist"
|
||||||
|
|
||||||
|
# Components versions
|
||||||
|
$CUDART_VER = "11.8.89"
|
||||||
|
$NVCC_VER = "11.8.89"
|
||||||
|
$NVRTC_VER = "11.8.89"
|
||||||
|
$CUBLAS_VER = "11.8.1.74"
|
||||||
|
$NVTX_VER = "11.8.86"
|
||||||
|
$VS_VER = "11.8.86"
|
||||||
|
$NVPROF_VER = "11.8.87"
|
||||||
|
$CCCL_VER = "11.8.89"
|
||||||
|
|
||||||
|
# Create the directory where the CUDA Toolkit will be installed
|
||||||
|
mkdir -p $CUDA_TOOLKIT_DIR
|
||||||
|
|
||||||
|
# Install unzip to extract the downloaded files
|
||||||
|
choco install unzip -y
|
||||||
|
|
||||||
|
# Download all the required components
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_cudart/windows-x86_64/cuda_cudart-windows-x86_64-${CUDART_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvcc/windows-x86_64/cuda_nvcc-windows-x86_64-${NVCC_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvrtc/windows-x86_64/cuda_nvrtc-windows-x86_64-${NVRTC_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/libcublas/windows-x86_64/libcublas-windows-x86_64-${CUBLAS_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvtx/windows-x86_64/cuda_nvtx-windows-x86_64-${NVTX_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/visual_studio_integration/windows-x86_64/visual_studio_integration-windows-x86_64-${VS_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvprof/windows-x86_64/cuda_nvprof-windows-x86_64-${NVPROF_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_cccl/windows-x86_64/cuda_cccl-windows-x86_64-${CCCL_VER}-archive.zip"
|
||||||
|
|
||||||
|
# Extract all the downloaded files to the CUDA Toolkit directory
|
||||||
|
unzip '*.zip' -d $CUDA_TOOLKIT_DIR
|
||||||
|
|
||||||
|
# Copy all the extracted files to the main CUDA Toolkit directory
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_cudart-windows-x86_64-${CUDART_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvcc-windows-x86_64-${NVCC_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvrtc-windows-x86_64-${NVRTC_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\libcublas-windows-x86_64-${CUBLAS_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvtx-windows-x86_64-${NVTX_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvprof-windows-x86_64-${NVPROF_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_cccl-windows-x86_64-${CCCL_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\visual_studio_integration-windows-x86_64-${VS_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
|
||||||
|
# Visual Studio integration
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\visual_studio_integration-windows-x86_64-${VS_VER}-archive\visual_studio_integration\MSBuildExtensions\*" "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VC\v160\BuildCustomizations" /E /I /H /Y
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
echo "$CUDA_TOOLKIT_DIR\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
echo "$CUDA_TOOLKIT_DIR\libnvvp" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
echo "CUDA_PATH=$CUDA_TOOLKIT_DIR" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
echo "CUDA_PATH_V11_8=$CUDA_TOOLKIT_DIR" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
|
||||||
|
- name: Install Cuda Toolkit 12.2.0
|
||||||
|
if: ${{ matrix.cuda-toolkit == '12.2.0' }}
|
||||||
|
run: |
|
||||||
|
$CUDA_VERSION = ${{ matrix.cuda-toolkit }}
|
||||||
|
$CUDA_TOOLKIT_DIR = "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v$CUDA_VERSION"
|
||||||
|
$CUDA_DOWNLOAD = "https://developer.download.nvidia.com/compute/cuda/redist"
|
||||||
|
|
||||||
|
# Components versions
|
||||||
|
$CUDART_VER = "12.2.140"
|
||||||
|
$NVCC_VER = "12.2.140"
|
||||||
|
$NVRTC_VER = "12.2.140"
|
||||||
|
$CUBLAS_VER = "12.2.5.6"
|
||||||
|
$NVTX_VER = "12.2.140"
|
||||||
|
$PROFILER_VER = "12.2.140"
|
||||||
|
$VS_VER = "12.2.140"
|
||||||
|
$NVPROF_VER = "12.2.142"
|
||||||
|
$CCCL_VER = "12.2.140"
|
||||||
|
|
||||||
|
# Create the directory where the CUDA Toolkit will be installed
|
||||||
|
mkdir -p $CUDA_TOOLKIT_DIR
|
||||||
|
|
||||||
|
# Install unzip to extract the downloaded files
|
||||||
|
choco install unzip -y
|
||||||
|
|
||||||
|
# Download all the required components
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_cudart/windows-x86_64/cuda_cudart-windows-x86_64-${CUDART_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvcc/windows-x86_64/cuda_nvcc-windows-x86_64-${NVCC_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvrtc/windows-x86_64/cuda_nvrtc-windows-x86_64-${NVRTC_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/libcublas/windows-x86_64/libcublas-windows-x86_64-${CUBLAS_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvtx/windows-x86_64/cuda_nvtx-windows-x86_64-${NVTX_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_profiler_api/windows-x86_64/cuda_profiler_api-windows-x86_64-${PROFILER_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/visual_studio_integration/windows-x86_64/visual_studio_integration-windows-x86_64-${VS_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_nvprof/windows-x86_64/cuda_nvprof-windows-x86_64-${NVPROF_VER}-archive.zip"
|
||||||
|
curl -O "$CUDA_DOWNLOAD/cuda_cccl/windows-x86_64/cuda_cccl-windows-x86_64-${CCCL_VER}-archive.zip"
|
||||||
|
|
||||||
|
# Extract all the downloaded files to the CUDA Toolkit directory
|
||||||
|
unzip -q '*.zip' -d $CUDA_TOOLKIT_DIR
|
||||||
|
|
||||||
|
# Copy all the extracted files to the main CUDA Toolkit directory
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_cudart-windows-x86_64-${CUDART_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvcc-windows-x86_64-${NVCC_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvrtc-windows-x86_64-${NVRTC_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\libcublas-windows-x86_64-${CUBLAS_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvtx-windows-x86_64-${NVTX_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_nvprof-windows-x86_64-${NVPROF_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_cccl-windows-x86_64-${CCCL_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\cuda_profiler_api-windows-x86_64-${PROFILER_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\visual_studio_integration-windows-x86_64-${VS_VER}-archive\*" "$CUDA_TOOLKIT_DIR" /E /I /H /Y
|
||||||
|
|
||||||
|
# Visual Studio integration
|
||||||
|
xcopy "$CUDA_TOOLKIT_DIR\visual_studio_integration-windows-x86_64-${VS_VER}-archive\visual_studio_integration\MSBuildExtensions\*" "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VC\v160\BuildCustomizations" /E /I /H /Y
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
echo "$CUDA_TOOLKIT_DIR\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
echo "$CUDA_TOOLKIT_DIR\libnvvp" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
echo "CUDA_PATH=$CUDA_TOOLKIT_DIR" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
echo "CUDA_PATH_V12_2=$CUDA_TOOLKIT_DIR" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||||
|
|
||||||
- name: Add msbuild to PATH
|
- name: Add msbuild to PATH
|
||||||
uses: microsoft/setup-msbuild@v2
|
uses: microsoft/setup-msbuild@v2
|
||||||
|
|
||||||
- name: Install CUDA Toolkit
|
|
||||||
id: cuda-toolkit
|
|
||||||
uses: Jimver/cuda-toolkit@v0.2.15
|
|
||||||
with:
|
|
||||||
cuda: '${{ matrix.cuda-toolkit }}'
|
|
||||||
|
|
||||||
- name: Install 7-Zip
|
- name: Install 7-Zip
|
||||||
run: choco install 7zip -y
|
run: choco install 7zip -y
|
||||||
|
|
||||||
@ -586,38 +858,53 @@ jobs:
|
|||||||
echo "SDL2_DIR=${{ github.workspace }}\SDL2-${{ matrix.sdl2_ver }}\cmake" | Out-File -FilePath $env:GITHUB_ENV -Append
|
echo "SDL2_DIR=${{ github.workspace }}\SDL2-${{ matrix.sdl2_ver }}\cmake" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
echo "${{ github.workspace }}\SDL2-${{ matrix.sdl2_ver }}\cmake" > SDL2_PATH.txt
|
echo "${{ github.workspace }}\SDL2-${{ matrix.sdl2_ver }}\cmake" > SDL2_PATH.txt
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Install cmake
|
||||||
shell: cmd
|
run: choco install cmake
|
||||||
run: |
|
|
||||||
cmake -S . -B ./build -A ${{ matrix.arch }} ^
|
|
||||||
-DCMAKE_BUILD_TYPE=${{ matrix.build }} ^
|
|
||||||
-DGGML_CUDA=${{ matrix.cublas }} ^
|
|
||||||
-DCMAKE_CUDA_ARCHITECTURES=all ^
|
|
||||||
-DWHISPER_SDL2=${{ matrix.sdl2 }} ^
|
|
||||||
-DSDL2_DIR="%SDL2_DIR%"
|
|
||||||
|
|
||||||
- name: Build Project
|
- name: Build Project
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
cd ./build
|
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||||
cmake --build . --config ${{ matrix.build }}
|
cmake --version
|
||||||
|
where cmake
|
||||||
|
cmake -S . -B build -G "Ninja Multi-Config" ^
|
||||||
|
-DCMAKE_BUILD_TYPE=${{ matrix.build }} ^
|
||||||
|
-DGGML_CUDA=${{ matrix.cublas }} ^
|
||||||
|
-DWHISPER_SDL2=${{ matrix.sdl2 }} ^
|
||||||
|
-DSDL2_DIR="%SDL2_DIR%"
|
||||||
|
set /A NINJA_JOBS=%NUMBER_OF_PROCESSORS%-1
|
||||||
|
cmake --build build --config ${{ matrix.build }} -j %NUMBER_OF_PROCESSORS%
|
||||||
|
|
||||||
|
- name: Check sccache status after build
|
||||||
|
run: |
|
||||||
|
sccache --show-stats
|
||||||
|
|
||||||
- name: Copy CUDA DLLs
|
- name: Copy CUDA DLLs
|
||||||
run: |
|
run: |
|
||||||
Get-ChildItem "${{ steps.cuda-toolkit.outputs.CUDA_PATH }}/bin/" -Filter "*.dll" |
|
Get-ChildItem "$env:CUDA_PATH\bin\" -Filter "*.dll" |
|
||||||
Copy-Item -Destination "build/bin/${{ matrix.build }}"
|
Copy-Item -Destination "build/bin/${{ matrix.build }}"
|
||||||
|
|
||||||
- name: Copy SDL2.dll
|
- name: Copy SDL2.dll
|
||||||
if: matrix.sdl2 == 'ON'
|
if: matrix.sdl2 == 'ON'
|
||||||
run: copy "$env:SDL2_DIR/../lib/${{ matrix.arch }}/SDL2.dll" build/bin/${{ matrix.build }}
|
run: copy "$env:SDL2_DIR/../lib/${{ matrix.arch }}/SDL2.dll" build/bin/${{ matrix.build }}
|
||||||
|
|
||||||
|
- name: Pack bin artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path "build/bin/${{ matrix.build }}" -DestinationPath "whisper-cublas-${{ matrix.cuda-toolkit }}-bin-${{ matrix.arch }}.zip"
|
||||||
|
|
||||||
- name: Upload binaries
|
- name: Upload binaries
|
||||||
|
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') ||
|
||||||
|
github.event.inputs.create_release == 'true' ||
|
||||||
|
github.event.inputs.pre_release_tag != '' }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: whisper-cublas-${{ matrix.cuda-toolkit }}-bin-${{ matrix.arch }}
|
name: whisper-cublas-${{ matrix.cuda-toolkit }}-bin-${{ matrix.arch }}.zip
|
||||||
path: build/bin/${{ matrix.build }}
|
path: whisper-cublas-${{ matrix.cuda-toolkit }}-bin-${{ matrix.arch }}.zip
|
||||||
|
|
||||||
emscripten:
|
emscripten:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -641,6 +928,7 @@ jobs:
|
|||||||
|
|
||||||
ios-xcode-build:
|
ios-xcode-build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
needs: determine-tag
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -671,20 +959,38 @@ jobs:
|
|||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \
|
||||||
-DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml
|
-DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml
|
||||||
cmake --build . --config Release -j $(sysctl -n hw.logicalcpu) -- CODE_SIGNING_ALLOWED=NO
|
cmake --build . --config Release -j $(sysctl -n hw.logicalcpu) -- CODE_SIGNING_ALLOWED=NO
|
||||||
sudo cmake --install . --config Release
|
|
||||||
|
|
||||||
- name: xcodebuild for swift package
|
- name: xcodebuild for swift package
|
||||||
id: xcodebuild
|
id: xcodebuild
|
||||||
run: |
|
run: |
|
||||||
xcodebuild -scheme whisper-Package -destination 'generic/platform=iOS'
|
./build-xcframework.sh
|
||||||
|
|
||||||
- name: Build objc example
|
- name: Build objc example
|
||||||
run: xcodebuild -project examples/whisper.objc/whisper.objc.xcodeproj -scheme whisper.objc -configuration ${{ matrix.build }} -sdk iphoneos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO build
|
run: xcodebuild -project examples/whisper.objc/whisper.objc.xcodeproj -scheme whisper.objc -configuration ${{ matrix.build }} -sdk iphoneos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO FRAMEWORK_FOLDER_PATH=./build-ios build
|
||||||
|
|
||||||
- name: Build swiftui example
|
- name: Build swiftui example
|
||||||
run: xcodebuild -project examples/whisper.swiftui/whisper.swiftui.xcodeproj -scheme WhisperCppDemo -configuration ${{ matrix.build }} -sdk iphoneos CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= -destination 'generic/platform=iOS' build
|
run: xcodebuild -project examples/whisper.swiftui/whisper.swiftui.xcodeproj -scheme WhisperCppDemo -configuration ${{ matrix.build }} -sdk iphoneos CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= -destination 'generic/platform=iOS' FRAMEWORK_FOLDER_PATH=./build-ios build
|
||||||
|
|
||||||
|
- name: Pack artifacts
|
||||||
|
id: pack_artifacts
|
||||||
|
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') ||
|
||||||
|
github.event.inputs.create_release == 'true' ||
|
||||||
|
github.event.inputs.pre_release_tag != '' }}
|
||||||
|
run: |
|
||||||
|
zip --symlinks -r whisper-${{ needs.determine-tag.outputs.tag_name }}-xcframework.zip build-apple/whisper.xcframework
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') ||
|
||||||
|
github.event.inputs.create_release == 'true' ||
|
||||||
|
github.event.inputs.pre_release_tag != '' }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: whisper-${{ needs.determine-tag.outputs.tag_name }}-xcframework.zip
|
||||||
|
name: whisper-${{ needs.determine-tag.outputs.tag_name }}-xcframework.zip
|
||||||
|
|
||||||
android:
|
android:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -713,64 +1019,118 @@ jobs:
|
|||||||
cd whisper/examples/whisper.android
|
cd whisper/examples/whisper.android
|
||||||
./gradlew assembleRelease --no-daemon
|
./gradlew assembleRelease --no-daemon
|
||||||
|
|
||||||
# TODO: disable because of following fail: https://github.com/ggerganov/whisper.cpp/actions/runs/11019444420/job/30627193602
|
android_java:
|
||||||
# android_java:
|
runs-on: ubuntu-22.04
|
||||||
# runs-on: ubuntu-22.04
|
|
||||||
#
|
steps:
|
||||||
# steps:
|
- name: Clone
|
||||||
# - name: Clone
|
uses: actions/checkout@v4
|
||||||
# uses: actions/checkout@v4
|
|
||||||
#
|
- name: set up JDK 11
|
||||||
# - name: set up JDK 11
|
uses: actions/setup-java@v4
|
||||||
# uses: actions/setup-java@v4
|
with:
|
||||||
# with:
|
java-version: '11'
|
||||||
# java-version: '11'
|
distribution: 'temurin'
|
||||||
# distribution: 'temurin'
|
cache: gradle
|
||||||
# cache: gradle
|
|
||||||
#
|
- name: Setup Android SDK
|
||||||
# - name: Setup Android SDK
|
uses: android-actions/setup-android@v3
|
||||||
# uses: android-actions/setup-android@v3
|
with:
|
||||||
# with:
|
cmdline-tools-version: 9.0
|
||||||
# cmdline-tools-version: 9.0
|
|
||||||
#
|
- name: Build
|
||||||
# - name: Build
|
run: |
|
||||||
# run: |
|
cd examples/whisper.android.java
|
||||||
# cd examples/whisper.android.java
|
chmod +x ./gradlew
|
||||||
# chmod +x ./gradlew
|
./gradlew assembleRelease
|
||||||
# ./gradlew assembleRelease
|
|
||||||
|
bindings-java:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
|
needs: ['windows']
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: zulu
|
||||||
|
java-version: 20
|
||||||
|
|
||||||
|
- name: Download Whisper Windows lib
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: whisper_x64.dll
|
||||||
|
|
||||||
|
- name: Download GGML Windows lib
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ggml_x64.dll
|
||||||
|
|
||||||
|
- name: Download GGML Base Windows lib
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ggml_base_x64.dll
|
||||||
|
|
||||||
|
- name: Download GGML CPU Windows lib
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ggml_cpu_x64.dll
|
||||||
|
|
||||||
|
- name: Download SDL2.dll
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: x64_SDL2.dll
|
||||||
|
|
||||||
|
- name: List downloaded files
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Get-ChildItem -Path "." -Recurse -Filter "*.dll"
|
||||||
|
|
||||||
|
- name: Move DLL to correct location
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
New-Item -Path "build\bin\Release" -ItemType Directory -Force
|
||||||
|
|
||||||
|
Copy-Item -Path "whisper.dll" -Destination "build\bin\Release\whisper.dll" -Force
|
||||||
|
Write-Host "Copied whisper.dll to build\bin\Release\whisper.dll directory"
|
||||||
|
|
||||||
|
Copy-Item -Path "ggml.dll" -Destination "build\bin\Release\ggml.dll" -Force
|
||||||
|
Write-Host "Copied ggml.dll to build\bin\Release\ggml.dll directory"
|
||||||
|
|
||||||
|
Copy-Item -Path "ggml-base.dll" -Destination "build\bin\Release\ggml-base.dll" -Force
|
||||||
|
Write-Host "Copied ggml-base.dll to build\bin\Release\ggml-base.dll directory"
|
||||||
|
|
||||||
|
Copy-Item -Path "ggml-cpu.dll" -Destination "build\bin\Release\ggml-cpu.dll" -Force
|
||||||
|
Write-Host "Copied ggml-cpu.dll to build\bin\Release\ggml-cpu.dll directory"
|
||||||
|
|
||||||
|
Copy-Item -Path "SDL2.dll" -Destination "build\bin\Release\SDL2.dll" -Force
|
||||||
|
Write-Host "Copied SDL2.dll to build\bin\Release\SDL2.dll directory"
|
||||||
|
|
||||||
|
- name: List build release files
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Get-ChildItem -Path "build\Release" -Recurse -Filter "*.dll"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
models\download-ggml-model.cmd tiny.en models/
|
||||||
|
cd bindings/java
|
||||||
|
chmod +x ./gradlew
|
||||||
|
./gradlew build --info
|
||||||
|
|
||||||
|
- name: Pack jar artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path "bindings/java/build/libs/whispercpp-*.jar" -DestinationPath "whispercpp.jar.zip"
|
||||||
|
|
||||||
|
- name: Upload jar
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: whispercpp.jar.zip
|
||||||
|
path: whispercpp.jar.zip
|
||||||
|
|
||||||
# TODO: disabled because of following fail: https://github.com/ggerganov/whisper.cpp/actions/runs/9686220096/job/26735899598
|
|
||||||
# java:
|
|
||||||
# needs: [ 'windows' ]
|
|
||||||
# runs-on: windows-latest
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/checkout@v4
|
|
||||||
#
|
|
||||||
# - name: Install Java
|
|
||||||
# uses: actions/setup-java@v4
|
|
||||||
# with:
|
|
||||||
# distribution: zulu
|
|
||||||
# java-version: 20
|
|
||||||
#
|
|
||||||
# - name: Download Windows lib
|
|
||||||
# uses: actions/download-artifact@v4
|
|
||||||
# with:
|
|
||||||
# name: win32-x86-64_whisper.dll
|
|
||||||
# path: bindings/java/build/generated/resources/main/win32-x86-64
|
|
||||||
#
|
|
||||||
# - name: Build
|
|
||||||
# run: |
|
|
||||||
# models\download-ggml-model.cmd tiny.en
|
|
||||||
# cd bindings/java
|
|
||||||
# chmod +x ./gradlew
|
|
||||||
# ./gradlew build
|
|
||||||
#
|
|
||||||
# - name: Upload jar
|
|
||||||
# uses: actions/upload-artifact@v4
|
|
||||||
# with:
|
|
||||||
# name: whispercpp.jar
|
|
||||||
# path: bindings/java/build/libs/whispercpp-*.jar
|
|
||||||
#
|
|
||||||
# - name: Publish package
|
# - name: Publish package
|
||||||
# if: ${{ github.ref == 'refs/heads/master' }}
|
# if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
# uses: gradle/gradle-build-action@v2.4.2
|
# uses: gradle/gradle-build-action@v2.4.2
|
||||||
@ -784,6 +1144,8 @@ jobs:
|
|||||||
# PGP_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
# PGP_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
|
||||||
quantize:
|
quantize:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -796,3 +1158,118 @@ jobs:
|
|||||||
cmake -B build
|
cmake -B build
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
./build/bin/quantize models/ggml-tiny.en.bin models/ggml-tiny.en-q4_0.bin q4_0
|
./build/bin/quantize models/ggml-tiny.en.bin models/ggml-tiny.en-q4_0.bin q4_0
|
||||||
|
|
||||||
|
release:
|
||||||
|
if: ${{ github.event.inputs.create_release == 'true' || github.event.inputs.pre_release_tag != '' }}
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
needs:
|
||||||
|
- determine-tag
|
||||||
|
- ios-xcode-build
|
||||||
|
- windows
|
||||||
|
- windows-blas
|
||||||
|
- windows-cublas
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone
|
||||||
|
id: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: ccache
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2.16
|
||||||
|
with:
|
||||||
|
key: release
|
||||||
|
evict-old-files: 1d
|
||||||
|
|
||||||
|
# Downloads all the artifacts from the previous jobs
|
||||||
|
- name: Download artifacts
|
||||||
|
id: download-artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./artifact
|
||||||
|
|
||||||
|
- name: Move artifacts
|
||||||
|
id: move_artifacts
|
||||||
|
run: mkdir -p ./artifact/release && mv ./artifact/*/*.zip ./artifact/release
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
id: create_release
|
||||||
|
uses: ggml-org/action-create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ needs.determine-tag.outputs.tag_name }}
|
||||||
|
prerelease: ${{ github.event.inputs.pre_release_tag != '' }}
|
||||||
|
|
||||||
|
- name: Upload release
|
||||||
|
id: upload_release
|
||||||
|
uses: actions/github-script@v3
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const release_id = '${{ steps.create_release.outputs.id }}';
|
||||||
|
for (let file of await fs.readdirSync('./artifact/release')) {
|
||||||
|
if (path.extname(file) === '.zip') {
|
||||||
|
console.log('uploadReleaseAsset', file);
|
||||||
|
await github.repos.uploadReleaseAsset({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
release_id: release_id,
|
||||||
|
name: file,
|
||||||
|
data: await fs.readFileSync(`./artifact/release/${file}`)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coreml-base-en:
|
||||||
|
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') ||
|
||||||
|
github.event.inputs.create_release == 'true' ||
|
||||||
|
github.event.inputs.pre_release_tag != '' }}
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs: determine-tag
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set environment variables
|
||||||
|
id: set_vars
|
||||||
|
run: |
|
||||||
|
echo "MODEL_NAME=base.en" >> $GITHUB_ENV
|
||||||
|
echo "GEN_MODEL_NAME=whisper-${{ needs.determine-tag.outputs.tag_name }}-ggml-base.en-encoder.mlmodelc" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Download model
|
||||||
|
run: |
|
||||||
|
./models/download-ggml-model.sh ${{ env.MODEL_NAME }}
|
||||||
|
|
||||||
|
- name: Generate CoreML model
|
||||||
|
run: |
|
||||||
|
python3.11 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install ane_transformers openai-whisper coremltools
|
||||||
|
./models/generate-coreml-model.sh ${{ env.MODEL_NAME }}
|
||||||
|
|
||||||
|
vad:
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' ||
|
||||||
|
github.event.inputs.run_type == 'full-ci' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cmake -B build
|
||||||
|
cmake --build build --config Release
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
ctest -R ^test-vad$ --test-dir build --output-on-failure -VV
|
||||||
|
3
.github/workflows/docker.yml
vendored
3
.github/workflows/docker.yml
vendored
@ -18,6 +18,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
- { tag: "main", dockerfile: ".devops/main.Dockerfile", platform: "linux/amd64" }
|
- { tag: "main", dockerfile: ".devops/main.Dockerfile", platform: "linux/amd64" }
|
||||||
|
- { tag: "main-musa", dockerfile: ".devops/main-musa.Dockerfile", platform: "linux/amd64" }
|
||||||
#TODO: the cuda image keeps failing - disable for now
|
#TODO: the cuda image keeps failing - disable for now
|
||||||
# https://github.com/ggerganov/whisper.cpp/actions/runs/11019444428/job/30602020339
|
# https://github.com/ggerganov/whisper.cpp/actions/runs/11019444428/job/30602020339
|
||||||
#- { tag: "main-cuda", dockerfile: ".devops/main-cuda.Dockerfile", platform: "linux/amd64" }
|
#- { tag: "main-cuda", dockerfile: ".devops/main-cuda.Dockerfile", platform: "linux/amd64" }
|
||||||
@ -28,6 +29,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
image: tonistiigi/binfmt:qemu-v7.0.0-28
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
91
.github/workflows/examples-wasm.yml
vendored
Normal file
91
.github/workflows/examples-wasm.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
name: Examples WASM
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master"]
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-wasm-github-pages:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
|
||||||
|
- name: Setup emsdk
|
||||||
|
uses: mymindstorm/setup-emsdk@v14
|
||||||
|
|
||||||
|
- name: Build WASM Examples
|
||||||
|
# Enable for real build later in whisper.cpp
|
||||||
|
run: |
|
||||||
|
mkdir -p build-em && cd build-em
|
||||||
|
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
|
make -j
|
||||||
|
|
||||||
|
- name: Create staging directory
|
||||||
|
run: mkdir -p staging
|
||||||
|
|
||||||
|
- name: Create .nojekyll file in staging directory
|
||||||
|
run: touch staging/.nojekyll
|
||||||
|
|
||||||
|
- name: Copy application files
|
||||||
|
run: |
|
||||||
|
build_dir=build-em/bin
|
||||||
|
|
||||||
|
ls ${build_dir}
|
||||||
|
|
||||||
|
# command.wasm
|
||||||
|
target_dir=staging/command.wasm
|
||||||
|
mkdir -p ${target_dir}
|
||||||
|
cp ${build_dir}/command.wasm/{index.html,command.js,helpers.js} ${target_dir}
|
||||||
|
cp ${build_dir}/libcommand.js ${target_dir}
|
||||||
|
|
||||||
|
# bench.wasm
|
||||||
|
target_dir=staging/bench.wasm
|
||||||
|
mkdir -p ${target_dir}
|
||||||
|
cp ${build_dir}/bench.wasm/{index.html,bench.js,helpers.js} ${target_dir}
|
||||||
|
cp ${build_dir}/libbench.js ${target_dir}
|
||||||
|
|
||||||
|
# stream.wasm
|
||||||
|
target_dir=staging/stream.wasm
|
||||||
|
mkdir -p ${target_dir}
|
||||||
|
cp ${build_dir}/stream.wasm/{index.html,stream.js,helpers.js} ${target_dir}
|
||||||
|
cp ${build_dir}/libstream.js ${target_dir}
|
||||||
|
|
||||||
|
# whisper.wasm (this will be the main example page)
|
||||||
|
target_dir=staging
|
||||||
|
mkdir -p ${target_dir}
|
||||||
|
cp ${build_dir}/whisper.wasm/{index.html,main.js,helpers.js} ${target_dir}
|
||||||
|
cp ${build_dir}/libmain.js ${target_dir}
|
||||||
|
|
||||||
|
# Copy Cross-Origin Isolation service worker
|
||||||
|
cp -v examples/coi-serviceworker.js staging/
|
||||||
|
|
||||||
|
- name: List files in staging directory (for debugging)
|
||||||
|
run: |
|
||||||
|
echo "Files in staging directory:"
|
||||||
|
find staging -type f | sort
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: ./staging
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -58,3 +58,5 @@ cmake-build-debug/
|
|||||||
.cxx/
|
.cxx/
|
||||||
.gradle/
|
.gradle/
|
||||||
local.properties
|
local.properties
|
||||||
|
.log
|
||||||
|
.exe
|
0
.gitmodules
vendored
0
.gitmodules
vendored
@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.5) # for add_link_options and implicit target directories.
|
cmake_minimum_required(VERSION 3.5) # for add_link_options and implicit target directories.
|
||||||
project("whisper.cpp" C CXX)
|
project("whisper.cpp" C CXX)
|
||||||
project("whisper.cpp" VERSION 1.7.4)
|
project("whisper.cpp" VERSION 1.7.5)
|
||||||
include(CheckIncludeFileCXX)
|
include(CheckIncludeFileCXX)
|
||||||
|
|
||||||
set(SOVERSION 1)
|
set(SOVERSION 1)
|
||||||
@ -38,8 +38,13 @@ if (EMSCRIPTEN)
|
|||||||
|
|
||||||
# TODO: without these, we get the following error:
|
# TODO: without these, we get the following error:
|
||||||
# wasm-ld: error: --shared-memory is disallowed by whisper.cpp.o because it was not compiled with 'atomics' or 'bulk-memory' features.
|
# wasm-ld: error: --shared-memory is disallowed by whisper.cpp.o because it was not compiled with 'atomics' or 'bulk-memory' features.
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -s TOTAL_STACK=5242880")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -s TOTAL_STACK=5242880")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
||||||
|
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_STACK=5242880")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -s TOTAL_STACK=5242880")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated")
|
||||||
else()
|
else()
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
set(BUILD_SHARED_LIBS_DEFAULT OFF)
|
set(BUILD_SHARED_LIBS_DEFAULT OFF)
|
||||||
@ -54,15 +59,13 @@ option(BUILD_SHARED_LIBS "build shared libraries" ${BUILD_SHARED_LIBS_DEFAULT})
|
|||||||
# option list
|
# option list
|
||||||
#
|
#
|
||||||
|
|
||||||
# general
|
|
||||||
option(WHISPER_CCACHE "whisper: use ccache if available" ON)
|
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
option(WHISPER_ALL_WARNINGS "whisper: enable all compiler warnings" ON)
|
option(WHISPER_ALL_WARNINGS "whisper: enable all compiler warnings" ON)
|
||||||
option(WHISPER_ALL_WARNINGS_3RD_PARTY "whisper: enable all compiler warnings in 3rd party libs" OFF)
|
option(WHISPER_ALL_WARNINGS_3RD_PARTY "whisper: enable all compiler warnings in 3rd party libs" OFF)
|
||||||
|
|
||||||
# build
|
# build
|
||||||
option(WHISPER_FATAL_WARNINGS "whisper: enable -Werror flag" OFF)
|
option(WHISPER_FATAL_WARNINGS "whisper: enable -Werror flag" OFF)
|
||||||
|
option(WHISPER_USE_SYSTEM_GGML "whisper: use system-installed GGML library" OFF)
|
||||||
|
|
||||||
# sanitizers
|
# sanitizers
|
||||||
option(WHISPER_SANITIZE_THREAD "whisper: enable thread sanitizer" OFF)
|
option(WHISPER_SANITIZE_THREAD "whisper: enable thread sanitizer" OFF)
|
||||||
@ -90,7 +93,6 @@ option(WHISPER_OPENVINO "whisper: support for OpenVINO" OFF)
|
|||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build-info.cmake)
|
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build-info.cmake)
|
||||||
|
|
||||||
# override ggml options
|
# override ggml options
|
||||||
set(GGML_CCACHE ${WHISPER_CCACHE})
|
|
||||||
set(GGML_SANITIZE_THREAD ${WHISPER_SANITIZE_THREAD})
|
set(GGML_SANITIZE_THREAD ${WHISPER_SANITIZE_THREAD})
|
||||||
set(GGML_SANITIZE_ADDRESS ${WHISPER_SANITIZE_ADDRESS})
|
set(GGML_SANITIZE_ADDRESS ${WHISPER_SANITIZE_ADDRESS})
|
||||||
set(GGML_SANITIZE_UNDEFINED ${WHISPER_SANITIZE_UNDEFINED})
|
set(GGML_SANITIZE_UNDEFINED ${WHISPER_SANITIZE_UNDEFINED})
|
||||||
@ -115,13 +117,38 @@ whisper_option_depr(WARNING WHISPER_OPENMP GGML_OPENMP)
|
|||||||
whisper_option_depr(WARNING WHISPER_RPC GGML_RPC)
|
whisper_option_depr(WARNING WHISPER_RPC GGML_RPC)
|
||||||
whisper_option_depr(WARNING WHISPER_SYCL GGML_SYCL)
|
whisper_option_depr(WARNING WHISPER_SYCL GGML_SYCL)
|
||||||
whisper_option_depr(WARNING WHISPER_SYCL_F16 GGML_SYCL_F16)
|
whisper_option_depr(WARNING WHISPER_SYCL_F16 GGML_SYCL_F16)
|
||||||
|
whisper_option_depr(WARNING WHISPER_CCACHE GGML_CCACHE)
|
||||||
|
|
||||||
#
|
#
|
||||||
# build the library
|
# build the library
|
||||||
#
|
#
|
||||||
|
|
||||||
if (NOT TARGET ggml)
|
if (NOT TARGET ggml)
|
||||||
add_subdirectory(ggml)
|
if (WHISPER_USE_SYSTEM_GGML)
|
||||||
|
find_package(ggml REQUIRED)
|
||||||
|
if (NOT ggml_FOUND)
|
||||||
|
message(FATAL_ERROR "System-installed GGML library not found.")
|
||||||
|
endif()
|
||||||
|
add_library(ggml ALIAS ggml::ggml)
|
||||||
|
else()
|
||||||
|
add_subdirectory(ggml)
|
||||||
|
if(WIN32)
|
||||||
|
# The following adds a _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR macro and is a workaround for
|
||||||
|
# the Windows C++ standard library which does not support constexpr mutexes.
|
||||||
|
# From the release notes://github.com/microsoft/STL/wiki/Changelog
|
||||||
|
# Disable constexpr mutex constructor on Windows
|
||||||
|
# Fixed mutex's constructor to be constexpr. #3824 #4000 #4339
|
||||||
|
# Note: Programs that aren't following the documented restrictions on binary compatibility may encounter
|
||||||
|
# null dereferences in mutex machinery. You must follow this rule:
|
||||||
|
# When you mix binaries built by different supported versions of the toolset, the Redistributable version
|
||||||
|
# must be at least as new as the latest toolset used by any app component.
|
||||||
|
# You can define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR as an escape hatch.
|
||||||
|
#
|
||||||
|
# Specifically to whisper.cpp this would cause a crash when using the Java bindings.
|
||||||
|
# resulting in a Invalid memory access error.
|
||||||
|
target_compile_definitions(ggml-base PRIVATE _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
# ... otherwise assume ggml is added by a parent CMakeLists.txt
|
# ... otherwise assume ggml is added by a parent CMakeLists.txt
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
@ -176,10 +203,43 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/whisper.pc"
|
|||||||
#
|
#
|
||||||
|
|
||||||
if (WHISPER_BUILD_TESTS AND NOT CMAKE_JS_VERSION)
|
if (WHISPER_BUILD_TESTS AND NOT CMAKE_JS_VERSION)
|
||||||
#include(CTest)
|
include(CTest)
|
||||||
#add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (WHISPER_BUILD_EXAMPLES)
|
if (WHISPER_BUILD_EXAMPLES)
|
||||||
add_subdirectory(examples)
|
add_subdirectory(examples)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
set(MSVC_WARNING_FLAGS
|
||||||
|
/wd4101 # Unreferenced local variable
|
||||||
|
/wd4005 # Macro redefinition
|
||||||
|
/wd4065 # switch statement contains 'default' but no 'case' labels
|
||||||
|
/wd4267 # Conversion from 'size_t' to a smaller type, possible loss of data
|
||||||
|
/wd4244 # Conversion from one type to another type, possible loss of ata
|
||||||
|
/wd4805 # Unsafe mix of type
|
||||||
|
/wd4305 # Truncation from 'type1' to 'type2' (often double to float)
|
||||||
|
/wd4996 # Function or variable may be unsafe/deprecated
|
||||||
|
)
|
||||||
|
function(disable_msvc_warnings target_name)
|
||||||
|
if(TARGET ${target_name})
|
||||||
|
target_compile_options(${target_name} PRIVATE ${MSVC_WARNING_FLAGS})
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
if (WHISPER_BUILD_EXAMPLES)
|
||||||
|
disable_msvc_warnings(whisper)
|
||||||
|
disable_msvc_warnings(common)
|
||||||
|
disable_msvc_warnings(common-sdl)
|
||||||
|
disable_msvc_warnings(lsp)
|
||||||
|
disable_msvc_warnings(wchess-core)
|
||||||
|
disable_msvc_warnings(whisper-command)
|
||||||
|
disable_msvc_warnings(whisper-cli)
|
||||||
|
disable_msvc_warnings(whisper-server)
|
||||||
|
disable_msvc_warnings(whisper-stream)
|
||||||
|
disable_msvc_warnings(whisper-talk-llama)
|
||||||
|
disable_msvc_warnings(whisper-bench)
|
||||||
|
disable_msvc_warnings(quantize)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
19
Makefile
19
Makefile
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
cmake -B build
|
cmake -B build $(CMAKE_ARGS)
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
|
|
||||||
# download a few audio samples into folder "./samples":
|
# download a few audio samples into folder "./samples":
|
||||||
@ -18,17 +18,6 @@ samples:
|
|||||||
@wget --quiet --show-progress -O samples/mm1.wav https://cdn.openai.com/whisper/draft-20220913a/micro-machines.wav
|
@wget --quiet --show-progress -O samples/mm1.wav https://cdn.openai.com/whisper/draft-20220913a/micro-machines.wav
|
||||||
@wget --quiet --show-progress -O samples/a13.mp3 https://upload.wikimedia.org/wikipedia/commons/transcoded/6/6f/Apollo13-wehaveaproblem.ogg/Apollo13-wehaveaproblem.ogg.mp3
|
@wget --quiet --show-progress -O samples/a13.mp3 https://upload.wikimedia.org/wikipedia/commons/transcoded/6/6f/Apollo13-wehaveaproblem.ogg/Apollo13-wehaveaproblem.ogg.mp3
|
||||||
@wget --quiet --show-progress -O samples/diffusion2023-07-03.flac https://archive.org/download/diffusion2023-07-03/diffusion2023-07-03.flac
|
@wget --quiet --show-progress -O samples/diffusion2023-07-03.flac https://archive.org/download/diffusion2023-07-03/diffusion2023-07-03.flac
|
||||||
@echo "Converting to 16-bit WAV ..."
|
|
||||||
@ffmpeg -loglevel -0 -y -i samples/gb0.ogg -ar 16000 -ac 1 -c:a pcm_s16le samples/gb0.wav
|
|
||||||
@ffmpeg -loglevel -0 -y -i samples/gb1.ogg -ar 16000 -ac 1 -c:a pcm_s16le samples/gb1.wav
|
|
||||||
@ffmpeg -loglevel -0 -y -i samples/hp0.ogg -ar 16000 -ac 1 -c:a pcm_s16le samples/hp0.wav
|
|
||||||
@rm samples/*.ogg
|
|
||||||
@ffmpeg -loglevel -0 -y -i samples/mm1.wav -ar 16000 -ac 1 -c:a pcm_s16le samples/mm0.wav
|
|
||||||
@rm samples/mm1.wav
|
|
||||||
@ffmpeg -loglevel -0 -y -i samples/a13.mp3 -ar 16000 -ac 1 -c:a pcm_s16le -ss 00:00:00 -to 00:00:30 samples/a13.wav
|
|
||||||
@rm samples/a13.mp3
|
|
||||||
@ffmpeg -loglevel -0 -y -i samples/diffusion2023-07-03.flac -ar 16000 -ac 1 -c:a pcm_s16le samples/diffusion2023-07-03.wav
|
|
||||||
@rm samples/diffusion2023-07-03.flac
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Models
|
# Models
|
||||||
@ -52,17 +41,17 @@ samples:
|
|||||||
|
|
||||||
tiny.en tiny base.en base small.en small medium.en medium large-v1 large-v2 large-v3 large-v3-turbo:
|
tiny.en tiny base.en base small.en small medium.en medium large-v1 large-v2 large-v3 large-v3-turbo:
|
||||||
bash ./models/download-ggml-model.sh $@
|
bash ./models/download-ggml-model.sh $@
|
||||||
cmake -B build
|
cmake -B build $(CMAKE_ARGS)
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "==============================================="
|
@echo "==============================================="
|
||||||
@echo "Running $@ on all samples in ./samples ..."
|
@echo "Running $@ on all samples in ./samples ..."
|
||||||
@echo "==============================================="
|
@echo "==============================================="
|
||||||
@echo ""
|
@echo ""
|
||||||
@for f in samples/*.wav; do \
|
@for f in samples/*.{flac,mp3,ogg,wav}; do \
|
||||||
echo "----------------------------------------------" ; \
|
echo "----------------------------------------------" ; \
|
||||||
echo "[+] Running $@ on $$f ... (run 'ffplay $$f' to listen)" ; \
|
echo "[+] Running $@ on $$f ... (run 'ffplay $$f' to listen)" ; \
|
||||||
echo "----------------------------------------------" ; \
|
echo "----------------------------------------------" ; \
|
||||||
echo "" ; \
|
echo "" ; \
|
||||||
./build/bin/whisper-cli -m models/ggml-$@.bin -f $$f ; \
|
./build/bin/whisper-cli -m models/ggml-$@.bin -f $$f ; \
|
||||||
echo "" ; \
|
echo "" ; \
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
// swift-tools-version:5.5
|
|
||||||
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "whisper",
|
|
||||||
platforms: [
|
|
||||||
.macOS(.v12),
|
|
||||||
.iOS(.v14),
|
|
||||||
.watchOS(.v4),
|
|
||||||
.tvOS(.v14)
|
|
||||||
],
|
|
||||||
products: [
|
|
||||||
.library(name: "whisper", targets: ["whisper"]),
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
.systemLibrary(name: "whisper", pkgConfig: "whisper"),
|
|
||||||
]
|
|
||||||
)
|
|
224
README.md
224
README.md
@ -2,15 +2,12 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
[](https://github.com/ggerganov/whisper.cpp/actions)
|
[](https://github.com/ggml-org/whisper.cpp/actions)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://conan.io/center/whisper-cpp)
|
[](https://conan.io/center/whisper-cpp)
|
||||||
[](https://www.npmjs.com/package/whisper.cpp/)
|
[](https://www.npmjs.com/package/whisper.cpp/)
|
||||||
|
|
||||||
> [!NOTE]
|
Stable: [v1.7.5](https://github.com/ggml-org/whisper.cpp/releases/tag/v1.7.5) / [Roadmap](https://github.com/orgs/ggml-org/projects/4/)
|
||||||
> New maintenance roadmap: https://github.com/ggerganov/whisper.cpp/discussions/2788
|
|
||||||
|
|
||||||
Stable: [v1.7.4](https://github.com/ggerganov/whisper.cpp/releases/tag/v1.7.4) / [Roadmap | F.A.Q.](https://github.com/ggerganov/whisper.cpp/discussions/126)
|
|
||||||
|
|
||||||
High-performance inference of [OpenAI's Whisper](https://github.com/openai/whisper) automatic speech recognition (ASR) model:
|
High-performance inference of [OpenAI's Whisper](https://github.com/openai/whisper) automatic speech recognition (ASR) model:
|
||||||
|
|
||||||
@ -26,7 +23,9 @@ High-performance inference of [OpenAI's Whisper](https://github.com/openai/whisp
|
|||||||
- [Efficient GPU support for NVIDIA](#nvidia-gpu-support)
|
- [Efficient GPU support for NVIDIA](#nvidia-gpu-support)
|
||||||
- [OpenVINO Support](#openvino-support)
|
- [OpenVINO Support](#openvino-support)
|
||||||
- [Ascend NPU Support](#ascend-npu-support)
|
- [Ascend NPU Support](#ascend-npu-support)
|
||||||
- [C-style API](https://github.com/ggerganov/whisper.cpp/blob/master/include/whisper.h)
|
- [Moore Threads GPU Support](#moore-threads-gpu-support)
|
||||||
|
- [C-style API](https://github.com/ggml-org/whisper.cpp/blob/master/include/whisper.h)
|
||||||
|
- [Voice Activity Detection (VAD)](#voice-activity-detection-vad)
|
||||||
|
|
||||||
Supported platforms:
|
Supported platforms:
|
||||||
|
|
||||||
@ -34,14 +33,14 @@ Supported platforms:
|
|||||||
- [x] [iOS](examples/whisper.objc)
|
- [x] [iOS](examples/whisper.objc)
|
||||||
- [x] [Android](examples/whisper.android)
|
- [x] [Android](examples/whisper.android)
|
||||||
- [x] [Java](bindings/java/README.md)
|
- [x] [Java](bindings/java/README.md)
|
||||||
- [x] Linux / [FreeBSD](https://github.com/ggerganov/whisper.cpp/issues/56#issuecomment-1350920264)
|
- [x] Linux / [FreeBSD](https://github.com/ggml-org/whisper.cpp/issues/56#issuecomment-1350920264)
|
||||||
- [x] [WebAssembly](examples/whisper.wasm)
|
- [x] [WebAssembly](examples/whisper.wasm)
|
||||||
- [x] Windows ([MSVC](https://github.com/ggerganov/whisper.cpp/blob/master/.github/workflows/build.yml#L117-L144) and [MinGW](https://github.com/ggerganov/whisper.cpp/issues/168)]
|
- [x] Windows ([MSVC](https://github.com/ggml-org/whisper.cpp/blob/master/.github/workflows/build.yml#L117-L144) and [MinGW](https://github.com/ggml-org/whisper.cpp/issues/168)]
|
||||||
- [x] [Raspberry Pi](https://github.com/ggerganov/whisper.cpp/discussions/166)
|
- [x] [Raspberry Pi](https://github.com/ggml-org/whisper.cpp/discussions/166)
|
||||||
- [x] [Docker](https://github.com/ggerganov/whisper.cpp/pkgs/container/whisper.cpp)
|
- [x] [Docker](https://github.com/ggml-org/whisper.cpp/pkgs/container/whisper.cpp)
|
||||||
|
|
||||||
The entire high-level implementation of the model is contained in [whisper.h](include/whisper.h) and [whisper.cpp](src/whisper.cpp).
|
The entire high-level implementation of the model is contained in [whisper.h](include/whisper.h) and [whisper.cpp](src/whisper.cpp).
|
||||||
The rest of the code is part of the [`ggml`](https://github.com/ggerganov/ggml) machine learning library.
|
The rest of the code is part of the [`ggml`](https://github.com/ggml-org/ggml) machine learning library.
|
||||||
|
|
||||||
Having such a lightweight implementation of the model allows to easily integrate it in different platforms and applications.
|
Having such a lightweight implementation of the model allows to easily integrate it in different platforms and applications.
|
||||||
As an example, here is a video of running the model on an iPhone 13 device - fully offline, on-device: [whisper.objc](examples/whisper.objc)
|
As an example, here is a video of running the model on an iPhone 13 device - fully offline, on-device: [whisper.objc](examples/whisper.objc)
|
||||||
@ -54,14 +53,14 @@ https://user-images.githubusercontent.com/1991296/204038393-2f846eae-c255-4099-a
|
|||||||
|
|
||||||
On Apple Silicon, the inference runs fully on the GPU via Metal:
|
On Apple Silicon, the inference runs fully on the GPU via Metal:
|
||||||
|
|
||||||
https://github.com/ggerganov/whisper.cpp/assets/1991296/c82e8f86-60dc-49f2-b048-d2fdbd6b5225
|
https://github.com/ggml-org/whisper.cpp/assets/1991296/c82e8f86-60dc-49f2-b048-d2fdbd6b5225
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
First clone the repository:
|
First clone the repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/ggerganov/whisper.cpp.git
|
git clone https://github.com/ggml-org/whisper.cpp.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Navigate into the directory:
|
Navigate into the directory:
|
||||||
@ -152,6 +151,7 @@ standard cmake setup with:
|
|||||||
cmake -B build -DGGML_BLAS=1
|
cmake -B build -DGGML_BLAS=1
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
./build/bin/whisper-cli [ .. etc .. ]
|
./build/bin/whisper-cli [ .. etc .. ]
|
||||||
|
```
|
||||||
|
|
||||||
## Quantization
|
## Quantization
|
||||||
|
|
||||||
@ -184,11 +184,11 @@ speed-up - more than x3 faster compared with CPU-only execution. Here are the in
|
|||||||
```
|
```
|
||||||
|
|
||||||
- To ensure `coremltools` operates correctly, please confirm that [Xcode](https://developer.apple.com/xcode/) is installed and execute `xcode-select --install` to install the command-line tools.
|
- To ensure `coremltools` operates correctly, please confirm that [Xcode](https://developer.apple.com/xcode/) is installed and execute `xcode-select --install` to install the command-line tools.
|
||||||
- Python 3.10 is recommended.
|
- Python 3.11 is recommended.
|
||||||
- MacOS Sonoma (version 14) or newer is recommended, as older versions of MacOS might experience issues with transcription hallucination.
|
- MacOS Sonoma (version 14) or newer is recommended, as older versions of MacOS might experience issues with transcription hallucination.
|
||||||
- [OPTIONAL] It is recommended to utilize a Python version management system, such as [Miniconda](https://docs.conda.io/en/latest/miniconda.html) for this step:
|
- [OPTIONAL] It is recommended to utilize a Python version management system, such as [Miniconda](https://docs.conda.io/en/latest/miniconda.html) for this step:
|
||||||
- To create an environment, use: `conda create -n py310-whisper python=3.10 -y`
|
- To create an environment, use: `conda create -n py311-whisper python=3.11 -y`
|
||||||
- To activate the environment, use: `conda activate py310-whisper`
|
- To activate the environment, use: `conda activate py311-whisper`
|
||||||
|
|
||||||
- Generate a Core ML model. For example, to generate a `base.en` model, use:
|
- Generate a Core ML model. For example, to generate a `base.en` model, use:
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ speed-up - more than x3 faster compared with CPU-only execution. Here are the in
|
|||||||
The first run on a device is slow, since the ANE service compiles the Core ML model to some device-specific format.
|
The first run on a device is slow, since the ANE service compiles the Core ML model to some device-specific format.
|
||||||
Next runs are faster.
|
Next runs are faster.
|
||||||
|
|
||||||
For more information about the Core ML implementation please refer to PR [#566](https://github.com/ggerganov/whisper.cpp/pull/566).
|
For more information about the Core ML implementation please refer to PR [#566](https://github.com/ggml-org/whisper.cpp/pull/566).
|
||||||
|
|
||||||
## OpenVINO support
|
## OpenVINO support
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ This can result in significant speedup in encoder performance. Here are the inst
|
|||||||
|
|
||||||
- Build `whisper.cpp` with OpenVINO support:
|
- Build `whisper.cpp` with OpenVINO support:
|
||||||
|
|
||||||
Download OpenVINO package from [release page](https://github.com/openvinotoolkit/openvino/releases). The recommended version to use is [2023.0.0](https://github.com/openvinotoolkit/openvino/releases/tag/2023.0.0).
|
Download OpenVINO package from [release page](https://github.com/openvinotoolkit/openvino/releases). The recommended version to use is [2024.6.0](https://github.com/openvinotoolkit/openvino/releases/tag/2024.6.0). Ready to use Binaries of the required libraries can be found in the [OpenVino Archives](https://storage.openvinotoolkit.org/repositories/openvino/packages/2024.6/)
|
||||||
|
|
||||||
After downloading & extracting package onto your development system, set up required environment by sourcing setupvars script. For example:
|
After downloading & extracting package onto your development system, set up required environment by sourcing setupvars script. For example:
|
||||||
|
|
||||||
@ -310,7 +310,7 @@ This can result in significant speedup in encoder performance. Here are the inst
|
|||||||
The first time run on an OpenVINO device is slow, since the OpenVINO framework will compile the IR (Intermediate Representation) model to a device-specific 'blob'. This device-specific blob will get
|
The first time run on an OpenVINO device is slow, since the OpenVINO framework will compile the IR (Intermediate Representation) model to a device-specific 'blob'. This device-specific blob will get
|
||||||
cached for the next run.
|
cached for the next run.
|
||||||
|
|
||||||
For more information about the OpenVINO implementation please refer to PR [#1037](https://github.com/ggerganov/whisper.cpp/pull/1037).
|
For more information about the OpenVINO implementation please refer to PR [#1037](https://github.com/ggml-org/whisper.cpp/pull/1037).
|
||||||
|
|
||||||
## NVIDIA GPU support
|
## NVIDIA GPU support
|
||||||
|
|
||||||
@ -324,6 +324,12 @@ cmake -B build -DGGML_CUDA=1
|
|||||||
cmake --build build -j --config Release
|
cmake --build build -j --config Release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or for newer NVIDIA GPU's (RTX 5000 series):
|
||||||
|
```
|
||||||
|
cmake -B build -DGGML_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES="86"
|
||||||
|
cmake --build build -j --config Release
|
||||||
|
```
|
||||||
|
|
||||||
## Vulkan GPU support
|
## Vulkan GPU support
|
||||||
Cross-vendor solution which allows you to accelerate workload on your GPU.
|
Cross-vendor solution which allows you to accelerate workload on your GPU.
|
||||||
First, make sure your graphics card driver provides support for Vulkan API.
|
First, make sure your graphics card driver provides support for Vulkan API.
|
||||||
@ -377,6 +383,56 @@ Run the inference examples as usual, for example:
|
|||||||
- If you have trouble with Ascend NPU device, please create a issue with **[CANN]** prefix/tag.
|
- If you have trouble with Ascend NPU device, please create a issue with **[CANN]** prefix/tag.
|
||||||
- If you run successfully with your Ascend NPU device, please help update the table `Verified devices`.
|
- If you run successfully with your Ascend NPU device, please help update the table `Verified devices`.
|
||||||
|
|
||||||
|
## Moore Threads GPU support
|
||||||
|
|
||||||
|
With Moore Threads cards the processing of the models is done efficiently on the GPU via muBLAS and custom MUSA kernels.
|
||||||
|
First, make sure you have installed `MUSA SDK rc3.1.1`: https://developer.mthreads.com/sdk/download/musa?equipment=&os=&driverVersion=&version=rc3.1.1
|
||||||
|
|
||||||
|
Now build `whisper.cpp` with MUSA support:
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -B build -DGGML_MUSA=1
|
||||||
|
cmake --build build -j --config Release
|
||||||
|
```
|
||||||
|
|
||||||
|
or specify the architecture for your Moore Threads GPU. For example, if you have a MTT S80 GPU, you can specify the architecture as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -B build -DGGML_MUSA=1 -DMUSA_ARCHITECTURES="21"
|
||||||
|
cmake --build build -j --config Release
|
||||||
|
```
|
||||||
|
|
||||||
|
## FFmpeg support (Linux only)
|
||||||
|
|
||||||
|
If you want to support more audio formats (such as Opus and AAC), you can turn on the `WHISPER_FFMPEG` build flag to enable FFmpeg integration.
|
||||||
|
|
||||||
|
First, you need to install required libraries:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debian/Ubuntu
|
||||||
|
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||||
|
|
||||||
|
# RHEL/Fedora
|
||||||
|
sudo dnf install libavcodec-free-devel libavformat-free-devel libavutil-free-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can build the project as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -B build -D WHISPER_FFMPEG=yes
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the following example to confirm it's working:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Convert an audio file to Opus format
|
||||||
|
ffmpeg -i samples/jfk.wav jfk.opus
|
||||||
|
|
||||||
|
# Transcribe the audio file
|
||||||
|
./build/bin/whisper-cli --model models/ggml-base.en.bin --file jfk.opus
|
||||||
|
```
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
@ -388,8 +444,9 @@ Run the inference examples as usual, for example:
|
|||||||
|
|
||||||
We have two Docker images available for this project:
|
We have two Docker images available for this project:
|
||||||
|
|
||||||
1. `ghcr.io/ggerganov/whisper.cpp:main`: This image includes the main executable file as well as `curl` and `ffmpeg`. (platforms: `linux/amd64`, `linux/arm64`)
|
1. `ghcr.io/ggml-org/whisper.cpp:main`: This image includes the main executable file as well as `curl` and `ffmpeg`. (platforms: `linux/amd64`, `linux/arm64`)
|
||||||
2. `ghcr.io/ggerganov/whisper.cpp:main-cuda`: Same as `main` but compiled with CUDA support. (platforms: `linux/amd64`)
|
2. `ghcr.io/ggml-org/whisper.cpp:main-cuda`: Same as `main` but compiled with CUDA support. (platforms: `linux/amd64`)
|
||||||
|
3. `ghcr.io/ggml-org/whisper.cpp:main-musa`: Same as `main` but compiled with MUSA support. (platforms: `linux/amd64`)
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
@ -402,11 +459,11 @@ docker run -it --rm \
|
|||||||
docker run -it --rm \
|
docker run -it --rm \
|
||||||
-v path/to/models:/models \
|
-v path/to/models:/models \
|
||||||
-v path/to/audios:/audios \
|
-v path/to/audios:/audios \
|
||||||
whisper.cpp:main "./main -m /models/ggml-base.bin -f /audios/jfk.wav"
|
whisper.cpp:main "whisper-cli -m /models/ggml-base.bin -f /audios/jfk.wav"
|
||||||
# transcribe an audio file in samples folder
|
# transcribe an audio file in samples folder
|
||||||
docker run -it --rm \
|
docker run -it --rm \
|
||||||
-v path/to/models:/models \
|
-v path/to/models:/models \
|
||||||
whisper.cpp:main "./main -m /models/ggml-base.bin -f ./samples/jfk.wav"
|
whisper.cpp:main "whisper-cli -m /models/ggml-base.bin -f ./samples/jfk.wav"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installing with Conan
|
## Installing with Conan
|
||||||
@ -427,7 +484,8 @@ For detailed instructions on how to use Conan, please refer to the [Conan docume
|
|||||||
|
|
||||||
This is a naive example of performing real-time inference on audio from your microphone.
|
This is a naive example of performing real-time inference on audio from your microphone.
|
||||||
The [stream](examples/stream) tool samples the audio every half a second and runs the transcription continuously.
|
The [stream](examples/stream) tool samples the audio every half a second and runs the transcription continuously.
|
||||||
More info is available in [issue #10](https://github.com/ggerganov/whisper.cpp/issues/10).
|
More info is available in [issue #10](https://github.com/ggml-org/whisper.cpp/issues/10).
|
||||||
|
You will need to have [sdl2](https://wiki.libsdl.org/SDL2/Installation) installed for it to work properly.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cmake -B build -DWHISPER_SDL2=ON
|
cmake -B build -DWHISPER_SDL2=ON
|
||||||
@ -515,7 +573,7 @@ main: processing './samples/jfk.wav' (176000 samples, 11.0 sec), 4 threads, 1 pr
|
|||||||
|
|
||||||
## Speaker segmentation via tinydiarize (experimental)
|
## Speaker segmentation via tinydiarize (experimental)
|
||||||
|
|
||||||
More information about this approach is available here: https://github.com/ggerganov/whisper.cpp/pull/1058
|
More information about this approach is available here: https://github.com/ggml-org/whisper.cpp/pull/1058
|
||||||
|
|
||||||
Sample usage:
|
Sample usage:
|
||||||
|
|
||||||
@ -542,7 +600,7 @@ main: processing './samples/a13.wav' (480000 samples, 30.0 sec), 4 threads, 1 pr
|
|||||||
## Karaoke-style movie generation (experimental)
|
## Karaoke-style movie generation (experimental)
|
||||||
|
|
||||||
The [whisper-cli](examples/cli) example provides support for output of karaoke-style movies, where the
|
The [whisper-cli](examples/cli) example provides support for output of karaoke-style movies, where the
|
||||||
currently pronounced word is highlighted. Use the `-wts` argument and run the generated bash script.
|
currently pronounced word is highlighted. Use the `-owts` argument and run the generated bash script.
|
||||||
This requires to have `ffmpeg` installed.
|
This requires to have `ffmpeg` installed.
|
||||||
|
|
||||||
Here are a few _"typical"_ examples:
|
Here are a few _"typical"_ examples:
|
||||||
@ -579,7 +637,7 @@ https://user-images.githubusercontent.com/1991296/199337538-b7b0c7a3-2753-4a88-a
|
|||||||
|
|
||||||
## Video comparison of different models
|
## Video comparison of different models
|
||||||
|
|
||||||
Use the [scripts/bench-wts.sh](https://github.com/ggerganov/whisper.cpp/blob/master/scripts/bench-wts.sh) script to generate a video in the following format:
|
Use the [scripts/bench-wts.sh](https://github.com/ggml-org/whisper.cpp/blob/master/scripts/bench-wts.sh) script to generate a video in the following format:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/bench-wts.sh samples/jfk.wav
|
./scripts/bench-wts.sh samples/jfk.wav
|
||||||
@ -596,7 +654,7 @@ In order to have an objective comparison of the performance of the inference acr
|
|||||||
use the [whisper-bench](examples/bench) tool. The tool simply runs the Encoder part of the model and prints how much time it
|
use the [whisper-bench](examples/bench) tool. The tool simply runs the Encoder part of the model and prints how much time it
|
||||||
took to execute it. The results are summarized in the following Github issue:
|
took to execute it. The results are summarized in the following Github issue:
|
||||||
|
|
||||||
[Benchmark results](https://github.com/ggerganov/whisper.cpp/issues/89)
|
[Benchmark results](https://github.com/ggml-org/whisper.cpp/issues/89)
|
||||||
|
|
||||||
Additionally a script to run whisper.cpp with different models and audio files is provided [bench.py](scripts/bench.py).
|
Additionally a script to run whisper.cpp with different models and audio files is provided [bench.py](scripts/bench.py).
|
||||||
|
|
||||||
@ -623,25 +681,24 @@ You can download the converted models using the [models/download-ggml-model.sh](
|
|||||||
or manually from here:
|
or manually from here:
|
||||||
|
|
||||||
- https://huggingface.co/ggerganov/whisper.cpp
|
- https://huggingface.co/ggerganov/whisper.cpp
|
||||||
- https://ggml.ggerganov.com
|
|
||||||
|
|
||||||
For more details, see the conversion script [models/convert-pt-to-ggml.py](models/convert-pt-to-ggml.py) or [models/README.md](models/README.md).
|
For more details, see the conversion script [models/convert-pt-to-ggml.py](models/convert-pt-to-ggml.py) or [models/README.md](models/README.md).
|
||||||
|
|
||||||
## [Bindings](https://github.com/ggerganov/whisper.cpp/discussions/categories/bindings)
|
## [Bindings](https://github.com/ggml-org/whisper.cpp/discussions/categories/bindings)
|
||||||
|
|
||||||
- [x] Rust: [tazz4843/whisper-rs](https://github.com/tazz4843/whisper-rs) | [#310](https://github.com/ggerganov/whisper.cpp/discussions/310)
|
- [x] Rust: [tazz4843/whisper-rs](https://github.com/tazz4843/whisper-rs) | [#310](https://github.com/ggml-org/whisper.cpp/discussions/310)
|
||||||
- [x] JavaScript: [bindings/javascript](bindings/javascript) | [#309](https://github.com/ggerganov/whisper.cpp/discussions/309)
|
- [x] JavaScript: [bindings/javascript](bindings/javascript) | [#309](https://github.com/ggml-org/whisper.cpp/discussions/309)
|
||||||
- React Native (iOS / Android): [whisper.rn](https://github.com/mybigday/whisper.rn)
|
- React Native (iOS / Android): [whisper.rn](https://github.com/mybigday/whisper.rn)
|
||||||
- [x] Go: [bindings/go](bindings/go) | [#312](https://github.com/ggerganov/whisper.cpp/discussions/312)
|
- [x] Go: [bindings/go](bindings/go) | [#312](https://github.com/ggml-org/whisper.cpp/discussions/312)
|
||||||
- [x] Java:
|
- [x] Java:
|
||||||
- [GiviMAD/whisper-jni](https://github.com/GiviMAD/whisper-jni)
|
- [GiviMAD/whisper-jni](https://github.com/GiviMAD/whisper-jni)
|
||||||
- [x] Ruby: [bindings/ruby](bindings/ruby) | [#507](https://github.com/ggerganov/whisper.cpp/discussions/507)
|
- [x] Ruby: [bindings/ruby](bindings/ruby) | [#507](https://github.com/ggml-org/whisper.cpp/discussions/507)
|
||||||
- [x] Objective-C / Swift: [ggerganov/whisper.spm](https://github.com/ggerganov/whisper.spm) | [#313](https://github.com/ggerganov/whisper.cpp/discussions/313)
|
- [x] Objective-C / Swift: [ggml-org/whisper.spm](https://github.com/ggml-org/whisper.spm) | [#313](https://github.com/ggml-org/whisper.cpp/discussions/313)
|
||||||
- [exPHAT/SwiftWhisper](https://github.com/exPHAT/SwiftWhisper)
|
- [exPHAT/SwiftWhisper](https://github.com/exPHAT/SwiftWhisper)
|
||||||
- [x] .NET: | [#422](https://github.com/ggerganov/whisper.cpp/discussions/422)
|
- [x] .NET: | [#422](https://github.com/ggml-org/whisper.cpp/discussions/422)
|
||||||
- [sandrohanea/whisper.net](https://github.com/sandrohanea/whisper.net)
|
- [sandrohanea/whisper.net](https://github.com/sandrohanea/whisper.net)
|
||||||
- [NickDarvey/whisper](https://github.com/NickDarvey/whisper)
|
- [NickDarvey/whisper](https://github.com/NickDarvey/whisper)
|
||||||
- [x] Python: | [#9](https://github.com/ggerganov/whisper.cpp/issues/9)
|
- [x] Python: | [#9](https://github.com/ggml-org/whisper.cpp/issues/9)
|
||||||
- [stlukey/whispercpp.py](https://github.com/stlukey/whispercpp.py) (Cython)
|
- [stlukey/whispercpp.py](https://github.com/stlukey/whispercpp.py) (Cython)
|
||||||
- [AIWintermuteAI/whispercpp](https://github.com/AIWintermuteAI/whispercpp) (Updated fork of aarnphm/whispercpp)
|
- [AIWintermuteAI/whispercpp](https://github.com/AIWintermuteAI/whispercpp) (Updated fork of aarnphm/whispercpp)
|
||||||
- [aarnphm/whispercpp](https://github.com/aarnphm/whispercpp) (Pybind11)
|
- [aarnphm/whispercpp](https://github.com/aarnphm/whispercpp) (Pybind11)
|
||||||
@ -649,6 +706,91 @@ For more details, see the conversion script [models/convert-pt-to-ggml.py](model
|
|||||||
- [x] R: [bnosac/audio.whisper](https://github.com/bnosac/audio.whisper)
|
- [x] R: [bnosac/audio.whisper](https://github.com/bnosac/audio.whisper)
|
||||||
- [x] Unity: [macoron/whisper.unity](https://github.com/Macoron/whisper.unity)
|
- [x] Unity: [macoron/whisper.unity](https://github.com/Macoron/whisper.unity)
|
||||||
|
|
||||||
|
## XCFramework
|
||||||
|
The XCFramework is a precompiled version of the library for iOS, visionOS, tvOS,
|
||||||
|
and macOS. It can be used in Swift projects without the need to compile the
|
||||||
|
library from source. For examples:
|
||||||
|
```swift
|
||||||
|
// swift-tools-version: 5.10
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "Whisper",
|
||||||
|
targets: [
|
||||||
|
.executableTarget(
|
||||||
|
name: "Whisper",
|
||||||
|
dependencies: [
|
||||||
|
"WhisperFramework"
|
||||||
|
]),
|
||||||
|
.binaryTarget(
|
||||||
|
name: "WhisperFramework",
|
||||||
|
url: "https://github.com/ggml-org/whisper.cpp/releases/download/v1.7.5/whisper-v1.7.5-xcframework.zip",
|
||||||
|
checksum: "c7faeb328620d6012e130f3d705c51a6ea6c995605f2df50f6e1ad68c59c6c4a"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Voice Activity Detection (VAD)
|
||||||
|
Support for Voice Activity Detection (VAD) can be enabled using the `--vad`
|
||||||
|
argument to `whisper-cli`. In addition to this option a VAD model is also
|
||||||
|
required.
|
||||||
|
|
||||||
|
The way this works is that first the audio samples are passed through
|
||||||
|
the VAD model which will detect speech segments. Using this information the
|
||||||
|
only the speech segments that are detected are extracted from the original audio
|
||||||
|
input and passed to whisper for processing. This reduces the amount of audio
|
||||||
|
data that needs to be processed by whisper and can significantly speed up the
|
||||||
|
transcription process.
|
||||||
|
|
||||||
|
The following VAD models are currently supported:
|
||||||
|
|
||||||
|
#### Silero-VAD
|
||||||
|
[Silero-vad](https://github.com/snakers4/silero-vad) is a lightweight VAD model
|
||||||
|
written in Python that is fast and accurate.
|
||||||
|
|
||||||
|
This model can be converted to ggml using the following command:
|
||||||
|
```console
|
||||||
|
$ python3 -m venv venv && source venv/bin/activate
|
||||||
|
$ (venv) pip install silero-vad
|
||||||
|
$ (venv) $ python models/convert-silero-vad-to-ggml.py --output models/silero.bin
|
||||||
|
Saving GGML Silero-VAD model to models/silero-v5.1.2-ggml.bin
|
||||||
|
```
|
||||||
|
And it can then be used with whisper as follows:
|
||||||
|
```console
|
||||||
|
$ ./build/bin/whisper-cli \
|
||||||
|
--file ./samples/jfk.wav \
|
||||||
|
--model ./models/ggml-base.en.bin \
|
||||||
|
--vad \
|
||||||
|
--vad-model ./models/silero-v5.1.2-ggml.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
#### VAD Options
|
||||||
|
|
||||||
|
* --vad-threshold: Threshold probability for speech detection. A probability
|
||||||
|
for a speech segment/frame above this threshold will be considered as speech.
|
||||||
|
|
||||||
|
* --vad-min-speech-duration-ms: Minimum speech duration in milliseconds. Speech
|
||||||
|
segments shorter than this value will be discarded to filter out brief noise or
|
||||||
|
false positives.
|
||||||
|
|
||||||
|
* --vad-min-silence-duration-ms: Minimum silence duration in milliseconds. Silence
|
||||||
|
periods must be at least this long to end a speech segment. Shorter silence
|
||||||
|
periods will be ignored and included as part of the speech.
|
||||||
|
|
||||||
|
* --vad-max-speech-duration-s: Maximum speech duration in seconds. Speech segments
|
||||||
|
longer than this will be automatically split into multiple segments at silence
|
||||||
|
points exceeding 98ms to prevent excessively long segments.
|
||||||
|
|
||||||
|
* --vad-speech-pad-ms: Speech padding in milliseconds. Adds this amount of padding
|
||||||
|
before and after each detected speech segment to avoid cutting off speech edges.
|
||||||
|
|
||||||
|
* --vad-samples-overlap: Amount of audio to extend from each speech segment into
|
||||||
|
the next one, in seconds (e.g., 0.10 = 100ms overlap). This ensures speech isn't
|
||||||
|
cut off abruptly between segments when they're concatenated together.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
There are various examples of using the library for different projects in the [examples](examples) folder.
|
There are various examples of using the library for different projects in the [examples](examples) folder.
|
||||||
@ -667,13 +809,13 @@ Some of the examples are even ported to run in the browser using WebAssembly. Ch
|
|||||||
| [whisper.android](examples/whisper.android) | | Android mobile application using whisper.cpp |
|
| [whisper.android](examples/whisper.android) | | Android mobile application using whisper.cpp |
|
||||||
| [whisper.nvim](examples/whisper.nvim) | | Speech-to-text plugin for Neovim |
|
| [whisper.nvim](examples/whisper.nvim) | | Speech-to-text plugin for Neovim |
|
||||||
| [generate-karaoke.sh](examples/generate-karaoke.sh) | | Helper script to easily [generate a karaoke video](https://youtu.be/uj7hVta4blM) of raw audio capture |
|
| [generate-karaoke.sh](examples/generate-karaoke.sh) | | Helper script to easily [generate a karaoke video](https://youtu.be/uj7hVta4blM) of raw audio capture |
|
||||||
| [livestream.sh](examples/livestream.sh) | | [Livestream audio transcription](https://github.com/ggerganov/whisper.cpp/issues/185) |
|
| [livestream.sh](examples/livestream.sh) | | [Livestream audio transcription](https://github.com/ggml-org/whisper.cpp/issues/185) |
|
||||||
| [yt-wsp.sh](examples/yt-wsp.sh) | | Download + transcribe and/or translate any VOD [(original)](https://gist.github.com/DaniruKun/96f763ec1a037cc92fe1a059b643b818) |
|
| [yt-wsp.sh](examples/yt-wsp.sh) | | Download + transcribe and/or translate any VOD [(original)](https://gist.github.com/DaniruKun/96f763ec1a037cc92fe1a059b643b818) |
|
||||||
| [wchess](examples/wchess) | [wchess.wasm](examples/wchess) | Voice-controlled chess |
|
| [wchess](examples/wchess) | [wchess.wasm](examples/wchess) | Voice-controlled chess |
|
||||||
|
|
||||||
## [Discussions](https://github.com/ggerganov/whisper.cpp/discussions)
|
## [Discussions](https://github.com/ggml-org/whisper.cpp/discussions)
|
||||||
|
|
||||||
If you have any kind of feedback about this project feel free to use the Discussions section and open a new topic.
|
If you have any kind of feedback about this project feel free to use the Discussions section and open a new topic.
|
||||||
You can use the [Show and tell](https://github.com/ggerganov/whisper.cpp/discussions/categories/show-and-tell) category
|
You can use the [Show and tell](https://github.com/ggml-org/whisper.cpp/discussions/categories/show-and-tell) category
|
||||||
to share your own projects that use `whisper.cpp`. If you have a question, make sure to check the
|
to share your own projects that use `whisper.cpp`. If you have a question, make sure to check the
|
||||||
[Frequently asked questions (#126)](https://github.com/ggerganov/whisper.cpp/discussions/126) discussion.
|
[Frequently asked questions (#126)](https://github.com/ggml-org/whisper.cpp/discussions/126) discussion.
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
module whisper [system] {
|
|
||||||
header "whisper.h"
|
|
||||||
link "whisper"
|
|
||||||
export *
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <whisper.h>
|
|
||||||
|
|
@ -11,11 +11,11 @@ UNAME_M := $(shell uname -m)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
GGML_METAL_PATH_RESOURCES := $(abspath ../..)
|
GGML_METAL_PATH_RESOURCES := $(abspath ../..)
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build_go
|
||||||
MODELS_DIR := models
|
MODELS_DIR := models
|
||||||
EXAMPLES_DIR := $(wildcard examples/*)
|
EXAMPLES_DIR := $(wildcard examples/*)
|
||||||
INCLUDE_PATH := $(abspath ../../include):$(abspath ../../ggml/include)
|
INCLUDE_PATH := $(abspath ../../include):$(abspath ../../ggml/include)
|
||||||
LIBRARY_PATH := $(abspath ../..)
|
LIBRARY_PATH := $(abspath ../../${BUILD_DIR}/src:$(abspath ../../${BUILD_DIR}/ggml/src))
|
||||||
|
|
||||||
ifeq ($(GGML_CUDA),1)
|
ifeq ($(GGML_CUDA),1)
|
||||||
LIBRARY_PATH := $(LIBRARY_PATH):$(CUDA_PATH)/targets/$(UNAME_M)-linux/lib/
|
LIBRARY_PATH := $(LIBRARY_PATH):$(CUDA_PATH)/targets/$(UNAME_M)-linux/lib/
|
||||||
@ -29,8 +29,10 @@ endif
|
|||||||
all: clean whisper examples
|
all: clean whisper examples
|
||||||
|
|
||||||
whisper: mkdir
|
whisper: mkdir
|
||||||
@echo Build whisper
|
cmake -S ../.. -B ../../${BUILD_DIR} \
|
||||||
@${MAKE} -C ../.. libwhisper.a
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF
|
||||||
|
cmake --build ../../${BUILD_DIR} --target whisper
|
||||||
|
|
||||||
test: model-small whisper modtidy
|
test: model-small whisper modtidy
|
||||||
ifeq ($(UNAME_S),Darwin)
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
@ -31,7 +31,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := context.Process(samples, nil, nil); err != nil {
|
if err := context.Process(samples, nil, nil, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ func main() {
|
|||||||
In order to build, you need to have the Go compiler installed. You can get it from [here](https://golang.org/dl/). Run the tests with:
|
In order to build, you need to have the Go compiler installed. You can get it from [here](https://golang.org/dl/). Run the tests with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/ggerganov/whisper.cpp.git
|
git clone https://github.com/ggml-org/whisper.cpp.git
|
||||||
cd whisper.cpp/bindings/go
|
cd whisper.cpp/bindings/go
|
||||||
make test
|
make test
|
||||||
```
|
```
|
||||||
@ -98,7 +98,7 @@ The API Documentation:
|
|||||||
|
|
||||||
Getting help:
|
Getting help:
|
||||||
|
|
||||||
* Follow the discussion for the go bindings [here](https://github.com/ggerganov/whisper.cpp/discussions/312)
|
* Follow the discussion for the go bindings [here](https://github.com/ggml-org/whisper.cpp/discussions/312)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
github.com/ggerganov/whisper.cpp/bindings/go
|
github.com/ggml-org/whisper.cpp/bindings/go
|
||||||
provides a speech-to-text service bindings for the Go programming language.
|
provides a speech-to-text service bindings for the Go programming language.
|
||||||
*/
|
*/
|
||||||
package whisper
|
package whisper
|
||||||
|
@ -9,22 +9,23 @@ import (
|
|||||||
// ContextForSignal returns a context object which is cancelled when a signal
|
// ContextForSignal returns a context object which is cancelled when a signal
|
||||||
// is received. It returns nil if no signal parameter is provided
|
// is received. It returns nil if no signal parameter is provided
|
||||||
func ContextForSignal(signals ...os.Signal) context.Context {
|
func ContextForSignal(signals ...os.Signal) context.Context {
|
||||||
if len(signals) == 0 {
|
if len(signals) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan os.Signal)
|
ch := make(chan os.Signal, 1) // Buffered channel with space for 1 signal
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// Send message on channel when signal received
|
// Send message on channel when signal received
|
||||||
signal.Notify(ch, signals...)
|
signal.Notify(ch, signals...)
|
||||||
|
|
||||||
// When any signal received, call cancel
|
// When any signal is received, call cancel
|
||||||
go func() {
|
go func() {
|
||||||
<-ch
|
<-ch
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Return success
|
// Return success
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -17,14 +18,27 @@ import (
|
|||||||
// CONSTANTS
|
// CONSTANTS
|
||||||
|
|
||||||
const (
|
const (
|
||||||
srcUrl = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main" // The location of the models
|
srcUrl = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/" // The location of the models
|
||||||
srcExt = ".bin" // Filename extension
|
srcExt = ".bin" // Filename extension
|
||||||
bufSize = 1024 * 64 // Size of the buffer used for downloading the model
|
bufSize = 1024 * 64 // Size of the buffer used for downloading the model
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// The models which will be downloaded, if no model is specified as an argument
|
// The models which will be downloaded, if no model is specified as an argument
|
||||||
modelNames = []string{"ggml-tiny.en", "ggml-tiny", "ggml-base.en", "ggml-base", "ggml-small.en", "ggml-small", "ggml-medium.en", "ggml-medium", "ggml-large-v1", "ggml-large-v2", "ggml-large-v3", "large-v3-turbo"}
|
modelNames = []string{
|
||||||
|
"tiny", "tiny-q5_1", "tiny-q8_0",
|
||||||
|
"tiny.en", "tiny.en-q5_1", "tiny.en-q8_0",
|
||||||
|
"base", "base-q5_1", "base-q8_0",
|
||||||
|
"base.en", "base.en-q5_1", "base.en-q8_0",
|
||||||
|
"small", "small-q5_1", "small-q8_0",
|
||||||
|
"small.en", "small.en-q5_1", "small.en-q8_0",
|
||||||
|
"medium", "medium-q5_0", "medium-q8_0",
|
||||||
|
"medium.en", "medium.en-q5_0", "medium.en-q8_0",
|
||||||
|
"large-v1",
|
||||||
|
"large-v2", "large-v2-q5_0", "large-v2-q8_0",
|
||||||
|
"large-v3", "large-v3-q5_0",
|
||||||
|
"large-v3-turbo", "large-v3-turbo-q5_0", "large-v3-turbo-q8_0",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -44,7 +58,25 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
name := filepath.Base(flag.CommandLine.Name())
|
name := filepath.Base(flag.CommandLine.Name())
|
||||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <model>\n\n", name)
|
fmt.Fprintf(flag.CommandLine.Output(), `
|
||||||
|
Usage: %s [options] [<model>...]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-out string Specify the output folder where models will be saved.
|
||||||
|
Default: Current working directory.
|
||||||
|
-timeout duration Set the maximum duration for downloading a model.
|
||||||
|
Example: 10m, 1h (default: 30m0s).
|
||||||
|
-quiet Suppress all output except errors.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
1. Download a specific model:
|
||||||
|
%s -out ./models tiny-q8_0
|
||||||
|
|
||||||
|
2. Download all models:
|
||||||
|
%s -out ./models
|
||||||
|
|
||||||
|
`, name, name, name)
|
||||||
|
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@ -114,23 +146,87 @@ func GetOut() (string, error) {
|
|||||||
// GetModels returns the list of models to download
|
// GetModels returns the list of models to download
|
||||||
func GetModels() []string {
|
func GetModels() []string {
|
||||||
if flag.NArg() == 0 {
|
if flag.NArg() == 0 {
|
||||||
return modelNames
|
fmt.Println("No model specified.")
|
||||||
} else {
|
fmt.Println("Preparing to download all models...")
|
||||||
return flag.Args()
|
|
||||||
|
// Calculate total download size
|
||||||
|
fmt.Println("Calculating total download size...")
|
||||||
|
totalSize, err := CalculateTotalDownloadSize(modelNames)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error calculating download sizes:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("View available models: https://huggingface.co/ggerganov/whisper.cpp/tree/main")
|
||||||
|
fmt.Printf("Total download size: %.2f GB\n", float64(totalSize)/(1024*1024*1024))
|
||||||
|
fmt.Println("Would you like to download all models? (y/N)")
|
||||||
|
|
||||||
|
// Prompt for user input
|
||||||
|
var response string
|
||||||
|
fmt.Scanln(&response)
|
||||||
|
if response != "y" && response != "Y" {
|
||||||
|
fmt.Println("Aborting. Specify a model to download.")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelNames // Return all models if confirmed
|
||||||
}
|
}
|
||||||
|
return flag.Args() // Return specific models if arguments are provided
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalculateTotalDownloadSize(models []string) (int64, error) {
|
||||||
|
var totalSize int64
|
||||||
|
client := http.Client{}
|
||||||
|
|
||||||
|
for _, model := range models {
|
||||||
|
modelURL, err := URLForModel(model)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue a HEAD request to get the file size
|
||||||
|
req, err := http.NewRequest("HEAD", modelURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
fmt.Printf("Warning: Unable to fetch size for %s (HTTP %d)\n", model, resp.StatusCode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
size := resp.ContentLength
|
||||||
|
totalSize += size
|
||||||
|
}
|
||||||
|
return totalSize, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLForModel returns the URL for the given model on huggingface.co
|
// URLForModel returns the URL for the given model on huggingface.co
|
||||||
func URLForModel(model string) (string, error) {
|
func URLForModel(model string) (string, error) {
|
||||||
|
// Ensure "ggml-" prefix is added only once
|
||||||
|
if !strings.HasPrefix(model, "ggml-") {
|
||||||
|
model = "ggml-" + model
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure ".bin" extension is added only once
|
||||||
if filepath.Ext(model) != srcExt {
|
if filepath.Ext(model) != srcExt {
|
||||||
model += srcExt
|
model += srcExt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the base URL
|
||||||
url, err := url.Parse(srcUrl)
|
url, err := url.Parse(srcUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else {
|
|
||||||
url.Path = filepath.Join(url.Path, model)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure no trailing slash in the base URL
|
||||||
|
url.Path = fmt.Sprintf("%s/%s", strings.TrimSuffix(url.Path, "/"), model)
|
||||||
return url.String(), nil
|
return url.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func Process(model whisper.Model, path string, flags *Flags) error {
|
|||||||
// Process the data
|
// Process the data
|
||||||
fmt.Fprintf(flags.Output(), " ...processing %q\n", path)
|
fmt.Fprintf(flags.Output(), " ...processing %q\n", path)
|
||||||
context.ResetTimings()
|
context.ResetTimings()
|
||||||
if err := context.Process(data, cb, nil); err != nil {
|
if err := context.Process(data, nil, cb, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,10 @@ func (context *context) Language() string {
|
|||||||
return whisper.Whisper_lang_str(context.params.Language())
|
return whisper.Whisper_lang_str(context.params.Language())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (context *context) DetectedLanguage() string {
|
||||||
|
return whisper.Whisper_lang_str(context.model.ctx.Whisper_full_lang_id())
|
||||||
|
}
|
||||||
|
|
||||||
// Set translate flag
|
// Set translate flag
|
||||||
func (context *context) SetTranslate(v bool) {
|
func (context *context) SetTranslate(v bool) {
|
||||||
context.params.SetTranslate(v)
|
context.params.SetTranslate(v)
|
||||||
@ -189,6 +193,7 @@ func (context *context) WhisperLangAutoDetect(offset_ms int, n_threads int) ([]f
|
|||||||
// Process new sample data and return any errors
|
// Process new sample data and return any errors
|
||||||
func (context *context) Process(
|
func (context *context) Process(
|
||||||
data []float32,
|
data []float32,
|
||||||
|
callEncoderBegin EncoderBeginCallback,
|
||||||
callNewSegment SegmentCallback,
|
callNewSegment SegmentCallback,
|
||||||
callProgress ProgressCallback,
|
callProgress ProgressCallback,
|
||||||
) error {
|
) error {
|
||||||
@ -203,7 +208,20 @@ func (context *context) Process(
|
|||||||
// We don't do parallel processing at the moment
|
// We don't do parallel processing at the moment
|
||||||
processors := 0
|
processors := 0
|
||||||
if processors > 1 {
|
if processors > 1 {
|
||||||
if err := context.model.ctx.Whisper_full_parallel(context.params, data, processors, nil, func(new int) {
|
if err := context.model.ctx.Whisper_full_parallel(context.params, data, processors, callEncoderBegin,
|
||||||
|
func(new int) {
|
||||||
|
if callNewSegment != nil {
|
||||||
|
num_segments := context.model.ctx.Whisper_full_n_segments()
|
||||||
|
s0 := num_segments - new
|
||||||
|
for i := s0; i < num_segments; i++ {
|
||||||
|
callNewSegment(toSegment(context.model.ctx, i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := context.model.ctx.Whisper_full(context.params, data, callEncoderBegin,
|
||||||
|
func(new int) {
|
||||||
if callNewSegment != nil {
|
if callNewSegment != nil {
|
||||||
num_segments := context.model.ctx.Whisper_full_n_segments()
|
num_segments := context.model.ctx.Whisper_full_n_segments()
|
||||||
s0 := num_segments - new
|
s0 := num_segments - new
|
||||||
@ -211,22 +229,11 @@ func (context *context) Process(
|
|||||||
callNewSegment(toSegment(context.model.ctx, i))
|
callNewSegment(toSegment(context.model.ctx, i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}); err != nil {
|
}, func(progress int) {
|
||||||
return err
|
if callProgress != nil {
|
||||||
}
|
callProgress(progress)
|
||||||
} else if err := context.model.ctx.Whisper_full(context.params, data, nil, func(new int) {
|
|
||||||
if callNewSegment != nil {
|
|
||||||
num_segments := context.model.ctx.Whisper_full_n_segments()
|
|
||||||
s0 := num_segments - new
|
|
||||||
for i := s0; i < num_segments; i++ {
|
|
||||||
callNewSegment(toSegment(context.model.ctx, i))
|
|
||||||
}
|
}
|
||||||
}
|
}); err != nil {
|
||||||
}, func(progress int) {
|
|
||||||
if callProgress != nil {
|
|
||||||
callProgress(progress)
|
|
||||||
}
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,37 @@ func TestProcess(t *testing.T) {
|
|||||||
context, err := model.NewContext()
|
context, err := model.NewContext()
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
|
|
||||||
err = context.Process(data, nil, nil)
|
err = context.Process(data, nil, nil, nil)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDetectedLanguage(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
fh, err := os.Open(SamplePath)
|
||||||
|
assert.NoError(err)
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
// Decode the WAV file - load the full buffer
|
||||||
|
dec := wav.NewDecoder(fh)
|
||||||
|
buf, err := dec.FullPCMBuffer()
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(uint16(1), dec.NumChans)
|
||||||
|
|
||||||
|
data := buf.AsFloat32Buffer().Data
|
||||||
|
|
||||||
|
model, err := whisper.New(ModelPath)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.NotNil(model)
|
||||||
|
defer model.Close()
|
||||||
|
|
||||||
|
context, err := model.NewContext()
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = context.Process(data, nil, nil, nil)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
expectedLanguage := "en"
|
||||||
|
actualLanguage := context.DetectedLanguage()
|
||||||
|
assert.Equal(expectedLanguage, actualLanguage)
|
||||||
|
}
|
||||||
|
@ -16,6 +16,10 @@ type SegmentCallback func(Segment)
|
|||||||
// processing. It is called during the Process function
|
// processing. It is called during the Process function
|
||||||
type ProgressCallback func(int)
|
type ProgressCallback func(int)
|
||||||
|
|
||||||
|
// EncoderBeginCallback is the callback function for checking if we want to
|
||||||
|
// continue processing. It is called during the Process function
|
||||||
|
type EncoderBeginCallback func() bool
|
||||||
|
|
||||||
// Model is the interface to a whisper model. Create a new model with the
|
// Model is the interface to a whisper model. Create a new model with the
|
||||||
// function whisper.New(string)
|
// function whisper.New(string)
|
||||||
type Model interface {
|
type Model interface {
|
||||||
@ -31,12 +35,13 @@ type Model interface {
|
|||||||
Languages() []string
|
Languages() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context is the speach recognition context.
|
// Context is the speech recognition context.
|
||||||
type Context interface {
|
type Context interface {
|
||||||
SetLanguage(string) error // Set the language to use for speech recognition, use "auto" for auto detect language.
|
SetLanguage(string) error // Set the language to use for speech recognition, use "auto" for auto detect language.
|
||||||
SetTranslate(bool) // Set translate flag
|
SetTranslate(bool) // Set translate flag
|
||||||
IsMultilingual() bool // Return true if the model is multilingual.
|
IsMultilingual() bool // Return true if the model is multilingual.
|
||||||
Language() string // Get language
|
Language() string // Get language
|
||||||
|
DetectedLanguage() string // Get detected language
|
||||||
|
|
||||||
SetOffset(time.Duration) // Set offset
|
SetOffset(time.Duration) // Set offset
|
||||||
SetDuration(time.Duration) // Set duration
|
SetDuration(time.Duration) // Set duration
|
||||||
@ -58,7 +63,7 @@ type Context interface {
|
|||||||
// Process mono audio data and return any errors.
|
// Process mono audio data and return any errors.
|
||||||
// If defined, newly generated segments are passed to the
|
// If defined, newly generated segments are passed to the
|
||||||
// callback function during processing.
|
// callback function during processing.
|
||||||
Process([]float32, SegmentCallback, ProgressCallback) error
|
Process([]float32, EncoderBeginCallback, SegmentCallback, ProgressCallback) error
|
||||||
|
|
||||||
// After process is called, return segments until the end of the stream
|
// After process is called, return segments until the end of the stream
|
||||||
// is reached, when io.EOF is returned.
|
// is reached, when io.EOF is returned.
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
// CGO
|
// CGO
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -lwhisper -lm -lstdc++ -fopenmp
|
#cgo LDFLAGS: -lwhisper -lggml -lggml-base -lggml-cpu -lm -lstdc++ -fopenmp
|
||||||
#cgo darwin LDFLAGS: -framework Accelerate -framework Metal -framework Foundation -framework CoreGraphics
|
#cgo darwin LDFLAGS: -framework Accelerate -framework Metal -framework Foundation -framework CoreGraphics
|
||||||
#include <whisper.h>
|
#include <whisper.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -31,10 +31,10 @@ public class Example {
|
|||||||
var whisperParams = whisper.getFullDefaultParams(WhisperSamplingStrategy.WHISPER_SAMPLING_GREEDY);
|
var whisperParams = whisper.getFullDefaultParams(WhisperSamplingStrategy.WHISPER_SAMPLING_GREEDY);
|
||||||
// custom configuration if required
|
// custom configuration if required
|
||||||
whisperParams.temperature_inc = 0f;
|
whisperParams.temperature_inc = 0f;
|
||||||
|
|
||||||
var samples = readAudio(); // divide each value by 32767.0f
|
var samples = readAudio(); // divide each value by 32767.0f
|
||||||
whisper.fullTranscribe(whisperParams, samples);
|
whisper.fullTranscribe(whisperParams, samples);
|
||||||
|
|
||||||
int segmentCount = whisper.getTextSegmentCount(context);
|
int segmentCount = whisper.getTextSegmentCount(context);
|
||||||
for (int i = 0; i < segmentCount; i++) {
|
for (int i = 0; i < segmentCount; i++) {
|
||||||
String text = whisper.getTextSegment(context, i);
|
String text = whisper.getTextSegment(context, i);
|
||||||
@ -52,7 +52,7 @@ public class Example {
|
|||||||
In order to build, you need to have the JDK 8 or higher installed. Run the tests with:
|
In order to build, you need to have the JDK 8 or higher installed. Run the tests with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/ggerganov/whisper.cpp.git
|
git clone https://github.com/ggml-org/whisper.cpp.git
|
||||||
cd whisper.cpp/bindings/java
|
cd whisper.cpp/bindings/java
|
||||||
|
|
||||||
./gradlew build
|
./gradlew build
|
||||||
|
@ -25,25 +25,43 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('copyLibwhisperDynlib', Copy) {
|
tasks.register('copyLibwhisperDynlib', Copy) {
|
||||||
from '../../build'
|
from '../../build/src'
|
||||||
include 'libwhisper.dynlib'
|
include 'libwhisper.dylib'
|
||||||
into 'build/generated/resources/main/darwin'
|
into 'build/generated/resources/main'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('copyLibwhisperSo', Copy) {
|
tasks.register('copyLibwhisperSo', Copy) {
|
||||||
from '../../build'
|
from '../../build/src'
|
||||||
include 'libwhisper.so'
|
include 'libwhisper.so'
|
||||||
into 'build/generated/resources/main/linux-x86-64'
|
into 'build/generated/resources/main'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('copyWhisperDll', Copy) {
|
tasks.register('copyWhisperDLL', Copy) {
|
||||||
from '../../build/Release'
|
from '../../build/bin/Release'
|
||||||
include 'whisper.dll'
|
include 'whisper.dll'
|
||||||
into 'build/generated/resources/main/windows-x86-64'
|
into 'build/generated/resources/main'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('copyGGML_BASE_DLL', Copy) {
|
||||||
|
from '../../build/bin/Release'
|
||||||
|
include 'ggml-base.dll'
|
||||||
|
into 'build/generated/resources/main'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('copyGGML_DLL', Copy) {
|
||||||
|
from '../../build/bin/Release'
|
||||||
|
include 'ggml.dll'
|
||||||
|
into 'build/generated/resources/main'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('copyGGML_CPU_DLL', Copy) {
|
||||||
|
from '../../build/bin/Release'
|
||||||
|
include 'ggml-cpu.dll'
|
||||||
|
into 'build/generated/resources/main'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('copyLibs') {
|
tasks.register('copyLibs') {
|
||||||
dependsOn copyLibwhisperDynlib, copyLibwhisperSo, copyWhisperDll
|
dependsOn copyLibwhisperDynlib, copyLibwhisperSo, copyWhisperDLL, copyGGML_BASE_DLL, copyGGML_DLL, copyGGML_CPU_DLL
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@ -55,7 +73,12 @@ java {
|
|||||||
withJavadocJar()
|
withJavadocJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourcesJar() {
|
||||||
|
dependsOn copyLibs
|
||||||
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
dependsOn copyLibs
|
||||||
exclude '**/whisper_java.exp', '**/whisper_java.lib'
|
exclude '**/whisper_java.exp', '**/whisper_java.lib'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +90,9 @@ tasks.withType(Test) {
|
|||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test.dependsOn copyLibs
|
||||||
|
processResources.dependsOn copyLibs
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "net.java.dev.jna:jna:5.13.0"
|
implementation "net.java.dev.jna:jna:5.13.0"
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter:5.9.2"
|
testImplementation "org.junit.jupiter:junit-jupiter:5.9.2"
|
||||||
|
0
bindings/java/gradlew
vendored
Normal file → Executable file
0
bindings/java/gradlew
vendored
Normal file → Executable file
@ -0,0 +1,24 @@
|
|||||||
|
package io.github.ggerganov.whispercpp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presets for alignment heads in DTW token timestamps
|
||||||
|
*/
|
||||||
|
public class WhisperConstants {
|
||||||
|
// Alignment heads presets
|
||||||
|
public static final int WHISPER_AHEADS_NONE = 0;
|
||||||
|
public static final int WHISPER_AHEADS_TINY_EN = 1;
|
||||||
|
public static final int WHISPER_AHEADS_TINY = 2;
|
||||||
|
public static final int WHISPER_AHEADS_BASE_EN = 3;
|
||||||
|
public static final int WHISPER_AHEADS_BASE = 4;
|
||||||
|
public static final int WHISPER_AHEADS_SMALL_EN = 5;
|
||||||
|
public static final int WHISPER_AHEADS_SMALL = 6;
|
||||||
|
public static final int WHISPER_AHEADS_MEDIUM_EN = 7;
|
||||||
|
public static final int WHISPER_AHEADS_MEDIUM = 8;
|
||||||
|
public static final int WHISPER_AHEADS_LARGE_V1 = 9;
|
||||||
|
public static final int WHISPER_AHEADS_LARGE_V2 = 10;
|
||||||
|
public static final int WHISPER_AHEADS_LARGE_V3 = 11;
|
||||||
|
public static final int WHISPER_AHEADS_LARGE_V3_TURBO = 12;
|
||||||
|
public static final int WHISPER_AHEADS_CUSTOM = 13;
|
||||||
|
public static final int WHISPER_AHEADS_N_TOP_MOST = 14;
|
||||||
|
public static final int WHISPER_AHEADS_COUNT = 15;
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package io.github.ggerganov.whispercpp;
|
package io.github.ggerganov.whispercpp;
|
||||||
|
|
||||||
|
import com.sun.jna.NativeLong;
|
||||||
import com.sun.jna.Structure;
|
import com.sun.jna.Structure;
|
||||||
import com.sun.jna.ptr.PointerByReference;
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
import io.github.ggerganov.whispercpp.ggml.GgmlType;
|
import io.github.ggerganov.whispercpp.ggml.GgmlType;
|
||||||
import io.github.ggerganov.whispercpp.WhisperModel;
|
import io.github.ggerganov.whispercpp.WhisperModel;
|
||||||
import io.github.ggerganov.whispercpp.params.WhisperContextParams;
|
import io.github.ggerganov.whispercpp.params.WhisperContextParams;
|
||||||
@ -9,33 +11,26 @@ import io.github.ggerganov.whispercpp.params.WhisperContextParams;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class WhisperContext extends Structure {
|
public class WhisperContext extends Structure {
|
||||||
int t_load_us = 0;
|
public NativeLong t_load_us;
|
||||||
int t_start_us = 0;
|
public NativeLong t_start_us;
|
||||||
|
|
||||||
/** weight type (FP32 / FP16 / QX) */
|
/** weight type (FP32 / FP16 / QX) */
|
||||||
GgmlType wtype = GgmlType.GGML_TYPE_F16;
|
public GgmlType wtype = GgmlType.GGML_TYPE_F16;
|
||||||
/** intermediate type (FP32 or FP16) */
|
/** intermediate type (FP32 or FP16) */
|
||||||
GgmlType itype = GgmlType.GGML_TYPE_F16;
|
public GgmlType itype = GgmlType.GGML_TYPE_F16;
|
||||||
|
|
||||||
// WhisperModel model;
|
public WhisperContextParams.ByValue params;
|
||||||
public PointerByReference model;
|
|
||||||
// whisper_vocab vocab;
|
public Pointer model;
|
||||||
// whisper_state * state = nullptr;
|
public Pointer vocab;
|
||||||
public PointerByReference vocab;
|
public Pointer state;
|
||||||
public PointerByReference state;
|
|
||||||
|
|
||||||
/** populated by whisper_init_from_file_with_params() */
|
/** populated by whisper_init_from_file_with_params() */
|
||||||
String path_model;
|
public Pointer path_model;
|
||||||
WhisperContextParams params;
|
|
||||||
|
|
||||||
// public static class ByReference extends WhisperContext implements Structure.ByReference {
|
@Override
|
||||||
// }
|
protected List<String> getFieldOrder() {
|
||||||
//
|
return List.of("t_load_us", "t_start_us", "wtype", "itype",
|
||||||
// public static class ByValue extends WhisperContext implements Structure.ByValue {
|
"params", "model", "vocab", "state", "path_model");
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// protected List<String> getFieldOrder() {
|
|
||||||
// return List.of("t_load_us", "t_start_us", "wtype", "itype", "model", "vocab", "state", "path_model");
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,11 @@ public class WhisperCpp implements AutoCloseable {
|
|||||||
* @param modelPath - absolute path, or just the name (eg: "base", "base-en" or "base.en")
|
* @param modelPath - absolute path, or just the name (eg: "base", "base-en" or "base.en")
|
||||||
* @param params - params to use when initialising the context
|
* @param params - params to use when initialising the context
|
||||||
*/
|
*/
|
||||||
public void initContext(String modelPath, WhisperContextParams params) throws FileNotFoundException {
|
public void initContext(String modelPath, WhisperContextParams.ByValue params) throws FileNotFoundException {
|
||||||
initContextImpl(modelPath, params);
|
initContextImpl(modelPath, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initContextImpl(String modelPath, WhisperContextParams params) throws FileNotFoundException {
|
private void initContextImpl(String modelPath, WhisperContextParams.ByValue params) throws FileNotFoundException {
|
||||||
if (ctx != null) {
|
if (ctx != null) {
|
||||||
lib.whisper_free(ctx);
|
lib.whisper_free(ctx);
|
||||||
}
|
}
|
||||||
@ -69,15 +69,13 @@ public class WhisperCpp implements AutoCloseable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides default params which can be used with `whisper_init_from_file_with_params()` etc.
|
* Provides default params which can be used with `whisper_init_from_file_with_params()` etc.
|
||||||
* Because this function allocates memory for the params, the caller must call either:
|
* Returns a ByValue instance to ensure proper parameter passing to native code.
|
||||||
* - call `whisper_free_context_params()`
|
|
||||||
* - `Native.free(Pointer.nativeValue(pointer));`
|
|
||||||
*/
|
*/
|
||||||
public WhisperContextParams getContextDefaultParams() {
|
public WhisperContextParams.ByValue getContextDefaultParams() {
|
||||||
paramsPointer = lib.whisper_context_default_params_by_ref();
|
WhisperContextParams.ByValue valueParams = new WhisperContextParams.ByValue(
|
||||||
WhisperContextParams params = new WhisperContextParams(paramsPointer);
|
lib.whisper_context_default_params_by_ref());
|
||||||
params.read();
|
valueParams.read();
|
||||||
return params;
|
return valueParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,7 +86,7 @@ public class WhisperCpp implements AutoCloseable {
|
|||||||
*
|
*
|
||||||
* @param strategy - GREEDY
|
* @param strategy - GREEDY
|
||||||
*/
|
*/
|
||||||
public WhisperFullParams getFullDefaultParams(WhisperSamplingStrategy strategy) {
|
public WhisperFullParams.ByValue getFullDefaultParams(WhisperSamplingStrategy strategy) {
|
||||||
Pointer pointer;
|
Pointer pointer;
|
||||||
|
|
||||||
// whisper_full_default_params_by_ref allocates memory which we need to delete, so only create max 1 pointer for each strategy.
|
// whisper_full_default_params_by_ref allocates memory which we need to delete, so only create max 1 pointer for each strategy.
|
||||||
@ -104,7 +102,7 @@ public class WhisperCpp implements AutoCloseable {
|
|||||||
pointer = beamParamsPointer;
|
pointer = beamParamsPointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
WhisperFullParams params = new WhisperFullParams(pointer);
|
WhisperFullParams.ByValue params = new WhisperFullParams.ByValue(pointer);
|
||||||
params.read();
|
params.read();
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
@ -138,15 +136,21 @@ public class WhisperCpp implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text.
|
* Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text.
|
||||||
* Not thread safe for same context
|
* Not thread safe for same context
|
||||||
* Uses the specified decoding strategy to obtain the text.
|
* Uses the specified decoding strategy to obtain the text.
|
||||||
*/
|
*/
|
||||||
public String fullTranscribe(WhisperFullParams whisperParams, float[] audioData) throws IOException {
|
public String fullTranscribe(WhisperFullParams.ByValue whisperParams, float[] audioData) throws IOException {
|
||||||
if (ctx == null) {
|
if (ctx == null) {
|
||||||
throw new IllegalStateException("Model not initialised");
|
throw new IllegalStateException("Model not initialised");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
WhisperFullParams.ByValue valueParams = new WhisperFullParams.ByValue(
|
||||||
|
lib.whisper_full_default_params_by_ref(WhisperSamplingStrategy.WHISPER_SAMPLING_BEAM_SEARCH.ordinal()));
|
||||||
|
valueParams.read();
|
||||||
|
*/
|
||||||
|
|
||||||
if (lib.whisper_full(ctx, whisperParams, audioData, audioData.length) != 0) {
|
if (lib.whisper_full(ctx, whisperParams, audioData, audioData.length) != 0) {
|
||||||
throw new IOException("Failed to process audio");
|
throw new IOException("Failed to process audio");
|
||||||
}
|
}
|
||||||
@ -163,12 +167,17 @@ public class WhisperCpp implements AutoCloseable {
|
|||||||
|
|
||||||
return str.toString().trim();
|
return str.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<WhisperSegment> fullTranscribeWithTime(WhisperFullParams whisperParams, float[] audioData) throws IOException {
|
public List<WhisperSegment> fullTranscribeWithTime(WhisperFullParams whisperParams, float[] audioData) throws IOException {
|
||||||
if (ctx == null) {
|
if (ctx == null) {
|
||||||
throw new IllegalStateException("Model not initialised");
|
throw new IllegalStateException("Model not initialised");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lib.whisper_full(ctx, whisperParams, audioData, audioData.length) != 0) {
|
WhisperFullParams.ByValue valueParams = new WhisperFullParams.ByValue(
|
||||||
|
lib.whisper_full_default_params_by_ref(WhisperSamplingStrategy.WHISPER_SAMPLING_BEAM_SEARCH.ordinal()));
|
||||||
|
valueParams.read();
|
||||||
|
|
||||||
|
if (lib.whisper_full(ctx, valueParams, audioData, audioData.length) != 0) {
|
||||||
throw new IOException("Failed to process audio");
|
throw new IOException("Failed to process audio");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import io.github.ggerganov.whispercpp.params.WhisperContextParams;
|
|||||||
import io.github.ggerganov.whispercpp.params.WhisperFullParams;
|
import io.github.ggerganov.whispercpp.params.WhisperFullParams;
|
||||||
|
|
||||||
public interface WhisperCppJnaLibrary extends Library {
|
public interface WhisperCppJnaLibrary extends Library {
|
||||||
|
|
||||||
WhisperCppJnaLibrary instance = Native.load("whisper", WhisperCppJnaLibrary.class);
|
WhisperCppJnaLibrary instance = Native.load("whisper", WhisperCppJnaLibrary.class);
|
||||||
|
|
||||||
String whisper_print_system_info();
|
String whisper_print_system_info();
|
||||||
@ -38,7 +39,7 @@ public interface WhisperCppJnaLibrary extends Library {
|
|||||||
* @param params Pointer to whisper_context_params
|
* @param params Pointer to whisper_context_params
|
||||||
* @return Whisper context on success, null on failure
|
* @return Whisper context on success, null on failure
|
||||||
*/
|
*/
|
||||||
Pointer whisper_init_from_file_with_params(String path_model, WhisperContextParams params);
|
Pointer whisper_init_from_file_with_params(String path_model, WhisperContextParams.ByValue params);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate (almost) all memory needed for the model by loading from a buffer.
|
* Allocate (almost) all memory needed for the model by loading from a buffer.
|
||||||
@ -180,12 +181,12 @@ public interface WhisperCppJnaLibrary extends Library {
|
|||||||
/**
|
/**
|
||||||
* @return the id of the specified language, returns -1 if not found.
|
* @return the id of the specified language, returns -1 if not found.
|
||||||
* Examples:
|
* Examples:
|
||||||
* "de" -> 2
|
* "de" -> 2
|
||||||
* "german" -> 2
|
* "german" -> 2
|
||||||
*/
|
*/
|
||||||
int whisper_lang_id(String lang);
|
int whisper_lang_id(String lang);
|
||||||
|
|
||||||
/** @return the short string of the specified language id (e.g. 2 -> "de"), returns nullptr if not found */
|
/** @return the short string of the specified language id (e.g. 2 -> "de"), returns nullptr if not found */
|
||||||
String whisper_lang_str(int id);
|
String whisper_lang_str(int id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -268,20 +269,21 @@ public interface WhisperCppJnaLibrary extends Library {
|
|||||||
void whisper_free_params(Pointer params);
|
void whisper_free_params(Pointer params);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text
|
* Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text
|
||||||
* Not thread safe for same context
|
* Not thread safe for same context
|
||||||
* Uses the specified decoding strategy to obtain the text.
|
* Uses the specified decoding strategy to obtain the text.
|
||||||
*/
|
*/
|
||||||
int whisper_full(Pointer ctx, WhisperFullParams params, final float[] samples, int n_samples);
|
int whisper_full(Pointer ctx, WhisperFullParams.ByValue params, final float[] samples, int n_samples);
|
||||||
|
|
||||||
int whisper_full_with_state(Pointer ctx, Pointer state, WhisperFullParams params, final float[] samples, int n_samples);
|
public int whisper_full_with_state(Pointer ctx, Pointer state, WhisperFullParams.ByValue params, float[] samples, int n_samples);
|
||||||
|
//int whisper_full_with_state(Pointer ctx, Pointer state, WhisperFullParams params, final float[] samples, int n_samples);
|
||||||
|
|
||||||
// Split the input audio in chunks and process each chunk separately using whisper_full_with_state()
|
// Split the input audio in chunks and process each chunk separately using whisper_full_with_state()
|
||||||
// Result is stored in the default state of the context
|
// Result is stored in the default state of the context
|
||||||
// Not thread safe if executed in parallel on the same context.
|
// Not thread safe if executed in parallel on the same context.
|
||||||
// It seems this approach can offer some speedup in some cases.
|
// It seems this approach can offer some speedup in some cases.
|
||||||
// However, the transcription accuracy can be worse at the beginning and end of each chunk.
|
// However, the transcription accuracy can be worse at the beginning and end of each chunk.
|
||||||
int whisper_full_parallel(Pointer ctx, WhisperFullParams params, final float[] samples, int n_samples, int n_processors);
|
int whisper_full_parallel(Pointer ctx, WhisperFullParams.ByValue params, final float[] samples, int n_samples, int n_processors);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of generated text segments.
|
* Number of generated text segments.
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package io.github.ggerganov.whispercpp.callbacks;
|
||||||
|
|
||||||
|
import com.sun.jna.Callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for aborting GGML computation
|
||||||
|
* Maps to the C typedef: bool (*ggml_abort_callback)(void * data)
|
||||||
|
*/
|
||||||
|
public interface GgmlAbortCallback extends Callback {
|
||||||
|
/**
|
||||||
|
* Return true to abort the computation, false to continue
|
||||||
|
*
|
||||||
|
* @param data User data passed to the callback
|
||||||
|
* @return true to abort, false to continue
|
||||||
|
*/
|
||||||
|
boolean invoke(com.sun.jna.Pointer data);
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package io.github.ggerganov.whispercpp.params;
|
||||||
|
import com.sun.jna.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class WhisperAhead extends Structure {
|
||||||
|
|
||||||
|
public int n_text_layer;
|
||||||
|
|
||||||
|
public int n_head;
|
||||||
|
|
||||||
|
public WhisperAhead() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhisperAhead(int textLayer, int head) {
|
||||||
|
super();
|
||||||
|
this.n_text_layer = textLayer;
|
||||||
|
this.n_head = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("n_text_layer", "n_head");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ByReference extends WhisperAhead implements Structure.ByReference {}
|
||||||
|
|
||||||
|
public static class ByValue extends WhisperAhead implements Structure.ByValue {}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package io.github.ggerganov.whispercpp.params;
|
||||||
|
import com.sun.jna.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class WhisperAheads extends Structure {
|
||||||
|
public NativeLong n_heads;
|
||||||
|
|
||||||
|
public Pointer heads;
|
||||||
|
|
||||||
|
public WhisperAheads() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create alignment heads from an array of WhisperAhead objects
|
||||||
|
*/
|
||||||
|
public void setHeads(WhisperAhead[] aheadsArray) {
|
||||||
|
this.n_heads = new NativeLong(aheadsArray.length);
|
||||||
|
|
||||||
|
int structSize = aheadsArray[0].size();
|
||||||
|
Memory mem = new Memory(structSize * aheadsArray.length);
|
||||||
|
|
||||||
|
for (int i = 0; i < aheadsArray.length; i++) {
|
||||||
|
aheadsArray[i].write();
|
||||||
|
byte[] buffer = aheadsArray[i].getPointer().getByteArray(0, structSize);
|
||||||
|
mem.write(i * structSize, buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.heads = mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("n_heads", "heads");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ByReference extends WhisperAheads implements Structure.ByReference {}
|
||||||
|
|
||||||
|
public static class ByValue extends WhisperAheads implements Structure.ByValue {}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package io.github.ggerganov.whispercpp.params;
|
package io.github.ggerganov.whispercpp.params;
|
||||||
|
|
||||||
import com.sun.jna.*;
|
import com.sun.jna.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -11,21 +9,73 @@ import java.util.List;
|
|||||||
* whisper_context_default_params()
|
* whisper_context_default_params()
|
||||||
*/
|
*/
|
||||||
public class WhisperContextParams extends Structure {
|
public class WhisperContextParams extends Structure {
|
||||||
|
|
||||||
public WhisperContextParams(Pointer p) {
|
public WhisperContextParams(Pointer p) {
|
||||||
super(p);
|
super(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Use GPU for inference Number (default = true) */
|
public WhisperContextParams() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Use GPU for inference (default = true) */
|
||||||
public CBool use_gpu;
|
public CBool use_gpu;
|
||||||
|
|
||||||
/** Use GPU for inference Number (default = true) */
|
/** Use flash attention (default = false) */
|
||||||
|
public CBool flash_attn;
|
||||||
|
|
||||||
|
/** CUDA device to use (default = 0) */
|
||||||
|
public int gpu_device;
|
||||||
|
|
||||||
|
/** [EXPERIMENTAL] Enable token-level timestamps with DTW (default = false) */
|
||||||
|
public CBool dtw_token_timestamps;
|
||||||
|
|
||||||
|
/** [EXPERIMENTAL] Alignment heads preset for DTW */
|
||||||
|
public int dtw_aheads_preset;
|
||||||
|
|
||||||
|
/** Number of top layers to use for DTW when using WHISPER_AHEADS_N_TOP_MOST preset */
|
||||||
|
public int dtw_n_top;
|
||||||
|
|
||||||
|
public WhisperAheads.ByValue dtw_aheads;
|
||||||
|
|
||||||
|
/** DTW memory size (internal use) */
|
||||||
|
public NativeLong dtw_mem_size;
|
||||||
|
|
||||||
|
/** Use GPU for inference */
|
||||||
public void useGpu(boolean enable) {
|
public void useGpu(boolean enable) {
|
||||||
use_gpu = enable ? CBool.TRUE : CBool.FALSE;
|
use_gpu = enable ? CBool.TRUE : CBool.FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Use flash attention */
|
||||||
|
public void useFlashAttn(boolean enable) {
|
||||||
|
flash_attn = enable ? CBool.TRUE : CBool.FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enable DTW token-level timestamps */
|
||||||
|
public void enableDtwTokenTimestamps(boolean enable) {
|
||||||
|
dtw_token_timestamps = enable ? CBool.TRUE : CBool.FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set DTW alignment heads preset */
|
||||||
|
public void setDtwAheadsPreset(int preset) {
|
||||||
|
dtw_aheads_preset = preset;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> getFieldOrder() {
|
protected List<String> getFieldOrder() {
|
||||||
return Arrays.asList("use_gpu");
|
return Arrays.asList(
|
||||||
|
"use_gpu",
|
||||||
|
"flash_attn",
|
||||||
|
"gpu_device",
|
||||||
|
"dtw_token_timestamps",
|
||||||
|
"dtw_aheads_preset",
|
||||||
|
"dtw_n_top",
|
||||||
|
"dtw_aheads",
|
||||||
|
"dtw_mem_size"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ByValue extends WhisperContextParams implements Structure.ByValue {
|
||||||
|
public ByValue() { super(); }
|
||||||
|
public ByValue(Pointer p) { super(p); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import io.github.ggerganov.whispercpp.callbacks.WhisperEncoderBeginCallback;
|
|||||||
import io.github.ggerganov.whispercpp.callbacks.WhisperLogitsFilterCallback;
|
import io.github.ggerganov.whispercpp.callbacks.WhisperLogitsFilterCallback;
|
||||||
import io.github.ggerganov.whispercpp.callbacks.WhisperNewSegmentCallback;
|
import io.github.ggerganov.whispercpp.callbacks.WhisperNewSegmentCallback;
|
||||||
import io.github.ggerganov.whispercpp.callbacks.WhisperProgressCallback;
|
import io.github.ggerganov.whispercpp.callbacks.WhisperProgressCallback;
|
||||||
|
import io.github.ggerganov.whispercpp.callbacks.GgmlAbortCallback;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -16,10 +17,12 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class WhisperFullParams extends Structure {
|
public class WhisperFullParams extends Structure {
|
||||||
|
|
||||||
|
public WhisperFullParams() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
public WhisperFullParams(Pointer p) {
|
public WhisperFullParams(Pointer p) {
|
||||||
super(p);
|
super(p);
|
||||||
// super(p, ALIGN_MSVC);
|
|
||||||
// super(p, ALIGN_GNUC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sampling strategy for whisper_full() function. */
|
/** Sampling strategy for whisper_full() function. */
|
||||||
@ -69,10 +72,10 @@ public class WhisperFullParams extends Structure {
|
|||||||
single_segment = single ? CBool.TRUE : CBool.FALSE;
|
single_segment = single ? CBool.TRUE : CBool.FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Flag to print special tokens (e.g., <SOT>, <EOT>, <BEG>, etc.). (default = false) */
|
/** Flag to print special tokens (e.g., <SOT>, <EOT>, <BEG>, etc.). (default = false) */
|
||||||
public CBool print_special;
|
public CBool print_special;
|
||||||
|
|
||||||
/** Flag to print special tokens (e.g., <SOT>, <EOT>, <BEG>, etc.). (default = false) */
|
/** Flag to print special tokens (e.g., <SOT>, <EOT>, <BEG>, etc.). (default = false) */
|
||||||
public void printSpecial(boolean enable) {
|
public void printSpecial(boolean enable) {
|
||||||
print_special = enable ? CBool.TRUE : CBool.FALSE;
|
print_special = enable ? CBool.TRUE : CBool.FALSE;
|
||||||
}
|
}
|
||||||
@ -129,6 +132,14 @@ public class WhisperFullParams extends Structure {
|
|||||||
/** Maximum tokens per segment (0, default = no limit) */
|
/** Maximum tokens per segment (0, default = no limit) */
|
||||||
public int max_tokens;
|
public int max_tokens;
|
||||||
|
|
||||||
|
/** [EXPERIMENTAL] Enable debug mode for extra info */
|
||||||
|
public CBool debug_mode;
|
||||||
|
|
||||||
|
/** Enable debug mode */
|
||||||
|
public void enableDebugMode(boolean enable) {
|
||||||
|
debug_mode = enable ? CBool.TRUE : CBool.FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/** Overwrite the audio context size (0 = use default). */
|
/** Overwrite the audio context size (0 = use default). */
|
||||||
public int audio_ctx;
|
public int audio_ctx;
|
||||||
|
|
||||||
@ -274,6 +285,16 @@ public class WhisperFullParams extends Structure {
|
|||||||
*/
|
*/
|
||||||
public Pointer encoder_begin_callback_user_data;
|
public Pointer encoder_begin_callback_user_data;
|
||||||
|
|
||||||
|
/** Callback used to abort GGML computation */
|
||||||
|
public Pointer abort_callback;
|
||||||
|
|
||||||
|
/** User data for the abort_callback */
|
||||||
|
public Pointer abort_callback_user_data;
|
||||||
|
|
||||||
|
public void setAbortCallback(GgmlAbortCallback callback) {
|
||||||
|
abort_callback = CallbackReference.getFunctionPointer(callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback by each decoder to filter obtained logits.
|
* Callback by each decoder to filter obtained logits.
|
||||||
* WhisperLogitsFilterCallback
|
* WhisperLogitsFilterCallback
|
||||||
@ -310,17 +331,28 @@ public class WhisperFullParams extends Structure {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> getFieldOrder() {
|
protected List<String> getFieldOrder() {
|
||||||
return Arrays.asList("strategy", "n_threads", "n_max_text_ctx", "offset_ms", "duration_ms", "translate",
|
return Arrays.asList("strategy", "n_threads", "n_max_text_ctx",
|
||||||
"no_context", "single_segment", "no_timestamps",
|
"offset_ms", "duration_ms", "translate", "no_context",
|
||||||
"print_special", "print_progress", "print_realtime", "print_timestamps", "token_timestamps",
|
"no_timestamps", "single_segment", "print_special",
|
||||||
"thold_pt", "thold_ptsum", "max_len", "split_on_word", "max_tokens", "audio_ctx",
|
"print_progress", "print_realtime", "print_timestamps",
|
||||||
"tdrz_enable", "suppress_regex", "initial_prompt", "prompt_tokens", "prompt_n_tokens", "language", "detect_language",
|
"token_timestamps", "thold_pt", "thold_ptsum", "max_len",
|
||||||
"suppress_blank", "suppress_nst", "temperature", "max_initial_ts", "length_penalty",
|
"split_on_word", "max_tokens", "debug_mode", "audio_ctx",
|
||||||
"temperature_inc", "entropy_thold", "logprob_thold", "no_speech_thold", "greedy", "beam_search",
|
"tdrz_enable", "suppress_regex", "initial_prompt",
|
||||||
"new_segment_callback", "new_segment_callback_user_data",
|
"prompt_tokens", "prompt_n_tokens", "language", "detect_language",
|
||||||
|
"suppress_blank", "suppress_nst", "temperature",
|
||||||
|
"max_initial_ts", "length_penalty", "temperature_inc",
|
||||||
|
"entropy_thold", "logprob_thold", "no_speech_thold", "greedy",
|
||||||
|
"beam_search", "new_segment_callback", "new_segment_callback_user_data",
|
||||||
"progress_callback", "progress_callback_user_data",
|
"progress_callback", "progress_callback_user_data",
|
||||||
"encoder_begin_callback", "encoder_begin_callback_user_data",
|
"encoder_begin_callback", "encoder_begin_callback_user_data",
|
||||||
|
"abort_callback", "abort_callback_user_data",
|
||||||
"logits_filter_callback", "logits_filter_callback_user_data",
|
"logits_filter_callback", "logits_filter_callback_user_data",
|
||||||
"grammar_rules", "n_grammar_rules", "i_start_rule", "grammar_penalty");
|
"grammar_rules", "n_grammar_rules", "i_start_rule", "grammar_penalty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ByValue extends WhisperFullParams implements Structure.ByValue {
|
||||||
|
public ByValue() { super(); }
|
||||||
|
public ByValue(Pointer p) { super(p); }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ class WhisperCppTest {
|
|||||||
float[] floats = new float[b.length / 2];
|
float[] floats = new float[b.length / 2];
|
||||||
|
|
||||||
//WhisperFullParams params = whisper.getFullDefaultParams(WhisperSamplingStrategy.WHISPER_SAMPLING_GREEDY);
|
//WhisperFullParams params = whisper.getFullDefaultParams(WhisperSamplingStrategy.WHISPER_SAMPLING_GREEDY);
|
||||||
WhisperFullParams params = whisper.getFullDefaultParams(WhisperSamplingStrategy.WHISPER_SAMPLING_BEAM_SEARCH);
|
WhisperFullParams.ByValue params = whisper.getFullDefaultParams(WhisperSamplingStrategy.WHISPER_SAMPLING_BEAM_SEARCH);
|
||||||
params.setProgressCallback((ctx, state, progress, user_data) -> System.out.println("progress: " + progress));
|
params.setProgressCallback((ctx, state, progress, user_data) -> System.out.println("progress: " + progress));
|
||||||
params.print_progress = CBool.FALSE;
|
params.print_progress = CBool.FALSE;
|
||||||
//params.initial_prompt = "and so my fellow Americans um, like";
|
//params.initial_prompt = "and so my fellow Americans um, like";
|
||||||
|
@ -33,6 +33,9 @@ mkdir build-em && cd build-em
|
|||||||
emcmake cmake .. && make -j
|
emcmake cmake .. && make -j
|
||||||
|
|
||||||
# run test
|
# run test
|
||||||
|
node ../tests/test-whisper.js
|
||||||
|
|
||||||
|
# For Node.js versions prior to v16.4.0, experimental features need to be enabled:
|
||||||
node --experimental-wasm-threads --experimental-wasm-simd ../tests/test-whisper.js
|
node --experimental-wasm-threads --experimental-wasm-simd ../tests/test-whisper.js
|
||||||
|
|
||||||
# publish npm package
|
# publish npm package
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "whisper.cpp",
|
"name": "whisper.cpp",
|
||||||
"version": "1.7.4",
|
"version": "1.7.5",
|
||||||
"description": "Whisper speech recognition",
|
"description": "Whisper speech recognition",
|
||||||
"main": "whisper.js",
|
"main": "whisper.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
3
bindings/ruby/.gitignore
vendored
3
bindings/ruby/.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
LICENSE
|
LICENSE
|
||||||
pkg/
|
pkg/
|
||||||
lib/whisper.*
|
lib/whisper.*
|
||||||
|
ext/sources/*
|
||||||
|
!ext/sources/CMakeGraphVizOptions.cmake
|
||||||
|
ext/mkmf.log
|
||||||
|
@ -16,6 +16,18 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|||||||
|
|
||||||
$ gem install whispercpp
|
$ gem install whispercpp
|
||||||
|
|
||||||
|
You can pass build options for whisper.cpp, for instance:
|
||||||
|
|
||||||
|
$ bundle config build.whispercpp --enable-ggml-cuda
|
||||||
|
|
||||||
|
or,
|
||||||
|
|
||||||
|
$ gem install whispercpp -- --enable-ggml-cuda
|
||||||
|
|
||||||
|
See whisper.cpp's [README](https://github.com/ggml-org/whisper.cpp/blob/master/README.md) for available options. You need convert options present the README to Ruby-style options.
|
||||||
|
For boolean options like `GGML_CUDA`, the README says `-DGGML_CUDA=1`. You need strip `-D`, prepend `--enable-` for `1` or `ON` (`--disable-` for `0` or `OFF`) and make it kebab-case: `--enable-ggml-cuda`.
|
||||||
|
For options which require arguments like `CMAKE_CUDA_ARCHITECTURES`, the README says `-DCMAKE_CUDA_ARCHITECTURES="86"`. You need strip `-D`, prepend `--`, make it kebab-case, append `=` and append argument: `--cmake-cuda-architectures="86"`.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@ -228,7 +240,7 @@ The second argument `samples` may be an array, an object with `length` and `each
|
|||||||
Development
|
Development
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
% git clone https://github.com/ggerganov/whisper.cpp.git
|
% git clone https://github.com/ggml-org/whisper.cpp.git
|
||||||
% cd whisper.cpp/bindings/ruby
|
% cd whisper.cpp/bindings/ruby
|
||||||
% rake test
|
% rake test
|
||||||
|
|
||||||
@ -241,5 +253,5 @@ License
|
|||||||
|
|
||||||
The same to [whisper.cpp][].
|
The same to [whisper.cpp][].
|
||||||
|
|
||||||
[whisper.cpp]: https://github.com/ggerganov/whisper.cpp
|
[whisper.cpp]: https://github.com/ggml-org/whisper.cpp
|
||||||
[models]: https://github.com/ggerganov/whisper.cpp/tree/master/models
|
[models]: https://github.com/ggml-org/whisper.cpp/tree/master/models
|
||||||
|
@ -3,11 +3,15 @@ require "bundler/gem_tasks"
|
|||||||
require "rake/testtask"
|
require "rake/testtask"
|
||||||
require_relative "extsources"
|
require_relative "extsources"
|
||||||
|
|
||||||
|
SOURCES_DIR = "ext/sources"
|
||||||
|
|
||||||
SOURCES = FileList[]
|
SOURCES = FileList[]
|
||||||
|
|
||||||
EXTSOURCES.each do |src|
|
EXTSOURCES.each do |src|
|
||||||
basename = src.pathmap("%f")
|
basename = src.pathmap("%f")
|
||||||
dest = basename == "LICENSE" ? basename : src.pathmap("%{../..,ext}p")
|
dest = basename == "LICENSE" ? basename
|
||||||
|
: src.pathmap("%{\\.\\./\\.\\.,#{SOURCES_DIR}}p")
|
||||||
|
.pathmap("%{\\.\\./javascript,#{SOURCES_DIR}/bindings/javascript}p")
|
||||||
dir = dest.pathmap("%d")
|
dir = dest.pathmap("%d")
|
||||||
file src
|
file src
|
||||||
directory dir
|
directory dir
|
||||||
@ -18,7 +22,6 @@ EXTSOURCES.each do |src|
|
|||||||
end
|
end
|
||||||
|
|
||||||
CLEAN.include SOURCES
|
CLEAN.include SOURCES
|
||||||
CLEAN.include FileList["ext/**/*.o", "ext/**/*.metal", "ext/**/*.tmp", "ext/whisper.{so,bundle,dll}"]
|
|
||||||
|
|
||||||
SRC = FileList["ext/*.{c,cpp,h}"]
|
SRC = FileList["ext/*.{c,cpp,h}"]
|
||||||
|
|
||||||
@ -36,6 +39,20 @@ file "ext/Makefile" => SRC + ["ext/extconf.rb"] + SOURCES do |t|
|
|||||||
ruby "extconf.rb"
|
ruby "extconf.rb"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if File.exist? "ext/Makefile"
|
||||||
|
task :make_clean do
|
||||||
|
cd "ext" do
|
||||||
|
sh "make", "clean"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
task clean: :make_clean
|
||||||
|
task :make_distclean do
|
||||||
|
cd "ext" do
|
||||||
|
sh "make", "distclean"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
task clobber: :make_distclean
|
||||||
|
end
|
||||||
|
|
||||||
file SO_FILE => "ext/Makefile" do |t|
|
file SO_FILE => "ext/Makefile" do |t|
|
||||||
chdir "ext" do
|
chdir "ext" do
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
ggml/src/ggml-cpu/ggml-cpu-cpp.o: \
|
|
||||||
ggml/src/ggml-cpu/ggml-cpu.cpp \
|
|
||||||
ggml/include/ggml-backend.h \
|
|
||||||
ggml/include/ggml.h \
|
|
||||||
ggml/include/ggml-alloc.h \
|
|
||||||
ggml/src/ggml-backend-impl.h \
|
|
||||||
ggml/include/ggml-cpu.h \
|
|
||||||
ggml/src/ggml-impl.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
|
61
bindings/ruby/ext/dependencies.rb
Normal file
61
bindings/ruby/ext/dependencies.rb
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
require "tsort"
|
||||||
|
|
||||||
|
class Dependencies
|
||||||
|
def initialize(cmake, options)
|
||||||
|
@cmake = cmake
|
||||||
|
@options = options
|
||||||
|
|
||||||
|
generate_dot
|
||||||
|
@libs = parse_dot
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@libs.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def dot_path
|
||||||
|
File.join(__dir__, "build", "whisper.cpp.dot")
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_dot
|
||||||
|
system @cmake, "-S", "sources", "-B", "build", "--graphviz", dot_path, "-D", "BUILD_SHARED_LIBS=OFF", @options.to_s, exception: true
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_dot
|
||||||
|
static_lib_shape = nil
|
||||||
|
nodes = {}
|
||||||
|
depends = Hash.new {|h, k| h[k] = []}
|
||||||
|
|
||||||
|
class << depends
|
||||||
|
include TSort
|
||||||
|
alias tsort_each_node each_key
|
||||||
|
def tsort_each_child(node, &block)
|
||||||
|
fetch(node, []).each(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
File.open(dot_path).each_line do |line|
|
||||||
|
case line
|
||||||
|
when /\[\s*label\s*=\s*"Static Library"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]/
|
||||||
|
static_lib_shape = $~[:shape]
|
||||||
|
when /\A\s*"(?<node>\w+)"\s*\[\s*label\s*=\s*"(?<label>\S+)"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]\s*;\s*\z/
|
||||||
|
node = $~[:node]
|
||||||
|
label = $~[:label]
|
||||||
|
shape = $~[:shape]
|
||||||
|
nodes[node] = [label, shape]
|
||||||
|
when /\A\s*"(?<depender>\w+)"\s*->\s*"(?<dependee>\w+)"/
|
||||||
|
depender = $~[:depender]
|
||||||
|
dependee = $~[:dependee]
|
||||||
|
depends[depender] ||= []
|
||||||
|
depends[depender] << dependee
|
||||||
|
end
|
||||||
|
end
|
||||||
|
depends.tsort.filter_map {|node|
|
||||||
|
label, shape = nodes[node]
|
||||||
|
shape == static_lib_shape ? label : nil
|
||||||
|
}.collect {|lib| "lib#{lib}.a"}
|
||||||
|
.reverse
|
||||||
|
end
|
||||||
|
end
|
@ -1,206 +1,22 @@
|
|||||||
require 'mkmf'
|
require "mkmf"
|
||||||
|
require_relative "options"
|
||||||
|
require_relative "dependencies"
|
||||||
|
|
||||||
# need to use c++ compiler flags
|
cmake = find_executable("cmake") || abort
|
||||||
$CXXFLAGS << ' -std=c++17'
|
options = Options.new
|
||||||
|
have_library("gomp") rescue nil
|
||||||
|
libs = Dependencies.new(cmake, options)
|
||||||
|
|
||||||
$LDFLAGS << ' -lstdc++'
|
$INCFLAGS << " -Isources/include -Isources/ggml/include -Isources/examples"
|
||||||
|
$LOCAL_LIBS << " #{libs}"
|
||||||
|
$cleanfiles << " build #{libs}"
|
||||||
|
|
||||||
# Set to true when building binary gems
|
create_makefile "whisper" do |conf|
|
||||||
if enable_config('static-stdlib', false)
|
conf << <<~EOF
|
||||||
$LDFLAGS << ' -static-libgcc -static-libstdc++'
|
$(TARGET_SO): #{libs}
|
||||||
end
|
#{libs}: cmake-targets
|
||||||
|
cmake-targets:
|
||||||
if enable_config('march-tune-native', false)
|
#{"\t"}#{cmake} -S sources -B build -D BUILD_SHARED_LIBS=OFF -D CMAKE_ARCHIVE_OUTPUT_DIRECTORY=#{__dir__} -D CMAKE_POSITION_INDEPENDENT_CODE=ON #{options}
|
||||||
$CFLAGS << ' -march=native -mtune=native'
|
#{"\t"}#{cmake} --build build --config Release --target common whisper
|
||||||
$CXXFLAGS << ' -march=native -mtune=native'
|
EOF
|
||||||
end
|
|
||||||
|
|
||||||
if ENV['WHISPER_METAL']
|
|
||||||
$GGML_METAL ||= true
|
|
||||||
$DEPRECATE_WARNING ||= true
|
|
||||||
end
|
|
||||||
|
|
||||||
$UNAME_S = `uname -s`.chomp
|
|
||||||
$UNAME_P = `uname -p`.chomp
|
|
||||||
$UNAME_M = `uname -m`.chomp
|
|
||||||
|
|
||||||
if $UNAME_S == 'Darwin'
|
|
||||||
unless ENV['GGML_NO_METAL']
|
|
||||||
$GGML_METAL ||= true
|
|
||||||
end
|
|
||||||
$GGML_NO_OPENMP ||= true
|
|
||||||
end
|
|
||||||
|
|
||||||
if $GGML_METAL
|
|
||||||
$GGML_METAL_EMBED_LIBRARY = true
|
|
||||||
end
|
|
||||||
|
|
||||||
$MK_CPPFLAGS = '-Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -Iexamples'
|
|
||||||
$MK_CFLAGS = '-std=c11 -fPIC'
|
|
||||||
$MK_CXXFLAGS = '-std=c++17 -fPIC'
|
|
||||||
$MK_NVCCFLAGS = '-std=c++17'
|
|
||||||
$MK_LDFLAGS = ''
|
|
||||||
|
|
||||||
$OBJ_GGML = []
|
|
||||||
$OBJ_WHISPER = []
|
|
||||||
$OBJ_COMMON = []
|
|
||||||
$OBJ_SDL = []
|
|
||||||
|
|
||||||
$MK_CPPFLAGS << ' -D_XOPEN_SOURCE=600'
|
|
||||||
|
|
||||||
if $UNAME_S == 'Linux'
|
|
||||||
$MK_CPPFLAGS << ' -D_GNU_SOURCE'
|
|
||||||
end
|
|
||||||
|
|
||||||
if $UNAME_S == 'Darwin'
|
|
||||||
$MK_CPPFLAGS << ' -D_DARWIN_C_SOURCE'
|
|
||||||
end
|
|
||||||
|
|
||||||
if ENV['WHISPER_DEBUG']
|
|
||||||
$MK_CFLAGS << ' -O0 -g'
|
|
||||||
$MK_CXXFLAGS << ' -O0 -g'
|
|
||||||
$MK_LDFLAGS << ' -g'
|
|
||||||
$MK_NVCCFLAGS << ' -O0 -g'
|
|
||||||
else
|
|
||||||
$MK_CPPFLAGS << ' -DNDEBUG'
|
|
||||||
$MK_CFLAGS << ' -O3'
|
|
||||||
$MK_CXXFLAGS << ' -O3'
|
|
||||||
$MK_NVCCFLAGS << ' -O3'
|
|
||||||
end
|
|
||||||
|
|
||||||
$WARN_FLAGS =
|
|
||||||
' -Wall' <<
|
|
||||||
' -Wextra' <<
|
|
||||||
' -Wpedantic' <<
|
|
||||||
' -Wcast-qual' <<
|
|
||||||
' -Wno-unused-function'
|
|
||||||
|
|
||||||
$MK_CFLAGS <<
|
|
||||||
$WARN_FLAGS <<
|
|
||||||
' -Wshadow' <<
|
|
||||||
' -Wstrict-prototypes' <<
|
|
||||||
' -Wpointer-arith' <<
|
|
||||||
' -Wmissing-prototypes' <<
|
|
||||||
' -Werror=implicit-int' <<
|
|
||||||
' -Werror=implicit-function-declaration'
|
|
||||||
|
|
||||||
$MK_CXXFLAGS <<
|
|
||||||
$WARN_FLAGS <<
|
|
||||||
' -Wmissing-declarations' <<
|
|
||||||
' -Wmissing-noreturn'
|
|
||||||
|
|
||||||
unless `#{cc_command} #{$LDFLAGS} -Wl,-v 2>&1`.chomp.include? 'dyld-1015.7'
|
|
||||||
$MK_CPPFLAGS << ' -DHAVE_BUGGY_APPLE_LINKER'
|
|
||||||
end
|
|
||||||
|
|
||||||
if %w[Linux Darwin FreeBSD NetBSD OpenBSD Haiku].include? $UNAME_S
|
|
||||||
$MK_CFLAGS << ' -pthread'
|
|
||||||
$MK_CXXFLAGS << ' -pthread'
|
|
||||||
end
|
|
||||||
|
|
||||||
unless $_WIN32
|
|
||||||
$DSO_EXT = '.so'
|
|
||||||
else
|
|
||||||
$DSO_EXT = '.dll'
|
|
||||||
end
|
|
||||||
|
|
||||||
unless ENV['RISCV']
|
|
||||||
if %w[x86_64 i686 amd64].include? $UNAME_M
|
|
||||||
$HOST_CXXFLAGS ||= ''
|
|
||||||
|
|
||||||
$MK_CFLAGS << ' -march=native -mtune=native'
|
|
||||||
$HOST_CXXFLAGS << ' -march=native -mtune=native'
|
|
||||||
end
|
|
||||||
else
|
|
||||||
$MK_CFLAGS << ' -march=rv64gcv -mabi=lp64d'
|
|
||||||
$MK_CXXFLAGS << ' -march=rv64gcv -mabi=lp64d'
|
|
||||||
end
|
|
||||||
|
|
||||||
unless ENV['GGML_NO_ACCELERATE']
|
|
||||||
if $UNAME_S == 'Darwin'
|
|
||||||
$MK_CPPFLAGS << ' -DGGML_USE_ACCELERATE -DGGML_USE_BLAS -DGGML_BLAS_USE_ACCELERATE'
|
|
||||||
$MK_CPPFLAGS << ' -DACCELERATE_NEW_LAPACK'
|
|
||||||
$MK_CPPFLAGS << ' -DACCELERATE_LAPACK_ILP64'
|
|
||||||
$MK_LDFLAGS << ' -framework Accelerate'
|
|
||||||
$OBJ_GGML << 'ggml/src/ggml-blas/ggml-blas.o'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if ENV['GGML_OPENBLAS']
|
|
||||||
$MK_CPPFLAGS << " -DGGML_USE_BLAS #{`pkg-config --cflags-only-I openblas`.chomp}"
|
|
||||||
$MK_CFLAGS << " #{`pkg-config --cflags-only-other openblas)`.chomp}"
|
|
||||||
$MK_LDFLAGS << " #{`pkg-config --libs openblas`}"
|
|
||||||
$OBJ_GGML << 'ggml/src/ggml-blas/ggml-blas.o'
|
|
||||||
end
|
|
||||||
|
|
||||||
if ENV['GGML_OPENBLAS64']
|
|
||||||
$MK_CPPFLAGS << " -DGGML_USE_BLAS #{`pkg-config --cflags-only-I openblas64`.chomp}"
|
|
||||||
$MK_CFLAGS << " #{`pkg-config --cflags-only-other openblas64)`.chomp}"
|
|
||||||
$MK_LDFLAGS << " #{`pkg-config --libs openblas64`}"
|
|
||||||
$OBJ_GGML << 'ggml/src/ggml-blas/ggml-blas.o'
|
|
||||||
end
|
|
||||||
|
|
||||||
if $GGML_METAL
|
|
||||||
$MK_CPPFLAGS << ' -DGGML_USE_METAL'
|
|
||||||
$MK_LDFLAGS << ' -framework Foundation -framework Metal -framework MetalKit'
|
|
||||||
$OBJ_GGML << 'ggml/src/ggml-metal/ggml-metal.o'
|
|
||||||
|
|
||||||
if ENV['GGML_METAL_NDEBUG']
|
|
||||||
$MK_CPPFLAGS << ' -DGGML_METAL_NDEBUG'
|
|
||||||
end
|
|
||||||
|
|
||||||
if $GGML_METAL_EMBED_LIBRARY
|
|
||||||
$MK_CPPFLAGS << ' -DGGML_METAL_EMBED_LIBRARY'
|
|
||||||
$OBJ_GGML << 'ggml/src/ggml-metal/ggml-metal-embed.o'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
$OBJ_GGML <<
|
|
||||||
'ggml/src/ggml.o' <<
|
|
||||||
'ggml/src/ggml-alloc.o' <<
|
|
||||||
'ggml/src/ggml-backend.o' <<
|
|
||||||
'ggml/src/ggml-backend-reg.o' <<
|
|
||||||
'ggml/src/ggml-opt.o' <<
|
|
||||||
'ggml/src/ggml-quants.o' <<
|
|
||||||
'ggml/src/ggml-threading.o' <<
|
|
||||||
'ggml/src/ggml-cpu/ggml-cpu.o' <<
|
|
||||||
'ggml/src/ggml-cpu/ggml-cpu-cpp.o' <<
|
|
||||||
'ggml/src/ggml-cpu/ggml-cpu-aarch64.o' <<
|
|
||||||
'ggml/src/ggml-cpu/ggml-cpu-hbm.o' <<
|
|
||||||
'ggml/src/ggml-cpu/ggml-cpu-quants.o' <<
|
|
||||||
'ggml/src/ggml-cpu/ggml-cpu-traits.o'
|
|
||||||
|
|
||||||
$OBJ_WHISPER <<
|
|
||||||
'src/whisper.o'
|
|
||||||
|
|
||||||
$objs = $OBJ_GGML + $OBJ_WHISPER + $OBJ_COMMON + $OBJ_SDL
|
|
||||||
$objs <<
|
|
||||||
"ruby_whisper.o" <<
|
|
||||||
"ruby_whisper_context.o" <<
|
|
||||||
"ruby_whisper_transcribe.o" <<
|
|
||||||
"ruby_whisper_params.o" <<
|
|
||||||
"ruby_whisper_error.o" <<
|
|
||||||
"ruby_whisper_segment.o" <<
|
|
||||||
"ruby_whisper_model.o"
|
|
||||||
|
|
||||||
$CPPFLAGS = "#{$MK_CPPFLAGS} #{$CPPFLAGS}"
|
|
||||||
$CFLAGS = "#{$CPPFLAGS} #{$MK_CFLAGS} #{$GF_CFLAGS} #{$CFLAGS}"
|
|
||||||
$BASE_CXXFLAGS = "#{$MK_CXXFLAGS} #{$CXXFLAGS}"
|
|
||||||
$CXXFLAGS = "#{$BASE_CXXFLAGS} #{$HOST_CXXFLAGS} #{$GF_CXXFLAGS} #{$CPPFLAGS}"
|
|
||||||
$NVCCFLAGS = "#{$MK_NVCCFLAGS} #{$NVCCFLAGS}"
|
|
||||||
$LDFLAGS = "#{$MK_LDFLAGS} #{$LDFLAGS}"
|
|
||||||
|
|
||||||
create_makefile('whisper')
|
|
||||||
|
|
||||||
File.open 'Makefile', 'a' do |file|
|
|
||||||
file.puts 'include scripts/get-flags.mk'
|
|
||||||
file.puts 'include cpu.mk'
|
|
||||||
|
|
||||||
if $GGML_METAL
|
|
||||||
file.puts 'include metal.mk'
|
|
||||||
|
|
||||||
if $GGML_METAL_EMBED_LIBRARY
|
|
||||||
file.puts 'include metal-embed.mk'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
ggml/src/ggml-metal/ggml-metal-embed.o: \
|
|
||||||
ggml/src/ggml-metal/ggml-metal.metal \
|
|
||||||
ggml/src/ggml-metal/ggml-metal-impl.h \
|
|
||||||
ggml/src/ggml-common.h
|
|
||||||
@echo "Embedding Metal library"
|
|
||||||
@sed -e '/__embed_ggml-common.h__/r ggml/src/ggml-common.h' -e '/__embed_ggml-common.h__/d' < ggml/src/ggml-metal/ggml-metal.metal > ggml/src/ggml-metal/ggml-metal-embed.metal.tmp
|
|
||||||
@sed -e '/#include "ggml-metal-impl.h"/r ggml/src/ggml-metal/ggml-metal-impl.h' -e '/#include "ggml-metal-impl.h"/d' < ggml/src/ggml-metal/ggml-metal-embed.metal.tmp > ggml/src/ggml-metal/ggml-metal-embed.metal
|
|
||||||
$(eval TEMP_ASSEMBLY=$(shell mktemp -d))
|
|
||||||
@echo ".section __DATA, __ggml_metallib" > $(TEMP_ASSEMBLY)/ggml-metal-embed.s
|
|
||||||
@echo ".globl _ggml_metallib_start" >> $(TEMP_ASSEMBLY)/ggml-metal-embed.s
|
|
||||||
@echo "_ggml_metallib_start:" >> $(TEMP_ASSEMBLY)/ggml-metal-embed.s
|
|
||||||
@echo ".incbin \"ggml/src/ggml-metal/ggml-metal-embed.metal\"" >> $(TEMP_ASSEMBLY)/ggml-metal-embed.s
|
|
||||||
@echo ".globl _ggml_metallib_end" >> $(TEMP_ASSEMBLY)/ggml-metal-embed.s
|
|
||||||
@echo "_ggml_metallib_end:" >> $(TEMP_ASSEMBLY)/ggml-metal-embed.s
|
|
||||||
$(CC) $(CFLAGS) -c $(TEMP_ASSEMBLY)/ggml-metal-embed.s -o $@
|
|
||||||
@rm -f ${TEMP_ASSEMBLY}/ggml-metal-embed.s
|
|
||||||
@rmdir ${TEMP_ASSEMBLY}
|
|
@ -1,6 +0,0 @@
|
|||||||
ggml/src/ggml-metal/ggml-metal.o: \
|
|
||||||
ggml/src/ggml-metal/ggml-metal.m \
|
|
||||||
ggml/src/ggml-metal/ggml-metal-impl.h \
|
|
||||||
ggml/include/ggml-metal.h \
|
|
||||||
ggml/include/ggml.h
|
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
|
219
bindings/ruby/ext/options.rb
Normal file
219
bindings/ruby/ext/options.rb
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
class Options
|
||||||
|
def initialize
|
||||||
|
@options = {}
|
||||||
|
@pending_options = []
|
||||||
|
@ignored_options = []
|
||||||
|
|
||||||
|
configure
|
||||||
|
end
|
||||||
|
|
||||||
|
def help
|
||||||
|
@options
|
||||||
|
.collect_concat {|name, (type, value)|
|
||||||
|
option = option_name(name)
|
||||||
|
if type == :bool
|
||||||
|
["--enable-#{option}", "--disable-#{option}"]
|
||||||
|
else
|
||||||
|
"--#{option}=#{type.upcase}"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
.join($/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@options
|
||||||
|
.reject {|name, (type, value)| value.nil?}
|
||||||
|
.collect {|name, (type, value)| "-D #{name}=#{value == true ? "ON" : value == false ? "OFF" : value.shellescape}"}
|
||||||
|
.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
def cmake_options
|
||||||
|
return @cmake_options if @cmake_options
|
||||||
|
|
||||||
|
output = nil
|
||||||
|
Dir.chdir __dir__ do
|
||||||
|
output = `cmake -S sources -B build -L`
|
||||||
|
end
|
||||||
|
started = false
|
||||||
|
@cmake_options = output.lines.filter_map {|line|
|
||||||
|
if line.chomp == "-- Cache values"
|
||||||
|
started = true
|
||||||
|
next
|
||||||
|
end
|
||||||
|
next unless started
|
||||||
|
option, value = line.chomp.split("=", 2)
|
||||||
|
name, type = option.split(":", 2)
|
||||||
|
[name, type, value]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_options
|
||||||
|
cmake_options.collect {|name, type, value| name} -
|
||||||
|
@options.keys - @pending_options - @ignored_options
|
||||||
|
end
|
||||||
|
|
||||||
|
def extra_options
|
||||||
|
@options.keys + @pending_options + @ignored_options -
|
||||||
|
cmake_options.collect {|name, type, value| name}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def configure
|
||||||
|
filepath "ACCELERATE_FRAMEWORK"
|
||||||
|
ignored "BUILD_SHARED_LIBS"
|
||||||
|
ignored "BUILD_TESTING"
|
||||||
|
ignored "CMAKE_BUILD_TYPE"
|
||||||
|
ignored "CMAKE_INSTALL_PREFIX"
|
||||||
|
string "CMAKE_OSX_ARCHITECTURES"
|
||||||
|
ignored "CMAKE_OSX_DEPLOYMENT_TARGET"
|
||||||
|
string "CMAKE_OSX_SYSROOT"
|
||||||
|
filepath "FOUNDATION_LIBRARY"
|
||||||
|
bool "GGML_ACCELERATE"
|
||||||
|
bool "GGML_ALL_WARNINGS_3RD_PARTY"
|
||||||
|
bool "GGML_AMX_BF16"
|
||||||
|
bool "GGML_AMX_INT8"
|
||||||
|
bool "GGML_AMX_TILE"
|
||||||
|
bool "GGML_AVX"
|
||||||
|
bool "GGML_AVX2"
|
||||||
|
bool "GGML_AVX512"
|
||||||
|
bool "GGML_AVX512_BF16"
|
||||||
|
bool "GGML_AVX512_VBMI"
|
||||||
|
bool "GGML_AVX512_VNNI"
|
||||||
|
bool "GGML_AVX_VNNI"
|
||||||
|
ignored "GGML_BACKEND_DL"
|
||||||
|
ignored "GGML_BIN_INSTALL_DIR"
|
||||||
|
bool "GGML_BLAS"
|
||||||
|
string "GGML_BLAS_VENDOR"
|
||||||
|
bool "GGML_BMI2"
|
||||||
|
ignored "GGML_BUILD_EXAMPLES"
|
||||||
|
ignored "GGML_BUILD_TESTS"
|
||||||
|
bool "GGML_CCACHE"
|
||||||
|
filepath "GGML_CCACHE_FOUND"
|
||||||
|
bool "GGML_CPU"
|
||||||
|
bool "GGML_CPU_AARCH64"
|
||||||
|
ignored "GGML_CPU_ALL_VARIANTS"
|
||||||
|
string "GGML_CPU_ARM_ARCH"
|
||||||
|
bool "GGML_CPU_HBM"
|
||||||
|
bool "GGML_CPU_KLEIDIAI"
|
||||||
|
string "GGML_CPU_POWERPC_CPUTYPE"
|
||||||
|
bool "GGML_CUDA"
|
||||||
|
string "GGML_CUDA_COMPRESSION_MODE"
|
||||||
|
bool "GGML_CUDA_F16"
|
||||||
|
bool "GGML_CUDA_FA"
|
||||||
|
bool "GGML_CUDA_FA_ALL_QUANTS"
|
||||||
|
bool "GGML_CUDA_FORCE_CUBLAS"
|
||||||
|
bool "GGML_CUDA_FORCE_MMQ"
|
||||||
|
ignored "GGML_CUDA_GRAPHS"
|
||||||
|
bool "GGML_CUDA_NO_PEER_COPY"
|
||||||
|
bool "GGML_CUDA_NO_VMM"
|
||||||
|
string "GGML_CUDA_PEER_MAX_BATCH_SIZE"
|
||||||
|
bool "GGML_F16C"
|
||||||
|
bool "GGML_FMA"
|
||||||
|
bool "GGML_GPROF"
|
||||||
|
bool "GGML_HIP"
|
||||||
|
bool "GGML_HIP_GRAPHS"
|
||||||
|
bool "GGML_HIP_NO_VMM"
|
||||||
|
bool "GGML_HIP_ROCWMMA_FATTN"
|
||||||
|
ignored "GGML_INCLUDE_INSTALL_DIR"
|
||||||
|
bool "GGML_KOMPUTE"
|
||||||
|
bool "GGML_LASX"
|
||||||
|
ignored "GGML_LIB_INSTALL_DIR"
|
||||||
|
ignored "GGML_LLAMAFILE"
|
||||||
|
bool "GGML_LSX"
|
||||||
|
bool "GGML_LTO"
|
||||||
|
bool "GGML_METAL"
|
||||||
|
bool "GGML_METAL_EMBED_LIBRARY"
|
||||||
|
string "GGML_METAL_MACOSX_VERSION_MIN"
|
||||||
|
bool "GGML_METAL_NDEBUG"
|
||||||
|
bool "GGML_METAL_SHADER_DEBUG"
|
||||||
|
string "GGML_METAL_STD"
|
||||||
|
bool "GGML_METAL_USE_BF16"
|
||||||
|
bool "GGML_MUSA"
|
||||||
|
bool "GGML_NATIVE"
|
||||||
|
bool "GGML_OPENCL"
|
||||||
|
bool "GGML_OPENCL_EMBED_KERNELS"
|
||||||
|
bool "GGML_OPENCL_PROFILING"
|
||||||
|
string "GGML_OPENCL_TARGET_VERSION"
|
||||||
|
bool "GGML_OPENCL_USE_ADRENO_KERNELS"
|
||||||
|
bool "GGML_OPENMP"
|
||||||
|
bool "GGML_RPC"
|
||||||
|
bool "GGML_RVV"
|
||||||
|
bool "GGML_RV_ZFH"
|
||||||
|
pending "GGML_SCCACHE_FOUND"
|
||||||
|
string "GGML_SCHED_MAX_COPIES"
|
||||||
|
bool "GGML_SSE42"
|
||||||
|
ignored "GGML_STATIC"
|
||||||
|
bool "GGML_SYCL"
|
||||||
|
string "GGML_SYCL_DEVICE_ARCH"
|
||||||
|
bool "GGML_SYCL_F16"
|
||||||
|
bool "GGML_SYCL_GRAPH"
|
||||||
|
string "GGML_SYCL_TARGET"
|
||||||
|
bool "GGML_VULKAN"
|
||||||
|
bool "GGML_VULKAN_CHECK_RESULTS"
|
||||||
|
bool "GGML_VULKAN_DEBUG"
|
||||||
|
bool "GGML_VULKAN_MEMORY_DEBUG"
|
||||||
|
bool "GGML_VULKAN_PERF"
|
||||||
|
ignored "GGML_VULKAN_RUN_TESTS"
|
||||||
|
filepath "GGML_VULKAN_SHADERS_GEN_TOOLCHAIN"
|
||||||
|
bool "GGML_VULKAN_SHADER_DEBUG_INFO"
|
||||||
|
pending "GGML_VULKAN_VALIDATE"
|
||||||
|
bool "GGML_VXE"
|
||||||
|
filepath "GIT_EXE"
|
||||||
|
filepath "MATH_LIBRARY"
|
||||||
|
filepath "METALKIT_FRAMEWORK"
|
||||||
|
filepath "METAL_FRAMEWORK"
|
||||||
|
bool "WHISPER_ALL_WARNINGS"
|
||||||
|
bool "WHISPER_ALL_WARNINGS_3RD_PARTY"
|
||||||
|
ignored "WHISPER_BIN_INSTALL_DIR"
|
||||||
|
ignored "WHISPER_BUILD_EXAMPLES"
|
||||||
|
ignored "WHISPER_BUILD_SERVER"
|
||||||
|
ignored"WHISPER_BUILD_TESTS"
|
||||||
|
bool "WHISPER_COREML"
|
||||||
|
bool "WHISPER_COREML_ALLOW_FALLBACK"
|
||||||
|
ignored "WHISPER_CURL"
|
||||||
|
bool "WHISPER_FATAL_WARNINGS"
|
||||||
|
ignored "WHISPER_FFMPEG"
|
||||||
|
ignored "WHISPER_INCLUDE_INSTALL_DIR"
|
||||||
|
ignored "WHISPER_LIB_INSTALL_DIR"
|
||||||
|
bool "WHISPER_OPENVINO"
|
||||||
|
bool "WHISPER_SANITIZE_ADDRESS"
|
||||||
|
bool "WHISPER_SANITIZE_THREAD"
|
||||||
|
bool "WHISPER_SANITIZE_UNDEFINED"
|
||||||
|
ignored "WHISPER_SDL2"
|
||||||
|
pending "WHISPER_USE_SYSTEM_GGML"
|
||||||
|
end
|
||||||
|
|
||||||
|
def option_name(name)
|
||||||
|
name.downcase.gsub("_", "-")
|
||||||
|
end
|
||||||
|
|
||||||
|
def bool(name)
|
||||||
|
option = option_name(name)
|
||||||
|
value = enable_config(option)
|
||||||
|
@options[name] = [:bool, value]
|
||||||
|
end
|
||||||
|
|
||||||
|
def string(name, type=:string)
|
||||||
|
option = "--#{option_name(name)}"
|
||||||
|
value = arg_config(option)
|
||||||
|
raise "String expected for #{option}" if value == true || value&.empty?
|
||||||
|
@options[name] = [type, value]
|
||||||
|
end
|
||||||
|
|
||||||
|
def path(name)
|
||||||
|
string(name, :path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filepath(name)
|
||||||
|
string(name, :filepath)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pending(name)
|
||||||
|
@pending_options << name
|
||||||
|
end
|
||||||
|
|
||||||
|
def ignored(name)
|
||||||
|
@ignored_options << name
|
||||||
|
end
|
||||||
|
end
|
@ -19,6 +19,7 @@ typedef struct {
|
|||||||
bool diarize;
|
bool diarize;
|
||||||
ruby_whisper_callback_container *new_segment_callback_container;
|
ruby_whisper_callback_container *new_segment_callback_container;
|
||||||
ruby_whisper_callback_container *progress_callback_container;
|
ruby_whisper_callback_container *progress_callback_container;
|
||||||
|
ruby_whisper_callback_container *encoder_begin_callback_container;
|
||||||
ruby_whisper_callback_container *abort_callback_container;
|
ruby_whisper_callback_container *abort_callback_container;
|
||||||
} ruby_whisper_params;
|
} ruby_whisper_params;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
rb_define_method(cParams, #param_name, ruby_whisper_params_get_ ## param_name, 0); \
|
rb_define_method(cParams, #param_name, ruby_whisper_params_get_ ## param_name, 0); \
|
||||||
rb_define_method(cParams, #param_name "=", ruby_whisper_params_set_ ## param_name, 1);
|
rb_define_method(cParams, #param_name "=", ruby_whisper_params_set_ ## param_name, 1);
|
||||||
|
|
||||||
#define RUBY_WHISPER_PARAMS_PARAM_NAMES_COUNT 30
|
#define RUBY_WHISPER_PARAMS_PARAM_NAMES_COUNT 32
|
||||||
|
|
||||||
extern VALUE cParams;
|
extern VALUE cParams;
|
||||||
|
|
||||||
@ -63,6 +63,8 @@ static ID id_new_segment_callback;
|
|||||||
static ID id_new_segment_callback_user_data;
|
static ID id_new_segment_callback_user_data;
|
||||||
static ID id_progress_callback;
|
static ID id_progress_callback;
|
||||||
static ID id_progress_callback_user_data;
|
static ID id_progress_callback_user_data;
|
||||||
|
static ID id_encoder_begin_callback;
|
||||||
|
static ID id_encoder_begin_callback_user_data;
|
||||||
static ID id_abort_callback;
|
static ID id_abort_callback;
|
||||||
static ID id_abort_callback_user_data;
|
static ID id_abort_callback_user_data;
|
||||||
|
|
||||||
@ -126,6 +128,33 @@ static void progress_callback(struct whisper_context *ctx, struct whisper_state
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool encoder_begin_callback(struct whisper_context *ctx, struct whisper_state *state, void *user_data) {
|
||||||
|
const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data;
|
||||||
|
bool is_aborted = false;
|
||||||
|
VALUE result;
|
||||||
|
|
||||||
|
// Currently, doesn't support state because
|
||||||
|
// those require to resolve GC-related problems.
|
||||||
|
if (!NIL_P(container->callback)) {
|
||||||
|
result = rb_funcall(container->callback, id_call, 3, *container->context, Qnil, container->user_data);
|
||||||
|
if (result == Qfalse) {
|
||||||
|
is_aborted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const long callbacks_len = RARRAY_LEN(container->callbacks);
|
||||||
|
if (0 == callbacks_len) {
|
||||||
|
return !is_aborted;
|
||||||
|
}
|
||||||
|
for (int j = 0; j < callbacks_len; j++) {
|
||||||
|
VALUE cb = rb_ary_entry(container->callbacks, j);
|
||||||
|
result = rb_funcall(cb, id_call, 0);
|
||||||
|
if (result == Qfalse) {
|
||||||
|
is_aborted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !is_aborted;
|
||||||
|
}
|
||||||
|
|
||||||
static bool abort_callback(void * user_data) {
|
static bool abort_callback(void * user_data) {
|
||||||
const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data;
|
const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data;
|
||||||
if (!NIL_P(container->callback)) {
|
if (!NIL_P(container->callback)) {
|
||||||
@ -161,6 +190,12 @@ void register_callbacks(ruby_whisper_params * rwp, VALUE * context) {
|
|||||||
rwp->params.progress_callback_user_data = rwp->progress_callback_container;
|
rwp->params.progress_callback_user_data = rwp->progress_callback_container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!NIL_P(rwp->encoder_begin_callback_container->callback) || 0 != RARRAY_LEN(rwp->encoder_begin_callback_container->callbacks)) {
|
||||||
|
rwp->encoder_begin_callback_container->context = context;
|
||||||
|
rwp->params.encoder_begin_callback = encoder_begin_callback;
|
||||||
|
rwp->params.encoder_begin_callback_user_data = rwp->encoder_begin_callback_container;
|
||||||
|
}
|
||||||
|
|
||||||
if (!NIL_P(rwp->abort_callback_container->callback) || 0 != RARRAY_LEN(rwp->abort_callback_container->callbacks)) {
|
if (!NIL_P(rwp->abort_callback_container->callback) || 0 != RARRAY_LEN(rwp->abort_callback_container->callbacks)) {
|
||||||
rwp->abort_callback_container->context = context;
|
rwp->abort_callback_container->context = context;
|
||||||
rwp->params.abort_callback = abort_callback;
|
rwp->params.abort_callback = abort_callback;
|
||||||
@ -173,6 +208,7 @@ rb_whisper_params_mark(ruby_whisper_params *rwp)
|
|||||||
{
|
{
|
||||||
rb_whisper_callbcack_container_mark(rwp->new_segment_callback_container);
|
rb_whisper_callbcack_container_mark(rwp->new_segment_callback_container);
|
||||||
rb_whisper_callbcack_container_mark(rwp->progress_callback_container);
|
rb_whisper_callbcack_container_mark(rwp->progress_callback_container);
|
||||||
|
rb_whisper_callbcack_container_mark(rwp->encoder_begin_callback_container);
|
||||||
rb_whisper_callbcack_container_mark(rwp->abort_callback_container);
|
rb_whisper_callbcack_container_mark(rwp->abort_callback_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,6 +234,7 @@ ruby_whisper_params_allocate(VALUE klass)
|
|||||||
rwp->diarize = false;
|
rwp->diarize = false;
|
||||||
rwp->new_segment_callback_container = rb_whisper_callback_container_allocate();
|
rwp->new_segment_callback_container = rb_whisper_callback_container_allocate();
|
||||||
rwp->progress_callback_container = rb_whisper_callback_container_allocate();
|
rwp->progress_callback_container = rb_whisper_callback_container_allocate();
|
||||||
|
rwp->encoder_begin_callback_container = rb_whisper_callback_container_allocate();
|
||||||
rwp->abort_callback_container = rb_whisper_callback_container_allocate();
|
rwp->abort_callback_container = rb_whisper_callback_container_allocate();
|
||||||
return Data_Wrap_Struct(klass, rb_whisper_params_mark, rb_whisper_params_free, rwp);
|
return Data_Wrap_Struct(klass, rb_whisper_params_mark, rb_whisper_params_free, rwp);
|
||||||
}
|
}
|
||||||
@ -849,6 +886,57 @@ ruby_whisper_params_set_progress_callback_user_data(VALUE self, VALUE value)
|
|||||||
rwp->progress_callback_container->user_data = value;
|
rwp->progress_callback_container->user_data = value;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ruby_whisper_params_get_encoder_begin_callback(VALUE self)
|
||||||
|
{
|
||||||
|
ruby_whisper_params *rwp;
|
||||||
|
Data_Get_Struct(self, ruby_whisper_params, rwp);
|
||||||
|
return rwp->encoder_begin_callback_container->callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets encoder begin callback, called when the encoder starts.
|
||||||
|
*
|
||||||
|
* params.encoder_begin_callback = ->(context, _, user_data) {
|
||||||
|
* # ...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* call-seq:
|
||||||
|
* encoder_begin_callback = callback -> callback
|
||||||
|
*/
|
||||||
|
static VALUE
|
||||||
|
ruby_whisper_params_set_encoder_begin_callback(VALUE self, VALUE value)
|
||||||
|
{
|
||||||
|
ruby_whisper_params *rwp;
|
||||||
|
Data_Get_Struct(self, ruby_whisper_params, rwp);
|
||||||
|
rwp->encoder_begin_callback_container->callback = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
ruby_whisper_params_get_encoder_begin_callback_user_data(VALUE self)
|
||||||
|
{
|
||||||
|
ruby_whisper_params *rwp;
|
||||||
|
Data_Get_Struct(self, ruby_whisper_params, rwp);
|
||||||
|
return rwp->encoder_begin_callback_container->user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets user data passed to the last argument of encoder begin callback.
|
||||||
|
*
|
||||||
|
* call-seq:
|
||||||
|
* encoder_begin_callback_user_data = user_data -> use_data
|
||||||
|
*/
|
||||||
|
static VALUE
|
||||||
|
ruby_whisper_params_set_encoder_begin_callback_user_data(VALUE self, VALUE value)
|
||||||
|
{
|
||||||
|
ruby_whisper_params *rwp;
|
||||||
|
Data_Get_Struct(self, ruby_whisper_params, rwp);
|
||||||
|
rwp->encoder_begin_callback_container->user_data = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
ruby_whisper_params_get_abort_callback(VALUE self)
|
ruby_whisper_params_get_abort_callback(VALUE self)
|
||||||
{
|
{
|
||||||
@ -918,7 +1006,7 @@ ruby_whisper_params_initialize(int argc, VALUE *argv, VALUE self)
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
rb_get_kwargs(kw_hash, ¶m_names, 0, RUBY_WHISPER_PARAMS_PARAM_NAMES_COUNT, &values);
|
rb_get_kwargs(kw_hash, param_names, 0, RUBY_WHISPER_PARAMS_PARAM_NAMES_COUNT, values);
|
||||||
Data_Get_Struct(self, ruby_whisper_params, rwp);
|
Data_Get_Struct(self, ruby_whisper_params, rwp);
|
||||||
|
|
||||||
for (i = 0; i < RUBY_WHISPER_PARAMS_PARAM_NAMES_COUNT; i++) {
|
for (i = 0; i < RUBY_WHISPER_PARAMS_PARAM_NAMES_COUNT; i++) {
|
||||||
@ -958,6 +1046,8 @@ ruby_whisper_params_initialize(int argc, VALUE *argv, VALUE self)
|
|||||||
SET_PARAM_IF_SAME(new_segment_callback_user_data)
|
SET_PARAM_IF_SAME(new_segment_callback_user_data)
|
||||||
SET_PARAM_IF_SAME(progress_callback)
|
SET_PARAM_IF_SAME(progress_callback)
|
||||||
SET_PARAM_IF_SAME(progress_callback_user_data)
|
SET_PARAM_IF_SAME(progress_callback_user_data)
|
||||||
|
SET_PARAM_IF_SAME(encoder_begin_callback)
|
||||||
|
SET_PARAM_IF_SAME(encoder_begin_callback_user_data)
|
||||||
SET_PARAM_IF_SAME(abort_callback)
|
SET_PARAM_IF_SAME(abort_callback)
|
||||||
SET_PARAM_IF_SAME(abort_callback_user_data)
|
SET_PARAM_IF_SAME(abort_callback_user_data)
|
||||||
}
|
}
|
||||||
@ -1008,6 +1098,26 @@ ruby_whisper_params_on_progress(VALUE self)
|
|||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hook called when the encoder starts.
|
||||||
|
*
|
||||||
|
* whisper.on_encoder_begin do
|
||||||
|
* # ...
|
||||||
|
* end
|
||||||
|
*
|
||||||
|
* call-seq:
|
||||||
|
* on_encoder_begin { ... }
|
||||||
|
*/
|
||||||
|
static VALUE
|
||||||
|
ruby_whisper_params_on_encoder_begin(VALUE self)
|
||||||
|
{
|
||||||
|
ruby_whisper_params *rws;
|
||||||
|
Data_Get_Struct(self, ruby_whisper_params, rws);
|
||||||
|
const VALUE blk = rb_block_proc();
|
||||||
|
rb_ary_push(rws->encoder_begin_callback_container->callbacks, blk);
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Call block to determine whether abort or not. Return +true+ when you want to abort.
|
* Call block to determine whether abort or not. Return +true+ when you want to abort.
|
||||||
*
|
*
|
||||||
@ -1068,10 +1178,13 @@ init_ruby_whisper_params(VALUE *mWhisper)
|
|||||||
DEFINE_PARAM(new_segment_callback_user_data, 25)
|
DEFINE_PARAM(new_segment_callback_user_data, 25)
|
||||||
DEFINE_PARAM(progress_callback, 26)
|
DEFINE_PARAM(progress_callback, 26)
|
||||||
DEFINE_PARAM(progress_callback_user_data, 27)
|
DEFINE_PARAM(progress_callback_user_data, 27)
|
||||||
DEFINE_PARAM(abort_callback, 28)
|
DEFINE_PARAM(encoder_begin_callback, 28)
|
||||||
DEFINE_PARAM(abort_callback_user_data, 29)
|
DEFINE_PARAM(encoder_begin_callback_user_data, 29)
|
||||||
|
DEFINE_PARAM(abort_callback, 30)
|
||||||
|
DEFINE_PARAM(abort_callback_user_data, 31)
|
||||||
|
|
||||||
rb_define_method(cParams, "on_new_segment", ruby_whisper_params_on_new_segment, 0);
|
rb_define_method(cParams, "on_new_segment", ruby_whisper_params_on_new_segment, 0);
|
||||||
rb_define_method(cParams, "on_progress", ruby_whisper_params_on_progress, 0);
|
rb_define_method(cParams, "on_progress", ruby_whisper_params_on_progress, 0);
|
||||||
|
rb_define_method(cParams, "on_encoder_begin", ruby_whisper_params_on_encoder_begin, 0);
|
||||||
rb_define_method(cParams, "abort_on", ruby_whisper_params_abort_on, 0);
|
rb_define_method(cParams, "abort_on", ruby_whisper_params_abort_on, 0);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include <ruby.h>
|
#include <ruby.h>
|
||||||
#include "ruby_whisper.h"
|
#include "ruby_whisper.h"
|
||||||
#define DR_WAV_IMPLEMENTATION
|
#include "common-whisper.h"
|
||||||
#include "dr_wav.h"
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -47,94 +46,20 @@ ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self) {
|
|||||||
std::vector<float> pcmf32; // mono-channel F32 PCM
|
std::vector<float> pcmf32; // mono-channel F32 PCM
|
||||||
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
|
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
|
||||||
|
|
||||||
// WAV input - this is directly from main.cpp example
|
if (!read_audio_data(fname_inp, pcmf32, pcmf32s, rwp->diarize)) {
|
||||||
{
|
fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str());
|
||||||
drwav wav;
|
return self;
|
||||||
std::vector<uint8_t> wav_data; // used for pipe input from stdin
|
|
||||||
|
|
||||||
if (fname_inp == "-") {
|
|
||||||
{
|
|
||||||
uint8_t buf[1024];
|
|
||||||
while (true) {
|
|
||||||
const size_t n = fread(buf, 1, sizeof(buf), stdin);
|
|
||||||
if (n == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
wav_data.insert(wav_data.end(), buf, buf + n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drwav_init_memory(&wav, wav_data.data(), wav_data.size(), nullptr) == false) {
|
|
||||||
fprintf(stderr, "error: failed to open WAV file from stdin\n");
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, wav_data.size());
|
|
||||||
} else if (drwav_init_file(&wav, fname_inp.c_str(), nullptr) == false) {
|
|
||||||
fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str());
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wav.channels != 1 && wav.channels != 2) {
|
|
||||||
fprintf(stderr, "WAV file '%s' must be mono or stereo\n", fname_inp.c_str());
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rwp->diarize && wav.channels != 2 && rwp->params.print_timestamps == false) {
|
|
||||||
fprintf(stderr, "WAV file '%s' must be stereo for diarization and timestamps have to be enabled\n", fname_inp.c_str());
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wav.sampleRate != WHISPER_SAMPLE_RATE) {
|
|
||||||
fprintf(stderr, "WAV file '%s' must be %i kHz\n", fname_inp.c_str(), WHISPER_SAMPLE_RATE/1000);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wav.bitsPerSample != 16) {
|
|
||||||
fprintf(stderr, "WAV file '%s' must be 16-bit\n", fname_inp.c_str());
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint64_t n = wav_data.empty() ? wav.totalPCMFrameCount : wav_data.size()/(wav.channels*wav.bitsPerSample/8);
|
|
||||||
|
|
||||||
std::vector<int16_t> pcm16;
|
|
||||||
pcm16.resize(n*wav.channels);
|
|
||||||
drwav_read_pcm_frames_s16(&wav, n, pcm16.data());
|
|
||||||
drwav_uninit(&wav);
|
|
||||||
|
|
||||||
// convert to mono, float
|
|
||||||
pcmf32.resize(n);
|
|
||||||
if (wav.channels == 1) {
|
|
||||||
for (uint64_t i = 0; i < n; i++) {
|
|
||||||
pcmf32[i] = float(pcm16[i])/32768.0f;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (uint64_t i = 0; i < n; i++) {
|
|
||||||
pcmf32[i] = float((int32_t)pcm16[2*i] + pcm16[2*i + 1])/65536.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rwp->diarize) {
|
|
||||||
// convert to stereo, float
|
|
||||||
pcmf32s.resize(2);
|
|
||||||
|
|
||||||
pcmf32s[0].resize(n);
|
|
||||||
pcmf32s[1].resize(n);
|
|
||||||
for (uint64_t i = 0; i < n; i++) {
|
|
||||||
pcmf32s[0][i] = float(pcm16[2*i])/32768.0f;
|
|
||||||
pcmf32s[1][i] = float(pcm16[2*i + 1])/32768.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
{
|
// Commented out because it is work in progress
|
||||||
static bool is_aborted = false; // NOTE: this should be atomic to avoid data race
|
// {
|
||||||
|
// static bool is_aborted = false; // NOTE: this should be atomic to avoid data race
|
||||||
|
|
||||||
rwp->params.encoder_begin_callback = [](struct whisper_context * /*ctx*/, struct whisper_state * /*state*/, void * user_data) {
|
// rwp->params.encoder_begin_callback = [](struct whisper_context * /*ctx*/, struct whisper_state * /*state*/, void * user_data) {
|
||||||
bool is_aborted = *(bool*)user_data;
|
// bool is_aborted = *(bool*)user_data;
|
||||||
return !is_aborted;
|
// return !is_aborted;
|
||||||
};
|
// };
|
||||||
rwp->params.encoder_begin_callback_user_data = &is_aborted;
|
// rwp->params.encoder_begin_callback_user_data = &is_aborted;
|
||||||
}
|
// }
|
||||||
|
|
||||||
register_callbacks(rwp, &self);
|
register_callbacks(rwp, &self);
|
||||||
|
|
||||||
|
8
bindings/ruby/ext/sources/CMakeGraphVizOptions.cmake
Normal file
8
bindings/ruby/ext/sources/CMakeGraphVizOptions.cmake
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
set(GRAPHVIZ_EXECUTABLES FALSE)
|
||||||
|
set(GRAPHVIZ_STATIC_LIBS TRUE)
|
||||||
|
set(GRAPHVIZ_SHARED_LIBS FALSE)
|
||||||
|
set(GRAPHVIZ_MODULE_LIBS FALSE)
|
||||||
|
set(GRAPHVIZ_INTERFACE_LIBS FALSE)
|
||||||
|
set(GRAPHVIZ_OBJECT_LIBS FALSE)
|
||||||
|
set(GRAPHVIZ_UNKNOWN_LIBS FALSE)
|
||||||
|
set(GRAPHVIZ_GENERATE_DEPENDERS FALSE)
|
@ -1,6 +1,34 @@
|
|||||||
require "yaml"
|
ignored_dirs = %w[
|
||||||
|
.devops
|
||||||
|
examples/wchess/wchess.wasm
|
||||||
|
examples/whisper.android
|
||||||
|
examples/whisper.android.java
|
||||||
|
examples/whisper.objc
|
||||||
|
examples/whisper.swiftui
|
||||||
|
grammars
|
||||||
|
models
|
||||||
|
samples
|
||||||
|
scripts
|
||||||
|
]
|
||||||
|
ignored_files = %w[
|
||||||
|
AUTHORS
|
||||||
|
Makefile
|
||||||
|
README.md
|
||||||
|
README_sycl.md
|
||||||
|
.gitignore
|
||||||
|
.gitmodules
|
||||||
|
whisper.nvim
|
||||||
|
twitch.sh
|
||||||
|
yt-wsp.sh
|
||||||
|
]
|
||||||
|
|
||||||
sources = `git ls-files -z ../..`.split("\x0")
|
EXTSOURCES =
|
||||||
paths = YAML.load_file("../../.github/workflows/bindings-ruby.yml")[true]["push"]["paths"]
|
`git ls-files -z ../..`.split("\x0")
|
||||||
paths.delete "bindings/ruby/**"
|
.select {|file|
|
||||||
EXTSOURCES = (Dir.glob(paths, base: "../..").collect {|path| "../../#{path}"} << "../../LICENSE") & sources
|
basename = File.basename(file)
|
||||||
|
|
||||||
|
ignored_dirs.all? {|dir| !file.start_with?("../../#{dir}")} &&
|
||||||
|
!ignored_files.include?(basename) &&
|
||||||
|
(file.start_with?("../..") || file.start_with?("../javascript")) &&
|
||||||
|
(!file.start_with?("../../.github/") || basename == "bindings-ruby.yml")
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ module Whisper
|
|||||||
when /darwin/
|
when /darwin/
|
||||||
Pathname(Dir.home)/"Library/Caches"
|
Pathname(Dir.home)/"Library/Caches"
|
||||||
else
|
else
|
||||||
ENV.key?("XDG_CACHE_HOME") ? ENV["XDG_CACHE_HOME"] : Pathname(Dir.home)/".cache"
|
ENV.key?("XDG_CACHE_HOME") ? Pathname(ENV["XDG_CACHE_HOME"]) : Pathname(Dir.home)/".cache"
|
||||||
end
|
end
|
||||||
base/"whisper.cpp"
|
base/"whisper.cpp"
|
||||||
end
|
end
|
||||||
@ -53,8 +53,10 @@ module Whisper
|
|||||||
http.request request do |response|
|
http.request request do |response|
|
||||||
case response
|
case response
|
||||||
when Net::HTTPNotModified
|
when Net::HTTPNotModified
|
||||||
# noop
|
# noop
|
||||||
when Net::HTTPOK
|
when Net::HTTPOK
|
||||||
|
return if !response.key?("last-modified") && cache_path.exist?
|
||||||
|
|
||||||
download response
|
download response
|
||||||
when Net::HTTPRedirection
|
when Net::HTTPRedirection
|
||||||
request URI(response["location"]), headers
|
request URI(response["location"]), headers
|
||||||
@ -68,7 +70,7 @@ module Whisper
|
|||||||
rescue => err
|
rescue => err
|
||||||
if cache_path.exist?
|
if cache_path.exist?
|
||||||
warn err
|
warn err
|
||||||
# Use cache file
|
# Use cache file
|
||||||
else
|
else
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
@ -7,6 +7,7 @@ module Whisper
|
|||||||
type log_callback = ^(Integer level, String message, Object user_data) -> void
|
type log_callback = ^(Integer level, String message, Object user_data) -> void
|
||||||
type new_segment_callback = ^(Whisper::Context, void, Integer n_new, Object user_data) -> void
|
type new_segment_callback = ^(Whisper::Context, void, Integer n_new, Object user_data) -> void
|
||||||
type progress_callback = ^(Whisper::Context, void, Integer progress, Object user_data) -> void
|
type progress_callback = ^(Whisper::Context, void, Integer progress, Object user_data) -> void
|
||||||
|
type encoder_begin_callback = ^(Whisper::Context, void, Object user_data) -> void
|
||||||
type abort_callback = ^(Whisper::Context, void, Object user_data) -> boolish
|
type abort_callback = ^(Whisper::Context, void, Object user_data) -> boolish
|
||||||
|
|
||||||
LOG_LEVEL_NONE: Integer
|
LOG_LEVEL_NONE: Integer
|
||||||
@ -23,9 +24,20 @@ module Whisper
|
|||||||
def self.log_set: (log_callback, Object? user_data) -> log_callback
|
def self.log_set: (log_callback, Object? user_data) -> log_callback
|
||||||
|
|
||||||
class Context
|
class Context
|
||||||
def self.new: (string | _ToPath | ::URI::HTTP) -> instance
|
def self.new: (path | ::URI::HTTP) -> instance
|
||||||
|
|
||||||
|
# transcribe a single file
|
||||||
|
# can emit to a block results
|
||||||
|
#
|
||||||
|
# params = Whisper::Params.new
|
||||||
|
# params.duration = 60_000
|
||||||
|
# whisper.transcribe "path/to/audio.wav", params do |text|
|
||||||
|
# puts text
|
||||||
|
# end
|
||||||
|
#
|
||||||
def transcribe: (string, Params) -> self
|
def transcribe: (string, Params) -> self
|
||||||
| (string, Params) { (String) -> void } -> self
|
| (string, Params) { (String) -> void } -> self
|
||||||
|
|
||||||
def model_n_vocab: () -> Integer
|
def model_n_vocab: () -> Integer
|
||||||
def model_n_audio_ctx: () -> Integer
|
def model_n_audio_ctx: () -> Integer
|
||||||
def model_n_audio_state: () -> Integer
|
def model_n_audio_state: () -> Integer
|
||||||
@ -34,19 +46,72 @@ module Whisper
|
|||||||
def model_n_mels: () -> Integer
|
def model_n_mels: () -> Integer
|
||||||
def model_ftype: () -> Integer
|
def model_ftype: () -> Integer
|
||||||
def model_type: () -> String
|
def model_type: () -> String
|
||||||
|
|
||||||
|
# Yields each Whisper::Segment:
|
||||||
|
#
|
||||||
|
# whisper.transcribe("path/to/audio.wav", params)
|
||||||
|
# whisper.each_segment do |segment|
|
||||||
|
# puts segment.text
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Returns an Enumerator if no block given:
|
||||||
|
#
|
||||||
|
# whisper.transcribe("path/to/audio.wav", params)
|
||||||
|
# enum = whisper.each_segment
|
||||||
|
# enum.to_a # => [#<Whisper::Segment>, ...]
|
||||||
|
#
|
||||||
def each_segment: { (Segment) -> void } -> void
|
def each_segment: { (Segment) -> void } -> void
|
||||||
| () -> Enumerator[Segment]
|
| () -> Enumerator[Segment]
|
||||||
|
|
||||||
def model: () -> Model
|
def model: () -> Model
|
||||||
def full_get_segment: (Integer nth) -> Segment
|
def full_get_segment: (Integer nth) -> Segment
|
||||||
def full_n_segments: () -> Integer
|
def full_n_segments: () -> Integer
|
||||||
|
|
||||||
|
# Language ID, which can be converted to string by Whisper.lang_str and Whisper.lang_str_full.
|
||||||
|
#
|
||||||
def full_lang_id: () -> Integer
|
def full_lang_id: () -> Integer
|
||||||
|
|
||||||
|
# Start time of a segment indexed by +segment_index+ in centiseconds (10 times milliseconds).
|
||||||
|
#
|
||||||
|
# full_get_segment_t0(3) # => 1668 (16680 ms)
|
||||||
|
#
|
||||||
def full_get_segment_t0: (Integer) -> Integer
|
def full_get_segment_t0: (Integer) -> Integer
|
||||||
|
|
||||||
|
# End time of a segment indexed by +segment_index+ in centiseconds (10 times milliseconds).
|
||||||
|
#
|
||||||
|
# full_get_segment_t1(3) # => 1668 (16680 ms)
|
||||||
|
#
|
||||||
def full_get_segment_t1: (Integer) -> Integer
|
def full_get_segment_t1: (Integer) -> Integer
|
||||||
|
|
||||||
|
# Whether the next segment indexed by +segment_index+ is predicated as a speaker turn.
|
||||||
|
#
|
||||||
|
# full_get_segment_speacker_turn_next(3) # => true
|
||||||
|
#
|
||||||
def full_get_segment_speaker_turn_next: (Integer) -> (true | false)
|
def full_get_segment_speaker_turn_next: (Integer) -> (true | false)
|
||||||
|
|
||||||
|
# Text of a segment indexed by +segment_index+.
|
||||||
|
#
|
||||||
|
# full_get_segment_text(3) # => "ask not what your country can do for you, ..."
|
||||||
|
#
|
||||||
def full_get_segment_text: (Integer) -> String
|
def full_get_segment_text: (Integer) -> String
|
||||||
|
|
||||||
def full_get_segment_no_speech_prob: (Integer) -> Float
|
def full_get_segment_no_speech_prob: (Integer) -> Float
|
||||||
|
|
||||||
|
# Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text
|
||||||
|
# Not thread safe for same context
|
||||||
|
# Uses the specified decoding strategy to obtain the text.
|
||||||
|
#
|
||||||
|
# The second argument +samples+ must be an array of samples, respond to :length, or be a MemoryView of an array of float. It must be 32 bit float PCM audio data.
|
||||||
|
#
|
||||||
def full: (Params, Array[Float] samples, ?Integer n_samples) -> self
|
def full: (Params, Array[Float] samples, ?Integer n_samples) -> self
|
||||||
| (Params, _Samples, ?Integer n_samples) -> self
|
| (Params, _Samples, ?Integer n_samples) -> self
|
||||||
|
|
||||||
|
# Split the input audio in chunks and process each chunk separately using whisper_full_with_state()
|
||||||
|
# Result is stored in the default state of the context
|
||||||
|
# Not thread safe if executed in parallel on the same context.
|
||||||
|
# It seems this approach can offer some speedup in some cases.
|
||||||
|
# However, the transcription accuracy can be worse at the beginning and end of each chunk.
|
||||||
|
#
|
||||||
def full_parallel: (Params, Array[Float], ?Integer n_samples) -> self
|
def full_parallel: (Params, Array[Float], ?Integer n_samples) -> self
|
||||||
| (Params, _Samples, ?Integer n_samples) -> self
|
| (Params, _Samples, ?Integer n_samples) -> self
|
||||||
| (Params, _Samples, ?Integer? n_samples, Integer n_processors) -> self
|
| (Params, _Samples, ?Integer? n_samples, Integer n_processors) -> self
|
||||||
@ -82,71 +147,223 @@ module Whisper
|
|||||||
?new_segment_callback_user_data: Object,
|
?new_segment_callback_user_data: Object,
|
||||||
?progress_callback: progress_callback,
|
?progress_callback: progress_callback,
|
||||||
?progress_callback_user_data: Object,
|
?progress_callback_user_data: Object,
|
||||||
|
?encoder_begin_callback: encoder_begin_callback,
|
||||||
|
?encoder_begin_callback_user_data: Object,
|
||||||
?abort_callback: abort_callback,
|
?abort_callback: abort_callback,
|
||||||
?abort_callback_user_data: Object
|
?abort_callback_user_data: Object
|
||||||
) -> instance
|
) -> instance
|
||||||
|
|
||||||
|
# params.language = "auto" | "en", etc...
|
||||||
|
#
|
||||||
def language=: (String) -> String # TODO: Enumerate lang names
|
def language=: (String) -> String # TODO: Enumerate lang names
|
||||||
|
|
||||||
def language: () -> String
|
def language: () -> String
|
||||||
def translate=: (boolish) -> boolish
|
def translate=: (boolish) -> boolish
|
||||||
def translate: () -> (true | false)
|
def translate: () -> (true | false)
|
||||||
def no_context=: (boolish) -> boolish
|
def no_context=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, does not use past transcription (if any) as initial prompt for the decoder.
|
||||||
|
#
|
||||||
def no_context: () -> (true | false)
|
def no_context: () -> (true | false)
|
||||||
|
|
||||||
def single_segment=: (boolish) -> boolish
|
def single_segment=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, forces single segment output (useful for streaming).
|
||||||
|
#
|
||||||
def single_segment: () -> (true | false)
|
def single_segment: () -> (true | false)
|
||||||
|
|
||||||
def print_special=: (boolish) -> boolish
|
def print_special=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, prints special tokens (e.g. <SOT>, <EOT>, <BEG>, etc.).
|
||||||
|
#
|
||||||
def print_special: () -> (true | false)
|
def print_special: () -> (true | false)
|
||||||
|
|
||||||
def print_progress=: (boolish) -> boolish
|
def print_progress=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, prints progress information.
|
||||||
|
#
|
||||||
def print_progress: () -> (true | false)
|
def print_progress: () -> (true | false)
|
||||||
|
|
||||||
def print_realtime=: (boolish) -> boolish
|
def print_realtime=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, prints results from within whisper.cpp. (avoid it, use callback instead)
|
||||||
|
#
|
||||||
def print_realtime: () -> (true | false)
|
def print_realtime: () -> (true | false)
|
||||||
|
|
||||||
|
# If true, prints timestamps for each text segment when printing realtime.
|
||||||
|
#
|
||||||
def print_timestamps=: (boolish) -> boolish
|
def print_timestamps=: (boolish) -> boolish
|
||||||
|
|
||||||
def print_timestamps: () -> (true | false)
|
def print_timestamps: () -> (true | false)
|
||||||
|
|
||||||
def suppress_blank=: (boolish) -> boolish
|
def suppress_blank=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, suppresses blank outputs.
|
||||||
|
#
|
||||||
def suppress_blank: () -> (true | false)
|
def suppress_blank: () -> (true | false)
|
||||||
|
|
||||||
def suppress_nst=: (boolish) -> boolish
|
def suppress_nst=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, suppresses non-speech-tokens.
|
||||||
|
#
|
||||||
def suppress_nst: () -> (true | false)
|
def suppress_nst: () -> (true | false)
|
||||||
|
|
||||||
def token_timestamps=: (boolish) -> boolish
|
def token_timestamps=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, enables token-level timestamps.
|
||||||
|
#
|
||||||
def token_timestamps: () -> (true | false)
|
def token_timestamps: () -> (true | false)
|
||||||
|
|
||||||
def split_on_word=: (boolish) -> boolish
|
def split_on_word=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, split on word rather than on token (when used with max_len).
|
||||||
|
#
|
||||||
def split_on_word: () -> (true | false)
|
def split_on_word: () -> (true | false)
|
||||||
|
|
||||||
def initial_prompt=: (_ToS) -> _ToS
|
def initial_prompt=: (_ToS) -> _ToS
|
||||||
|
|
||||||
|
# Tokens to provide to the whisper decoder as initial prompt
|
||||||
|
# these are prepended to any existing text context from a previous call
|
||||||
|
# use whisper_tokenize() to convert text to tokens.
|
||||||
|
# Maximum of whisper_n_text_ctx()/2 tokens are used (typically 224).
|
||||||
|
#
|
||||||
def initial_prompt: () -> (String | nil)
|
def initial_prompt: () -> (String | nil)
|
||||||
|
|
||||||
def diarize=: (boolish) -> boolish
|
def diarize=: (boolish) -> boolish
|
||||||
|
|
||||||
|
# If true, enables diarization.
|
||||||
|
#
|
||||||
def diarize: () -> (true | false)
|
def diarize: () -> (true | false)
|
||||||
|
|
||||||
def offset=: (Integer) -> Integer
|
def offset=: (Integer) -> Integer
|
||||||
|
|
||||||
|
# Start offset in ms.
|
||||||
|
#
|
||||||
def offset: () -> Integer
|
def offset: () -> Integer
|
||||||
|
|
||||||
def duration=: (Integer) -> Integer
|
def duration=: (Integer) -> Integer
|
||||||
|
|
||||||
|
# Audio duration to process in ms.
|
||||||
|
#
|
||||||
def duration: () -> Integer
|
def duration: () -> Integer
|
||||||
|
|
||||||
def max_text_tokens=: (Integer) -> Integer
|
def max_text_tokens=: (Integer) -> Integer
|
||||||
|
|
||||||
|
# Max tokens to use from past text as prompt for the decoder.
|
||||||
|
#
|
||||||
def max_text_tokens: () -> Integer
|
def max_text_tokens: () -> Integer
|
||||||
|
|
||||||
def temperature=: (Float) -> Float
|
def temperature=: (Float) -> Float
|
||||||
def temperature: () -> Float
|
def temperature: () -> Float
|
||||||
def max_initial_ts=: (Float) -> Float
|
def max_initial_ts=: (Float) -> Float
|
||||||
|
|
||||||
|
# See https://github.com/openai/whisper/blob/f82bc59f5ea234d4b97fb2860842ed38519f7e65/whisper/decoding.py#L97
|
||||||
|
#
|
||||||
def max_initial_ts: () -> Float
|
def max_initial_ts: () -> Float
|
||||||
|
|
||||||
def length_penalty=: (Float) -> Float
|
def length_penalty=: (Float) -> Float
|
||||||
def length_penalty: () -> Float
|
def length_penalty: () -> Float
|
||||||
def temperature_inc=: (Float) -> Float
|
def temperature_inc=: (Float) -> Float
|
||||||
def temperature_inc: () -> Float
|
def temperature_inc: () -> Float
|
||||||
def entropy_thold=: (Float) -> Float
|
def entropy_thold=: (Float) -> Float
|
||||||
|
|
||||||
|
# Similar to OpenAI's "compression_ratio_threshold"
|
||||||
|
#
|
||||||
def entropy_thold: () -> Float
|
def entropy_thold: () -> Float
|
||||||
|
|
||||||
def logprob_thold=: (Float) -> Float
|
def logprob_thold=: (Float) -> Float
|
||||||
def logprob_thold: () -> Float
|
def logprob_thold: () -> Float
|
||||||
def no_speech_thold=: (Float) -> Float
|
def no_speech_thold=: (Float) -> Float
|
||||||
def no_speech_thold: () -> Float
|
def no_speech_thold: () -> Float
|
||||||
|
|
||||||
|
# Sets new segment callback, called for every newly generated text segment.
|
||||||
|
#
|
||||||
|
# params.new_segment_callback = ->(context, _, n_new, user_data) {
|
||||||
|
# # ...
|
||||||
|
# }
|
||||||
|
#
|
||||||
def new_segment_callback=: (new_segment_callback) -> new_segment_callback
|
def new_segment_callback=: (new_segment_callback) -> new_segment_callback
|
||||||
def new_segment_callback: () -> (new_segment_callback | nil)
|
def new_segment_callback: () -> (new_segment_callback | nil)
|
||||||
|
|
||||||
|
# Sets user data passed to the last argument of new segment callback.
|
||||||
|
#
|
||||||
def new_segment_callback_user_data=: (Object) -> Object
|
def new_segment_callback_user_data=: (Object) -> Object
|
||||||
|
|
||||||
def new_segment_callback_user_data: () -> Object
|
def new_segment_callback_user_data: () -> Object
|
||||||
|
|
||||||
|
# Sets progress callback, called on each progress update.
|
||||||
|
#
|
||||||
|
# params.new_segment_callback = ->(context, _, progress, user_data) {
|
||||||
|
# # ...
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# +progress+ is an Integer between 0 and 100.
|
||||||
|
#
|
||||||
def progress_callback=: (progress_callback) -> progress_callback
|
def progress_callback=: (progress_callback) -> progress_callback
|
||||||
|
|
||||||
def progress_callback: () -> (progress_callback | nil)
|
def progress_callback: () -> (progress_callback | nil)
|
||||||
|
|
||||||
|
# Sets user data passed to the last argument of progress callback.
|
||||||
|
#
|
||||||
def progress_callback_user_data=: (Object) -> Object
|
def progress_callback_user_data=: (Object) -> Object
|
||||||
|
|
||||||
def progress_callback_user_data: () -> Object
|
def progress_callback_user_data: () -> Object
|
||||||
|
|
||||||
|
# Sets encoder begin callback, called when the encoder starts.
|
||||||
|
#
|
||||||
|
def encoder_begin_callback=: (encoder_begin_callback) -> encoder_begin_callback
|
||||||
|
|
||||||
|
def encoder_begin_callback: () -> (encoder_begin_callback | nil)
|
||||||
|
|
||||||
|
# Sets user data passed to the last argument of encoder begin callback.
|
||||||
|
#
|
||||||
|
def encoder_begin_callback_user_data=: (Object) -> Object
|
||||||
|
|
||||||
|
def encoder_begin_callback_user_data: () -> Object
|
||||||
|
|
||||||
|
# Sets abort callback, called to check if the process should be aborted.
|
||||||
|
#
|
||||||
|
# params.abort_callback = ->(user_data) {
|
||||||
|
# # ...
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
#
|
||||||
def abort_callback=: (abort_callback) -> abort_callback
|
def abort_callback=: (abort_callback) -> abort_callback
|
||||||
|
|
||||||
def abort_callback: () -> (abort_callback | nil)
|
def abort_callback: () -> (abort_callback | nil)
|
||||||
|
|
||||||
|
# Sets user data passed to the last argument of abort callback.
|
||||||
|
#
|
||||||
def abort_callback_user_data=: (Object) -> Object
|
def abort_callback_user_data=: (Object) -> Object
|
||||||
|
|
||||||
def abort_callback_user_data: () -> Object
|
def abort_callback_user_data: () -> Object
|
||||||
|
|
||||||
|
# Hook called on new segment. Yields each Whisper::Segment.
|
||||||
|
#
|
||||||
|
# whisper.on_new_segment do |segment|
|
||||||
|
# # ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
def on_new_segment: { (Segment) -> void } -> void
|
def on_new_segment: { (Segment) -> void } -> void
|
||||||
|
|
||||||
|
# Hook called on progress update. Yields each progress Integer between 0 and 100.
|
||||||
|
#
|
||||||
def on_progress: { (Integer progress) -> void } -> void
|
def on_progress: { (Integer progress) -> void } -> void
|
||||||
|
|
||||||
|
# Hook called on encoder starts.
|
||||||
|
#
|
||||||
|
def on_encoder_begin: { () -> void } -> void
|
||||||
|
|
||||||
|
# Call block to determine whether abort or not. Return +true+ when you want to abort.
|
||||||
|
#
|
||||||
|
# params.abort_on do
|
||||||
|
# if some_condition
|
||||||
|
# true # abort
|
||||||
|
# else
|
||||||
|
# false # continue
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
def abort_on: { (Object user_data) -> boolish } -> void
|
def abort_on: { (Object user_data) -> boolish } -> void
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -167,16 +384,24 @@ module Whisper
|
|||||||
def type: () -> String
|
def type: () -> String
|
||||||
|
|
||||||
class URI
|
class URI
|
||||||
def self.new: (string | ::URI::HTTP) -> self
|
def self.new: (string | ::URI::HTTP) -> instance
|
||||||
def to_path: -> String
|
def to_path: -> String
|
||||||
def clear_cache: -> void
|
def clear_cache: -> void
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Segment
|
class Segment
|
||||||
|
# Start time in milliseconds.
|
||||||
|
#
|
||||||
def start_time: () -> Integer
|
def start_time: () -> Integer
|
||||||
|
|
||||||
|
# End time in milliseconds.
|
||||||
|
#
|
||||||
def end_time: () -> Integer
|
def end_time: () -> Integer
|
||||||
|
|
||||||
|
# Whether the next segment is predicted as a speaker turn.
|
||||||
def speaker_next_turn?: () -> (true | false)
|
def speaker_next_turn?: () -> (true | false)
|
||||||
|
|
||||||
def text: () -> String
|
def text: () -> String
|
||||||
def no_speech_prob: () -> Float
|
def no_speech_prob: () -> Float
|
||||||
end
|
end
|
||||||
|
@ -6,9 +6,9 @@ class TestBase < Test::Unit::TestCase
|
|||||||
AUDIO = File.join(__dir__, "..", "..", "..", "samples", "jfk.wav")
|
AUDIO = File.join(__dir__, "..", "..", "..", "samples", "jfk.wav")
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
attr_reader :whisper
|
def whisper
|
||||||
|
return @whisper if @whisper
|
||||||
|
|
||||||
def startup
|
|
||||||
@whisper = Whisper::Context.new("base.en")
|
@whisper = Whisper::Context.new("base.en")
|
||||||
params = Whisper::Params.new
|
params = Whisper::Params.new
|
||||||
params.print_timestamps = false
|
params.print_timestamps = false
|
||||||
@ -21,4 +21,15 @@ class TestBase < Test::Unit::TestCase
|
|||||||
def whisper
|
def whisper
|
||||||
self.class.whisper
|
self.class.whisper
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module BuildOptions
|
||||||
|
load "ext/options.rb", self
|
||||||
|
Options.include self
|
||||||
|
|
||||||
|
def enable_config(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def arg_config(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -25,7 +25,7 @@ class TestCallback < TestBase
|
|||||||
assert start_time >= 0
|
assert start_time >= 0
|
||||||
assert_kind_of Integer, end_time
|
assert_kind_of Integer, end_time
|
||||||
assert end_time > 0
|
assert end_time > 0
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, text if i_segment == 0
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, text) if i_segment == 0
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +111,48 @@ class TestCallback < TestBase
|
|||||||
assert_equal 100, last
|
assert_equal 100, last
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_encoder_begin_callback
|
||||||
|
i = 0
|
||||||
|
@params.encoder_begin_callback = ->(context, state, user_data) {
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
@whisper.transcribe(@audio, @params)
|
||||||
|
assert i > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_encoder_begin_callback_abort
|
||||||
|
logs = []
|
||||||
|
Whisper.log_set -> (level, buffer, user_data) {
|
||||||
|
logs << buffer if level == Whisper::LOG_LEVEL_ERROR
|
||||||
|
}, logs
|
||||||
|
@params.encoder_begin_callback = ->(context, state, user_data) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@whisper.transcribe(@audio, @params)
|
||||||
|
assert_match(/encoder_begin_callback returned false - aborting/, logs.join)
|
||||||
|
Whisper.log_set ->(level, buffer, user_data) {}, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_encoder_begin_callback_user_data
|
||||||
|
udata = Object.new
|
||||||
|
@params.encoder_begin_callback_user_data = udata
|
||||||
|
yielded = nil
|
||||||
|
@params.encoder_begin_callback = ->(context, state, user_data) {
|
||||||
|
yielded = user_data
|
||||||
|
}
|
||||||
|
@whisper.transcribe(@audio, @params)
|
||||||
|
assert_same udata, yielded
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_on_encoder_begin
|
||||||
|
i = 0
|
||||||
|
@params.on_encoder_begin do
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
@whisper.transcribe(@audio, @params)
|
||||||
|
assert i > 0
|
||||||
|
end
|
||||||
|
|
||||||
def test_abort_callback
|
def test_abort_callback
|
||||||
i = 0
|
i = 0
|
||||||
@params.abort_callback = ->(user_data) {
|
@params.abort_callback = ->(user_data) {
|
||||||
@ -145,9 +187,9 @@ class TestCallback < TestBase
|
|||||||
|
|
||||||
def test_abort_on
|
def test_abort_on
|
||||||
do_abort = false
|
do_abort = false
|
||||||
aborted_from_callback = false
|
_aborted_from_callback = false
|
||||||
@params.on_new_segment do |segment|
|
@params.on_new_segment do |segment|
|
||||||
do_abort = true if segment.text.match? /ask/
|
do_abort = true if segment.text.match?(/ask/)
|
||||||
end
|
end
|
||||||
i = 0
|
i = 0
|
||||||
@params.abort_on do
|
@params.abort_on do
|
||||||
|
@ -4,7 +4,7 @@ class TestError < TestBase
|
|||||||
def test_error
|
def test_error
|
||||||
error = Whisper::Error.new(-2)
|
error = Whisper::Error.new(-2)
|
||||||
assert_equal "failed to compute log mel spectrogram", error.message
|
assert_equal "failed to compute log mel spectrogram", error.message
|
||||||
assert_equal -2, error.code
|
assert_equal(-2, error.code)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unknown_error
|
def test_unknown_error
|
||||||
@ -14,7 +14,7 @@ class TestError < TestBase
|
|||||||
|
|
||||||
def test_non_int_code
|
def test_non_int_code
|
||||||
assert_raise TypeError do
|
assert_raise TypeError do
|
||||||
error = Whisper::Error.new("non int")
|
_error = Whisper::Error.new("non int")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -21,11 +21,26 @@ class TestPackage < TestBase
|
|||||||
match_data = `rake -Tbuild`.match(/(whispercpp-(.+)\.gem)/)
|
match_data = `rake -Tbuild`.match(/(whispercpp-(.+)\.gem)/)
|
||||||
filename = match_data[1]
|
filename = match_data[1]
|
||||||
version = match_data[2]
|
version = match_data[2]
|
||||||
basename = "whisper.#{RbConfig::CONFIG["DLEXT"]}"
|
|
||||||
Dir.mktmpdir do |dir|
|
Dir.mktmpdir do |dir|
|
||||||
system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{filename.shellescape}", exception: true
|
system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{filename.shellescape}", exception: true
|
||||||
assert_path_exist File.join(dir, "gems/whispercpp-#{version}/lib", basename)
|
assert_installed dir, version
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def assert_installed(dir, version)
|
||||||
|
assert_path_exist File.join(dir, "gems/whispercpp-#{version}/lib", "whisper.#{RbConfig::CONFIG["DLEXT"]}")
|
||||||
|
assert_path_exist File.join(dir, "gems/whispercpp-#{version}/LICENSE")
|
||||||
|
assert_path_not_exist File.join(dir, "gems/whispercpp-#{version}/ext/build")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_build_options
|
||||||
|
options = BuildOptions::Options.new
|
||||||
|
assert_empty options.missing_options
|
||||||
|
if ENV["TEST_EXTRA_OPTIONS"] == "1"
|
||||||
|
assert_empty options.extra_options
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -162,7 +162,7 @@ class TestParams < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_length_penalty
|
def test_length_penalty
|
||||||
assert_equal -1.0, @params.length_penalty
|
assert_equal(-1.0, @params.length_penalty)
|
||||||
@params.length_penalty = 0.5
|
@params.length_penalty = 0.5
|
||||||
assert_equal 0.5, @params.length_penalty
|
assert_equal 0.5, @params.length_penalty
|
||||||
end
|
end
|
||||||
@ -180,9 +180,9 @@ class TestParams < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_logprob_thold
|
def test_logprob_thold
|
||||||
assert_in_delta -1.0, @params.logprob_thold
|
assert_in_delta(-1.0, @params.logprob_thold)
|
||||||
@params.logprob_thold = -0.5
|
@params.logprob_thold = -0.5
|
||||||
assert_in_delta -0.5, @params.logprob_thold
|
assert_in_delta(-0.5, @params.logprob_thold)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_no_speech_thold
|
def test_no_speech_thold
|
||||||
|
@ -49,13 +49,13 @@ class TestSegment < TestBase
|
|||||||
if index == 0
|
if index == 0
|
||||||
seg = segment
|
seg = segment
|
||||||
assert_equal 0, segment.start_time
|
assert_equal 0, segment.start_time
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, segment.text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, segment.text)
|
||||||
end
|
end
|
||||||
index += 1
|
index += 1
|
||||||
end
|
end
|
||||||
whisper.transcribe(AUDIO, params)
|
whisper.transcribe(AUDIO, params)
|
||||||
assert_equal 0, seg.start_time
|
assert_equal 0, seg.start_time
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, seg.text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, seg.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_on_new_segment_twice
|
def test_on_new_segment_twice
|
||||||
|
@ -16,7 +16,7 @@ class TestWhisper < TestBase
|
|||||||
params.print_timestamps = false
|
params.print_timestamps = false
|
||||||
|
|
||||||
@whisper.transcribe(AUDIO, params) {|text|
|
@whisper.transcribe(AUDIO, params) {|text|
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, text)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class TestWhisper < TestBase
|
|||||||
def test_full_get_segment
|
def test_full_get_segment
|
||||||
segment = whisper.full_get_segment(0)
|
segment = whisper.full_get_segment(0)
|
||||||
assert_equal 0, segment.start_time
|
assert_equal 0, segment.start_time
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, segment.text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, segment.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_get_segment_t0
|
def test_full_get_segment_t0
|
||||||
@ -59,7 +59,7 @@ class TestWhisper < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_full_get_segment_text
|
def test_full_get_segment_text
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, whisper.full_get_segment_text(0)
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, whisper.full_get_segment_text(0))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_get_segment_no_speech_prob
|
def test_full_get_segment_no_speech_prob
|
||||||
@ -134,14 +134,14 @@ class TestWhisper < TestBase
|
|||||||
@whisper.full(@params, @samples, @samples.length)
|
@whisper.full(@params, @samples, @samples.length)
|
||||||
|
|
||||||
assert_equal 1, @whisper.full_n_segments
|
assert_equal 1, @whisper.full_n_segments
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_without_length
|
def test_full_without_length
|
||||||
@whisper.full(@params, @samples)
|
@whisper.full(@params, @samples)
|
||||||
|
|
||||||
assert_equal 1, @whisper.full_n_segments
|
assert_equal 1, @whisper.full_n_segments
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_enumerator
|
def test_full_enumerator
|
||||||
@ -149,7 +149,7 @@ class TestWhisper < TestBase
|
|||||||
@whisper.full(@params, samples, @samples.length)
|
@whisper.full(@params, samples, @samples.length)
|
||||||
|
|
||||||
assert_equal 1, @whisper.full_n_segments
|
assert_equal 1, @whisper.full_n_segments
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_enumerator_without_length
|
def test_full_enumerator_without_length
|
||||||
@ -171,26 +171,28 @@ class TestWhisper < TestBase
|
|||||||
@whisper.full(@params, samples)
|
@whisper.full(@params, samples)
|
||||||
|
|
||||||
assert_equal 1, @whisper.full_n_segments
|
assert_equal 1, @whisper.full_n_segments
|
||||||
assert_match /ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text
|
assert_match(/ask not what your country can do for you, ask what you can do for your country/, @whisper.each_segment.first.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_parallel
|
def test_full_parallel
|
||||||
@whisper.full_parallel(@params, @samples, @samples.length, Etc.nprocessors)
|
nprocessors = 2
|
||||||
|
@whisper.full_parallel(@params, @samples, @samples.length, nprocessors)
|
||||||
|
|
||||||
assert_equal Etc.nprocessors, @whisper.full_n_segments
|
assert_equal nprocessors, @whisper.full_n_segments
|
||||||
text = @whisper.each_segment.collect(&:text).join
|
text = @whisper.each_segment.collect(&:text).join
|
||||||
assert_match /ask what you can do/i, text
|
assert_match(/ask what you can do/i, text)
|
||||||
assert_match /for your country/i, text
|
assert_match(/for your country/i, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_parallel_with_memory_view
|
def test_full_parallel_with_memory_view
|
||||||
|
nprocessors = 2
|
||||||
samples = JFKReader.new(AUDIO)
|
samples = JFKReader.new(AUDIO)
|
||||||
@whisper.full_parallel(@params, samples, nil, Etc.nprocessors)
|
@whisper.full_parallel(@params, samples, nil, nprocessors)
|
||||||
|
|
||||||
assert_equal Etc.nprocessors, @whisper.full_n_segments
|
assert_equal nprocessors, @whisper.full_n_segments
|
||||||
text = @whisper.each_segment.collect(&:text).join
|
text = @whisper.each_segment.collect(&:text).join
|
||||||
assert_match /ask what you can do/i, text
|
assert_match(/ask what you can do/i, text)
|
||||||
assert_match /for your country/i, text
|
assert_match(/for your country/i, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_parallel_without_length_and_n_processors
|
def test_full_parallel_without_length_and_n_processors
|
||||||
@ -198,17 +200,18 @@ class TestWhisper < TestBase
|
|||||||
|
|
||||||
assert_equal 1, @whisper.full_n_segments
|
assert_equal 1, @whisper.full_n_segments
|
||||||
text = @whisper.each_segment.collect(&:text).join
|
text = @whisper.each_segment.collect(&:text).join
|
||||||
assert_match /ask what you can do/i, text
|
assert_match(/ask what you can do/i, text)
|
||||||
assert_match /for your country/i, text
|
assert_match(/for your country/i, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_parallel_without_length
|
def test_full_parallel_without_length
|
||||||
@whisper.full_parallel(@params, @samples, nil, Etc.nprocessors)
|
nprocessors = 2
|
||||||
|
@whisper.full_parallel(@params, @samples, nil, nprocessors)
|
||||||
|
|
||||||
assert_equal Etc.nprocessors, @whisper.full_n_segments
|
assert_equal nprocessors, @whisper.full_n_segments
|
||||||
text = @whisper.each_segment.collect(&:text).join
|
text = @whisper.each_segment.collect(&:text).join
|
||||||
assert_match /ask what you can do/i, text
|
assert_match(/ask what you can do/i, text)
|
||||||
assert_match /for your country/i, text
|
assert_match(/for your country/i, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_full_parallel_without_n_processors
|
def test_full_parallel_without_n_processors
|
||||||
@ -216,8 +219,8 @@ class TestWhisper < TestBase
|
|||||||
|
|
||||||
assert_equal 1, @whisper.full_n_segments
|
assert_equal 1, @whisper.full_n_segments
|
||||||
text = @whisper.each_segment.collect(&:text).join
|
text = @whisper.each_segment.collect(&:text).join
|
||||||
assert_match /ask what you can do/i, text
|
assert_match(/ask what you can do/i, text)
|
||||||
assert_match /for your country/i, text
|
assert_match(/for your country/i, text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,8 +3,8 @@ require_relative "extsources"
|
|||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "whispercpp"
|
s.name = "whispercpp"
|
||||||
s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
|
s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
|
||||||
s.version = '1.3.1'
|
s.version = '1.3.2'
|
||||||
s.date = '2024-12-19'
|
s.date = '2025-05-11'
|
||||||
s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
|
s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
|
||||||
s.email = 'todd.fisher@gmail.com'
|
s.email = 'todd.fisher@gmail.com'
|
||||||
s.extra_rdoc_files = ['LICENSE', 'README.md']
|
s.extra_rdoc_files = ['LICENSE', 'README.md']
|
||||||
@ -15,7 +15,8 @@ Gem::Specification.new do |s|
|
|||||||
if s.extra_rdoc_files.include?(basename)
|
if s.extra_rdoc_files.include?(basename)
|
||||||
basename
|
basename
|
||||||
else
|
else
|
||||||
file.sub("../..", "ext")
|
file.sub("../..", "ext/sources")
|
||||||
|
.sub("../javascript", "ext/sources/bindings/javascript")
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ Gem::Specification.new do |s|
|
|||||||
s.required_ruby_version = '>= 3.1.0'
|
s.required_ruby_version = '>= 3.1.0'
|
||||||
|
|
||||||
#### Documentation and testing.
|
#### Documentation and testing.
|
||||||
s.homepage = 'https://github.com/ggerganov/whisper.cpp'
|
s.homepage = 'https://github.com/ggml-org/whisper.cpp'
|
||||||
s.rdoc_options = ['--main', 'README.md']
|
s.rdoc_options = ['--main', 'README.md']
|
||||||
|
|
||||||
|
|
||||||
|
547
build-xcframework.sh
Executable file
547
build-xcframework.sh
Executable file
@ -0,0 +1,547 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Options
|
||||||
|
IOS_MIN_OS_VERSION=16.4
|
||||||
|
MACOS_MIN_OS_VERSION=13.3
|
||||||
|
VISIONOS_MIN_OS_VERSION=1.0
|
||||||
|
TVOS_MIN_OS_VERSION=16.4
|
||||||
|
|
||||||
|
BUILD_SHARED_LIBS=OFF
|
||||||
|
WHISPER_BUILD_EXAMPLES=OFF
|
||||||
|
WHISPER_BUILD_TESTS=OFF
|
||||||
|
WHISPER_BUILD_SERVER=OFF
|
||||||
|
GGML_METAL=ON
|
||||||
|
GGML_METAL_EMBED_LIBRARY=ON
|
||||||
|
GGML_BLAS_DEFAULT=ON
|
||||||
|
GGML_METAL_USE_BF16=ON
|
||||||
|
GGML_OPENMP=OFF
|
||||||
|
|
||||||
|
COMMON_C_FLAGS="-Wno-macro-redefined -Wno-shorten-64-to-32 -Wno-unused-command-line-argument -g"
|
||||||
|
COMMON_CXX_FLAGS="-Wno-macro-redefined -Wno-shorten-64-to-32 -Wno-unused-command-line-argument -g"
|
||||||
|
|
||||||
|
# Common options for all builds
|
||||||
|
COMMON_CMAKE_ARGS=(
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY=""
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT="dwarf-with-dsym"
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS=YES
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_COPY_PHASE_STRIP=NO
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_STRIP_INSTALLED_PRODUCT=NO
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml
|
||||||
|
-DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}
|
||||||
|
-DWHISPER_BUILD_EXAMPLES=${WHISPER_BUILD_EXAMPLES}
|
||||||
|
-DWHISPER_BUILD_TESTS=${WHISPER_BUILD_TESTS}
|
||||||
|
-DWHISPER_BUILD_SERVER=${WHISPER_BUILD_SERVER}
|
||||||
|
-DGGML_METAL_EMBED_LIBRARY=${GGML_METAL_EMBED_LIBRARY}
|
||||||
|
-DGGML_BLAS_DEFAULT=${GGML_BLAS_DEFAULT}
|
||||||
|
-DGGML_METAL=${GGML_METAL}
|
||||||
|
-DGGML_METAL_USE_BF16=${GGML_METAL_USE_BF16}
|
||||||
|
-DGGML_NATIVE=OFF
|
||||||
|
-DGGML_OPENMP=${GGML_OPENMP}
|
||||||
|
)
|
||||||
|
|
||||||
|
XCODE_VERSION=$(xcodebuild -version 2>/dev/null | head -n1 | awk '{ print $2 }')
|
||||||
|
MAJOR_VERSION=$(echo $XCODE_VERSION | cut -d. -f1)
|
||||||
|
MINOR_VERSION=$(echo $XCODE_VERSION | cut -d. -f2)
|
||||||
|
echo "Detected Xcode version: $XCODE_VERSION"
|
||||||
|
|
||||||
|
check_required_tool() {
|
||||||
|
local tool=$1
|
||||||
|
local install_message=$2
|
||||||
|
|
||||||
|
if ! command -v $tool &> /dev/null; then
|
||||||
|
echo "Error: $tool is required but not found."
|
||||||
|
echo "$install_message"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
echo "Checking for required tools..."
|
||||||
|
check_required_tool "cmake" "Please install CMake 3.28.0 or later (brew install cmake)"
|
||||||
|
check_required_tool "xcodebuild" "Please install Xcode and Xcode Command Line Tools (xcode-select --install)"
|
||||||
|
check_required_tool "libtool" "Please install libtool which should be available with Xcode Command Line Tools (CLT). Make sure Xcode CLT is installed (xcode-select --install)"
|
||||||
|
check_required_tool "dsymutil" "Please install Xcode and Xcode Command Line Tools (xcode-select --install)"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
## Clean up previous builds
|
||||||
|
rm -rf build-apple
|
||||||
|
rm -rf build-ios-sim
|
||||||
|
rm -rf build-ios-device
|
||||||
|
rm -rf build-macos
|
||||||
|
rm -rf build-visionos
|
||||||
|
rm -rf build-visionos-sim
|
||||||
|
rm -rf build-tvos-sim
|
||||||
|
rm -rf build-tvos-device
|
||||||
|
|
||||||
|
# Setup the xcframework build directory structure
|
||||||
|
setup_framework_structure() {
|
||||||
|
local build_dir=$1
|
||||||
|
local min_os_version=$2
|
||||||
|
local platform=$3 # "ios", "macos", "visionos", or "tvos"
|
||||||
|
local framework_name="whisper"
|
||||||
|
|
||||||
|
echo "Creating ${platform}-style framework structure for ${build_dir}"
|
||||||
|
|
||||||
|
if [[ "$platform" == "macos" ]]; then
|
||||||
|
# macOS versioned structure uses versioned directories
|
||||||
|
mkdir -p ${build_dir}/framework/${framework_name}.framework/Versions/A/Headers
|
||||||
|
mkdir -p ${build_dir}/framework/${framework_name}.framework/Versions/A/Modules
|
||||||
|
mkdir -p ${build_dir}/framework/${framework_name}.framework/Versions/A/Resources
|
||||||
|
|
||||||
|
# Create symbolic links
|
||||||
|
ln -sf A ${build_dir}/framework/${framework_name}.framework/Versions/Current
|
||||||
|
ln -sf Versions/Current/Headers ${build_dir}/framework/${framework_name}.framework/Headers
|
||||||
|
ln -sf Versions/Current/Modules ${build_dir}/framework/${framework_name}.framework/Modules
|
||||||
|
ln -sf Versions/Current/Resources ${build_dir}/framework/${framework_name}.framework/Resources
|
||||||
|
ln -sf Versions/Current/${framework_name} ${build_dir}/framework/${framework_name}.framework/${framework_name}
|
||||||
|
|
||||||
|
# Set header and module paths
|
||||||
|
local header_path=${build_dir}/framework/${framework_name}.framework/Versions/A/Headers/
|
||||||
|
local module_path=${build_dir}/framework/${framework_name}.framework/Versions/A/Modules/
|
||||||
|
else
|
||||||
|
# iOS/VisionOS/tvOS use a flat structure
|
||||||
|
mkdir -p ${build_dir}/framework/${framework_name}.framework/Headers
|
||||||
|
mkdir -p ${build_dir}/framework/${framework_name}.framework/Modules
|
||||||
|
|
||||||
|
# Remove any existing structure to ensure clean build
|
||||||
|
rm -rf ${build_dir}/framework/${framework_name}.framework/Versions
|
||||||
|
|
||||||
|
# Set header and module paths
|
||||||
|
local header_path=${build_dir}/framework/${framework_name}.framework/Headers/
|
||||||
|
local module_path=${build_dir}/framework/${framework_name}.framework/Modules/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy all required headers (common for all platforms)
|
||||||
|
cp include/whisper.h ${header_path}
|
||||||
|
cp ggml/include/ggml.h ${header_path}
|
||||||
|
cp ggml/include/ggml-alloc.h ${header_path}
|
||||||
|
cp ggml/include/ggml-backend.h ${header_path}
|
||||||
|
cp ggml/include/ggml-metal.h ${header_path}
|
||||||
|
cp ggml/include/ggml-cpu.h ${header_path}
|
||||||
|
cp ggml/include/ggml-blas.h ${header_path}
|
||||||
|
cp ggml/include/gguf.h ${header_path}
|
||||||
|
|
||||||
|
# Create module map (common for all platforms)
|
||||||
|
cat > ${module_path}module.modulemap << EOF
|
||||||
|
framework module whisper {
|
||||||
|
header "whisper.h"
|
||||||
|
header "ggml.h"
|
||||||
|
header "ggml-alloc.h"
|
||||||
|
header "ggml-backend.h"
|
||||||
|
header "ggml-metal.h"
|
||||||
|
header "ggml-cpu.h"
|
||||||
|
header "ggml-blas.h"
|
||||||
|
header "gguf.h"
|
||||||
|
|
||||||
|
link "c++"
|
||||||
|
link framework "Accelerate"
|
||||||
|
link framework "Metal"
|
||||||
|
link framework "Foundation"
|
||||||
|
|
||||||
|
export *
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Platform-specific settings for Info.plist
|
||||||
|
local platform_name=""
|
||||||
|
local sdk_name=""
|
||||||
|
local supported_platform=""
|
||||||
|
|
||||||
|
case "$platform" in
|
||||||
|
"ios")
|
||||||
|
platform_name="iphoneos"
|
||||||
|
sdk_name="iphoneos${min_os_version}"
|
||||||
|
supported_platform="iPhoneOS"
|
||||||
|
local plist_path="${build_dir}/framework/${framework_name}.framework/Info.plist"
|
||||||
|
local device_family=' <key>UIDeviceFamily</key>
|
||||||
|
<array>
|
||||||
|
<integer>1</integer>
|
||||||
|
<integer>2</integer>
|
||||||
|
</array>'
|
||||||
|
;;
|
||||||
|
"macos")
|
||||||
|
platform_name="macosx"
|
||||||
|
sdk_name="macosx${min_os_version}"
|
||||||
|
supported_platform="MacOSX"
|
||||||
|
local plist_path="${build_dir}/framework/${framework_name}.framework/Versions/A/Resources/Info.plist"
|
||||||
|
local device_family=""
|
||||||
|
;;
|
||||||
|
"visionos")
|
||||||
|
platform_name="xros"
|
||||||
|
sdk_name="xros${min_os_version}"
|
||||||
|
supported_platform="XRPlatform"
|
||||||
|
local plist_path="${build_dir}/framework/${framework_name}.framework/Info.plist"
|
||||||
|
local device_family=""
|
||||||
|
;;
|
||||||
|
"tvos")
|
||||||
|
platform_name="appletvos"
|
||||||
|
sdk_name="appletvos${min_os_version}"
|
||||||
|
supported_platform="AppleTVOS"
|
||||||
|
local plist_path="${build_dir}/framework/${framework_name}.framework/Info.plist"
|
||||||
|
local device_family=' <key>UIDeviceFamily</key>
|
||||||
|
<array>
|
||||||
|
<integer>3</integer>
|
||||||
|
</array>'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Create Info.plist
|
||||||
|
cat > ${plist_path} << EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>whisper</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>org.ggml.whisper</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>whisper</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>${min_os_version}</string>
|
||||||
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
|
<array>
|
||||||
|
<string>${supported_platform}</string>
|
||||||
|
</array>${device_family}
|
||||||
|
<key>DTPlatformName</key>
|
||||||
|
<string>${platform_name}</string>
|
||||||
|
<key>DTSDKName</key>
|
||||||
|
<string>${sdk_name}</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create dynamic libraries from static libraries.
|
||||||
|
combine_static_libraries() {
|
||||||
|
local build_dir="$1"
|
||||||
|
local release_dir="$2"
|
||||||
|
local platform="$3" # "ios", "macos", "visionos", or "tvos"
|
||||||
|
local is_simulator="$4"
|
||||||
|
local base_dir="$(pwd)"
|
||||||
|
local framework_name="whisper"
|
||||||
|
|
||||||
|
# Determine output path based on platform
|
||||||
|
local output_lib=""
|
||||||
|
if [[ "$platform" == "macos" ]]; then
|
||||||
|
# macOS uses versioned structure
|
||||||
|
output_lib="${build_dir}/framework/${framework_name}.framework/Versions/A/${framework_name}"
|
||||||
|
else
|
||||||
|
# iOS, visionOS, and tvOS use a directory flat structure
|
||||||
|
output_lib="${build_dir}/framework/${framework_name}.framework/${framework_name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local libs=(
|
||||||
|
"${base_dir}/${build_dir}/src/${release_dir}/libwhisper.a"
|
||||||
|
"${base_dir}/${build_dir}/ggml/src/${release_dir}/libggml.a"
|
||||||
|
"${base_dir}/${build_dir}/ggml/src/${release_dir}/libggml-base.a"
|
||||||
|
"${base_dir}/${build_dir}/ggml/src/${release_dir}/libggml-cpu.a"
|
||||||
|
"${base_dir}/${build_dir}/ggml/src/ggml-metal/${release_dir}/libggml-metal.a"
|
||||||
|
"${base_dir}/${build_dir}/ggml/src/ggml-blas/${release_dir}/libggml-blas.a"
|
||||||
|
)
|
||||||
|
if [[ "$platform" == "macos" || "$platform" == "ios" ]]; then
|
||||||
|
echo "Adding libwhisper.coreml library to the build."
|
||||||
|
libs+=(
|
||||||
|
"${base_dir}/${build_dir}/src/${release_dir}/libwhisper.coreml.a"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create temporary directory for processing
|
||||||
|
local temp_dir="${base_dir}/${build_dir}/temp"
|
||||||
|
echo "Creating temporary directory: ${temp_dir}"
|
||||||
|
mkdir -p "${temp_dir}"
|
||||||
|
|
||||||
|
# Since we have multiple architectures libtool will find object files that do not
|
||||||
|
# match the target architecture. We suppress these warnings.
|
||||||
|
libtool -static -o "${temp_dir}/combined.a" "${libs[@]}" 2> /dev/null
|
||||||
|
|
||||||
|
# Determine SDK, architectures, and install_name based on platform and simulator flag.
|
||||||
|
local sdk=""
|
||||||
|
local archs=""
|
||||||
|
local min_version_flag=""
|
||||||
|
local install_name=""
|
||||||
|
local frameworks="-framework Foundation -framework Metal -framework Accelerate"
|
||||||
|
|
||||||
|
case "$platform" in
|
||||||
|
"ios")
|
||||||
|
if [[ "$is_simulator" == "true" ]]; then
|
||||||
|
sdk="iphonesimulator"
|
||||||
|
archs="arm64 x86_64"
|
||||||
|
min_version_flag="-mios-simulator-version-min=${IOS_MIN_OS_VERSION}"
|
||||||
|
else
|
||||||
|
sdk="iphoneos"
|
||||||
|
archs="arm64"
|
||||||
|
min_version_flag="-mios-version-min=${IOS_MIN_OS_VERSION}"
|
||||||
|
fi
|
||||||
|
install_name="@rpath/whisper.framework/whisper"
|
||||||
|
frameworks+=" -framework CoreML"
|
||||||
|
;;
|
||||||
|
"macos")
|
||||||
|
sdk="macosx"
|
||||||
|
archs="arm64 x86_64"
|
||||||
|
min_version_flag="-mmacosx-version-min=${MACOS_MIN_OS_VERSION}"
|
||||||
|
install_name="@rpath/whisper.framework/Versions/Current/whisper"
|
||||||
|
frameworks+=" -framework CoreML"
|
||||||
|
;;
|
||||||
|
"visionos")
|
||||||
|
if [[ "$is_simulator" == "true" ]]; then
|
||||||
|
sdk="xrsimulator"
|
||||||
|
archs="arm64 x86_64"
|
||||||
|
min_version_flag="-mtargetos=xros${VISIONOS_MIN_OS_VERSION}-simulator"
|
||||||
|
else
|
||||||
|
sdk="xros"
|
||||||
|
archs="arm64"
|
||||||
|
min_version_flag="-mtargetos=xros${VISIONOS_MIN_OS_VERSION}"
|
||||||
|
fi
|
||||||
|
# Use flat structure for visionOS, same as iOS
|
||||||
|
install_name="@rpath/whisper.framework/whisper"
|
||||||
|
;;
|
||||||
|
"tvos")
|
||||||
|
if [[ "$is_simulator" == "true" ]]; then
|
||||||
|
sdk="appletvsimulator"
|
||||||
|
archs="arm64 x86_64"
|
||||||
|
min_version_flag="-mtvos-simulator-version-min=${TVOS_MIN_OS_VERSION}"
|
||||||
|
else
|
||||||
|
sdk="appletvos"
|
||||||
|
archs="arm64"
|
||||||
|
min_version_flag="-mtvos-version-min=${TVOS_MIN_OS_VERSION}"
|
||||||
|
fi
|
||||||
|
install_name="@rpath/whisper.framework/whisper"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Build architecture flags
|
||||||
|
local arch_flags=""
|
||||||
|
for arch in $archs; do
|
||||||
|
arch_flags+=" -arch $arch"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create dynamic library
|
||||||
|
echo "Creating dynamic library for ${platform}."
|
||||||
|
xcrun -sdk $sdk clang++ -dynamiclib \
|
||||||
|
-isysroot $(xcrun --sdk $sdk --show-sdk-path) \
|
||||||
|
$arch_flags \
|
||||||
|
$min_version_flag \
|
||||||
|
-Wl,-force_load,"${temp_dir}/combined.a" \
|
||||||
|
$frameworks \
|
||||||
|
-install_name "$install_name" \
|
||||||
|
-o "${base_dir}/${output_lib}"
|
||||||
|
|
||||||
|
# Platform-specific post-processing for device builds
|
||||||
|
if [[ "$is_simulator" == "false" ]]; then
|
||||||
|
if command -v xcrun vtool &>/dev/null; then
|
||||||
|
case "$platform" in
|
||||||
|
"ios")
|
||||||
|
echo "Marking binary as a framework binary for iOS..."
|
||||||
|
xcrun vtool -set-build-version ios ${IOS_MIN_OS_VERSION} ${IOS_MIN_OS_VERSION} -replace \
|
||||||
|
-output "${base_dir}/${output_lib}" "${base_dir}/${output_lib}"
|
||||||
|
;;
|
||||||
|
"visionos")
|
||||||
|
echo "Marking binary as a framework binary for visionOS..."
|
||||||
|
if [[ "$MAJOR_VERSION" -gt 16 ]] || [[ "$MAJOR_VERSION" -eq 16 && "$MINOR_VERSION" -gt 2 ]]; then
|
||||||
|
echo "Xcode version greater than 16.2, using visionOS."
|
||||||
|
VISION_OS_BUILD_VERSION="visionos"
|
||||||
|
else
|
||||||
|
echo "Xcode version less than or equal to 16.2, using xros."
|
||||||
|
VISION_OS_BUILD_VERSION="xros"
|
||||||
|
fi
|
||||||
|
xcrun vtool -set-build-version ${VISION_OS_BUILD_VERSION} ${VISIONOS_MIN_OS_VERSION} ${VISIONOS_MIN_OS_VERSION} -replace \
|
||||||
|
-output "${base_dir}/${output_lib}" "${base_dir}/${output_lib}"
|
||||||
|
;;
|
||||||
|
"tvos")
|
||||||
|
echo "Marking binary as a framework binary for tvOS..."
|
||||||
|
xcrun vtool -set-build-version tvos ${TVOS_MIN_OS_VERSION} ${TVOS_MIN_OS_VERSION} -replace \
|
||||||
|
-output "${base_dir}/${output_lib}" "${base_dir}/${output_lib}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
echo "Warning: vtool not found. Binary may not pass App Store validation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating properly formatted dSYM..."
|
||||||
|
# Create a separate directory for dSYMs for all platforms
|
||||||
|
mkdir -p "${base_dir}/${build_dir}/dSYMs"
|
||||||
|
|
||||||
|
# iOS and visionOS style dSYM (flat structure)
|
||||||
|
if [[ "$platform" == "ios" || "$platform" == "visionos" || "$platform" == "tvos" ]]; then
|
||||||
|
# Generate dSYM in the dSYMs directory
|
||||||
|
xcrun dsymutil "${base_dir}/${output_lib}" -o "${base_dir}/${build_dir}/dSYMs/whisper.dSYM"
|
||||||
|
|
||||||
|
# Create a copy of the binary that will be stripped
|
||||||
|
cp "${base_dir}/${output_lib}" "${temp_dir}/binary_to_strip"
|
||||||
|
|
||||||
|
# Strip debug symbols from the copy
|
||||||
|
xcrun strip -S "${temp_dir}/binary_to_strip" -o "${temp_dir}/stripped_lib"
|
||||||
|
|
||||||
|
# Replace the original with the stripped version
|
||||||
|
mv "${temp_dir}/stripped_lib" "${base_dir}/${output_lib}"
|
||||||
|
else
|
||||||
|
# macOS style dSYM
|
||||||
|
# First strip debug info to a separate file
|
||||||
|
xcrun strip -S "${base_dir}/${output_lib}" -o "${temp_dir}/stripped_lib"
|
||||||
|
|
||||||
|
# Generate dSYM in the dSYMs directory
|
||||||
|
xcrun dsymutil "${base_dir}/${output_lib}" -o "${base_dir}/${build_dir}/dSYMs/whisper.dSYM"
|
||||||
|
|
||||||
|
# Replace original binary with stripped version
|
||||||
|
mv "${temp_dir}/stripped_lib" "${base_dir}/${output_lib}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove any automatically generated dSYM files in the framework structure as they will
|
||||||
|
# otherwise case Invalid Bundle Structure validation errors.
|
||||||
|
if [ -d "${base_dir}/${output_lib}.dSYM" ]; then
|
||||||
|
echo "Removing generated dSYM file in framework structure: ${base_dir}/${output_lib}.dSYM"
|
||||||
|
rm -rf "${base_dir}/${output_lib}.dSYM"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -rf "${temp_dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Building for iOS simulator..."
|
||||||
|
cmake -B build-ios-sim -G Xcode \
|
||||||
|
"${COMMON_CMAKE_ARGS[@]}" \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${IOS_MIN_OS_VERSION} \
|
||||||
|
-DIOS=ON \
|
||||||
|
-DCMAKE_SYSTEM_NAME=iOS \
|
||||||
|
-DCMAKE_OSX_SYSROOT=iphonesimulator \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphonesimulator \
|
||||||
|
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
|
||||||
|
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
|
||||||
|
-DWHISPER_COREML="ON" \
|
||||||
|
-DWHISPER_COREML_ALLOW_FALLBACK="ON" \
|
||||||
|
-S .
|
||||||
|
cmake --build build-ios-sim --config Release -- -quiet
|
||||||
|
|
||||||
|
echo "Building for iOS devices..."
|
||||||
|
cmake -B build-ios-device -G Xcode \
|
||||||
|
"${COMMON_CMAKE_ARGS[@]}" \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${IOS_MIN_OS_VERSION} \
|
||||||
|
-DCMAKE_OSX_SYSROOT=iphoneos \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64" \
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphoneos \
|
||||||
|
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
|
||||||
|
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
|
||||||
|
-DWHISPER_COREML="ON" \
|
||||||
|
-DWHISPER_COREML_ALLOW_FALLBACK="ON" \
|
||||||
|
-S .
|
||||||
|
cmake --build build-ios-device --config Release -- -quiet
|
||||||
|
|
||||||
|
echo "Building for macOS..."
|
||||||
|
cmake -B build-macos -G Xcode \
|
||||||
|
"${COMMON_CMAKE_ARGS[@]}" \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOS_MIN_OS_VERSION} \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
|
||||||
|
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
|
||||||
|
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
|
||||||
|
-DWHISPER_COREML="ON" \
|
||||||
|
-DWHISPER_COREML_ALLOW_FALLBACK="ON" \
|
||||||
|
-S .
|
||||||
|
cmake --build build-macos --config Release -- -quiet
|
||||||
|
|
||||||
|
echo "Building for visionOS..."
|
||||||
|
cmake -B build-visionos -G Xcode \
|
||||||
|
"${COMMON_CMAKE_ARGS[@]}" \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${VISIONOS_MIN_OS_VERSION} \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64" \
|
||||||
|
-DCMAKE_SYSTEM_NAME=visionOS \
|
||||||
|
-DCMAKE_OSX_SYSROOT=xros \
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xros \
|
||||||
|
-DCMAKE_C_FLAGS="-D_XOPEN_SOURCE=700 ${COMMON_C_FLAGS}" \
|
||||||
|
-DCMAKE_CXX_FLAGS="-D_XOPEN_SOURCE=700 ${COMMON_CXX_FLAGS}" \
|
||||||
|
-S .
|
||||||
|
cmake --build build-visionos --config Release -- -quiet
|
||||||
|
|
||||||
|
echo "Building for visionOS simulator..."
|
||||||
|
cmake -B build-visionos-sim -G Xcode \
|
||||||
|
"${COMMON_CMAKE_ARGS[@]}" \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${VISIONOS_MIN_OS_VERSION} \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
|
||||||
|
-DCMAKE_SYSTEM_NAME=visionOS \
|
||||||
|
-DCMAKE_OSX_SYSROOT=xrsimulator \
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xrsimulator \
|
||||||
|
-DCMAKE_C_FLAGS="-D_XOPEN_SOURCE=700 ${COMMON_C_FLAGS}" \
|
||||||
|
-DCMAKE_CXX_FLAGS="-D_XOPEN_SOURCE=700 ${COMMON_CXX_FLAGS}" \
|
||||||
|
-S .
|
||||||
|
cmake --build build-visionos-sim --config Release -- -quiet
|
||||||
|
|
||||||
|
# Add tvOS builds (might need the same u_int definitions as watchOS and visionOS)
|
||||||
|
echo "Building for tvOS simulator..."
|
||||||
|
cmake -B build-tvos-sim -G Xcode \
|
||||||
|
"${COMMON_CMAKE_ARGS[@]}" \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
|
||||||
|
-DCMAKE_SYSTEM_NAME=tvOS \
|
||||||
|
-DCMAKE_OSX_SYSROOT=appletvsimulator \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
|
||||||
|
-DGGML_METAL=ON \
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvsimulator \
|
||||||
|
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
|
||||||
|
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
|
||||||
|
-S .
|
||||||
|
cmake --build build-tvos-sim --config Release -- -quiet
|
||||||
|
|
||||||
|
echo "Building for tvOS devices..."
|
||||||
|
cmake -B build-tvos-device -G Xcode \
|
||||||
|
"${COMMON_CMAKE_ARGS[@]}" \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
|
||||||
|
-DCMAKE_SYSTEM_NAME=tvOS \
|
||||||
|
-DCMAKE_OSX_SYSROOT=appletvos \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES="arm64" \
|
||||||
|
-DGGML_METAL=ON \
|
||||||
|
-DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvos \
|
||||||
|
-DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
|
||||||
|
-DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
|
||||||
|
-S .
|
||||||
|
cmake --build build-tvos-device --config Release -- -quiet
|
||||||
|
|
||||||
|
# Setup frameworks and copy binaries and headers
|
||||||
|
echo "Setting up framework structures..."
|
||||||
|
setup_framework_structure "build-ios-sim" ${IOS_MIN_OS_VERSION} "ios"
|
||||||
|
setup_framework_structure "build-ios-device" ${IOS_MIN_OS_VERSION} "ios"
|
||||||
|
setup_framework_structure "build-macos" ${MACOS_MIN_OS_VERSION} "macos"
|
||||||
|
setup_framework_structure "build-visionos" ${VISIONOS_MIN_OS_VERSION} "visionos"
|
||||||
|
setup_framework_structure "build-visionos-sim" ${VISIONOS_MIN_OS_VERSION} "visionos"
|
||||||
|
setup_framework_structure "build-tvos-sim" ${TVOS_MIN_OS_VERSION} "tvos"
|
||||||
|
setup_framework_structure "build-tvos-device" ${TVOS_MIN_OS_VERSION} "tvos"
|
||||||
|
|
||||||
|
# Create dynamic libraries from static libraries
|
||||||
|
echo "Creating dynamic libraries from static libraries..."
|
||||||
|
combine_static_libraries "build-ios-sim" "Release-iphonesimulator" "ios" "true"
|
||||||
|
combine_static_libraries "build-ios-device" "Release-iphoneos" "ios" "false"
|
||||||
|
combine_static_libraries "build-macos" "Release" "macos" "false"
|
||||||
|
combine_static_libraries "build-visionos" "Release-xros" "visionos" "false"
|
||||||
|
combine_static_libraries "build-visionos-sim" "Release-xrsimulator" "visionos" "true"
|
||||||
|
combine_static_libraries "build-tvos-sim" "Release-appletvsimulator" "tvos" "true"
|
||||||
|
combine_static_libraries "build-tvos-device" "Release-appletvos" "tvos" "false"
|
||||||
|
|
||||||
|
# Create XCFramework with correct debug symbols paths
|
||||||
|
echo "Creating XCFramework..."
|
||||||
|
xcodebuild -create-xcframework \
|
||||||
|
-framework $(pwd)/build-ios-sim/framework/whisper.framework \
|
||||||
|
-debug-symbols $(pwd)/build-ios-sim/dSYMs/whisper.dSYM \
|
||||||
|
-framework $(pwd)/build-ios-device/framework/whisper.framework \
|
||||||
|
-debug-symbols $(pwd)/build-ios-device/dSYMs/whisper.dSYM \
|
||||||
|
-framework $(pwd)/build-macos/framework/whisper.framework \
|
||||||
|
-debug-symbols $(pwd)/build-macos/dSYMS/whisper.dSYM \
|
||||||
|
-framework $(pwd)/build-visionos/framework/whisper.framework \
|
||||||
|
-debug-symbols $(pwd)/build-visionos/dSYMs/whisper.dSYM \
|
||||||
|
-framework $(pwd)/build-visionos-sim/framework/whisper.framework \
|
||||||
|
-debug-symbols $(pwd)/build-visionos-sim/dSYMs/whisper.dSYM \
|
||||||
|
-framework $(pwd)/build-tvos-device/framework/whisper.framework \
|
||||||
|
-debug-symbols $(pwd)/build-tvos-device/dSYMs/whisper.dSYM \
|
||||||
|
-framework $(pwd)/build-tvos-sim/framework/whisper.framework \
|
||||||
|
-debug-symbols $(pwd)/build-tvos-sim/dSYMs/whisper.dSYM \
|
||||||
|
-output $(pwd)/build-apple/whisper.xcframework
|
41
ci/README.md
Normal file
41
ci/README.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# CI
|
||||||
|
|
||||||
|
In addition to [Github Actions](https://github.com/ggerganov/whisper.cpp/actions) `whisper.cpp` uses a custom CI framework:
|
||||||
|
|
||||||
|
https://github.com/ggml-org/ci
|
||||||
|
|
||||||
|
It monitors the `master` branch for new commits and runs the
|
||||||
|
[ci/run.sh](https://github.com/ggerganov/whisper.cpp/blob/master/ci/run.sh) script on dedicated cloud instances. This allows us
|
||||||
|
to execute heavier workloads compared to just using Github Actions. Also with time, the cloud instances will be scaled
|
||||||
|
to cover various hardware architectures, including GPU and Apple Silicon instances.
|
||||||
|
|
||||||
|
Collaborators can optionally trigger the CI run by adding the `ggml-ci` keyword to their commit message.
|
||||||
|
Only the branches of this repo are monitored for this keyword.
|
||||||
|
|
||||||
|
It is a good practice, before publishing changes to execute the full CI locally on your machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir tmp
|
||||||
|
|
||||||
|
# CPU-only build
|
||||||
|
bash ./ci/run.sh ./tmp/results ./tmp/mnt
|
||||||
|
|
||||||
|
# with CUDA support
|
||||||
|
GG_BUILD_CUDA=1 bash ./ci/run.sh ./tmp/results ./tmp/mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
The CI script supports several environment variables to control the build:
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `GG_BUILD_CUDA` | Enable NVIDIA CUDA GPU acceleration |
|
||||||
|
| `GG_BUILD_SYCL` | Enable Intel SYCL acceleration |
|
||||||
|
| `GG_BUILD_VULKAN` | Enable Vulkan GPU acceleration |
|
||||||
|
| `GG_BUILD_METAL` | Enable Metal acceleration on Apple Silicon |
|
||||||
|
| `GG_BUILD_BLAS` | Enable BLAS CPU acceleration |
|
||||||
|
| `GG_BUILD_OPENVINO` | Enable OpenVINO support |
|
||||||
|
| `GG_BUILD_COREML` | Enable Core ML support for Apple Neural Engine |
|
||||||
|
| `GG_BUILD_LOW_PERF` | Limit tests for low-performance hardware |
|
||||||
|
| `GG_BUILD_TEST_MODELS` | Comma-separated list of models to test (e.g. "tiny.en,tiny,base,medium", defaults to all models unless `GG_BUILD_LOW_PERF` is set) |
|
336
ci/run.sh
Normal file
336
ci/run.sh
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# sample usage:
|
||||||
|
#
|
||||||
|
# mkdir tmp
|
||||||
|
#
|
||||||
|
# # CPU-only build
|
||||||
|
# bash ./ci/run.sh ./tmp/results ./tmp/mnt
|
||||||
|
#
|
||||||
|
# # with CUDA support
|
||||||
|
# GG_BUILD_CUDA=1 bash ./ci/run.sh ./tmp/results ./tmp/mnt
|
||||||
|
#
|
||||||
|
# # with SYCL support
|
||||||
|
# GG_BUILD_SYCL=1 bash ./ci/run.sh ./tmp/results ./tmp/mnt
|
||||||
|
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "usage: $0 <output-dir> <mnt-dir>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$1"
|
||||||
|
mkdir -p "$2"
|
||||||
|
|
||||||
|
OUT=$(realpath "$1")
|
||||||
|
MNT=$(realpath "$2")
|
||||||
|
|
||||||
|
rm -f "$OUT/*.log"
|
||||||
|
rm -f "$OUT/*.exit"
|
||||||
|
rm -f "$OUT/*.md"
|
||||||
|
|
||||||
|
sd=`dirname $0`
|
||||||
|
cd $sd/../
|
||||||
|
SRC=`pwd`
|
||||||
|
|
||||||
|
ALL_MODELS=( "tiny.en" "tiny" "base.en" "base" "small.en" "small" "medium.en" "medium" "large-v1" "large-v2" "large-v3" "large-v3-turbo" )
|
||||||
|
BENCH_N_THREADS=4
|
||||||
|
BENCH_ENCODER_ONLY=0
|
||||||
|
BENCH_FLASH_ATTN=0
|
||||||
|
|
||||||
|
# check for user-specified models first. if not specified, use fast models
|
||||||
|
if [ ! -z ${GG_BUILD_TEST_MODELS} ]; then
|
||||||
|
IFS=',' read -r -a MODELS <<< "${GG_BUILD_TEST_MODELS}"
|
||||||
|
else
|
||||||
|
if [ ! -z ${GG_BUILD_LOW_PERF} ]; then
|
||||||
|
MODELS=( "tiny" "base" "small" )
|
||||||
|
else
|
||||||
|
MODELS=("${ALL_MODELS[@]}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
CMAKE_EXTRA="-DWHISPER_FATAL_WARNINGS=ON"
|
||||||
|
|
||||||
|
if [ ! -z ${GG_BUILD_CUDA} ]; then
|
||||||
|
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=native"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z ${GG_BUILD_SYCL} ]; then
|
||||||
|
if [ -z ${ONEAPI_ROOT} ]; then
|
||||||
|
echo "Not detected ONEAPI_ROOT, please install oneAPI base toolkit and enable it by:"
|
||||||
|
echo "source /opt/intel/oneapi/setvars.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_SYCL=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DGGML_SYCL_F16=ON"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z ${GG_BUILD_OPENVINO} ]; then
|
||||||
|
CMAKE_EXTRA="${CMAKE_EXTRA} -DWHISPER_OPENVINO=ON"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z ${GG_BUILD_METAL} ]; then
|
||||||
|
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_METAL=ON"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z ${GG_BUILD_VULKAN} ]; then
|
||||||
|
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_VULKAN=ON"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z ${GG_BUILD_BLAS} ]; then
|
||||||
|
CMAKE_EXTRA="${CMAKE_EXTRA} -DGGML_BLAS=ON"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z ${GG_BUILD_COREML} ]; then
|
||||||
|
CMAKE_EXTRA="${CMAKE_EXTRA} -DWHISPER_COREML=ON"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## helpers
|
||||||
|
|
||||||
|
# download a file if it does not exist or if it is outdated
|
||||||
|
function gg_wget {
|
||||||
|
local out=$1
|
||||||
|
local url=$2
|
||||||
|
|
||||||
|
local cwd=`pwd`
|
||||||
|
|
||||||
|
mkdir -p $out
|
||||||
|
cd $out
|
||||||
|
|
||||||
|
# should not re-download if file is the same
|
||||||
|
wget -nv -N $url
|
||||||
|
|
||||||
|
cd $cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_download_model {
|
||||||
|
local model_name=$1
|
||||||
|
local model_file="$MNT/models/ggml-${model_name}.bin"
|
||||||
|
|
||||||
|
if [ ! -f ${model_file} ]; then
|
||||||
|
local cwd=`pwd`
|
||||||
|
mkdir -p "$MNT/models"
|
||||||
|
cd "$MNT/models"
|
||||||
|
bash "$cwd/models/download-ggml-model.sh" ${model_name} .
|
||||||
|
cd "$cwd"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_printf {
|
||||||
|
printf -- "$@" >> $OUT/README.md
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to check command exit status
|
||||||
|
function gg_check_last_command_status {
|
||||||
|
local exit_file=$1
|
||||||
|
local command_name=$2
|
||||||
|
|
||||||
|
local exit_status=$?
|
||||||
|
echo "$exit_status" > "$exit_file"
|
||||||
|
|
||||||
|
if [ $exit_status -ne 0 ]; then
|
||||||
|
echo "Error: Command $command_name failed with exit status $exit_status"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usage: gg_run <test_name> [additional_args...]
|
||||||
|
#
|
||||||
|
# Parameters:
|
||||||
|
# test_name - Name of the test to run (calls gg_run_<test_name>)
|
||||||
|
# additional_args - Any additional arguments to pass to the test function (first argument is appended to the log filename)
|
||||||
|
function gg_run {
|
||||||
|
ci=$1
|
||||||
|
|
||||||
|
if [ $# -gt 1 ]; then
|
||||||
|
ci="${ci}_${2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
gg_run_$1 "$@" | tee $OUT/$ci.log
|
||||||
|
cur=$?
|
||||||
|
echo "$cur" > $OUT/$ci.exit
|
||||||
|
|
||||||
|
set +x
|
||||||
|
set +o pipefail
|
||||||
|
|
||||||
|
gg_sum_$1 "$@"
|
||||||
|
|
||||||
|
ret=$((ret | cur))
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_check_build_requirements {
|
||||||
|
if ! command -v cmake &> /dev/null; then
|
||||||
|
gg_printf 'cmake not found, please install'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v make &> /dev/null; then
|
||||||
|
gg_printf 'make not found, please install'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
## ci
|
||||||
|
|
||||||
|
function gg_run_ctest {
|
||||||
|
mode=$2
|
||||||
|
|
||||||
|
cd ${SRC}
|
||||||
|
|
||||||
|
rm -rf build-ci-${mode} && mkdir build-ci-${mode} && cd build-ci-${mode}
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
gg_check_build_requirements
|
||||||
|
|
||||||
|
(time cmake -DCMAKE_BUILD_TYPE=${mode} ${CMAKE_EXTRA} .. ) 2>&1 | tee -a $OUT/${ci}-cmake.log
|
||||||
|
(time make -j$(nproc) ) 2>&1 | tee -a $OUT/${ci}-make.log
|
||||||
|
|
||||||
|
(time ctest --output-on-failure -L main -E test-opt ) 2>&1 | tee -a $OUT/${ci}-ctest.log
|
||||||
|
|
||||||
|
set +e
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_sum_ctest {
|
||||||
|
mode=$2
|
||||||
|
|
||||||
|
gg_printf '### %s\n\n' "${ci}"
|
||||||
|
|
||||||
|
gg_printf 'Runs ctest in '${mode}' mode\n'
|
||||||
|
gg_printf '- status: %s\n' "$(cat $OUT/${ci}.exit)"
|
||||||
|
gg_printf '```\n'
|
||||||
|
gg_printf '%s\n' "$(cat $OUT/${ci}-ctest.log)"
|
||||||
|
gg_printf '```\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_run_bench {
|
||||||
|
cd ${SRC}
|
||||||
|
|
||||||
|
# set flash attention flag if enabled
|
||||||
|
fattn=""
|
||||||
|
if [ "$BENCH_FLASH_ATTN" -eq 1 ]; then
|
||||||
|
fattn="-fa"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# run memcpy benchmark if not encoder-only mode
|
||||||
|
if [ "$BENCH_ENCODER_ONLY" -eq 0 ]; then
|
||||||
|
echo "Running memcpy benchmark"
|
||||||
|
(time ./build-ci-release/bin/whisper-bench -w 1 -t $BENCH_N_THREADS 2>&1) | tee -a $OUT/${ci}-memcpy.log
|
||||||
|
gg_check_last_command_status "$OUT/${ci}-memcpy.exit" "memcpy benchmark"
|
||||||
|
|
||||||
|
echo "Running ggml_mul_mat benchmark with $BENCH_N_THREADS threads"
|
||||||
|
(time ./build-ci-release/bin/whisper-bench -w 2 -t $BENCH_N_THREADS 2>&1) | tee -a $OUT/${ci}-mul_mat.log
|
||||||
|
gg_check_last_command_status "$OUT/${ci}-mul_mat.exit" "ggml_mul_mat benchmark"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Running benchmark for all models"
|
||||||
|
|
||||||
|
# generate header for the benchmark table
|
||||||
|
{
|
||||||
|
printf "| %16s | %13s | %3s | %3s | %7s | %7s | %7s | %7s | %7s |\n" "Config" "Model" "Th" "FA" "Enc." "Dec." "Bch5" "PP" "Commit"
|
||||||
|
printf "| %16s | %13s | %3s | %3s | %7s | %7s | %7s | %7s | %7s |\n" "---" "---" "---" "---" "---" "---" "---" "---" "---"
|
||||||
|
} | tee -a $OUT/${ci}-models-table.log
|
||||||
|
|
||||||
|
# run benchmark for each model
|
||||||
|
for model in "${MODELS[@]}"; do
|
||||||
|
echo "Benchmarking model: $model"
|
||||||
|
|
||||||
|
# run the benchmark and capture output
|
||||||
|
output=$(./build-ci-release/bin/whisper-bench -m $MNT/models/ggml-$model.bin -t $BENCH_N_THREADS $fattn 2>&1)
|
||||||
|
ret=$?
|
||||||
|
|
||||||
|
# save the raw output
|
||||||
|
echo "$output" > $OUT/${ci}-bench-$model.log
|
||||||
|
|
||||||
|
if [ $ret -eq 0 ]; then
|
||||||
|
# parse the benchmark results
|
||||||
|
encode_time=$(echo "$output" | grep "encode time" | awk '{print $11}')
|
||||||
|
decode_time=$(echo "$output" | grep "decode time" | awk '{print $11}')
|
||||||
|
batchd_time=$(echo "$output" | grep "batchd time" | awk '{print $11}')
|
||||||
|
prompt_time=$(echo "$output" | grep "prompt time" | awk '{print $11}')
|
||||||
|
system_info=$(echo "$output" | grep "system_info")
|
||||||
|
actual_threads=$(echo "$output" | grep "system_info" | awk '{print $4}')
|
||||||
|
|
||||||
|
# determine configuration
|
||||||
|
config=""
|
||||||
|
if [[ $system_info == *"AVX2 = 1"* ]]; then
|
||||||
|
config="$config AVX2"
|
||||||
|
fi
|
||||||
|
if [[ $system_info == *"NEON = 1"* ]]; then
|
||||||
|
config="$config NEON"
|
||||||
|
fi
|
||||||
|
if [[ $system_info == *"BLAS = 1"* ]]; then
|
||||||
|
config="$config BLAS"
|
||||||
|
fi
|
||||||
|
if [[ $system_info == *"COREML = 1"* ]]; then
|
||||||
|
config="$config COREML"
|
||||||
|
fi
|
||||||
|
if [[ $system_info == *"CUDA = 1"* ]]; then
|
||||||
|
config="$config CUDA"
|
||||||
|
fi
|
||||||
|
if [[ $system_info == *"METAL = 1"* ]]; then
|
||||||
|
config="$config METAL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# get commit hash
|
||||||
|
commit=$(git rev-parse --short HEAD)
|
||||||
|
|
||||||
|
# add row to benchmark table
|
||||||
|
printf "| %16s | %13s | %3s | %3s | %7s | %7s | %7s | %7s | %7s |\n" \
|
||||||
|
"$config" "$model" "$actual_threads" "$BENCH_FLASH_ATTN" "$encode_time" "$decode_time" "$batchd_time" "$prompt_time" "$commit" \
|
||||||
|
| tee -a $OUT/${ci}-models-table.log
|
||||||
|
else
|
||||||
|
echo "Benchmark failed for model: $model" | tee -a $OUT/${ci}-bench-errors.log
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function gg_sum_bench {
|
||||||
|
gg_printf '### %s\n\n' "${ci}"
|
||||||
|
|
||||||
|
gg_printf 'Whisper Benchmark Results\n'
|
||||||
|
gg_printf '- status: %s\n' "$(cat $OUT/${ci}.exit)"
|
||||||
|
|
||||||
|
# show memcpy and ggml_mul_mat benchmark results if available
|
||||||
|
if [ "$BENCH_ENCODER_ONLY" -eq 0 ]; then
|
||||||
|
if [ -f "$OUT/${ci}-memcpy.log" ]; then
|
||||||
|
gg_printf '#### memcpy Benchmark\n\n'
|
||||||
|
gg_printf '```\n%s\n```\n\n' "$(cat $OUT/${ci}-memcpy.log)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$OUT/${ci}-mul_mat.log" ]; then
|
||||||
|
gg_printf '#### ggml_mul_mat Benchmark\n\n'
|
||||||
|
gg_printf '```\n%s\n```\n\n' "$(cat $OUT/${ci}-mul_mat.log)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# show model benchmark results
|
||||||
|
gg_printf '#### Model Benchmarks\n\n'
|
||||||
|
if [ -f "$OUT/${ci}-models-table.log" ]; then
|
||||||
|
gg_printf '%s\n\n' "$(cat $OUT/${ci}-models-table.log)"
|
||||||
|
else
|
||||||
|
gg_printf 'No model benchmark results available.\n\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# show any errors that occurred
|
||||||
|
if [ -f "$OUT/${ci}-bench-errors.log" ]; then
|
||||||
|
gg_printf '#### Benchmark Errors\n\n'
|
||||||
|
gg_printf '```\n%s\n```\n\n' "$(cat $OUT/${ci}-bench-errors.log)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ret=0
|
||||||
|
|
||||||
|
for model in "${MODELS[@]}"; do
|
||||||
|
test $ret -eq 0 && gg_download_model ${model}
|
||||||
|
done
|
||||||
|
if [ -z ${GG_BUILD_SYCL}]; then
|
||||||
|
test $ret -eq 0 && gg_run ctest debug
|
||||||
|
fi
|
||||||
|
test $ret -eq 0 && gg_run ctest release
|
||||||
|
|
||||||
|
test $ret -eq 0 && gg_run bench
|
||||||
|
|
||||||
|
exit $ret
|
@ -14,10 +14,6 @@ if (WHISPER_SDL2)
|
|||||||
message(STATUS "SDL2_LIBRARIES = ${SDL2_LIBRARIES}")
|
message(STATUS "SDL2_LIBRARIES = ${SDL2_LIBRARIES}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WHISPER_CLBLAST)
|
|
||||||
find_package(CLBlast REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# common
|
# common
|
||||||
|
|
||||||
set(TARGET common)
|
set(TARGET common)
|
||||||
@ -56,6 +52,8 @@ add_library(${TARGET} STATIC
|
|||||||
common.cpp
|
common.cpp
|
||||||
common-ggml.h
|
common-ggml.h
|
||||||
common-ggml.cpp
|
common-ggml.cpp
|
||||||
|
common-whisper.h
|
||||||
|
common-whisper.cpp
|
||||||
grammar-parser.h
|
grammar-parser.h
|
||||||
grammar-parser.cpp
|
grammar-parser.cpp
|
||||||
${COMMON_SOURCES_FFMPEG}
|
${COMMON_SOURCES_FFMPEG}
|
||||||
@ -63,7 +61,7 @@ add_library(${TARGET} STATIC
|
|||||||
|
|
||||||
include(DefaultTargetOptions)
|
include(DefaultTargetOptions)
|
||||||
|
|
||||||
target_link_libraries(${TARGET} PRIVATE whisper ${COMMON_EXTRA_LIBS})
|
target_link_libraries(${TARGET} PRIVATE whisper ${COMMON_EXTRA_LIBS} ${CMAKE_DL_LIBS})
|
||||||
|
|
||||||
set_target_properties(${TARGET} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
set_target_properties(${TARGET} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||||
set_target_properties(${TARGET} PROPERTIES FOLDER "libs")
|
set_target_properties(${TARGET} PROPERTIES FOLDER "libs")
|
||||||
|
@ -18,6 +18,13 @@ const whisperParamsMock = {
|
|||||||
translate: true,
|
translate: true,
|
||||||
no_timestamps: false,
|
no_timestamps: false,
|
||||||
audio_ctx: 0,
|
audio_ctx: 0,
|
||||||
|
max_len: 0,
|
||||||
|
prompt: "",
|
||||||
|
print_progress: false,
|
||||||
|
progress_callback: (progress) => {
|
||||||
|
console.log(`Progress: ${progress}`);
|
||||||
|
},
|
||||||
|
max_context: -1
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Run whisper.node", () => {
|
describe("Run whisper.node", () => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "napi.h"
|
#include "napi.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "common-whisper.h"
|
||||||
|
|
||||||
#include "whisper.h"
|
#include "whisper.h"
|
||||||
|
|
||||||
@ -127,192 +128,227 @@ void whisper_print_segment_callback(struct whisper_context * ctx, struct whisper
|
|||||||
|
|
||||||
void cb_log_disable(enum ggml_log_level, const char *, void *) {}
|
void cb_log_disable(enum ggml_log_level, const char *, void *) {}
|
||||||
|
|
||||||
int run(whisper_params ¶ms, std::vector<std::vector<std::string>> &result) {
|
class ProgressWorker : public Napi::AsyncWorker {
|
||||||
if (params.no_prints) {
|
|
||||||
whisper_log_set(cb_log_disable, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.fname_inp.empty() && params.pcmf32.empty()) {
|
|
||||||
fprintf(stderr, "error: no input files or audio buffer specified\n");
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.language != "auto" && whisper_lang_id(params.language.c_str()) == -1) {
|
|
||||||
fprintf(stderr, "error: unknown language '%s'\n", params.language.c_str());
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// whisper init
|
|
||||||
|
|
||||||
struct whisper_context_params cparams = whisper_context_default_params();
|
|
||||||
cparams.use_gpu = params.use_gpu;
|
|
||||||
cparams.flash_attn = params.flash_attn;
|
|
||||||
struct whisper_context * ctx = whisper_init_from_file_with_params(params.model.c_str(), cparams);
|
|
||||||
|
|
||||||
if (ctx == nullptr) {
|
|
||||||
fprintf(stderr, "error: failed to initialize whisper context\n");
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if params.pcmf32 is provided, set params.fname_inp to "buffer"
|
|
||||||
// this is simpler than further modifications in the code
|
|
||||||
if (!params.pcmf32.empty()) {
|
|
||||||
fprintf(stderr, "info: using audio buffer as input\n");
|
|
||||||
params.fname_inp.clear();
|
|
||||||
params.fname_inp.emplace_back("buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int f = 0; f < (int) params.fname_inp.size(); ++f) {
|
|
||||||
const auto fname_inp = params.fname_inp[f];
|
|
||||||
const auto fname_out = f < (int)params.fname_out.size() && !params.fname_out[f].empty() ? params.fname_out[f] : params.fname_inp[f];
|
|
||||||
|
|
||||||
std::vector<float> pcmf32; // mono-channel F32 PCM
|
|
||||||
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
|
|
||||||
|
|
||||||
// read the input audio file if params.pcmf32 is not provided
|
|
||||||
if (params.pcmf32.empty()) {
|
|
||||||
if (!::read_wav(fname_inp, pcmf32, pcmf32s, params.diarize)) {
|
|
||||||
fprintf(stderr, "error: failed to read WAV file '%s'\n", fname_inp.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pcmf32 = params.pcmf32;
|
|
||||||
}
|
|
||||||
|
|
||||||
// print system information
|
|
||||||
if (!params.no_prints) {
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
fprintf(stderr, "system_info: n_threads = %d / %d | %s\n",
|
|
||||||
params.n_threads*params.n_processors, std::thread::hardware_concurrency(), whisper_print_system_info());
|
|
||||||
}
|
|
||||||
|
|
||||||
// print some info about the processing
|
|
||||||
if (!params.no_prints) {
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
if (!whisper_is_multilingual(ctx)) {
|
|
||||||
if (params.language != "en" || params.translate) {
|
|
||||||
params.language = "en";
|
|
||||||
params.translate = false;
|
|
||||||
fprintf(stderr, "%s: WARNING: model is not multilingual, ignoring language and translation options\n", __func__);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "%s: processing '%s' (%d samples, %.1f sec), %d threads, %d processors, lang = %s, task = %s, timestamps = %d, audio_ctx = %d ...\n",
|
|
||||||
__func__, fname_inp.c_str(), int(pcmf32.size()), float(pcmf32.size())/WHISPER_SAMPLE_RATE,
|
|
||||||
params.n_threads, params.n_processors,
|
|
||||||
params.language.c_str(),
|
|
||||||
params.translate ? "translate" : "transcribe",
|
|
||||||
params.no_timestamps ? 0 : 1,
|
|
||||||
params.audio_ctx);
|
|
||||||
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the inference
|
|
||||||
{
|
|
||||||
whisper_full_params wparams = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
|
|
||||||
|
|
||||||
wparams.strategy = params.beam_size > 1 ? WHISPER_SAMPLING_BEAM_SEARCH : WHISPER_SAMPLING_GREEDY;
|
|
||||||
|
|
||||||
wparams.print_realtime = false;
|
|
||||||
wparams.print_progress = params.print_progress;
|
|
||||||
wparams.print_timestamps = !params.no_timestamps;
|
|
||||||
wparams.print_special = params.print_special;
|
|
||||||
wparams.translate = params.translate;
|
|
||||||
wparams.language = params.language.c_str();
|
|
||||||
wparams.n_threads = params.n_threads;
|
|
||||||
wparams.n_max_text_ctx = params.max_context >= 0 ? params.max_context : wparams.n_max_text_ctx;
|
|
||||||
wparams.offset_ms = params.offset_t_ms;
|
|
||||||
wparams.duration_ms = params.duration_ms;
|
|
||||||
|
|
||||||
wparams.token_timestamps = params.output_wts || params.max_len > 0;
|
|
||||||
wparams.thold_pt = params.word_thold;
|
|
||||||
wparams.entropy_thold = params.entropy_thold;
|
|
||||||
wparams.logprob_thold = params.logprob_thold;
|
|
||||||
wparams.max_len = params.output_wts && params.max_len == 0 ? 60 : params.max_len;
|
|
||||||
wparams.audio_ctx = params.audio_ctx;
|
|
||||||
|
|
||||||
wparams.greedy.best_of = params.best_of;
|
|
||||||
wparams.beam_search.beam_size = params.beam_size;
|
|
||||||
|
|
||||||
wparams.initial_prompt = params.prompt.c_str();
|
|
||||||
|
|
||||||
wparams.no_timestamps = params.no_timestamps;
|
|
||||||
|
|
||||||
whisper_print_user_data user_data = { ¶ms, &pcmf32s };
|
|
||||||
|
|
||||||
// this callback is called on each new segment
|
|
||||||
if (!wparams.print_realtime) {
|
|
||||||
wparams.new_segment_callback = whisper_print_segment_callback;
|
|
||||||
wparams.new_segment_callback_user_data = &user_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// example for abort mechanism
|
|
||||||
// in this example, we do not abort the processing, but we could if the flag is set to true
|
|
||||||
// the callback is called before every encoder run - if it returns false, the processing is aborted
|
|
||||||
{
|
|
||||||
static bool is_aborted = false; // NOTE: this should be atomic to avoid data race
|
|
||||||
|
|
||||||
wparams.encoder_begin_callback = [](struct whisper_context * /*ctx*/, struct whisper_state * /*state*/, void * user_data) {
|
|
||||||
bool is_aborted = *(bool*)user_data;
|
|
||||||
return !is_aborted;
|
|
||||||
};
|
|
||||||
wparams.encoder_begin_callback_user_data = &is_aborted;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (whisper_full_parallel(ctx, wparams, pcmf32.data(), pcmf32.size(), params.n_processors) != 0) {
|
|
||||||
fprintf(stderr, "failed to process audio\n");
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
|
||||||
result.resize(n_segments);
|
|
||||||
for (int i = 0; i < n_segments; ++i) {
|
|
||||||
const char * text = whisper_full_get_segment_text(ctx, i);
|
|
||||||
const int64_t t0 = whisper_full_get_segment_t0(ctx, i);
|
|
||||||
const int64_t t1 = whisper_full_get_segment_t1(ctx, i);
|
|
||||||
|
|
||||||
result[i].emplace_back(to_timestamp(t0, params.comma_in_time));
|
|
||||||
result[i].emplace_back(to_timestamp(t1, params.comma_in_time));
|
|
||||||
result[i].emplace_back(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
whisper_print_timings(ctx);
|
|
||||||
whisper_free(ctx);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Worker : public Napi::AsyncWorker {
|
|
||||||
public:
|
public:
|
||||||
Worker(Napi::Function& callback, whisper_params params)
|
ProgressWorker(Napi::Function& callback, whisper_params params, Napi::Function progress_callback, Napi::Env env)
|
||||||
: Napi::AsyncWorker(callback), params(params) {}
|
: Napi::AsyncWorker(callback), params(params), env(env) {
|
||||||
|
// Create thread-safe function
|
||||||
void Execute() override {
|
if (!progress_callback.IsEmpty()) {
|
||||||
run(params, result);
|
tsfn = Napi::ThreadSafeFunction::New(
|
||||||
}
|
env,
|
||||||
|
progress_callback,
|
||||||
void OnOK() override {
|
"Progress Callback",
|
||||||
Napi::HandleScope scope(Env());
|
0,
|
||||||
Napi::Object res = Napi::Array::New(Env(), result.size());
|
1
|
||||||
for (uint64_t i = 0; i < result.size(); ++i) {
|
);
|
||||||
Napi::Object tmp = Napi::Array::New(Env(), 3);
|
}
|
||||||
for (uint64_t j = 0; j < 3; ++j) {
|
}
|
||||||
tmp[j] = Napi::String::New(Env(), result[i][j]);
|
|
||||||
}
|
~ProgressWorker() {
|
||||||
res[i] = tmp;
|
if (tsfn) {
|
||||||
|
// Make sure to release the thread-safe function on destruction
|
||||||
|
tsfn.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Execute() override {
|
||||||
|
// Use custom run function with progress callback support
|
||||||
|
run_with_progress(params, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnOK() override {
|
||||||
|
Napi::HandleScope scope(Env());
|
||||||
|
Napi::Object res = Napi::Array::New(Env(), result.size());
|
||||||
|
for (uint64_t i = 0; i < result.size(); ++i) {
|
||||||
|
Napi::Object tmp = Napi::Array::New(Env(), 3);
|
||||||
|
for (uint64_t j = 0; j < 3; ++j) {
|
||||||
|
tmp[j] = Napi::String::New(Env(), result[i][j]);
|
||||||
|
}
|
||||||
|
res[i] = tmp;
|
||||||
|
}
|
||||||
|
Callback().Call({Env().Null(), res});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress callback function - using thread-safe function
|
||||||
|
void OnProgress(int progress) {
|
||||||
|
if (tsfn) {
|
||||||
|
// Use thread-safe function to call JavaScript callback
|
||||||
|
auto callback = [progress](Napi::Env env, Napi::Function jsCallback) {
|
||||||
|
jsCallback.Call({Napi::Number::New(env, progress)});
|
||||||
|
};
|
||||||
|
|
||||||
|
tsfn.BlockingCall(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Callback().Call({Env().Null(), res});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
whisper_params params;
|
whisper_params params;
|
||||||
std::vector<std::vector<std::string>> result;
|
std::vector<std::vector<std::string>> result;
|
||||||
|
Napi::Env env;
|
||||||
|
Napi::ThreadSafeFunction tsfn;
|
||||||
|
|
||||||
|
// Custom run function with progress callback support
|
||||||
|
int run_with_progress(whisper_params ¶ms, std::vector<std::vector<std::string>> &result) {
|
||||||
|
if (params.no_prints) {
|
||||||
|
whisper_log_set(cb_log_disable, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.fname_inp.empty() && params.pcmf32.empty()) {
|
||||||
|
fprintf(stderr, "error: no input files or audio buffer specified\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.language != "auto" && whisper_lang_id(params.language.c_str()) == -1) {
|
||||||
|
fprintf(stderr, "error: unknown language '%s'\n", params.language.c_str());
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// whisper init
|
||||||
|
struct whisper_context_params cparams = whisper_context_default_params();
|
||||||
|
cparams.use_gpu = params.use_gpu;
|
||||||
|
cparams.flash_attn = params.flash_attn;
|
||||||
|
struct whisper_context * ctx = whisper_init_from_file_with_params(params.model.c_str(), cparams);
|
||||||
|
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
fprintf(stderr, "error: failed to initialize whisper context\n");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If params.pcmf32 provides, set params.fname_inp as "buffer"
|
||||||
|
if (!params.pcmf32.empty()) {
|
||||||
|
fprintf(stderr, "info: using audio buffer as input\n");
|
||||||
|
params.fname_inp.clear();
|
||||||
|
params.fname_inp.emplace_back("buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int f = 0; f < (int) params.fname_inp.size(); ++f) {
|
||||||
|
const auto fname_inp = params.fname_inp[f];
|
||||||
|
const auto fname_out = f < (int)params.fname_out.size() && !params.fname_out[f].empty() ? params.fname_out[f] : params.fname_inp[f];
|
||||||
|
|
||||||
|
std::vector<float> pcmf32; // mono-channel F32 PCM
|
||||||
|
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
|
||||||
|
|
||||||
|
// If params.pcmf32 is empty, read input audio file
|
||||||
|
if (params.pcmf32.empty()) {
|
||||||
|
if (!::read_audio_data(fname_inp, pcmf32, pcmf32s, params.diarize)) {
|
||||||
|
fprintf(stderr, "error: failed to read audio file '%s'\n", fname_inp.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pcmf32 = params.pcmf32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print system info
|
||||||
|
if (!params.no_prints) {
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "system_info: n_threads = %d / %d | %s\n",
|
||||||
|
params.n_threads*params.n_processors, std::thread::hardware_concurrency(), whisper_print_system_info());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print processing info
|
||||||
|
if (!params.no_prints) {
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
if (!whisper_is_multilingual(ctx)) {
|
||||||
|
if (params.language != "en" || params.translate) {
|
||||||
|
params.language = "en";
|
||||||
|
params.translate = false;
|
||||||
|
fprintf(stderr, "%s: WARNING: model is not multilingual, ignoring language and translation options\n", __func__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%s: processing '%s' (%d samples, %.1f sec), %d threads, %d processors, lang = %s, task = %s, timestamps = %d, audio_ctx = %d ...\n",
|
||||||
|
__func__, fname_inp.c_str(), int(pcmf32.size()), float(pcmf32.size())/WHISPER_SAMPLE_RATE,
|
||||||
|
params.n_threads, params.n_processors,
|
||||||
|
params.language.c_str(),
|
||||||
|
params.translate ? "translate" : "transcribe",
|
||||||
|
params.no_timestamps ? 0 : 1,
|
||||||
|
params.audio_ctx);
|
||||||
|
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run inference
|
||||||
|
{
|
||||||
|
whisper_full_params wparams = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
|
||||||
|
|
||||||
|
wparams.strategy = params.beam_size > 1 ? WHISPER_SAMPLING_BEAM_SEARCH : WHISPER_SAMPLING_GREEDY;
|
||||||
|
|
||||||
|
wparams.print_realtime = false;
|
||||||
|
wparams.print_progress = params.print_progress;
|
||||||
|
wparams.print_timestamps = !params.no_timestamps;
|
||||||
|
wparams.print_special = params.print_special;
|
||||||
|
wparams.translate = params.translate;
|
||||||
|
wparams.language = params.language.c_str();
|
||||||
|
wparams.n_threads = params.n_threads;
|
||||||
|
wparams.n_max_text_ctx = params.max_context >= 0 ? params.max_context : wparams.n_max_text_ctx;
|
||||||
|
wparams.offset_ms = params.offset_t_ms;
|
||||||
|
wparams.duration_ms = params.duration_ms;
|
||||||
|
|
||||||
|
wparams.token_timestamps = params.output_wts || params.max_len > 0;
|
||||||
|
wparams.thold_pt = params.word_thold;
|
||||||
|
wparams.entropy_thold = params.entropy_thold;
|
||||||
|
wparams.logprob_thold = params.logprob_thold;
|
||||||
|
wparams.max_len = params.output_wts && params.max_len == 0 ? 60 : params.max_len;
|
||||||
|
wparams.audio_ctx = params.audio_ctx;
|
||||||
|
|
||||||
|
wparams.greedy.best_of = params.best_of;
|
||||||
|
wparams.beam_search.beam_size = params.beam_size;
|
||||||
|
|
||||||
|
wparams.initial_prompt = params.prompt.c_str();
|
||||||
|
|
||||||
|
wparams.no_timestamps = params.no_timestamps;
|
||||||
|
|
||||||
|
whisper_print_user_data user_data = { ¶ms, &pcmf32s };
|
||||||
|
|
||||||
|
// This callback is called for each new segment
|
||||||
|
if (!wparams.print_realtime) {
|
||||||
|
wparams.new_segment_callback = whisper_print_segment_callback;
|
||||||
|
wparams.new_segment_callback_user_data = &user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set progress callback
|
||||||
|
wparams.progress_callback = [](struct whisper_context * /*ctx*/, struct whisper_state * /*state*/, int progress, void * user_data) {
|
||||||
|
ProgressWorker* worker = static_cast<ProgressWorker*>(user_data);
|
||||||
|
worker->OnProgress(progress);
|
||||||
|
};
|
||||||
|
wparams.progress_callback_user_data = this;
|
||||||
|
|
||||||
|
// Abort mechanism example
|
||||||
|
{
|
||||||
|
static bool is_aborted = false; // Note: this should be atomic to avoid data races
|
||||||
|
|
||||||
|
wparams.encoder_begin_callback = [](struct whisper_context * /*ctx*/, struct whisper_state * /*state*/, void * user_data) {
|
||||||
|
bool is_aborted = *(bool*)user_data;
|
||||||
|
return !is_aborted;
|
||||||
|
};
|
||||||
|
wparams.encoder_begin_callback_user_data = &is_aborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (whisper_full_parallel(ctx, wparams, pcmf32.data(), pcmf32.size(), params.n_processors) != 0) {
|
||||||
|
fprintf(stderr, "failed to process audio\n");
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
|
result.resize(n_segments);
|
||||||
|
for (int i = 0; i < n_segments; ++i) {
|
||||||
|
const char * text = whisper_full_get_segment_text(ctx, i);
|
||||||
|
const int64_t t0 = whisper_full_get_segment_t0(ctx, i);
|
||||||
|
const int64_t t1 = whisper_full_get_segment_t1(ctx, i);
|
||||||
|
|
||||||
|
result[i].emplace_back(to_timestamp(t0, params.comma_in_time));
|
||||||
|
result[i].emplace_back(to_timestamp(t1, params.comma_in_time));
|
||||||
|
result[i].emplace_back(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
whisper_print_timings(ctx);
|
||||||
|
whisper_free(ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Napi::Value whisper(const Napi::CallbackInfo& info) {
|
Napi::Value whisper(const Napi::CallbackInfo& info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
if (info.Length() <= 0 || !info[0].IsObject()) {
|
if (info.Length() <= 0 || !info[0].IsObject()) {
|
||||||
@ -331,6 +367,29 @@ Napi::Value whisper(const Napi::CallbackInfo& info) {
|
|||||||
int32_t audio_ctx = whisper_params.Get("audio_ctx").As<Napi::Number>();
|
int32_t audio_ctx = whisper_params.Get("audio_ctx").As<Napi::Number>();
|
||||||
bool comma_in_time = whisper_params.Get("comma_in_time").As<Napi::Boolean>();
|
bool comma_in_time = whisper_params.Get("comma_in_time").As<Napi::Boolean>();
|
||||||
int32_t max_len = whisper_params.Get("max_len").As<Napi::Number>();
|
int32_t max_len = whisper_params.Get("max_len").As<Napi::Number>();
|
||||||
|
|
||||||
|
// Add support for max_context
|
||||||
|
int32_t max_context = -1;
|
||||||
|
if (whisper_params.Has("max_context") && whisper_params.Get("max_context").IsNumber()) {
|
||||||
|
max_context = whisper_params.Get("max_context").As<Napi::Number>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// support prompt
|
||||||
|
std::string prompt = "";
|
||||||
|
if (whisper_params.Has("prompt") && whisper_params.Get("prompt").IsString()) {
|
||||||
|
prompt = whisper_params.Get("prompt").As<Napi::String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add support for print_progress
|
||||||
|
bool print_progress = false;
|
||||||
|
if (whisper_params.Has("print_progress")) {
|
||||||
|
print_progress = whisper_params.Get("print_progress").As<Napi::Boolean>();
|
||||||
|
}
|
||||||
|
// Add support for progress_callback
|
||||||
|
Napi::Function progress_callback;
|
||||||
|
if (whisper_params.Has("progress_callback") && whisper_params.Get("progress_callback").IsFunction()) {
|
||||||
|
progress_callback = whisper_params.Get("progress_callback").As<Napi::Function>();
|
||||||
|
}
|
||||||
|
|
||||||
Napi::Value pcmf32Value = whisper_params.Get("pcmf32");
|
Napi::Value pcmf32Value = whisper_params.Get("pcmf32");
|
||||||
std::vector<float> pcmf32_vec;
|
std::vector<float> pcmf32_vec;
|
||||||
@ -354,9 +413,13 @@ Napi::Value whisper(const Napi::CallbackInfo& info) {
|
|||||||
params.pcmf32 = pcmf32_vec;
|
params.pcmf32 = pcmf32_vec;
|
||||||
params.comma_in_time = comma_in_time;
|
params.comma_in_time = comma_in_time;
|
||||||
params.max_len = max_len;
|
params.max_len = max_len;
|
||||||
|
params.max_context = max_context;
|
||||||
|
params.print_progress = print_progress;
|
||||||
|
params.prompt = prompt;
|
||||||
|
|
||||||
Napi::Function callback = info[1].As<Napi::Function>();
|
Napi::Function callback = info[1].As<Napi::Function>();
|
||||||
Worker* worker = new Worker(callback, params);
|
// Create a new Worker class with progress callback support
|
||||||
|
ProgressWorker* worker = new ProgressWorker(callback, params, progress_callback, env);
|
||||||
worker->Queue();
|
worker->Queue();
|
||||||
return env.Undefined();
|
return env.Undefined();
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ const whisperParams = {
|
|||||||
no_timestamps: false,
|
no_timestamps: false,
|
||||||
audio_ctx: 0,
|
audio_ctx: 0,
|
||||||
max_len: 0,
|
max_len: 0,
|
||||||
|
progress_callback: (progress) => {
|
||||||
|
console.log(`progress: ${progress}%`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const arguments = process.argv.slice(2);
|
const arguments = process.argv.slice(2);
|
||||||
|
@ -35,7 +35,7 @@ set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
|
|||||||
-s INITIAL_MEMORY=2000MB \
|
-s INITIAL_MEMORY=2000MB \
|
||||||
-s TOTAL_MEMORY=2000MB \
|
-s TOTAL_MEMORY=2000MB \
|
||||||
-s FORCE_FILESYSTEM=1 \
|
-s FORCE_FILESYSTEM=1 \
|
||||||
-s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap']\" \
|
-s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap', 'HEAPU8']\" \
|
||||||
${EXTRA_FLAGS} \
|
${EXTRA_FLAGS} \
|
||||||
")
|
")
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Benchmark the performance of whisper.cpp in the browser using WebAssembly
|
Benchmark the performance of whisper.cpp in the browser using WebAssembly
|
||||||
|
|
||||||
Link: https://whisper.ggerganov.com/bench/
|
Link: https://ggerganov.github.io/whisper.cpp/bench.wasm
|
||||||
|
|
||||||
Terminal version: [examples/bench](/examples/bench)
|
Terminal version: [examples/bench](/examples/bench)
|
||||||
|
|
||||||
@ -15,8 +15,23 @@ cd whisper.cpp
|
|||||||
mkdir build-em && cd build-em
|
mkdir build-em && cd build-em
|
||||||
emcmake cmake ..
|
emcmake cmake ..
|
||||||
make -j
|
make -j
|
||||||
|
```
|
||||||
|
The example can then be started by running a local HTTP server:
|
||||||
|
```console
|
||||||
|
python3 examples/server.py
|
||||||
|
```
|
||||||
|
And then opening a browser to the following URL:
|
||||||
|
http://localhost:8000/bench.wasm
|
||||||
|
|
||||||
|
To run the example in a different server, you need to copy the following files
|
||||||
|
to the server's HTTP path:
|
||||||
|
```
|
||||||
# copy the produced page to your HTTP path
|
# copy the produced page to your HTTP path
|
||||||
cp bin/bench.wasm/* /path/to/html/
|
cp bin/bench.wasm/* /path/to/html/
|
||||||
|
cp bin/libbench.js /path/to/html/
|
||||||
cp bin/libbench.worker.js /path/to/html/
|
cp bin/libbench.worker.js /path/to/html/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 📝 **Note:** As of Emscripten 3.1.58 (April 2024), separate worker.js files are no
|
||||||
|
> longer generated and the worker is embedded in the main JS file. So the worker
|
||||||
|
> file will not be geneated for versions later than `3.1.58`.
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script src="../coi-serviceworker.js"></script>
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main-container">
|
<div id="main-container">
|
||||||
@ -36,11 +38,10 @@
|
|||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<b>More examples:</b>
|
<b>More examples:</b>
|
||||||
<a href="https://whisper.ggerganov.com/">main</a> |
|
<a href="../">main</a> |
|
||||||
<a href="https://whisper.ggerganov.com/bench">bench</a> |
|
<a href="../bench.wasm/">bench</a> |
|
||||||
<a href="https://whisper.ggerganov.com/stream">stream</a> |
|
<a href="../stream.wasm">stream</a> |
|
||||||
<a href="https://whisper.ggerganov.com/command">command</a> |
|
<a href="../command.wasm/">command</a> |
|
||||||
<a href="https://whisper.ggerganov.com/talk">talk</a> |
|
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ A very basic tool for benchmarking the inference performance on your device. The
|
|||||||
the transformer on some random audio data and records the execution time. This way we can have an objective comparison
|
the transformer on some random audio data and records the execution time. This way we can have an objective comparison
|
||||||
of the performance of the model for various setups.
|
of the performance of the model for various setups.
|
||||||
|
|
||||||
Benchmark results are tracked in the following Github issue: https://github.com/ggerganov/whisper.cpp/issues/89
|
Benchmark results are tracked in the following Github issue: https://github.com/ggml-org/whisper.cpp/issues/89
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# run the bench too on the small.en model using 4 threads
|
# run the bench too on the small.en model using 4 threads
|
||||||
@ -40,7 +40,7 @@ system_info: n_threads = 4 | AVX2 = 0 | AVX512 = 0 | NEON = 1 | FP16_VA = 1 | WA
|
|||||||
|
|
||||||
If you wish, you can submit these results here:
|
If you wish, you can submit these results here:
|
||||||
|
|
||||||
https://github.com/ggerganov/whisper.cpp/issues/89
|
https://github.com/ggml-org/whisper.cpp/issues/89
|
||||||
|
|
||||||
Please include the following information:
|
Please include the following information:
|
||||||
|
|
||||||
|
@ -50,11 +50,11 @@ void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params & para
|
|||||||
fprintf(stderr, " -t N, --threads N [%-7d] number of threads to use during computation\n", params.n_threads);
|
fprintf(stderr, " -t N, --threads N [%-7d] number of threads to use during computation\n", params.n_threads);
|
||||||
fprintf(stderr, " -m FNAME, --model FNAME [%-7s] model path\n", params.model.c_str());
|
fprintf(stderr, " -m FNAME, --model FNAME [%-7s] model path\n", params.model.c_str());
|
||||||
fprintf(stderr, " -w N, --what N [%-7d] what to benchmark:\n", params.what);
|
fprintf(stderr, " -w N, --what N [%-7d] what to benchmark:\n", params.what);
|
||||||
fprintf(stderr, " -ng, --no-gpu [%-7s] disable GPU\n", params.use_gpu ? "false" : "true");
|
|
||||||
fprintf(stderr, " -fa, --flash-attn [%-7s] enable flash attention\n", params.flash_attn ? "true" : "false");
|
|
||||||
fprintf(stderr, " %-7s 0 - whisper\n", "");
|
fprintf(stderr, " %-7s 0 - whisper\n", "");
|
||||||
fprintf(stderr, " %-7s 1 - memcpy\n", "");
|
fprintf(stderr, " %-7s 1 - memcpy\n", "");
|
||||||
fprintf(stderr, " %-7s 2 - ggml_mul_mat\n", "");
|
fprintf(stderr, " %-7s 2 - ggml_mul_mat\n", "");
|
||||||
|
fprintf(stderr, " -ng, --no-gpu [%-7s] disable GPU\n", params.use_gpu ? "false" : "true");
|
||||||
|
fprintf(stderr, " -fa, --flash-attn [%-7s] enable flash attention\n", params.flash_attn ? "true" : "false");
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ It can be used as a reference for using the `whisper.cpp` library in other proje
|
|||||||
```
|
```
|
||||||
./build/bin/whisper-cli -h
|
./build/bin/whisper-cli -h
|
||||||
|
|
||||||
usage: ./build-pkg/bin/whisper-cli [options] file0.wav file1.wav ...
|
usage: ./build/bin/whisper-cli [options] file0 file1 ...
|
||||||
|
supported audio formats: flac, mp3, ogg, wav
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-h, --help [default] show this help message and exit
|
-h, --help [default] show this help message and exit
|
||||||
@ -24,6 +25,7 @@ options:
|
|||||||
-wt N, --word-thold N [0.01 ] word timestamp probability threshold
|
-wt N, --word-thold N [0.01 ] word timestamp probability threshold
|
||||||
-et N, --entropy-thold N [2.40 ] entropy threshold for decoder fail
|
-et N, --entropy-thold N [2.40 ] entropy threshold for decoder fail
|
||||||
-lpt N, --logprob-thold N [-1.00 ] log probability threshold for decoder fail
|
-lpt N, --logprob-thold N [-1.00 ] log probability threshold for decoder fail
|
||||||
|
-nth N, --no-speech-thold N [0.60 ] no speech threshold
|
||||||
-tp, --temperature N [0.00 ] The sampling temperature, between 0 and 1
|
-tp, --temperature N [0.00 ] The sampling temperature, between 0 and 1
|
||||||
-tpi, --temperature-inc N [0.20 ] The increment of temperature, between 0 and 1
|
-tpi, --temperature-inc N [0.20 ] The increment of temperature, between 0 and 1
|
||||||
-debug, --debug-mode [false ] enable debug mode (eg. dump log_mel)
|
-debug, --debug-mode [false ] enable debug mode (eg. dump log_mel)
|
||||||
@ -50,12 +52,13 @@ options:
|
|||||||
-dl, --detect-language [false ] exit after automatically detecting language
|
-dl, --detect-language [false ] exit after automatically detecting language
|
||||||
--prompt PROMPT [ ] initial prompt (max n_text_ctx/2 tokens)
|
--prompt PROMPT [ ] initial prompt (max n_text_ctx/2 tokens)
|
||||||
-m FNAME, --model FNAME [models/ggml-base.en.bin] model path
|
-m FNAME, --model FNAME [models/ggml-base.en.bin] model path
|
||||||
-f FNAME, --file FNAME [ ] input WAV file path
|
-f FNAME, --file FNAME [ ] input audio file path
|
||||||
-oved D, --ov-e-device DNAME [CPU ] the OpenVINO device used for encode inference
|
-oved D, --ov-e-device DNAME [CPU ] the OpenVINO device used for encode inference
|
||||||
-dtw MODEL --dtw MODEL [ ] compute token-level timestamps
|
-dtw MODEL --dtw MODEL [ ] compute token-level timestamps
|
||||||
-ls, --log-score [false ] log best decoder scores of tokens
|
-ls, --log-score [false ] log best decoder scores of tokens
|
||||||
-ng, --no-gpu [false ] disable GPU
|
-ng, --no-gpu [false ] disable GPU
|
||||||
-fa, --flash-attn [false ] flash attention
|
-fa, --flash-attn [false ] flash attention
|
||||||
|
-sns, --suppress-nst [false ] suppress non-speech tokens
|
||||||
--suppress-regex REGEX [ ] regular expression matching tokens to suppress
|
--suppress-regex REGEX [ ] regular expression matching tokens to suppress
|
||||||
--grammar GRAMMAR [ ] GBNF grammar to guide decoding
|
--grammar GRAMMAR [ ] GBNF grammar to guide decoding
|
||||||
--grammar-rule RULE [ ] top-level GBNF grammar rule name
|
--grammar-rule RULE [ ] top-level GBNF grammar rule name
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "common-whisper.h"
|
||||||
|
|
||||||
#include "whisper.h"
|
#include "whisper.h"
|
||||||
#include "grammar-parser.h"
|
#include "grammar-parser.h"
|
||||||
@ -6,19 +7,17 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <regex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#pragma warning(disable: 4244 4267) // possible loss of data
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// helper function to replace substrings
|
// helper function to replace substrings
|
||||||
@ -99,6 +98,16 @@ struct whisper_params {
|
|||||||
std::vector<std::string> fname_out = {};
|
std::vector<std::string> fname_out = {};
|
||||||
|
|
||||||
grammar_parser::parse_state grammar_parsed;
|
grammar_parser::parse_state grammar_parsed;
|
||||||
|
|
||||||
|
// Voice Activity Detection (VAD) parameters
|
||||||
|
bool vad = false;
|
||||||
|
std::string vad_model = "";
|
||||||
|
float vad_threshold = 0.5f;
|
||||||
|
int vad_min_speech_duration_ms = 250;
|
||||||
|
int vad_min_silence_duration_ms = 100;
|
||||||
|
float vad_max_speech_duration_s = FLT_MAX;
|
||||||
|
int vad_speech_pad_ms = 30;
|
||||||
|
float vad_samples_overlap = 0.1f;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void whisper_print_usage(int argc, char ** argv, const whisper_params & params);
|
static void whisper_print_usage(int argc, char ** argv, const whisper_params & params);
|
||||||
@ -187,6 +196,15 @@ static bool whisper_params_parse(int argc, char ** argv, whisper_params & params
|
|||||||
else if ( arg == "--grammar") { params.grammar = ARGV_NEXT; }
|
else if ( arg == "--grammar") { params.grammar = ARGV_NEXT; }
|
||||||
else if ( arg == "--grammar-rule") { params.grammar_rule = ARGV_NEXT; }
|
else if ( arg == "--grammar-rule") { params.grammar_rule = ARGV_NEXT; }
|
||||||
else if ( arg == "--grammar-penalty") { params.grammar_penalty = std::stof(ARGV_NEXT); }
|
else if ( arg == "--grammar-penalty") { params.grammar_penalty = std::stof(ARGV_NEXT); }
|
||||||
|
// Voice Activity Detection (VAD)
|
||||||
|
else if ( arg == "--vad") { params.vad = true; }
|
||||||
|
else if (arg == "-vm" || arg == "--vad-model") { params.vad_model = ARGV_NEXT; }
|
||||||
|
else if (arg == "-vt" || arg == "--vad-threshold") { params.vad_threshold = std::stof(ARGV_NEXT); }
|
||||||
|
else if (arg == "-vsd" || arg == "--vad-min-speech-duration-ms") { params.vad_min_speech_duration_ms = std::stoi(ARGV_NEXT); }
|
||||||
|
else if (arg == "-vsd" || arg == "--vad-min-silence-duration-ms") { params.vad_min_speech_duration_ms = std::stoi(ARGV_NEXT); }
|
||||||
|
else if (arg == "-vmsd" || arg == "--vad-max-speech-duration-s") { params.vad_max_speech_duration_s = std::stof(ARGV_NEXT); }
|
||||||
|
else if (arg == "-vp" || arg == "--vad-speech-pad-ms") { params.vad_speech_pad_ms = std::stoi(ARGV_NEXT); }
|
||||||
|
else if (arg == "-vo" || arg == "--vad-samples-overlap") { params.vad_samples_overlap = std::stof(ARGV_NEXT); }
|
||||||
else {
|
else {
|
||||||
fprintf(stderr, "error: unknown argument: %s\n", arg.c_str());
|
fprintf(stderr, "error: unknown argument: %s\n", arg.c_str());
|
||||||
whisper_print_usage(argc, argv, params);
|
whisper_print_usage(argc, argv, params);
|
||||||
@ -199,7 +217,8 @@ static bool whisper_params_parse(int argc, char ** argv, whisper_params & params
|
|||||||
|
|
||||||
static void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params & params) {
|
static void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params & params) {
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
fprintf(stderr, "usage: %s [options] file0.wav file1.wav ...\n", argv[0]);
|
fprintf(stderr, "usage: %s [options] file0 file1 ...\n", argv[0]);
|
||||||
|
fprintf(stderr, "supported audio formats: flac, mp3, ogg, wav\n");
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
fprintf(stderr, "options:\n");
|
fprintf(stderr, "options:\n");
|
||||||
fprintf(stderr, " -h, --help [default] show this help message and exit\n");
|
fprintf(stderr, " -h, --help [default] show this help message and exit\n");
|
||||||
@ -244,7 +263,7 @@ static void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params
|
|||||||
fprintf(stderr, " -dl, --detect-language [%-7s] exit after automatically detecting language\n", params.detect_language ? "true" : "false");
|
fprintf(stderr, " -dl, --detect-language [%-7s] exit after automatically detecting language\n", params.detect_language ? "true" : "false");
|
||||||
fprintf(stderr, " --prompt PROMPT [%-7s] initial prompt (max n_text_ctx/2 tokens)\n", params.prompt.c_str());
|
fprintf(stderr, " --prompt PROMPT [%-7s] initial prompt (max n_text_ctx/2 tokens)\n", params.prompt.c_str());
|
||||||
fprintf(stderr, " -m FNAME, --model FNAME [%-7s] model path\n", params.model.c_str());
|
fprintf(stderr, " -m FNAME, --model FNAME [%-7s] model path\n", params.model.c_str());
|
||||||
fprintf(stderr, " -f FNAME, --file FNAME [%-7s] input WAV file path\n", "");
|
fprintf(stderr, " -f FNAME, --file FNAME [%-7s] input audio file path\n", "");
|
||||||
fprintf(stderr, " -oved D, --ov-e-device DNAME [%-7s] the OpenVINO device used for encode inference\n", params.openvino_encode_device.c_str());
|
fprintf(stderr, " -oved D, --ov-e-device DNAME [%-7s] the OpenVINO device used for encode inference\n", params.openvino_encode_device.c_str());
|
||||||
fprintf(stderr, " -dtw MODEL --dtw MODEL [%-7s] compute token-level timestamps\n", params.dtw.c_str());
|
fprintf(stderr, " -dtw MODEL --dtw MODEL [%-7s] compute token-level timestamps\n", params.dtw.c_str());
|
||||||
fprintf(stderr, " -ls, --log-score [%-7s] log best decoder scores of tokens\n", params.log_score?"true":"false");
|
fprintf(stderr, " -ls, --log-score [%-7s] log best decoder scores of tokens\n", params.log_score?"true":"false");
|
||||||
@ -255,6 +274,18 @@ static void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params
|
|||||||
fprintf(stderr, " --grammar GRAMMAR [%-7s] GBNF grammar to guide decoding\n", params.grammar.c_str());
|
fprintf(stderr, " --grammar GRAMMAR [%-7s] GBNF grammar to guide decoding\n", params.grammar.c_str());
|
||||||
fprintf(stderr, " --grammar-rule RULE [%-7s] top-level GBNF grammar rule name\n", params.grammar_rule.c_str());
|
fprintf(stderr, " --grammar-rule RULE [%-7s] top-level GBNF grammar rule name\n", params.grammar_rule.c_str());
|
||||||
fprintf(stderr, " --grammar-penalty N [%-7.1f] scales down logits of nongrammar tokens\n", params.grammar_penalty);
|
fprintf(stderr, " --grammar-penalty N [%-7.1f] scales down logits of nongrammar tokens\n", params.grammar_penalty);
|
||||||
|
// Voice Activity Detection (VAD) parameters
|
||||||
|
fprintf(stderr, "\nVoice Activity Detection (VAD) options:\n");
|
||||||
|
fprintf(stderr, " --vad [%-7s] enable Voice Activity Detection (VAD)\n", params.vad ? "true" : "false");
|
||||||
|
fprintf(stderr, " -vm FNAME, --vad-model FNAME [%-7s] VAD model path\n", params.vad_model.c_str());
|
||||||
|
fprintf(stderr, " -vt N, --vad-threshold N [%-7.2f] VAD threshold for speech recognition\n", params.vad_threshold);
|
||||||
|
fprintf(stderr, " -vspd N, --vad-min-speech-duration-ms N [%-7d] VAD min speech duration (0.0-1.0)\n", params.vad_min_speech_duration_ms);
|
||||||
|
fprintf(stderr, " -vsd N, --vad-min-silence-duration-ms N [%-7d] VAD min silence duration (to split segments)\n", params.vad_min_silence_duration_ms);
|
||||||
|
fprintf(stderr, " -vmsd N, --vad-max-speech-duration-s N [%-7s] VAD max speech duration (auto-split longer)\n", params.vad_max_speech_duration_s == FLT_MAX ?
|
||||||
|
std::string("FLT_MAX").c_str() :
|
||||||
|
std::to_string(params.vad_max_speech_duration_s).c_str());
|
||||||
|
fprintf(stderr, " -vp N, --vad-speech-pad-ms N [%-7d] VAD speech padding (extend segments)\n", params.vad_speech_pad_ms);
|
||||||
|
fprintf(stderr, " -vo N, --vad-samples-overlap N [%-7.2f] VAD samples overlap (seconds between segments)\n", params.vad_samples_overlap);
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,15 +407,7 @@ static void whisper_print_segment_callback(struct whisper_context * ctx, struct
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool output_txt(struct whisper_context * ctx, const char * fname, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
static void output_txt(struct whisper_context * ctx, std::ofstream & fout, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
||||||
std::ofstream fout(fname);
|
|
||||||
if (!fout.is_open()) {
|
|
||||||
fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
|
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
for (int i = 0; i < n_segments; ++i) {
|
for (int i = 0; i < n_segments; ++i) {
|
||||||
const char * text = whisper_full_get_segment_text(ctx, i);
|
const char * text = whisper_full_get_segment_text(ctx, i);
|
||||||
@ -399,19 +422,9 @@ static bool output_txt(struct whisper_context * ctx, const char * fname, const w
|
|||||||
|
|
||||||
fout << speaker << text << "\n";
|
fout << speaker << text << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool output_vtt(struct whisper_context * ctx, const char * fname, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
static void output_vtt(struct whisper_context * ctx, std::ofstream & fout, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
||||||
std::ofstream fout(fname);
|
|
||||||
if (!fout.is_open()) {
|
|
||||||
fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
|
|
||||||
fout << "WEBVTT\n\n";
|
fout << "WEBVTT\n\n";
|
||||||
|
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
@ -431,19 +444,9 @@ static bool output_vtt(struct whisper_context * ctx, const char * fname, const w
|
|||||||
fout << to_timestamp(t0) << " --> " << to_timestamp(t1) << "\n";
|
fout << to_timestamp(t0) << " --> " << to_timestamp(t1) << "\n";
|
||||||
fout << speaker << text << "\n\n";
|
fout << speaker << text << "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool output_srt(struct whisper_context * ctx, const char * fname, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
static void output_srt(struct whisper_context * ctx, std::ofstream & fout, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
||||||
std::ofstream fout(fname);
|
|
||||||
if (!fout.is_open()) {
|
|
||||||
fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
|
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
for (int i = 0; i < n_segments; ++i) {
|
for (int i = 0; i < n_segments; ++i) {
|
||||||
const char * text = whisper_full_get_segment_text(ctx, i);
|
const char * text = whisper_full_get_segment_text(ctx, i);
|
||||||
@ -460,8 +463,6 @@ static bool output_srt(struct whisper_context * ctx, const char * fname, const w
|
|||||||
fout << to_timestamp(t0, true) << " --> " << to_timestamp(t1, true) << "\n";
|
fout << to_timestamp(t0, true) << " --> " << to_timestamp(t1, true) << "\n";
|
||||||
fout << speaker << text << "\n\n";
|
fout << speaker << text << "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char * escape_double_quotes_and_backslashes(const char * str) {
|
static char * escape_double_quotes_and_backslashes(const char * str) {
|
||||||
@ -527,15 +528,7 @@ static char * escape_double_quotes_in_csv(const char * str) {
|
|||||||
return escaped;
|
return escaped;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool output_csv(struct whisper_context * ctx, const char * fname, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
static void output_csv(struct whisper_context * ctx, std::ofstream & fout, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
||||||
std::ofstream fout(fname);
|
|
||||||
if (!fout.is_open()) {
|
|
||||||
fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
|
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
fout << "start,end,";
|
fout << "start,end,";
|
||||||
if (params.diarize && pcmf32s.size() == 2)
|
if (params.diarize && pcmf32s.size() == 2)
|
||||||
@ -558,14 +551,9 @@ static bool output_csv(struct whisper_context * ctx, const char * fname, const w
|
|||||||
}
|
}
|
||||||
fout << "\"" << text_escaped << "\"\n";
|
fout << "\"" << text_escaped << "\"\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool output_score(struct whisper_context * ctx, const char * fname, const whisper_params & /*params*/, std::vector<std::vector<float>> /*pcmf32s*/) {
|
static void output_score(struct whisper_context * ctx, std::ofstream & fout, const whisper_params & /*params*/, std::vector<std::vector<float>> /*pcmf32s*/) {
|
||||||
std::ofstream fout(fname);
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
|
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
// fprintf(stderr,"segments: %d\n",n_segments);
|
// fprintf(stderr,"segments: %d\n",n_segments);
|
||||||
for (int i = 0; i < n_segments; ++i) {
|
for (int i = 0; i < n_segments; ++i) {
|
||||||
@ -578,16 +566,14 @@ static bool output_score(struct whisper_context * ctx, const char * fname, const
|
|||||||
// fprintf(stderr,"token: %s %f\n",token,probability);
|
// fprintf(stderr,"token: %s %f\n",token,probability);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool output_json(
|
static void output_json(
|
||||||
struct whisper_context * ctx,
|
struct whisper_context * ctx,
|
||||||
const char * fname,
|
std::ofstream & fout,
|
||||||
const whisper_params & params,
|
const whisper_params & params,
|
||||||
std::vector<std::vector<float>> pcmf32s,
|
std::vector<std::vector<float>> pcmf32s) {
|
||||||
bool full) {
|
const bool full = params.output_jsn_full;
|
||||||
std::ofstream fout(fname);
|
|
||||||
int indent = 0;
|
int indent = 0;
|
||||||
|
|
||||||
auto doindent = [&]() {
|
auto doindent = [&]() {
|
||||||
@ -667,12 +653,6 @@ static bool output_json(
|
|||||||
end_obj(end);
|
end_obj(end);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!fout.is_open()) {
|
|
||||||
fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
start_obj(nullptr);
|
start_obj(nullptr);
|
||||||
value_s("systeminfo", whisper_print_system_info(), false);
|
value_s("systeminfo", whisper_print_system_info(), false);
|
||||||
start_obj("model");
|
start_obj("model");
|
||||||
@ -746,17 +726,12 @@ static bool output_json(
|
|||||||
|
|
||||||
end_arr(true);
|
end_arr(true);
|
||||||
end_obj(true);
|
end_obj(true);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// karaoke video generation
|
// karaoke video generation
|
||||||
// outputs a bash script that uses ffmpeg to generate a video with the subtitles
|
// outputs a bash script that uses ffmpeg to generate a video with the subtitles
|
||||||
// TODO: font parameter adjustments
|
// TODO: font parameter adjustments
|
||||||
static bool output_wts(struct whisper_context * ctx, const char * fname, const char * fname_inp, const whisper_params & params, float t_sec, std::vector<std::vector<float>> pcmf32s) {
|
static bool output_wts(struct whisper_context * ctx, std::ofstream & fout, const whisper_params & params, std::vector<std::vector<float>> pcmf32s, const char * fname_inp, float t_sec, const char * fname_out) {
|
||||||
std::ofstream fout(fname);
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
|
|
||||||
static const char * font = params.font_path.c_str();
|
static const char * font = params.font_path.c_str();
|
||||||
|
|
||||||
std::ifstream fin(font);
|
std::ifstream fin(font);
|
||||||
@ -872,20 +847,12 @@ static bool output_wts(struct whisper_context * ctx, const char * fname, const c
|
|||||||
|
|
||||||
fout.close();
|
fout.close();
|
||||||
|
|
||||||
fprintf(stderr, "%s: run 'source %s' to generate karaoke video\n", __func__, fname);
|
fprintf(stderr, "# %s: run 'source %s' to generate karaoke video\n", __func__, fname_out);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool output_lrc(struct whisper_context * ctx, const char * fname, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
static void output_lrc(struct whisper_context * ctx, std::ofstream & fout, const whisper_params & params, std::vector<std::vector<float>> pcmf32s) {
|
||||||
std::ofstream fout(fname);
|
|
||||||
if (!fout.is_open()) {
|
|
||||||
fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
|
|
||||||
|
|
||||||
fout << "[by:whisper.cpp]\n";
|
fout << "[by:whisper.cpp]\n";
|
||||||
|
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
@ -913,8 +880,6 @@ static bool output_lrc(struct whisper_context * ctx, const char * fname, const w
|
|||||||
|
|
||||||
fout << '[' << timestamp_lrc << ']' << speaker << text << "\n";
|
fout << '[' << timestamp_lrc << ']' << speaker << text << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1063,14 +1028,61 @@ int main(int argc, char ** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int f = 0; f < (int) params.fname_inp.size(); ++f) {
|
for (int f = 0; f < (int) params.fname_inp.size(); ++f) {
|
||||||
const auto fname_inp = params.fname_inp[f];
|
const auto & fname_inp = params.fname_inp[f];
|
||||||
const auto fname_out = f < (int) params.fname_out.size() && !params.fname_out[f].empty() ? params.fname_out[f] : params.fname_inp[f];
|
struct fout_factory {
|
||||||
|
std::string fname_out;
|
||||||
|
const size_t basename_length;
|
||||||
|
const bool is_stdout;
|
||||||
|
bool used_stdout;
|
||||||
|
decltype(whisper_print_segment_callback) * const print_segment_callback;
|
||||||
|
std::ofstream fout;
|
||||||
|
|
||||||
|
fout_factory (const std::string & fname_out_, const std::string & fname_inp, whisper_params & params) :
|
||||||
|
fname_out{!fname_out_.empty() ? fname_out_ : fname_inp},
|
||||||
|
basename_length{fname_out.size()},
|
||||||
|
is_stdout{fname_out == "-"},
|
||||||
|
used_stdout{},
|
||||||
|
print_segment_callback{is_stdout ? nullptr : whisper_print_segment_callback} {
|
||||||
|
if (!print_segment_callback) {
|
||||||
|
params.print_progress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool open(const char * ext, const char * function) {
|
||||||
|
if (is_stdout) {
|
||||||
|
if (used_stdout) {
|
||||||
|
fprintf(stderr, "warning: Not appending multiple file formats to stdout\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
used_stdout = true;
|
||||||
|
#ifdef _WIN32
|
||||||
|
fout = std::ofstream{"CON"};
|
||||||
|
#else
|
||||||
|
fout = std::ofstream{"/dev/stdout"};
|
||||||
|
#endif
|
||||||
|
// Not using fprintf stderr here because it might equal stdout
|
||||||
|
// Also assuming /dev is mounted
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fname_out.resize(basename_length);
|
||||||
|
fname_out += ext;
|
||||||
|
fout = std::ofstream{fname_out};
|
||||||
|
if (!fout.is_open()) {
|
||||||
|
fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname_out.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%s: saving output to '%s'\n", function, fname_out.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} fout_factory{f < (int) params.fname_out.size() ? params.fname_out[f] : "", fname_inp, params};
|
||||||
|
|
||||||
std::vector<float> pcmf32; // mono-channel F32 PCM
|
std::vector<float> pcmf32; // mono-channel F32 PCM
|
||||||
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
|
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
|
||||||
|
|
||||||
if (!::read_wav(fname_inp, pcmf32, pcmf32s, params.diarize)) {
|
if (!::read_audio_data(fname_inp, pcmf32, pcmf32s, params.diarize)) {
|
||||||
fprintf(stderr, "error: failed to read WAV file '%s'\n", fname_inp.c_str());
|
fprintf(stderr, "error: failed to read audio file '%s'\n", fname_inp.c_str());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1101,6 +1113,9 @@ int main(int argc, char ** argv) {
|
|||||||
params.tinydiarize ? "tdrz = 1, " : "",
|
params.tinydiarize ? "tdrz = 1, " : "",
|
||||||
params.no_timestamps ? 0 : 1);
|
params.no_timestamps ? 0 : 1);
|
||||||
|
|
||||||
|
if (params.print_colors) {
|
||||||
|
fprintf(stderr, "%s: color scheme: red (low confidence), yellow (medium), green (high confidence)\n", __func__);
|
||||||
|
}
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1151,6 +1166,16 @@ int main(int argc, char ** argv) {
|
|||||||
|
|
||||||
wparams.suppress_nst = params.suppress_nst;
|
wparams.suppress_nst = params.suppress_nst;
|
||||||
|
|
||||||
|
wparams.vad = params.vad;
|
||||||
|
wparams.vad_model_path = params.vad_model.c_str();
|
||||||
|
|
||||||
|
wparams.vad_params.threshold = params.vad_threshold;
|
||||||
|
wparams.vad_params.min_speech_duration_ms = params.vad_min_speech_duration_ms;
|
||||||
|
wparams.vad_params.min_silence_duration_ms = params.vad_min_silence_duration_ms;
|
||||||
|
wparams.vad_params.max_speech_duration_s = params.vad_max_speech_duration_s;
|
||||||
|
wparams.vad_params.speech_pad_ms = params.vad_speech_pad_ms;
|
||||||
|
wparams.vad_params.samples_overlap = params.vad_samples_overlap;
|
||||||
|
|
||||||
whisper_print_user_data user_data = { ¶ms, &pcmf32s, 0 };
|
whisper_print_user_data user_data = { ¶ms, &pcmf32s, 0 };
|
||||||
|
|
||||||
const auto & grammar_parsed = params.grammar_parsed;
|
const auto & grammar_parsed = params.grammar_parsed;
|
||||||
@ -1169,7 +1194,7 @@ int main(int argc, char ** argv) {
|
|||||||
|
|
||||||
// this callback is called on each new segment
|
// this callback is called on each new segment
|
||||||
if (!wparams.print_realtime) {
|
if (!wparams.print_realtime) {
|
||||||
wparams.new_segment_callback = whisper_print_segment_callback;
|
wparams.new_segment_callback = fout_factory.print_segment_callback;
|
||||||
wparams.new_segment_callback_user_data = &user_data;
|
wparams.new_segment_callback_user_data = &user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1211,54 +1236,26 @@ int main(int argc, char ** argv) {
|
|||||||
|
|
||||||
// output stuff
|
// output stuff
|
||||||
{
|
{
|
||||||
printf("\n");
|
// macros to stringify function name
|
||||||
|
#define output_func(func, ext, param, ...) if (param && fout_factory.open(ext, #func)) {\
|
||||||
|
func(ctx, fout_factory.fout, params, __VA_ARGS__); \
|
||||||
|
}
|
||||||
|
#define output_ext(ext, ...) output_func(output_##ext, "." #ext, params.output_##ext, __VA_ARGS__)
|
||||||
|
|
||||||
// output to text file
|
output_ext(txt, pcmf32s);
|
||||||
if (params.output_txt) {
|
output_ext(vtt, pcmf32s);
|
||||||
const auto fname_txt = fname_out + ".txt";
|
output_ext(srt, pcmf32s);
|
||||||
output_txt(ctx, fname_txt.c_str(), params, pcmf32s);
|
output_ext(wts, pcmf32s, fname_inp.c_str(), float(pcmf32.size() + 1000)/WHISPER_SAMPLE_RATE, fout_factory.fname_out.c_str());
|
||||||
}
|
output_ext(csv, pcmf32s);
|
||||||
|
output_func(output_json, ".json", params.output_jsn, pcmf32s);
|
||||||
|
output_ext(lrc, pcmf32s);
|
||||||
|
output_func(output_score, ".score.txt", params.log_score, pcmf32s);
|
||||||
|
|
||||||
// output to VTT file
|
#undef output_ext
|
||||||
if (params.output_vtt) {
|
#undef output_func
|
||||||
const auto fname_vtt = fname_out + ".vtt";
|
|
||||||
output_vtt(ctx, fname_vtt.c_str(), params, pcmf32s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// output to SRT file
|
if (fout_factory.is_stdout && !fout_factory.used_stdout) {
|
||||||
if (params.output_srt) {
|
fprintf(stderr, "warning: '--output-file -' used without any other '--output-*'");
|
||||||
const auto fname_srt = fname_out + ".srt";
|
|
||||||
output_srt(ctx, fname_srt.c_str(), params, pcmf32s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// output to WTS file
|
|
||||||
if (params.output_wts) {
|
|
||||||
const auto fname_wts = fname_out + ".wts";
|
|
||||||
output_wts(ctx, fname_wts.c_str(), fname_inp.c_str(), params, float(pcmf32.size() + 1000)/WHISPER_SAMPLE_RATE, pcmf32s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// output to CSV file
|
|
||||||
if (params.output_csv) {
|
|
||||||
const auto fname_csv = fname_out + ".csv";
|
|
||||||
output_csv(ctx, fname_csv.c_str(), params, pcmf32s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// output to JSON file
|
|
||||||
if (params.output_jsn) {
|
|
||||||
const auto fname_jsn = fname_out + ".json";
|
|
||||||
output_json(ctx, fname_jsn.c_str(), params, pcmf32s, params.output_jsn_full);
|
|
||||||
}
|
|
||||||
|
|
||||||
// output to LRC file
|
|
||||||
if (params.output_lrc) {
|
|
||||||
const auto fname_lrc = fname_out + ".lrc";
|
|
||||||
output_lrc(ctx, fname_lrc.c_str(), params, pcmf32s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// output to score file
|
|
||||||
if (params.log_score) {
|
|
||||||
const auto fname_score = fname_out + ".score.txt";
|
|
||||||
output_score(ctx, fname_score.c_str(), params, pcmf32s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
146
examples/coi-serviceworker.js
Normal file
146
examples/coi-serviceworker.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
|
||||||
|
let coepCredentialless = false;
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
self.addEventListener("install", () => self.skipWaiting());
|
||||||
|
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
|
||||||
|
|
||||||
|
self.addEventListener("message", (ev) => {
|
||||||
|
if (!ev.data) {
|
||||||
|
return;
|
||||||
|
} else if (ev.data.type === "deregister") {
|
||||||
|
self.registration
|
||||||
|
.unregister()
|
||||||
|
.then(() => {
|
||||||
|
return self.clients.matchAll();
|
||||||
|
})
|
||||||
|
.then(clients => {
|
||||||
|
clients.forEach((client) => client.navigate(client.url));
|
||||||
|
});
|
||||||
|
} else if (ev.data.type === "coepCredentialless") {
|
||||||
|
coepCredentialless = ev.data.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("fetch", function (event) {
|
||||||
|
const r = event.request;
|
||||||
|
if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = (coepCredentialless && r.mode === "no-cors")
|
||||||
|
? new Request(r, {
|
||||||
|
credentials: "omit",
|
||||||
|
})
|
||||||
|
: r;
|
||||||
|
event.respondWith(
|
||||||
|
fetch(request)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 0) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newHeaders = new Headers(response.headers);
|
||||||
|
newHeaders.set("Cross-Origin-Embedder-Policy",
|
||||||
|
coepCredentialless ? "credentialless" : "require-corp"
|
||||||
|
);
|
||||||
|
if (!coepCredentialless) {
|
||||||
|
newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin");
|
||||||
|
}
|
||||||
|
newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");
|
||||||
|
|
||||||
|
return new Response(response.body, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: newHeaders,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
(() => {
|
||||||
|
const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf");
|
||||||
|
window.sessionStorage.removeItem("coiReloadedBySelf");
|
||||||
|
const coepDegrading = (reloadedBySelf == "coepdegrade");
|
||||||
|
|
||||||
|
// You can customize the behavior of this script through a global `coi` variable.
|
||||||
|
const coi = {
|
||||||
|
shouldRegister: () => !reloadedBySelf,
|
||||||
|
shouldDeregister: () => false,
|
||||||
|
coepCredentialless: () => true,
|
||||||
|
coepDegrade: () => true,
|
||||||
|
doReload: () => window.location.reload(),
|
||||||
|
quiet: false,
|
||||||
|
...window.coi
|
||||||
|
};
|
||||||
|
|
||||||
|
const n = navigator;
|
||||||
|
const controlling = n.serviceWorker && n.serviceWorker.controller;
|
||||||
|
|
||||||
|
// Record the failure if the page is served by serviceWorker.
|
||||||
|
if (controlling && !window.crossOriginIsolated) {
|
||||||
|
window.sessionStorage.setItem("coiCoepHasFailed", "true");
|
||||||
|
}
|
||||||
|
const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed");
|
||||||
|
|
||||||
|
if (controlling) {
|
||||||
|
// Reload only on the first failure.
|
||||||
|
const reloadToDegrade = coi.coepDegrade() && !(
|
||||||
|
coepDegrading || window.crossOriginIsolated
|
||||||
|
);
|
||||||
|
n.serviceWorker.controller.postMessage({
|
||||||
|
type: "coepCredentialless",
|
||||||
|
value: (reloadToDegrade || coepHasFailed && coi.coepDegrade())
|
||||||
|
? false
|
||||||
|
: coi.coepCredentialless(),
|
||||||
|
});
|
||||||
|
if (reloadToDegrade) {
|
||||||
|
!coi.quiet && console.log("Reloading page to degrade COEP.");
|
||||||
|
window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade");
|
||||||
|
coi.doReload("coepdegrade");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coi.shouldDeregister()) {
|
||||||
|
n.serviceWorker.controller.postMessage({ type: "deregister" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
|
||||||
|
// already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
|
||||||
|
if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return;
|
||||||
|
|
||||||
|
if (!window.isSecureContext) {
|
||||||
|
!coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In some environments (e.g. Firefox private mode) this won't be available
|
||||||
|
if (!n.serviceWorker) {
|
||||||
|
!coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
n.serviceWorker.register(window.document.currentScript.src).then(
|
||||||
|
(registration) => {
|
||||||
|
!coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope);
|
||||||
|
|
||||||
|
registration.addEventListener("updatefound", () => {
|
||||||
|
!coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
|
||||||
|
window.sessionStorage.setItem("coiReloadedBySelf", "updatefound");
|
||||||
|
coi.doReload();
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the registration is active, but it's not controlling the page
|
||||||
|
if (registration.active && !n.serviceWorker.controller) {
|
||||||
|
!coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker.");
|
||||||
|
window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling");
|
||||||
|
coi.doReload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
!coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
}
|
@ -36,7 +36,7 @@ set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
|
|||||||
-s INITIAL_MEMORY=1024MB \
|
-s INITIAL_MEMORY=1024MB \
|
||||||
-s TOTAL_MEMORY=1024MB \
|
-s TOTAL_MEMORY=1024MB \
|
||||||
-s FORCE_FILESYSTEM=1 \
|
-s FORCE_FILESYSTEM=1 \
|
||||||
-s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap']\" \
|
-s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap', 'HEAPU8']\" \
|
||||||
${EXTRA_FLAGS} \
|
${EXTRA_FLAGS} \
|
||||||
")
|
")
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
This is a basic Voice Assistant example that accepts voice commands from the microphone.
|
This is a basic Voice Assistant example that accepts voice commands from the microphone.
|
||||||
It runs in fully in the browser via WebAseembly.
|
It runs in fully in the browser via WebAseembly.
|
||||||
|
|
||||||
Online demo: https://whisper.ggerganov.com/command/
|
Online demo: https://ggerganov.github.io/whisper.cpp/command.wasm
|
||||||
|
|
||||||
Terminal version: [examples/command](/examples/command)
|
Terminal version: [examples/command](/examples/command)
|
||||||
|
|
||||||
@ -15,9 +15,23 @@ git clone https://github.com/ggerganov/whisper.cpp
|
|||||||
cd whisper.cpp
|
cd whisper.cpp
|
||||||
mkdir build-em && cd build-em
|
mkdir build-em && cd build-em
|
||||||
emcmake cmake ..
|
emcmake cmake ..
|
||||||
make -j
|
make -j libcommand
|
||||||
|
```
|
||||||
|
The example can then be started by running a local HTTP server:
|
||||||
|
```console
|
||||||
|
python3 examples/server.py
|
||||||
|
```
|
||||||
|
And then opening a browser to the following URL:
|
||||||
|
http://localhost:8000/command.wasm/
|
||||||
|
|
||||||
# copy the produced page to your HTTP path
|
To run the example in a different server, you need to copy the following files
|
||||||
|
to the server's HTTP path:
|
||||||
|
```
|
||||||
cp bin/command.wasm/* /path/to/html/
|
cp bin/command.wasm/* /path/to/html/
|
||||||
|
cp bin/libcommand.js /path/to/html/
|
||||||
cp bin/libcommand.worker.js /path/to/html/
|
cp bin/libcommand.worker.js /path/to/html/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 📝 **Note:** As of Emscripten 3.1.58 (April 2024), separate worker.js files are no
|
||||||
|
> longer generated and the worker is embedded in the main JS file. So the worker
|
||||||
|
> file will not be geneated for versions later than `3.1.58`.
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script src="../coi-serviceworker.js"></script>
|
||||||
|
<link rel="icon" href="data:,">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main-container">
|
<div id="main-container">
|
||||||
@ -36,11 +38,10 @@
|
|||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<b>More examples:</b>
|
<b>More examples:</b>
|
||||||
<a href="https://whisper.ggerganov.com/">main</a> |
|
<a href="../">main</a> |
|
||||||
<a href="https://whisper.ggerganov.com/bench">bench</a> |
|
<a href="../bench.wasm/">bench</a> |
|
||||||
<a href="https://whisper.ggerganov.com/stream">stream</a> |
|
<a href="../stream.wasm">stream</a> |
|
||||||
<a href="https://whisper.ggerganov.com/command">command</a> |
|
<a href="../command.wasm/">command</a> |
|
||||||
<a href="https://whisper.ggerganov.com/talk">talk</a> |
|
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// Speak short text commands to the microphone.
|
// Speak short text commands to the microphone.
|
||||||
// This program will detect your voice command and convert them to text.
|
// This program will detect your voice command and convert them to text.
|
||||||
//
|
//
|
||||||
// ref: https://github.com/ggerganov/whisper.cpp/issues/171
|
// ref: https://github.com/ggml-org/whisper.cpp/issues/171
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "common-sdl.h"
|
#include "common-sdl.h"
|
||||||
@ -11,22 +11,15 @@
|
|||||||
#include "whisper.h"
|
#include "whisper.h"
|
||||||
#include "grammar-parser.h"
|
#include "grammar-parser.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <mutex>
|
#include <map>
|
||||||
#include <regex>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#define NOMINMAX
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// command-line parameters
|
// command-line parameters
|
||||||
struct whisper_params {
|
struct whisper_params {
|
||||||
@ -685,10 +678,6 @@ static int process_general_transcription(struct whisper_context * ctx, audio_asy
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char ** argv) {
|
int main(int argc, char ** argv) {
|
||||||
#if defined(_WIN32)
|
|
||||||
SetConsoleOutputCP(CP_UTF8);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
whisper_params params;
|
whisper_params params;
|
||||||
|
|
||||||
if (whisper_params_parse(argc, argv, params) == false) {
|
if (whisper_params_parse(argc, argv, params) == false) {
|
||||||
|
@ -159,15 +159,11 @@ void audio_async::callback(uint8_t * stream, int len) {
|
|||||||
|
|
||||||
memcpy(&m_audio[m_audio_pos], stream, n0 * sizeof(float));
|
memcpy(&m_audio[m_audio_pos], stream, n0 * sizeof(float));
|
||||||
memcpy(&m_audio[0], stream + n0 * sizeof(float), (n_samples - n0) * sizeof(float));
|
memcpy(&m_audio[0], stream + n0 * sizeof(float), (n_samples - n0) * sizeof(float));
|
||||||
|
|
||||||
m_audio_pos = (m_audio_pos + n_samples) % m_audio.size();
|
|
||||||
m_audio_len = m_audio.size();
|
|
||||||
} else {
|
} else {
|
||||||
memcpy(&m_audio[m_audio_pos], stream, n_samples * sizeof(float));
|
memcpy(&m_audio[m_audio_pos], stream, n_samples * sizeof(float));
|
||||||
|
|
||||||
m_audio_pos = (m_audio_pos + n_samples) % m_audio.size();
|
|
||||||
m_audio_len = std::min(m_audio_len + n_samples, m_audio.size());
|
|
||||||
}
|
}
|
||||||
|
m_audio_pos = (m_audio_pos + n_samples) % m_audio.size();
|
||||||
|
m_audio_len = std::min(m_audio_len + n_samples, m_audio.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
168
examples/common-whisper.cpp
Normal file
168
examples/common-whisper.cpp
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
#define _USE_MATH_DEFINES // for M_PI
|
||||||
|
|
||||||
|
#include "common-whisper.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include "whisper.h"
|
||||||
|
|
||||||
|
// third-party utilities
|
||||||
|
// use your favorite implementations
|
||||||
|
#define STB_VORBIS_HEADER_ONLY
|
||||||
|
#include "stb_vorbis.c" /* Enables Vorbis decoding. */
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MA_NO_DEVICE_IO
|
||||||
|
#define MA_NO_THREADING
|
||||||
|
#define MA_NO_ENCODING
|
||||||
|
#define MA_NO_GENERATION
|
||||||
|
#define MA_NO_RESOURCE_MANAGER
|
||||||
|
#define MA_NO_NODE_GRAPH
|
||||||
|
#define MINIAUDIO_IMPLEMENTATION
|
||||||
|
#include "miniaudio.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#ifdef WHISPER_FFMPEG
|
||||||
|
// as implemented in ffmpeg_trancode.cpp only embedded in common lib if whisper built with ffmpeg support
|
||||||
|
extern bool ffmpeg_decode_audio(const std::string & ifname, std::vector<uint8_t> & wav_data);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool read_audio_data(const std::string & fname, std::vector<float>& pcmf32, std::vector<std::vector<float>>& pcmf32s, bool stereo) {
|
||||||
|
std::vector<uint8_t> audio_data; // used for pipe input from stdin or ffmpeg decoding output
|
||||||
|
|
||||||
|
ma_result result;
|
||||||
|
ma_decoder_config decoder_config;
|
||||||
|
ma_decoder decoder;
|
||||||
|
|
||||||
|
decoder_config = ma_decoder_config_init(ma_format_f32, stereo ? 2 : 1, WHISPER_SAMPLE_RATE);
|
||||||
|
|
||||||
|
if (fname == "-") {
|
||||||
|
#ifdef _WIN32
|
||||||
|
_setmode(_fileno(stdin), _O_BINARY);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t buf[1024];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
const size_t n = fread(buf, 1, sizeof(buf), stdin);
|
||||||
|
if (n == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
audio_data.insert(audio_data.end(), buf, buf + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((result = ma_decoder_init_memory(audio_data.data(), audio_data.size(), &decoder_config, &decoder)) != MA_SUCCESS) {
|
||||||
|
|
||||||
|
fprintf(stderr, "Error: failed to open audio data from stdin (%s)\n", ma_result_description(result));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, audio_data.size());
|
||||||
|
}
|
||||||
|
else if (((result = ma_decoder_init_file(fname.c_str(), &decoder_config, &decoder)) != MA_SUCCESS)) {
|
||||||
|
#if defined(WHISPER_FFMPEG)
|
||||||
|
if (ffmpeg_decode_audio(fname, audio_data) != 0) {
|
||||||
|
fprintf(stderr, "error: failed to ffmpeg decode '%s'\n", fname.c_str());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((result = ma_decoder_init_memory(audio_data.data(), audio_data.size(), &decoder_config, &decoder)) != MA_SUCCESS) {
|
||||||
|
fprintf(stderr, "error: failed to read audio data as wav (%s)\n", ma_result_description(result));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if ((result = ma_decoder_init_memory(fname.c_str(), fname.size(), &decoder_config, &decoder)) != MA_SUCCESS) {
|
||||||
|
fprintf(stderr, "error: failed to read audio data as wav (%s)\n", ma_result_description(result));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_uint64 frame_count;
|
||||||
|
ma_uint64 frames_read;
|
||||||
|
|
||||||
|
if ((result = ma_decoder_get_length_in_pcm_frames(&decoder, &frame_count)) != MA_SUCCESS) {
|
||||||
|
fprintf(stderr, "error: failed to retrieve the length of the audio data (%s)\n", ma_result_description(result));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pcmf32.resize(stereo ? frame_count*2 : frame_count);
|
||||||
|
|
||||||
|
if ((result = ma_decoder_read_pcm_frames(&decoder, pcmf32.data(), frame_count, &frames_read)) != MA_SUCCESS) {
|
||||||
|
fprintf(stderr, "error: failed to read the frames of the audio data (%s)\n", ma_result_description(result));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stereo) {
|
||||||
|
pcmf32s.resize(2);
|
||||||
|
pcmf32s[0].resize(frame_count);
|
||||||
|
pcmf32s[1].resize(frame_count);
|
||||||
|
for (uint64_t i = 0; i < frame_count; i++) {
|
||||||
|
pcmf32s[0][i] = pcmf32[2*i];
|
||||||
|
pcmf32s[1][i] = pcmf32[2*i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_decoder_uninit(&decoder);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 500 -> 00:05.000
|
||||||
|
// 6000 -> 01:00.000
|
||||||
|
std::string to_timestamp(int64_t t, bool comma) {
|
||||||
|
int64_t msec = t * 10;
|
||||||
|
int64_t hr = msec / (1000 * 60 * 60);
|
||||||
|
msec = msec - hr * (1000 * 60 * 60);
|
||||||
|
int64_t min = msec / (1000 * 60);
|
||||||
|
msec = msec - min * (1000 * 60);
|
||||||
|
int64_t sec = msec / 1000;
|
||||||
|
msec = msec - sec * 1000;
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "%02d:%02d:%02d%s%03d", (int) hr, (int) min, (int) sec, comma ? "," : ".", (int) msec);
|
||||||
|
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int timestamp_to_sample(int64_t t, int n_samples, int whisper_sample_rate) {
|
||||||
|
return std::max(0, std::min((int) n_samples - 1, (int) ((t*whisper_sample_rate)/100)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool speak_with_file(const std::string & command, const std::string & text, const std::string & path, int voice_id) {
|
||||||
|
std::ofstream speak_file(path.c_str());
|
||||||
|
if (speak_file.fail()) {
|
||||||
|
fprintf(stderr, "%s: failed to open speak_file\n", __func__);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
speak_file.write(text.c_str(), text.size());
|
||||||
|
speak_file.close();
|
||||||
|
int ret = system((command + " " + std::to_string(voice_id) + " " + path).c_str());
|
||||||
|
if (ret != 0) {
|
||||||
|
fprintf(stderr, "%s: failed to speak\n", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef STB_VORBIS_HEADER_ONLY
|
||||||
|
#include "stb_vorbis.c"
|
24
examples/common-whisper.h
Normal file
24
examples/common-whisper.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// Read WAV audio file and store the PCM data into pcmf32
|
||||||
|
// fname can be a buffer of WAV data instead of a filename
|
||||||
|
// The sample rate of the audio must be equal to COMMON_SAMPLE_RATE
|
||||||
|
// If stereo flag is set and the audio has 2 channels, the pcmf32s will contain 2 channel PCM
|
||||||
|
bool read_audio_data(
|
||||||
|
const std::string & fname,
|
||||||
|
std::vector<float> & pcmf32,
|
||||||
|
std::vector<std::vector<float>> & pcmf32s,
|
||||||
|
bool stereo);
|
||||||
|
|
||||||
|
// convert timestamp to string, 6000 -> 01:00.000
|
||||||
|
std::string to_timestamp(int64_t t, bool comma = false);
|
||||||
|
|
||||||
|
// given a timestamp get the sample
|
||||||
|
int timestamp_to_sample(int64_t t, int n_samples, int whisper_sample_rate);
|
||||||
|
|
||||||
|
// write text to file, and call system("command voice_id file")
|
||||||
|
bool speak_with_file(const std::string & command, const std::string & text, const std::string & path, int voice_id);
|
@ -2,33 +2,14 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
// third-party utilities
|
|
||||||
// use your favorite implementations
|
|
||||||
#define DR_WAV_IMPLEMENTATION
|
|
||||||
#include "dr_wav.h"
|
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <codecvt>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <regex>
|
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <codecvt>
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#pragma warning(disable: 4244 4267) // possible loss of data
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <io.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef WHISPER_FFMPEG
|
|
||||||
// as implemented in ffmpeg_trancode.cpp only embedded in common lib if whisper built with ffmpeg support
|
|
||||||
extern bool ffmpeg_decode_audio(const std::string & ifname, std::vector<uint8_t> & wav_data);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Function to check if the next argument exists
|
// Function to check if the next argument exists
|
||||||
static std::string get_next_arg(int& i, int argc, char** argv, const std::string& flag, gpt_params& params) {
|
static std::string get_next_arg(int& i, int argc, char** argv, const std::string& flag, gpt_params& params) {
|
||||||
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
||||||
@ -262,17 +243,6 @@ std::map<std::string, int32_t> json_parse(const std::string & fname) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string convert_to_utf8(const std::wstring & input) {
|
|
||||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
|
|
||||||
return converter.to_bytes(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::wstring convert_to_wstring(const std::string & input) {
|
|
||||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
|
|
||||||
return converter.from_bytes(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gpt_split_words(std::string str, std::vector<std::string>& words) {
|
void gpt_split_words(std::string str, std::vector<std::string>& words) {
|
||||||
const std::string pattern = R"('s|'t|'re|'ve|'m|'ll|'d| ?[[:alpha:]]+| ?[[:digit:]]+| ?[^\s[:alpha:][:digit:]]+|\s+(?!\S)|\s+)";
|
const std::string pattern = R"('s|'t|'re|'ve|'m|'ll|'d| ?[[:alpha:]]+| ?[[:digit:]]+| ?[^\s[:alpha:][:digit:]]+|\s+(?!\S)|\s+)";
|
||||||
const std::regex re(pattern);
|
const std::regex re(pattern);
|
||||||
@ -624,129 +594,6 @@ gpt_vocab::id gpt_sample_top_k_top_p_repeat(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_wav_buffer(const std::string buf) {
|
|
||||||
// RIFF ref: https://en.wikipedia.org/wiki/Resource_Interchange_File_Format
|
|
||||||
// WAV ref: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
|
|
||||||
if (buf.size() < 12 || buf.substr(0, 4) != "RIFF" || buf.substr(8, 4) != "WAVE") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t chunk_size = *reinterpret_cast<const uint32_t*>(buf.data() + 4);
|
|
||||||
if (chunk_size + 8 != buf.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool read_wav(const std::string & fname, std::vector<float>& pcmf32, std::vector<std::vector<float>>& pcmf32s, bool stereo) {
|
|
||||||
drwav wav;
|
|
||||||
std::vector<uint8_t> wav_data; // used for pipe input from stdin or ffmpeg decoding output
|
|
||||||
|
|
||||||
if (fname == "-") {
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
_setmode(_fileno(stdin), _O_BINARY);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint8_t buf[1024];
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
const size_t n = fread(buf, 1, sizeof(buf), stdin);
|
|
||||||
if (n == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
wav_data.insert(wav_data.end(), buf, buf + n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drwav_init_memory(&wav, wav_data.data(), wav_data.size(), nullptr) == false) {
|
|
||||||
fprintf(stderr, "error: failed to open WAV file from stdin\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, wav_data.size());
|
|
||||||
}
|
|
||||||
else if (is_wav_buffer(fname)) {
|
|
||||||
if (drwav_init_memory(&wav, fname.c_str(), fname.size(), nullptr) == false) {
|
|
||||||
fprintf(stderr, "error: failed to open WAV file from fname buffer\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (drwav_init_file(&wav, fname.c_str(), nullptr) == false) {
|
|
||||||
#if defined(WHISPER_FFMPEG)
|
|
||||||
if (ffmpeg_decode_audio(fname, wav_data) != 0) {
|
|
||||||
fprintf(stderr, "error: failed to ffmpeg decode '%s' \n", fname.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (drwav_init_memory(&wav, wav_data.data(), wav_data.size(), nullptr) == false) {
|
|
||||||
fprintf(stderr, "error: failed to read wav data as wav \n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname.c_str());
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wav.channels != 1 && wav.channels != 2) {
|
|
||||||
fprintf(stderr, "%s: WAV file '%s' must be mono or stereo\n", __func__, fname.c_str());
|
|
||||||
drwav_uninit(&wav);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stereo && wav.channels != 2) {
|
|
||||||
fprintf(stderr, "%s: WAV file '%s' must be stereo for diarization\n", __func__, fname.c_str());
|
|
||||||
drwav_uninit(&wav);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wav.sampleRate != COMMON_SAMPLE_RATE) {
|
|
||||||
fprintf(stderr, "%s: WAV file '%s' must be %i kHz\n", __func__, fname.c_str(), COMMON_SAMPLE_RATE/1000);
|
|
||||||
drwav_uninit(&wav);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wav.bitsPerSample != 16) {
|
|
||||||
fprintf(stderr, "%s: WAV file '%s' must be 16-bit\n", __func__, fname.c_str());
|
|
||||||
drwav_uninit(&wav);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint64_t n = wav_data.empty() ? wav.totalPCMFrameCount : wav_data.size()/(wav.channels*wav.bitsPerSample/8);
|
|
||||||
|
|
||||||
std::vector<int16_t> pcm16;
|
|
||||||
pcm16.resize(n*wav.channels);
|
|
||||||
drwav_read_pcm_frames_s16(&wav, n, pcm16.data());
|
|
||||||
drwav_uninit(&wav);
|
|
||||||
|
|
||||||
// convert to mono, float
|
|
||||||
pcmf32.resize(n);
|
|
||||||
if (wav.channels == 1) {
|
|
||||||
for (uint64_t i = 0; i < n; i++) {
|
|
||||||
pcmf32[i] = float(pcm16[i])/32768.0f;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (uint64_t i = 0; i < n; i++) {
|
|
||||||
pcmf32[i] = float(pcm16[2*i] + pcm16[2*i + 1])/65536.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stereo) {
|
|
||||||
// convert to stereo, float
|
|
||||||
pcmf32s.resize(2);
|
|
||||||
|
|
||||||
pcmf32s[0].resize(n);
|
|
||||||
pcmf32s[1].resize(n);
|
|
||||||
for (uint64_t i = 0; i < n; i++) {
|
|
||||||
pcmf32s[0][i] = float(pcm16[2*i])/32768.0f;
|
|
||||||
pcmf32s[1][i] = float(pcm16[2*i + 1])/32768.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void high_pass_filter(std::vector<float> & data, float cutoff, float sample_rate) {
|
void high_pass_filter(std::vector<float> & data, float cutoff, float sample_rate) {
|
||||||
const float rc = 1.0f / (2.0f * M_PI * cutoff);
|
const float rc = 1.0f / (2.0f * M_PI * cutoff);
|
||||||
const float dt = 1.0f / sample_rate;
|
const float dt = 1.0f / sample_rate;
|
||||||
@ -822,90 +669,7 @@ float similarity(const std::string & s0, const std::string & s1) {
|
|||||||
return 1.0f - (dist / std::max(s0.size(), s1.size()));
|
return 1.0f - (dist / std::max(s0.size(), s1.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sam_params_parse(int argc, char ** argv, sam_params & params) {
|
bool is_file_exist(const char * filename) {
|
||||||
for (int i = 1; i < argc; i++) {
|
std::ifstream infile(filename);
|
||||||
std::string arg = argv[i];
|
|
||||||
|
|
||||||
if (arg == "-s" || arg == "--seed") {
|
|
||||||
params.seed = std::stoi(argv[++i]);
|
|
||||||
} else if (arg == "-t" || arg == "--threads") {
|
|
||||||
params.n_threads = std::stoi(argv[++i]);
|
|
||||||
} else if (arg == "-m" || arg == "--model") {
|
|
||||||
params.model = argv[++i];
|
|
||||||
} else if (arg == "-i" || arg == "--inp") {
|
|
||||||
params.fname_inp = argv[++i];
|
|
||||||
} else if (arg == "-o" || arg == "--out") {
|
|
||||||
params.fname_out = argv[++i];
|
|
||||||
} else if (arg == "-h" || arg == "--help") {
|
|
||||||
sam_print_usage(argc, argv, params);
|
|
||||||
exit(0);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "error: unknown argument: %s\n", arg.c_str());
|
|
||||||
sam_print_usage(argc, argv, params);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sam_print_usage(int /*argc*/, char ** argv, const sam_params & params) {
|
|
||||||
fprintf(stderr, "usage: %s [options]\n", argv[0]);
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
fprintf(stderr, "options:\n");
|
|
||||||
fprintf(stderr, " -h, --help show this help message and exit\n");
|
|
||||||
fprintf(stderr, " -s SEED, --seed SEED RNG seed (default: -1)\n");
|
|
||||||
fprintf(stderr, " -t N, --threads N number of threads to use during computation (default: %d)\n", params.n_threads);
|
|
||||||
fprintf(stderr, " -m FNAME, --model FNAME\n");
|
|
||||||
fprintf(stderr, " model path (default: %s)\n", params.model.c_str());
|
|
||||||
fprintf(stderr, " -i FNAME, --inp FNAME\n");
|
|
||||||
fprintf(stderr, " input file (default: %s)\n", params.fname_inp.c_str());
|
|
||||||
fprintf(stderr, " -o FNAME, --out FNAME\n");
|
|
||||||
fprintf(stderr, " output file (default: %s)\n", params.fname_out.c_str());
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 500 -> 00:05.000
|
|
||||||
// 6000 -> 01:00.000
|
|
||||||
std::string to_timestamp(int64_t t, bool comma) {
|
|
||||||
int64_t msec = t * 10;
|
|
||||||
int64_t hr = msec / (1000 * 60 * 60);
|
|
||||||
msec = msec - hr * (1000 * 60 * 60);
|
|
||||||
int64_t min = msec / (1000 * 60);
|
|
||||||
msec = msec - min * (1000 * 60);
|
|
||||||
int64_t sec = msec / 1000;
|
|
||||||
msec = msec - sec * 1000;
|
|
||||||
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d%s%03d", (int) hr, (int) min, (int) sec, comma ? "," : ".", (int) msec);
|
|
||||||
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
int timestamp_to_sample(int64_t t, int n_samples, int whisper_sample_rate) {
|
|
||||||
return std::max(0, std::min((int) n_samples - 1, (int) ((t*whisper_sample_rate)/100)));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_file_exist(const char *fileName)
|
|
||||||
{
|
|
||||||
std::ifstream infile(fileName);
|
|
||||||
return infile.good();
|
return infile.good();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool speak_with_file(const std::string & command, const std::string & text, const std::string & path, int voice_id)
|
|
||||||
{
|
|
||||||
std::ofstream speak_file(path.c_str());
|
|
||||||
if (speak_file.fail()) {
|
|
||||||
fprintf(stderr, "%s: failed to open speak_file\n", __func__);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
speak_file.write(text.c_str(), text.size());
|
|
||||||
speak_file.close();
|
|
||||||
int ret = system((command + " " + std::to_string(voice_id) + " " + path).c_str());
|
|
||||||
if (ret != 0) {
|
|
||||||
fprintf(stderr, "%s: failed to speak\n", __func__);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#define COMMON_SAMPLE_RATE 16000
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// GPT CLI argument parsing
|
// GPT CLI argument parsing
|
||||||
//
|
//
|
||||||
@ -136,19 +134,6 @@ gpt_vocab::id gpt_sample_top_k_top_p_repeat(
|
|||||||
// Audio utils
|
// Audio utils
|
||||||
//
|
//
|
||||||
|
|
||||||
// Check if a buffer is a WAV audio file
|
|
||||||
bool is_wav_buffer(const std::string buf);
|
|
||||||
|
|
||||||
// Read WAV audio file and store the PCM data into pcmf32
|
|
||||||
// fname can be a buffer of WAV data instead of a filename
|
|
||||||
// The sample rate of the audio must be equal to COMMON_SAMPLE_RATE
|
|
||||||
// If stereo flag is set and the audio has 2 channels, the pcmf32s will contain 2 channel PCM
|
|
||||||
bool read_wav(
|
|
||||||
const std::string & fname,
|
|
||||||
std::vector<float> & pcmf32,
|
|
||||||
std::vector<std::vector<float>> & pcmf32s,
|
|
||||||
bool stereo);
|
|
||||||
|
|
||||||
// Write PCM data into WAV audio file
|
// Write PCM data into WAV audio file
|
||||||
class wav_writer {
|
class wav_writer {
|
||||||
private:
|
private:
|
||||||
@ -266,23 +251,6 @@ bool vad_simple(
|
|||||||
// compute similarity between two strings using Levenshtein distance
|
// compute similarity between two strings using Levenshtein distance
|
||||||
float similarity(const std::string & s0, const std::string & s1);
|
float similarity(const std::string & s0, const std::string & s1);
|
||||||
|
|
||||||
//
|
|
||||||
// SAM argument parsing
|
|
||||||
//
|
|
||||||
|
|
||||||
struct sam_params {
|
|
||||||
int32_t seed = -1; // RNG seed
|
|
||||||
int32_t n_threads = std::min(4, (int32_t) std::thread::hardware_concurrency());
|
|
||||||
|
|
||||||
std::string model = "models/sam-vit-b/ggml-model-f16.bin"; // model path
|
|
||||||
std::string fname_inp = "img.jpg";
|
|
||||||
std::string fname_out = "img.out";
|
|
||||||
};
|
|
||||||
|
|
||||||
bool sam_params_parse(int argc, char ** argv, sam_params & params);
|
|
||||||
|
|
||||||
void sam_print_usage(int argc, char ** argv, const sam_params & params);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Terminal utils
|
// Terminal utils
|
||||||
//
|
//
|
||||||
@ -315,7 +283,7 @@ static std::string set_xterm256_foreground(int r, int g, int b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lowest is red, middle is yellow, highest is green. Color scheme from
|
// Lowest is red, middle is yellow, highest is green. Color scheme from
|
||||||
// Paul Tol; it is colorblind friendly https://personal.sron.nl/~pault/
|
// Paul Tol; it is colorblind friendly https://sronpersonalpages.nl/~pault
|
||||||
const std::vector<std::string> k_colors = {
|
const std::vector<std::string> k_colors = {
|
||||||
set_xterm256_foreground(220, 5, 12),
|
set_xterm256_foreground(220, 5, 12),
|
||||||
set_xterm256_foreground(232, 96, 28),
|
set_xterm256_foreground(232, 96, 28),
|
||||||
@ -330,14 +298,5 @@ const std::vector<std::string> k_colors = {
|
|||||||
// Other utils
|
// Other utils
|
||||||
//
|
//
|
||||||
|
|
||||||
// convert timestamp to string, 6000 -> 01:00.000
|
|
||||||
std::string to_timestamp(int64_t t, bool comma = false);
|
|
||||||
|
|
||||||
// given a timestamp get the sample
|
|
||||||
int timestamp_to_sample(int64_t t, int n_samples, int whisper_sample_rate);
|
|
||||||
|
|
||||||
// check if file exists using ifstream
|
// check if file exists using ifstream
|
||||||
bool is_file_exist(const char *fileName);
|
bool is_file_exist(const char * filename);
|
||||||
|
|
||||||
// write text to file, and call system("command voice_id file")
|
|
||||||
bool speak_with_file(const std::string & command, const std::string & text, const std::string & path, int voice_id);
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
add_executable(main ./deprecation-warning.cpp)
|
add_executable(main ./deprecation-warning.cpp)
|
||||||
add_executable(bench ./deprecation-warning.cpp)
|
add_executable(bench ./deprecation-warning.cpp)
|
||||||
add_executable(stream ./deprecation-warning.cpp)
|
if (WHISPER_SDL2)
|
||||||
add_executable(command ./deprecation-warning.cpp)
|
add_executable(stream ./deprecation-warning.cpp)
|
||||||
|
add_executable(command ./deprecation-warning.cpp)
|
||||||
|
endif()
|
||||||
|
8815
examples/dr_wav.h
8815
examples/dr_wav.h
File diff suppressed because it is too large
Load Diff
@ -194,7 +194,7 @@ static int decode_audio(struct audio_buffer *audio_buf, s16 **data, int *size)
|
|||||||
AVIOContext *avio_ctx;
|
AVIOContext *avio_ctx;
|
||||||
AVStream *stream;
|
AVStream *stream;
|
||||||
AVCodecContext *codec;
|
AVCodecContext *codec;
|
||||||
AVPacket packet;
|
AVPacket *packet;
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
struct SwrContext *swr;
|
struct SwrContext *swr;
|
||||||
u8 *avio_ctx_buffer;
|
u8 *avio_ctx_buffer;
|
||||||
@ -249,6 +249,20 @@ static int decode_audio(struct audio_buffer *audio_buf, s16 **data, int *size)
|
|||||||
/* prepare resampler */
|
/* prepare resampler */
|
||||||
swr = swr_alloc();
|
swr = swr_alloc();
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_MAJOR > 60
|
||||||
|
AVChannelLayout in_ch_layout = codec->ch_layout;
|
||||||
|
AVChannelLayout out_ch_layout = AV_CHANNEL_LAYOUT_MONO;
|
||||||
|
|
||||||
|
/* Set the source audio layout as-is */
|
||||||
|
av_opt_set_chlayout(swr, "in_chlayout", &in_ch_layout, 0);
|
||||||
|
av_opt_set_int(swr, "in_sample_rate", codec->sample_rate, 0);
|
||||||
|
av_opt_set_sample_fmt(swr, "in_sample_fmt", codec->sample_fmt, 0);
|
||||||
|
|
||||||
|
/* Convert it into 16khz Mono */
|
||||||
|
av_opt_set_chlayout(swr, "out_chlayout", &out_ch_layout, 0);
|
||||||
|
av_opt_set_int(swr, "out_sample_rate", WAVE_SAMPLE_RATE, 0);
|
||||||
|
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||||
|
#else
|
||||||
av_opt_set_int(swr, "in_channel_count", codec->channels, 0);
|
av_opt_set_int(swr, "in_channel_count", codec->channels, 0);
|
||||||
av_opt_set_int(swr, "out_channel_count", 1, 0);
|
av_opt_set_int(swr, "out_channel_count", 1, 0);
|
||||||
av_opt_set_int(swr, "in_channel_layout", codec->channel_layout, 0);
|
av_opt_set_int(swr, "in_channel_layout", codec->channel_layout, 0);
|
||||||
@ -257,6 +271,7 @@ static int decode_audio(struct audio_buffer *audio_buf, s16 **data, int *size)
|
|||||||
av_opt_set_int(swr, "out_sample_rate", WAVE_SAMPLE_RATE, 0);
|
av_opt_set_int(swr, "out_sample_rate", WAVE_SAMPLE_RATE, 0);
|
||||||
av_opt_set_sample_fmt(swr, "in_sample_fmt", codec->sample_fmt, 0);
|
av_opt_set_sample_fmt(swr, "in_sample_fmt", codec->sample_fmt, 0);
|
||||||
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
swr_init(swr);
|
swr_init(swr);
|
||||||
if (!swr_is_initialized(swr)) {
|
if (!swr_is_initialized(swr)) {
|
||||||
@ -264,7 +279,11 @@ static int decode_audio(struct audio_buffer *audio_buf, s16 **data, int *size)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_init_packet(&packet);
|
packet=av_packet_alloc();
|
||||||
|
if (!packet) {
|
||||||
|
LOG("Error allocating the packet\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
frame = av_frame_alloc();
|
frame = av_frame_alloc();
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
LOG("Error allocating the frame\n");
|
LOG("Error allocating the frame\n");
|
||||||
@ -274,8 +293,8 @@ static int decode_audio(struct audio_buffer *audio_buf, s16 **data, int *size)
|
|||||||
/* iterate through frames */
|
/* iterate through frames */
|
||||||
*data = NULL;
|
*data = NULL;
|
||||||
*size = 0;
|
*size = 0;
|
||||||
while (av_read_frame(fmt_ctx, &packet) >= 0) {
|
while (av_read_frame(fmt_ctx, packet) >= 0) {
|
||||||
avcodec_send_packet(codec, &packet);
|
avcodec_send_packet(codec, packet);
|
||||||
|
|
||||||
err = avcodec_receive_frame(codec, frame);
|
err = avcodec_receive_frame(codec, frame);
|
||||||
if (err == AVERROR(EAGAIN))
|
if (err == AVERROR(EAGAIN))
|
||||||
@ -286,10 +305,11 @@ static int decode_audio(struct audio_buffer *audio_buf, s16 **data, int *size)
|
|||||||
/* Flush any remaining conversion buffers... */
|
/* Flush any remaining conversion buffers... */
|
||||||
convert_frame(swr, codec, frame, data, size, true);
|
convert_frame(swr, codec, frame, data, size, true);
|
||||||
|
|
||||||
|
av_packet_free(&packet);
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
swr_free(&swr);
|
swr_free(&swr);
|
||||||
//avio_context_free(); // todo?
|
//avio_context_free(); // todo?
|
||||||
avcodec_close(codec);
|
avcodec_free_context(&codec);
|
||||||
avformat_close_input(&fmt_ctx);
|
avformat_close_input(&fmt_ctx);
|
||||||
avformat_free_context(fmt_ctx);
|
avformat_free_context(fmt_ctx);
|
||||||
|
|
||||||
|
@ -41,20 +41,17 @@ fi
|
|||||||
# record some raw audio
|
# record some raw audio
|
||||||
sox -d rec.wav
|
sox -d rec.wav
|
||||||
|
|
||||||
# resample to 16kHz
|
|
||||||
ffmpeg -y -i ./rec.wav -ar 16000 -ac 1 -c:a pcm_s16le ./rec16.wav > /dev/null 2>&1
|
|
||||||
|
|
||||||
# run Whisper
|
# run Whisper
|
||||||
echo "Processing ..."
|
echo "Processing ..."
|
||||||
${executable} -m models/ggml-base.en.bin rec16.wav -owts > /dev/null 2>&1
|
${executable} -m models/ggml-base.en.bin rec.wav -owts > /dev/null 2>&1
|
||||||
|
|
||||||
# generate Karaoke video
|
# generate Karaoke video
|
||||||
echo "Generating video ..."
|
echo "Generating video ..."
|
||||||
source rec16.wav.wts > /dev/null 2>&1
|
source rec.wav.wts > /dev/null 2>&1
|
||||||
|
|
||||||
# play the video
|
# play the video
|
||||||
echo "Playing ./rec16.wav.mp4 ..."
|
echo "Playing ./rec16.wav.mp4 ..."
|
||||||
ffplay -loglevel 0 -autoexit ./rec16.wav.mp4
|
ffplay -loglevel 0 -autoexit ./rec.wav.mp4
|
||||||
|
|
||||||
echo "Done"
|
echo "Done"
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Transcribe audio livestream by feeding ffmpeg output to whisper.cpp at regular intervals
|
# Transcribe audio livestream by feeding ffmpeg output to whisper.cpp at regular intervals
|
||||||
# Idea by @semiformal-net
|
# Idea by @semiformal-net
|
||||||
# ref: https://github.com/ggerganov/whisper.cpp/issues/185
|
# ref: https://github.com/ggml-org/whisper.cpp/issues/185
|
||||||
#
|
#
|
||||||
|
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
#include "whisper.h"
|
#include "whisper.h"
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <deque>
|
||||||
|
#include <iostream>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
|
||||||
#include <set>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
93468
examples/miniaudio.h
Normal file
93468
examples/miniaudio.h
Normal file
File diff suppressed because it is too large
Load Diff
115
examples/server.py
Normal file
115
examples/server.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
SCRIPT_DIR = Path(__file__).parent.absolute()
|
||||||
|
DIRECTORY = os.path.join(SCRIPT_DIR, "../build-em/bin")
|
||||||
|
DIRECTORY = os.path.abspath(DIRECTORY)
|
||||||
|
|
||||||
|
# The context root we want for all applications
|
||||||
|
CONTEXT_ROOT = "/whisper.cpp"
|
||||||
|
|
||||||
|
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, directory=DIRECTORY, **kwargs)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
# Redirect root to the context root
|
||||||
|
if self.path == '/':
|
||||||
|
self.send_response(302)
|
||||||
|
self.send_header('Location', CONTEXT_ROOT + '/')
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle requests under the context root
|
||||||
|
if self.path.startswith(CONTEXT_ROOT):
|
||||||
|
# Remove the context root prefix to get the actual path
|
||||||
|
actual_path = self.path[len(CONTEXT_ROOT):]
|
||||||
|
|
||||||
|
if not actual_path:
|
||||||
|
self.send_response(302)
|
||||||
|
self.send_header('Location', CONTEXT_ROOT + '/')
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
|
||||||
|
if '.worker.js' in actual_path:
|
||||||
|
worker_file = os.path.basename(actual_path)
|
||||||
|
worker_path = os.path.join(DIRECTORY, worker_file)
|
||||||
|
|
||||||
|
if os.path.exists(worker_path):
|
||||||
|
print(f"Found worker file: {worker_path}")
|
||||||
|
self.path = '/' + worker_file
|
||||||
|
else:
|
||||||
|
print(f"Worker file not found: {worker_path}")
|
||||||
|
|
||||||
|
elif actual_path == '/':
|
||||||
|
self.path = '/whisper.wasm/index.html'
|
||||||
|
elif actual_path.startswith('/bench.wasm/') or actual_path.startswith('/command.wasm/') or actual_path.startswith('/stream.wasm/'):
|
||||||
|
# Keep the path as is, just remove the context root
|
||||||
|
self.path = actual_path
|
||||||
|
# For all other paths under the context root
|
||||||
|
else:
|
||||||
|
# Check if this is a request to a file in whisper.wasm
|
||||||
|
potential_file = os.path.join(DIRECTORY, 'whisper.wasm', actual_path.lstrip('/'))
|
||||||
|
if os.path.exists(potential_file) and not os.path.isdir(potential_file):
|
||||||
|
self.path = '/whisper.wasm' + actual_path
|
||||||
|
else:
|
||||||
|
# Try to resolve the file from the base directory
|
||||||
|
potential_file = os.path.join(DIRECTORY, actual_path.lstrip('/'))
|
||||||
|
if os.path.exists(potential_file):
|
||||||
|
self.path = actual_path
|
||||||
|
|
||||||
|
# For direct requests to worker files (without context root as these
|
||||||
|
# are in the build-em/bin directory
|
||||||
|
elif '.worker.js' in self.path:
|
||||||
|
worker_file = os.path.basename(self.path)
|
||||||
|
worker_path = os.path.join(DIRECTORY, worker_file)
|
||||||
|
|
||||||
|
if os.path.exists(worker_path):
|
||||||
|
self.path = '/' + worker_file
|
||||||
|
|
||||||
|
# Handle coi-serviceworker.js separately
|
||||||
|
if 'coi-serviceworker.js' in self.path:
|
||||||
|
worker_file = "coi-serviceworker.js"
|
||||||
|
worker_path = os.path.join(SCRIPT_DIR, worker_file)
|
||||||
|
if os.path.exists(worker_path):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/javascript')
|
||||||
|
self.end_headers()
|
||||||
|
with open(worker_path, 'rb') as file:
|
||||||
|
self.wfile.write(file.read())
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print(f"Warning: Could not find {worker_path}")
|
||||||
|
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
def end_headers(self):
|
||||||
|
# Add required headers for SharedArrayBuffer
|
||||||
|
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
|
||||||
|
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
|
||||||
|
self.send_header("Access-Control-Allow-Origin", "*")
|
||||||
|
super().end_headers()
|
||||||
|
|
||||||
|
PORT = 8000
|
||||||
|
|
||||||
|
# Enable address reuse
|
||||||
|
class CustomServer(socketserver.TCPServer):
|
||||||
|
allow_reuse_address = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
with CustomServer(("", PORT), CustomHTTPRequestHandler) as httpd:
|
||||||
|
print(f"Serving directory '{DIRECTORY}' at http://localhost:{PORT}")
|
||||||
|
print(f"Application context root: http://localhost:{PORT}{CONTEXT_ROOT}/")
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nServer stopped.")
|
||||||
|
# Force complete exit
|
||||||
|
sys.exit(0)
|
||||||
|
except OSError as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
sys.exit(1)
|
File diff suppressed because it is too large
Load Diff
@ -1,22 +1,18 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "common-whisper.h"
|
||||||
|
|
||||||
#include "whisper.h"
|
#include "whisper.h"
|
||||||
#include "httplib.h"
|
#include "httplib.h"
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <fstream>
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstring>
|
|
||||||
#include <sstream>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#pragma warning(disable: 4244 4267) // possible loss of data
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace httplib;
|
using namespace httplib;
|
||||||
using json = nlohmann::ordered_json;
|
using json = nlohmann::ordered_json;
|
||||||
@ -79,6 +75,7 @@ struct whisper_params {
|
|||||||
bool use_gpu = true;
|
bool use_gpu = true;
|
||||||
bool flash_attn = false;
|
bool flash_attn = false;
|
||||||
bool suppress_nst = false;
|
bool suppress_nst = false;
|
||||||
|
bool no_context = false;
|
||||||
|
|
||||||
std::string language = "en";
|
std::string language = "en";
|
||||||
std::string prompt = "";
|
std::string prompt = "";
|
||||||
@ -140,6 +137,8 @@ void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params & para
|
|||||||
fprintf(stderr, " --convert, [%-7s] Convert audio to WAV, requires ffmpeg on the server\n", sparams.ffmpeg_converter ? "true" : "false");
|
fprintf(stderr, " --convert, [%-7s] Convert audio to WAV, requires ffmpeg on the server\n", sparams.ffmpeg_converter ? "true" : "false");
|
||||||
fprintf(stderr, " -sns, --suppress-nst [%-7s] suppress non-speech tokens\n", params.suppress_nst ? "true" : "false");
|
fprintf(stderr, " -sns, --suppress-nst [%-7s] suppress non-speech tokens\n", params.suppress_nst ? "true" : "false");
|
||||||
fprintf(stderr, " -nth N, --no-speech-thold N [%-7.2f] no speech threshold\n", params.no_speech_thold);
|
fprintf(stderr, " -nth N, --no-speech-thold N [%-7.2f] no speech threshold\n", params.no_speech_thold);
|
||||||
|
fprintf(stderr, " -nc, --no-context [%-7s] do not use previous audio context\n", params.no_context ? "true" : "false");
|
||||||
|
fprintf(stderr, " -ng, --no-gpu [%-7s] do not use gpu\n", params.use_gpu ? "false" : "true");
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +185,7 @@ bool whisper_params_parse(int argc, char ** argv, whisper_params & params, serve
|
|||||||
else if (arg == "-fa" || arg == "--flash-attn") { params.flash_attn = true; }
|
else if (arg == "-fa" || arg == "--flash-attn") { params.flash_attn = true; }
|
||||||
else if (arg == "-sns" || arg == "--suppress-nst") { params.suppress_nst = true; }
|
else if (arg == "-sns" || arg == "--suppress-nst") { params.suppress_nst = true; }
|
||||||
else if (arg == "-nth" || arg == "--no-speech-thold") { params.no_speech_thold = std::stof(argv[++i]); }
|
else if (arg == "-nth" || arg == "--no-speech-thold") { params.no_speech_thold = std::stof(argv[++i]); }
|
||||||
|
else if (arg == "-nc" || arg == "--no-context") { params.no_context = true; }
|
||||||
|
|
||||||
// server params
|
// server params
|
||||||
else if ( arg == "--port") { sparams.port = std::stoi(argv[++i]); }
|
else if ( arg == "--port") { sparams.port = std::stoi(argv[++i]); }
|
||||||
@ -506,6 +506,10 @@ void get_req_parameters(const Request & req, whisper_params & params)
|
|||||||
{
|
{
|
||||||
params.suppress_nst = parse_str_to_bool(req.get_file_value("suppress_nst").content);
|
params.suppress_nst = parse_str_to_bool(req.get_file_value("suppress_nst").content);
|
||||||
}
|
}
|
||||||
|
if (req.has_file("no_context"))
|
||||||
|
{
|
||||||
|
params.no_context = parse_str_to_bool(req.get_file_value("no_context").content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@ -723,8 +727,8 @@ int main(int argc, char ** argv) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read wav content into pcmf32
|
// read audio content into pcmf32
|
||||||
if (!::read_wav(temp_filename, pcmf32, pcmf32s, params.diarize))
|
if (!::read_audio_data(temp_filename, pcmf32, pcmf32s, params.diarize))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "error: failed to read WAV file '%s'\n", temp_filename.c_str());
|
fprintf(stderr, "error: failed to read WAV file '%s'\n", temp_filename.c_str());
|
||||||
const std::string error_resp = "{\"error\":\"failed to read WAV file\"}";
|
const std::string error_resp = "{\"error\":\"failed to read WAV file\"}";
|
||||||
@ -735,10 +739,10 @@ int main(int argc, char ** argv) {
|
|||||||
// remove temp file
|
// remove temp file
|
||||||
std::remove(temp_filename.c_str());
|
std::remove(temp_filename.c_str());
|
||||||
} else {
|
} else {
|
||||||
if (!::read_wav(audio_file.content, pcmf32, pcmf32s, params.diarize))
|
if (!::read_audio_data(audio_file.content, pcmf32, pcmf32s, params.diarize))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "error: failed to read WAV file\n");
|
fprintf(stderr, "error: failed to read audio data\n");
|
||||||
const std::string error_resp = "{\"error\":\"failed to read WAV file\"}";
|
const std::string error_resp = "{\"error\":\"failed to read audio data\"}";
|
||||||
res.set_content(error_resp, "application/json");
|
res.set_content(error_resp, "application/json");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -818,6 +822,7 @@ int main(int argc, char ** argv) {
|
|||||||
|
|
||||||
wparams.no_timestamps = params.no_timestamps;
|
wparams.no_timestamps = params.no_timestamps;
|
||||||
wparams.token_timestamps = !params.no_timestamps && params.response_format == vjson_format;
|
wparams.token_timestamps = !params.no_timestamps && params.response_format == vjson_format;
|
||||||
|
wparams.no_context = params.no_context;
|
||||||
|
|
||||||
wparams.suppress_nst = params.suppress_nst;
|
wparams.suppress_nst = params.suppress_nst;
|
||||||
|
|
||||||
@ -834,33 +839,25 @@ int main(int argc, char ** argv) {
|
|||||||
wparams.progress_callback_user_data = &user_data;
|
wparams.progress_callback_user_data = &user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// examples for abort mechanism
|
// tell whisper to abort if the HTTP connection closed
|
||||||
// in examples below, we do not abort the processing, but we could if the flag is set to true
|
wparams.abort_callback = [](void *user_data) {
|
||||||
|
// user_data is a pointer to our Request
|
||||||
// the callback is called before every encoder run - if it returns false, the processing is aborted
|
auto req_ptr = static_cast<const httplib::Request*>(user_data);
|
||||||
{
|
return req_ptr->is_connection_closed();
|
||||||
static bool is_aborted = false; // NOTE: this should be atomic to avoid data race
|
};
|
||||||
|
wparams.abort_callback_user_data = (void*)&req;
|
||||||
wparams.encoder_begin_callback = [](struct whisper_context * /*ctx*/, struct whisper_state * /*state*/, void * user_data) {
|
|
||||||
bool is_aborted = *(bool*)user_data;
|
|
||||||
return !is_aborted;
|
|
||||||
};
|
|
||||||
wparams.encoder_begin_callback_user_data = &is_aborted;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the callback is called before every computation - if it returns true, the computation is aborted
|
|
||||||
{
|
|
||||||
static bool is_aborted = false; // NOTE: this should be atomic to avoid data race
|
|
||||||
|
|
||||||
wparams.abort_callback = [](void * user_data) {
|
|
||||||
bool is_aborted = *(bool*)user_data;
|
|
||||||
return is_aborted;
|
|
||||||
};
|
|
||||||
wparams.abort_callback_user_data = &is_aborted;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (whisper_full_parallel(ctx, wparams, pcmf32.data(), pcmf32.size(), params.n_processors) != 0) {
|
if (whisper_full_parallel(ctx, wparams, pcmf32.data(), pcmf32.size(), params.n_processors) != 0) {
|
||||||
|
// handle failure or early abort
|
||||||
|
if (req.is_connection_closed()) {
|
||||||
|
// log client disconnect
|
||||||
|
fprintf(stderr, "client disconnected, aborted processing\n");
|
||||||
|
res.status = 499; // Client Closed Request (nginx convention)
|
||||||
|
res.set_content("{\"error\":\"client disconnected\"}", "application/json");
|
||||||
|
return;
|
||||||
|
}
|
||||||
fprintf(stderr, "%s: failed to process audio\n", argv[0]);
|
fprintf(stderr, "%s: failed to process audio\n", argv[0]);
|
||||||
|
res.status = 500; // Internal Server Error
|
||||||
const std::string error_resp = "{\"error\":\"failed to process audio\"}";
|
const std::string error_resp = "{\"error\":\"failed to process audio\"}";
|
||||||
res.set_content(error_resp, "application/json");
|
res.set_content(error_resp, "application/json");
|
||||||
return;
|
return;
|
||||||
@ -918,14 +915,26 @@ int main(int argc, char ** argv) {
|
|||||||
res.set_content(ss.str(), "text/vtt");
|
res.set_content(ss.str(), "text/vtt");
|
||||||
} else if (params.response_format == vjson_format) {
|
} else if (params.response_format == vjson_format) {
|
||||||
/* try to match openai/whisper's Python format */
|
/* try to match openai/whisper's Python format */
|
||||||
std::string results = output_str(ctx, params, pcmf32s);
|
std::string results = output_str(ctx, params, pcmf32s);
|
||||||
|
// Get language probabilities
|
||||||
|
std::vector<float> lang_probs(whisper_lang_max_id() + 1, 0.0f);
|
||||||
|
const auto detected_lang_id = whisper_lang_auto_detect(ctx, 0, params.n_threads, lang_probs.data());
|
||||||
json jres = json{
|
json jres = json{
|
||||||
{"task", params.translate ? "translate" : "transcribe"},
|
{"task", params.translate ? "translate" : "transcribe"},
|
||||||
{"language", whisper_lang_str_full(whisper_full_lang_id(ctx))},
|
{"language", whisper_lang_str_full(whisper_full_lang_id(ctx))},
|
||||||
{"duration", float(pcmf32.size())/WHISPER_SAMPLE_RATE},
|
{"duration", float(pcmf32.size())/WHISPER_SAMPLE_RATE},
|
||||||
{"text", results},
|
{"text", results},
|
||||||
{"segments", json::array()}
|
{"segments", json::array()},
|
||||||
|
{"detected_language", whisper_lang_str_full(detected_lang_id)},
|
||||||
|
{"detected_language_probability", lang_probs[detected_lang_id]},
|
||||||
|
{"language_probabilities", json::object()}
|
||||||
};
|
};
|
||||||
|
// Add all language probabilities
|
||||||
|
for (int i = 0; i <= whisper_lang_max_id(); ++i) {
|
||||||
|
if (lang_probs[i] > 0.001f) { // Only include non-negligible probabilities
|
||||||
|
jres["language_probabilities"][whisper_lang_str(i)] = lang_probs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
const int n_segments = whisper_full_n_segments(ctx);
|
const int n_segments = whisper_full_n_segments(ctx);
|
||||||
for (int i = 0; i < n_segments; ++i)
|
for (int i = 0; i < n_segments; ++i)
|
||||||
{
|
{
|
||||||
@ -1024,6 +1033,11 @@ int main(int argc, char ** argv) {
|
|||||||
// check if the model is in the file system
|
// check if the model is in the file system
|
||||||
});
|
});
|
||||||
|
|
||||||
|
svr.Get(sparams.request_path + "/health", [&](const Request &, Response &res){
|
||||||
|
const std::string health_response = "{\"status\":\"ok\"}";
|
||||||
|
res.set_content(health_response, "application/json");
|
||||||
|
});
|
||||||
|
|
||||||
svr.set_exception_handler([](const Request &, Response &res, std::exception_ptr ep) {
|
svr.set_exception_handler([](const Request &, Response &res, std::exception_ptr ep) {
|
||||||
const char fmt[] = "500 Internal Server Error\n%s";
|
const char fmt[] = "500 Internal Server Error\n%s";
|
||||||
char buf[BUFSIZ];
|
char buf[BUFSIZ];
|
||||||
|
5584
examples/stb_vorbis.c
Normal file
5584
examples/stb_vorbis.c
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user