Skip to content

Commit 1e72e4b

Browse files
committed
Merge branch 'master' into swe_pr/get_rows
2 parents 1a79d18 + f3ff80e commit 1e72e4b

File tree

3 files changed

+130
-30
lines changed

3 files changed

+130
-30
lines changed

examples/server/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
set(CMAKE_CXX_STANDARD 17)
2+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
3+
14
set(TARGET whisper-server)
25
add_executable(${TARGET} server.cpp httplib.h)
36

examples/server/server.cpp

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,23 @@
1414
#include <string>
1515
#include <thread>
1616
#include <vector>
17+
#include <memory>
18+
#include <csignal>
19+
#include <atomic>
20+
#include <functional>
21+
#include <cstdlib>
22+
#if defined (_WIN32)
23+
#include <windows.h>
24+
#endif
1725

1826
using namespace httplib;
1927
using json = nlohmann::ordered_json;
2028

29+
enum server_state {
30+
SERVER_STATE_LOADING_MODEL, // Server is starting up, model not fully loaded yet
31+
SERVER_STATE_READY, // Server is ready and model is loaded
32+
};
33+
2134
namespace {
2235

2336
// output formats
@@ -27,6 +40,20 @@ const std::string srt_format = "srt";
2740
const std::string vjson_format = "verbose_json";
2841
const std::string vtt_format = "vtt";
2942

43+
std::function<void(int)> shutdown_handler;
44+
std::atomic_flag is_terminating = ATOMIC_FLAG_INIT;
45+
46+
inline void signal_handler(int signal) {
47+
if (is_terminating.test_and_set()) {
48+
// in case it hangs, we can force terminate the server by hitting Ctrl+C twice
49+
// this is for better developer experience, we can remove when the server is stable enough
50+
fprintf(stderr, "Received second interrupt, terminating immediately.\n");
51+
exit(1);
52+
}
53+
54+
shutdown_handler(signal);
55+
}
56+
3057
struct server_params
3158
{
3259
std::string hostname = "127.0.0.1";
@@ -654,6 +681,9 @@ int main(int argc, char ** argv) {
654681
}
655682
}
656683

684+
std::unique_ptr<httplib::Server> svr = std::make_unique<httplib::Server>();
685+
std::atomic<server_state> state{SERVER_STATE_LOADING_MODEL};
686+
657687
struct whisper_context * ctx = whisper_init_from_file_with_params(params.model.c_str(), cparams);
658688

659689
if (ctx == nullptr) {
@@ -663,9 +693,10 @@ int main(int argc, char ** argv) {
663693

664694
// initialize openvino encoder. this has no effect on whisper.cpp builds that don't have OpenVINO configured
665695
whisper_ctx_init_openvino_encoder(ctx, nullptr, params.openvino_encode_device.c_str(), nullptr);
696+
state.store(SERVER_STATE_READY);
697+
666698

667-
Server svr;
668-
svr.set_default_headers({{"Server", "whisper.cpp"},
699+
svr->set_default_headers({{"Server", "whisper.cpp"},
669700
{"Access-Control-Allow-Origin", "*"},
670701
{"Access-Control-Allow-Headers", "content-type, authorization"}});
671702

@@ -744,15 +775,15 @@ int main(int argc, char ** argv) {
744775
whisper_params default_params = params;
745776

746777
// this is only called if no index.html is found in the public --path
747-
svr.Get(sparams.request_path + "/", [&default_content](const Request &, Response &res){
778+
svr->Get(sparams.request_path + "/", [&](const Request &, Response &res){
748779
res.set_content(default_content, "text/html");
749780
return false;
750781
});
751782

752-
svr.Options(sparams.request_path + sparams.inference_path, [&](const Request &, Response &){
783+
svr->Options(sparams.request_path + sparams.inference_path, [&](const Request &, Response &){
753784
});
754785

755-
svr.Post(sparams.request_path + sparams.inference_path, [&](const Request &req, Response &res){
786+
svr->Post(sparams.request_path + sparams.inference_path, [&](const Request &req, Response &res){
756787
// acquire whisper model mutex lock
757788
std::lock_guard<std::mutex> lock(whisper_mutex);
758789

@@ -1068,8 +1099,9 @@ int main(int argc, char ** argv) {
10681099
// reset params to their defaults
10691100
params = default_params;
10701101
});
1071-
svr.Post(sparams.request_path + "/load", [&](const Request &req, Response &res){
1102+
svr->Post(sparams.request_path + "/load", [&](const Request &req, Response &res){
10721103
std::lock_guard<std::mutex> lock(whisper_mutex);
1104+
state.store(SERVER_STATE_LOADING_MODEL);
10731105
if (!req.has_file("model"))
10741106
{
10751107
fprintf(stderr, "error: no 'model' field in the request\n");
@@ -1101,18 +1133,25 @@ int main(int argc, char ** argv) {
11011133
// initialize openvino encoder. this has no effect on whisper.cpp builds that don't have OpenVINO configured
11021134
whisper_ctx_init_openvino_encoder(ctx, nullptr, params.openvino_encode_device.c_str(), nullptr);
11031135

1136+
state.store(SERVER_STATE_READY);
11041137
const std::string success = "Load was successful!";
11051138
res.set_content(success, "application/text");
11061139

11071140
// check if the model is in the file system
11081141
});
11091142

1110-
svr.Get(sparams.request_path + "/health", [&](const Request &, Response &res){
1111-
const std::string health_response = "{\"status\":\"ok\"}";
1112-
res.set_content(health_response, "application/json");
1143+
svr->Get(sparams.request_path + "/health", [&](const Request &, Response &res){
1144+
server_state current_state = state.load();
1145+
if (current_state == SERVER_STATE_READY) {
1146+
const std::string health_response = "{\"status\":\"ok\"}";
1147+
res.set_content(health_response, "application/json");
1148+
} else {
1149+
res.set_content("{\"status\":\"loading model\"}", "application/json");
1150+
res.status = 503;
1151+
}
11131152
});
11141153

1115-
svr.set_exception_handler([](const Request &, Response &res, std::exception_ptr ep) {
1154+
svr->set_exception_handler([](const Request &, Response &res, std::exception_ptr ep) {
11161155
const char fmt[] = "500 Internal Server Error\n%s";
11171156
char buf[BUFSIZ];
11181157
try {
@@ -1126,7 +1165,7 @@ int main(int argc, char ** argv) {
11261165
res.status = 500;
11271166
});
11281167

1129-
svr.set_error_handler([](const Request &req, Response &res) {
1168+
svr->set_error_handler([](const Request &req, Response &res) {
11301169
if (res.status == 400) {
11311170
res.set_content("Invalid request", "text/plain");
11321171
} else if (res.status != 500) {
@@ -1136,29 +1175,61 @@ int main(int argc, char ** argv) {
11361175
});
11371176

11381177
// set timeouts and change hostname and port
1139-
svr.set_read_timeout(sparams.read_timeout);
1140-
svr.set_write_timeout(sparams.write_timeout);
1178+
svr->set_read_timeout(sparams.read_timeout);
1179+
svr->set_write_timeout(sparams.write_timeout);
11411180

1142-
if (!svr.bind_to_port(sparams.hostname, sparams.port))
1181+
if (!svr->bind_to_port(sparams.hostname, sparams.port))
11431182
{
11441183
fprintf(stderr, "\ncouldn't bind to server socket: hostname=%s port=%d\n\n",
11451184
sparams.hostname.c_str(), sparams.port);
11461185
return 1;
11471186
}
11481187

11491188
// Set the base directory for serving static files
1150-
svr.set_base_dir(sparams.public_path);
1189+
svr->set_base_dir(sparams.public_path);
11511190

11521191
// to make it ctrl+clickable:
11531192
printf("\nwhisper server listening at http://%s:%d\n\n", sparams.hostname.c_str(), sparams.port);
11541193

1155-
if (!svr.listen_after_bind())
1156-
{
1157-
return 1;
1158-
}
1194+
shutdown_handler = [&](int signal) {
1195+
printf("\nCaught signal %d, shutting down gracefully...\n", signal);
1196+
if (svr) {
1197+
svr->stop();
1198+
}
1199+
};
1200+
1201+
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
1202+
struct sigaction sigint_action;
1203+
sigint_action.sa_handler = signal_handler;
1204+
sigemptyset (&sigint_action.sa_mask);
1205+
sigint_action.sa_flags = 0;
1206+
sigaction(SIGINT, &sigint_action, NULL);
1207+
sigaction(SIGTERM, &sigint_action, NULL);
1208+
#elif defined (_WIN32)
1209+
auto console_ctrl_handler = +[](DWORD ctrl_type) -> BOOL {
1210+
return (ctrl_type == CTRL_C_EVENT) ? (signal_handler(SIGINT), true) : false;
1211+
};
1212+
SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(console_ctrl_handler), true);
1213+
#endif
1214+
1215+
// clean up function, to be called before exit
1216+
auto clean_up = [&]() {
1217+
whisper_print_timings(ctx);
1218+
whisper_free(ctx);
1219+
};
1220+
1221+
std::thread t([&] {
1222+
if (!svr->listen_after_bind()) {
1223+
fprintf(stderr, "error: server listen failed\n");
1224+
}
1225+
});
1226+
1227+
svr->wait_until_ready();
1228+
1229+
t.join();
1230+
11591231

1160-
whisper_print_timings(ctx);
1161-
whisper_free(ctx);
1232+
clean_up();
11621233

11631234
return 0;
11641235
}

examples/yt-wsp.sh

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,41 @@ cleanup() {
8080
}
8181

8282
print_help() {
83-
echo "################################################################################"
84-
echo "Usage: ./examples/yt-wsp.sh <video_url>"
85-
echo "# See configurable env variables in the script; there are many!"
86-
echo "# This script will produce an MP4 muxed file in the working directory; it will"
87-
echo "# be named for the title and id of the video."
88-
echo "# passing in https://youtu.be/VYJtb2YXae8 produces a file named";
89-
echo "# 'Why_we_all_need_subtitles_now-VYJtb2YXae8-res.mp4'"
90-
echo "# Requirements: ffmpeg yt-dlp whisper.cpp"
91-
echo "################################################################################"
83+
cat << 'EOF'
84+
Usage:
85+
MODEL_PATH=<model> \
86+
WHISPER_EXECUTABLE=<whisper-cli> \
87+
WHISPER_LANG=en \
88+
WHISPER_THREAD_COUNT=<int> \
89+
./examples/yt-wsp.sh <video_url>
90+
91+
Description:
92+
This script downloads a YouTube video, generates subtitles using Whisper,
93+
and muxes them into an MP4 output file.
94+
95+
Output:
96+
An MP4 file with embedded subtitles will be produced in the working directory.
97+
The file will be named using the video title and ID.
98+
Example:
99+
Input: https://youtu.be/VYJtb2YXae8
100+
Output: Why_we_all_need_subtitles_now-VYJtb2YXae8-res.mp4
101+
102+
Requirements:
103+
- ffmpeg
104+
- yt-dlp
105+
- whisper.cpp
106+
107+
Environment Variables:
108+
MODEL_PATH Path to the Whisper model (e.g., models/ggml-base.en.bin)
109+
WHISPER_EXECUTABLE Path to the Whisper CLI executable
110+
WHISPER_LANG Language code (e.g., 'en' for English)
111+
WHISPER_THREAD_COUNT Number of CPU threads to use
112+
113+
Tip:
114+
The script has many configurable environment variables.
115+
Review the source code to explore all options.
116+
117+
EOF
92118
}
93119

94120
check_requirements() {

0 commit comments

Comments
 (0)