/* real time subtitle translate for PotPlayer using Bai Du API */ // string GetTitle() -> get title for UI // string GetVersion -> get version for manage // string GetDesc() -> get detail information // string GetLoginTitle() -> get title for login dialog // string GetLoginDesc() -> get desc for login dialog // string ServerLogin(string User, string Pass) -> login // string ServerLogout() -> logout // array GetSrcLangs() -> get source language // array GetDstLangs() -> get target language // string Translate(string Text, string &in SrcLang, string &in DstLang) -> do translate !! //必须配置的部分,不过现在已经移交到“实时字幕翻译”中了 //它的位置是: 打开任意视频或者点击左上角的PolPlayer -> 字幕 -> 实时字幕翻译 -> 实时字幕翻译设置 -> 选中百度翻译 -> 点右边的 “账户设置” string appId = "";//appid string toKey = "";//密钥 //可选配置,一般而言是不用修改的! int coolTime = 1000;//冷却时间,这里的单位是毫秒,1秒钟=1000毫秒,如果提示 error:54003, 那么就加大这个数字,建议一次加100 string userAgent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36";//这个是可选配置,一般不用修改! //执行环境,请不要修改! int NULL = 0; int executeThreadId = NULL;//这个变量的命名是我的目标,不过,暂时没能实现!只是做了个还有小bug的临时替代方案 int nextExecuteTime = 0;//下次执行代码的时间 string GetVersion(){ return "1"; } string GetTitle(){ return "{$CP950=Bai Du 翻譯$}{$CP0=Bai Du translate$}"; } string GetDesc(){ return "https://fanyi.baidu.com/"; } string GetLoginTitle(){ return "请输入配置"; } string GetLoginDesc(){ return "请输入AppId和密钥!"; } string GetUserText(){ return "App ID:"; } string GetPasswordText(){ return "密钥:"; } array GetSrcLangs(){ array ret = GetLangTable(); ret.insertAt(0, ""); // empty is auto return ret; } array GetDstLangs(){ return GetLangTable(); } string ServerLogin(string appIdStr, string toKeyStr){ if (appIdStr.empty() || toKeyStr.empty()) return "fail"; appId = appIdStr; toKey = toKeyStr; return "200 ok"; } string Translate(string text, string &in srcLang, string &in dstLang){ string ret = ""; if(!text.empty()){//确实有内容需要翻译才有必要继续 //开发文档。需要App id 等信息 //http://api.fanyi.baidu.com/api/trans/product/apidoc // HostOpenConsole(); // for debug //语言选择 srcLang = GetLang(srcLang); dstLang = GetLang(dstLang); // API.. Always UTF-8 string q = HostUrlEncode(text); string salt = "" + HostGetTickCount();//随机数 string sign = HostHashMD5(appId + text + salt + toKey);//签名 appid+q+salt+密钥 string parames = "from=" + srcLang + "&to=" + dstLang + "&appid=" + appId + "&sign=" + sign + "&salt=" + salt + "&q=" + q; string url = "http://api.fanyi.baidu.com/api/trans/vip/translate?" + parames; //线程同步 - 独占锁 acquireExclusiveLock(); //计算冷却时间,应百度翻译新版API要求,加入频率设定 int tickCount = HostGetTickCount(); int sleepTime = nextExecuteTime - tickCount; // HostPrintUTF8("tickCount == " + tickCount + " sleepTime == " + sleepTime);// for debug if(sleepTime > 0){//如果冷却时间还没到,有需要休息的部分 HostSleep(sleepTime);//那么就休息这些时间 } // HostPrintUTF8("url == " + url);// for debug string html = HostUrlGetString(url, userAgent); //更新下次执行任务的时间 nextExecuteTime = coolTime + HostGetTickCount();//上面 HostUrlGetString 需要时间执行,所以需要重新获取 TickCount //线程同步 - 释放独占锁 releaseExclusiveLock(); if(!html.empty()){//如果成功取得 Html 内容 ret = JsonParse(html);//那么解析这个 HTML 里面的 json 内容 } if (ret.length() > 0){//如果有翻译结果 srcLang = "UTF8"; dstLang = "UTF8"; } if(text == ret){//如果翻译后的译文,跟原文一致 ret = "";//那么忽略这个字幕 } } return ret; } //获取语言 string GetLang(string &in lang){ string result = lang; if(result.empty()){//空字符串 result = "auto"; } else if(result == "zh-CN"){//简体中文 result = "zh"; } else if(result == "zh-TW"){//繁体中文 result = "cht"; } else if(result == "ja"){//日语 result = "jp"; } else if(result == "ro"){//罗马尼亚语 result = "rom"; } return result; } array langTable = { "zh-CN",//->zh "zh-TW",//->cht "en", "ja",//->jp "kor", "fra", "spa", "th", "ara", "ru", "pt", "de", "it", "el", "nl", "pl", "bul", "est", "dan", "fin", "cs", "ro",//->rom "slo", "swe", "hu", "vie" "yue",//粤语 "wyw",//文言文 }; //获取支持语言 array GetLangTable(){ return langTable; } //解析Json数据 string JsonParse(string json){ string ret = "";//返回值 JsonReader reader; JsonValue root; if (reader.parse(json, root)){//如果成功解析了json内容 if(root.isObject()){//要求是对象模式 bool hasError = false; array keys = root.getKeys();//获取json root对象中所有的key //查找是否存在错误 for(uint i = 0; i < keys.size(); i++){ if("error_code" == keys[i]){ hasError = true; break; } } if(hasError){//如果发生了错误 JsonValue errorCode = root["error_code"];//错误编号 JsonValue errorMsg = root["error_msg"];//错误信息描述 ret = "error: " + errorCode.asString() + ", error_msg=" + errorMsg.asString(); }else{//如果没发生错误 JsonValue transResult = root["trans_result"];//取得翻译结果 if(transResult.isArray()){//如果有翻译结果-必须是数组形式 for(int i = 0; i < transResult.size(); i++){ JsonValue item = transResult[i];//取得翻译结果 JsonValue dst = item["dst"];//获取翻译结果的目标 if(i > 0){//如果需要处理多行的情况 ret += "\n";//第二行开始的开头位置,加上换行符 } ret += dst.asString();//拼接翻译结果,可能存在多行 } } } } } return ret; } /** 上独占锁 - 当前仅仅只是模拟版,还有 bug ,不过暂时可临时使用 */ void acquireExclusiveLock(){ int tickCount1 = HostGetTickCount();//取得第一个时刻 HostSleep(1); int tickCount2 = HostGetTickCount();//取得第二个时刻 /** 注意: 1、这是一个临时的方案 2、因为我本地尝试:HostLoadLibrary("Kernel32.dll") 没能正常工作,所以才采用当前这个临时方案 3、key 原本应该是唯一的,不然可能存在多个线程得到的是同一个tickCount。会导致多个线程同时执行,意味着这多个线程只能成功一个翻译,虽然已经做了部分防御,但是不能确保万一! 4、当然,上方的触发的概率不高,不过确实存在这个bug。 5、所以当前只能作为临时方案,有更好的方案时,必须替换掉 */ int key = tickCount1 << 16 + (tickCount2 & 0xFFFF);//两个时刻合并,使得多线程重复相同数字的概率下降,但还是有可能重复,当前这个算法,仅仅能作为临时的解决方案而已! while(executeThreadId != key){ if(executeThreadId == NULL){//如果没其他任务在执行了 executeThreadId = key;//尝试注册当前任务为执行任务 } HostSleep(1);//休息下,看看有没有抢着注册的其他线程任务,或者等待正在执行的任务解除锁 if(executeThreadId == key){//如果没被其他线程抢注册了 HostSleep(1);//再次休息下 if(executeThreadId == key){//二次确认,确保原子性 break;//成功抢到执行权限,不必再等待了 } } } } /** 释放独占锁 - 当前仅仅只是模拟版,还有 bug ,不过暂时可临时使用 */ void releaseExclusiveLock(){ executeThreadId = NULL;//解除锁 }