diff --git a/accessibility.h b/accessibility.h index dadf33fbc1..df965df2bf 100644 --- a/accessibility.h +++ b/accessibility.h @@ -31,36 +31,11 @@ #endif #include "configuration.h" -#include "tasks/tasks_internal.h" - -#ifdef HAVE_THREADS -#include "rthreads/rthreads.h" -#endif typedef struct { - /* The last request task, used to prepare and send the translation */ - retro_task_t *request_task; - - /* The last response task, used to parse costly translation data */ - retro_task_t *response_task; - - /* Timestamp of the last translation request */ - retro_time_t last_call; - - #ifdef HAVE_THREADS - /* Necessary because last_image is manipulated by task handlers */ - slock_t *image_lock; - #endif - - /* Frame captured during the last call to the translation service */ - uint8_t *last_image; - unsigned last_image_size; - - /* 1 if the automatic mode has been enabled, 0 otherwise */ int ai_service_auto; - - /* Text-to-speech narrator override flag */ + /* Is text-to-speech accessibility turned on? */ bool enabled; } access_state_t; @@ -71,86 +46,46 @@ bool is_narrator_running(bool accessibility_enable); #endif /* - Invoke this method to send a request to the AI service. - It makes the following POST request using URL params: - – source_lang (optional): language code of the content currently running. - – target_lang (optional): language of the content to return. - – output: comma-separated list of formats that must be provided by the - service. Also lists supported sub-formats. + This function does all the stuff needed to translate the game screen, + using the URL given in the settings. Once the image from the frame + buffer is sent to the server, the callback will write the translated + image to the screen. - The currently supported formats are: - – sound: raw audio to playback. (wav) - – text: text to be read through internal text-to-speech capabilities. - 'subs' can be specified on top of that to explain that we are looking - for short text response in the manner of subtitles. - – image: image to display on top of the video feed. Widgets will be used - first if possible, otherwise we'll try to draw it directly on the - video buffer. (bmp, png, png-a) [All in 24-bits BGR formats] + Supported client/services (thus far) + -VGTranslate client ( www.gitlab.com/spherebeaker/vg_translate ) + -Ztranslate client/service ( www.ztranslate.net/docs/service ) - In addition, the request contains a JSON payload, formatted as such: - – image: captured frame from the currently running content (in base64). - – format: format of the captured frame ("png", or "bmp"). - – coords: array describing the coordinates of the image within the - viewport space (x, y, width, height). - – viewport: array describing the size of the viewport (width, height). - – label: a text string describing the content (__). - – state: a JSON object describing the state of the frontend, containing: - – paused: 1 if the content has been paused, 0 otherwise. - – : the name of a retropad input, valued 1 if pressed. - (a, b, x, y, l, r, l2, r2, l3, r3) - (up, down, left, right, start, select) + To use a client, download the relevant code/release, configure + them, and run them on your local machine, or network. Set the + retroarch configuration to point to your local client (usually + listening on localhost:4404 ) and enable translation service. - The translation component then expects a response from the AI service in the - form of a JSON payload, formatted as such: - – image: base64 representation of an image in a supported format. - – sound: base64 representation of a sound byte in a supported format. - – text: results from the service as a string. - – text_position: hint for the position of the text when the service is - running in text mode (ie subtitles). Position is a number, - 1 for Bottom or 2 for Top (defaults to bottom). - – press: a list of retropad input to forcibly press. On top of the - expected keys (cf. 'state' above) values 'pause' and 'unpause' can be - specified to control the flow of the content. - – error: any error encountered with the request. - – auto: either 'auto' or 'continue' to control automatic requests. + If you don't want to run a client, you can also use a service, + which is basically like someone running a client for you. The + downside here is that your retroarch device will have to have + an internet connection, and you may have to sign up for it. - All fields are optional, but at least one of them must be present. - If 'error' is set, the error is shown to the user and everything else is - ignored, even 'auto' settings. + To make your own server, it must listen for a POST request, which + will consist of a JSON body, with the "image" field as a base64 + encoded string of a 24bit-BMP/PNG that the will be translated. + The server must output the translated image in the form of a + JSON body, with the "image" field also as a base64 encoded + 24bit-BMP, or as an alpha channel png. - With 'auto' on 'auto', RetroArch will automatically send a new request - (with a minimum delay enforced by uints.ai_service_poll_delay), with a value - of 'continue', RetroArch will ignore the returned content and skip to the - next automatic request. This allows the service to specify that the returned - content is the same as the one previously sent, so RetroArch does not need to - update its display unless necessary. With 'continue' the service *must* - still send the content, as we may need to display it if the user paused the - AI service for instance. - - {paused} boolean is passed in to indicate if the current call was made - during a paused frame. Due to how the menu widgets work, if the AI service - is called in 'auto' mode, then this call will be made while the menu widgets - unpause the core for a frame to update the on-screen widgets. To tell the AI - service what the pause mode is honestly, we store the runloop_paused - variable from before the service wipes the widgets, and pass that in here. + "paused" boolean is passed in to indicate if the current call + was made during a paused frame. Due to how the menu widgets work, + if the ai service is called in "auto" mode, then this call will + be made while the menu widgets unpause the core for a frame to update + the on-screen widgets. To tell the ai service what the pause + mode is honestly, we store the runloop_paused variable from before + the handle_translation_cb wipes the widgets, and pass that in here. */ bool run_translation_service(settings_t *settings, bool paused); -void translation_release(bool inform); - -/* Proxy for calls related to menu navigation */ -bool navigation_say( +bool accessibility_speak_priority( bool accessibility_enable, unsigned accessibility_narrator_speech_speed, - const char* speak_text, - int priority); - -/* Local platform-specific TTS */ -bool accessibility_speak_priority( - unsigned accessibility_narrator_speech_speed, - const char *speak_text, - int priority, - const char* voice); + const char* speak_text, int priority); access_state_t *access_state_get_ptr(void); diff --git a/config.def.h b/config.def.h index 39d8d120e6..a1b1729466 100644 --- a/config.def.h +++ b/config.def.h @@ -1822,14 +1822,8 @@ #define DEFAULT_AI_SERVICE_MODE 1 -#define DEFAULT_AI_SERVICE_TEXT_POSITION 0 -#define DEFAULT_AI_SERVICE_TEXT_PADDING 5 - #define DEFAULT_AI_SERVICE_URL "http://localhost:4404/" -#define DEFAULT_AI_SERVICE_POLL_DELAY 0 -#define MAXIMUM_AI_SERVICE_POLL_DELAY 500 - #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) #define DEFAULT_BUILTIN_MEDIAPLAYER_ENABLE true #else diff --git a/configuration.c b/configuration.c index c3613d8702..0a0fa25097 100644 --- a/configuration.c +++ b/configuration.c @@ -2511,9 +2511,6 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("ai_service_mode", &settings->uints.ai_service_mode, true, DEFAULT_AI_SERVICE_MODE, false); SETTING_UINT("ai_service_target_lang", &settings->uints.ai_service_target_lang, true, 0, false); SETTING_UINT("ai_service_source_lang", &settings->uints.ai_service_source_lang, true, 0, false); - SETTING_UINT("ai_service_poll_delay", &settings->uints.ai_service_poll_delay, true, DEFAULT_AI_SERVICE_POLL_DELAY, false); - SETTING_UINT("ai_service_text_position", &settings->uints.ai_service_text_position, true, DEFAULT_AI_SERVICE_TEXT_POSITION, false); - SETTING_UINT("ai_service_text_padding", &settings->uints.ai_service_text_padding, true, DEFAULT_AI_SERVICE_TEXT_PADDING, false); #ifdef HAVE_LIBNX SETTING_UINT("libnx_overclock", &settings->uints.libnx_overclock, true, SWITCH_DEFAULT_CPU_PROFILE, false); diff --git a/configuration.h b/configuration.h index 01285d01c3..e17c0286e1 100644 --- a/configuration.h +++ b/configuration.h @@ -348,9 +348,6 @@ typedef struct settings unsigned ai_service_mode; unsigned ai_service_target_lang; unsigned ai_service_source_lang; - unsigned ai_service_poll_delay; - unsigned ai_service_text_position; - unsigned ai_service_text_padding; unsigned core_updater_auto_backup_history_size; unsigned video_black_frame_insertion; diff --git a/frontend/drivers/platform_darwin.m b/frontend/drivers/platform_darwin.m index 5c88860239..9727e9164f 100644 --- a/frontend/drivers/platform_darwin.m +++ b/frontend/drivers/platform_darwin.m @@ -873,9 +873,10 @@ static bool is_narrator_running_macos(void) } static bool accessibility_speak_macos(int speed, - const char* speak_text, int priority, const char* voice) + const char* speak_text, int priority) { int pid; + const char *voice = get_user_language_iso639_1(false); char* language_speaker = accessibility_mac_language_code(voice); char* speeds[10] = {"80", "100", "125", "150", "170", "210", "260", "310", "380", "450"}; diff --git a/frontend/drivers/platform_unix.c b/frontend/drivers/platform_unix.c index b153565145..4255e2766e 100644 --- a/frontend/drivers/platform_unix.c +++ b/frontend/drivers/platform_unix.c @@ -2886,10 +2886,10 @@ static const char* accessibility_unix_language_code(const char* language) } static bool accessibility_speak_unix(int speed, - const char* speak_text, int priority, const char* voice) + const char* speak_text, int priority) { int pid; - const char* language = accessibility_unix_language_code(voice); + const char* language = accessibility_unix_language_code(get_user_language_iso639_1(true)); char* voice_out = (char*)malloc(3 + strlen(language)); char* speed_out = (char*)malloc(3 + 3); const char* speeds[10] = {"80", "100", "125", "150", "170", "210", "260", "310", "380", "450"}; diff --git a/frontend/drivers/platform_win32.c b/frontend/drivers/platform_win32.c index a24a5fea6d..5a4487fa38 100644 --- a/frontend/drivers/platform_win32.c +++ b/frontend/drivers/platform_win32.c @@ -1035,9 +1035,10 @@ static bool is_narrator_running_windows(void) } static bool accessibility_speak_windows(int speed, - const char* speak_text, int priority, const char* voice) + const char* speak_text, int priority) { char cmd[512]; + const char *voice = get_user_language_iso639_1(true); const char *language = accessibility_win_language_code(voice); const char *langid = accessibility_win_language_id(voice); bool res = false; @@ -1081,12 +1082,9 @@ static bool accessibility_speak_windows(int speed, if (!wc || res != 0) { RARCH_ERR("Error communicating with NVDA\n"); - /* Fallback on powershell immediately and retry */ - g_plat_win32_flags &= ~PLAT_WIN32_FLAG_USE_NVDA; - g_plat_win32_flags |= PLAT_WIN32_FLAG_USE_POWERSHELL; if (wc) free(wc); - return accessibility_speak_windows(speed, speak_text, priority, voice); + return false; } nvdaController_cancelSpeech_func(); diff --git a/frontend/frontend_driver.h b/frontend/frontend_driver.h index a9634214dd..3278fedefa 100644 --- a/frontend/frontend_driver.h +++ b/frontend/frontend_driver.h @@ -112,7 +112,7 @@ typedef struct frontend_ctx_driver enum retro_language (*get_user_language)(void); bool (*is_narrator_running)(void); bool (*accessibility_speak)(int speed, - const char* speak_text, int priority, const char* voice); + const char* speak_text, int priority); bool (*set_gamemode)(bool on); const char *ident; diff --git a/gfx/gfx_widgets.c b/gfx/gfx_widgets.c index 70fbde9296..cec4420858 100644 --- a/gfx/gfx_widgets.c +++ b/gfx/gfx_widgets.c @@ -1532,66 +1532,6 @@ static void INLINE gfx_widgets_font_unbind(gfx_widget_font_data_t *font_data) font_driver_bind_block(font_data->font, NULL); } -#ifdef HAVE_TRANSLATE -static void gfx_widgets_ai_line( - video_frame_info_t *video, char *line, int line_idx, int line_total) -{ - settings_t *settings = config_get_ptr(); - gfx_display_t *p_disp = (gfx_display_t*)video->disp_userdata; - dispgfx_widget_t *p_widget = (dispgfx_widget_t*)video->widgets_userdata; - void *userdata = video->userdata; - unsigned video_width = video->width; - unsigned video_height = video->height; - - int line_width = font_driver_get_message_width( - p_widget->gfx_widget_fonts.regular.font, - line, strlen(line), 1.0f); - - int hpadding = p_widget->simple_widget_padding; - int vpadding = settings->uints.ai_service_text_padding; - int half_vw = video_width * 0.5f; - int block_width = line_width + hpadding * 2; - int block_height = p_widget->simple_widget_height; - int block_x = half_vw - block_width * 0.5f; - int block_y = 0; - int line_y = 0; - - int position = (settings->uints.ai_service_text_position > 0) - ? settings->uints.ai_service_text_position - : p_widget->ai_service_text_position; - - switch (position) - { - case 0: /* Undef. */ - case 1: /* Bottom */ - block_y = (video_height * (100 - vpadding) * 0.01f) - - ((line_total - line_idx) * block_height); - break; - case 2: /* Top */ - block_y = (video_height * (vpadding * 0.01f)) - + (line_idx * block_height); - break; - } - - line_y = block_y + block_height * 0.5f - + p_widget->gfx_widget_fonts.regular.line_centre_offset; - - gfx_display_set_alpha(p_widget->backdrop_orig, DEFAULT_BACKDROP); - - gfx_display_draw_quad( - p_disp, userdata, video_width, video_height, - block_x, block_y, block_width, block_height, - video_width, video_height, - p_widget->backdrop_orig, - NULL); - - gfx_widgets_draw_text( - &p_widget->gfx_widget_fonts.regular, - line, half_vw, line_y, - video_width, video_height, - 0xFFFFFFFF, TEXT_ALIGN_CENTER, true); -} -#endif void gfx_widgets_frame(void *data) { @@ -1642,7 +1582,12 @@ void gfx_widgets_frame(void *data) /* AI Service overlay */ if (p_dispwidget->ai_service_overlay_state > 0) { - size_t text_length = strlen(p_dispwidget->ai_service_text); + float outline_color[16] = { + 0.00, 1.00, 0.00, 1.00, + 0.00, 1.00, 0.00, 1.00, + 0.00, 1.00, 0.00, 1.00, + 0.00, 1.00, 0.00, 1.00, + }; gfx_display_set_alpha(p_dispwidget->pure_white, 1.0f); @@ -1668,47 +1613,63 @@ void gfx_widgets_frame(void *data) if (dispctx->blend_end) dispctx->blend_end(userdata); } - - /* AI Service subtitle overlay widget */ - if (text_length > 0) - { - int padding = p_dispwidget->simple_widget_padding; - int text_width = font_driver_get_message_width( - p_dispwidget->gfx_widget_fonts.regular.font, - p_dispwidget->ai_service_text, - text_length, 1.0f); - - if (text_width > (video_width * 0.9f - padding * 2)) - { - size_t text_half = text_length / 2; - char *extra_line = (char*)malloc(sizeof(char) * text_length); - for (; text_half > 0; text_half--) - { - if (p_dispwidget->ai_service_text[text_half] == ' ') - { - p_dispwidget->ai_service_text[text_half] = '\0'; - gfx_widgets_ai_line( - video_info, p_dispwidget->ai_service_text, 0, 2); - strlcpy( - extra_line, - p_dispwidget->ai_service_text + text_half + 1, - text_length - text_half); - gfx_widgets_ai_line( - video_info, extra_line, 1, 2); - - p_dispwidget->ai_service_text[text_half] = ' '; - free(extra_line); - break; - } - } - } - else - { - gfx_widgets_ai_line( - video_info, p_dispwidget->ai_service_text, 0, 1); - } - } + /* top line */ + gfx_display_draw_quad( + p_disp, + userdata, + video_width, video_height, + 0, 0, + video_width, + p_dispwidget->divider_width_1px, + video_width, + video_height, + outline_color, + NULL + ); + /* bottom line */ + gfx_display_draw_quad( + p_disp, + userdata, + video_width, video_height, + 0, + video_height - p_dispwidget->divider_width_1px, + video_width, + p_dispwidget->divider_width_1px, + video_width, + video_height, + outline_color, + NULL + ); + /* left line */ + gfx_display_draw_quad( + p_disp, + userdata, + video_width, + video_height, + 0, + 0, + p_dispwidget->divider_width_1px, + video_height, + video_width, + video_height, + outline_color, + NULL + ); + /* right line */ + gfx_display_draw_quad( + p_disp, + userdata, + video_width, video_height, + video_width - p_dispwidget->divider_width_1px, + 0, + p_dispwidget->divider_width_1px, + video_height, + video_width, + video_height, + outline_color, + NULL + ); if (p_dispwidget->ai_service_overlay_state == 2) p_dispwidget->ai_service_overlay_state = 3; } @@ -2259,7 +2220,6 @@ void gfx_widgets_ai_service_overlay_unload(void) if (p_dispwidget->ai_service_overlay_state == 1) { video_driver_texture_unload(&p_dispwidget->ai_service_overlay_texture); - p_dispwidget->ai_service_text[0] = '\0'; p_dispwidget->ai_service_overlay_texture = 0; p_dispwidget->ai_service_overlay_state = 0; } diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index 52dec19db5..43bd95f4c4 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -242,8 +242,6 @@ typedef struct dispgfx_widget #ifdef HAVE_TRANSLATE unsigned ai_service_overlay_width; unsigned ai_service_overlay_height; - unsigned ai_service_text_position; - char ai_service_text[255]; #endif uint8_t flags; diff --git a/input/input_driver.c b/input/input_driver.c index dc524d3037..18f8f96705 100644 --- a/input/input_driver.c +++ b/input/input_driver.c @@ -7147,7 +7147,7 @@ void input_keyboard_event(bool down, unsigned code, say_char[1] = '\0'; if (character == 127 || character == 8) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, "backspace", 10); @@ -7155,12 +7155,12 @@ void input_keyboard_event(bool down, unsigned code, { const char *lut_name = accessibility_lut_name(c); if (lut_name) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, lut_name, 10); else if (character != 0) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, say_char, 10); diff --git a/intl/msg_hash_ar.h b/intl/msg_hash_ar.h index 8a5702dd3d..cc822b0a85 100644 --- a/intl/msg_hash_ar.h +++ b/intl/msg_hash_ar.h @@ -5493,10 +5493,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "وضع المشرف" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "أعلى" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "المحفوظات والمفضلة" diff --git a/intl/msg_hash_be.h b/intl/msg_hash_be.h index 403a8ddeef..9cb593e849 100644 --- a/intl/msg_hash_be.h +++ b/intl/msg_hash_be.h @@ -6065,26 +6065,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Рэжым дыктара" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Рэжым тэксту" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Тэкст + дыктар" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Выява + дыктар" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Унізе" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Угары" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Гісторыя ды ўпадабанае" diff --git a/intl/msg_hash_chs.h b/intl/msg_hash_chs.h index 12018979cc..bb52e2fccb 100644 --- a/intl/msg_hash_chs.h +++ b/intl/msg_hash_chs.h @@ -7002,31 +7002,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "服务将翻译成的语言。「默认」是英语。" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "AI 服务自动轮询延迟" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "自动调用之间采用最小延迟。降低反应,但会提高CPU性能。" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "AI 服务文本位置覆盖" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "当服务处于文本模式时覆盖遮罩的位置。" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "AI 服务文本覆盖 (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "当服务处于文本模式时应用到文本遮罩层的垂直填充。 更多填充将把文本显示到屏幕中心。" - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10277,26 +10252,6 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "讲述人模式" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "文本模式" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "文本 + 解说" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "图像 + 解说" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "底端" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "顶部" - ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "历史和收藏" @@ -13048,22 +13003,6 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "核心安装失败" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "不支持 AI 服务的视频驱动程序。" - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "自动翻译已启用。" - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "自动翻译已禁用。" - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "没有要翻译的内容。" - ) MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "按右键五次删除所有金手指。" diff --git a/intl/msg_hash_cht.h b/intl/msg_hash_cht.h index 93bd65bd9f..33779f3bc7 100644 --- a/intl/msg_hash_cht.h +++ b/intl/msg_hash_cht.h @@ -9465,10 +9465,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "自動朗讀模式" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "頂端" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "歷史和我的最愛" diff --git a/intl/msg_hash_cs.h b/intl/msg_hash_cs.h index fc43ae1b86..48b064a3ca 100644 --- a/intl/msg_hash_cs.h +++ b/intl/msg_hash_cs.h @@ -6710,30 +6710,7 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "Jazyk, do kterého bude služba překládána. Výchozí hodnota je angličtina." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Zpoždění automatického dotazování služby AI" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Minimální prodleva v ms mezi automatickými voláními. Snižuje reaktivitu, ale zvyšuje výkon procesoru." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Přepsání polohy textu služby AI" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Přepsání pozice překryvu, když je služba v režimu Text." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Výplň textu služby AI (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Vertikální výplň, která se použije na překrytí textu, když je služba v režimu Text. Větší výplň posune text směrem ke středu obrazovky." - ) + /* Settings > Accessibility */ @@ -9909,26 +9886,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Režim vypravěče" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Textový režim" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Text + Vypravěč" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Obrázek + Vypravěč" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Spodní" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Nahoře" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Historie a oblíbené položky" @@ -12684,22 +12642,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Instalace jádra selhala" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Ovladač videa není podporován pro službu AI." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Automatický překlad povolen." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Automatický překlad vypnut." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Není co překládat." - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Pětkrát stiskněte pravé tlačítko a smažte všechny cheaty." diff --git a/intl/msg_hash_de.h b/intl/msg_hash_de.h index 6d948f5044..459dfa3524 100644 --- a/intl/msg_hash_de.h +++ b/intl/msg_hash_de.h @@ -6854,31 +6854,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "Die Sprache, in die der Dienst übersetzt. 'Standard' ist Englisch." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Automatische Abfrageverzögerung für KI-Dienst" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Mindestverzögerung in ms zwischen automatischen Aufrufen. Verringert die Reaktionszeit, erhöht aber die CPU-Leistung." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "KI-Dienst-Textposition überschreiben" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Die Position des Overlays übersteuern, wenn sich der Dienst im Textmodus befindet." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "KI-Dienst-Textauffüllung (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Vertikaler Abstand, der auf das Textoverlay angewendet wird, wenn der Dienst im Textmodus ist. Ein größeres Auffüllen schiebt den Text in die Mitte des Bildschirms." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10029,26 +10004,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Erzählermodus" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Textmodus" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Text und Erzähler" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Bild und Erzähler" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Unten" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Oben" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Verlauf & Favoriten" @@ -12672,22 +12628,6 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Core Installation fehlgeschlagen" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Videotreiber wird für den KI-Dienst nicht unterstützt." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Automatische Übersetzung aktiviert." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Automatische Übersetzung deaktiviert." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Nichts zu übersetzen." - ) MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Drücke fünf Mal rechts, um alle Cheats zu löschen." diff --git a/intl/msg_hash_en.h b/intl/msg_hash_en.h index 12ff452ea2..eb59032edb 100644 --- a/intl/msg_hash_en.h +++ b/intl/msg_hash_en.h @@ -1034,10 +1034,7 @@ MSG_HASH( /* Settings > AI Service */ -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Vertical padding to apply to the text overlay, when the service is in Text mode. More padding will push the text towards the centre of the screen." - ) + /* Settings > Accessibility */ diff --git a/intl/msg_hash_es.h b/intl/msg_hash_es.h index be58a0e2cf..eebb6720f6 100644 --- a/intl/msg_hash_es.h +++ b/intl/msg_hash_es.h @@ -6966,31 +6966,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "Indica el idioma de destino de la traducción. En caso de seleccionar «Predeterminado», se traducirá a inglés." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Retraso al autosondear con el servicio de IA" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Indica el retraso mínimo (en ms) entre cada llamada automática. Reduce la reactividad, pero mejora el rendimiento de la CPU." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Personalizar posición de textos del servicio de IA" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Personaliza la posición de la superposición del modo Texto del servicio de IA." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Relleno de textos del servicio de IA (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Indica el relleno vertical que se añadirá a la superposición de textos cuando el servicio de IA esté configurado en el modo Texto. Si hay más relleno, el texto se desplazará hacia el centro de la pantalla." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10229,26 +10204,6 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Modo narrador" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Modo Texto" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Texto + Narración" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Imagen + Narración" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Giro de 180°" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Inicio" - ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Historial y favoritos" @@ -13000,22 +12955,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Error al instalar el núcleo" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "El controlador de vídeo no es compatible con el servicio de IA." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Traducción automática activada." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Traducción automática desactivada." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "No hay nada que traducir." - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Pulsa derecha cinco veces para eliminar todos los trucos." diff --git a/intl/msg_hash_fi.h b/intl/msg_hash_fi.h index 2a8032c3a1..d581c8e89f 100644 --- a/intl/msg_hash_fi.h +++ b/intl/msg_hash_fi.h @@ -5986,31 +5986,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "Kieli johon palvelu kääntää. 'Oletus' on englanti." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Tekoälypalvelun automaattinen kyselyviive" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Vähimmäisviive millisekunteina automaattisten kutsujen välillä. Laskee viivettä, mutta lisää prosessorin käyttöä." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Tekoälypalvelun tekstin sijainnin muutos" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Sijainnin muutos, kun palvelu on tekstitilassa." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Tekoälypalvelun tekstin marginaali (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Pystysuuntainen lisämarginaali, joka lisätään tekstipäällykseen tekstitilassa. Enemmän marginaalia työntää tekstin lähemmäksi näytön keskiosaa." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -8981,26 +8956,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Kertojatila" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Tekstitila" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Teksti + lukija" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Kuva + lukija" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Alhaalla" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Yläreuna" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Historia ja suosikit" @@ -11724,22 +11680,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Ytimen asennus epäonnistui" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Videoajuri ei tue tekoälyaplvelua." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Automaattinen käännös käytössä." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Automaattinen käännös pois käytöstä." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Ei mitään käännettävää." - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Poista kaikki huijaukset painamalla oikealle viisi kertaa." diff --git a/intl/msg_hash_fr.h b/intl/msg_hash_fr.h index 45c0bb2420..d9551eb70f 100644 --- a/intl/msg_hash_fr.h +++ b/intl/msg_hash_fr.h @@ -6926,31 +6926,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "La langue vers laquelle le service va traduire. 'Par défaut' est anglais." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Délai d'interrogation automatique du service IA" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Délai minimum en ms entre les appels automatiques. Diminue la réactivité, mais augmente les performances du processeur." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Modification de la position du texte pour le service IA" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Modifier la position de la surimpression, lorsque le service est en mode texte." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Marge intérieure du texte pour le service IA (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Marge verticale à appliquer au texte en surimpression, lorsque le service est en mode texte. Plus de remplissage poussera le texte vers le centre de l'écran." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10149,26 +10124,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Mode narrateur" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Mode texte" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Texte + narrateur" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Image + narrateur" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Bas" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Haut" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Historique et favoris" @@ -12972,22 +12928,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Installation du cœur échouée" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Pilote vidéo non pris en charge pour le service IA." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Traduction automatique activée." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Traduction automatique désactivée." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Rien à traduire." - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Appuyez cinq fois sur Droite pour supprimer tous les cheats." diff --git a/intl/msg_hash_hu.h b/intl/msg_hash_hu.h index 8ab6ead078..58637976d5 100644 --- a/intl/msg_hash_hu.h +++ b/intl/msg_hash_hu.h @@ -6930,31 +6930,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "A fordítási szolgáltatás célnyelve. \"Alapértelmezett\" az angol." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Az AI szolgáltatás lekérdezési késleltetése" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Az automatikus hívások közti minimális késleltetés. Lassítja a reakcióidőt, de növeli a CPU teljesítményt." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Az AI szolgáltatás szöveg helyzetének felülbírálata" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "A rátét pozíciójának felülbírálása, amikor a szolgáltatás Szöveges módban van." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Az AI szolgáltatás szövegének kitöltése (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "A szöveges rátét függőleges kitöltése, amikor a szolgáltatás Szöveges módban van. Nagyobb kitöltés a szöveget a képernyő közepe felé mozdítja." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10173,26 +10148,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Narrátor mód" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Szöveges mód" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Szöveg + narrátor" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Kép + narrátor" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Felfordítás" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Felül" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Előzmények és kedvencek" @@ -12724,22 +12680,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Mag telepítése sikertelen" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Az AI szolgáltatás nem támogatja ezt a videomeghajtót." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Automatikus fordítás engedélyezve." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Automatikus fordítás letiltva." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Nincs mit lefordítani." - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Nyomja meg a jobbra gombot ötször minden csalás törléséhez." diff --git a/intl/msg_hash_it.h b/intl/msg_hash_it.h index 094bbcc004..d911efe9e8 100644 --- a/intl/msg_hash_it.h +++ b/intl/msg_hash_it.h @@ -6878,31 +6878,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "La lingua in cui il servizio si tradurrà. 'Predefinito' è l'inglese." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Ritardo Auto-Polling Servizio IA" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Ritardo minimo in ms tra le chiamate automatiche. Abbassa la reattività ma aumenta le prestazioni della CPU." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Sostituzione Posizione Testo Servizio IA" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Sovrascrivi la posizione dell'overlay, quando il servizio è in modalità Testo." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Riempimento testo Servizio IA (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Riempimento verticale da applicare al testo sovrapposto, quando il servizio è in modalità Testo. Più riempimento spingerà il testo verso il centro dello schermo." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10101,26 +10076,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Modalità Narratore" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Modalità Testo" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Testo + Narratore" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Immagine + Narratore" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Inferiore" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Alto" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Cronologia & Preferiti" @@ -12808,22 +12764,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Installazione core fallita" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Il driver video non è supportato per il servizio IA." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Traduzione automatica abilitata." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Traduzione automatica disabilitata." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Niente da tradurre." - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Premi a destra cinque volte per eliminare tutti i trucchi." diff --git a/intl/msg_hash_ja.h b/intl/msg_hash_ja.h index f2c661cb9c..561161eed2 100644 --- a/intl/msg_hash_ja.h +++ b/intl/msg_hash_ja.h @@ -6966,30 +6966,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "サービスが翻訳した後の言語です。[デフォルト] は英語です。" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "AI サービスの自動ポーリング遅延" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "自動コール間の最小遅延ミリ秒です。反応性が低下しますが、CPU パフォーマンスが向上します。" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "AI サービスのテキスト位置優先" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "サービスがテキストモードの場合、オーバーレイの位置を上書きします。" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "AI サービステキスト余白 (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "サービスがテキストモードのとき、テキストオーバーレイに適用される垂直余白です。より多くの余白は、テキストを画面の中心に向かってプッシュします。" - ) /* Settings > Accessibility */ @@ -10229,26 +10205,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "ナレーターモード" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "テキストモード" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "テキスト + ナレーター" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "画像 + ナレーター" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "下" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "先頭" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "履歴 & お気に入り" @@ -12948,22 +12905,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "コアのインストールに失敗しました" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "ビデオドライバが AI サービスに対応していません。" - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "自動翻訳を有効にしました。" - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "自動翻訳を無効にしました。" - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "翻訳するものはありません。" - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "すべてのチートを削除するには右を 5 回押してください。" diff --git a/intl/msg_hash_ko.h b/intl/msg_hash_ko.h index 5ef3d5a668..ee4513379c 100644 --- a/intl/msg_hash_ko.h +++ b/intl/msg_hash_ko.h @@ -7010,31 +7010,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "서비스가 번역하여 표시할 언어입니다. '기본'을 선택하면 영어로 번역합니다." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "AI 서비스 자동 폴링 딜레이" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "자동 호출 사이의 최소 딜레이(ms)입니다. 반응성이 낮아지지만 CPU 성능을 높일 수 있습니다." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "AI 서비스 텍스트 위치 재정의" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "AI 서비스를 텍스트 모드로 사용할 때, 오버레이의 위치를 재정의합니다." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "AI 서비스 텍스트 패딩 (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "AI 서비스를 텍스트 모드로 사용할 때, 텍스트 오버레이에 적용될 수직 패딩입니다. 패딩을 늘리면 텍스트가 화면 가운데로 밀려납니다." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10277,26 +10252,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "나레이터 모드" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "텍스트 모드" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "텍스트 + 나레이터" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "이미지 + 나레이터" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "아래" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "상단" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "최근 실행 & 즐겨찾기" @@ -13100,22 +13056,6 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "코어 설치 실패" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "이 비디오 드라이버에서는 AI 서비스를 사용할 수 없습니다." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "자동 번역이 활성화되었습니다." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "자동 번역이 비활성화되었습니다." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "번역할 것이 없습니다." - ) MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "모든 치트를 삭제하려면 오른쪽을 다섯 번 입력하십시오." diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index d0e578d086..4423e17f50 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -6278,18 +6278,6 @@ MSG_HASH( MENU_ENUM_LABEL_AI_SERVICE_SOURCE_LANG, "ai_service_source_lang" ) -MSG_HASH( - MENU_ENUM_LABEL_AI_SERVICE_POLL_DELAY, - "ai_service_poll_delay" - ) -MSG_HASH( - MENU_ENUM_LABEL_AI_SERVICE_TEXT_POSITION, - "ai_service_text_position" - ) -MSG_HASH( - MENU_ENUM_LABEL_AI_SERVICE_TEXT_PADDING, - "ai_service_text_padding" - ) MSG_HASH( MENU_ENUM_LABEL_SETTINGS_SHOW_DRIVERS, "settings_show_drivers" diff --git a/intl/msg_hash_pl.h b/intl/msg_hash_pl.h index d67030089b..1eece6c0cf 100644 --- a/intl/msg_hash_pl.h +++ b/intl/msg_hash_pl.h @@ -6298,23 +6298,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "Język, który serwis przetłumaczy. \"Domyślnie\" to język angielski." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Opóźnienie automatycznego sprawdzania usług AI" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Minimalne opóźnienie w ms między połączeniami automatycznymi. Obniża reaktywność, ale zwiększa wydajność procesora." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Zastępowanie pozycji tekstowej usługi AI" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Zastąp pozycję nakładki, gdy usługa jest w trybie tekstowym." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -9085,10 +9068,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Tryb Narratora" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Dolny" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Historia i Ulubione" diff --git a/intl/msg_hash_pt_br.h b/intl/msg_hash_pt_br.h index b6bb13e53d..475726921f 100644 --- a/intl/msg_hash_pt_br.h +++ b/intl/msg_hash_pt_br.h @@ -8541,14 +8541,6 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Modo narrador" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Embaixo" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Topo" - ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Histórico e favoritos" @@ -11128,18 +11120,6 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Falha na instalação do núcleo" ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Tradução automática ativada." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Tradução automática desativada." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Nada para traduzir." - ) MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Pressione direita cinco vezes para excluir todas as trapaças." diff --git a/intl/msg_hash_ru.h b/intl/msg_hash_ru.h index c17fc7696b..5320411304 100644 --- a/intl/msg_hash_ru.h +++ b/intl/msg_hash_ru.h @@ -6990,31 +6990,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "Язык, на который будет осуществляться перевод. По умолчанию используется английский." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Задержка автоопроса AI-сервиса" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Минимальная задержка (в мс) между автом. вызовами. Снижает быстродействие, но повышает производительность CPU." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Переопределение положения текста" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Изменяет положение оверлея при использовании AI-сервиса в режиме текста." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Смещение текста (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Отступ по вертикали для наложения при использовании AI-сервиса в режиме текста. При увеличении отступа текст будет смещаться к центру экрана." - ) - /* Settings > Accessibility */ MSG_HASH( @@ -10249,26 +10224,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Режим диктора" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Режим текста" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Текст + диктор" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Изображение + диктор" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Внизу" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Вверху" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "История и избранное" @@ -13024,22 +12980,6 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Не удалось установить ядро" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Видеодрайвер не поддерживается AI-сервисом." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Автоматический перевод включен." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Автоматический перевод выключен." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Нет данных для перевода." - ) MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Нажмите вправо пять раз для удаления всех чит-кодов." diff --git a/intl/msg_hash_sk.h b/intl/msg_hash_sk.h index 60b3afc89d..7e1de0336d 100644 --- a/intl/msg_hash_sk.h +++ b/intl/msg_hash_sk.h @@ -4949,14 +4949,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_SPEECH_MODE, "Režim reči" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Dole" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Hore" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "História & Obľúbené" diff --git a/intl/msg_hash_sv.h b/intl/msg_hash_sv.h index 8f96803104..124c6e9537 100644 --- a/intl/msg_hash_sv.h +++ b/intl/msg_hash_sv.h @@ -7017,14 +7017,7 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_MODE, "Bildläge" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Underkant" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Toppen" - ) + MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Historik & Favoriter" @@ -8820,18 +8813,7 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Kärninstallation misslyckad" ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Automatisk översättning aktiverad." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Automatisk översättning inaktiverad." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Inget att översätta" - ) + MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Tryck höger 5 gånger för att radera alla fusk." diff --git a/intl/msg_hash_tr.h b/intl/msg_hash_tr.h index f7c215db37..c7f434ba32 100644 --- a/intl/msg_hash_tr.h +++ b/intl/msg_hash_tr.h @@ -6970,30 +6970,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "Hizmetin çevireceği dil. \"Varsayılan\" İngilizcedir." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "Çeviri Hizmeti Otomatik Seçim Gecikmesi" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Otomatik çağrılar arasında ms cinsinden asgari gecikme. Tepkimeyi azaltır ancak CPU performansını artırır." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "Çeviri Hizmeti Metin Konumunu Özelleştir" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Hizmet Metin modundayken kaplamanın konumunu özelleştirin." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "Çeviri Hizmeti Metin Dolgusu (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Hizmet Metin modundayken metin kaplamasına uygulanacak dikey dolgu. Daha fazla dolgu, metni ekranın ortasına doğru itecektir." - ) /* Settings > Accessibility */ @@ -10229,26 +10205,6 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Ekran Okuyucusu Kipi" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Metin Modu" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Metin + Anlatıcı" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Resim + Anlatıcı" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Alt" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Üst" - ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Geçmiş & Sık Kullanılanlar" @@ -13088,22 +13044,6 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Çekirdek kurulumu başarısız" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Video sürücüsü Çeviri Hizmeti için desteklenmiyor." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Otomatik çeviri etkin." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Otomatik çeviri devre dışı." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Çevirecek birşey yok." - ) MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Tüm hileleri silmek için beş kez sağa basın." diff --git a/intl/msg_hash_uk.h b/intl/msg_hash_uk.h index e1d9e0c339..4932095d1d 100644 --- a/intl/msg_hash_uk.h +++ b/intl/msg_hash_uk.h @@ -5193,10 +5193,6 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Режим Оповідача" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Верх" - ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "Історія та Обране" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index cff050a5ac..6a1fd67698 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -7020,7 +7020,7 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_MODE, - "Show translation as an image overlay (Image Mode), as direct audio (Speech), text-to-speech (Narrator), or text overlay (Text)." + "Show translation as a text overlay (Image Mode), play as Text-To-Speech (Speech), or use a system narrator like NVDA (Narrator)." ) MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_URL, @@ -7062,30 +7062,6 @@ MSG_HASH( MENU_ENUM_SUBLABEL_AI_SERVICE_TARGET_LANG, "The language the service will translate to. 'Default' is English." ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - "AI Service Auto-Polling Delay" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY, - "Minimum delay in ms between automatic calls. Lowers reactivity but increases CPU performance." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - "AI Service Text Position Override" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION, - "Override for the position of the overlay, when the service is in Text mode." - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - "AI Service Text Padding (%)" - ) -MSG_HASH( - MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING, - "Vertical padding to apply to the text overlay, when the service is in Text mode. More padding will push the text towards the center of the screen." - ) /* Settings > Accessibility */ @@ -10693,26 +10669,6 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, "Narrator Mode" ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - "Text Mode" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - "Text + Narrator" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - "Image + Narrator" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - "Bottom" - ) -MSG_HASH( - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - "Top" - ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PLAYLIST_ENTRY_REMOVE_ENABLE_HIST_FAV, "History & Favorites" @@ -13776,22 +13732,6 @@ MSG_HASH( /* FIXME Should be MSG_ */ MENU_ENUM_LABEL_VALUE_SIDELOAD_CORE_ERROR, "Core installation failed" ) -MSG_HASH( - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - "Video driver not supported for AI Service." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_ENABLED, - "Automatic translation enabled." - ) -MSG_HASH( - MSG_AI_AUTO_MODE_DISABLED, - "Automatic translation disabled." - ) -MSG_HASH( - MSG_AI_NOTHING_TO_TRANSLATE, - "Nothing to translate." - ) MSG_HASH( MSG_CHEAT_DELETE_ALL_INSTRUCTIONS, "Press right five times to delete all cheats." diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index a2653a472f..5a5e212242 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -269,9 +269,6 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_target_lang, MENU_ENUM_S DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_source_lang, MENU_ENUM_SUBLABEL_AI_SERVICE_SOURCE_LANG) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_url, MENU_ENUM_SUBLABEL_AI_SERVICE_URL) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_enable, MENU_ENUM_SUBLABEL_AI_SERVICE_ENABLE) -DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_poll_delay, MENU_ENUM_SUBLABEL_AI_SERVICE_POLL_DELAY) -DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_text_position, MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_POSITION) -DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_text_padding, MENU_ENUM_SUBLABEL_AI_SERVICE_TEXT_PADDING) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_power_management_settings_list, MENU_ENUM_SUBLABEL_POWER_MANAGEMENT_SETTINGS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_privacy_settings_list, MENU_ENUM_SUBLABEL_PRIVACY_SETTINGS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_midi_settings_list, MENU_ENUM_SUBLABEL_MIDI_SETTINGS) @@ -5131,15 +5128,6 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_AI_SERVICE_ENABLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_ai_service_enable); break; - case MENU_ENUM_LABEL_AI_SERVICE_POLL_DELAY: - BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_ai_service_poll_delay); - break; - case MENU_ENUM_LABEL_AI_SERVICE_TEXT_POSITION: - BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_ai_service_text_position); - break; - case MENU_ENUM_LABEL_AI_SERVICE_TEXT_PADDING: - BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_ai_service_text_padding); - break; case MENU_ENUM_LABEL_AI_SERVICE_SETTINGS: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_ai_service_settings_list); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 52a2a1905e..8432497435 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -7933,11 +7933,8 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_AI_SERVICE_MODE, PARSE_ONLY_UINT, false}, {MENU_ENUM_LABEL_AI_SERVICE_URL, PARSE_ONLY_STRING, false}, {MENU_ENUM_LABEL_AI_SERVICE_PAUSE, PARSE_ONLY_BOOL, false}, - {MENU_ENUM_LABEL_AI_SERVICE_POLL_DELAY, PARSE_ONLY_UINT, false}, {MENU_ENUM_LABEL_AI_SERVICE_SOURCE_LANG, PARSE_ONLY_UINT, false}, {MENU_ENUM_LABEL_AI_SERVICE_TARGET_LANG, PARSE_ONLY_UINT, false}, - {MENU_ENUM_LABEL_AI_SERVICE_TEXT_POSITION, PARSE_ONLY_UINT, false}, - {MENU_ENUM_LABEL_AI_SERVICE_TEXT_PADDING, PARSE_ONLY_UINT, false}, }; for (i = 0; i < ARRAY_SIZE(build_list); i++) @@ -7947,11 +7944,8 @@ unsigned menu_displaylist_build_list( case MENU_ENUM_LABEL_AI_SERVICE_MODE: case MENU_ENUM_LABEL_AI_SERVICE_URL: case MENU_ENUM_LABEL_AI_SERVICE_PAUSE: - case MENU_ENUM_LABEL_AI_SERVICE_POLL_DELAY: case MENU_ENUM_LABEL_AI_SERVICE_SOURCE_LANG: case MENU_ENUM_LABEL_AI_SERVICE_TARGET_LANG: - case MENU_ENUM_LABEL_AI_SERVICE_TEXT_POSITION: - case MENU_ENUM_LABEL_AI_SERVICE_TEXT_PADDING: if (ai_service_enable) build_list[i].checked = true; break; diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 2828d8970f..9e94ca5c8f 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -7007,7 +7007,7 @@ static int generic_menu_iterate( && is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, menu->menu_state_msg, 10); @@ -7139,18 +7139,18 @@ static int generic_menu_iterate( menu_st, current_sublabel, sizeof(current_sublabel)); if (string_is_equal(current_sublabel, "")) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, menu->menu_state_msg, 10); else - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, current_sublabel, 10); } else - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, menu->menu_state_msg, 10); @@ -7312,7 +7312,7 @@ static int generic_menu_iterate( && is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, "Closed dialog.", 10); @@ -7750,7 +7750,7 @@ int generic_menu_entry_action( } if (!string_is_empty(speak_string)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, speak_string, 10); @@ -7883,7 +7883,7 @@ bool menu_input_dialog_start_search(void) if (is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH), 10); @@ -7937,7 +7937,7 @@ bool menu_input_dialog_start(menu_input_ctx_line_t *line) if (is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, "Keyboard input:", 10); diff --git a/menu/menu_setting.c b/menu/menu_setting.c index f5ff2f1f88..60ebc17794 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -3028,42 +3028,6 @@ static void setting_get_string_representation_uint_ai_service_mode( case 2: enum_idx = MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE; break; - case 3: - enum_idx = MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE; - break; - case 4: - enum_idx = MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE; - break; - case 5: - enum_idx = MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE; - break; - default: - break; - } - - if (enum_idx != 0) - strlcpy(s, msg_hash_to_str(enum_idx), len); -} - -static void setting_get_string_representation_uint_ai_service_text_position( - rarch_setting_t *setting, - char *s, size_t len) -{ - enum msg_hash_enums enum_idx = MSG_UNKNOWN; - if (!setting) - return; - - switch (*setting->value.target.unsigned_integer) - { - case 0: - enum_idx = MENU_ENUM_LABEL_VALUE_NONE; - break; - case 1: - enum_idx = MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM; - break; - case 2: - enum_idx = MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP; - break; default: break; } @@ -20040,7 +20004,7 @@ static bool setting_append_list( (*list)[list_info->index - 1].get_string_representation = &setting_get_string_representation_uint_ai_service_mode; (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; - menu_settings_list_current_add_range(list, list_info, 0, 5, 1, true, true); + menu_settings_list_current_add_range(list, list_info, 0, 2, 1, true, true); CONFIG_STRING( list, list_info, @@ -20123,49 +20087,6 @@ static bool setting_append_list( (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; menu_settings_list_current_add_range(list, list_info, TRANSLATION_LANG_DONT_CARE, (TRANSLATION_LANG_LAST-1), 1, true, true); - CONFIG_UINT( - list, list_info, - &settings->uints.ai_service_poll_delay, - MENU_ENUM_LABEL_AI_SERVICE_POLL_DELAY, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_POLL_DELAY, - DEFAULT_AI_SERVICE_POLL_DELAY, - &group_info, - &subgroup_info, - parent_group, - general_write_handler, - general_read_handler); - (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; - menu_settings_list_current_add_range(list, list_info, 0, MAXIMUM_AI_SERVICE_POLL_DELAY, 50, true, true); - - CONFIG_UINT( - list, list_info, - &settings->uints.ai_service_text_position, - MENU_ENUM_LABEL_AI_SERVICE_TEXT_POSITION, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION, - DEFAULT_AI_SERVICE_TEXT_POSITION, - &group_info, - &subgroup_info, - parent_group, - general_write_handler, - general_read_handler); - (*list)[list_info->index - 1].get_string_representation = - &setting_get_string_representation_uint_ai_service_text_position; - (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; - menu_settings_list_current_add_range(list, list_info, 0, 2, 1, true, true); - - CONFIG_UINT( - list, list_info, - &settings->uints.ai_service_text_padding, - MENU_ENUM_LABEL_AI_SERVICE_TEXT_PADDING, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_PADDING, - DEFAULT_AI_SERVICE_TEXT_PADDING, - &group_info, - &subgroup_info, - parent_group, - general_write_handler, - general_read_handler); - (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; - menu_settings_list_current_add_range(list, list_info, 0, 20, 1, true, true); END_SUB_GROUP(list, list_info, parent_group); END_GROUP(list, list_info, parent_group); diff --git a/msg_hash.h b/msg_hash.h index 3690c9fc63..490b5960a9 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -572,10 +572,6 @@ enum msg_hash_enums MSG_FAILED_TO_ENTER_GAMEMODE_LINUX, MSG_VRR_RUNLOOP_ENABLED, MSG_VRR_RUNLOOP_DISABLED, - MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED, - MSG_AI_AUTO_MODE_ENABLED, - MSG_AI_AUTO_MODE_DISABLED, - MSG_AI_NOTHING_TO_TRANSLATE, MSG_VIDEO_REFRESH_RATE_CHANGED, MSG_IOS_TOUCH_MOUSE_ENABLED, @@ -2921,9 +2917,6 @@ enum msg_hash_enums MENU_LABEL(AI_SERVICE_URL), MENU_LABEL(AI_SERVICE_ENABLE), MENU_LABEL(AI_SERVICE_PAUSE), - MENU_LABEL(AI_SERVICE_POLL_DELAY), - MENU_LABEL(AI_SERVICE_TEXT_POSITION), - MENU_LABEL(AI_SERVICE_TEXT_PADDING), MSG_ACCESSIBILITY_STARTUP, MSG_AI_SERVICE_STOPPED, @@ -3630,12 +3623,6 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_MODE, MENU_ENUM_LABEL_VALUE_AI_SERVICE_SPEECH_MODE, MENU_ENUM_LABEL_VALUE_AI_SERVICE_NARRATOR_MODE, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_MODE, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_NARRATOR_MODE, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_IMAGE_NARRATOR_MODE, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_TOP, - MENU_ENUM_LABEL_VALUE_AI_SERVICE_TEXT_POSITION_BOTTOM, - MENU_ENUM_LABEL_VALUE_NONE, MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE, diff --git a/retroarch.c b/retroarch.c index 6a91033d61..2c7ddf0833 100644 --- a/retroarch.c +++ b/retroarch.c @@ -3084,9 +3084,6 @@ bool command_event(enum event_command cmd, void *data) #if defined(HAVE_ACCESSIBILITY) || defined(HAVE_TRANSLATE) access_state_t *access_st = access_state_get_ptr(); #endif -#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS) - dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr(); -#endif #ifdef HAVE_MENU struct menu_state *menu_st = menu_state_get_ptr(); #endif @@ -3103,12 +3100,12 @@ bool command_event(enum event_command cmd, void *data) #ifdef HAVE_OVERLAY input_overlay_unload(); #endif -#ifdef HAVE_TRANSLATE - translation_release(true); -#ifdef HAVE_GFX_WIDGETS - if (p_dispwidget->ai_service_overlay_state != 0) +#if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS) + /* Because the overlay is a display widget, + * it's going to be written + * over the menu, so we unset it here. */ + if (dispwidget_get_ptr()->ai_service_overlay_state != 0) gfx_widgets_ai_service_overlay_unload(); -#endif #endif break; case CMD_EVENT_OVERLAY_INIT: @@ -3177,16 +3174,11 @@ bool command_event(enum event_command cmd, void *data) if (is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg_hash_to_str(MSG_UNPAUSED), 10); #endif -#ifdef HAVE_GFX_WIDGETS - if (p_dispwidget->ai_service_overlay_state != 0) - gfx_widgets_ai_service_overlay_unload(); -#endif - translation_release(true); command_event(CMD_EVENT_UNPAUSE, NULL); } else /* Pause on call */ @@ -3205,24 +3197,18 @@ bool command_event(enum event_command cmd, void *data) * Also, this mode is required for "auto" translation * packages, since you don't want to pause for that. */ - if (access_st->ai_service_auto != 0) + if (access_st->ai_service_auto == 2) { /* Auto mode was turned on, but we pressed the * toggle button, so turn it off now. */ - translation_release(true); -#ifdef HAVE_GFX_WIDGETS - if (p_dispwidget->ai_service_overlay_state != 0) - gfx_widgets_ai_service_overlay_unload(); + access_st->ai_service_auto = 0; +#ifdef HAVE_MENU_WIDGETS + gfx_widgets_ai_service_overlay_unload(); #endif } - else + else { -#ifdef HAVE_GFX_WIDGETS - if (p_dispwidget->ai_service_overlay_state != 0) - gfx_widgets_ai_service_overlay_unload(); - else -#endif - command_event(CMD_EVENT_AI_SERVICE_CALL, NULL); + command_event(CMD_EVENT_AI_SERVICE_CALL, NULL); } } #endif @@ -4640,12 +4626,12 @@ bool command_event(enum event_command cmd, void *data) access_st->enabled)) { if (paused) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg_hash_to_str(MSG_PAUSED), 10); else - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg_hash_to_str(MSG_UNPAUSED), 10); @@ -5402,7 +5388,7 @@ bool command_event(enum event_command cmd, void *data) if (is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg_hash_to_str(MSG_AI_SERVICE_STOPPED), @@ -5417,7 +5403,7 @@ bool command_event(enum event_command cmd, void *data) access_st->enabled) && (ai_service_mode == 2) && is_narrator_running(accessibility_enable)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg_hash_to_str(MSG_AI_SERVICE_STOPPED), @@ -5428,7 +5414,9 @@ bool command_event(enum event_command cmd, void *data) bool paused = (runloop_st->flags & RUNLOOP_FLAG_PAUSED) ? true : false; if (data) paused = *((bool*)data); - + if ( (access_st->ai_service_auto == 0) + && !settings->bools.ai_service_pause) + access_st->ai_service_auto = 1; run_translation_service(settings, paused); } #endif @@ -7476,7 +7464,7 @@ bool retroarch_main_init(int argc, char *argv[]) if (is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg_hash_to_str(MSG_ACCESSIBILITY_STARTUP), @@ -8193,9 +8181,6 @@ bool retroarch_main_quit(void) video_driver_state_t*video_st = video_state_get_ptr(); settings_t *settings = config_get_ptr(); bool config_save_on_exit = settings->bools.config_save_on_exit; -#ifdef HAVE_ACCESSIBILITY - access_state_t *access_st = access_state_get_ptr(); -#endif /* Restore video driver before saving */ video_driver_restore_cached(settings); @@ -8288,20 +8273,6 @@ bool retroarch_main_quit(void) retroarch_menu_running_finished(true); #endif -#ifdef HAVE_TRANSLATE - translation_release(false); -#endif - -#ifdef HAVE_ACCESSIBILITY -#ifdef HAVE_THREADS - if (access_st->image_lock) - { - slock_free(access_st->image_lock); - access_st->image_lock = NULL; - } -#endif -#endif - return true; } @@ -8426,7 +8397,7 @@ void retroarch_favorites_deinit(void) } #ifdef HAVE_ACCESSIBILITY -bool navigation_say( +bool accessibility_speak_priority( bool accessibility_enable, unsigned accessibility_narrator_speech_speed, const char* speak_text, int priority) @@ -8436,48 +8407,29 @@ bool navigation_say( accessibility_enable, access_st->enabled)) { - const char *voice = get_user_language_iso639_1(false); - bool native_narrator = accessibility_speak_priority(accessibility_narrator_speech_speed, - speak_text, priority, voice); - if (!native_narrator) - { - /* - * The following method is a fallback for other platforms to use the - * AI Service url to do the TTS. However, since the playback is done - * via the audio mixer, which only processes the audio while the - * core is running, this playback method won't work. When the audio - * mixer can handle playing streams while the core is paused, then - * we can use this. - */ + frontend_ctx_driver_t *frontend = + frontend_state_get_ptr()->current_frontend_ctx; + + RARCH_LOG("Spoke: %s\n", speak_text); + + if (frontend && frontend->accessibility_speak) + return frontend->accessibility_speak(accessibility_narrator_speech_speed, speak_text, + priority); + /* The following method is a fallback for other platforms to use the + AI Service url to do the TTS. However, since the playback is done + via the audio mixer, which only processes the audio while the + core is running, this playback method won't work. When the audio + mixer can handle playing streams while the core is paused, then + we can use this. */ #if 0 #if defined(HAVE_NETWORKING) return accessibility_speak_ai_service(speak_text, voice, priority); #endif #endif - } } return true; } -bool accessibility_speak_priority( - unsigned accessibility_narrator_speech_speed, - const char *speak_text, - int priority, - const char *voice) -{ - frontend_ctx_driver_t *frontend = - frontend_state_get_ptr()->current_frontend_ctx; - - RARCH_LOG("Spoke: %s\n", speak_text); - - if (frontend && frontend->accessibility_speak) - return frontend->accessibility_speak(accessibility_narrator_speech_speed, - speak_text, priority, voice); - - RARCH_LOG("Platform not supported for accessibility.\n"); - - return false; -} #endif diff --git a/runloop.c b/runloop.c index 3f50f17f66..f04c8ced66 100644 --- a/runloop.c +++ b/runloop.c @@ -5309,7 +5309,7 @@ void runloop_msg_queue_push(const char *msg, if (is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*) msg, 0); @@ -7334,7 +7334,7 @@ void runloop_task_msg_queue_push( if (is_accessibility_enabled( accessibility_enable, access_st->enabled)) - navigation_say( + accessibility_speak_priority( accessibility_enable, accessibility_narrator_speech_speed, (char*)msg, 0); diff --git a/tasks/task_translation.c b/tasks/task_translation.c index b714012ebe..de1bf62f87 100644 --- a/tasks/task_translation.c +++ b/tasks/task_translation.c @@ -29,11 +29,9 @@ #include #include #include -#include #include #include #include -#include #include "../translation_defines.h" #ifdef HAVE_GFX_WIDGETS @@ -49,71 +47,588 @@ #include "../paths.h" #include "../runloop.h" #include "../verbosity.h" -#include "../msg_hash.h" #include "tasks_internal.h" -static const char* ACCESS_INPUT_LABELS[] = +static void task_auto_translate_handler(retro_task_t *task) { - "b", "y", "select", "start", "up", "down", "left", "right", - "a", "x", "l", "r", "l2", "r2", "l3", "r3" -}; - -static const char* ACCESS_RESPONSE_KEYS[] = -{ - "image", "sound", "text", "error", "auto", "press", "text_position" -}; - -typedef struct -{ - uint8_t *data; - unsigned size; - unsigned width; - unsigned height; - - unsigned content_x; - unsigned content_y; - unsigned content_width; - unsigned content_height; - unsigned viewport_width; - unsigned viewport_height; -} access_frame_t; - -typedef struct -{ - char *data; - int length; - char format[4]; -} access_base64_t; - -typedef struct -{ - char *inputs; - bool paused; -} access_request_t; - -typedef struct -{ - char *image; - int image_size; -#ifdef HAVE_AUDIOMIXER - void *sound; - int sound_size; + int *mode_ptr = (int*)task->user_data; + uint32_t runloop_flags = runloop_get_flags(); + access_state_t *access_st = access_state_get_ptr(); +#ifdef HAVE_ACCESSIBILITY + settings_t *settings = config_get_ptr(); #endif - char *error; - char *text; - char *recall; - char *input; - int text_position; -} access_response_t; -/* UTILITIES ---------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ + if (task_get_cancelled(task)) + goto task_finished; -/** - * Returns the string representation of the translation language enum value. - */ -static const char* ai_service_get_str(enum translation_lang id) + switch (*mode_ptr) + { + case 1: /* Speech Mode */ +#ifdef HAVE_AUDIOMIXER + if (!audio_driver_is_ai_service_speech_running()) + goto task_finished; +#endif + break; + case 2: /* Narrator Mode */ +#ifdef HAVE_ACCESSIBILITY + if (!is_narrator_running( + settings->bools.accessibility_enable)) + goto task_finished; +#endif + break; + default: + break; + } + + return; + +task_finished: + if (access_st->ai_service_auto == 1) + access_st->ai_service_auto = 2; + + task_set_finished(task, true); + + if (*mode_ptr == 1 || *mode_ptr == 2) + { + bool was_paused = (runloop_flags & RUNLOOP_FLAG_PAUSED) ? true : false; + command_event(CMD_EVENT_AI_SERVICE_CALL, &was_paused); + } + if (task->user_data) + free(task->user_data); +} + +static void call_auto_translate_task( + settings_t *settings, + bool *was_paused) +{ + int ai_service_mode = settings->uints.ai_service_mode; + access_state_t *access_st = access_state_get_ptr(); + + /*Image Mode*/ + if (ai_service_mode == 0) + { + if (access_st->ai_service_auto == 1) + access_st->ai_service_auto = 2; + + command_event(CMD_EVENT_AI_SERVICE_CALL, was_paused); + } + else /* Speech or Narrator Mode */ + { + int* mode = NULL; + retro_task_t *t = task_init(); + if (!t) + return; + + mode = (int*)malloc(sizeof(int)); + *mode = ai_service_mode; + + t->handler = task_auto_translate_handler; + t->user_data = mode; + t->mute = true; + task_queue_push(t); + } +} + +static void handle_translation_cb( + retro_task_t *task, void *task_data, + void *user_data, const char *error) +{ + uint8_t* raw_output_data = NULL; + char* raw_image_file_data = NULL; + struct scaler_ctx* scaler = NULL; + http_transfer_data_t *data = (http_transfer_data_t*)task_data; + int new_image_size = 0; +#ifdef HAVE_AUDIOMIXER + int new_sound_size = 0; +#endif + void* raw_image_data = NULL; + void* raw_image_data_alpha = NULL; + void* raw_sound_data = NULL; + rjson_t *json = NULL; + int json_current_key = 0; + char* err_str = NULL; + char* txt_str = NULL; + char* auto_str = NULL; + char* key_str = NULL; + settings_t* settings = config_get_ptr(); + uint32_t runloop_flags = runloop_get_flags(); +#ifdef HAVE_ACCESSIBILITY + input_driver_state_t *input_st = input_state_get_ptr(); +#endif + video_driver_state_t + *video_st = video_state_get_ptr(); + const enum retro_pixel_format + video_driver_pix_fmt = video_st->pix_fmt; + access_state_t *access_st = access_state_get_ptr(); +#ifdef HAVE_GFX_WIDGETS + bool gfx_widgets_paused = (video_st->flags & + VIDEO_FLAG_WIDGETS_PAUSED) ? true : false; + dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr(); +#endif +#ifdef HAVE_ACCESSIBILITY + bool accessibility_enable = settings->bools.accessibility_enable; + unsigned accessibility_narrator_speech_speed = settings->uints.accessibility_narrator_speech_speed; +#ifdef HAVE_GFX_WIDGETS + /* When auto mode is on, we turn off the overlay + * once we have the result for the next call.*/ + if (p_dispwidget->ai_service_overlay_state != 0 + && access_st->ai_service_auto == 2) + gfx_widgets_ai_service_overlay_unload(); +#endif +#endif + +#ifdef DEBUG + if (access_st->ai_service_auto != 2) + RARCH_LOG("RESULT FROM AI SERVICE...\n"); +#endif + + if (!data || error || !data->data) + goto finish; + + if (!(json = rjson_open_buffer(data->data, data->len))) + goto finish; + + /* Parse JSON body for the image and sound data */ + for (;;) + { + static const char* keys[] = { "image", "sound", "text", "error", "auto", "press" }; + + const char *str = NULL; + size_t str_len = 0; + enum rjson_type json_type = rjson_next(json); + + if (json_type == RJSON_DONE || json_type == RJSON_ERROR) + break; + if (json_type != RJSON_STRING) + continue; + if (rjson_get_context_type(json) != RJSON_OBJECT) + continue; + str = rjson_get_string(json, &str_len); + + if ((rjson_get_context_count(json) & 1) == 1) + { + int i; + json_current_key = -1; + + for (i = 0; i < (int)ARRAY_SIZE(keys); i++) + { + if (string_is_equal(str, keys[i])) + { + json_current_key = i; + break; + } + } + } + else + { + switch (json_current_key) + { + case 0: /* image */ + raw_image_file_data = (char*)unbase64(str, + (int)str_len, &new_image_size); + break; +#ifdef HAVE_AUDIOMIXER + case 1: /* sound */ + raw_sound_data = (void*)unbase64(str, + (int)str_len, &new_sound_size); + break; +#endif + case 2: /* text */ + txt_str = strdup(str); + break; + case 3: /* error */ + err_str = strdup(str); + break; + case 4: /* auto */ + auto_str = strdup(str); + break; + case 5: /* press */ + key_str = strdup(str); + break; + } + json_current_key = -1; + } + } + + if (string_is_equal(err_str, "No text found.")) + { +#ifdef DEBUG + RARCH_LOG("No text found...\n"); +#endif + if (txt_str) + { + free(txt_str); + txt_str = NULL; + } + + txt_str = (char*)malloc(15); + strlcpy(txt_str, err_str, 15); +#ifdef HAVE_GFX_WIDGETS + if (gfx_widgets_paused) + { + /* In this case we have to unpause and then repause for a frame */ + p_dispwidget->ai_service_overlay_state = 2; + command_event(CMD_EVENT_UNPAUSE, NULL); + } +#endif + } + + if ( !raw_image_file_data + && !raw_sound_data + && !txt_str + && !key_str + && (access_st->ai_service_auto != 2)) + { + error = "Invalid JSON body."; + goto finish; + } + + if (raw_image_file_data) + { + unsigned image_width, image_height; + /* Get the video frame dimensions reference */ + const void *dummy_data = video_st->frame_cache_data; + unsigned width = video_st->frame_cache_width; + unsigned height = video_st->frame_cache_height; + + /* try two different modes for text display * + * In the first mode, we use display widget overlays, but they require + * the video poke interface to be able to load image buffers. + * + * The other method is to draw to the video buffer directly, which needs + * a software core to be running. */ +#ifdef HAVE_GFX_WIDGETS + if ( video_st->poke + && video_st->poke->load_texture + && video_st->poke->unload_texture) + { + enum image_type_enum image_type; + /* Write to overlay */ + if ( raw_image_file_data[0] == 'B' + && raw_image_file_data[1] == 'M') + image_type = IMAGE_TYPE_BMP; + else if ( raw_image_file_data[1] == 'P' + && raw_image_file_data[2] == 'N' + && raw_image_file_data[3] == 'G') + image_type = IMAGE_TYPE_PNG; + else + { + RARCH_LOG("Invalid image type returned from server.\n"); + goto finish; + } + + if (!gfx_widgets_ai_service_overlay_load( + raw_image_file_data, (unsigned)new_image_size, + image_type)) + { + RARCH_LOG("Video driver not supported for AI Service."); + runloop_msg_queue_push( + /* msg_hash_to_str(MSG_VIDEO_DRIVER_NOT_SUPPORTED), */ + "Video driver not supported.", + 1, 180, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + else if (gfx_widgets_paused) + { + /* In this case we have to unpause and then repause for a frame */ + /* Unpausing state */ + p_dispwidget->ai_service_overlay_state = 2; + command_event(CMD_EVENT_UNPAUSE, NULL); + } + } + else +#endif + /* Can't use display widget overlays, so try writing to video buffer */ + { + size_t pitch; + /* Write to video buffer directly (software cores only) */ + + /* This is a BMP file coming back. */ + if ( raw_image_file_data[0] == 'B' + && raw_image_file_data[1] == 'M') + { + /* Get image data (24 bit), and convert to the emulated pixel format */ + image_width = + ((uint32_t) ((uint8_t)raw_image_file_data[21]) << 24) + + ((uint32_t) ((uint8_t)raw_image_file_data[20]) << 16) + + ((uint32_t) ((uint8_t)raw_image_file_data[19]) << 8) + + ((uint32_t) ((uint8_t)raw_image_file_data[18]) << 0); + + image_height = + ((uint32_t) ((uint8_t)raw_image_file_data[25]) << 24) + + ((uint32_t) ((uint8_t)raw_image_file_data[24]) << 16) + + ((uint32_t) ((uint8_t)raw_image_file_data[23]) << 8) + + ((uint32_t) ((uint8_t)raw_image_file_data[22]) << 0); + raw_image_data = (void*)malloc(image_width * image_height * 3 * sizeof(uint8_t)); + memcpy(raw_image_data, + raw_image_file_data + 54 * sizeof(uint8_t), + image_width * image_height * 3 * sizeof(uint8_t)); + } + /* PNG coming back from the url */ + else if (raw_image_file_data[1] == 'P' + && raw_image_file_data[2] == 'N' + && raw_image_file_data[3] == 'G') + { + int retval = 0; + rpng_t *rpng = NULL; + image_width = + ((uint32_t) ((uint8_t)raw_image_file_data[16]) << 24)+ + ((uint32_t) ((uint8_t)raw_image_file_data[17]) << 16)+ + ((uint32_t) ((uint8_t)raw_image_file_data[18]) << 8)+ + ((uint32_t) ((uint8_t)raw_image_file_data[19]) << 0); + image_height = + ((uint32_t) ((uint8_t)raw_image_file_data[20]) << 24)+ + ((uint32_t) ((uint8_t)raw_image_file_data[21]) << 16)+ + ((uint32_t) ((uint8_t)raw_image_file_data[22]) << 8)+ + ((uint32_t) ((uint8_t)raw_image_file_data[23]) << 0); + + if (!(rpng = rpng_alloc())) + { + error = "Can't allocate memory."; + goto finish; + } + + rpng_set_buf_ptr(rpng, raw_image_file_data, (size_t)new_image_size); + rpng_start(rpng); + while (rpng_iterate_image(rpng)); + + do + { + retval = rpng_process_image(rpng, &raw_image_data_alpha, + (size_t)new_image_size, &image_width, &image_height); + } while (retval == IMAGE_PROCESS_NEXT); + + /* Returned output from the png processor is an upside down RGBA + * image, so we have to change that to RGB first. This should + * probably be replaced with a scaler call.*/ + { + unsigned ui; + int tw, th, tc; + int d = 0; + raw_image_data = (void*)malloc(image_width*image_height*3*sizeof(uint8_t)); + for (ui = 0; ui < image_width * image_height * 4; ui++) + { + if (ui % 4 != 3) + { + tc = d % 3; + th = image_height-d / (image_width * 3) - 1; + tw = (d % (image_width * 3)) / 3; + ((uint8_t*) raw_image_data)[tw * 3 + th * 3 * image_width + tc] = ((uint8_t *)raw_image_data_alpha)[ui]; + d += 1; + } + } + } + rpng_free(rpng); + } + else + { + RARCH_LOG("Output from URL not a valid file type, or is not supported.\n"); + goto finish; + } + + if (!(scaler = (struct scaler_ctx*)calloc(1, sizeof(struct scaler_ctx)))) + goto finish; + + if (dummy_data == RETRO_HW_FRAME_BUFFER_VALID) + { + /* + In this case, we used the viewport to grab the image + and translate it, and we have the translated image in + the raw_image_data buffer. + */ + RARCH_LOG("Hardware frame buffer core, but selected video driver isn't supported.\n"); + goto finish; + } + + /* The assigned pitch may not be reliable. The width of + the video frame can change during run-time, but the + pitch may not, so we just assign it as the width + times the byte depth. + */ + + if (video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888) + { + raw_output_data = (uint8_t*)malloc(width * height * 4 * sizeof(uint8_t)); + scaler->out_fmt = SCALER_FMT_ARGB8888; + pitch = width * 4; + scaler->out_stride = (int)pitch; + } + else + { + raw_output_data = (uint8_t*)malloc(width * height * 2 * sizeof(uint8_t)); + scaler->out_fmt = SCALER_FMT_RGB565; + pitch = width * 2; + scaler->out_stride = width; + } + + if (!raw_output_data) + goto finish; + + scaler->in_fmt = SCALER_FMT_BGR24; + scaler->in_width = image_width; + scaler->in_height = image_height; + scaler->out_width = width; + scaler->out_height = height; + scaler->scaler_type = SCALER_TYPE_POINT; + scaler_ctx_gen_filter(scaler); + scaler->in_stride = -1 * width * 3; + + scaler_ctx_scale_direct(scaler, raw_output_data, + (uint8_t*)raw_image_data + (image_height - 1) * width * 3); + video_driver_frame(raw_output_data, image_width, image_height, pitch); + } + } + +#ifdef HAVE_AUDIOMIXER + if (raw_sound_data) + { + audio_mixer_stream_params_t params; + + params.volume = 1.0f; + params.slot_selection_type = AUDIO_MIXER_SLOT_SELECTION_MANUAL; /* user->slot_selection_type; */ + params.slot_selection_idx = 10; + params.stream_type = AUDIO_STREAM_TYPE_SYSTEM; /* user->stream_type; */ + params.type = AUDIO_MIXER_TYPE_WAV; + params.state = AUDIO_STREAM_STATE_PLAYING; + params.buf = raw_sound_data; + params.bufsize = new_sound_size; + params.cb = NULL; + params.basename = NULL; + + audio_driver_mixer_add_stream(¶ms); + + if (raw_sound_data) + { + free(raw_sound_data); + raw_sound_data = NULL; + } + } +#endif + + if (key_str) + { + size_t i; + char key[8]; + size_t length = strlen(key_str); + size_t start = 0; + + for (i = 1; i < length; i++) + { + char t = key_str[i]; + if (i == length - 1 || t == ' ' || t == ',') + { + if (i == length - 1 && t != ' ' && t!= ',') + i++; + + if (i-start > 7) + { + start = i; + continue; + } + + strncpy(key, key_str + start, i-start); + key[i-start] = '\0'; + +#ifdef HAVE_ACCESSIBILITY + if (string_is_equal(key, "b")) + input_st->ai_gamepad_state[0] = 2; + if (string_is_equal(key, "y")) + input_st->ai_gamepad_state[1] = 2; + if (string_is_equal(key, "select")) + input_st->ai_gamepad_state[2] = 2; + if (string_is_equal(key, "start")) + input_st->ai_gamepad_state[3] = 2; + + if (string_is_equal(key, "up")) + input_st->ai_gamepad_state[4] = 2; + if (string_is_equal(key, "down")) + input_st->ai_gamepad_state[5] = 2; + if (string_is_equal(key, "left")) + input_st->ai_gamepad_state[6] = 2; + if (string_is_equal(key, "right")) + input_st->ai_gamepad_state[7] = 2; + + if (string_is_equal(key, "a")) + input_st->ai_gamepad_state[8] = 2; + if (string_is_equal(key, "x")) + input_st->ai_gamepad_state[9] = 2; + if (string_is_equal(key, "l")) + input_st->ai_gamepad_state[10] = 2; + if (string_is_equal(key, "r")) + input_st->ai_gamepad_state[11] = 2; + + if (string_is_equal(key, "l2")) + input_st->ai_gamepad_state[12] = 2; + if (string_is_equal(key, "r2")) + input_st->ai_gamepad_state[13] = 2; + if (string_is_equal(key, "l3")) + input_st->ai_gamepad_state[14] = 2; + if (string_is_equal(key, "r3")) + input_st->ai_gamepad_state[15] = 2; +#endif + + if (string_is_equal(key, "pause")) + command_event(CMD_EVENT_PAUSE, NULL); + if (string_is_equal(key, "unpause")) + command_event(CMD_EVENT_UNPAUSE, NULL); + + start = i+1; + } + } + } + +#ifdef HAVE_ACCESSIBILITY + if ( txt_str + && is_accessibility_enabled( + accessibility_enable, + access_st->enabled)) + accessibility_speak_priority( + accessibility_enable, + accessibility_narrator_speech_speed, + txt_str, 10); +#endif + +finish: + if (error) + RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), error); + + if (user_data) + free(user_data); + + if (json) + rjson_free(json); + if (raw_image_file_data) + free(raw_image_file_data); + if (raw_image_data_alpha) + free(raw_image_data_alpha); + if (raw_image_data) + free(raw_image_data); + if (scaler) + free(scaler); + if (err_str) + free(err_str); + if (txt_str) + free(txt_str); + if (raw_output_data) + free(raw_output_data); + + if (auto_str) + { + if (string_is_equal(auto_str, "auto")) + { + bool was_paused = (runloop_flags & RUNLOOP_FLAG_PAUSED) ? true : false; + if ( (access_st->ai_service_auto != 0) + && !settings->bools.ai_service_pause) + call_auto_translate_task(settings, &was_paused); + } + free(auto_str); + } + if (key_str) + free(key_str); +} + +static const char *ai_service_get_str(enum translation_lang id) { switch (id) { @@ -147,6 +662,8 @@ static const char* ai_service_get_str(enum translation_lang id) return "zh-TW"; case TRANSLATION_LANG_CA: return "ca"; + case TRANSLATION_LANG_BE: + return "be"; case TRANSLATION_LANG_BG: return "bg"; case TRANSLATION_LANG_BN: @@ -237,8 +754,6 @@ static const char* ai_service_get_str(enum translation_lang id) return "tr"; case TRANSLATION_LANG_UK: return "uk"; - case TRANSLATION_LANG_BE: - return "be"; case TRANSLATION_LANG_UR: return "ur"; case TRANSLATION_LANG_VI: @@ -255,9 +770,385 @@ static const char* ai_service_get_str(enum translation_lang id) return ""; } -/** - * Returns true if the accessibility narrator is currently playing audio. - */ +bool run_translation_service(settings_t *settings, bool paused) +{ + struct video_viewport vp; + uint8_t header[54]; + size_t pitch; + unsigned width, height; + const void *data = NULL; + uint8_t *bit24_image = NULL; + uint8_t *bit24_image_prev = NULL; + struct scaler_ctx *scaler = (struct scaler_ctx*) + calloc(1, sizeof(struct scaler_ctx)); + bool error = false; + + uint8_t *bmp_buffer = NULL; + uint64_t buffer_bytes = 0; + char *bmp64_buffer = NULL; + rjsonwriter_t *jsonwriter = NULL; + const char *json_buffer = NULL; + int bmp64_length = 0; + bool TRANSLATE_USE_BMP = false; + char *sys_lbl = NULL; + core_info_t *core_info = NULL; + video_driver_state_t *video_st = video_state_get_ptr(); + access_state_t *access_st = access_state_get_ptr(); +#ifdef HAVE_ACCESSIBILITY + input_driver_state_t *input_st = input_state_get_ptr(); +#endif +#ifdef HAVE_GFX_WIDGETS + dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr(); + /* For the case when ai service pause is disabled. */ + if ( (p_dispwidget->ai_service_overlay_state != 0) + && (access_st->ai_service_auto == 1)) + { + gfx_widgets_ai_service_overlay_unload(); + goto finish; + } +#endif + + + /* get the core info here so we can pass long the game name */ + core_info_get_current_core(&core_info); + + if (core_info) + { + size_t lbl_len; + const char *lbl = NULL; + const char *sys_id = core_info->system_id + ? core_info->system_id : "core"; + size_t sys_id_len = strlen(sys_id); + const struct playlist_entry *entry = NULL; + playlist_t *current_playlist = playlist_get_cached(); + + if (current_playlist) + { + playlist_get_index_by_path( + current_playlist, path_get(RARCH_PATH_CONTENT), &entry); + + if (entry && !string_is_empty(entry->label)) + lbl = entry->label; + } + + if (!lbl) + lbl = path_basename(path_get(RARCH_PATH_BASENAME)); + lbl_len = strlen(lbl); + sys_lbl = (char*)malloc(lbl_len + sys_id_len + 3); + memcpy(sys_lbl, sys_id, sys_id_len); + memcpy(sys_lbl + sys_id_len, "__", 2); + memcpy(sys_lbl + 2 + sys_id_len, lbl, lbl_len); + sys_lbl[sys_id_len + 2 + lbl_len] = '\0'; + } + + if (!scaler) + goto finish; + + data = video_st->frame_cache_data; + width = video_st->frame_cache_width; + height = video_st->frame_cache_height; + pitch = video_st->frame_cache_pitch; + + if (!data) + goto finish; + + if (data == RETRO_HW_FRAME_BUFFER_VALID) + { + /* + The direct frame capture didn't work, so try getting it + from the viewport instead. This isn't as good as the + raw frame buffer, since the viewport may us bilinear + filtering, or other shaders that will completely trash + the OCR, but it's better than nothing. + */ + vp.x = 0; + vp.y = 0; + vp.width = 0; + vp.height = 0; + vp.full_width = 0; + vp.full_height = 0; + + video_driver_get_viewport_info(&vp); + + if (!vp.width || !vp.height) + goto finish; + + bit24_image_prev = (uint8_t*)malloc(vp.width * vp.height * 3); + bit24_image = (uint8_t*)malloc(width * height * 3); + + if (!bit24_image_prev || !bit24_image) + goto finish; + + if (!( video_st->current_video->read_viewport + && video_st->current_video->read_viewport( + video_st->data, bit24_image_prev, false))) + { + RARCH_LOG("Could not read viewport for translation service...\n"); + goto finish; + } + + /* TODO: Rescale down to regular resolution */ + scaler->in_fmt = SCALER_FMT_BGR24; + scaler->out_fmt = SCALER_FMT_BGR24; + scaler->scaler_type = SCALER_TYPE_POINT; + scaler->in_width = vp.width; + scaler->in_height = vp.height; + scaler->out_width = width; + scaler->out_height = height; + scaler_ctx_gen_filter(scaler); + + scaler->in_stride = vp.width*3; + scaler->out_stride = width*3; + scaler_ctx_scale_direct(scaler, bit24_image, bit24_image_prev); + } + else + { + const enum retro_pixel_format + video_driver_pix_fmt = video_st->pix_fmt; + /* This is a software core, so just change the pixel format to 24-bit. */ + if (!(bit24_image = (uint8_t*)malloc(width * height * 3))) + goto finish; + + if (video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888) + scaler->in_fmt = SCALER_FMT_ARGB8888; + else + scaler->in_fmt = SCALER_FMT_RGB565; + video_frame_convert_to_bgr24( + scaler, + (uint8_t *)bit24_image, + (const uint8_t*)data + ((int)height - 1)*pitch, + width, height, + (int)-pitch); + } + scaler_ctx_gen_reset(scaler); + + if (!bit24_image) + { + error = true; + goto finish; + } + + if (TRANSLATE_USE_BMP) + { + /* + At this point, we should have a screenshot in the buffer, + so allocate an array to contain the BMP image along with + the BMP header as bytes, and then covert that to a + b64 encoded array for transport in JSON. + */ + form_bmp_header(header, width, height, false); + if (!(bmp_buffer = (uint8_t*)malloc(width * height * 3 + 54))) + goto finish; + + memcpy(bmp_buffer, header, 54 * sizeof(uint8_t)); + memcpy(bmp_buffer + 54, + bit24_image, + width * height * 3 * sizeof(uint8_t)); + buffer_bytes = sizeof(uint8_t) * (width * height * 3 + 54); + } + else + { + pitch = width * 3; + bmp_buffer = rpng_save_image_bgr24_string( + bit24_image + width * (height-1) * 3, + width, height, (signed)-pitch, &buffer_bytes); + } + + if (!(bmp64_buffer = base64((void *)bmp_buffer, + (int)(sizeof(uint8_t) * buffer_bytes), + &bmp64_length))) + goto finish; + + if (!(jsonwriter = rjsonwriter_open_memory())) + goto finish; + + rjsonwriter_raw(jsonwriter, "{", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_add_string(jsonwriter, "image"); + rjsonwriter_raw(jsonwriter, ":", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_add_string_len(jsonwriter, bmp64_buffer, bmp64_length); + + /* Form request... */ + if (sys_lbl) + { + rjsonwriter_raw(jsonwriter, ",", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_add_string(jsonwriter, "label"); + rjsonwriter_raw(jsonwriter, ":", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_add_string(jsonwriter, sys_lbl); + } + + rjsonwriter_raw(jsonwriter, ",", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_add_string(jsonwriter, "state"); + rjsonwriter_raw(jsonwriter, ":", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_raw(jsonwriter, "{", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_add_string(jsonwriter, "paused"); + rjsonwriter_raw(jsonwriter, ":", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_rawf(jsonwriter, "%u", (paused ? 1 : 0)); + { + static const char* state_labels[] = { "b", "y", "select", "start", "up", "down", "left", "right", "a", "x", "l", "r", "l2", "r2", "l3", "r3" }; + int i; + for (i = 0; i < (int)ARRAY_SIZE(state_labels); i++) + { + rjsonwriter_raw(jsonwriter, ",", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_add_string(jsonwriter, state_labels[i]); + rjsonwriter_raw(jsonwriter, ":", 1); + rjsonwriter_raw(jsonwriter, " ", 1); +#ifdef HAVE_ACCESSIBILITY + rjsonwriter_rawf(jsonwriter, "%u", + (input_st->ai_gamepad_state[i] ? 1 : 0)); +#else + rjsonwriter_rawf(jsonwriter, "%u", 0); +#endif + } + } + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_raw(jsonwriter, "}", 1); + rjsonwriter_raw(jsonwriter, " ", 1); + rjsonwriter_raw(jsonwriter, "}", 1); + + if (!(json_buffer = rjsonwriter_get_memory_buffer(jsonwriter, NULL))) + goto finish; /* ran out of memory */ + +#ifdef DEBUG + if (access_st->ai_service_auto != 2) + RARCH_LOG("Request size: %d\n", bmp64_length); +#endif + { + char new_ai_service_url[PATH_MAX_LENGTH]; + char separator = '?'; + unsigned ai_service_source_lang = settings->uints.ai_service_source_lang; + unsigned ai_service_target_lang = settings->uints.ai_service_target_lang; + const char *ai_service_url = settings->arrays.ai_service_url; + size_t _len = strlcpy(new_ai_service_url, + ai_service_url, sizeof(new_ai_service_url)); + + /* if query already exists in url, then use &'s instead */ + if (strrchr(new_ai_service_url, '?')) + separator = '&'; + + /* source lang */ + if (ai_service_source_lang != TRANSLATION_LANG_DONT_CARE) + { + const char *lang_source = ai_service_get_str( + (enum translation_lang)ai_service_source_lang); + + if (!string_is_empty(lang_source)) + { + new_ai_service_url[ _len] = separator; + new_ai_service_url[++_len] = '\0'; + _len += strlcpy(new_ai_service_url + _len, + "source_lang=", + sizeof(new_ai_service_url) - _len); + _len += strlcpy(new_ai_service_url + _len, + lang_source, + sizeof(new_ai_service_url) - _len); + separator = '&'; + } + } + + /* target lang */ + if (ai_service_target_lang != TRANSLATION_LANG_DONT_CARE) + { + const char *lang_target = ai_service_get_str( + (enum translation_lang)ai_service_target_lang); + + if (!string_is_empty(lang_target)) + { + new_ai_service_url[ _len] = separator; + new_ai_service_url[++_len] = '\0'; + _len += strlcpy(new_ai_service_url + _len, + "target_lang=", + sizeof(new_ai_service_url) - _len); + _len += strlcpy(new_ai_service_url + _len, + lang_target, + sizeof(new_ai_service_url) - _len); + separator = '&'; + } + } + + /* mode */ + { + unsigned ai_service_mode = settings->uints.ai_service_mode; + /*"image" is included for backwards compatability with + * vgtranslate < 1.04 */ + + new_ai_service_url[ _len] = separator; + new_ai_service_url[++_len] = '\0'; + _len += strlcpy(new_ai_service_url + _len, + "output=", + sizeof(new_ai_service_url) - _len); + + switch (ai_service_mode) + { + case 2: + strlcpy(new_ai_service_url + _len, + "text", + sizeof(new_ai_service_url) - _len); + break; + case 1: + case 3: + _len += strlcpy(new_ai_service_url + _len, + "sound,wav", + sizeof(new_ai_service_url) - _len); + if (ai_service_mode == 1) + break; + /* fall-through intentional for ai_service_mode == 3 */ + case 0: + _len += strlcpy(new_ai_service_url + _len, + "image,png", + sizeof(new_ai_service_url) - _len); +#ifdef HAVE_GFX_WIDGETS + if ( video_st->poke + && video_st->poke->load_texture + && video_st->poke->unload_texture) + strlcpy(new_ai_service_url + _len, + ",png-a", + sizeof(new_ai_service_url) - _len); +#endif + break; + default: + break; + } + + } +#ifdef DEBUG + if (access_st->ai_service_auto != 2) + RARCH_LOG("SENDING... %s\n", new_ai_service_url); +#endif + task_push_http_post_transfer(new_ai_service_url, + json_buffer, true, NULL, handle_translation_cb, NULL); + } + + error = false; +finish: + if (bit24_image_prev) + free(bit24_image_prev); + if (bit24_image) + free(bit24_image); + + if (scaler) + free(scaler); + + if (bmp_buffer) + free(bmp_buffer); + + if (bmp64_buffer) + free(bmp64_buffer); + if (sys_lbl) + free(sys_lbl); + if (jsonwriter) + rjsonwriter_free(jsonwriter); + return !error; +} + #ifdef HAVE_ACCESSIBILITY bool is_narrator_running(bool accessibility_enable) { @@ -271,1531 +1162,6 @@ bool is_narrator_running(bool accessibility_enable) if (frontend && frontend->is_narrator_running) return frontend->is_narrator_running(); } - return false; -} -#endif - -/** - * Returns true if array {a} and {b}, both of the same size {size} are equal. - * This method prevents a potential bug with memcmp on some platforms. - */ -static bool u8_array_equal(uint8_t *a, uint8_t *b, int size) -{ - int i = 0; - for (; i < size; i++) - { - if (a[i] != b[i]) - return false; - } return true; } - -/** - * Helper method to simplify accessibility speech usage. This method will only - * use TTS to read the provided text if accessibility has been enabled in the - * frontend or by RetroArch's internal override mechanism. - */ -static void accessibility_speak(const char *text) -{ -#ifdef HAVE_ACCESSIBILITY - settings_t *settings = config_get_ptr(); - unsigned speed = settings->uints.accessibility_narrator_speech_speed; - bool narrator_on = settings->bools.accessibility_enable; - navigation_say(narrator_on, speed, text, 10); #endif -} - -/** - * Speaks the provided text using TTS. This only happens if the narrator has - * been enabled or the service is running in Narrator mode, in which case it - * must been used even if the user has disabled it. - */ -static void translation_speak(const char *text) -{ -#ifdef HAVE_ACCESSIBILITY - settings_t *settings = config_get_ptr(); - access_state_t *access_st = access_state_get_ptr(); - - unsigned mode = settings->uints.ai_service_mode; - unsigned speed = settings->uints.accessibility_narrator_speech_speed; - bool narrator_on = settings->bools.accessibility_enable; - const char* voice = ai_service_get_str(settings->uints.ai_service_target_lang); - - /* Force the use of the narrator in Narrator modes (TTS) */ - if (mode == 2 || mode == 4 || mode == 5 || narrator_on || access_st->enabled) - accessibility_speak_priority(speed, text, 10, voice); -#endif -} - -/** - * Displays the given message on screen and returns true. Returns false if no - * {message} is provided (i.e. it is NULL). The message will be displayed as - * information or error depending on the {error} boolean. In addition, it will - * be logged if {error} is true, or if this is a debug build. The message will - * also be played by the accessibility narrator if the user enabled it. - */ -static bool translation_user_message(const char *message, bool error) -{ - if (message) - { - accessibility_speak(message); - runloop_msg_queue_push( - message, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, - error ? MESSAGE_QUEUE_CATEGORY_ERROR : MESSAGE_QUEUE_CATEGORY_INFO); - if (error) - RARCH_ERR("[Translate] %s\n", message); -#ifdef DEBUG - else - RARCH_LOG("[Translate] %s\n", message); -#endif - return true; - } - return false; -} - -/** - * Displays the given hash on screen and returns true. Returns false if no - * {hash} is provided (i.e. it is NULL). The message will be displayed as - * information or error depending on the {error} boolean. In addition, it will - * be logged if {error} is true, or if this is a debug build. The message will - * also be played by the accessibility narrator if the user enabled it. - */ -static bool translation_hash_message(enum msg_hash_enums hash, bool error) -{ - if (hash) - { - const char *message = msg_hash_to_str(hash); - const char *intl = msg_hash_to_str_us(hash); - - accessibility_speak(message); - runloop_msg_queue_push( - message, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, - error ? MESSAGE_QUEUE_CATEGORY_ERROR : MESSAGE_QUEUE_CATEGORY_INFO); - if (error) - RARCH_ERR("[Translate] %s\n", intl); -#ifdef DEBUG - else - RARCH_LOG("[Translate] %s\n", intl); -#endif - return true; - } - return false; -} - -/** - * Displays the given message on screen and returns true. Returns false if no - * {message} is provided (i.e. it is NULL). The message will be displayed as - * an error and it will be logged. The message will also be played by the - * accessibility narrator if the user enabled it. - */ -static INLINE bool translation_user_error(const char *message) -{ - return translation_user_message(message, true); -} - -#if 0 -/** - * Displays the given message on screen and returns true. Returns false if no - * {message} is provided (i.e. it is NULL). The message will be displayed as - * information and will only be logged if this is a debug build. The message - * will also be played by the accessibility narrator if the user enabled it. - */ -static INLINE bool translation_user_info(const char *message) -{ - return translation_user_message(message, false); -} -#endif - -/** - * Displays the given hash on screen and returns true. Returns false if no - * {hash} is provided (i.e. it is NULL). The message will be displayed as - * an error and it will be logged. The message will also be played by the - * accessibility narrator if the user enabled it. - */ -static INLINE bool translation_hash_error(enum msg_hash_enums hash) -{ - return translation_hash_message(hash, true); -} - -/** - * Displays the given hash on screen and returns true. Returns false if no - * {hash} is provided (i.e. it is NULL). The message will be displayed as - * information and will only be logged if this is a debug build. The message - * will also be played by the accessibility narrator if the user enabled it. - */ -static INLINE bool translation_hash_info(enum msg_hash_enums hash) -{ - return translation_hash_message(hash, false); -} - -/** - * Releases all data held by the service and stops it as soon as possible. - * If {inform} is true, a message will be displayed to the user if the service - * was running in automatic mode to warn them that it is now stopping. - */ -void translation_release(bool inform) -{ -#ifdef HAVE_GFX_WIDGETS - dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr(); -#endif - access_state_t *access_st = access_state_get_ptr(); - unsigned service_auto_prev = access_st->ai_service_auto; - access_st->ai_service_auto = 0; - -#ifdef DEBUG - RARCH_LOG("[Translate]: AI Service is now stopping.\n"); -#endif - - if (access_st->request_task) - task_set_cancelled(access_st->request_task, true); - if (access_st->response_task) - task_set_cancelled(access_st->response_task, true); - -#ifdef HAVE_THREADS - if (access_st->image_lock) - { - slock_lock(access_st->image_lock); -#endif - if (access_st->last_image) - free(access_st->last_image); - - access_st->last_image = NULL; - access_st->last_image_size = 0; - -#ifdef HAVE_THREADS - slock_unlock(access_st->image_lock); - } -#endif - -#ifdef HAVE_GFX_WIDGETS - if (p_dispwidget->ai_service_overlay_state != 0) - gfx_widgets_ai_service_overlay_unload(); -#endif - - if (inform && service_auto_prev != 0) - translation_hash_info(MSG_AI_AUTO_MODE_DISABLED); -} - -/* AUTOMATION --------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -/** - * Handler invoking the next automatic request. This method simply waits for - * any previous request to terminate before re-invoking the translation service. - * By delegating this to a task handler we can safely do so in the task thread - * instead of hogging the main thread. - */ -static void call_auto_translate_hndl(retro_task_t *task) -{ - int *mode_ptr = (int*)task->user_data; - uint32_t runloop_flags = runloop_get_flags(); - access_state_t *access_st = access_state_get_ptr(); - - if (task_get_cancelled(task)) - goto finish; - - switch (*mode_ptr) - { - case 1: /* Speech Mode */ -#ifdef HAVE_AUDIOMIXER - if (!audio_driver_is_ai_service_speech_running()) - goto finish; -#endif - break; - case 2: /* Narrator Mode */ - case 3: /* Text Mode */ - case 4: /* Text + Narrator */ - case 5: /* Image + Narrator */ -#ifdef HAVE_ACCESSIBILITY - if (!is_narrator_running(config_get_ptr()->bools.accessibility_enable)) - goto finish; -#endif - break; - default: - goto finish; - } - return; - -finish: - task_set_finished(task, true); - - if (task->user_data) - free(task->user_data); - - /* Final check to see if the user did not disable the service altogether */ - if (access_st->ai_service_auto != 0) - { - bool was_paused = runloop_flags & RUNLOOP_FLAG_PAUSED; - command_event(CMD_EVENT_AI_SERVICE_CALL, &was_paused); - } -} - -/** - * Invokes the next automatic request. This method delegates the invokation to - * a task to allow for threading. The task will only execute after the polling - * delay configured by the user has been honored since the last request. - */ -static void call_auto_translate_task(settings_t *settings) -{ - int* mode = NULL; - access_state_t *access_st = access_state_get_ptr(); - int ai_service_mode = settings->uints.ai_service_mode; - unsigned delay = settings->uints.ai_service_poll_delay; - retro_task_t *task = task_init(); - if (!task) - return; - - mode = (int*)malloc(sizeof(int)); - *mode = ai_service_mode; - - task->handler = call_auto_translate_hndl; - task->user_data = mode; - task->mute = true; - task->when = access_st->last_call + (delay * 1000); - task_queue_push(task); -} - -/* RESPONSE ----------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -/** - * Parses the JSON returned by the translation server and returns structured - * data. May return NULL if the parsing cannot be completed or the JSON is - * malformed. If unsupported keys are provided in the JSON, they will simply - * be ignored. Only the available data will be populated in the returned object - * and everything else will be zero-initialized. - */ -static access_response_t* parse_response_json(http_transfer_data_t *data) -{ - int key = -1; - rjson_t* json = NULL; - access_response_t *response = NULL; - enum rjson_type type; - - if (!data || !data->data) - goto finish; - if (!(json = rjson_open_buffer(data->data, data->len))) - goto finish; - if (!(response = (access_response_t*)calloc(1, sizeof(access_response_t)))) - goto finish; - - for (;;) - { - size_t length = 0; - const char *string = NULL; - type = rjson_next(json); - - if (type == RJSON_DONE || type == RJSON_ERROR) - break; - if (rjson_get_context_type(json) != RJSON_OBJECT) - continue; - - if (type == RJSON_STRING && (rjson_get_context_count(json) & 1) == 1) - { - unsigned i; - string = rjson_get_string(json, &length); - for (i = 0; i < ARRAY_SIZE(ACCESS_RESPONSE_KEYS) && key == -1; i++) - { - if (string_is_equal(string, ACCESS_RESPONSE_KEYS[i])) - key = i; - } - } - else - { - if (type != RJSON_STRING && key < 6) - continue; - else - string = rjson_get_string(json, &length); - - switch (key) - { - case 0: /* image */ - response->image = (length == 0) ? NULL : (char*)unbase64( - string, (int)length, &response->image_size); - break; -#ifdef HAVE_AUDIOMIXER - case 1: /* sound */ - response->sound = (length == 0) ? NULL : (void*)unbase64( - string, (int)length, &response->sound_size); - break; -#endif - case 2: /* text */ - response->text = strdup(string); - break; - case 3: /* error */ - response->error = strdup(string); - break; - case 4: /* auto */ - response->recall = strdup(string); - break; - case 5: /* press */ - response->input = strdup(string); - break; - case 6: /* text_position */ - if (type == RJSON_NUMBER) - response->text_position = rjson_get_int(json); - break; - } - key = -1; - } - } - - if (type == RJSON_ERROR) - { - RARCH_LOG("[Translate] JSON error: %s\n", rjson_get_error(json)); - translation_user_error("Service returned a malformed JSON"); - free(response); - response = NULL; - } - -finish: - if (json) - rjson_free(json); - else - translation_user_error("Internal error parsing returned JSON."); - - return response; -} - -/** - * Parses the image data of given type and displays it using widgets. If the - * image widget is already shown, it will be unloaded first automatically. - * This method will disable automatic translation if the widget could not be - * loaded to prevent further errors. - */ -#ifdef HAVE_GFX_WIDGETS -static void translation_response_image_widget( - char *image, int image_length, enum image_type_enum *image_type) -{ - video_driver_state_t *video_st = video_state_get_ptr(); - dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr(); - - bool ai_res; - bool gfx_widgets_paused = video_st->flags & VIDEO_FLAG_WIDGETS_PAUSED; - - if (p_dispwidget->ai_service_overlay_state != 0) - gfx_widgets_ai_service_overlay_unload(); - - ai_res = gfx_widgets_ai_service_overlay_load( - image, (unsigned)image_length, (*image_type)); - - if (!ai_res) - { - translation_hash_error(MSG_AI_VIDEO_DRIVER_NOT_SUPPORTED); - translation_release(true); - } - else if (gfx_widgets_paused) - { - /* Unpause for a frame otherwise widgets won't be displayed */ - p_dispwidget->ai_service_overlay_state = 2; - command_event(CMD_EVENT_UNPAUSE, NULL); - } -} -#endif - -/** - * Parses the image buffer, converting the data to the raw image format we need - * to display the image within RetroArch. Writes the raw image data in {body} - * as well as its {width} and {height} as determined by the image header. - * Returns true if the process was successful. - */ -static bool translation_get_image_body( - char *image, int image_size, enum image_type_enum *image_type, - void *body, unsigned *width, unsigned *height) -{ -#ifdef HAVE_RPNG - rpng_t *rpng = NULL; - void *rpng_alpha = NULL; - int rpng_ret = 0; -#endif - - if ((*image_type) == IMAGE_TYPE_BMP) - { - if (image_size < 55) - return false; - - *width = ((uint32_t) ((uint8_t)image[21]) << 24) - + ((uint32_t) ((uint8_t)image[20]) << 16) - + ((uint32_t) ((uint8_t)image[19]) << 8) - + ((uint32_t) ((uint8_t)image[18]) << 0); - *height = ((uint32_t) ((uint8_t)image[25]) << 24) - + ((uint32_t) ((uint8_t)image[24]) << 16) - + ((uint32_t) ((uint8_t)image[23]) << 8) - + ((uint32_t) ((uint8_t)image[22]) << 0); - - image_size = (*width) * (*height) * 3 * sizeof(uint8_t); - body = (void*)malloc(image_size); - if (!body) - return false; - - memcpy(body, image + 54 * sizeof(uint8_t), image_size); - return true; - } - -#ifdef HAVE_RPNG - else if ((*image_type) == IMAGE_TYPE_PNG) - { - if (image_size < 24) - return false; - if (!(rpng = rpng_alloc())) - return false; - - *width = ((uint32_t) ((uint8_t)image[16]) << 24) - + ((uint32_t) ((uint8_t)image[17]) << 16) - + ((uint32_t) ((uint8_t)image[18]) << 8) - + ((uint32_t) ((uint8_t)image[19]) << 0); - *height = ((uint32_t) ((uint8_t)image[20]) << 24) - + ((uint32_t) ((uint8_t)image[21]) << 16) - + ((uint32_t) ((uint8_t)image[22]) << 8) - + ((uint32_t) ((uint8_t)image[23]) << 0); - - rpng_set_buf_ptr(rpng, image, (size_t)image_size); - rpng_start(rpng); - while (rpng_iterate_image(rpng)); - - do - { - rpng_ret = rpng_process_image( - rpng, &rpng_alpha, (size_t)image_size, width, height); - } while (rpng_ret == IMAGE_PROCESS_NEXT); - - /* - * Returned output from the png processor is an upside down RGBA - * image, so we have to change that to RGB first. This should - * probably be replaced with a scaler call. - */ - { - int d = 0; - int tw, th, tc; - unsigned ui; - image_size = (*width) * (*height) * 3 * sizeof(uint8_t); - body = (void*)malloc(image_size); - if (!body) - { - free(rpng_alpha); - rpng_free(rpng); - return false; - } - - for (ui = 0; ui < (*width) * (*height) * 4; ui++) - { - if (ui % 4 != 3) - { - tc = d % 3; - th = (*height) - d / (3 * (*width)) - 1; - tw = (d % ((*width) * 3)) / 3; - ((uint8_t*) body)[tw * 3 + th * 3 * (*width) + tc] - = ((uint8_t*)rpng_alpha)[ui]; - d++; - } - } - } - free(rpng_alpha); - rpng_free(rpng); - return true; - } -#endif - - return false; -} - -/** - * Displays the raw image on screen by directly writing to the frame buffer. - * This method may fail depending on the current video driver. - */ - /* TODO/FIXME: Does nothing with Vulkan apparently? */ -static void translation_response_image_direct( - char *image, int image_size, enum image_type_enum *image_type) -{ - size_t pitch; - unsigned width; - unsigned height; - unsigned vp_width; - unsigned vp_height; - - void *image_body = NULL; - uint8_t *raw_output_data = NULL; - size_t raw_output_size = 0; - const void *dummy_data = NULL; - struct scaler_ctx *scaler = NULL; - video_driver_state_t *video_st = video_state_get_ptr(); - const enum retro_pixel_format video_driver_pix_fmt = video_st->pix_fmt; - - if (!(translation_get_image_body( - image, image_size, image_type, image_body, &width, &height))) - goto finish; - - if (!(scaler = (struct scaler_ctx*)calloc(1, sizeof(struct scaler_ctx)))) - goto finish; - - dummy_data = video_st->frame_cache_data; - vp_width = video_st->frame_cache_width; - vp_height = video_st->frame_cache_height; - pitch = video_st->frame_cache_pitch; - - if (!vp_width || !vp_height) - goto finish; - - if (dummy_data == RETRO_HW_FRAME_BUFFER_VALID) - { - /* In this case, we used the viewport to grab the image and translate it, - * and we have the translated image in the image_body buffer. */ - translation_user_error("Video driver unsupported for hardware frame."); - translation_release(true); - goto finish; - } - - /* - * The assigned pitch may not be reliable. The width of the video frame can - * change during run-time, but the pitch may not, so we just assign it as - * the width times the byte depth. - */ - if (video_driver_pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888) - { - raw_output_size = vp_width * vp_height * 4 * sizeof(uint8_t); - raw_output_data = (uint8_t*)malloc(raw_output_size); - scaler->out_fmt = SCALER_FMT_ARGB8888; - scaler->out_stride = vp_width * 4; - pitch = vp_width * 4; - } - else - { - raw_output_size = vp_width * vp_height * 2 * sizeof(uint8_t); - raw_output_data = (uint8_t*)malloc(raw_output_size); - scaler->out_fmt = SCALER_FMT_RGB565; - scaler->out_stride = vp_width * 1; - pitch = vp_width * 2; - } - - if (!raw_output_data) - goto finish; - - scaler->in_fmt = SCALER_FMT_BGR24; - scaler->in_width = width; - scaler->in_height = height; - scaler->out_width = vp_width; - scaler->out_height = vp_height; - scaler->scaler_type = SCALER_TYPE_POINT; - scaler_ctx_gen_filter(scaler); - - scaler->in_stride = -1 * vp_width * 3; - - scaler_ctx_scale_direct( - scaler, raw_output_data, - (uint8_t*)image_body + (height - 1) * width * 3); - video_driver_frame(raw_output_data, width, height, pitch); - -finish: - if (image_body) - free(image_body); - if (scaler) - free(scaler); - if (raw_output_data) - free(raw_output_data); -} - -/** - * Parses image data received by the server following a translation request. - * This method assumes that image data is present in the response, it cannot - * be null. If widgets are supported, this method will prefer using them to - * overlay the picture on top of the video, otherwise it will try to write the - * data directly into the frame buffer, which is much less reliable. - */ -static void translation_response_image_hndl(retro_task_t *task) -{ - /* - * TODO/FIXME: Moved processing to the callback to fix an issue with - * texture loading off the main thread in OpenGL. I'm leaving the original - * structure here so we can move back to the handler if it becomes possible - * in the future. - */ - task_set_finished(task, true); -} - -/** - * Callback invoked once the image data received from the server has been - * processed and eventually displayed. This is necessary to ensure that the - * next automatic request will be invoked once the task is finished. - */ -static void translation_response_image_cb( - retro_task_t *task, void *task_data, void *user_data, const char *error) -{ - settings_t* settings = config_get_ptr(); - access_state_t *access_st = access_state_get_ptr(); - - enum image_type_enum image_type; - access_response_t *response = (access_response_t*)task->user_data; - video_driver_state_t *video_st = video_state_get_ptr(); - - if (task_get_cancelled(task) || response->image_size < 4) - goto finish; - - if ( response->image[0] == 'B' - && response->image[1] == 'M') - image_type = IMAGE_TYPE_BMP; -#ifdef HAVE_RPNG - else if (response->image[1] == 'P' - && response->image[2] == 'N' - && response->image[3] == 'G') - image_type = IMAGE_TYPE_PNG; -#endif - else - { - translation_user_error("Service returned an unsupported image type."); - translation_release(true); - goto finish; - } - -#ifdef HAVE_GFX_WIDGETS - if ( video_st->poke - && video_st->poke->load_texture - && video_st->poke->unload_texture) - translation_response_image_widget( - response->image, response->image_size, &image_type); - else -#endif - translation_response_image_direct( - response->image, response->image_size, &image_type); - -finish: - free(response->image); - free(response); - - if (access_st->ai_service_auto != 0) - call_auto_translate_task(settings); -} - -/** - * Processes text data received by the server following a translation request. - * Does nothing if the response does not contain any text data (NULL). Text - * is either forcibly read by the narrator, even if it is disabled in the - * front-end (Narrator Mode) or displayed on screen (in Text Mode). In the - * later, it will only be read if the front-end narrator is enabled. - */ -static void translation_response_text(access_response_t *response) -{ - settings_t *settings = config_get_ptr(); - unsigned service_mode = settings->uints.ai_service_mode; - access_state_t *access_st = access_state_get_ptr(); - - if ( (!response->text || string_is_empty(response->text)) - && (service_mode == 2 || service_mode == 3 || service_mode == 4) - && access_st->ai_service_auto == 0) - { - translation_hash_info(MSG_AI_NOTHING_TO_TRANSLATE); - return; - } - - if (response->text) - { - /* The text should be displayed on screen in Text or Text+Narrator mode */ - if (service_mode == 3 || service_mode == 4) - { -#ifdef HAVE_GFX_WIDGETS - if (settings->bools.menu_enable_widgets) - { - dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr(); - - if (p_dispwidget->ai_service_overlay_state == 1) - gfx_widgets_ai_service_overlay_unload(); - - strlcpy(p_dispwidget->ai_service_text, response->text, 255); - - if (response->text_position > 0) - p_dispwidget->ai_service_text_position - = (unsigned)response->text_position; - else - p_dispwidget->ai_service_text_position = 0; - - p_dispwidget->ai_service_overlay_state = 1; - } - else - { -#endif - /* - * TODO/FIXME: Obviously this will not be as good as using widgets, - * since messages run on a timer but it's an alternative at least. - * Maybe split the message here so it fits the viewport. - */ - runloop_msg_queue_push( - response->text, 2, 180, - true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, - MESSAGE_QUEUE_CATEGORY_INFO); - -#ifdef HAVE_GFX_WIDGETS - } -#endif - } - translation_speak(&response->text[0]); - free(response->text); - } -} - -/** - * Processes audio data received by the server following a translation request. - * Does nothing if the response does not contain any audio data (NULL). Audio - * data is simply played as soon as possible using the audio driver. - */ -static void translation_response_sound(access_response_t *response) -{ -#ifdef HAVE_AUDIOMIXER - if (response->sound) - { - audio_mixer_stream_params_t params; - - params.volume = 1.0f; - /* user->slot_selection_type; */ - params.slot_selection_type = AUDIO_MIXER_SLOT_SELECTION_MANUAL; - params.slot_selection_idx = 10; - /* user->stream_type; */ - params.stream_type = AUDIO_STREAM_TYPE_SYSTEM; - params.type = AUDIO_MIXER_TYPE_WAV; - params.state = AUDIO_STREAM_STATE_PLAYING; - params.buf = response->sound; - params.bufsize = response->sound_size; - params.cb = NULL; - params.basename = NULL; - - audio_driver_mixer_add_stream(¶ms); - free(response->sound); - } -#endif -} - -/** - * Processes input data received by the server following a translation request. - * Does nothing if the response does not contain any input data (NULL). This - * method will try to forcibly press all the retropad keys listed in the input - * string (comma-separated). - */ -static void translation_response_input(access_response_t *response) -{ - if (response->input) - { -#ifdef HAVE_ACCESSIBILITY - input_driver_state_t *input_st = input_state_get_ptr(); -#endif - char *token = strtok(response->input, ","); - - while (token) - { - if (string_is_equal(token, "pause")) - command_event(CMD_EVENT_PAUSE, NULL); - else if (string_is_equal(token, "unpause")) - command_event(CMD_EVENT_UNPAUSE, NULL); -#ifdef HAVE_ACCESSIBILITY - else - { - unsigned i = 0; - bool found = false; - - for (; i < ARRAY_SIZE(ACCESS_INPUT_LABELS) && !found; i++) - found = string_is_equal(ACCESS_INPUT_LABELS[i], response->input); - - if (found) - input_st->ai_gamepad_state[i] = 2; - } -#endif - token = strtok(NULL, ","); - } - free(response->input); - } -} - -/** - * Callback invoked when the server responds to our translation request. If the - * service is still running by then, this method will parse the JSON payload - * and process the data, eventually re-invoking the translation service for - * a new request if the server allowed automatic translation. - */ -static void translation_response_cb( - retro_task_t *task, void *task_data, void *user_data, const char *error) -{ - http_transfer_data_t *data = (http_transfer_data_t*)task_data; - access_state_t *access_st = access_state_get_ptr(); - settings_t *settings = config_get_ptr(); - access_response_t *response = NULL; - bool auto_mode_prev = access_st->ai_service_auto; - unsigned service_mode = settings->uints.ai_service_mode; - - /* We asked the service to stop by calling translation_release, so bail */ - if (!access_st->last_image) - goto finish; - if (translation_user_error(error)) - goto abort; - if (!(response = parse_response_json(data))) - goto abort; - if (translation_user_error(response->error)) - goto abort; - - access_st->ai_service_auto = (response->recall == NULL) ? 0 : 1; - if (auto_mode_prev != access_st->ai_service_auto) - translation_hash_info(auto_mode_prev - ? MSG_AI_AUTO_MODE_DISABLED : MSG_AI_AUTO_MODE_ENABLED); - - /* - * We want to skip the data on auto=continue, unless automatic translation - * has just been enabled, meaning data must be displayed again to the user. - */ - if ( !string_is_equal(response->recall, "continue") - || (auto_mode_prev == 0 && access_st->ai_service_auto == 1)) - { -#ifdef HAVE_GFX_WIDGETS - dispgfx_widget_t *p_dispwidget = dispwidget_get_ptr(); - if (p_dispwidget->ai_service_overlay_state != 0) - gfx_widgets_ai_service_overlay_unload(); -#endif - translation_response_text(response); - translation_response_sound(response); - translation_response_input(response); - - if (response->image) - { - retro_task_t *task = task_init(); - if (!task) - goto finish; - - task->handler = translation_response_image_hndl; - task->callback = translation_response_image_cb; - task->user_data = response; - task->mute = true; - access_st->response_task = task; - task_queue_push(task); - - /* Leave memory clean-up and auto callback to the task itself */ - return; - } - else if (access_st->ai_service_auto == 0 - && (service_mode == 0 || service_mode == 5)) - translation_hash_info(MSG_AI_NOTHING_TO_TRANSLATE); - } - goto finish; - -abort: - translation_release(true); - if (response && response->error) - free(response->error); - -finish: - if (response) - { - if (response->image) - free(response->image); - if (response->recall) - free(response->recall); - free(response); - - if (access_st->ai_service_auto != 0) - call_auto_translate_task(settings); - } -} - -/* REQUEST ------------------------------------------------------------------ */ -/* -------------------------------------------------------------------------- */ - -/** - * Grabs and returns a frame from the video driver. If the frame buffer cannot - * be accessed, this method will try to obtain a capture of the viewport as a - * fallback, although this frame may be altered by any filter or shader enabled - * by the user. Returns null if both methods fail. - */ -static access_frame_t* translation_grab_frame(void) -{ - size_t pitch; - struct video_viewport vp = {0}; - const void *data = NULL; - uint8_t *bit24_image_prev = NULL; - struct scaler_ctx *scaler = NULL; - access_frame_t *frame = NULL; - video_driver_state_t *video_st = video_state_get_ptr(); - const enum retro_pixel_format pix_fmt = video_st->pix_fmt; - - if (!(scaler = (struct scaler_ctx*)calloc(1, sizeof(struct scaler_ctx)))) - goto finish; - if (!(frame = (access_frame_t*)malloc(sizeof(access_frame_t)))) - goto finish; - - data = video_st->frame_cache_data; - frame->data = NULL; - frame->width = video_st->frame_cache_width; - frame->height = video_st->frame_cache_height; - pitch = video_st->frame_cache_pitch; - - if (!data) - goto finish; - - video_driver_get_viewport_info(&vp); - if (!vp.width || !vp.height) - goto finish; - - frame->content_x = vp.x; - frame->content_y = vp.y; - frame->content_width = vp.width; - frame->content_height = vp.height; - frame->viewport_width = vp.full_width; - frame->viewport_height = vp.full_height; - frame->size = frame->width * frame->height * 3; - - if (!(frame->data = (uint8_t*)malloc(frame->size))) - goto finish; - - if (data == RETRO_HW_FRAME_BUFFER_VALID) - { - /* Direct frame capture failed, fallback on viewport capture */ - if (!(bit24_image_prev = (uint8_t*)malloc(vp.width * vp.height * 3))) - goto finish; - - if (!( video_st->current_video->read_viewport - && video_st->current_video->read_viewport( - video_st->data, bit24_image_prev, false))) - { - translation_user_error("Could not read viewport."); - translation_release(true); - goto finish; - } - - /* TODO: Rescale down to regular resolution */ - scaler->in_fmt = SCALER_FMT_BGR24; - scaler->out_fmt = SCALER_FMT_BGR24; - scaler->scaler_type = SCALER_TYPE_POINT; - scaler->in_width = vp.width; - scaler->in_height = vp.height; - scaler->out_width = frame->width; - scaler->out_height = frame->height; - scaler_ctx_gen_filter(scaler); - - scaler->in_stride = vp.width * 3; - scaler->out_stride = frame->width * 3; - scaler_ctx_scale_direct(scaler, frame->data, bit24_image_prev); - } - else - { - /* This is a software core, so just change the pixel format to 24-bit */ - if (pix_fmt == RETRO_PIXEL_FORMAT_XRGB8888) - scaler->in_fmt = SCALER_FMT_ARGB8888; - else - scaler->in_fmt = SCALER_FMT_RGB565; - - video_frame_convert_to_bgr24( - scaler, frame->data, (const uint8_t*)data, - frame->width, frame->height, (int)pitch); - } - scaler_ctx_gen_reset(scaler); - -finish: - if (bit24_image_prev) - free(bit24_image_prev); - if (scaler) - free(scaler); - - if (frame) - { - if (frame->data) - return frame; - free(frame); - } - return NULL; -} - -/** - * Returns true if the {frame} passed in parameter is a duplicate of the last - * frame the service was invoked on. This method effectively helps to prevent - * the service from spamming the server with the same request over and over - * again when running in automatic mode. This method will also save the image - * in the {frame} structure as the new last image for the service. - */ -static bool translation_dupe_fail(access_frame_t *frame) -{ - access_state_t *access_st = access_state_get_ptr(); - bool size_equal = (frame->size == access_st->last_image_size); - bool has_failed = false; - -#ifdef HAVE_THREADS - slock_lock(access_st->image_lock); -#endif - if (access_st->last_image && access_st->ai_service_auto != 0) - { - if ( size_equal - && u8_array_equal(frame->data, access_st->last_image, frame->size)) - has_failed = true; - } - - /* Init last image or reset buffer size if image size changed */ - if (!has_failed && (!access_st->last_image || !size_equal)) - { - if (access_st->last_image) - free(access_st->last_image); - - access_st->last_image_size = frame->size; - if (!(access_st->last_image = (uint8_t*)malloc(frame->size))) - has_failed = true; - } - - if (!has_failed) - memcpy(access_st->last_image, frame->data, frame->size); - -#ifdef HAVE_THREADS - slock_unlock(access_st->image_lock); -#endif - return has_failed; -} - -/** - * Converts and returns the {frame} as a base64 encoded PNG or BMP. The - * selected image type will be available in the returned object, and will - * favor PNG if possible. Returns NULL on failure. - */ -static access_base64_t* translation_frame_encode(access_frame_t *frame) -{ -#ifndef HAVE_RPNG - uint8_t header[54]; -#endif - uint8_t *buffer = NULL; - uint64_t bytes = 0; - access_base64_t *encode = NULL; - - if (!(encode = (access_base64_t*)malloc(sizeof(access_base64_t)))) - goto finish; - -#ifdef HAVE_RPNG - strcpy(encode->format, "png"); - buffer = rpng_save_image_bgr24_string( - frame->data, frame->width, frame->height, - frame->width * 3, &bytes); -#else - strcpy(encode->format, "bmp"); - form_bmp_header(header, frame->width, frame->height, false); - if (!(buffer = (uint8_t*)malloc(frame->size + 54))) - goto finish; - - memcpy(buffer, header, 54 * sizeof(uint8_t)); - memcpy(buffer + 54, frame->data, frame->size * sizeof(uint8_t)); - bytes = sizeof(uint8_t) * (frame->size + 54); -#endif - - encode->data = base64( - (void*)buffer, (int)(bytes * sizeof(uint8_t)), &encode->length); - -finish: - if (buffer) - free(buffer); - - if (encode->data) - return encode; - else - free(encode); - - return NULL; -} - -/** - * Returns a newly allocated string describing the content and core currently - * running. The string will contains the name of the core (or 'core') followed - * by a double underscore (_) and the name of the content. Returns NULL on - * failure. - */ -static char* translation_get_content_label(void) -{ - const char *label = NULL; - char* system_label = NULL; - core_info_t *core_info = NULL; - - core_info_get_current_core(&core_info); - if (core_info) - { - const struct playlist_entry *entry = NULL; - playlist_t *current_playlist = playlist_get_cached(); - const char *system_id; - size_t system_id_len; - size_t label_len; - - system_id = (core_info->system_id) ? core_info->system_id : "core"; - system_id_len = strlen(system_id); - - if (current_playlist) - { - playlist_get_index_by_path( - current_playlist, path_get(RARCH_PATH_CONTENT), &entry); - - if (entry && !string_is_empty(entry->label)) - label = entry->label; - } - - if (!label) - label = path_basename(path_get(RARCH_PATH_BASENAME)); - - label_len = strlen(label); - if (!(system_label = (char*)malloc(label_len + system_id_len + 3))) - return NULL; - - memcpy(system_label, system_id, system_id_len); - memcpy(system_label + system_id_len, "__", 2); - memcpy(system_label + 2 + system_id_len, label, label_len); - system_label[system_id_len + 2 + label_len] = '\0'; - } - - return system_label; -} - -/** - * Creates and returns a JSON writer containing the payload to send alongside - * the translation request. {label} may be NULL, in which case no label will - * be supplied in the JSON. Returns NULL if the writer cannot be initialized. - */ -static rjsonwriter_t* build_request_json( - access_base64_t *image, access_request_t *request, - access_frame_t *frame, char *label) -{ - unsigned i; - rjsonwriter_t* writer = NULL; - - if (!(writer = rjsonwriter_open_memory())) - return NULL; - - rjsonwriter_add_start_object(writer); - { - rjsonwriter_add_string(writer, "image"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_string_len(writer, image->data, image->length); - - rjsonwriter_add_comma(writer); - rjsonwriter_add_string(writer, "format"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_string(writer, image->format); - - rjsonwriter_add_comma(writer); - rjsonwriter_add_string(writer, "coords"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_start_array(writer); - { - rjsonwriter_add_unsigned(writer, frame->content_x); - rjsonwriter_add_comma(writer); - rjsonwriter_add_unsigned(writer, frame->content_y); - rjsonwriter_add_comma(writer); - rjsonwriter_add_unsigned(writer, frame->content_width); - rjsonwriter_add_comma(writer); - rjsonwriter_add_unsigned(writer, frame->content_height); - } - rjsonwriter_add_end_array(writer); - - rjsonwriter_add_comma(writer); - rjsonwriter_add_string(writer, "viewport"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_start_array(writer); - { - rjsonwriter_add_unsigned(writer, frame->viewport_width); - rjsonwriter_add_comma(writer); - rjsonwriter_add_unsigned(writer, frame->viewport_height); - } - rjsonwriter_add_end_array(writer); - - if (label) - { - rjsonwriter_add_comma(writer); - rjsonwriter_add_string(writer, "label"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_string(writer, label); - } - - rjsonwriter_add_comma(writer); - rjsonwriter_add_string(writer, "state"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_start_object(writer); - { - rjsonwriter_add_string(writer, "paused"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_unsigned(writer, (request->paused ? 1 : 0)); - - for (i = 0; i < ARRAY_SIZE(ACCESS_INPUT_LABELS); i++) - { - rjsonwriter_add_comma(writer); - rjsonwriter_add_string(writer, ACCESS_INPUT_LABELS[i]); - rjsonwriter_add_colon(writer); - rjsonwriter_add_unsigned(writer, request->inputs[i]); - } - rjsonwriter_add_end_object(writer); - } - rjsonwriter_add_end_object(writer); - } - - return writer; -} - -/** - * Writes in the provided {buffer} the URL for the translation request. The - * buffer is guaranteed to contain the server URL as well as an 'output' param - * specifying the accepted data types for this service. - */ -static void build_request_url(char *buffer, size_t length, settings_t *settings) -{ - char token[2]; - size_t _len; - bool poke_supported = false; - unsigned service_source_lang = settings->uints.ai_service_source_lang; - unsigned service_target_lang = settings->uints.ai_service_target_lang; - const char *service_url = settings->arrays.ai_service_url; - unsigned ai_service_mode = settings->uints.ai_service_mode; -#ifdef HAVE_GFX_WIDGETS - video_driver_state_t *video_st = video_state_get_ptr(); - poke_supported = video_st->poke - && video_st->poke->load_texture - && video_st->poke->unload_texture; -#endif - - token[1] = '\0'; - if (strrchr(service_url, '?')) - token[0] = '&'; - else - token[0] = '?'; - - _len = strlcpy(buffer, service_url, length); - buffer += _len ; - length -= _len; - - if (service_source_lang != TRANSLATION_LANG_DONT_CARE) - { - const char *lang_source - = ai_service_get_str((enum translation_lang)service_source_lang); - - if (!string_is_empty(lang_source)) - { - _len = strlcpy(buffer, token, length); - buffer += _len; - length -= _len; - - _len = strlcpy(buffer, "source_lang=", length); - buffer += _len; - length -= _len; - - _len = strlcpy(buffer, lang_source, length); - buffer += _len; - length -= _len; - token[0] = '&'; - } - } - - if (service_target_lang != TRANSLATION_LANG_DONT_CARE) - { - const char *lang_target - = ai_service_get_str((enum translation_lang)service_target_lang); - - if (!string_is_empty(lang_target)) - { - _len = strlcpy(buffer, token, length); - buffer += _len; - length -= _len; - - _len = strlcpy(buffer, "target_lang=", length); - buffer += _len; - length -= _len; - - _len = strlcpy(buffer, lang_target, length); - buffer += _len; - length -= _len; - token[0] = '&'; - } - } - - _len = strlcpy(buffer, token, length); - buffer += _len; - length -= _len; - - _len = strlcpy(buffer, "output=", length); - buffer += _len; - length -= _len; - - switch (ai_service_mode) - { - case 0: /* Image Mode */ - _len = strlcpy(buffer, "image,bmp", length); - buffer += _len; - length -= _len; -#ifdef HAVE_RPNG - _len = strlcpy(buffer, ",png", length); - buffer += _len; - length -= _len; - if (poke_supported) - { - strlcpy(buffer, ",png-a", length); - buffer += _len; - length -= _len; - } -#endif - break; - - case 1: /* Speech Mode */ - _len = strlcpy(buffer, "sound,wav", length); - buffer += _len; - length -= _len; - break; - - case 2: /* Narrator Mode */ - _len = strlcpy(buffer, "text", length); - buffer += _len; - length -= _len; - break; - - case 3: /* Text Mode */ - case 4: /* Text + Narrator */ - _len = strlcpy(buffer, "text,subs", length); - buffer += _len; - length -= _len; - break; - - case 5: /* Image + Narrator */ - _len = strlcpy(buffer, "text,image,bmp", length); - buffer += _len; - length -= _len; -#ifdef HAVE_RPNG - _len = strlcpy(buffer, ",png", length); - buffer += _len; - length -= _len; - if (poke_supported) - { - _len = strlcpy(buffer, ",png-a", length); - buffer += _len; - length -= _len; - } -#endif - break; - } -} - -/** - * Captures a frame from the currently running core and sends a request to the - * translation server. Processing and encoding this data comes with a cost, so - * it is offloaded to the task thread. - */ -static void translation_request_hndl(retro_task_t *task) -{ - access_request_t *request = (access_request_t*)task->user_data; - settings_t *settings = config_get_ptr(); - access_state_t *access_st = access_state_get_ptr(); - access_frame_t *frame = NULL; - access_base64_t *encode = NULL; - char *label = NULL; - rjsonwriter_t *writer = NULL; - const char *json = NULL; - bool sent = false; - char url[PATH_MAX_LENGTH]; - - if (task_get_cancelled(task)) - goto finish; - - access_st->last_call = cpu_features_get_time_usec(); - - frame = translation_grab_frame(); - if (task_get_cancelled(task) || !frame) - goto finish; - - if (translation_dupe_fail(frame)) - goto finish; - - encode = translation_frame_encode(frame); - if (task_get_cancelled(task) || !encode) - goto finish; - - label = translation_get_content_label(); - writer = build_request_json(encode, request, frame, label); - if (task_get_cancelled(task) || !writer) - goto finish; - - json = rjsonwriter_get_memory_buffer(writer, NULL); - build_request_url(url, PATH_MAX_LENGTH, settings); - if (task_get_cancelled(task) || !json) - goto finish; - -#ifdef DEBUG - if (access_st->ai_service_auto == 0) - RARCH_LOG("[Translate]: Sending request to: %s\n", url); -#endif - sent = true; - task_push_http_post_transfer( - url, json, true, NULL, translation_response_cb, NULL); - -finish: - task_set_finished(task, true); - - if (frame) - { - if (frame->data) - free(frame->data); - free(frame); - } - if (encode) - { - if (encode->data) - free(encode->data); - free(encode); - } - if (label) - free(label); - if (writer) - rjsonwriter_free(writer); - if (request) - { - if (request->inputs) - free(request->inputs); - free(request); - } - - /* Plan next auto-request if this one was skipped */ - if (!sent && access_st->ai_service_auto != 0) - call_auto_translate_task(settings); -} - -/** - * Invokes the translation service. Captures a frame from the current content - * core and sends it over HTTP to the translation server. Once the server - * responds, the translation data is displayed accordingly to the preferences - * of the user. Returns true if the request could be built and sent. - */ -bool run_translation_service(settings_t *settings, bool paused) -{ - retro_task_t *task = NULL; - access_request_t *request = NULL; - access_state_t *access_st = access_state_get_ptr(); -#ifdef HAVE_ACCESSIBILITY - unsigned i; - input_driver_state_t *input_st = input_state_get_ptr(); -#endif - - if (!(request = (access_request_t*)malloc(sizeof(access_request_t)))) - goto failure; - -#ifdef HAVE_THREADS - if (!access_st->image_lock) - { - if (!(access_st->image_lock = slock_new())) - goto failure; - } -#endif - - task = task_init(); - if (!task) - goto failure; - - /* Freeze frontend state while we're still running on the main thread */ - request->paused = paused; - request->inputs = (char*)malloc( - sizeof(char) * ARRAY_SIZE(ACCESS_INPUT_LABELS)); - -#ifdef HAVE_ACCESSIBILITY - for (i = 0; i < ARRAY_SIZE(ACCESS_INPUT_LABELS); i++) - request->inputs[i] = input_st->ai_gamepad_state[i] ? 1 : 0; -#endif - - task->handler = translation_request_hndl; - task->user_data = request; - task->mute = true; - access_st->request_task = task; - task_queue_push(task); - - return true; - -failure: - if (request) - free(request); - - return false; -}