From 9aad61f94081aaafd414f64a16757321f8acdfbd Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 12 Jun 2025 15:29:20 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=E6=96=B0=E5=A2=9E=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E8=AF=AD=E8=A8=80=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- langAi.py | 549 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 549 insertions(+) create mode 100644 langAi.py diff --git a/langAi.py b/langAi.py new file mode 100644 index 00000000..2ef67bb4 --- /dev/null +++ b/langAi.py @@ -0,0 +1,549 @@ +#!/usr/bin/python3 +import base64 +import datetime +import hashlib +import hmac +import json +import os +import sys +import time +import urllib.parse +from typing import Dict + +import requests + +# 运行格式方式:python langAi.py youdao 备注:aliyun 阿里云 youdao 有道翻译 + +# 语言文件路径 /Users/mac/www/app-starlock/lan +path = "/Users/mac/Desktop/app-starlock/lan/" +# 原语言 +SOURCE_LANG = 'lan_en' + +# 过滤不翻译的语言 +FILTER_LANG = ['lan_zh', 'lan_en', 'lan_keys'] + +# 阿里云语言映射 +ALIYUN_LANG_MAP = { + 'lan_zh': { + 'name': '中文', + 'code': 'zh' + }, + 'lan_tw': { + 'name': '繁体中文', + 'code': 'zh-tw', + }, + 'lan_en': { + 'name': '英文', + 'code': 'en', + }, + 'lan_fr': { + 'name': '法文', + 'code': 'fr', + }, + 'lan_ru': { + 'name': '俄文', + 'code': 'ru', + }, + 'lan_de': { + 'name': '德文', + 'code': 'de', + }, + 'lan_ja': { + 'name': '日文', + 'code': 'ja', + }, + 'lan_ko': { + 'name': '韩文', + 'code': 'ko', + }, + 'lan_it': { + 'name': '意大利文', + 'code': 'it', + }, + 'lan_pt': { + 'name': '葡萄牙文', + 'code': 'pt', + }, + 'lan_es': { + 'name': '西班牙文', + 'code': 'es', + }, + 'lan_ar': { + 'name': '阿拉伯文', + 'code': 'ar', + }, + 'lan_vi': { + 'name': '越南文', + 'code': 'vi', + }, + 'lan_ms': { + 'name': '马来文', + 'code': 'ms', + }, + 'lan_nl': { + 'name': '荷兰文', + 'code': 'nl', + }, + 'lan_ro': { + 'name': '罗马尼亚文', + 'code': 'ro', + }, + 'lan_lt': { + 'name': '立陶宛文', + 'code': 'lt', + }, + 'lan_sv': { + 'name': '瑞典文', + 'code': 'sv', + }, + 'lan_et': { + 'name': '爱沙尼亚文', + 'code': 'et', + }, + 'lan_pl': { + 'name': '波兰文', + 'code': 'pl', + }, + 'lan_sk': { + 'name': '斯洛伐克文', + 'code': 'sk', + }, + 'lan_cs': { + 'name': '捷克文', + 'code': 'cs', + }, + 'lan_el': { + 'name': '希腊文', + 'code': 'el', + }, + 'lan_he': { + 'name': '希伯来文', + 'code': 'he', + }, + 'lan_tr': { + 'name': '土耳其文', + 'code': 'tr', + }, + 'lan_hu': { + 'name': '匈牙利文', + 'code': 'hu', + }, + 'lan_bg': { + 'name': '保加利亚文', + 'code': 'bg', + }, + 'lan_kk': { + 'name': '哈萨克文', + 'code': 'kk', + }, + 'lan_bn': { + 'name': '孟加拉文', + 'code': 'bn', + }, + 'lan_hr': { + 'name': '克罗地亚文', + 'code': 'hbs', + }, + 'lan_th': { + 'name': '泰文', + 'code': 'th', + }, + 'lan_id': { + 'name': '印尼文', + 'code': 'id', + }, + 'lan_fi': { + 'name': '芬兰文', + 'code': 'fi', + }, + 'lan_da': { + 'name': '丹麦文', + 'code': 'da', + }, +} + +# 有道语言映射 +YOUDAO_LANG_MAP = { + 'lan_zh': { + 'name': '中文', + 'code': 'zh-CHS', + }, + 'lan_tw': { + 'name': '繁体中文(中国台湾)', + 'code': 'zh-CHT', + }, + 'lan_hk': { + 'name': '繁体中文(中国香港)', + 'code': 'yue', + }, + 'lan_en': { + 'name': '英文', + 'code': 'en' + }, + 'lan_fr': { + 'name': '法文', + 'code': 'fr' + }, + 'lan_ru': { + 'name': '俄文', + 'code': 'ru' + }, + 'lan_de': { + 'name': '德文', + 'code': 'de' + }, + 'lan_ja': { + 'name': '日文', + 'code': 'ja' + }, + 'lan_ko': { + 'name': '韩文', + 'code': 'ko' + }, + 'lan_it': { + 'name': '意大利文', + 'code': 'it' + }, + 'lan_pt': { + 'name': '葡萄牙文', + 'code': 'pt' + }, + 'lan_es': { + 'name': '西班牙文', + 'code': 'es' + }, + 'lan_ar': { + 'name': '阿拉伯文', + 'code': 'ar' + }, + 'lan_vi': { + 'name': '越南文', + 'code': 'vi' + }, + 'lan_ms': { + 'name': '马来文', + 'code': 'ms' + }, + 'lan_nl': { + 'name': '荷兰文', + 'code': 'nl' + }, + 'lan_ro': { + 'name': '罗马尼亚文', + 'code': 'ro' + }, + 'lan_lt': { + 'name': '立陶宛文', + 'code': 'lt' + }, + 'lan_sv': { + 'name': '瑞典文', + 'code': 'sv' + }, + 'lan_et': { + 'name': '爱沙尼亚文', + 'code': 'et' + }, + 'lan_pl': { + 'name': '波兰文', + 'code': 'pl' + }, + 'lan_sk': { + 'name': '斯洛伐克文', + 'code': 'sk' + }, + 'lan_cs': { + 'name': '捷克文', + 'code': 'cs' + }, + 'lan_el': { + 'name': '希腊文', + 'code': 'el' + }, + 'lan_he': { + 'name': '希伯来文', + 'code': 'he' + }, + 'lan_tr': { + 'name': '土耳其文', + 'code': 'tr' + }, + 'lan_hu': { + 'name': '匈牙利文', + 'code': 'hu' + }, + 'lan_bg': { + 'name': '保加利亚文', + 'code': 'bg' + }, + 'lan_kk': { + 'name': '哈萨克文', + 'code': 'kk' + }, + 'lan_bn': { + 'name': '孟加拉文', + 'code': 'bn' + }, + 'lan_hr': { + 'name': '克罗地亚文', + 'code': 'hr' + }, + 'lan_th': { + 'name': '泰文', + 'code': 'th' + }, + 'lan_id': { + 'name': '印尼文', + 'code': 'id' + }, + 'lan_fi': { + 'name': '芬兰文', + 'code': 'fi' + }, + 'lan_da': { + 'name': '丹麦文', + 'code': 'da' + }, + 'lan_uk': { + 'name': '乌克兰文', + 'code': 'uk' + }, + 'lan_sr_cyrl': { + 'name': '塞尔维亚语(西里尔文)', + 'code': 'sr-Cyrl' + }, + # 'lan_sr_latn': { + # 'name': '塞尔维亚语(拉丁文)', + # 'code': 'sr-Latn' + # }, +} + +# =============================== 以下为固定代码,非必要请勿修改 =============================== + +# 阿里云配置 +ALIYUN_CONFIG = { + 'domain': 'mt.cn-hangzhou.aliyuncs.com/api/translate/web/ecommerce', + 'accessKeyId': 'LTAI5tFke4yGaY94ondXkmHn', + 'accessKeySecret': '08zPw8BoRqfqlKDTMX65k7gfeER33x', +} + +# 有道翻译配置 +YOUDAO_CONFIG = { + 'domain': 'https://openapi.youdao.com/api', + 'appKey': '3e4923bad470f8d2', + 'appSecret': '5thWwZ7D0RIzT0aquCF6NKmaiakAyhqr' +} + +CHANNELS = ['aliyun', 'youdao'] + +if len(sys.argv) < 2: + print('Usage: python langAi.py aliLang') + sys.exit(1) + +# 检测传入参数是否合法 +if sys.argv[1] not in CHANNELS: + print('channel must be one of {}'.format(CHANNELS)) + sys.exit(1) + + +# 主函数 +def main(channel): + en_path = path + SOURCE_LANG + ".json" + # 判断文件是否存在 + if not os.path.exists(en_path): + print('文件不存在:', en_path) + sys.exit(1) + + # 读取文件内容 + en_us_content = read_file(en_path) + + # 把json转数组 + en_us_data = json.loads(en_us_content) + + # 语言列表 如果channel为aliyun赋值ALIYUN_LANG_MAP,否则赋值YOUDAO_LANG_MAP + lang_map = ALIYUN_LANG_MAP if channel == 'aliyun' else YOUDAO_LANG_MAP + + # 遍历ALIYUN_LANG_MAP + for key in lang_map: + # 过滤掉不翻译的语言 + if key in FILTER_LANG: + continue + + lang_path = path + key + ".json" + # 验证文件是否存在,如果不存在即创建 + if not os.path.exists(lang_path): + create_file(lang_path) + + data_content = read_file(lang_path) + if data_content.strip(): # Check if the file is not empty + datas = json.loads(data_content) + else: + datas = {} + + total = len(en_us_data) # 总数 + current = 0 # 当前数 + + for en_us_key in en_us_data: + current += 1 + print('语言:%s-%s 正在翻译处理第%d/%d批数据' % (key,lang_map[key]['code'], current, total)) + # 过滤已存在的key + if en_us_key in datas: + continue + + # 异常处理 + try: + datas[en_us_key] = translation(channel, lang_map[SOURCE_LANG]['code'], lang_map[key]['code'], + en_us_data[en_us_key]) + except Exception as e: + print('翻译异常:', e.args, 'key:', en_us_key, 'content:', en_us_data[en_us_key]) + continue + + # 写入文件 + with open(lang_path, 'w', encoding='utf-8') as file: + json.dump(datas, file, ensure_ascii=False, indent=4) + + +# 翻译 +def translation(channel, source_language, target_language, text): + translation_text = '' + if channel == 'aliyun': + translation_text = aliyun_translation(source_language, target_language, text) + elif channel == 'youdao': + translation_text = youdao_translation(source_language, target_language, text) + # 延迟1秒 + time.sleep(1) + + return translation_text + + +# 有道翻译 +def youdao_translation(source_language, target_language, text): + post_body = { + 'q': text, + 'from': source_language, + 'to': target_language, + 'appKey': YOUDAO_CONFIG['appKey'], + 'salt': hashlib.md5(os.urandom(32)).hexdigest(), # 随机字符串,可使用UUID进行生产 + 'signType': 'v3', + 'curtime': str(int(datetime.datetime.now().timestamp())), + } + + # 签名生成方法如下: + # signType=v3; + # sign=sha256(应用ID+input+salt+curtime+应用密钥); + # 其中,input的计算方式为:input=q前10个字符 + q长度 + q后10个字符(当q长度大于20)或 input=q字符串(当q长度小于等于20); + + input_text = post_body['q'] + if len(input_text) > 20: + input_text = input_text[:10] + str(len(input_text)) + input_text[-10:] + post_body['input'] = input_text + + sign = hashlib.sha256((YOUDAO_CONFIG['appKey'] + input_text + post_body['salt'] + post_body['curtime'] + + YOUDAO_CONFIG['appSecret']).encode('utf-8')).hexdigest() + + post_body['sign'] = sign + + req = fetch_content(YOUDAO_CONFIG['domain'], 'POST', post_body) + req = json.loads(req) + return req['translation'][0] + + +# 阿里云翻译 +def aliyun_translation(source_language, target_language, text): + headers = { + 'Version': '2018-10-12', + } + post_body = { + "FormatType": "text", + "SourceLanguage": source_language, + "TargetLanguage": target_language, + "SourceText": text, + "Ccene": "general", + 'Action': 'TranslateGeneral', + } + req = aliyun_request(ALIYUN_CONFIG['domain'], {**post_body, **headers}) + if req['Code'] == '200': + return req['Data']['Translated'] + else: + raise Exception(req) + + +def aliyun_request(domain: str, params: Dict, security: bool = True, + method: str = 'POST') -> Dict: + # Add common parameters + api_params = { + "SignatureMethod": "HMAC-SHA1", + "SignatureNonce": hashlib.md5(os.urandom(32)).hexdigest(), + "SignatureVersion": "1.0", + "AccessKeyId": ALIYUN_CONFIG['accessKeyId'], + "Timestamp": datetime.datetime.utcnow().isoformat("T"), + "Format": "JSON", + } + api_params.update(params) + + # Sort parameters by key + sorted_params = sorted(api_params.items(), key=lambda x: x[0]) + + sorted_query_string_tmp = "" + for key, value in sorted(sorted_params): + sorted_query_string_tmp += "&" + aliyun_encode(key) + "=" + aliyun_encode(value) + + string_to_sign = "{}&%2F&{}".format(method, aliyun_encode(sorted_query_string_tmp[1:])) + + # Calculate the signature + signature = base64.b64encode( + hmac.new((ALIYUN_CONFIG['accessKeySecret'] + "&").encode('utf-8'), string_to_sign.encode('utf-8'), + hashlib.sha1).digest()).decode( + 'utf-8') + + # Add the signature to the parameters + api_params['Signature'] = signature + + # Send the request + url = ("https" if security else "http") + "://" + domain + "/" + if method == 'POST': + response = requests.post(url, data=api_params) + else: + response = requests.get(url, params=api_params) + return response.json() + + +# 阿里云编码 +def aliyun_encode(value): + res = urllib.parse.quote_plus(value) + res = res.replace("+", "%20") + res = res.replace("*", "%2A") + res = res.replace("%7E", "~") + return res + + +def fetch_content(url, method, body): + headers = {"x-sdk-client": "python/2.0.0"} + + if method == 'POST': + response = requests.post(url, data=body, headers=headers) + else: + response = requests.get(url, params=body, headers=headers) + + if response.status_code != 200: + raise Exception(f"Request failed with status {response.status_code}") + + return response.text + + +# 创建文件 +def create_file(file_path): + with open(file_path, 'w') as file: + pass + + +# 读取文件内容 +def read_file(file_path): + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + return content + + +# translation_text = youdao_translation('en', 'de', 'Your application <:name> has been approved.') +# print(translation_text) +main(sys.argv[1]) From c0b2114683c497deaa22176440f022db0902cc24 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 12 Jun 2025 16:35:55 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=EF=BC=88=E5=AF=B9=E8=AE=B2=E9=93=83?= =?UTF-8?q?=E5=A3=B0=EF=BC=89=E8=B0=83=E6=95=B4=E8=B7=AF=E7=94=B1=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=98=B5=E7=A7=B0;=E8=B0=83=E6=95=B4=E9=93=83?= =?UTF-8?q?=E5=A3=B0=E6=92=AD=E6=94=BE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/appRouters.dart | 6 +++--- lib/talk/other/audio_player_manager.dart | 16 ++++++++++++++-- .../handle/impl/udp_talk_expect_handler.dart | 8 ++++---- lib/talk/starChart/star_chart_manage.dart | 2 +- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/appRouters.dart b/lib/appRouters.dart index 495b54c7..6ff7ac91 100755 --- a/lib/appRouters.dart +++ b/lib/appRouters.dart @@ -516,7 +516,7 @@ abstract class Routers { static const String doubleLockLinkPage = '/doubleLockLinkPage'; //双锁联动 static const String starChartPage = '/starChartPage'; //星图 static const String starChartTalkView = '/starChartTalkView'; //星图对讲页面 - static const String h264WebView = '/h264WebView'; //星图对讲页面 + static const String h264View = '/h264View'; //星图对讲页面 static const String imageTransmissionView = '/imageTransmissionView'; //星图对讲页面(图传) static const String permissionGuidancePage = @@ -1192,7 +1192,7 @@ abstract class AppRouters { GetPage( name: Routers.starChartTalkView, page: () => const TalkViewPage()), GetPage( - name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), + name: Routers.h264View, page: () => TalkViewNativeDecodePage()), // 插件播放页面 GetPage( name: Routers.imageTransmissionView, @@ -1202,6 +1202,6 @@ abstract class AppRouters { name: Routers.permissionGuidancePage, page: () => PermissionGuidancePage()), // 插件播放页面 - // GetPage(name: Routers.h264WebView, page: () => H264WebView()), // webview播放页面 + // GetPage(name: Routers.h264View, page: () => H264WebView()), // webview播放页面 ]; } diff --git a/lib/talk/other/audio_player_manager.dart b/lib/talk/other/audio_player_manager.dart index 2c7c5fb9..d49db782 100644 --- a/lib/talk/other/audio_player_manager.dart +++ b/lib/talk/other/audio_player_manager.dart @@ -16,14 +16,22 @@ class AudioPlayerManager { // 单例的 AudioPlayer 实例 final AudioPlayer _audioPlayer = AudioPlayer(); + // 播放状态,防止重复播放 + bool _isPlaying = false; // 播放铃声 Future playRingtone() async { + if (_isPlaying) { + log(text: 'Ringtone is already playing.'); + return; + } try { await _audioPlayer.setReleaseMode(ReleaseMode.loop); await _audioPlayer.play(AssetSource('ring1.mp3')); + _isPlaying = true; log(text: 'Ringtone started playing.'); } catch (e) { + _isPlaying = false; log(text: 'Error playing ringtone: $e'); } } @@ -31,12 +39,12 @@ class AudioPlayerManager { // 停止播放铃声 Future stopRingtone() async { try { - // 不需要设置 ReleaseMode,在停止时保持默认即可 _audioPlayer.setReleaseMode(ReleaseMode.loop); await _audioPlayer.stop(); - + _isPlaying = false; log(text: 'Ringtone stopped.'); } catch (e) { + _isPlaying = false; log(text: 'Error stopping ringtone: $e'); } } @@ -45,8 +53,10 @@ class AudioPlayerManager { Future pauseRingtone() async { try { await _audioPlayer.pause(); + _isPlaying = false; log(text: 'Ringtone paused.'); } catch (e) { + _isPlaying = false; log(text: 'Error pausing ringtone: $e'); } } @@ -54,8 +64,10 @@ class AudioPlayerManager { Future resumeRingtone() async { try { await _audioPlayer.resume(); + _isPlaying = true; log(text: 'Ringtone resumed.'); } catch (e) { + _isPlaying = false; log(text: 'Error resuming ringtone: $e'); } } diff --git a/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart index d500fd44..c592b454 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart @@ -41,9 +41,6 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle final TalkExpectResp talkExpectResp = scpMessage.Payload; if (talkExpectResp != null) { - // 播放铃声 - //test:使用自定义铃声 - playRingtone(); // print('收到预期音视频数据回复,scpMessage:$scpMessage'); // 停止发送预期数据的定时器 startChartManage.stopTalkExpectMessageTimer(); @@ -66,6 +63,8 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle talkStatus.setAnsweredSuccessfully(); // 主动呼叫时需要启动ping startChartManage.startTalkPingMessageTimer(); + } else if (talkStatus.status != TalkStatus.answeredSuccessfully) { + playRingtone(); } AppLog.log( '视频画面需要旋转:${talkExpectResp.rotate},画面宽高:${talkExpectResp.width}-${talkExpectResp.height}'); @@ -97,6 +96,7 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle talkExpectResp.width == 640 && talkExpectResp.height == 480)) { Get.toNamed(Routers.imageTransmissionView); + return; } if (startChartManage @@ -104,7 +104,7 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle .videoType .contains(VideoTypeE.H264)) { Get.toNamed( - Routers.h264WebView, + Routers.h264View, ); } else { Get.toNamed( diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart index 2d8b4529..7a185c47 100644 --- a/lib/talk/starChart/star_chart_manage.dart +++ b/lib/talk/starChart/star_chart_manage.dart @@ -430,7 +430,7 @@ class StartChartManage { // 优先使用H264,其次是MJPEG if (isH264) { Get.toNamed( - Routers.h264WebView, + Routers.h264View, ); } else if (isMJpeg) { Get.toNamed(