自动发帖软件
标题:
AIWROK软件自动文字转语音例子
[打印本页]
作者:
发帖软件
时间:
2 小时前
标题:
AIWROK软件自动文字转语音例子
AIWROK软件自动文字转语音例子
3.png
(1.29 MB, 下载次数: 0)
2 小时前
上传
/**
* ============================================================================
* AIWROK TTS 文件⑤:text-to-speech.cn(微软 Azure 神经网络音色·免登录)
* ============================================================================
* 来源:https://www.text-to-speech.cn/
* 网站后端代理微软 Azure TTS,音色和 Edge-TTS 完全一样
* 但用 HTTP POST 代替 WebSocket,AIWROK 兼容性更好
*
* 音质:⭐⭐⭐⭐⭐ 和豆包桃子一样自然,无机器感
*
* 中文女声推荐:
* zh-CN-XiaoxiaoNeural 晓晓 最自然(默认)
* zh-CN-XiaochenNeural 晓辰 年轻女声
* zh-CN-XiaorouNeural 晓柔 温柔
* zh-CN-XiaohanNeural 晓涵 温柔
* zh-CN-XiaomengNeural 晓梦 年轻女声
* zh-CN-XiaoyiNeural 晓伊 女童
* 中文男声:
* zh-CN-YunxiNeural 云希 年轻男声
* zh-CN-YunjianNeural 云健 成年男声
*
* 用法:
* ttsWeb("你好呀");
* ttsWeb("你好呀", "zh-CN-XiaorouNeural"); // 晓柔
*
* 文件保存:/sdcard/Pictures/aiwrok_tts/
* ============================================================================
*/
importClass(Packages.android.media.MediaPlayer);
importClass(Packages.java.io.File);
importClass(Packages.java.io.FileInputStream);
importClass(Packages.java.io.FileOutputStream);
importClass(Packages.java.lang.Thread);
importClass(Packages.javax.net.ssl.HttpsURLConnection);
importClass(Packages.java.net.URL);
importClass(Packages.java.io.BufferedReader);
importClass(Packages.java.io.InputStreamReader);
importClass(Packages.java.io.OutputStream);
var WEB_CFG = {
cacheDir: "/sdcard/Pictures/aiwrok_tts/",
// 默认音色:晓晓(最自然)
voice: "zh-CN-XiaoxiaoNeural",
// 语言值必须是中文全称(网站要求)
language: "中文(普通话,简体)",
// 音质:audio-16khz-32kbitrate-mono-mp3 / audio-24khz-160kbitrate-mono-mp3 / audio-48khz-192kbitrate-mono-mp3
kbitrate: "audio-24khz-160kbitrate-mono-mp3",
// 语速 / 音调
rate: "0",
pitch: "0",
// 音量(0-100)
volume: "75",
// 情感:0=默认
style: "0",
styledegree: "1",
// 其他参数
role: "0",
silence: "",
predict: "0",
// 验证码(网站固定值)
yzm: "202410170001",
// 单次最大字数
maxLen: 200,
// 两个站点(互为备用)
sites: [
{
page: "https://www.text-to-speech.cn/",
api: "https://www.text-to-speech.cn/getSpeek.php"
},
{
page: "http://new.text-to-speech.cn/tts/",
api: "http://new.text-to-speech.cn/wp-content/plugins/speech-tts/getSpeek.php"
}
]
};
function ensureDir() {
var d = new File(WEB_CFG.cacheDir);
if (!d.exists()) d.mkdirs();
}
function isValidAudio(p) {
try {
var f = new File(p);
if (!f.exists() || f.length() < 512) return false;
var fis = new FileInputStream(p);
var h = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 3);
var r = fis.read(h);
fis.close();
if (r < 2) return false;
var b0 = h[0] & 0xFF, b1 = h[1] & 0xFF;
if (b0 == 0x49 && b1 == 0x44) return true;
if (b0 == 0xFF && (b1 & 0xE0) == 0xE0) return true;
return false;
} catch (e) { return false; }
}
function splitText(text, maxLen) {
var segs = [];
if (!text) return segs;
text = String(text);
var parts = text.split(/([。!?!?,,;;\n\r])/);
var cur = "";
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
if (p == null) continue;
if (cur.length + p.length > maxLen) {
if (cur.length > 0) { segs.push(cur); cur = ""; }
if (p.length > maxLen) {
for (var j = 0; j < p.length; j += maxLen) {
segs.push(p.substring(j, Math.min(j + maxLen, p.length)));
}
} else { cur = p; }
} else { cur += p; }
}
if (cur.length > 0) segs.push(cur);
return segs;
}
/**
* 从页面 HTML 中提取 nonce 和 token
*/
function getPageAuth(pageUrl) {
var conn = null;
var reader = null;
try {
var url = new URL(pageUrl);
conn = url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
conn.setConnectTimeout(15000);
conn.setReadTimeout(30000);
conn.setUseCaches(false);
var code = conn.getResponseCode();
if (code != 200) return null;
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
var sb = new java.lang.StringBuilder();
var line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
var html = String(sb.toString());
// 提取 token(网站在 script 标签里用 const token = 'xxx' 定义)
var token = "";
var tokenPatterns = [
/const\s+token\s*=\s*['"]([a-zA-Z0-9:_\-]+)['"]/i,
/var\s+token\s*=\s*['"]([a-zA-Z0-9:_\-]+)['"]/i,
/["']token["']\s*[:=]\s*["']([a-zA-Z0-9:_\-]+)["']/i
];
for (var i = 0; i < tokenPatterns.length; i++) {
var m = html.match(tokenPatterns[i]);
if (m && m[1]) { token = m[1]; break; }
}
// 站点2可能需要 nonce(WordPress)
var nonce = "";
var noncePatterns = [
/["']nonce["']\s*[:=]\s*["']([a-f0-9]{6,32})["']/i,
/nonce\s*[:=]\s*["']([a-f0-9]{6,32})["']/i
];
for (var i = 0; i < noncePatterns.length; i++) {
var m = html.match(noncePatterns[i]);
if (m && m[1]) { nonce = m[1]; break; }
}
return { nonce: nonce, token: token };
} catch (e) {
printl(" ↳ 获取页面认证信息失败: " + e.message);
return null;
} finally {
if (reader != null) { try { reader.close(); } catch (e) {} }
if (conn != null) { try { conn.disconnect(); } catch (e) {} }
}
}
/**
* POST 到 getSpeek.php 获取 TTS 音频
*/
function ttsApiCall(apiUrl, text, voice, auth) {
var conn = null;
var reader = null;
var os = null;
try {
// 构建表单数据(参数名和值必须与网站完全一致)
var params = "language=" + encodeURIComponent(WEB_CFG.language) +
"&voice=" + encodeURIComponent(voice) +
"&text=" + encodeURIComponent(text) +
"&role=" + WEB_CFG.role +
"&style=" + WEB_CFG.style +
"&rate=" + WEB_CFG.rate +
"&pitch=" + WEB_CFG.pitch +
"&kbitrate=" + encodeURIComponent(WEB_CFG.kbitrate) +
"&silence=" + WEB_CFG.silence +
"&styledegree=" + WEB_CFG.styledegree +
"&volume=" + WEB_CFG.volume +
"&predict=" + WEB_CFG.predict +
"&user_id=" +
"&yzm=" + WEB_CFG.yzm +
"&replice=1";
if (auth && auth.nonce) params += "&nonce=" + auth.nonce;
if (auth && auth.token) params += "&token=" + encodeURIComponent(auth.token);
var url = new URL(apiUrl);
conn = url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
conn.setRequestProperty("Accept", "application/json, text/plain, */*");
conn.setRequestProperty("Referer", apiUrl.replace("/getSpeek.php", "/"));
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(15000);
conn.setReadTimeout(30000);
conn.setUseCaches(false);
// 发送请求体(Rhino 的 JS String 没有 getBytes,要用 java.lang.String)
os = conn.getOutputStream();
var jParams = new java.lang.String(params);
os.write(jParams.getBytes("UTF-8"));
os.flush();
os.close();
os = null;
var code = conn.getResponseCode();
var inputStream;
if (code >= 400) {
inputStream = conn.getErrorStream();
} else {
inputStream = conn.getInputStream();
}
if (inputStream == null) return null;
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
var sb = new java.lang.StringBuilder();
var line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
var response = String(sb.toString());
return { code: code, body: response };
} catch (e) {
printl(" ↳ API 请求失败: " + e.message);
return null;
} finally {
if (os != null) { try { os.close(); } catch (e) {} }
if (reader != null) { try { reader.close(); } catch (e) {} }
if (conn != null) { try { conn.disconnect(); } catch (e) {} }
}
}
/**
* 从 JSON 响应中提取下载 URL
*/
function parseDownloadUrl(jsonStr) {
try {
// Rhino 的 JSON.parse 可能不可用,用正则提取
// 格式: {"code":200,"download":"https://...mp3","srt":""}
var codeMatch = jsonStr.match(/"code"\s*:\s*(\d+)/);
if (!codeMatch || codeMatch[1] != "200") {
printl(" ↳ API 返回错误: " + jsonStr.substring(0, 200));
return null;
}
var dlMatch = jsonStr.match(/"download"\s*:\s*"([^"]+)"/);
if (dlMatch && dlMatch[1]) return dlMatch[1];
return null;
} catch (e) {
printl(" ↳ 解析响应失败: " + e.message);
return null;
}
}
/**
* 下载 MP3 文件(带重试)
*/
function downloadMp3(url, savePath) {
for (var retry = 0; retry < 3; retry++) {
try {
new File(savePath)["delete"]();
var client = new okHttp();
client.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
client.downloadFile(url, savePath);
if (isValidAudio(savePath)) return true;
} catch (e) {
Thread.sleep(1000);
}
if (retry < 2) Thread.sleep(1000);
}
return false;
}
/**
* 合成单段文字(多次重试,401时等待后刷新token)
*/
function synthSegment(text, voice, savePath) {
for (var retry = 0; retry < 4; retry++) {
for (var s = 0; s < WEB_CFG.sites.length; s++) {
var site = WEB_CFG.sites[s];
// 1. 每次都重新获取 token(token 会过期)
var auth = getPageAuth(site.page);
if (!auth) {
continue;
}
// 2. 调用 API
var resp = ttsApiCall(site.api, text, voice, auth);
if (!resp || resp.code != 200) {
continue;
}
// 3. 解析下载 URL
var dlUrl = parseDownloadUrl(resp.body);
if (!dlUrl) {
// 401 错误:token 过期,等 3 秒再试
if (resp.body && resp.body.indexOf("401") >= 0) {
printl(" ↳ token 过期,等待 3 秒后重试...");
Thread.sleep(3000);
}
continue;
}
// 4. 下载 MP3
if (downloadMp3(dlUrl, savePath)) {
return true;
}
}
// 重试前等待
if (retry < 3) {
var wait = (retry + 1) * 2000; // 2s, 4s, 6s 递增
printl(" ↳ 第 " + (retry + 1) + " 次重试(等待 " + (wait / 1000) + "s)...");
Thread.sleep(wait);
}
}
return false;
}
/**
* 文字转语音(主函数)
* @param text 文本
* @param voice 音色名(默认晓晓)
* @return 成功段数
*/
function ttsWeb(text, voice) {
voice = voice || WEB_CFG.voice;
if (!text || String(text).length === 0) { printl("⚠️ 文本为空"); return 0; }
ensureDir();
var segs = splitText(text, WEB_CFG.maxLen);
printl("🔊 [text-to-speech.cn] 音色:" + voice + " 共 " + segs.length + " 段");
var okCount = 0;
for (var i = 0; i < segs.length; i++) {
var seg = segs[i];
var tag = seg.substring(0, 10).replace(/[\\/:*?"<>|]/g, "_");
var savePath = WEB_CFG.cacheDir + "web_" + voice.replace(/[^a-zA-Z0-9]/g, "").substring(0, 15) +
"_" + (i + 1) + "_" + Date.now() + ".mp3";
printl("[" + (i + 1) + "/" + segs.length + "] " + seg.substring(0, 25) + "...");
if (synthSegment(seg, voice, savePath)) {
var f = new File(savePath);
printl(" ✅ " + f.length() + " bytes");
okCount++;
} else {
printl(" ❌ 合成失败");
}
if (i < segs.length - 1) Thread.sleep(1500);
}
printl("✅ 完成 (" + okCount + "/" + segs.length + " 段)");
return okCount;
}
// ======================== 演示 ========================
function demo() {
printl("========================================");
printl(" ⑤ text-to-speech.cn TTS 演示");
printl(" 微软 Azure 神经网络音色·免登录");
printl("========================================");
// 测试短句
printl("\n【晓晓 - 默认】");
ttsWeb("你好呀,我是晓晓,一个温暖文静的声音。今天的风很温柔,适合慢慢聊天。");
Thread.sleep(500);
printl("\n【晓柔 - 温柔】");
ttsWeb("你好,我是晓柔,说话轻轻的,像在你耳边讲故事一样。", "zh-CN-XiaorouNeural");
Thread.sleep(500);
printl("\n【晓辰 - 年轻女声】");
ttsWeb("你好,我是晓辰,一个阳光的年轻女孩声音,很高兴认识你哦。", "zh-CN-XiaochenNeural");
Thread.sleep(500);
printl("\n【云希 - 年轻男声】");
ttsWeb("你好,我是云希,一个年轻男声,希望你喜欢。", "zh-CN-YunxiNeural");
printl("\n演示结束。MP3 都在 /sdcard/Pictures/aiwrok_tts/ 目录下。");
}
// ======================== 裕泰茶馆全文朗读 ========================
function readArticle() {
var ARTICLE = [
"光绪末年,北京的裕泰茶馆的秋阳总斜着扫过八仙桌,茶碗里的茉莉浮浮沉沉。掌柜王利发攥着布巾擦柜台,眼睛瞅着门,耳朵却撒得满茶馆都是,这地方,三教九流聚聚着,闲话里藏着买卖,叹气里裹着门路。",
"这天晌午,绸缎庄的秦二爷一屁股坐下,茶没端就叹气:晦气,新到的苏杭绸缎压了半仓,这年头兵荒马乱的,谁还有闲钱做什么新衣裳呀,再撂俩月,怕是要生霉喂虫子去了。",
"话音刚落,跑码头的李把头掀帘子进来,灌了大碗凉茶:您那愁?我从南边运来了百斤好药材,找不着下家,码头堆一天扣一天的钱,再耗下去,我得把裤腰带当出去算了。",
"角落开饭庄的张胖子也拍着大腿搭腔:都别喊苦了,我那饭庄刚换了金字招牌,想给伙计做身整齐的衣裳,还想寻点正经好羊肉做招牌菜,跑了半月,没着落呀,眼看就要开张了,急得一满嘴是泡。",
"王利发手里擦着茶碗,慢悠悠的插了话:唉,我当多大的难处,秦二爷,您那绸缎,张掌柜正愁没处买,张掌柜您要的羊肉,街西口卖肉的马大哥,昨儿还在我这念叨囤了好货出不去,李把头您那药材,对过德仁掌柜,前儿还托我打听南边的新货哪有。",
"三句话落地,仨人先是愣了神,紧跟着眼睛都亮了,当场就凑到一桌子,您让几分利,我担点运费,没半个时辰,三桩买卖全说妥了,秦二爷清了库存,李把头卸包袱,张胖子凑了开张的家当,个个喜颜开,临走每人都塞给王利发一块碎银子,说是喝茶的钱。",
"靠窗坐着的孙秀才,穿得打补丁长衫,天天来茶馆蹭座位抄书,张嘴就是寒窗苦读,凭真才实学立身,这会儿他推了推破眼镜,酸溜溜的说,张掌柜,您这算是什么本事?不过是耍耍嘴皮子牵线,投机取巧罢了,哪像我,日日苦读,靠的是真本事吃饭,才踏实呀。",
"王利发也不恼,给他添了半碗热茶,笑着说:孙先生,您努力是真的,可您细想哦,这天下的本事,您学得完吗?您既不会织绸缎,也不会跑码头,边挑羊肉都不会,总不能样样都自己攥在手里吧?",
"我这小茶馆,赚得不光是茶钱,也是一个方便的钱,谁手里有闲货,谁心里有难处,我听着记着,到时候两头凑到一块儿,大家都省了力气,我也落个中间费,您天天闷头抄书,总想着自己成天大的事儿,可连街对面的药铺缺啥药材都不知道,这努力有啥用,不就走歪了跑。",
"孙秀才张了张嘴,半天没说出话来。",
"转眼过了半年,秦二爷的绸缎庄开了分号,李把头的货栈雇了仨伙计,张胖子的饭庄成了京城小有名气的馆子。仨人逢年过节都来给王利发送礼,裕泰茶馆也翻修了门面,比原先敞亮不少。",
"只有孙秀才,还是天天抱着旧书抄,嘴里念叨着怀才不遇,长衫上的补丁反倒又多了两块。",
"后来有人跟王利发讨教发财的诀窍,他总摆着手笑:哪有什么诀窍?我既没金山也没银矿,就是认得人多,知道谁有啥、谁缺啥,给人搭个桥罢了。这世上的钱,从来不是攥在自己手里才叫钱。你要是能成了那搭桥的人,各色价值都得从你这儿过,财富哪里还用追?它自然就往你这儿流。",
"茶客们听了,有的点头,有的撇嘴,茶碗盖一掀,茉莉花香又飘满了一屋子。"
];
printl("========================================");
printl(" 裕泰茶馆的搭桥经 · text-to-speech.cn");
printl(" 音色:晓晓(微软 Azure 神经网络)");
printl("========================================");
printl("文章共 " + ARTICLE.length + " 段");
ensureDir();
var okCount = 0;
var voice = "zh-CN-XiaoxiaoNeural";
var failed = []; // 记录失败的段落
for (var i = 0; i < ARTICLE.length; i++) {
var seg = ARTICLE[i];
var savePath = WEB_CFG.cacheDir + "web_xiaoxiao_seg" + (i + 1) + ".mp3";
// 已下载的跳过
var existFile = new File(savePath);
if (existFile.exists() && existFile.length() > 512) {
printl("\n[" + (i + 1) + "/" + ARTICLE.length + "] 已存在,跳过 (" + existFile.length() + " bytes)");
okCount++;
continue;
}
printl("\n[" + (i + 1) + "/" + ARTICLE.length + "] " + seg.substring(0, 25) + "...");
if (synthSegment(seg, voice, savePath)) {
var f = new File(savePath);
printl(" ✅ " + f.length() + " bytes");
okCount++;
} else {
printl(" ❌ 合成失败,稍后重试");
failed.push(i);
}
// 段间间隔 2 秒(避免频繁请求导致 token 过期)
if (i < ARTICLE.length - 1) Thread.sleep(2000);
}
// 第二轮:重试失败的段落
if (failed.length > 0) {
printl("\n========================================");
printl(" 第二轮:重试 " + failed.length + " 个失败段落");
printl("========================================");
for (var r = 0; r < failed.length; r++) {
var idx = failed[r];
var seg = ARTICLE[idx];
var savePath = WEB_CFG.cacheDir + "web_xiaoxiao_seg" + (idx + 1) + ".mp3";
printl("\n[重试 " + (r + 1) + "/" + failed.length + "] 段落 " + (idx + 1) + ": " + seg.substring(0, 25) + "...");
// 等待 5 秒让 token 冷却
Thread.sleep(5000);
if (synthSegment(seg, voice, savePath)) {
var f = new File(savePath);
printl(" ✅ " + f.length() + " bytes");
okCount++;
} else {
printl(" ❌ 仍然失败");
}
}
}
printl("\n========================================");
printl(" 完成 (" + okCount + "/" + ARTICLE.length + " 段)");
printl(" MP3 保存在: " + WEB_CFG.cacheDir);
printl("========================================");
}
// 运行:取消注释你想执行的函数
// demo(); // 演示 4 种音色
readArticle(); // 朗读裕泰茶馆全文
复制代码
欢迎光临 自动发帖软件 (http://www.fatiegongju.com/)
Powered by Discuz! X3.2