yum-archive/TaSTT-Whisper

High-performance GPGPU inference of OpenAI's Whisper automatic speech recognition (ASR) model

git clone https://git.yummers.dev/yum-archive/TaSTT-Whisper

KonstantinSource codes8c4603c

master
28.1 KiB684 linesraw
1#include "whisper.h"
2
3// third-party utilities
4// use your favorite implementations
5#define DR_WAV_IMPLEMENTATION
6#include "dr_wav.h"
7
8#include <cmath>
9#include <fstream>
10#include <cstdio>
11#include <string>
12#include <thread>
13#include <vector>
14
15// Terminal color map. 10 colors grouped in ranges [0.0, 0.1, ..., 0.9]
16// Lowest is red, middle is yellow, highest is green.
17const std::vector<std::string> k_colors = {
18    "\033[38;5;196m", "\033[38;5;202m", "\033[38;5;208m", "\033[38;5;214m", "\033[38;5;220m",
19    "\033[38;5;226m", "\033[38;5;190m", "\033[38;5;154m", "\033[38;5;118m", "\033[38;5;82m",
20};
21
22//  500 -> 00:05.000
23// 6000 -> 01:00.000
24std::string to_timestamp(int64_t t, bool comma = false) {
25    int64_t msec = t * 10;
26    int64_t hr = msec / (1000 * 60 * 60);
27    msec = msec - hr * (1000 * 60 * 60);
28    int64_t min = msec / (1000 * 60);
29    msec = msec - min * (1000 * 60);
30    int64_t sec = msec / 1000;
31    msec = msec - sec * 1000;
32
33    char buf[32];
34    snprintf(buf, sizeof(buf), "%02d:%02d:%02d%s%03d", (int) hr, (int) min, (int) sec, comma ? "," : ".", (int) msec);
35
36    return std::string(buf);
37}
38
39int timestamp_to_sample(int64_t t, int n_samples) {
40    return std::max(0, std::min((int) n_samples - 1, (int) ((t*WHISPER_SAMPLE_RATE)/100)));
41}
42
43// helper function to replace substrings
44void replace_all(std::string & s, const std::string & search, const std::string & replace) {
45    for (size_t pos = 0; ; pos += replace.length()) {
46        pos = s.find(search, pos);
47        if (pos == std::string::npos) break;
48        s.erase(pos, search.length());
49        s.insert(pos, replace);
50    }
51}
52
53// command-line parameters
54struct whisper_params {
55    int32_t n_threads    = std::min(4, (int32_t) std::thread::hardware_concurrency());
56    int32_t n_processors = 1;
57    int32_t offset_t_ms  = 0;
58    int32_t offset_n     = 0;
59    int32_t duration_ms  = 0;
60    int32_t max_context  = -1;
61    int32_t max_len      = 0;
62
63    float word_thold = 0.01f;
64
65    bool speed_up       = false;
66    bool translate      = false;
67    bool diarize        = false;
68    bool output_txt     = false;
69    bool output_vtt     = false;
70    bool output_srt     = false;
71    bool output_wts     = false;
72    bool print_special  = false;
73    bool print_colors   = false;
74    bool print_progress = false;
75    bool no_timestamps  = false;
76
77    std::string language = "en";
78    std::string prompt;
79    std::string model    = "models/ggml-base.en.bin";
80
81    std::vector<std::string> fname_inp = {};
82};
83
84void whisper_print_usage(int argc, char ** argv, const whisper_params & params);
85
86bool whisper_params_parse(int argc, char ** argv, whisper_params & params) {
87    for (int i = 1; i < argc; i++) {
88        std::string arg = argv[i];
89
90        if (arg[0] != '-') {
91            params.fname_inp.push_back(arg);
92            continue;
93        }
94
95        if (arg == "-h" || arg == "--help") {
96            whisper_print_usage(argc, argv, params);
97            exit(0);
98        }
99        else if (arg == "-t"    || arg == "--threads")        { params.n_threads      = std::stoi(argv[++i]); }
100        else if (arg == "-p"    || arg == "--processors")     { params.n_processors   = std::stoi(argv[++i]); }
101        else if (arg == "-ot"   || arg == "--offset-t")       { params.offset_t_ms    = std::stoi(argv[++i]); }
102        else if (arg == "-on"   || arg == "--offset-n")       { params.offset_n       = std::stoi(argv[++i]); }
103        else if (arg == "-d"    || arg == "--duration")       { params.duration_ms    = std::stoi(argv[++i]); }
104        else if (arg == "-mc"   || arg == "--max-context")    { params.max_context    = std::stoi(argv[++i]); }
105        else if (arg == "-ml"   || arg == "--max-len")        { params.max_len        = std::stoi(argv[++i]); }
106        else if (arg == "-wt"   || arg == "--word-thold")     { params.word_thold     = std::stof(argv[++i]); }
107        else if (arg == "-su"   || arg == "--speed-up")       { params.speed_up       = true; }
108        else if (arg == "-tr"   || arg == "--translate")      { params.translate      = true; }
109        else if (arg == "-di"   || arg == "--diarize")        { params.diarize        = true; }
110        else if (arg == "-otxt" || arg == "--output-txt")     { params.output_txt     = true; }
111        else if (arg == "-ovtt" || arg == "--output-vtt")     { params.output_vtt     = true; }
112        else if (arg == "-osrt" || arg == "--output-srt")     { params.output_srt     = true; }
113        else if (arg == "-owts" || arg == "--output-words")   { params.output_wts     = true; }
114        else if (arg == "-ps"   || arg == "--print-special")  { params.print_special  = true; }
115        else if (arg == "-pc"   || arg == "--print-colors")   { params.print_colors   = true; }
116        else if (arg == "-pp"   || arg == "--print-progress") { params.print_progress = true; }
117        else if (arg == "-nt"   || arg == "--no-timestamps")  { params.no_timestamps  = true; }
118        else if (arg == "-l"    || arg == "--language")       { params.language       = argv[++i]; }
119        else if (                  arg == "--prompt")         { params.prompt         = argv[++i]; }
120        else if (arg == "-m"    || arg == "--model")          { params.model          = argv[++i]; }
121        else if (arg == "-f"    || arg == "--file")           { params.fname_inp.emplace_back(argv[++i]); }
122        else {
123            fprintf(stderr, "error: unknown argument: %s\n", arg.c_str());
124            whisper_print_usage(argc, argv, params);
125            exit(0);
126        }
127    }
128
129    return true;
130}
131
132void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params & params) {
133    fprintf(stderr, "\n");
134    fprintf(stderr, "usage: %s [options] file0.wav file1.wav ...\n", argv[0]);
135    fprintf(stderr, "\n");
136    fprintf(stderr, "options:\n");
137    fprintf(stderr, "  -h,       --help           [default] show this help message and exit\n");
138    fprintf(stderr, "  -t N,     --threads N      [%-7d] number of threads to use during computation\n",    params.n_threads);
139    fprintf(stderr, "  -p N,     --processors N   [%-7d] number of processors to use during computation\n", params.n_processors);
140    fprintf(stderr, "  -ot N,    --offset-t N     [%-7d] time offset in milliseconds\n",                    params.offset_t_ms);
141    fprintf(stderr, "  -on N,    --offset-n N     [%-7d] segment index offset\n",                           params.offset_n);
142    fprintf(stderr, "  -d  N,    --duration N     [%-7d] duration of audio to process in milliseconds\n",   params.duration_ms);
143    fprintf(stderr, "  -mc N,    --max-context N  [%-7d] maximum number of text context tokens to store\n", params.max_context);
144    fprintf(stderr, "  -ml N,    --max-len N      [%-7d] maximum segment length in characters\n",           params.max_len);
145    fprintf(stderr, "  -wt N,    --word-thold N   [%-7.2f] word timestamp probability threshold\n",         params.word_thold);
146    fprintf(stderr, "  -su,      --speed-up       [%-7s] speed up audio by x2 (reduced accuracy)\n",        params.speed_up ? "true" : "false");
147    fprintf(stderr, "  -tr,      --translate      [%-7s] translate from source language to english\n",      params.translate ? "true" : "false");
148    fprintf(stderr, "  -di,      --diarize        [%-7s] stereo audio diarization\n",                       params.diarize ? "true" : "false");
149    fprintf(stderr, "  -otxt,    --output-txt     [%-7s] output result in a text file\n",                   params.output_txt ? "true" : "false");
150    fprintf(stderr, "  -ovtt,    --output-vtt     [%-7s] output result in a vtt file\n",                    params.output_vtt ? "true" : "false");
151    fprintf(stderr, "  -osrt,    --output-srt     [%-7s] output result in a srt file\n",                    params.output_srt ? "true" : "false");
152    fprintf(stderr, "  -owts,    --output-words   [%-7s] output script for generating karaoke video\n",     params.output_wts ? "true" : "false");
153    fprintf(stderr, "  -ps,      --print-special  [%-7s] print special tokens\n",                           params.print_special ? "true" : "false");
154    fprintf(stderr, "  -pc,      --print-colors   [%-7s] print colors\n",                                   params.print_colors ? "true" : "false");
155    fprintf(stderr, "  -pp,      --print-progress [%-7s] print progress\n",                                 params.print_progress ? "true" : "false");
156    fprintf(stderr, "  -nt,      --no-timestamps  [%-7s] do not print timestamps\n",                        params.no_timestamps ? "false" : "true");
157    fprintf(stderr, "  -l LANG,  --language LANG  [%-7s] spoken language ('auto' for auto-detect)\n",       params.language.c_str());
158    fprintf(stderr, "            --prompt PROMPT  [%-7s] initial prompt\n",                                 params.prompt.c_str());
159    fprintf(stderr, "  -m FNAME, --model FNAME    [%-7s] model path\n",                                     params.model.c_str());
160    fprintf(stderr, "  -f FNAME, --file FNAME     [%-7s] input WAV file path\n",                            "");
161    fprintf(stderr, "\n");
162}
163
164struct whisper_print_user_data {
165    const whisper_params * params;
166
167    const std::vector<std::vector<float>> * pcmf32s;
168};
169
170void whisper_print_segment_callback(struct whisper_context * ctx, int n_new, void * user_data) {
171    const auto & params  = *((whisper_print_user_data *) user_data)->params;
172    const auto & pcmf32s = *((whisper_print_user_data *) user_data)->pcmf32s;
173
174    const int n_segments = whisper_full_n_segments(ctx);
175
176    // print the last n_new segments
177    const int s0 = n_segments - n_new;
178    if (s0 == 0) {
179        printf("\n");
180    }
181
182    for (int i = s0; i < n_segments; i++) {
183        if (params.no_timestamps) {
184            if (params.print_colors) {
185                for (int j = 0; j < whisper_full_n_tokens(ctx, i); ++j) {
186                    if (params.print_special == false) {
187                        const whisper_token id = whisper_full_get_token_id(ctx, i, j);
188                        if (id >= whisper_token_eot(ctx)) {
189                            continue;
190                        }
191                    }
192
193                    const char * text = whisper_full_get_token_text(ctx, i, j);
194                    const float  p    = whisper_full_get_token_p   (ctx, i, j);
195
196                    const int col = std::max(0, std::min((int) k_colors.size(), (int) (std::pow(p, 3)*float(k_colors.size()))));
197
198                    printf("%s%s%s", k_colors[col].c_str(), text, "\033[0m");
199                }
200            } else {
201                const char * text = whisper_full_get_segment_text(ctx, i);
202                printf("%s", text);
203            }
204            fflush(stdout);
205        } else {
206            const int64_t t0 = whisper_full_get_segment_t0(ctx, i);
207            const int64_t t1 = whisper_full_get_segment_t1(ctx, i);
208
209            std::string speaker;
210
211            if (params.diarize && pcmf32s.size() == 2) {
212                const int64_t n_samples = pcmf32s[0].size();
213
214                const int64_t is0 = timestamp_to_sample(t0, n_samples);
215                const int64_t is1 = timestamp_to_sample(t1, n_samples);
216
217                double energy0 = 0.0f;
218                double energy1 = 0.0f;
219
220                for (int64_t j = is0; j < is1; j++) {
221                    energy0 += fabs(pcmf32s[0][j]);
222                    energy1 += fabs(pcmf32s[1][j]);
223                }
224
225                if (energy0 > 1.1*energy1) {
226                    speaker = "(speaker 0)";
227                } else if (energy1 > 1.1*energy0) {
228                    speaker = "(speaker 1)";
229                } else {
230                    speaker = "(speaker ?)";
231                }
232
233                //printf("is0 = %lld, is1 = %lld, energy0 = %f, energy1 = %f, %s\n", is0, is1, energy0, energy1, speaker.c_str());
234            }
235
236            if (params.print_colors) {
237                printf("[%s --> %s]  ", to_timestamp(t0).c_str(), to_timestamp(t1).c_str());
238                for (int j = 0; j < whisper_full_n_tokens(ctx, i); ++j) {
239                    if (params.print_special == false) {
240                        const whisper_token id = whisper_full_get_token_id(ctx, i, j);
241                        if (id >= whisper_token_eot(ctx)) {
242                            continue;
243                        }
244                    }
245
246                    const char * text = whisper_full_get_token_text(ctx, i, j);
247                    const float  p    = whisper_full_get_token_p   (ctx, i, j);
248
249                    const int col = std::max(0, std::min((int) k_colors.size(), (int) (std::pow(p, 3)*float(k_colors.size()))));
250
251                    printf("%s%s%s%s", speaker.c_str(), k_colors[col].c_str(), text, "\033[0m");
252                }
253                printf("\n");
254            } else {
255                const char * text = whisper_full_get_segment_text(ctx, i);
256
257                printf("[%s --> %s]  %s%s\n", to_timestamp(t0).c_str(), to_timestamp(t1).c_str(), speaker.c_str(), text);
258            }
259        }
260    }
261}
262
263bool output_txt(struct whisper_context * ctx, const char * fname) {
264    std::ofstream fout(fname);
265    if (!fout.is_open()) {
266        fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
267        return false;
268    }
269
270    fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
271
272    const int n_segments = whisper_full_n_segments(ctx);
273    for (int i = 0; i < n_segments; ++i) {
274        const char * text = whisper_full_get_segment_text(ctx, i);
275        fout << text << "\n";
276    }
277
278    return true;
279}
280
281bool output_vtt(struct whisper_context * ctx, const char * fname) {
282    std::ofstream fout(fname);
283    if (!fout.is_open()) {
284        fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
285        return false;
286    }
287
288    fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
289
290    fout << "WEBVTT\n\n";
291
292    const int n_segments = whisper_full_n_segments(ctx);
293    for (int i = 0; i < n_segments; ++i) {
294        const char * text = whisper_full_get_segment_text(ctx, i);
295        const int64_t t0 = whisper_full_get_segment_t0(ctx, i);
296        const int64_t t1 = whisper_full_get_segment_t1(ctx, i);
297
298        fout << to_timestamp(t0) << " --> " << to_timestamp(t1) << "\n";
299        fout << text << "\n\n";
300    }
301
302    return true;
303}
304
305bool output_srt(struct whisper_context * ctx, const char * fname, const whisper_params & params) {
306    std::ofstream fout(fname);
307    if (!fout.is_open()) {
308        fprintf(stderr, "%s: failed to open '%s' for writing\n", __func__, fname);
309        return false;
310    }
311
312    fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
313
314    const int n_segments = whisper_full_n_segments(ctx);
315    for (int i = 0; i < n_segments; ++i) {
316        const char * text = whisper_full_get_segment_text(ctx, i);
317        const int64_t t0 = whisper_full_get_segment_t0(ctx, i);
318        const int64_t t1 = whisper_full_get_segment_t1(ctx, i);
319
320        fout << i + 1 + params.offset_n << "\n";
321        fout << to_timestamp(t0, true) << " --> " << to_timestamp(t1, true) << "\n";
322        fout << text << "\n\n";
323    }
324
325    return true;
326}
327
328// karaoke video generation
329// outputs a bash script that uses ffmpeg to generate a video with the subtitles
330// TODO: font parameter adjustments
331bool output_wts(struct whisper_context * ctx, const char * fname, const char * fname_inp, const whisper_params & /*params*/, float t_sec) {
332    std::ofstream fout(fname);
333
334    fprintf(stderr, "%s: saving output to '%s'\n", __func__, fname);
335
336    // TODO: become parameter
337    static const char * font = "/System/Library/Fonts/Supplemental/Courier New Bold.ttf";
338
339    fout << "#!/bin/bash" << "\n";
340    fout << "\n";
341
342    fout << "ffmpeg -i " << fname_inp << " -f lavfi -i color=size=1200x120:duration=" << t_sec << ":rate=25:color=black -vf \"";
343
344    for (int i = 0; i < whisper_full_n_segments(ctx); i++) {
345        const int64_t t0 = whisper_full_get_segment_t0(ctx, i);
346        const int64_t t1 = whisper_full_get_segment_t1(ctx, i);
347
348        const int n = whisper_full_n_tokens(ctx, i);
349
350        std::vector<whisper_token_data> tokens(n);
351        for (int j = 0; j < n; ++j) {
352            tokens[j] = whisper_full_get_token_data(ctx, i, j);
353        }
354
355        if (i > 0) {
356            fout << ",";
357        }
358
359        // background text
360        fout << "drawtext=fontfile='" << font << "':fontsize=24:fontcolor=gray:x=(w-text_w)/2:y=h/2:text='':enable='between(t," << t0/100.0 << "," << t0/100.0 << ")'";
361
362        bool is_first = true;
363
364        for (int j = 0; j < n; ++j) {
365            const auto & token = tokens[j];
366
367            if (tokens[j].id >= whisper_token_eot(ctx)) {
368                continue;
369            }
370
371            std::string txt_bg;
372            std::string txt_fg; // highlight token
373            std::string txt_ul; // underline
374
375            txt_bg = "> ";
376            txt_fg = "> ";
377            txt_ul = "\\ \\ ";
378
379            {
380                for (int k = 0; k < n; ++k) {
381                    const auto & token2 = tokens[k];
382
383                    if (tokens[k].id >= whisper_token_eot(ctx)) {
384                        continue;
385                    }
386
387                    const std::string txt = whisper_token_to_str(ctx, token2.id);
388
389                    txt_bg += txt;
390
391                    if (k == j) {
392                        for (int l = 0; l < (int) txt.size(); ++l) {
393                            txt_fg += txt[l];
394                            txt_ul += "_";
395                        }
396                        txt_fg += "|";
397                    } else {
398                        for (int l = 0; l < (int) txt.size(); ++l) {
399                            txt_fg += "\\ ";
400                            txt_ul += "\\ ";
401                        }
402                    }
403                }
404
405                ::replace_all(txt_bg, "'", "\u2019");
406                ::replace_all(txt_bg, "\"", "\\\"");
407                ::replace_all(txt_fg, "'", "\u2019");
408                ::replace_all(txt_fg, "\"", "\\\"");
409            }
410
411            if (is_first) {
412                // background text
413                fout << ",drawtext=fontfile='" << font << "':fontsize=24:fontcolor=gray:x=(w-text_w)/2:y=h/2:text='" << txt_bg << "':enable='between(t," << t0/100.0 << "," << t1/100.0 << ")'";
414                is_first = false;
415            }
416
417            // foreground text
418            fout << ",drawtext=fontfile='" << font << "':fontsize=24:fontcolor=lightgreen:x=(w-text_w)/2+8:y=h/2:text='" << txt_fg << "':enable='between(t," << token.t0/100.0 << "," << token.t1/100.0 << ")'";
419
420            // underline
421            fout << ",drawtext=fontfile='" << font << "':fontsize=24:fontcolor=lightgreen:x=(w-text_w)/2+8:y=h/2+16:text='" << txt_ul << "':enable='between(t," << token.t0/100.0 << "," << token.t1/100.0 << ")'";
422        }
423    }
424
425    fout << "\" -c:v libx264 -pix_fmt yuv420p -y " << fname_inp << ".mp4" << "\n";
426
427    fout << "\n\n";
428    fout << "echo \"Your video has been saved to " << fname_inp << ".mp4\"" << "\n";
429    fout << "\n";
430    fout << "echo \"  ffplay " << fname_inp << ".mp4\"\n";
431    fout << "\n";
432
433    fout.close();
434
435    fprintf(stderr, "%s: run 'source %s' to generate karaoke video\n", __func__, fname);
436
437    return true;
438}
439
440int main(int argc, char ** argv) {
441    whisper_params params;
442
443    if (whisper_params_parse(argc, argv, params) == false) {
444        return 1;
445    }
446
447    if (params.fname_inp.empty()) {
448        fprintf(stderr, "error: no input files specified\n");
449        whisper_print_usage(argc, argv, params);
450        return 2;
451    }
452
453    if (params.language != "auto" && whisper_lang_id(params.language.c_str()) == -1) {
454        fprintf(stderr, "error: unknown language '%s'\n", params.language.c_str());
455        whisper_print_usage(argc, argv, params);
456        exit(0);
457    }
458
459    // whisper init
460
461    struct whisper_context * ctx = whisper_init(params.model.c_str());
462
463    if (ctx == nullptr) {
464        fprintf(stderr, "error: failed to initialize whisper context\n");
465        return 3;
466    }
467
468    // initial prompt
469    std::vector<whisper_token> prompt_tokens;
470
471    if (!params.prompt.empty()) {
472        prompt_tokens.resize(1024);
473        prompt_tokens.resize(whisper_tokenize(ctx, params.prompt.c_str(), prompt_tokens.data(), prompt_tokens.size()));
474
475        fprintf(stderr, "\n");
476        fprintf(stderr, "initial prompt: '%s'\n", params.prompt.c_str());
477        fprintf(stderr, "initial tokens: [ ");
478        for (int i = 0; i < (int) prompt_tokens.size(); ++i) {
479            fprintf(stderr, "%d ", prompt_tokens[i]);
480        }
481        fprintf(stderr, "]\n");
482    }
483
484    for (int f = 0; f < (int) params.fname_inp.size(); ++f) {
485        const auto fname_inp = params.fname_inp[f];
486
487        std::vector<float> pcmf32; // mono-channel F32 PCM
488        std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
489
490        // WAV input
491        {
492            drwav wav;
493            std::vector<uint8_t> wav_data; // used for pipe input from stdin
494
495            if (fname_inp == "-") {
496                {
497                    uint8_t buf[1024];
498                    while (true)
499                    {
500                        const size_t n = fread(buf, 1, sizeof(buf), stdin);
501                        if (n == 0) {
502                            break;
503                        }
504                        wav_data.insert(wav_data.end(), buf, buf + n);
505                    }
506                }
507
508                if (drwav_init_memory(&wav, wav_data.data(), wav_data.size(), nullptr) == false) {
509                    fprintf(stderr, "error: failed to open WAV file from stdin\n");
510                    return 4;
511                }
512
513                fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, wav_data.size());
514            }
515            else if (drwav_init_file(&wav, fname_inp.c_str(), nullptr) == false) {
516                fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str());
517                return 5;
518            }
519
520            if (wav.channels != 1 && wav.channels != 2) {
521                fprintf(stderr, "%s: WAV file '%s' must be mono or stereo\n", argv[0], fname_inp.c_str());
522                return 6;
523            }
524
525            if (params.diarize && wav.channels != 2 && params.no_timestamps == false) {
526                fprintf(stderr, "%s: WAV file '%s' must be stereo for diarization and timestamps have to be enabled\n", argv[0], fname_inp.c_str());
527                return 6;
528            }
529
530            if (wav.sampleRate != WHISPER_SAMPLE_RATE) {
531                fprintf(stderr, "%s: WAV file '%s' must be 16 kHz\n", argv[0], fname_inp.c_str());
532                return 8;
533            }
534
535            if (wav.bitsPerSample != 16) {
536                fprintf(stderr, "%s: WAV file '%s' must be 16-bit\n", argv[0], fname_inp.c_str());
537                return 9;
538            }
539
540            const uint64_t n = wav_data.empty() ? wav.totalPCMFrameCount : wav_data.size()/(wav.channels*wav.bitsPerSample/8);
541
542            std::vector<int16_t> pcm16;
543            pcm16.resize(n*wav.channels);
544            drwav_read_pcm_frames_s16(&wav, n, pcm16.data());
545            drwav_uninit(&wav);
546
547            // convert to mono, float
548            pcmf32.resize(n);
549            if (wav.channels == 1) {
550                for (uint64_t i = 0; i < n; i++) {
551                    pcmf32[i] = float(pcm16[i])/32768.0f;
552                }
553            } else {
554                for (uint64_t i = 0; i < n; i++) {
555                    pcmf32[i] = float(pcm16[2*i] + pcm16[2*i + 1])/65536.0f;
556                }
557            }
558
559            if (params.diarize) {
560                // convert to stereo, float
561                pcmf32s.resize(2);
562
563                pcmf32s[0].resize(n);
564                pcmf32s[1].resize(n);
565                for (uint64_t i = 0; i < n; i++) {
566                    pcmf32s[0][i] = float(pcm16[2*i])/32768.0f;
567                    pcmf32s[1][i] = float(pcm16[2*i + 1])/32768.0f;
568                }
569            }
570        }
571
572        // print system information
573        {
574            fprintf(stderr, "\n");
575            fprintf(stderr, "system_info: n_threads = %d / %d | %s\n",
576                    params.n_threads*params.n_processors, std::thread::hardware_concurrency(), whisper_print_system_info());
577        }
578
579        // print some info about the processing
580        {
581            fprintf(stderr, "\n");
582            if (!whisper_is_multilingual(ctx)) {
583                if (params.language != "en" || params.translate) {
584                    params.language = "en";
585                    params.translate = false;
586                    fprintf(stderr, "%s: WARNING: model is not multilingual, ignoring language and translation options\n", __func__);
587                }
588            }
589            fprintf(stderr, "%s: processing '%s' (%d samples, %.1f sec), %d threads, %d processors, lang = %s, task = %s, timestamps = %d ...\n",
590                    __func__, fname_inp.c_str(), int(pcmf32.size()), float(pcmf32.size())/WHISPER_SAMPLE_RATE,
591                    params.n_threads, params.n_processors,
592                    params.language.c_str(),
593                    params.translate ? "translate" : "transcribe",
594                    params.no_timestamps ? 0 : 1);
595
596            fprintf(stderr, "\n");
597        }
598
599        // run the inference
600        {
601            whisper_full_params wparams = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
602
603            wparams.print_realtime   = false;
604            wparams.print_progress   = params.print_progress;
605            wparams.print_timestamps = !params.no_timestamps;
606            wparams.print_special    = params.print_special;
607            wparams.translate        = params.translate;
608            wparams.language         = params.language.c_str();
609            wparams.n_threads        = params.n_threads;
610            wparams.n_max_text_ctx   = params.max_context >= 0 ? params.max_context : wparams.n_max_text_ctx;
611            wparams.offset_ms        = params.offset_t_ms;
612            wparams.duration_ms      = params.duration_ms;
613
614            wparams.token_timestamps = params.output_wts || params.max_len > 0;
615            wparams.thold_pt         = params.word_thold;
616            wparams.max_len          = params.output_wts && params.max_len == 0 ? 60 : params.max_len;
617
618            wparams.speed_up         = params.speed_up;
619
620            wparams.prompt_tokens    = prompt_tokens.empty() ? nullptr : prompt_tokens.data();
621            wparams.prompt_n_tokens  = prompt_tokens.empty() ? 0       : prompt_tokens.size();
622
623            whisper_print_user_data user_data = { &params, &pcmf32s };
624
625            // this callback is called on each new segment
626            if (!wparams.print_realtime) {
627                wparams.new_segment_callback           = whisper_print_segment_callback;
628                wparams.new_segment_callback_user_data = &user_data;
629            }
630
631            // example for abort mechanism
632            // in this example, we do not abort the processing, but we could if the flag is set to true
633            // the callback is called before every encoder run - if it returns false, the processing is aborted
634            {
635                static bool is_aborted = false; // NOTE: this should be atomic to avoid data race
636
637                wparams.encoder_begin_callback = [](struct whisper_context * /*ctx*/, void * user_data) {
638                    bool is_aborted = *(bool*)user_data;
639                    return !is_aborted;
640                };
641                wparams.encoder_begin_callback_user_data = &is_aborted;
642            }
643
644            if (whisper_full_parallel(ctx, wparams, pcmf32.data(), pcmf32.size(), params.n_processors) != 0) {
645                fprintf(stderr, "%s: failed to process audio\n", argv[0]);
646                return 10;
647            }
648        }
649
650        // output stuff
651        {
652            printf("\n");
653
654            // output to text file
655            if (params.output_txt) {
656                const auto fname_txt = fname_inp + ".txt";
657                output_txt(ctx, fname_txt.c_str());
658            }
659
660            // output to VTT file
661            if (params.output_vtt) {
662                const auto fname_vtt = fname_inp + ".vtt";
663                output_vtt(ctx, fname_vtt.c_str());
664            }
665
666            // output to SRT file
667            if (params.output_srt) {
668                const auto fname_srt = fname_inp + ".srt";
669                output_srt(ctx, fname_srt.c_str(), params);
670            }
671
672            // output to WTS file
673            if (params.output_wts) {
674                const auto fname_wts = fname_inp + ".wts";
675                output_wts(ctx, fname_wts.c_str(), fname_inp.c_str(), params, float(pcmf32.size() + 1000)/WHISPER_SAMPLE_RATE);
676            }
677        }
678    }
679
680    whisper_print_timings(ctx);
681    whisper_free(ctx);
682
683    return 0;
684}