// MFCApplication2.cpp #include "stdafx.h" #include "MFCApplication2.h" #include "MFCApplication2Dlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // デバッグ出力 #define ods OutputDebugString // general #include #include #include #include #include using namespace std; constexpr auto BUFSIZE = 2049; // C++ REST SDK #include #include using namespace pplx; using namespace utility; using namespace web; using namespace web::http; using namespace Concurrency; using namespace Concurrency::streams; // wav -> opus #include "opusenc_exec.h" using namespace std::chrono; class processingTimer { // Timer class by fujimori public: system_clock::time_point s, e; __int64 elapsed; char buf[20]; void start() { s = system_clock::now(); } void stop() { e = system_clock::now(); elapsed = duration_cast (e - s).count(); sprintf_s(buf, "%6lld msec.\n", elapsed); ods(buf); } }; std::wstring shift_jis_to_utf_16(const std::string& str) { // Source: qiita: https://qiita.com/yumetodo/items/453d14eff41b805d8fc4 static_assert(sizeof(wchar_t) == 2, "this function is windows only"); const int len = ::MultiByteToWideChar(932/*CP_ACP*/, 0, str.c_str(), -1, nullptr, 0); std::wstring re(len * 2 + 2, L'\0'); if (!::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, &re[0], len)) { const auto ec = ::GetLastError(); switch (ec) { case ERROR_INSUFFICIENT_BUFFER: throw std::runtime_error("in function utf_16_to_shift_jis, WideCharToMultiByte fail. cause: ERROR_INSUFFICIENT_BUFFER"); break; case ERROR_INVALID_FLAGS: throw std::runtime_error("in function utf_16_to_shift_jis, WideCharToMultiByte fail. cause: ERROR_INVALID_FLAGS"); break; case ERROR_INVALID_PARAMETER: throw std::runtime_error("in function utf_16_to_shift_jis, WideCharToMultiByte fail. cause: ERROR_INVALID_PARAMETER"); break; case ERROR_NO_UNICODE_TRANSLATION: throw std::runtime_error("in function utf_16_to_shift_jis, WideCharToMultiByte fail. cause: ERROR_NO_UNICODE_TRANSLATION"); break; default: throw std::runtime_error("in function utf_16_to_shift_jis, WideCharToMultiByte fail. cause: unknown(" + std::to_string(ec) + ')'); break; } } const std::size_t real_len = std::wcslen(re.c_str()); re.resize(real_len); re.shrink_to_fit(); return re; } char* format_number(unsigned long long num) { // source: http://www.ohfuji.name/?p=16 // 桁数の多い数にカンマを入れる std::vector sepnum; int number = abs(int(num)); int sgn = num >= 0 ? 1 : -1; while (number / 1000) { sepnum.push_back(number % 1000); number /= 1000; } std::stringstream ss; ss << number * sgn; for (std::vector::reverse_iterator i = sepnum.rbegin(); i < sepnum.rend(); i++) { ss << "," << std::setfill('0') << std::setw(3) << *i; } //modified string str = string(ss.str()); char *cstr = new char[str.size() + 1]; // memory allocated. std::char_traits::copy(cstr, str.c_str(), str.size() + 1); return cstr; } char* str2char(std::string str) { // std::stringをchar*にキャストする // source: https://marycore.jp/prog/cpp/convert-string-to-char/ char* cstr = new char[str.size() + 1]; std::char_traits::copy(cstr, str.c_str(), str.size() + 1); return cstr; } void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) { const char *end; const char *p; const char *s; if (path[0] && path[1] == ':') { if (drv) { *drv++ = *path++; *drv++ = *path++; *drv = '\0'; } } else if (drv) *drv = '\0'; for (end = path; *end && *end != ':';) end++; for (p = end; p > path && *--p != '\\' && *p != '/';) if (*p == '.') { end = p; break; } if (ext) for (s = end; (*ext = *s++);) ext++; for (p = end; p > path;) if (*--p == '\\' || *p == '/') { p++; break; } if (name) { for (s = p; s < end;) *name++ = *s++; *name = '\0'; } if (dir) { for (s = path; s < p;) *dir++ = *s++; *dir = '\0'; } } bool exec_speech2text_api(const CString &sound) { bool is_exec_failed = false; // return this. bool is_job_failed = false; //// Cancellation //// cancellation_token_source cts; auto cts_token = cts.get_token(); // あるタスクのキャンセル処理を作る手順 // 1. ラムダ式でctsの参照をキャプチャし、第二引数に cts.get_toekn() をとる // 2. タスクの戻り値(auto task = ...)を使って例外処理を書く // 3. あとは任意のタイミングで cts.cancel(); するとタスクが安全に中断される //// Timer //// processingTimer timer; processingTimer upload_timer; processingTimer polling_timer; char *filename = [&]() { std::string fullpath = (LPCTSTR)sound; int path_i = fullpath.find_last_of("\\") + 1; int ext_i = fullpath.find_last_of("."); std::string pathname = fullpath.substr(0, path_i + 1); std::string extname = fullpath.substr(ext_i, fullpath.size() - ext_i); std::string filename = fullpath.substr(path_i, ext_i - path_i); return str2char(filename); }(); // generate JSON & OPUS file path. char *sound_file = [sound]() { return str2char((string)(CT2CA)sound); }(); char json_file[1024]; char opus_file[1024]; { char *in_file = sound_file; char drive[3]; char dir[256]; char fname[256]; char ext[256]; splitpath(in_file, drive, dir, fname, ext); if (memcmp(".wav", ext, strlen(ext)) == 0) { sprintf_s(opus_file, "%s%s%s.opus", drive, dir, fname); sprintf_s(json_file, "%s%s%s.txt", drive, dir, fname); } } //// File Stream //// ofstream output_stream(json_file); wstring ws_sound_filepath = shift_jis_to_utf_16(string(opus_file)); // source: http://rodriguezlucha.blogspot.com/2015/05/simple-c-casablanca-rest-sdk.html streams::basic_istream input_stream = streams::file_stream::open_istream(ws_sound_filepath).get(); auto sound_filesize = [ws_sound_filepath]() { // source: https://gya-ia.hatenablog.com/entry/20151015/1444839789 ifstream ifs(ws_sound_filepath, ios::binary); ifs.seekg(0, ios::end); auto eofpos = ifs.tellg(); ifs.clear(); ifs.seekg(0, ios::beg); auto begpos = ifs.tellg(); unsigned long long size = eofpos - begpos; ifs.close(); return size; }(); //// HTTP //// client::http_client_config cli_cfg; wstring url; { #if 0 credentials cred(U("9427bcd0-6410-4ef7-b195-9e5b93ae9542"), U("xCPGobm6tk2X"));//yoshida url = L"https://stream.watsonplatform.net/speech-to-text/api/v1/recognitions?model=ja-JP_BroadbandModel×tamps=true"; #else 1 credentials cred(U("apikey"), U("RDu96dzZZmbJvHtd6lGfGLzOrRTqkPLD13ynUEKom952"));//miyazawa url = L"https://gateway-tok.watsonplatform.net/speech-to-text/api/v1/recognitions?model=ja-JP_BroadbandModel×tamps=true"; #endif cli_cfg.set_credentials(cred); } client::http_client cli(url, cli_cfg); http_request req(methods::POST); { req.headers().add(U("Content-Type"), U("audio/ogg;codecs=opus")); req.headers().add(U("Transfer-Encoding"), U("chunked")); req.headers().add(U("Host"), U("gateway-tok.watsonplatform.net")); req.headers().add(U("Accept"), U("application/json,text/html")); req.headers().add(U("Expect"), U("")); req.headers().add(U("Connection"), U("Keep-Alive")); req.set_body(input_stream); } // watch uploading progress. unsigned int current_progress = 0; req.set_progress_handler([&](message_direction::direction type, size64_t read_bytes) { auto progress = unsigned int(floor((double)read_bytes / (double)sound_filesize * 100)); if (progress > current_progress + 2) { char buf[BUFSIZE] = {}; char* cstr_total = format_number(sound_filesize); char* cstr_done = format_number(read_bytes); sprintf_s(buf, "[%s] %16s bytes, %3u % uploaded...", filename, cstr_done, progress); delete[] cstr_total; // memory released. delete[] cstr_done; // memory released. ods(buf); for (unsigned int i = 0; i < 10; i++) { if (i < progress / 10){ ods("="); } else if (i == progress / 10){ ods(">"); } else{ ods("."); } } ods("\n"); current_progress = progress; } }); //// Connection //// upload_timer.start(); auto upload_task = cli.request(req, cts_token).then([&](http_response res) { upload_timer.stop(); // if status_code is 'Created', go next. ods("uploaded.\n"); try { if (res.status_code() == status_codes::Created) { json::value j; j = res.extract_json().get(); ods(conversions::to_utf8string(j.serialize()).c_str()); wstring url = j[L"url"].as_string(); return url; } else { is_exec_failed = true; string j; j = res.extract_utf8string().get(); char buf[BUFSIZE] = {}; sprintf_s(buf, "error code: %d\n%s", res.status_code(), str2char(j)); ods(buf); cts.cancel(); return wstring(); } } catch (http_exception &e) { char str[BUFSIZE] = {}; sprintf_s(str, "HTTP Exception :: %s\n", e.what()); ods(str); ods("http_exception occured while extracting json (;_;)"); is_exec_failed = true; return wstring(); } }, cts.get_token()).then([&](wstring url) -> json::value { if (is_exec_failed) {return json::value();} //// HTTP for Polling //// client::http_client cli(url, cli_cfg); http_request req(methods::GET); http_response res; json::value j = json::value(); string s; //// Polling //// polling_timer.start(); while (1) { timer.start(); try { res = cli.request(req).get(); timer.stop(); res.headers().set_content_type(L"application/json"); j = res.extract_json().get(); ods(conversions::to_utf8string(j.serialize()).c_str()); json::value status = j[L"status"]; if (!status.is_null()) { s = conversions::to_utf8string(status.serialize()).c_str(); if (s == "\"processing\"") { ods("\n processing...\n"); Sleep(2500); } else if (s == "\"waiting\"") { ods("\n waiting...\n"); Sleep(2500); } else if (s == "\"failed\"") { polling_timer.stop(); ods("\n job failed.\n"); is_job_failed = true; return j; } else if (s == "\"completed\"") { polling_timer.stop(); ods("\n job completed.\n"); return j; } } } catch (const http_exception &e) { ods("http_exception occured while polling (;_;)"); char buf[BUFSIZE] = {}; sprintf_s(buf, "HTTP Exception :: %s\n", e.what()); ods(buf); is_exec_failed = true; break; } } cts.cancel(); return j; }, cts.get_token()).then([&output_stream](json::value j) -> void { // output json to file. output_stream << conversions::to_utf8string(j.serialize()).c_str(); output_stream.close(); return; }); try{ if (upload_task.wait() == task_status::canceled) { ods("task has been cancelled\n"); } else if (is_job_failed == true){ is_exec_failed = true; } else { upload_task.get(); } } catch (http_exception &e) { char str[BUFSIZE] = {}; sprintf_s(str, "HTTP Exception :: %s\n", e.what()); ods(str); ods("http_exception occured while uploading (;_;)"); is_exec_failed = true; } catch (const exception& e){ char str[BUFSIZE] = {}; sprintf_s(str, "Something Exception :: %s\n", e.what()); ods(str); is_exec_failed = true; } return is_exec_failed; } bool try_exec_api(const CString &sound) { // 0:not error 1:error int is_exec_failed = false; // 先にwav->opus変換を行う char *sound_char = [sound](){ return str2char((string)(CT2CA)sound); }(); // 変換 convert(sound_char); // 成功するまでAPIを叩く while (1) { is_exec_failed = exec_speech2text_api(sound); if (is_exec_failed == false) { ods("connection was successed, program will be finish correctry.\n"); is_exec_failed = true; break; } else { ods("connection was failed, function will be recalled...\n"); continue; } } return is_exec_failed; } void run_exec_parallel(vector *sound) { // 渡された音声・テキストのパスの数だけ、マルチスレッドを作る vector> f; for (size_t i = 0; i < sound->size(); i++) { f.push_back(async(launch::async, try_exec_api, sound->at(i).c_str())); } for (size_t i = 0; i < sound->size(); i++) { f.at(i).wait(); } return; } BEGIN_MESSAGE_MAP(CMFCApplication2App, CWinApp) ON_COMMAND(ID_HELP, &CWinApp::OnHelp) END_MESSAGE_MAP() CMFCApplication2App::CMFCApplication2App(){ m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART; } CMFCApplication2App theApp; BOOL CMFCApplication2App::InitInstance() { INITCOMMONCONTROLSEX InitCtrls; InitCtrls.dwSize = sizeof(InitCtrls); InitCtrls.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&InitCtrls); CWinApp::InitInstance(); AfxEnableControlContainer(); CShellManager *pShellManager = new CShellManager; CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); SetRegistryKey(_T("アプリケーション ウィザードで生成されたローカル アプリケーション")); // 音声ファイルと出力テキストのパスをvectorで渡す { // テストデータ vector sound = { "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_01.wav" , "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_02.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_03.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_04.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_05.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_06.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_07.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_08.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_09.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_10.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_11.wav", "C:\\Users\\mfujimori\\dev\\sp2txt\\baseball_12.wav" }; // 実行 run_exec_parallel(&sound); } CMFCApplication2Dlg dlg; m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); if (nResponse == IDOK) {} else if (nResponse == IDCANCEL) {} else if (nResponse == -1) { TRACE(traceAppMsg, 0, "警告: ダイアログの作成に失敗しました。アプリケーションは予期せずに終了します。\n"); TRACE(traceAppMsg, 0, "警告: ダイアログで MFC コントロールを使用している場合、#define _AFX_NO_MFC_CONTROLS_IN_DIALOGS を指定できません。\n"); } if (pShellManager != NULL) { delete pShellManager; } #if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS) ControlBarCleanUp(); #endif return FALSE; }